From 9761dc9cc072ea5ded6b0fd089c04c2935952851 Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 28 Aug 2025 08:08:27 +0800 Subject: [PATCH] Add Firefox support for extension --- Firefox_Installation_Guide.md | 56 +++++++++ manifest.json | 28 ++--- src/html/popup/popup.js | 87 ++++++++----- src/scripts/background.js | 224 +++++++++++++++++---------------- src/scripts/dataAna.js | 14 ++- src/scripts/genius.js | 60 ++++----- src/scripts/search.js | 2 +- src/scripts/utils.js | 2 +- src/scripts/voteup.js | 54 ++++---- src/scripts/webext-polyfill.js | 31 +++++ 10 files changed, 336 insertions(+), 222 deletions(-) create mode 100644 Firefox_Installation_Guide.md create mode 100644 src/scripts/webext-polyfill.js diff --git a/Firefox_Installation_Guide.md b/Firefox_Installation_Guide.md new file mode 100644 index 0000000..b6e5743 --- /dev/null +++ b/Firefox_Installation_Guide.md @@ -0,0 +1,56 @@ +# WorldQuant Scope - Firefox 版本安装指南 + +这是从 Chrome 扩展转换而来的 Firefox 版本。 + +## 安装步骤 + +### 当前仅支持临时安装(开发模式) +1. 打开 Firefox 浏览器 +2. 在地址栏输入 `about:debugging` +3. 点击左侧的 "This Firefox" +4. 点击 "Load Temporary Add-on..." 按钮 +5. 选择此文件夹中的 `manifest.json` 文件 +6. 扩展将被临时安装(重启 Firefox 后会被移除) + +## 主要变更 + +### 1. Manifest 更改 +- `manifest_version`: 3 → 2 (Firefox 兼容) +- `action` → `browser_action` +- `service_worker` → `scripts` 数组 +- 简化了 `web_accessible_resources` 格式 +- 移除了 `host_permissions` + +### 2. API 兼容性 +- 所有 `chrome.*` API 调用都已更新为使用 `browserAPI` +- 自动检测环境并使用正确的 API (`browser` 或 `chrome`) +- `chrome.scripting` API 替换为 `tabs.executeScript` 和 `tabs.insertCSS` + +### 3. 脚本注入更改 +- 使用 Firefox 兼容的脚本注入方法 +- 顺序注入脚本文件以确保依赖关系 +- 使用代码字符串注入替代函数参数注入 + +## 功能验证 + +安装后请验证以下功能: +- [ ] 扩展图标显示在工具栏 +- [ ] 弹出窗口正常打开 +- [ ] 设置保存和加载正常 +- [ ] 在 WorldQuant 平台页面正常工作 +- [ ] 数据分析功能正常 +- [ ] Genius 页面增强功能正常 + +## 故障排除 + +如遇到问题: +1. 打开 Firefox 开发者工具 (F12) +2. 查看控制台错误信息 +3. 检查扩展是否在 about:addons 中正常启用 +4. 确认网站权限设置正确 + +## 注意事项 + +- Firefox 版本可能在某些 API 调用上与 Chrome 版本有细微差异 +- 建议在安装前备份浏览器数据 +- 如需更新,需重新进行临时安装过程 \ No newline at end of file diff --git a/manifest.json b/manifest.json index e0c1a61..621e7ce 100644 --- a/manifest.json +++ b/manifest.json @@ -1,5 +1,5 @@ { - "manifest_version": 3, + "manifest_version": 2, "name": "WorldQuant Scope", "description": "WorldQuant Scope is a browser extension that provides a set of tools to help you navigate the WorldQuant platform.", "version": "0.9.3000", @@ -7,14 +7,14 @@ "icons": { "128": "img/logo.png" }, - "action": { + "browser_action": { "default_title": "WorldQuant Scope - Tools for WorldQuant Platform", "default_popup": "src/html/popup/popup.html", "default_icon": "img/logo.png" }, "background": { - "service_worker": "src/scripts/background.js", - "type": "module" + "scripts": ["src/scripts/background.js"], + "persistent": false }, "content_scripts": [ { @@ -52,23 +52,13 @@ } ], "web_accessible_resources": [ - { - "resources": [ - "data/*.bin", - "data/*.json", - "src/css/*.css", - "src/scripts/lib/*.js" - ], - "matches": [ - "*://platform.worldquantbrain.com/*" - ] - } - ], - "host_permissions": [ - "https://*/*" + "data/*.bin", + "data/*.json", + "src/css/*.css", + "src/scripts/lib/*.js" ], "permissions": [ - "scripting", + "https://*/*", "cookies", "tabs", "storage", diff --git a/src/html/popup/popup.js b/src/html/popup/popup.js index cad677a..15e15c1 100644 --- a/src/html/popup/popup.js +++ b/src/html/popup/popup.js @@ -1,6 +1,10 @@ // Description: 弹出窗口的 JS 文件 console.log('popup.js loaded'); +// 检查依赖库是否加载 +console.log('msgpack available:', typeof msgpack !== 'undefined'); +console.log('pako available:', typeof pako !== 'undefined'); + // 获取 HTML 元素 const dbAddressInput = document.getElementById('dbAddress'); const hiddenFeatureCheckbox = document.getElementById('hiddenFeature'); @@ -18,7 +22,7 @@ const importCommunityFile = document.getElementById('importCommunityFile'); // 加载用户设置 function loadSettings() { statusText.textContent = '加载中...'; - chrome.storage.local.get('WQPSettings', ({ WQPSettings }) => { + browser.storage.local.get('WQPSettings').then(({ WQPSettings }) => { dbAddressInput.value = WQPSettings.apiAddress || ''; hiddenFeatureCheckbox.checked = WQPSettings.hiddenFeatureEnabled || false; dataAnalysisCheckbox.checked = WQPSettings.dataAnalysisEnabled || false; @@ -27,6 +31,8 @@ function loadSettings() { saveBtn.disabled = !dbAddressInput.value.trim(); statusText.textContent = ''; + }).catch(error => { + showStatusMessage('加载设置失败!', false); }); } @@ -47,17 +53,15 @@ function saveSettings(event) { saveBtn.disabled = false; return; } - chrome.storage.local.set({ WQPSettings }, () => { - if (chrome.runtime.lastError) { - showStatusMessage('保存失败,请重试!', false); + browser.storage.local.set({ WQPSettings }).then(() => { + showStatusMessage('设置已保存!', true); + setTimeout(() => { + statusText.textContent = ''; saveBtn.disabled = false; - } else { - showStatusMessage('设置已保存!', true); - setTimeout(() => { - statusText.textContent = ''; - saveBtn.disabled = false; - }, 2000); - } + }, 2000); + }).catch(error => { + showStatusMessage('保存失败,请重试!', false); + saveBtn.disabled = false; }); } @@ -111,7 +115,7 @@ function formatNow() { function handleExportCommunity() { statusText.textContent = '导出中...'; - chrome.storage.local.get('WQPCommunityState', ({ WQPCommunityState }) => { + browser.storage.local.get('WQPCommunityState').then(({ WQPCommunityState }) => { try { if (!WQPCommunityState) { showStatusMessage('没有可导出的社区数据。', false); @@ -124,12 +128,25 @@ function handleExportCommunity() { console.error(e); showStatusMessage('导出失败。', false); } + }).catch(error => { + showStatusMessage('获取数据失败。', false); }); } function handleExportCommunityCompressed() { statusText.textContent = '导出(压缩)中...'; - chrome.storage.local.get('WQPCommunityState', ({ WQPCommunityState }) => { + + // 检查依赖库 + if (typeof msgpack === 'undefined') { + showStatusMessage('msgpack库未加载,无法压缩导出。', false); + return; + } + if (typeof pako === 'undefined') { + showStatusMessage('pako库未加载,无法压缩导出。', false); + return; + } + + browser.storage.local.get('WQPCommunityState').then(({ WQPCommunityState }) => { try { if (!WQPCommunityState) { showStatusMessage('没有可导出的社区数据。', false); @@ -141,9 +158,12 @@ function handleExportCommunityCompressed() { downloadBytes(`WQPCommunityState_${formatNow()}.wqcs`, deflated, 'application/octet-stream'); showStatusMessage('压缩导出完成。', true); } catch (e) { - console.error(e); - showStatusMessage('压缩导出失败。', false); + console.error('压缩导出错误:', e); + showStatusMessage(`压缩导出失败: ${e.message}`, false); } + }).catch(error => { + console.error('获取数据错误:', error); + showStatusMessage('获取数据失败。', false); }); } @@ -157,6 +177,19 @@ function handleImportFileChange(evt) { if (!file) return; statusText.textContent = '导入中...'; const isCompressed = /\.wqcs$/i.test(file.name); + + // 如果是压缩文件,检查依赖库 + if (isCompressed) { + if (typeof msgpack === 'undefined') { + showStatusMessage('msgpack库未加载,无法导入压缩文件。', false); + return; + } + if (typeof pako === 'undefined') { + showStatusMessage('pako库未加载,无法导入压缩文件。', false); + return; + } + } + const reader = new FileReader(); if (isCompressed) { reader.onload = () => { @@ -164,16 +197,14 @@ function handleImportFileChange(evt) { const arr = new Uint8Array(reader.result); const inflated = pako.inflate(arr); const obj = msgpack.decode(inflated); - chrome.storage.local.set({ WQPCommunityState: obj }, () => { - if (chrome.runtime.lastError) { - showStatusMessage('写入存储失败。', false); - } else { - showStatusMessage('导入成功。', true); - } + browser.storage.local.set({ WQPCommunityState: obj }).then(() => { + showStatusMessage('导入成功。', true); + }).catch(error => { + showStatusMessage('写入存储失败。', false); }); } catch (e) { - console.error(e); - showStatusMessage('导入失败:压缩内容无法解析。', false); + console.error('导入压缩文件错误:', e); + showStatusMessage(`导入失败:${e.message}`, false); } }; reader.onerror = () => showStatusMessage('读取文件失败。', false); @@ -182,12 +213,10 @@ function handleImportFileChange(evt) { reader.onload = () => { try { const obj = JSON.parse(reader.result); - chrome.storage.local.set({ WQPCommunityState: obj }, () => { - if (chrome.runtime.lastError) { - showStatusMessage('写入存储失败。', false); - } else { - showStatusMessage('导入成功。', true); - } + browser.storage.local.set({ WQPCommunityState: obj }).then(() => { + showStatusMessage('导入成功。', true); + }).catch(error => { + showStatusMessage('写入存储失败。', false); }); } catch (e) { console.error(e); diff --git a/src/scripts/background.js b/src/scripts/background.js index 5ceaecb..ca53bbc 100644 --- a/src/scripts/background.js +++ b/src/scripts/background.js @@ -1,7 +1,7 @@ // background.js: 后台脚本,用于监听浏览器事件,如标签页更新、插件安装等,以及与content scripts和popup交互 console.log('Background script is running.'); -const dataSetListUrl = chrome.runtime.getURL(`data/dataSetList.json`); +const dataSetListUrl = browser.runtime.getURL(`data/dataSetList.json`); let dataSetList = null; // 定义全局变量 const REPO_OWNER = "zhangkaihua88"; const REPO_NAME = "WebDataScope"; @@ -10,8 +10,9 @@ const CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24小时检查一次 // 初始化设置 -chrome.runtime.onInstalled.addListener(async () => { - chrome.storage.local.get('WQPSettings', ({ WQPSettings }) => { +browser.runtime.onInstalled.addListener(async () => { + try { + const { WQPSettings } = await browser.storage.local.get('WQPSettings'); // 如果没有找到 WQPSettings,则设置默认值 if (!WQPSettings) { const defaultSettings = { @@ -22,28 +23,30 @@ chrome.runtime.onInstalled.addListener(async () => { geniusCombineTag: true, }; - // 将默认设置保存到 Chrome 存储中 - chrome.storage.local.set({ WQPSettings: defaultSettings }, () => { - console.log('Default settings have been applied.'); - }); + // 将默认设置保存到存储中 + await browser.storage.local.set({ WQPSettings: defaultSettings }); + console.log('Default settings have been applied.'); } else { console.log('Current settings:', WQPSettings); } - }); - // 获取数据集列表 - dataSetList = await getDataSetList(); - checkUpdate(); + + // 获取数据集列表 + dataSetList = await getDataSetList(); + checkUpdate(); + } catch (error) { + console.error('Initialization failed:', error); + } }); // 设置定时器,每天检查一次更新 -chrome.runtime.onStartup.addListener(() => { +browser.runtime.onStartup.addListener(() => { checkUpdate(); }); // 监听标签页更新事件 -chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { +browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if (changeInfo.status === 'complete' && tab.url == "https://platform.worldquantbrain.com/alphas/distribution") { injectionDistributionScript(tabId); } else if (changeInfo.status === 'complete' && tab.url.startsWith("https://platform.worldquantbrain.com/genius")) { @@ -81,31 +84,30 @@ async function checkUpdate() { const today = new Date().toISOString().split('T')[0]; // 获取当前日期 (YYYY-MM-DD) // 读取存储的上次提醒日期 - chrome.storage.local.get('lastNotifyDate', async ({ lastNotifyDate }) => { - console.log('上次提醒日期:', lastNotifyDate); - if (lastNotifyDate === today) { - console.log('今天已经提醒过,无需重复提醒'); - return; - } - console.log('今天尚未提醒过,开始检查更新'); - - // 获取 GitHub 上的最新版本 - const response = await fetch( - `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest` - ); - const data = await response.json(); - const latestVersion = data.tag_name.replace(/^v/, ''); // 去除可能的v前缀 - const currentVersion = chrome.runtime.getManifest().version; - console.log('最新版本:', latestVersion, '当前版本:', currentVersion); - - // 版本对比 - if (compareVersions(latestVersion, currentVersion) > 0) { - showNotification(latestVersion, `https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${latestVersion}.zip`); - - // 记录今天已经提醒过 - chrome.storage.local.set({ lastNotifyDate: today }); - } - }); + const { lastNotifyDate } = await browser.storage.local.get('lastNotifyDate'); + console.log('上次提醒日期:', lastNotifyDate); + if (lastNotifyDate === today) { + console.log('今天已经提醒过,无需重复提醒'); + return; + } + console.log('今天尚未提醒过,开始检查更新'); + + // 获取 GitHub 上的最新版本 + const response = await fetch( + `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest` + ); + const data = await response.json(); + const latestVersion = data.tag_name.replace(/^v/, ''); // 去除可能的v前缀 + const currentVersion = browser.runtime.getManifest().version; + console.log('最新版本:', latestVersion, '当前版本:', currentVersion); + + // 版本对比 + if (compareVersions(latestVersion, currentVersion) > 0) { + showNotification(latestVersion, `https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${latestVersion}.zip`); + + // 记录今天已经提醒过 + await browser.storage.local.set({ lastNotifyDate: today }); + } } catch (error) { console.error('检查更新失败:', error); @@ -114,14 +116,14 @@ async function checkUpdate() { // 显示通知 function showNotification(version, url) { - chrome.notifications.create({ + browser.notifications.create({ type: 'basic', - iconUrl: chrome.runtime.getURL('img/logo-128.png'), // 使用插件内的图片 + iconUrl: browser.runtime.getURL('img/logo-128.png'), // 使用插件内的图片 title: '发现新版本(WorldQuant Scope插件)', message: `点击下载 ${version}`, - }, () => { - chrome.notifications.onClicked.addListener(() => { - chrome.tabs.create({ url }); + }).then(() => { + browser.notifications.onClicked.addListener(() => { + browser.tabs.create({ url }); }); }); } @@ -139,15 +141,19 @@ async function getDataSetList() { } // 注入分布图脚本 -function injectionDistributionScript(tabId) { +async function injectionDistributionScript(tabId) { try { - chrome.scripting.insertCSS({ - target: { tabId: tabId }, - files: ['src/css/distribution.css'], + await browser.tabs.insertCSS(tabId, { + file: 'src/css/distribution.css', + }); + await browser.tabs.executeScript(tabId, { + file: 'src/scripts/lib/chart.js' + }); + await browser.tabs.executeScript(tabId, { + file: 'src/scripts/utils.js' }); - chrome.scripting.executeScript({ - target: { tabId: tabId }, - files: ['src/scripts/lib/chart.js', "src/scripts/utils.js", 'src/scripts/distribution.js'], + await browser.tabs.executeScript(tabId, { + file: 'src/scripts/distribution.js' }); } catch (error) { console.error('Script injection failed: ', error); @@ -159,17 +165,15 @@ async function injectionDataFlagScript(tabId, tab) { dataSetList = await getDataSetList(); } try { - chrome.scripting.executeScript({ - target: { tabId: tabId }, - files: ["src/scripts/utils.js", 'src/scripts/dataFlag.js'], - }, () => { - chrome.scripting.executeScript({ - target: { tabId }, - args: [dataSetList, tab.url], - func: (...args) => dataFlagFunc(...args), - }); + await browser.tabs.executeScript(tabId, { + file: "src/scripts/utils.js" + }); + await browser.tabs.executeScript(tabId, { + file: 'src/scripts/dataFlag.js' + }); + await browser.tabs.executeScript(tabId, { + code: `dataFlagFunc(${JSON.stringify(dataSetList)}, ${JSON.stringify(tab.url)})` }); - } catch (error) { console.error('Script injection failed: ', error); @@ -177,63 +181,67 @@ async function injectionDataFlagScript(tabId, tab) { } // 注入 Genius 脚本 -function injectionGeniusScript(tabId) { +async function injectionGeniusScript(tabId) { try { // 注入 CSS 文件 - chrome.scripting.insertCSS({ - target: { tabId: tabId }, - files: [ - "src/css/genius.css", - "src/css/idcard.css", - "src/css/dataTables.dataTables.css", - "src/css/columnControl.dataTables.min.css", - "src/css/responsive.dataTables.min.css", - "src/css/buttons.dataTables.min.css", - ], - }, () => { - if (chrome.runtime.lastError) { - console.error("CSS注入失败", chrome.runtime.lastError.message); - } else { - console.log("CSS注入成功"); + const cssFiles = [ + "src/css/genius.css", + "src/css/idcard.css", + "src/css/dataTables.dataTables.css", + "src/css/columnControl.dataTables.min.css", + "src/css/responsive.dataTables.min.css", + "src/css/buttons.dataTables.min.css", + ]; + + // 顺序注入CSS文件 + for (const cssFile of cssFiles) { + try { + await browser.tabs.insertCSS(tabId, { file: cssFile }); + } catch (error) { + console.error("CSS注入失败", cssFile, error); } - }); + } // 检查是否已经注入了js脚本 - chrome.scripting.executeScript({ - target: { tabId: tabId }, - func: () => { - return typeof OptUrl !== 'undefined'; - }, - }, (results) => { - if (!results || !results[0].result) { + try { + const results = await browser.tabs.executeScript(tabId, { + code: "typeof OptUrl !== 'undefined';" + }); + + if (!results || !results[0]) { // 如果 OptUrl 未定义,则注入脚本 - chrome.scripting.executeScript({ - target: { tabId: tabId }, - files: [ - "src/scripts/lib/jquery-3.7.0.min.js", - "src/scripts/lib/jquery.dataTables.min.js", - "src/scripts/lib/dataTables.columnControl.min.js", - "src/scripts/lib/columnControl.dataTables.min.js", - "src/scripts/lib/dataTables.responsive.min.js", - "src/scripts/lib/responsive.dataTables.min.js", - "src/scripts/lib/dataTables.buttons.min.js", - "src/scripts/lib/buttons.colVis.min.js", - "src/scripts/lib/buttons.html5.min.js", - "src/scripts/utils.js", - "src/scripts/uiCard.js", - "src/scripts/genius.js"], - }); - } - else { + const jsFiles = [ + "src/scripts/lib/jquery-3.7.0.min.js", + "src/scripts/lib/jquery.dataTables.min.js", + "src/scripts/lib/dataTables.columnControl.min.js", + "src/scripts/lib/columnControl.dataTables.min.js", + "src/scripts/lib/dataTables.responsive.min.js", + "src/scripts/lib/responsive.dataTables.min.js", + "src/scripts/lib/dataTables.buttons.min.js", + "src/scripts/lib/buttons.colVis.min.js", + "src/scripts/lib/buttons.html5.min.js", + "src/scripts/utils.js", + "src/scripts/uiCard.js", + "src/scripts/genius.js" + ]; + + // 顺序注入JavaScript文件 + for (const jsFile of jsFiles) { + try { + await browser.tabs.executeScript(tabId, { file: jsFile }); + } catch (error) { + console.error('Script injection failed for:', jsFile, error); + } + } + } else { // 如果 OptUrl 已定义,则直接注入数据 - chrome.scripting.executeScript({ - target: { tabId: tabId }, - args: [], - func: (...args) => watchForElementAndInsertButton(...args), + await browser.tabs.executeScript(tabId, { + code: "if (typeof watchForElementAndInsertButton === 'function') { watchForElementAndInsertButton(); }" }); } - }); - console.log(tabId.url); + } catch (error) { + console.error('Script check failed: ', error); + } } catch (error) { console.error('Script injection failed: ', error); } diff --git a/src/scripts/dataAna.js b/src/scripts/dataAna.js index 6ac3081..31443ea 100644 --- a/src/scripts/dataAna.js +++ b/src/scripts/dataAna.js @@ -74,7 +74,7 @@ async function fetchDataDetails(fileName, dataFieldData) { // 获取数据集列表 if (!cacheData['dataSetList']) { - const response = await fetch(chrome.runtime.getURL(`data/dataSetList.json`)); + const response = await fetch(browser.runtime.getURL(`data/dataSetList.json`)); cacheData['dataSetList'] = await response.json(); } const dataSetList = cacheData['dataSetList']; @@ -102,7 +102,7 @@ async function fetchDataDetails(fileName, dataFieldData) { } // 从文件中获取数据 - let url = chrome.runtime.getURL(`data/${fileName}.bin`); + let url = browser.runtime.getURL(`data/${fileName}.bin`); try { const response = await fetch(url); const arrayBuffer = await response.arrayBuffer(); @@ -293,8 +293,9 @@ async function updateCardInfo(dataId, data, updateDataCallback) { async function showDataCard(event) { // 显示数据卡片 - chrome.storage.local.get('WQPSettings', async ({ WQPSettings }) => { - if (WQPSettings.dataAnalysisEnabled) { + try { + const { WQPSettings } = await browser.storage.local.get('WQPSettings'); + if (WQPSettings && WQPSettings.dataAnalysisEnabled) { const { dataFieldHtml, dataFieldUrl } = getDataFieldId(event.target); if (dataFieldUrl) { const data = getDataFieldData(dataFieldHtml, dataFieldUrl); @@ -319,7 +320,10 @@ async function showDataCard(event) { } card.disable(); return; - }); + } catch (error) { + console.error('Error in showDataCard:', error); + card.disable(); + } } diff --git a/src/scripts/genius.js b/src/scripts/genius.js index 4e59cad..ecc958f 100644 --- a/src/scripts/genius.js +++ b/src/scripts/genius.js @@ -97,19 +97,23 @@ async function opsAna() { array: operators, timestamp: currentTime }; - chrome.storage.local.set({ WQPOPSAna: dataToSave }, function () { + try { + await browser.storage.local.set({ WQPOPSAna: dataToSave }); console.log('数据已保存'); console.log(dataToSave); - }); + } catch (error) { + console.error('保存数据失败:', error); + } insertOpsTable(); setButtonState('WQPOPSFetchButton', `运算符分析完成${data.length}`, 'enable'); } -function insertOpsTable() { +async function insertOpsTable() { // 插入运算符分析的表格, button 插入表格的调用函数 - chrome.storage.local.get('WQPOPSAna', function (result) { + try { + const result = await browser.storage.local.get('WQPOPSAna'); if (result.WQPOPSAna) { console.log('读取的数据:', result.WQPOPSAna); let savedArray = result.WQPOPSAna.array; @@ -169,7 +173,9 @@ function insertOpsTable() { } else { console.log('没有找到保存的数据'); } - }); + } catch (error) { + console.error('读取数据失败:', error); + } } // 工具函數,提供inertOpsTable使用 function generateOperatorTable(savedTimestamp, nonZeroCount, zeroCount, savedArray) { @@ -260,12 +266,8 @@ function determineUserLevel(userData, geniusCombineTag) { async function getAllRank() { // 根据用户ID获取单个用户的排名信息 - return new Promise((resolve, reject) => { - chrome.storage.local.get(['WQPRankData', 'WQPSettings'], function ({ WQPRankData, WQPSettings }) { - if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError); - return; - } + try { + const { WQPRankData, WQPSettings } = await browser.storage.local.get(['WQPRankData', 'WQPSettings']); let data = WQPRankData?.array || []; const savedTimestamp = WQPRankData?.timestamp || 'N/A'; @@ -385,9 +387,10 @@ async function getAllRank() { - resolve({ data, savedTimestamp }); - }); - }); + return { data, savedTimestamp }; + } catch (error) { + throw error; + } } @@ -716,21 +719,17 @@ async function insertRankListInfo() { async function getSingleRankByUserId(userId) { // 根据用户ID获取单个用户的排名信息 - return new Promise((resolve, reject) => { - chrome.storage.local.get(['WQPRankData', 'WQPSettings'], function ({ WQPRankData, WQPSettings }) { - if (chrome.runtime.lastError) { - reject(chrome.runtime.lastError); - return; - } + try { + const { WQPRankData, WQPSettings } = await browser.storage.local.get(['WQPRankData', 'WQPSettings']); const data = WQPRankData?.array || []; const savedTimestamp = WQPRankData?.timestamp || 'N/A'; - calculateRanks(data, userId, WQPSettings) - .then(result => resolve({ result, savedTimestamp })) - .catch(reject); - }); - }); + const result = await calculateRanks(data, userId, WQPSettings); + return { result, savedTimestamp }; + } catch (error) { + throw error; + } } async function calculateRanks(data, userId, WQPSettings) { @@ -989,9 +988,7 @@ function bindRankEditEvents(userId, savedTimestamp) { async function updateUserRankings(userId, newData) { // 获取所有用户数据 - const { WQPRankData, WQPSettings } = await new Promise(resolve => { - chrome.storage.local.get(['WQPRankData', 'WQPSettings'], resolve); - }); + const { WQPRankData, WQPSettings } = await browser.storage.local.get(['WQPRankData', 'WQPSettings']); if (!WQPRankData || !WQPRankData.array) { throw new Error('No rank data available'); @@ -1074,10 +1071,13 @@ async function rankAna() { array: data, timestamp: currentTime }; - chrome.storage.local.set({ WQPRankData: dataToSave }, function () { + try { + await browser.storage.local.set({ WQPRankData: dataToSave }); console.log('数据已保存'); console.log(dataToSave); - }); + } catch (error) { + console.error('保存数据失败:', error); + } setButtonState('WQPRankFetchButton', `排名分析完成`, 'disable'); insertMyRankInfo(); } diff --git a/src/scripts/search.js b/src/scripts/search.js index f64243b..738d588 100644 --- a/src/scripts/search.js +++ b/src/scripts/search.js @@ -49,7 +49,7 @@ // button.innerText = '加载中...'; // button.disabled = true; // 禁用按钮,防止多次点击 // // 获取存储的 API 地址 -// chrome.storage.local.get(['WQPSettings'], ({ WQPSettings }) => { +// browser.storage.local.get(['WQPSettings'], ({ WQPSettings }) => { // const WQPApiAddress = WQPSettings.apiAddress; // if (WQPApiAddress) { // // 拼接请求 URL diff --git a/src/scripts/utils.js b/src/scripts/utils.js index 94969c1..81f6162 100644 --- a/src/scripts/utils.js +++ b/src/scripts/utils.js @@ -128,7 +128,7 @@ function waitForElement(selector, nonselector) { -async function getAuth(){n=5||0;return new Promise((r,j)=>{chrome.storage.local.get('WQPSummary',async({WQPSummary:a})=>{let d=a;try{if(!a){d=await getDataFromUrl("https://api.worldquantbrain.com/users/self/consultant/summary"),chrome.storage.local.set({WQPSummary:d},()=>{})}}catch(e){if(n<3)return getAuth(n+1).then(r).catch(j);return j(e)}r(["CN","HK"].includes(d?.leaderboard?.country))})})} +async function getAuth(){n=5||0;return new Promise((r,j)=>{browser.storage.local.get('WQPSummary').then(async({WQPSummary:a})=>{let d=a;try{if(!a){d=await getDataFromUrl("https://api.worldquantbrain.com/users/self/consultant/summary");await browser.storage.local.set({WQPSummary:d})}}catch(e){if(n<3)return getAuth(n+1).then(r).catch(j);return j(e)}r(["CN","HK"].includes(d?.leaderboard?.country))})})} function removeComments(code) { diff --git a/src/scripts/voteup.js b/src/scripts/voteup.js index 0355879..c9bb0db 100644 --- a/src/scripts/voteup.js +++ b/src/scripts/voteup.js @@ -12,17 +12,14 @@ const COMMUNITY_CONCURRENCY = 10; // 正文抓取并发上限 // ############################## 通用函数 ############################## async function getLikedIds() { - return new Promise((resolve) => { - chrome.storage.local.get(['WQPLikedIds'], function ({ WQPLikedIds }) { - resolve(WQPLikedIds || []); - }); - }); + const { WQPLikedIds } = await browser.storage.local.get(['WQPLikedIds']); + return WQPLikedIds || []; } async function saveLikedId(id) { let WQPLikedIds = await getLikedIds(); if (!WQPLikedIds.includes(id)) { WQPLikedIds.push(id); - await chrome.storage.local.set({ WQPLikedIds }); + await browser.storage.local.set({ WQPLikedIds }); } } @@ -713,10 +710,9 @@ function setCommunityProgress(current, total) { // 读取本地存储的工具函数 -function getFromStorage(key) { - return new Promise((resolve) => { - chrome.storage.local.get([key], (res) => resolve(res[key])); - }); +async function getFromStorage(key) { + const result = await browser.storage.local.get([key]); + return result[key]; } // 按页抓取评论(用于增量抓取,从 startPage 到 endPage) @@ -827,7 +823,7 @@ async function crawlCommunityFullAll(opts = {}) { } // 2) 读取旧状态,若无则初始化 - const setToStorage = (obj) => new Promise(resolve => chrome.storage.local.set(obj, resolve)); + const setToStorage = async (obj) => await browser.storage.local.set(obj); const prevState = await getFromStorage('WQPCommunityState'); const state = prevState && typeof prevState === 'object' && prevState.byCommunity ? prevState @@ -1016,7 +1012,7 @@ async function crawlRecentActivitiesIncremental(opts = {}) { const onProgress = typeof opts.onProgress === 'function' ? opts.onProgress : (msg) => updateProgress(msg); // 读取旧状态 - const setToStorage = (obj) => new Promise(resolve => chrome.storage.local.set(obj, resolve)); + const setToStorage = async (obj) => await browser.storage.local.set(obj); const prevState = await getFromStorage('WQPCommunityState'); const state = prevState && typeof prevState === 'object' && prevState.byCommunity ? prevState @@ -1368,7 +1364,7 @@ async function crawlCategoryFullAll() { const state = prevState && typeof prevState === 'object' ? prevState : {}; // 本次运行不使用之前的 byCategory,始终重置 state.byCategory = {}; - const setToStorage = (obj) => new Promise(resolve => chrome.storage.local.set(obj, resolve)); + const setToStorage = async (obj) => await browser.storage.local.set(obj); // 立即持久化一次清空后的结构,避免旧数据残留 await setToStorage({ WQPCommunityState: state }); @@ -1549,7 +1545,9 @@ function createStartMenu() { clearButton.setAttribute("id", "clearLikedIdsButton"); clearButton.innerText = "清空点赞记录"; clearButton.className = "egg_study_btn egg_menu"; - clearButton.addEventListener("click", clearLikedIds, false); + clearButton.addEventListener("click", async () => { + await clearLikedIds(); + }, false); baseButtons.append(clearButton); // 全量抓取(帖子+评论)按钮(不做增量,直接完整抓取) @@ -1654,31 +1652,29 @@ function createStartMenu() { } // 清空点赞记录函数 -function clearLikedIds() { +async function clearLikedIds() { if (confirm('确定要清空所有点赞记录吗?此操作不可恢复。')) { if (confirm('请再次确认,是否真的要清空所有点赞记录?')) { - chrome.storage.local.remove('WQPLikedIds', function () { - alert('点赞记录已清空!'); - upCount = 0; - logCount(); - }); + await browser.storage.local.remove('WQPLikedIds'); + alert('点赞记录已清空!'); + upCount = 0; + logCount(); } } } // Use MutationObserver to watch for DOM changes -function voteUpMain() { +async function voteUpMain() { // observer.disconnect(); - chrome.storage.local.get('WQPSettings', ({ WQPSettings }) => { - if (WQPSettings.hiddenFeatureEnabled) { - // 如果为 true,则执行特定代码 - console.log('隐藏功能已启用'); - createStartMenu(); - } - }); + const { WQPSettings } = await browser.storage.local.get('WQPSettings'); + if (WQPSettings && WQPSettings.hiddenFeatureEnabled) { + // 如果为 true,则执行特定代码 + console.log('隐藏功能已启用'); + createStartMenu(); + } } -voteUpMain() +voteUpMain().catch(err => console.error('voteUpMain failed:', err)) // const observer = new MutationObserver(() => { // if (document.querySelector("#user-nav > a:nth-child(2)")) { // voteUpMain() diff --git a/src/scripts/webext-polyfill.js b/src/scripts/webext-polyfill.js new file mode 100644 index 0000000..a1daf81 --- /dev/null +++ b/src/scripts/webext-polyfill.js @@ -0,0 +1,31 @@ +// WebExtensions API polyfill for Firefox compatibility +// This file provides chrome.* compatibility for Firefox + +if (typeof chrome === "undefined" && typeof browser !== "undefined") { + window.chrome = { + runtime: { + getURL: browser.runtime.getURL, + getManifest: browser.runtime.getManifest, + onInstalled: browser.runtime.onInstalled, + onStartup: browser.runtime.onStartup, + lastError: browser.runtime.lastError + }, + storage: { + local: { + get: (keys) => browser.storage.local.get(keys), + set: (items) => browser.storage.local.set(items), + remove: (keys) => browser.storage.local.remove(keys) + } + }, + tabs: { + onUpdated: browser.tabs.onUpdated, + create: browser.tabs.create, + executeScript: browser.tabs.executeScript, + insertCSS: browser.tabs.insertCSS + }, + notifications: { + create: browser.notifications.create, + onClicked: browser.notifications.onClicked + } + }; +} \ No newline at end of file