Files

451 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
构建脚本,用于生成不同浏览器版本的扩展包
支持平台:
- Chromium(Manifest V3)
- Firefox(Manifest V2)
使用方法:
python build.py --platform all --package
"""
import argparse
import json
import os
import re
import shutil
import zipfile
from pathlib import Path
def create_build_directory(platform, clean=True):
"""
创建构建目录
Args:
platform (str): 目标平台名称 (chrome/firefox)
clean (bool): 是否清理已存在的目录
Returns:
Path: 构建目录路径
"""
build_dir = Path(f"build/{platform}")
if clean and build_dir.exists():
shutil.rmtree(build_dir)
build_dir.mkdir(parents=True, exist_ok=True)
return build_dir
def copy_common_files(build_dir):
"""
复制通用文件到构建目录
Args:
build_dir (Path): 目标构建目录
"""
# 创建 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 doc_files:
if os.path.exists(file):
shutil.copy2(file, build_dir)
# 复制图标目录
if os.path.exists("icons"):
shutil.copytree("icons", build_dir / "icons", dirs_exist_ok=True)
def read_package_metadata():
with open("package.json", encoding="utf-8") as f:
package = json.load(f)
return {
"name": package["name"],
"version": package["version"],
"description": package["description"],
"author": package["author"],
"homepage": package["homepage"],
}
def build_chrome():
"""
构建 Chromium 版本
Returns:
Path: 构建目录路径
"""
print("构建 Chrome 版本...")
build_dir = create_build_directory("chrome")
# 复制通用文件
copy_common_files(build_dir)
# 复制 Chrome manifest
shutil.copy2("manifest.json", build_dir / "manifest.json")
# 为 Chrome 创建扁平化的文件结构
flatten_for_browser(build_dir, "chrome")
print(f"Chrome 版本构建完成: {build_dir}")
return build_dir
def build_firefox():
"""
构建 Firefox 版本
Returns:
Path: 构建目录路径
"""
print("构建 Firefox 版本...")
build_dir = create_build_directory("firefox")
# 复制通用文件
copy_common_files(build_dir)
# 复制 Firefox manifest
shutil.copy2("manifest-firefox.json", build_dir / "manifest.json")
# 为 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/download-core.js -> download-core.js
download_core_file = src_dir / "shared" / "download-core.js"
if download_core_file.exists():
shutil.copy2(download_core_file, build_dir / "download-core.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 优化文件
Args:
build_dir (Path): 构建目录路径
"""
# 移除 background.js 中的 importScripts 调用,因为在 Manifest V3 中不需要
bg_file = build_dir / "background.js"
if bg_file.exists():
content = bg_file.read_text(encoding="utf-8")
# 注释掉 importScripts 调用
content = content.replace(
"importScripts('webext-compat.js');",
"// importScripts('webext-compat.js'); // Chrome 不需要显式导入",
)
bg_file.write_text(content, encoding="utf-8")
def optimize_for_firefox(build_dir):
"""
为 Firefox 优化文件
Args:
build_dir (Path): 构建目录路径
"""
# Firefox 特有的优化可以在这里添加
pass
def create_package(build_dir, platform):
"""
创建扩展包
Args:
build_dir (Path): 构建目录路径
platform (str): 目标平台名称 (chrome/firefox)
Returns:
Path: 扩展包文件路径
"""
package_dir = Path("packages")
package_dir.mkdir(exist_ok=True)
# 读取版本信息
manifest_file = build_dir / "manifest.json"
with open(manifest_file, encoding="utf-8") as f:
manifest = json.load(f)
version = manifest["version"]
# 根据平台设置文件扩展名和命名
if platform == "chrome":
file_ext = "zip"
platform_name = "chromium"
elif platform == "firefox":
file_ext = "xpi"
platform_name = "firefox"
else:
file_ext = "zip"
platform_name = platform
package_name = f"Xget-Now_{version}.{platform_name}.{file_ext}"
package_path = package_dir / package_name
# 创建扩展包
compression = zipfile.ZIP_DEFLATED
with zipfile.ZipFile(package_path, "w", compression) as zipf:
for file_path in build_dir.rglob("*"):
if file_path.is_file():
arcname = file_path.relative_to(build_dir)
zipf.write(file_path, arcname)
print(f"扩展包已创建: {package_path}")
return package_path
def create_userscript_package(script_path):
package_dir = Path("packages")
package_dir.mkdir(exist_ok=True)
metadata = read_package_metadata()
package_name = f"Xget-Now_{metadata['version']}.userscript.user.js"
package_path = package_dir / package_name
shutil.copy2(script_path, package_path)
print(f"用户脚本包已创建: {package_path}")
return package_path
def extract_userscript_matches():
source = Path("src/shared/download-core.js").read_text(encoding="utf-8")
match = re.search(
r"const CORE_USERSCRIPT_MATCHES = \[(.*?)\];",
source,
re.DOTALL,
)
if not match:
raise ValueError("无法从 download-core.js 中提取 CORE_USERSCRIPT_MATCHES")
raw_items = match.group(1)
return re.findall(r'"([^"]+)"', raw_items)
def build_userscript():
print("构建 userscript 版本...")
build_dir = create_build_directory("userscript")
metadata = read_package_metadata()
matches = extract_userscript_matches()
metadata_lines = [
"// ==UserScript==",
"// @name Xget Now",
f"// @namespace {metadata['homepage']}",
f"// @version {metadata['version']}",
f"// @description {metadata['description']}",
f"// @author {metadata['author']}",
"// @license GPL-3.0",
]
metadata_lines.extend(f"// @match {pattern}" for pattern in matches)
metadata_lines.extend(
[
"// @grant GM_getValue",
"// @grant GM_setValue",
"// @grant GM_deleteValue",
"// @grant GM_registerMenuCommand",
"// @grant GM_xmlhttpRequest",
"// @connect *",
"// @run-at document-start",
"// ==/UserScript==",
"",
]
)
script_parts = [
"\n".join(metadata_lines),
Path("src/shared/download-core.js").read_text(encoding="utf-8"),
"",
Path("src/userscript/index.js").read_text(encoding="utf-8"),
"",
]
output_path = build_dir / "xget-now.user.js"
output_path.write_text("\n".join(script_parts), encoding="utf-8")
print(f"Userscript 构建完成: {output_path}")
return output_path
def validate_manifest(manifest_path, platform):
"""
验证 manifest 文件格式和内容
Args:
manifest_path (str): manifest 文件路径
platform (str): 目标平台名称 (chrome/firefox)
Returns:
bool: 验证是否通过
"""
try:
with open(manifest_path, encoding="utf-8") as f:
manifest = json.load(f)
if platform == "chrome":
# 验证 Manifest V3 要求
assert manifest.get("manifest_version") == 3, "Chrome 需要 Manifest V3"
assert "action" in manifest, "Chrome 需要 action 字段"
assert "service_worker" in manifest.get("background", {}), (
"Chrome 需要 service_worker"
)
elif platform == "firefox":
# 验证 Manifest V2 要求
assert manifest.get("manifest_version") == 2, "Firefox 需要 Manifest V2"
assert "browser_action" in manifest, "Firefox 需要 browser_action 字段"
assert (
"applications" in manifest or "browser_specific_settings" in manifest
), "Firefox 需要应用ID"
print(f"[OK] {platform} manifest 验证通过")
return True
except Exception as e:
print(f"[FAIL] {platform} manifest 验证失败: {e}")
return False
def main():
parser = argparse.ArgumentParser(description="构建 Xget Now 扩展")
parser.add_argument(
"--platform",
choices=["chrome", "firefox", "userscript", "all"],
default="all",
help="要构建的平台",
)
parser.add_argument("--package", action="store_true", help="创建扩展包")
parser.add_argument(
"--clean", action="store_true", default=True, help="清理构建目录"
)
args = parser.parse_args()
platforms = []
if args.platform == "all":
platforms = ["chrome", "firefox", "userscript"]
else:
platforms = [args.platform]
build_results = []
for platform in platforms:
try:
if platform == "chrome":
build_dir = build_chrome()
elif platform == "firefox":
build_dir = build_firefox()
elif platform == "userscript":
userscript_path = build_userscript()
build_results.append((platform, userscript_path))
if args.package:
create_userscript_package(userscript_path)
continue
# 验证 manifest
manifest_path = build_dir / "manifest.json"
if not validate_manifest(manifest_path, platform):
continue
build_results.append((platform, build_dir))
# 创建扩展包
if args.package:
create_package(build_dir, platform)
except Exception as e:
print(f"构建 {platform} 时出错: {e}")
# 总结
print("\n构建总结:")
for platform, build_dir in build_results:
print(f"[OK] {platform}: {build_dir}")
if args.package:
print("\n构建包位于 packages/ 目录")
if __name__ == "__main__":
main()