Created
July 10, 2025 03:32
-
-
Save Wxh16144/23a7fdeef32074f5fa769043b741ae32 to your computer and use it in GitHub Desktop.
Chrome 扩展二分诊断工具
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const fs = require('fs'); | |
| const path = require('path'); | |
| const os = require('os'); | |
| const BACKUP_DIR = path.join(os.homedir(), 'chrome-extensions-backup'); | |
| // 获取Chrome扩展目录路径 | |
| function getChromeExtensionsPath() { | |
| const platform = os.platform(); | |
| const homedir = os.homedir(); | |
| switch (platform) { | |
| case 'win32': | |
| return path.join( | |
| process.env.LOCALAPPDATA || | |
| path.join(homedir, 'AppData', 'Local'), | |
| 'Google', 'Chrome', 'User Data', 'Default', 'Extensions' | |
| ); | |
| case 'darwin': // macOS | |
| return path.join( | |
| homedir, | |
| 'Library', 'Application Support', 'Google', 'Chrome', 'Default', 'Extensions' | |
| ); | |
| case 'linux': | |
| return path.join( | |
| homedir, | |
| '.config', 'google-chrome', 'Default', 'Extensions' | |
| ); | |
| default: | |
| throw new Error(`Unsupported platform: ${platform}`); | |
| } | |
| } | |
| // 获取所有扩展信息 | |
| async function getChromeExtensions() { | |
| const extensionsPath = getChromeExtensionsPath(); | |
| const extensions = []; | |
| console.log(`Looking for Chrome extensions in: ${extensionsPath}`); | |
| try { | |
| // 检查目录是否存在 | |
| await fs.promises.access(extensionsPath); | |
| } catch (error) { | |
| throw new Error(`Chrome extensions directory not found: ${extensionsPath}`); | |
| } | |
| // 读取所有扩展ID目录 | |
| const extensionIds = await fs.promises.readdir(extensionsPath); | |
| console.log(`Found ${extensionIds.length} extensions`); | |
| for (const extId of extensionIds) { | |
| const extPath = path.join(extensionsPath, extId); | |
| const stat = await fs.promises.stat(extPath); | |
| if (!stat.isDirectory()) continue; | |
| try { | |
| // 获取第一个版本目录 | |
| const versions = await fs.promises.readdir(extPath); | |
| if (versions.length === 0) continue; | |
| // 获取最新版本目录 | |
| const latestVersion = versions.sort().reverse()[0]; // 假设版本名是可排序的 | |
| // 读取manifest.json | |
| const manifestPath = path.join(extPath, latestVersion, 'manifest.json'); | |
| const manifestData = await fs.promises.readFile(manifestPath, 'utf8'); | |
| const manifest = JSON.parse(manifestData); | |
| // 处理本地化名称(可能包含多语言支持) | |
| let name = manifest.name; | |
| if (typeof name === 'object' && name !== null) { | |
| name = name.en || name.en_US || Object.values(name)[0] || ''; | |
| } | |
| // 处理本地化描述 | |
| let description = manifest.description || ''; | |
| if (typeof description === 'object' && description !== null) { | |
| description = description.en || description.en_US || Object.values(description)[0] || ''; | |
| } | |
| extensions.push({ | |
| id: extId, | |
| name: name, | |
| version: manifest.version, | |
| manifest_version: manifest.manifest_version, | |
| description: description, | |
| // homepage_url: manifest.homepage_url || '', | |
| // permissions: manifest.permissions || [], | |
| // icons: manifest.icons ? Object.values(manifest.icons) : [], | |
| path: path.join(extPath, latestVersion), | |
| }); | |
| } catch (error) { | |
| console.error(`Error processing extension ${extId}: ${error.message}`); | |
| } | |
| } | |
| return extensions; | |
| } | |
| // 递归复制目录 | |
| async function copyDir(src, dest) { | |
| await fs.promises.mkdir(dest, { recursive: true }); | |
| const entries = await fs.promises.readdir(src, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| const srcPath = path.join(src, entry.name); | |
| const destPath = path.join(dest, entry.name); | |
| if (entry.isDirectory()) { | |
| await copyDir(srcPath, destPath); | |
| } else { | |
| await fs.promises.copyFile(srcPath, destPath); | |
| } | |
| } | |
| } | |
| // 备份所有扩展到安全目录 | |
| async function backupAllExtensions(targetDir = BACKUP_DIR) { | |
| const extensionsPath = getChromeExtensionsPath(); | |
| await fs.promises.mkdir(targetDir, { recursive: true }); | |
| const extensionIds = await fs.promises.readdir(extensionsPath); | |
| for (const extId of extensionIds) { | |
| const extPath = path.join(extensionsPath, extId); | |
| const stat = await fs.promises.stat(extPath).catch(() => null); | |
| if (!stat || !stat.isDirectory()) continue; | |
| const destPath = path.join(targetDir, extId); | |
| await fs.promises.rm(destPath, { recursive: true, force: true }).catch(() => {}); | |
| await copyDir(extPath, destPath); | |
| } | |
| } | |
| // 写入所有扩展ID到本地文件 | |
| async function saveAllExtensionIds() { | |
| const extensionsPath = getChromeExtensionsPath(); | |
| const extensionIds = await fs.promises.readdir(extensionsPath); | |
| const outputPath = path.join(process.cwd(), 'all_extension_ids.json'); | |
| await fs.promises.writeFile(outputPath, JSON.stringify(extensionIds, null, 2)); | |
| console.log(`All extension IDs saved to: ${outputPath}`); | |
| } | |
| // 二分指定ID文件,生成两个新文件 | |
| async function splitIds(inputFile) { | |
| const ids = JSON.parse(await fs.promises.readFile(inputFile, 'utf8')); | |
| const mid = Math.floor(ids.length / 2); | |
| const first = ids.slice(0, mid); | |
| const second = ids.slice(mid); | |
| const base = path.basename(inputFile, '.json'); | |
| const dir = path.dirname(inputFile); | |
| const out1 = path.join(dir, `${base}_1.json`); | |
| const out2 = path.join(dir, `${base}_2.json`); | |
| await fs.promises.writeFile(out1, JSON.stringify(first, null, 2)); | |
| await fs.promises.writeFile(out2, JSON.stringify(second, null, 2)); | |
| console.log(`Split ${inputFile} into:`); | |
| console.log(` ${out1} (${first.length} ids)`); | |
| console.log(` ${out2} (${second.length} ids)`); | |
| } | |
| // 清空并恢复指定ID的扩展到Chrome目录 | |
| async function restoreExtensionsFromIdsFile(idsFile) { | |
| const ids = JSON.parse(await fs.promises.readFile(idsFile, 'utf8')); | |
| const chromeDir = getChromeExtensionsPath(); | |
| // 清空Chrome扩展目录 | |
| const oldIds = await fs.promises.readdir(chromeDir); | |
| for (const extId of oldIds) { | |
| const extPath = path.join(chromeDir, extId); | |
| await fs.promises.rm(extPath, { recursive: true, force: true }).catch(() => {}); | |
| } | |
| // 复制所需扩展 | |
| for (const extId of ids) { | |
| const src = path.join(BACKUP_DIR, extId); | |
| const stat = await fs.promises.stat(src).catch(() => null); | |
| if (!stat || !stat.isDirectory()) continue; | |
| const dest = path.join(chromeDir, extId); | |
| await copyDir(src, dest); | |
| } | |
| console.log(`Restored ${ids.length} extensions from ${idsFile} to Chrome extensions directory.`); | |
| } | |
| // 主函数 | |
| async function main() { | |
| try { | |
| const args = process.argv.slice(2); | |
| if (args.includes('--backup')) { | |
| await backupAllExtensions(); | |
| console.log(`All Chrome extensions have been backed up to: ${BACKUP_DIR}`); | |
| return; | |
| } | |
| if (args.includes('--save-ids')) { | |
| await saveAllExtensionIds(); | |
| return; | |
| } | |
| if (args.includes('--split-ids')) { | |
| const idx = args.indexOf('--split-ids'); | |
| const inputFile = args[idx + 1]; | |
| if (!inputFile) { | |
| console.error('Please provide the input file, e.g. --split-ids all_extension_ids.json'); | |
| return; | |
| } | |
| await splitIds(inputFile); | |
| return; | |
| } | |
| if (args.includes('--restore-ids')) { | |
| const idx = args.indexOf('--restore-ids'); | |
| const idsFile = args[idx + 1]; | |
| if (!idsFile) { | |
| console.error('Please provide the ids file, e.g. --restore-ids all_extension_ids_2.json'); | |
| return; | |
| } | |
| await restoreExtensionsFromIdsFile(idsFile); | |
| return; | |
| } | |
| console.log('Scanning Chrome extensions...'); | |
| const extensions = await getChromeExtensions(); | |
| const result = { | |
| timestamp: new Date().toISOString(), | |
| browser: 'Google Chrome', | |
| profile: 'Default', | |
| extensions_count: extensions.length, | |
| extensions: extensions | |
| }; | |
| // 保存为JSON文件 | |
| const outputPath = path.join(process.cwd(), 'chrome_extensions.json'); | |
| await fs.promises.writeFile(outputPath, JSON.stringify(result, null, 2)); | |
| console.log(`\n✅ Successfully found ${extensions.length} extensions`); | |
| console.log(`📄 Results saved to: ${outputPath}`); | |
| } catch (error) { | |
| console.error(`❌ Error: ${error.message}`); | |
| console.log('Possible solutions:'); | |
| console.log('1. Ensure Chrome is installed on your system'); | |
| console.log('2. If using a non-default profile, modify the script'); | |
| console.log('3. Run the script with appropriate permissions'); | |
| process.exit(1); | |
| } | |
| } | |
| // 执行主函数 | |
| main(); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
chrome-ext-bisect
安装
将
chrome-ext-bisect.js文件放到任意目录下, 随后用 node 执行即可Important
首次使用前,务必先执行备份命令,防止数据丢失!
命令说明
1. 备份所有扩展(强烈推荐第一步)
~/chrome-extensions-backup目录。2. 导出所有扩展 ID
all_extension_ids.json。3. 二分扩展 ID 列表
all_extension_ids_1.json、all_extension_ids_2.json)。4. 恢复指定扩展集合到 Chrome 目录
推荐诊断流程
node chrome-ext-bisect.js --backupnode chrome-ext-bisect.js --save-idsnode chrome-ext-bisect.js --split-ids all_extension_ids.json--restore-ids恢复某一半扩展,重启 Chrome 观察问题注意事项
~/chrome-extensions-backup,如需更改请修改源码常量。