diff --git a/README.md b/README.md index a8fe9d6..8f7ba67 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ git clone https://github.com/lingbopro/easy-github-hosts.git 2. **完成**:程序将在原始`hosts`文件恢复完成后通知您。 -## 选项 + ## TODO diff --git a/ipFetcher.js b/ipFetcher.js index 47e70dc..f70b60a 100644 --- a/ipFetcher.js +++ b/ipFetcher.js @@ -38,31 +38,32 @@ const sites = [ * @returns {Promise} - 返回网站的 IP 地址 */ async function getIP(host) { - const url = `http://ip-api.com/json/${host}?fields=status,message,query`; + let url = `http://ip-api.com/json/${host}?fields=status,message,query`; console.log(`${appName}: Getting IP for '${host}' ( ${url} )`); try { const response = await fetch(url, { method: 'GET' }); if (response.ok) { const data = await response.json(); - if (data.status === 'success') { + if (data.status == 'success') { const ip = data.query; console.log(`${appName}: Got IP for '${host}' : ${ip}`); return ip; } else { - console.error(`${appName}: ERROR - API returned an error message:\n${data.message}`); + console.log(`${appName}: ERROR - API returned an error message:\n${data.message}`); return ""; } } else { if (response.headers['X-R1'] <= 0) { - console.error(`${appName}: ERROR - The API call limit is reached`); - console.error(`${appName}: ERROR - Try again in at least ${response.headers['X-Ttl']} seconds`); + console.log(`${appName}: ERROR - The API call limit is reached`); + console.log(`${appName}: ERROR - Try again in at least ${response.headers['X-Ttl']} seconds`); throw new Error('API call limit reached'); } - console.error(`${appName}: ERROR - returned an HTTP error code:\n${response.status} (${response.statusText})`); + console.log(`${appName}: ERROR - returned an HTTP error code:\n${response.status} (${response.statusText})`); return ""; } } catch (error) { - console.error(`${appName}: ERROR - An error occurred while getting IP for '${host}' :`, error); + console.log(`${appName}: ERROR - An error occurred while getting IP for '${host}' :`); + console.error(error); return ""; } } @@ -74,19 +75,19 @@ async function getIP(host) { * @returns {Promise} - 返回包含所有网站 IP 地址的数组 */ async function getIPs(cache = true) { - const promises = sites.map(async (site) => { + let promises = sites.map(async (site) => { try { - const ip = await getIP(site); + let ip = await getIP(site); return { host: site, ip: ip }; } catch (err) { - if (err.message === 'API call limit reached') { + if (err.message == 'API call limit reached') { process.exit(1); } else { throw err; } } }); - const IPs = await Promise.all(promises); + let IPs = await Promise.all(promises); if (cache) { cacheIPs(IPs); @@ -103,19 +104,18 @@ async function getIPs(cache = true) { */ function cacheIPs(IPs) { console.log(`${appName}: Caching IPs...`); - const now = Date.now(); - const cache = { time: now, IPs: IPs }; - const json = JSON.stringify(cache); - + let now = Date.now(); + let cache = { time: now, IPs: IPs }; + let json = JSON.stringify(cache); try { - const cacheDir = path.join(__dirname, "files/cache"); - if (!fs.existsSync(cacheDir)) { - fs.mkdirSync(cacheDir, { recursive: true }); + if (!fs.existsSync(path.join(__dirname, "files/cache"))) { + fs.mkdirSync(path.join(__dirname, "files/cache"), { recursive: true }); } - fs.writeFileSync(path.join(cacheDir, "cache.json"), json); + fs.writeFileSync(path.join(__dirname, "files/cache/cache.json"), json); console.info(`${appName}: Successfully cached IPs`); } catch (error) { - console.error(`${appName}: ERROR - An error occurred while caching IPs :`, error); + console.error(`${appName}: ERROR - An error occurred while caching IPs :`); + console.error(error); } } @@ -126,28 +126,42 @@ function cacheIPs(IPs) { */ function readCache() { try { - const cachePath = path.join(__dirname, "files/cache/cache.json"); - if (fs.existsSync(cachePath)) { - const data = fs.readFileSync(cachePath); - const cache = JSON.parse(data); - const now = Date.now(); - if (now - cache.time < 60 * 60 * 3) { // 保留3小时内的缓存 - const isValidCache = cache.IPs.every(record => typeof record.host === "string" && typeof record.ip === "string"); - if (isValidCache) { + if (fs.existsSync(path.join(__dirname, "files/cache/cache.json"))) { + let data = fs.readFileSync(path.join(__dirname, "files/cache/cache.json")); + let cache = JSON.parse(data); + let now = Date.now(); + if (now - cache.time < 60 * 60 * 3) { + let flag = true; + for (let record of cache.IPs) { + if (!(typeof record.host === "string" && typeof record.ip === "string")) { + flag = false; + break; + } + } + if (flag) { return cache.IPs; } else { console.info(`${appName}: cache file corrupted, ignore it`); + return null; } } else { console.info(`${appName}: cache file expired, ignore it`); + return null; } } else { console.info(`${appName}: cache file not exist, ignore it`); + return null; } } catch (error) { - console.error(`${appName}: ERROR - An error occurred while reading cache:`, error); + console.error(`${appName}: ERROR - An error occurred while reading cache:`); + console.error(error); + return null; } - return null; +} + +function checkIPv4(IP) { + const parts = IP.split("."); + return parts.length === 4 && parts.every(part => !isNaN(part) && Number(part) >= 0 && Number(part) <= 255); } module.exports = { getIPs, readCache }; diff --git a/main.js b/main.js index dd2d553..fa6d7f6 100644 --- a/main.js +++ b/main.js @@ -7,18 +7,40 @@ const { restoreHosts } = require("./restoreHosts.js"); const appName = "Easy GitHub Hosts"; -const cmd = process.argv[2]; +function main() { + let argv = process.argv.slice(2); -(async () => { - switch (cmd) { - case "--restore": - console.log(`${appName}: Command --restore detected`); + // Filter out Node.js specific flags + argv = argv.filter(arg => !arg.startsWith('--inspect') && !arg.startsWith('--inspect-brk')); + + if (argv.length === 0) { + console.error(`${appName}: ERROR - No arguments provided.`); + console.info(`${appName}: Usage: node main.js `); + console.info(`${appName}: Commands:`); + console.info(`${appName}: - update: Update the HOSTS file with GitHub IPs`); + console.info(`${appName}: - restore: Restore the HOSTS file from backup`); + process.exit(1); + } + + const command = argv[0]; + + switch (command) { + case "update": + updateHosts(); + break; + case "restore": restoreHosts(); break; - case "--update": default: - console.log(`${appName}: Command --update detected or no command provided`); - await updateHosts(); - break; + console.error(`${appName}: ERROR - Unknown command: ${command}`); + console.info(`${appName}: Usage: node main.js `); + console.info(`${appName}: Commands:`); + console.info(`${appName}: - update: Update the HOSTS file with GitHub IPs`); + console.info(`${appName}: - restore: Restore the HOSTS file from backup`); + process.exit(1); } -})(); +} + +if (require.main === module) { + main(); +} diff --git a/package-lock.json b/package-lock.json index 5c44709..21a2e50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "easy-github-hosts", - "version": "1.4.1", + "version": "1.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "easy-github-hosts", - "version": "1.4.1", + "version": "1.4.2", "license": "MIT" } } diff --git a/package.json b/package.json index 1fc1e46..35e35d0 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,11 @@ { "name": "easy-github-hosts", - "version": "1.4.1", + "version": "1.4.2", "description": "Easy to add IP of GitHub into your HOSTS file.", "author": "lingbopro,Minemetero", "license": "MIT", "main": "main.js", "scripts": { - "test": "node main.js --debug --noedit", "start": "node main.js --update", "restore": "node main.js --restore" }, diff --git a/restoreHosts.js b/restoreHosts.js index 7be03f2..8ef1dd0 100644 --- a/restoreHosts.js +++ b/restoreHosts.js @@ -15,7 +15,7 @@ function restoreHosts() { console.log(`${appName}: Starting restoration`); // 之前为什么要用hosts路径作为文件名呢... 嗨嗨嗨,你问我? const hostsPath = os.type().includes("Windows") ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts"; - const backupPath = path.join(__dirname, 'files/backup', 'hostsfile.backup'); + const backupPath = path.join(__dirname, 'files/backup', `hostsfile.backup`); if (!fs.existsSync(backupPath)) { console.error(`${appName}: ERROR - Backup file not found: ${backupPath}`); diff --git a/updateHosts.js b/updateHosts.js index d6cf4bf..e7d0f5c 100644 --- a/updateHosts.js +++ b/updateHosts.js @@ -51,7 +51,8 @@ function parseHostsRecord(record) { * @returns {array} 行信息 */ function getLines(content) { - return content.replace(/\r\n/g, '\n').replace(/\x00/g, '').split('\n'); + content = content.replace(/\r\n/g, '\n').replace(/\x00/g, ''); + return content.split('\n'); } /** @@ -60,11 +61,10 @@ function getLines(content) { * @returns {string} - 备份文件路径 */ function createBackup(hostsPath) { - const backupPath = path.join(__dirname, 'files/backup', 'hostsfile.backup'); // 读取HOSTS文件内容 + const backupPath = path.join(__dirname, 'files/backup', `hostsfile.backup`); try { - const backupDir = path.dirname(backupPath); - if (!fs.existsSync(backupDir)) { - fs.mkdirSync(backupDir, { recursive: true }); + if (!fs.existsSync(path.join(__dirname, "files/backup"))) { + fs.mkdirSync(path.join(__dirname, "files/backup"), { recursive: true }); } fs.copyFileSync(hostsPath, backupPath); console.log(`${appName}: Created backup at ${backupPath}`); @@ -87,7 +87,12 @@ function createBackup(hostsPath) { * @returns {number} 下标 */ function findByItemProperty(array, property, find) { - return array.findIndex(item => item[property] === find); + for (let i = 0; i < array.length; i++) { + if (array[i][property] === find) { + return i; + } + } + return -1; } /** @@ -99,7 +104,7 @@ async function updateHosts() { const hostsPath = os.type().includes("Windows") ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts"; try { - const hostsContent = fs.readFileSync(hostsPath, 'utf-8'); + let hostsContent = fs.readFileSync(hostsPath, 'utf-8'); console.log(`${appName}: Successfully read HOSTS file`); const lines = getLines(hostsContent); @@ -108,80 +113,78 @@ async function updateHosts() { let IPs = []; if (!nocache) { - const cache = readCache(); - if (cache !== null) { + let cache = readCache(); + if (cache === null) { + IPs = await getIPs(); + } else { IPs = cache; - console.log(`${appName}: Read IPs from cache`); } + } else { + try { + IPs = await getIPs(!nocache); + } catch (err) { + console.error(`${appName}: ERROR - Error fetching IPs:`, err); + process.exit(1); + } } if (IPs.length === 0) { IPs = await getIPs(true); console.log(`${appName}: Read IPs from the internet`); - } - - const backupPath = createBackup(hostsPath); + } - const records = lines.map(parseHostsRecord); + if (IPs.length === 0) { + IPs = await getIPs(true); + console.log(`${appName}: Read IPs from the internet`); + } - let noModify = false; + let newHostsContent = ''; + let availableIPs = IPs.filter(ipRecord => checkIPv4(ipRecord.ip)); - records.forEach(record => { - const index = findByItemProperty(IPs, 'host', record.host); - if (index !== -1 && IPs[index].ip && checkIPv4(IPs[index].ip)) { - record.ip = IPs[index].ip; - } else if (index !== -1) { - noModify = true; + lines.forEach(line => { + let parsed = parseHostsRecord(line); + if (findByItemProperty(availableIPs, 'host', parsed.host) === -1) { + newHostsContent += line + '\n'; } }); - const newLines = records.map(record => { - if (record.ip) { - return `${record.ip} ${record.host} # ${record.description}`; - } else if (record.host) { - return `${record.host} ${record.description}`; - } else { - return `#${record.description}`; - } + availableIPs.forEach(value => { + newHostsContent += `${value.ip} ${value.host} # Easy GitHub Hosts\n`; }); - const newContent = newLines.join(os.EOL); - - if (noedit || noModify) { - if (diff) { - console.log(`${appName}: Showing diff`); - const rl = readline.createInterface({ - input: fs.createReadStream(hostsPath), - output: process.stdout, - terminal: false - }); - - rl.on('line', (line) => { - if (!newContent.includes(line)) { - console.log(`- ${line}`); - } - }); - - rl.on('close', () => { - newLines.forEach(line => { - if (!hostsContent.includes(line)) { - console.log(`+ ${line}`); - } - }); - }); - } else { - console.log(`${appName}: Update is ready but will not be performed`); - } + if (noedit) { + console.log(`${appName}: HOSTS Content:`); + console.log(newHostsContent); + if (diff) process.exit(0); } else { - fs.writeFileSync(hostsPath, newContent, 'utf-8'); - console.log(`${appName}: HOSTS file updated successfully`); - } - } catch (error) { - if (error.code === 'EPERM') { - console.error(`${appName}: ERROR - Permission denied. Please run this program as Administrator (or super user).`); - } else { - console.error(`${appName}: ERROR - An error occurred:`, error); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + rl.question(`${appName}: Are you sure you want to update the hosts file? (yes/no) `, answer => { + if (answer.toLowerCase() === 'yes') { + createBackup(hostsPath); + try { + fs.writeFileSync(hostsPath, newHostsContent, 'utf-8'); + console.log(`${appName}: Successfully updated HOSTS file`); + } catch (err) { + if (err.code === 'EPERM') { + console.error(`${appName}: ERROR - Permission denied while writing to HOSTS file. Please run this program as Administrator (or super user).`); + } else { + console.error(`${appName}: ERROR - Error writing HOSTS file:`, err); + } + process.exit(1); + } + } else { + console.log(`${appName}: Update cancelled`); + } + rl.close(); + process.exit(0); + }); } + } catch (err) { + console.error(`${appName}: ERROR - Error reading HOSTS file:`, err); process.exit(1); } } @@ -190,4 +193,4 @@ module.exports = { updateHosts }; if (require.main === module) { updateHosts(); -} \ No newline at end of file +}