diff --git a/.gitignore b/.gitignore index 1d5774d..229b967 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,9 @@ yarn-error.log* # Build outputs dist/ build/ +packages/ *.zip +*.xpi extension.zip # OS generated files diff --git a/build.py b/build.py index 8325ab3..2cab302 100644 --- a/build.py +++ b/build.py @@ -43,20 +43,22 @@ def copy_common_files(build_dir): Args: build_dir (Path): 目标构建目录 """ - common_files = [ - "webext-compat.js", - "background.js", - "content.js", - "popup.js", - "popup.html", - "platforms.js", + # 创建 src 目录结构 + src_dir = build_dir / "src" + + # 复制源代码目录 + if os.path.exists("src"): + shutil.copytree("src", src_dir, dirs_exist_ok=True) + + # 复制根目录的文档文件 + doc_files = [ "LICENSE", "README.md", "PRIVACY_POLICY.md", "SECURITY.md", ] - for file in common_files: + for file in doc_files: if os.path.exists(file): shutil.copy2(file, build_dir) @@ -81,8 +83,8 @@ def build_chrome(): # 复制 Chrome manifest shutil.copy2("manifest.json", build_dir / "manifest.json") - # 修改 background.js 为 Chrome 优化 - optimize_for_chrome(build_dir) + # 为 Chrome 创建扁平化的文件结构 + flatten_for_browser(build_dir, "chrome") print(f"Chrome 版本构建完成: {build_dir}") return build_dir @@ -104,13 +106,79 @@ def build_firefox(): # 复制 Firefox manifest shutil.copy2("manifest-firefox.json", build_dir / "manifest.json") - # 优化文件以适配 Firefox - optimize_for_firefox(build_dir) + # 为 Firefox 创建扁平化的文件结构 + flatten_for_browser(build_dir, "firefox") print(f"Firefox 版本构建完成: {build_dir}") return build_dir +def flatten_for_browser(build_dir, platform): + """ + 将 src 目录中的文件扁平化到构建目录根 + + Args: + build_dir (Path): 构建目录路径 + platform (str): 目标平台 (chrome/firefox) + """ + src_dir = build_dir / "src" + + if not src_dir.exists(): + print(f"警告: {src_dir} 不存在") + return + + # 将文件从 src 子目录移动到根目录 + # background/index.js -> background.js + bg_file = src_dir / "background" / "index.js" + if bg_file.exists(): + shutil.copy2(bg_file, build_dir / "background.js") + + # content/index.js -> content.js + content_file = src_dir / "content" / "index.js" + if content_file.exists(): + shutil.copy2(content_file, build_dir / "content.js") + + # popup/index.html -> popup.html + popup_html = src_dir / "popup" / "index.html" + if popup_html.exists(): + shutil.copy2(popup_html, build_dir / "popup.html") + + # popup/popup.js -> popup.js + popup_js = src_dir / "popup" / "popup.js" + if popup_js.exists(): + shutil.copy2(popup_js, build_dir / "popup.js") + + # shared/platforms.js -> platforms.js + platforms_file = src_dir / "shared" / "platforms.js" + if platforms_file.exists(): + shutil.copy2(platforms_file, build_dir / "platforms.js") + + # shared/platform-detector.js -> platform-detector.js + detector_file = src_dir / "shared" / "platform-detector.js" + if detector_file.exists(): + shutil.copy2(detector_file, build_dir / "platform-detector.js") + + # shared/compat/webext-compat.js -> webext-compat.js + webext_compat = src_dir / "shared" / "compat" / "webext-compat.js" + if webext_compat.exists(): + shutil.copy2(webext_compat, build_dir / "webext-compat.js") + + # shared/compat/firefox-compat.js -> firefox-compat.js + firefox_compat = src_dir / "shared" / "compat" / "firefox-compat.js" + if firefox_compat.exists(): + shutil.copy2(firefox_compat, build_dir / "firefox-compat.js") + + # 删除 src 目录 + if src_dir.exists(): + shutil.rmtree(src_dir) + + # 平台特定优化 + if platform == "chrome": + optimize_for_chrome(build_dir) + elif platform == "firefox": + optimize_for_firefox(build_dir) + + def optimize_for_chrome(build_dir): """ 为 Chrome 优化文件 diff --git a/background.js b/src/background/index.js similarity index 100% rename from background.js rename to src/background/index.js diff --git a/content.js b/src/content/index.js similarity index 100% rename from content.js rename to src/content/index.js diff --git a/popup.html b/src/popup/index.html similarity index 100% rename from popup.html rename to src/popup/index.html diff --git a/popup.js b/src/popup/popup.js similarity index 100% rename from popup.js rename to src/popup/popup.js diff --git a/firefox-compat.js b/src/shared/compat/firefox-compat.js similarity index 100% rename from firefox-compat.js rename to src/shared/compat/firefox-compat.js diff --git a/webext-compat.js b/src/shared/compat/webext-compat.js similarity index 74% rename from webext-compat.js rename to src/shared/compat/webext-compat.js index ab362fc..3ac6735 100644 --- a/webext-compat.js +++ b/src/shared/compat/webext-compat.js @@ -23,7 +23,7 @@ const detectBrowser = () => { const ua = navigator.userAgent; let manifest; - + try { manifest = chrome.runtime.getManifest(); } catch (error) { @@ -101,7 +101,7 @@ const webext = (() => { runtime: { getManifest: () => ({ version: "0.0.0" }) }, storage: { local: {}, sync: {} }, tabs: {}, - downloads: {} + downloads: {}, }; } @@ -162,7 +162,10 @@ const webext = (() => { // sendMessage(message) runtimeAPI.sendMessage(args[0], (response) => { if (api.runtime.lastError) { - console.warn("SendMessage failed:", api.runtime.lastError.message); + console.warn( + "SendMessage failed:", + api.runtime.lastError.message + ); resolve(null); } else { resolve(response); @@ -170,15 +173,18 @@ const webext = (() => { }); } else if (args.length === 2) { // 检查第二个参数是否是函数(回调) - if (typeof args[1] === 'function') { + if (typeof args[1] === "function") { // sendMessage(message, callback) - 直接调用回调,不使用 Promise runtimeAPI.sendMessage(args[0], args[1]); resolve(undefined); // 立即解析 Promise - } else if (typeof args[0] === 'string' && args[0].length > 10) { + } else if (typeof args[0] === "string" && args[0].length > 10) { // sendMessage(extensionId, message) runtimeAPI.sendMessage(args[0], args[1], (response) => { if (api.runtime.lastError) { - console.warn("SendMessage failed:", api.runtime.lastError.message); + console.warn( + "SendMessage failed:", + api.runtime.lastError.message + ); resolve(null); } else { resolve(response); @@ -188,7 +194,10 @@ const webext = (() => { // sendMessage(message, options) runtimeAPI.sendMessage(args[0], args[1], (response) => { if (api.runtime.lastError) { - console.warn("SendMessage failed:", api.runtime.lastError.message); + console.warn( + "SendMessage failed:", + api.runtime.lastError.message + ); resolve(null); } else { resolve(response); @@ -197,7 +206,7 @@ const webext = (() => { } } else if (args.length === 3) { // 检查第三个参数是否是函数(回调) - if (typeof args[2] === 'function') { + if (typeof args[2] === "function") { // sendMessage(extensionId, message, callback) 或 sendMessage(message, options, callback) runtimeAPI.sendMessage(args[0], args[1], args[2]); resolve(undefined); // 立即解析 Promise @@ -205,14 +214,17 @@ const webext = (() => { // sendMessage(extensionId, message, options) runtimeAPI.sendMessage(args[0], args[1], args[2], (response) => { if (api.runtime.lastError) { - console.warn("SendMessage failed:", api.runtime.lastError.message); + console.warn( + "SendMessage failed:", + api.runtime.lastError.message + ); resolve(null); } else { resolve(response); } }); } - } else if (args.length === 4 && typeof args[3] === 'function') { + } else if (args.length === 4 && typeof args[3] === "function") { // sendMessage(extensionId, message, options, callback) runtimeAPI.sendMessage(args[0], args[1], args[2], args[3]); resolve(undefined); // 立即解析 Promise @@ -244,7 +256,10 @@ const webext = (() => { // sendMessage(tabId, message) tabsAPI.sendMessage(tabId, message, (response) => { if (api.runtime.lastError) { - console.warn("Tabs sendMessage failed:", api.runtime.lastError.message); + console.warn( + "Tabs sendMessage failed:", + api.runtime.lastError.message + ); resolve(null); } else { resolve(response); @@ -254,7 +269,10 @@ const webext = (() => { // sendMessage(tabId, message, options) tabsAPI.sendMessage(tabId, message, options, (response) => { if (api.runtime.lastError) { - console.warn("Tabs sendMessage failed:", api.runtime.lastError.message); + console.warn( + "Tabs sendMessage failed:", + api.runtime.lastError.message + ); resolve(null); } else { resolve(response); @@ -285,33 +303,35 @@ const webext = (() => { }, // Tabs API - tabs: api.tabs ? { - query: promisifyNoFail(api.tabs, "query", []), - get: promisifyNoFail(api.tabs, "get"), - create: promisifyNoFail(api.tabs, "create"), - update: promisifyNoFail(api.tabs, "update"), - remove: promisifyNoFail(api.tabs, "remove"), - sendMessage: createTabsSendMessage(api.tabs), - executeScript: promisifyNoFail(api.tabs, "executeScript"), - insertCSS: promisifyNoFail(api.tabs, "insertCSS"), - removeCSS: promisifyNoFail(api.tabs, "removeCSS"), - onUpdated: api.tabs.onUpdated, - onActivated: api.tabs.onActivated, - onRemoved: api.tabs.onRemoved, - } : { - query: async () => [], - get: async () => null, - create: async () => null, - update: async () => null, - remove: async () => null, - sendMessage: async () => null, - executeScript: async () => null, - insertCSS: async () => null, - removeCSS: async () => null, - onUpdated: { addListener: () => {}, removeListener: () => {} }, - onActivated: { addListener: () => {}, removeListener: () => {} }, - onRemoved: { addListener: () => {}, removeListener: () => {} }, - }, + tabs: api.tabs + ? { + query: promisifyNoFail(api.tabs, "query", []), + get: promisifyNoFail(api.tabs, "get"), + create: promisifyNoFail(api.tabs, "create"), + update: promisifyNoFail(api.tabs, "update"), + remove: promisifyNoFail(api.tabs, "remove"), + sendMessage: createTabsSendMessage(api.tabs), + executeScript: promisifyNoFail(api.tabs, "executeScript"), + insertCSS: promisifyNoFail(api.tabs, "insertCSS"), + removeCSS: promisifyNoFail(api.tabs, "removeCSS"), + onUpdated: api.tabs.onUpdated, + onActivated: api.tabs.onActivated, + onRemoved: api.tabs.onRemoved, + } + : { + query: async () => [], + get: async () => null, + create: async () => null, + update: async () => null, + remove: async () => null, + sendMessage: async () => null, + executeScript: async () => null, + insertCSS: async () => null, + removeCSS: async () => null, + onUpdated: { addListener: () => {}, removeListener: () => {} }, + onActivated: { addListener: () => {}, removeListener: () => {} }, + onRemoved: { addListener: () => {}, removeListener: () => {} }, + }, // Storage API storage: { @@ -338,35 +358,43 @@ const webext = (() => { }, // Downloads API - downloads: api.downloads ? { - download: promisifyNoFail(api.downloads, "download"), - search: promisifyNoFail(api.downloads, "search", []), - cancel: promisifyNoFail(api.downloads, "cancel"), - pause: promisifyNoFail(api.downloads, "pause"), - resume: promisifyNoFail(api.downloads, "resume"), - erase: promisifyNoFail(api.downloads, "erase"), - removeFile: promisifyNoFail(api.downloads, "removeFile"), - show: promisifyNoFail(api.downloads, "show"), - showDefaultFolder: promisifyNoFail(api.downloads, "showDefaultFolder"), - onCreated: api.downloads.onCreated, - onChanged: api.downloads.onChanged, - onErased: api.downloads.onErased, - onDeterminingFilename: api.downloads.onDeterminingFilename, - } : { - download: async () => null, - search: async () => [], - cancel: async () => null, - pause: async () => null, - resume: async () => null, - erase: async () => null, - removeFile: async () => null, - show: async () => null, - showDefaultFolder: async () => null, - onCreated: { addListener: () => {}, removeListener: () => {} }, - onChanged: { addListener: () => {}, removeListener: () => {} }, - onErased: { addListener: () => {}, removeListener: () => {} }, - onDeterminingFilename: { addListener: () => {}, removeListener: () => {} }, - }, + downloads: api.downloads + ? { + download: promisifyNoFail(api.downloads, "download"), + search: promisifyNoFail(api.downloads, "search", []), + cancel: promisifyNoFail(api.downloads, "cancel"), + pause: promisifyNoFail(api.downloads, "pause"), + resume: promisifyNoFail(api.downloads, "resume"), + erase: promisifyNoFail(api.downloads, "erase"), + removeFile: promisifyNoFail(api.downloads, "removeFile"), + show: promisifyNoFail(api.downloads, "show"), + showDefaultFolder: promisifyNoFail( + api.downloads, + "showDefaultFolder" + ), + onCreated: api.downloads.onCreated, + onChanged: api.downloads.onChanged, + onErased: api.downloads.onErased, + onDeterminingFilename: api.downloads.onDeterminingFilename, + } + : { + download: async () => null, + search: async () => [], + cancel: async () => null, + pause: async () => null, + resume: async () => null, + erase: async () => null, + removeFile: async () => null, + show: async () => null, + showDefaultFolder: async () => null, + onCreated: { addListener: () => {}, removeListener: () => {} }, + onChanged: { addListener: () => {}, removeListener: () => {} }, + onErased: { addListener: () => {}, removeListener: () => {} }, + onDeterminingFilename: { + addListener: () => {}, + removeListener: () => {}, + }, + }, // Browser Action / Action API (兼容不同版本) action: (() => { @@ -435,7 +463,9 @@ if (typeof module !== "undefined" && module.exports) { // 服务工作器环境 self.webext = webext; self.webextFlavor = webextFlavor; - console.log("WebExt compatibility layer loaded in service worker environment"); + console.log( + "WebExt compatibility layer loaded in service worker environment" + ); } else { // 全局环境 this.webext = webext; diff --git a/platform-detector.js b/src/shared/platform-detector.js similarity index 100% rename from platform-detector.js rename to src/shared/platform-detector.js diff --git a/platforms.js b/src/shared/platforms.js similarity index 100% rename from platforms.js rename to src/shared/platforms.js