50 Commits
Author SHA1 Message Date
GitHub Action 00ee7e6592 chore: 更新版本到 2.0.5 [skip ci] 2025-11-09 07:57:51 +00:00
xixu-me 1fb387a32d Add Firefox compatibility layer and improve API detection
Introduces firefox-compat.js to handle Firefox-specific messaging and storage, and updates background.js and popup.js to robustly detect and use the appropriate extension APIs for both Chrome and Firefox environments. Manifest and popup files are updated to load the new compatibility script, ensuring reliable settings management and messaging across browsers.
2025-11-09 15:56:34 +08:00
xixu-me 38e8d54129 Merge pull request #18 from xixu-me/dependabot/github_actions/github-actions-dc500464ed
actions(deps): bump actions/upload-artifact from 4 to 5 in the github-actions group
2025-10-27 21:00:51 +08:00
dependabot[bot] 4f6eff4173 actions(deps): bump actions/upload-artifact in the github-actions group
Bumps the github-actions group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact).


Updates `actions/upload-artifact` from 4 to 5
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 09:58:46 +00:00
xixu-me bec2770f2e Merge pull request #16 from xixu-me/dependabot/github_actions/github-actions-5e51f74d5c
actions(deps): bump actions/setup-node from 5 to 6 in the github-actions group
2025-10-21 20:01:43 +08:00
dependabot[bot] ca45a98bab actions(deps): bump actions/setup-node in the github-actions group
Bumps the github-actions group with 1 update: [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/setup-node` from 5 to 6
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-20 10:25:31 +00:00
xixu-me bf3ef0427c Rename 'Microsoft Edge' to 'Edge' in docs and workflows
Updated all references from 'Microsoft Edge' to 'Edge' across issue templates, pull request templates, workflow files, privacy policy, and README for consistency and brevity.
2025-10-01 16:07:19 +08:00
xixu-me c68c2e0efa Merge pull request #14 from xixu-me/dependabot/github_actions/github-actions-9c09232849
actions(deps): bump the github-actions group with 2 updates
2025-09-08 19:06:16 +08:00
dependabot[bot] bdd7327292 actions(deps): bump the github-actions group with 2 updates
Bumps the github-actions group with 2 updates: [actions/setup-python](https://github.com/actions/setup-python) and [actions/setup-node](https://github.com/actions/setup-node).


Updates `actions/setup-python` from 5 to 6
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

Updates `actions/setup-node` from 4 to 5
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 10:07:20 +00:00
xixu-me 1682b76967 Update funding configuration in FUNDING.yml
Added custom sponsorship URL and Buy Me a Coffee username.
2025-08-24 17:42:10 +08:00
GitHub Action b2a96c9027 chore: 更新版本到 2.0.4 [skip ci] 2025-08-24 09:17:33 +00:00
xixu-me a6d62103c1 Move and restyle 'URL 转换器' link in header
The 'URL 转换器' link was moved from the bottom of the popup to the header section and given new styles for improved visibility and consistency with other header elements.
2025-08-24 17:16:36 +08:00
xixu-me 440b27b1b7 Add Chrome Web Store mirror link to README
A mirror link for the Chrome Web Store has been added to the 商店可用性 section to provide an alternative download source.
2025-08-19 19:19:18 +08:00
GitHub Action c0feccdd2b chore: 更新版本到 2.0.3 [skip ci] 2025-08-19 10:57:32 +00:00
xixu-me 1f74a391b7 Add URL converter link to popup menu
A new link to the URL converter (https://xuc.xi-xu.me/) has been added to the popup menu for easier access.
2025-08-19 18:56:54 +08:00
xixu-me a57fddb857 Merge pull request #10 from xixu-me/dependabot/github_actions/github-actions-a331d3ec2d
actions(deps): bump actions/checkout from 4 to 5 in the github-actions group
2025-08-18 21:53:50 +08:00
dependabot[bot] f9abababdb actions(deps): bump actions/checkout in the github-actions group
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 4 to 5
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-18 13:53:05 +00:00
GitHub Action 92f9326bb9 chore: 更新版本到 2.0.2 [skip ci] 2025-08-13 12:34:22 +00:00
xixu-me e80768c70a Merge pull request #7 from xixu-me/dependabot/github_actions/github-actions-cd7291ea65
actions(deps): bump actions/setup-python from 4 to 5 in the github-actions group
2025-08-11 23:24:08 +08:00
dependabot[bot] be6539c2c2 actions(deps): bump actions/setup-python in the github-actions group
Bumps the github-actions group with 1 update: [actions/setup-python](https://github.com/actions/setup-python).


Updates `actions/setup-python` from 4 to 5
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 15:23:22 +00:00
GitHub Action 011d269446 chore: 更新版本到 2.0.1 [skip ci] 2025-08-11 05:16:14 +00:00
xixu-me 06a503eab0 Improve WebExtension API compatibility and error handling
Adds robust error handling for missing or inaccessible WebExtension APIs, including safe fallbacks for runtime, tabs, and downloads. Enhances sendMessage wrappers for both runtime and tabs to handle various argument patterns and errors gracefully. Exports the compatibility layer in more environments (window, self, global) and logs the environment where it is loaded. This improves reliability in diverse extension contexts and non-browser environments.
2025-08-10 20:18:56 +08:00
xixu-me 77e525b64e Add Edge extension link to README
Included a link to the Microsoft Edge add-on for Xget in the list of places to leave a review.
2025-08-10 19:19:09 +08:00
xixu-me 77198bcb62 Remove benchmark section from README
The benchmark table and related notes have been removed from the documentation, possibly to avoid outdated or location-dependent performance claims.
2025-08-10 19:14:42 +08:00
xixu-me 796d50182e Add Firefox support and update cross-browser documentation
Expanded extension support to include Firefox alongside Chromium browsers. Updated README with detailed installation, build, and troubleshooting instructions for both Chromium and Firefox, clarified browser compatibility, and improved cross-browser guidance throughout code comments and documentation. Minor terminology changes were made in scripts and workflow files to consistently refer to 'Chromium' instead of 'Chrome/Chromium'.
2025-08-10 19:12:03 +08:00
xixu-me 95bb7d56f9 Refactor and document build, dev, and extension scripts
Added detailed docstrings and comments to Python build and dev scripts, improving maintainability and clarity. Enhanced JavaScript files (background.js, content.js, popup.js, platform-detector.js, webext-compat.js) with module-level and function-level documentation. Standardized package naming in build.py and updated the GitHub Actions workflow for simplified, platform-agnostic publishing, removing platform selection input and automating release steps for Chrome, Edge, and Firefox. Improved code quality checks and test coverage in dev.py.
2025-08-10 18:43:32 +08:00
xixu-me 3019487608 Simplify release workflow and remove Firefox notes
Removed detailed Firefox-specific release notes and manual Firefox publishing reminders from the GitHub Actions workflow. The release step now relies on auto-generated release notes, streamlining the publishing process for multi-browser support.
2025-08-10 14:09:03 +08:00
xixu-me 74759cae4f Update Firefox extension ID in manifest
Changed the gecko extension ID from 'xget-now@xixu.me' to 'i@xi-xu.me' and removed the deprecated 'applications' field. This aligns the manifest with current Firefox extension manifest standards.
2025-08-10 14:06:04 +08:00
GitHub Action d56ff757ea chore: 更新版本到 2.0.0 [skip ci] 2025-08-10 05:52:24 +00:00
xixu-me 8babb688a6 Merge pull request #6 from xixu-me/alert-autofix-4
Potential fix for code scanning alert no. 4: Workflow does not contain permissions
2025-08-10 13:50:24 +08:00
xixu-meandCopilot Autofix powered by AI 4e8f821de4 Potential fix for code scanning alert no. 4: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-08-10 13:48:05 +08:00
xixu-me 130eec751f Add Firefox support and cross-browser compatibility layer
This update introduces full Firefox support using a new manifest-firefox.json (Manifest V2), adds a webext-compat.js compatibility layer for unified API usage, and refactors all extension scripts (background.js, content.js, popup.js) to use the new webext API. The build and development process is automated with new Python scripts (build.py, dev.py), and GitHub Actions workflows are updated to support multi-platform build, test, and release. Platform detection and capability abstraction are added, and all relevant files and manifests are updated for cross-browser compatibility.
2025-08-10 13:44:08 +08:00
xixu-me ad5b3cae72 Refactor download event handling and redirection logic
Prevents multiple registrations of the download listener and refactors the download handling logic to immediately call suggest, then process redirection asynchronously. Improves error handling and notification logic for redirected downloads.
2025-08-10 12:24:44 +08:00
xixu-me ed3a4c0ee3 Prevent multiple suggest calls in handleDownload
Introduces a guard to ensure the suggest callback is only called once in handleDownload, avoiding potential issues from multiple invocations in error or early return scenarios.
2025-08-10 12:12:23 +08:00
xixu-me 367c9fee96 Rename extension from Xget for Chromium to Xget Now
Updated all references, documentation, metadata, and user-facing strings to use the new name 'Xget Now' instead of 'Xget for Chromium'. This includes changes in issue templates, workflow files, privacy and security policies, README, manifest, package.json, popup UI, and code comments/logs. Repository URLs and links have also been updated to reflect the new project name.
2025-08-10 12:06:54 +08:00
GitHub Action d78b231fe9 chore: 更新版本到 2.0.0 [skip ci] 2025-08-08 14:12:53 +00:00
xixu-me 12d2a235bf Add compact style for setting group and label
Introduced 'compact' classes for .setting-group and .setting-label to adjust padding and margin for a more condensed layout. Applied these classes to the extension enable setting for improved UI spacing.
2025-08-08 21:56:37 +08:00
xixu-me 3966bae9c0 Enhance scrollbars and scrolling UX in popup
Improved the appearance and behavior of scrollbars in the .content and .platforms sections with custom styles, smooth scrolling, and auto-hide features. Added JavaScript logic for dynamic scrollbar visibility, visual feedback during scrolling, and keyboard navigation support for better accessibility and user experience.
2025-08-08 21:53:00 +08:00
xixu-me 8b576adbba Improve popup UI with modern styles and status animation
Refactors popup.html with enhanced CSS for a more modern, visually appealing interface, including new color variables, gradients, and interactive effects. Updates status message handling in popup.js to animate hiding with CSS transitions. Also updates some UI text and icons for clarity and consistency.
2025-08-08 21:42:14 +08:00
xixu-me 09e87db44a Add support for 20+ platforms and UI overhaul
Expanded download acceleration support to over 20 platforms including npm, PyPI, Maven, RubyGems, CRAN, SourceForge, arXiv, F-Droid, and more. Refactored platform configuration into a dedicated file, updated content/background scripts for broader detection and redirection, and redesigned the popup UI for categorized platform toggles. Updated manifest, package metadata, and documentation to reflect the new capabilities and version 2.0.0.
2025-08-08 21:17:24 +08:00
xixu-me d37b67c320 Update README.md 2025-07-30 10:10:34 +08:00
xixu-me 9f9a544b00 Update README.md 2025-07-26 07:57:51 +08:00
xixu-me 69cf36da68 Add Crx 搜搜 mirror link to README
Added a new row in the download table with a link to view the extension on Crx 搜搜 as a mirror source.
2025-07-23 20:21:36 +08:00
xixu-me 3183c24ae8 Update license wording in README
Changed the wording from '本项目' (this project) to '本存储库' (this repository) in the license section for improved clarity.
2025-07-21 19:41:57 +08:00
xixu-me 4dbd0ea25f Update installation instructions in README
Clarified the step to download and extract the latest release file before installing the extension in Chromium-based browsers.
2025-07-21 18:11:32 +08:00
xixu-me aba18148a7 Update GitHub Releases link to latest release
Changed the Releases page link in the README to point directly to the latest release for easier access to the most recent extension version.
2025-07-21 18:03:33 +08:00
xixu-me ef4009b913 Simplify extension packaging to a single zip file
Consolidated Chrome and Edge extension packages into a single 'extension.zip' for all Chromium-based browsers. Removed CRX packaging and updated workflow, release notes, and README to reflect the unified distribution method.
2025-07-21 17:59:56 +08:00
xixu-me 5a210a1d0a Update Chrome references in docs and workflow
Replaced 'Chrome 浏览器' with 'Chrome' in both the publish workflow and README for consistency and clarity.
2025-07-21 17:49:24 +08:00
xixu-me 1f154e475d Update Chrome Web Store URLs in docs and workflow
Corrected the Chrome Web Store links in both the README and GitHub Actions workflow to remove the redundant '/xget-for-chrome' path segment. This ensures users are directed to the correct extension page.
2025-07-21 17:47:02 +08:00
xixu-me aba0591c3d Update Chrome Web Store references and links
Renamed all references from 'Chrome 网上应用店' to 'Chrome 应用商店' in workflow and README for consistency. Updated Chrome Web Store and Microsoft Edge Add-ons links to include language and region parameters.
2025-07-21 17:41:23 +08:00
22 changed files with 3470 additions and 404 deletions

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
custom: https://xi-xu.me/#sponsorships
buy_me_a_coffee: xixu
+2 -2
View File
@@ -12,7 +12,7 @@ body:
id: version
attributes:
label: Extension Version
description: What version of Xget for Chromium are you using?
description: What version of Xget Now are you using?
placeholder: "1.0.0"
validations:
required: true
@@ -24,7 +24,7 @@ body:
description: Which browser are you using?
options:
- Chrome
- Microsoft Edge
- Edge
- Brave
- Other Chromium-based browser
validations:
+1 -1
View File
@@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: |
Have a question about Xget for Chromium? We're here to help!
Have a question about Xget Now? We're here to help!
- type: dropdown
id: category
+1 -1
View File
@@ -40,7 +40,7 @@
## 浏览器测试
<!-- 检查你测试过的浏览器 -->
- [ ] Chrome
- [ ] Microsoft Edge
- [ ] Edge
- [ ] Brave
- [ ] 其他:____________
+120 -92
View File
@@ -1,5 +1,6 @@
name: 发布扩展到多个应用商店
# 工作流触发条件:手动触发,允许指定版本号
on:
workflow_dispatch:
inputs:
@@ -8,51 +9,77 @@ on:
required: true
type: string
# 工作流权限设置
permissions:
contents: write
packages: write
contents: write # 创建 GitHub Release 需要
packages: write # 发布包需要
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
# 第一步:检出源代码
- name: 检出代码
uses: actions/checkout@v4
uses: actions/checkout@v5
# 第二步:设置 Python 环境用于构建脚本
- name: 设置 Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
# 第三步:设置 Node.js 环境用于 JSON 验证
- name: 设置 Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "18"
- name: 验证 manifest.json
# 第四步:验证 manifest 文件格式
- name: 验证 manifest 文件
run: |
# 验证 manifest.json 是否为有效的 JSON
# 验证 Chrome manifest.json 是否为有效的 JSON
node -e "JSON.parse(require('fs').readFileSync('manifest.json', 'utf8'))"
echo "✅ manifest.json 格式有效"
echo "✅ Chrome manifest.json 格式有效"
# 验证 Firefox manifest 是否为有效的 JSON
node -e "JSON.parse(require('fs').readFileSync('manifest-firefox.json', 'utf8'))"
echo "✅ Firefox manifest.json 格式有效"
# 第五步:设置版本号
- name: 从输入设置版本
id: version
run: |
# 使用手动提供的版本
# 从工作流输入获取版本号
VERSION="${{ inputs.version }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "手动设置版本: $VERSION"
# 第六步:更新所有配置文件中的版本号
- name: 更新 manifest 版本
run: |
# 更新 manifest.json 中的版本以匹配标签
# 更新 Chrome manifest.json 中的版本
node -e "
const fs = require('fs');
const manifest = JSON.parse(fs.readFileSync('manifest.json', 'utf8'));
manifest.version = '${{ steps.version.outputs.version }}';
fs.writeFileSync('manifest.json', JSON.stringify(manifest, null, 2));
console.log('已更新 manifest.json 版本为:', manifest.version);
console.log('已更新 Chrome manifest.json 版本为:', manifest.version);
"
# 更新 Firefox manifest.json 中的版本
node -e "
const fs = require('fs');
const manifest = JSON.parse(fs.readFileSync('manifest-firefox.json', 'utf8'));
manifest.version = '${{ steps.version.outputs.version }}';
fs.writeFileSync('manifest-firefox.json', JSON.stringify(manifest, null, 2));
console.log('已更新 Firefox manifest.json 版本为:', manifest.version);
"
# 第七步:同步更新 package.json 版本
- name: 更新 package.json 版本
run: |
# 更新 package.json 中的版本以匹配标签
# 更新 package.json 中的版本以保持一致性
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
@@ -61,14 +88,15 @@ jobs:
console.log('已更新 package.json 版本为:', pkg.version);
"
# 第八步:提交版本更改并创建 Git 标签
- name: 提交并推送版本更新
run: |
# 配置 git 用户信息以进行提交
# 配置 Git 用户信息用于自动提交
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
# 添加更新的文件
git add manifest.json package.json
git add manifest.json manifest-firefox.json package.json
# 检查是否有需要提交的更改
if git diff --staged --quiet; then
@@ -87,111 +115,91 @@ jobs:
echo "✅ 版本更新已提交并推送到主分支,包含标签"
fi
- name: 创建扩展包
- name: 构建扩展包
run: |
# 创建 Chrome 扩展 zip 包
zip -r chrome-extension.zip . \
-x "*.git*" \
-x "node_modules/*" \
-x ".github/*" \
-x "*.md" \
-x "LICENSE" \
-x "*.zip" \
-x "*.crx"
echo "🏗️ 开始构建扩展包..."
# 创建 Microsoft Edge 扩展 zip 包(与 Chrome 的 MV3 相同)
cp chrome-extension.zip edge-extension.zip
# 运行构建脚本构建所有平台
python build.py --platform all --package
echo "📦 已创建扩展包"
ls -la *.zip
echo "📦 扩展包构建完成"
ls -la packages/ || echo "packages 目录不存在"
- name: 为 GitHub 发布创建 CRX 文件
- name: 运行测试
run: |
# 安装 chrome-extension-packer
npm install -g crx3
echo "🧪 运行基础测试..."
python dev.py test
# 为 CRX 签名创建私钥(仅用于分发)
openssl genrsa -out key.pem 2048
echo "🔍 运行代码检查..."
python dev.py lint
# 验证 manifest.json 是否存在
ls -la manifest.json
echo "当前目录: $(pwd)"
echo "目录内容:"
ls -la
# 为扩展文件创建临时目录
mkdir -p extension-temp
# 将扩展文件复制到临时目录
cp manifest.json extension-temp/
cp background.js extension-temp/
cp content.js extension-temp/
cp popup.html extension-temp/
cp popup.js extension-temp/
cp -r icons extension-temp/
# 验证临时目录内容
echo "扩展临时目录内容:"
ls -la extension-temp/
# 从临时目录创建 CRX 文件
cd extension-temp
crx3 -o ../extension.crx -p ../key.pem .
cd ..
echo "📦 已创建 extension.crx"
ls -la extension.crx
# 清理
rm -rf extension-temp
- name: 发布到 Chrome 网上应用店
- name: 发布到 Chrome 应用商店
continue-on-error: true
id: chrome-publish
uses: mnao305/chrome-extension-upload@v5.0.0
with:
file-path: chrome-extension.zip
file-path: packages/Xget-Now_${{ steps.version.outputs.version }}.chromium.zip
extension-id: ${{ secrets.CHROME_EXTENSION_ID }}
client-id: ${{ secrets.CHROME_CLIENT_ID }}
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
publish: true # 如果只想上传而不发布,设置为 false
publish: true
- name: 发布到 Microsoft Edge 加载项
- name: 发布到 Edge 加载项
continue-on-error: true
id: edge-publish
uses: wdzeng/edge-addon@v2
with:
product-id: ${{ secrets.EDGE_PRODUCT_ID }}
zip-path: edge-extension.zip
zip-path: packages/Xget-Now_${{ steps.version.outputs.version }}.chromium.zip
api-key: ${{ secrets.EDGE_API_KEY }}
client-id: ${{ secrets.EDGE_CLIENT_ID }}
notes-for-certification: "自动发布 v${{ steps.version.outputs.version }} - ${{ github.sha }}"
- name: 发布到 Firefox 附加组件
continue-on-error: true
id: firefox-publish
uses: browser-actions/release-firefox-addon@latest
with:
addon-id: ${{ secrets.FIREFOX_ADDON_ID }}
addon-path: packages/Xget-Now_${{ steps.version.outputs.version }}.firefox.xpi
auth-api-issuer: ${{ secrets.FIREFOX_AUTH_API_ISSUER }}
auth-api-secret: ${{ secrets.FIREFOX_AUTH_API_SECRET }}
approval-note: |
这是一个下载加速器扩展,支持多个平台的文件下载加速。
源代码位于 GitHub: https://github.com/xixu-me/Xget-Now
构建说明:直接从源代码打包,无需编译或转译。
- name: 创建 GitHub 发布
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.version }}
name: Xget for Chromium ${{ steps.version.outputs.version }}
name: Xget Now ${{ steps.version.outputs.version }}
files: |
chrome-extension.zip
edge-extension.zip
extension.crx
packages/Xget-Now_${{ steps.version.outputs.version }}.chromium.zip
packages/Xget-Now_${{ steps.version.outputs.version }}.firefox.xpi
generate_release_notes: true
body: |
此版本已自动发布到多个扩展商店。
## 📦 扩展包下载
## 📦 安装选项
### Chromium
- **文件**: `Xget-Now_${{ steps.version.outputs.version }}.chromium.zip`
- **支持**: Chrome、Edge 等基于 Chromium 的浏览器
- **Manifest**: v3
- **Chrome 网上应用店**: [从 Chrome 网上应用店安装](https://chromewebstore.google.com/detail/ajiejgobfcifcikbahpijopolfjoodgf)
- **Microsoft Edge 加载项**: [从 Microsoft Edge 加载项安装](https://microsoftedge.microsoft.com/addons/detail/jigpfhbegabdenhihpplcjhpfdcgnalc)
- **手动安装**: 下载下面的文件或查看 [安装指南](https://github.com/xixu-me/Xget-for-Chromium?tab=readme-ov-file#-%E5%AE%89%E8%A3%85) 获取详细说明
### Firefox
- **文件**: `Xget-Now_${{ steps.version.outputs.version }}.firefox.xpi`
- **支持**: Firefox 或其他基于 Firefox 的浏览器
- **Manifest**: v2
## 📁 可用文件
## 🚀 安装方法
详细的安装说明请参考:[手动安装指南](https://github.com/xixu-me/Xget-Now?tab=readme-ov-file#%E6%89%8B%E5%8A%A8%E5%AE%89%E8%A3%85)
## 📝 更新日志
详细的更改内容请查看下方的自动生成的发布说明。
- `chrome-extension.zip` - 适用于 Chrome 浏览器
- `edge-extension.zip` - 适用于 Microsoft Edge
- `extension.crx` - 适用于所有基于 Chromium 的浏览器的预打包扩展文件
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -199,29 +207,49 @@ jobs:
run: |
echo "📊 发布结果摘要:"
echo "================================"
echo "📦 版本: ${{ steps.version.outputs.version }}"
echo ""
if [ "${{ steps.chrome-publish.outcome }}" = "success" ]; then
echo "✅ Chrome 网上应用店: 发布成功"
echo "✅ Chrome 应用商店: 发布成功"
elif [ "${{ steps.chrome-publish.outcome }}" = "failure" ]; then
echo "❌ Chrome 网上应用店: 发布失败"
echo "❌ Chrome 应用商店: 发布失败"
else
echo "⚠️ Chrome 网上应用店: 已跳过(缺少密钥)"
echo "⚠️ Chrome 应用商店: 已跳过(缺少密钥或其他原因)"
fi
if [ "${{ steps.edge-publish.outcome }}" = "success" ]; then
echo "✅ Microsoft Edge 加载项: 发布成功"
echo "✅ Edge 加载项: 发布成功"
elif [ "${{ steps.edge-publish.outcome }}" = "failure" ]; then
echo "❌ Microsoft Edge 加载项: 发布失败"
echo "❌ Edge 加载项: 发布失败"
else
echo "⚠️ Microsoft Edge 加载项: 已跳过(缺少密钥)"
echo "⚠️ Edge 加载项: 已跳过(缺少密钥或其他原因)"
fi
echo "✅ GitHub 发布: 已创建扩展文件"
if [ "${{ steps.firefox-publish.outcome }}" = "success" ]; then
echo "✅ Firefox 附加组件: 发布成功"
elif [ "${{ steps.firefox-publish.outcome }}" = "failure" ]; then
echo "❌ Firefox 附加组件: 发布失败"
else
echo "⚠️ Firefox 附加组件: 已跳过(缺少密钥或其他原因)"
fi
echo ""
echo "✅ GitHub 发布: 已创建包含所有扩展文件"
echo "================================"
- name: 通知成功
run: |
echo "🎉 成功发布 Xget for Chromium ${{ steps.version.outputs.version }} 到多个商店!"
echo "🔗 Chrome 网上应用店: https://chrome.google.com/webstore/detail/${{ secrets.CHROME_EXTENSION_ID }}"
echo "🔗 Microsoft Edge 加载项: 查看发布页面获取手动安装文件"
echo ""
echo "🎉 成功发布 Xget Now ${{ steps.version.outputs.version }}!"
echo "================================"
echo "🔗 Chrome 应用商店: https://chrome.google.com/webstore/detail/${{ secrets.CHROME_EXTENSION_ID }}"
echo "🔗 Edge 加载项: 查看发布页面获取最新信息"
echo "🦊 Firefox 附加组件: 查看 Mozilla Add-ons 获取最新信息"
echo "📦 GitHub 发布: https://github.com/${{ github.repository }}/releases/tag/v${{ steps.version.outputs.version }}"
echo "📖 安装指南: https://github.com/${{ github.repository }}#installation"
echo ""
echo "📁 发布的文件:"
echo " - Xget-Now_${{ steps.version.outputs.version }}.chromium.zip (Chromium)"
echo " - Xget-Now_${{ steps.version.outputs.version }}.firefox.xpi (Firefox)"
echo "================================"
+198
View File
@@ -0,0 +1,198 @@
name: 测试构建
permissions:
contents: read
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
test-build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v5
- name: 设置 Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: 设置 Node.js
uses: actions/setup-node@v6
with:
node-version: "18"
- name: 验证 manifest 文件
run: |
echo "验证 Chrome manifest..."
node -e "
const manifest = JSON.parse(require('fs').readFileSync('manifest.json', 'utf8'));
console.log('Chrome manifest 版本:', manifest.version);
console.log('Manifest 版本:', manifest.manifest_version);
if (manifest.manifest_version !== 3) {
throw new Error('Chrome manifest 应该使用 Manifest V3');
}
"
echo "验证 Firefox manifest..."
node -e "
const manifest = JSON.parse(require('fs').readFileSync('manifest-firefox.json', 'utf8'));
console.log('Firefox manifest 版本:', manifest.version);
console.log('Manifest 版本:', manifest.manifest_version);
if (manifest.manifest_version !== 2) {
throw new Error('Firefox manifest 应该使用 Manifest V2');
}
"
- name: 检查必需文件
run: |
echo "检查必需文件是否存在..."
required_files=(
"manifest.json"
"manifest-firefox.json"
"background.js"
"content.js"
"popup.js"
"popup.html"
"webext-compat.js"
"build.py"
"dev.py"
)
for file in "${required_files[@]}"; do
if [ -f "$file" ]; then
echo "✅ $file"
else
echo "❌ $file 缺失"
exit 1
fi
done
- name: 运行基础测试
run: |
echo "运行基础测试..."
python dev.py test
- name: 运行代码检查
run: |
echo "运行代码检查..."
python dev.py lint
- name: 测试 Chrome 构建
run: |
echo "测试 Chrome 构建..."
python build.py --platform chrome
# 验证构建结果
if [ -d "build/chrome" ]; then
echo "✅ Chrome 构建目录已创建"
ls -la build/chrome/
else
echo "❌ Chrome 构建失败"
exit 1
fi
- name: 测试 Firefox 构建
run: |
echo "测试 Firefox 构建..."
python build.py --platform firefox
# 验证构建结果
if [ -d "build/firefox" ]; then
echo "✅ Firefox 构建目录已创建"
ls -la build/firefox/
else
echo "❌ Firefox 构建失败"
exit 1
fi
- name: 测试扩展包生成
run: |
echo "测试扩展包生成..."
python build.py --platform all --package
# 验证扩展包
if [ -d "packages" ]; then
echo "✅ 扩展包目录已创建"
ls -la packages/
# 检查是否有 Chrome 和 Firefox 包
if [ -f packages/xget-now-chrome-v*.zip ]; then
echo "✅ Chrome 扩展包已生成"
else
echo "❌ Chrome 扩展包生成失败"
fi
if [ -f packages/xget-now-firefox-v*.zip ]; then
echo "✅ Firefox 扩展包已生成"
else
echo "❌ Firefox 扩展包生成失败"
fi
else
echo "❌ 扩展包生成失败"
exit 1
fi
- name: 验证构建的 manifest 文件
run: |
echo "验证构建的 manifest 文件..."
# 检查 Chrome 构建的 manifest
echo "检查 Chrome manifest..."
node -e "
const manifest = JSON.parse(require('fs').readFileSync('build/chrome/manifest.json', 'utf8'));
console.log('Chrome 构建版本:', manifest.version);
if (manifest.manifest_version !== 3) {
throw new Error('Chrome 构建应该使用 Manifest V3');
}
if (!manifest.action) {
throw new Error('Chrome 构建应该有 action 字段');
}
"
# 检查 Firefox 构建的 manifest
echo "检查 Firefox manifest..."
node -e "
const manifest = JSON.parse(require('fs').readFileSync('build/firefox/manifest.json', 'utf8'));
console.log('Firefox 构建版本:', manifest.version);
if (manifest.manifest_version !== 2) {
throw new Error('Firefox 构建应该使用 Manifest V2');
}
if (!manifest.browser_action) {
throw new Error('Firefox 构建应该有 browser_action 字段');
}
if (!manifest.applications && !manifest.browser_specific_settings) {
throw new Error('Firefox 构建应该有 applications 或 browser_specific_settings 字段');
}
"
- name: 上传构建结果
uses: actions/upload-artifact@v5
with:
name: extension-builds-${{ github.sha }}
path: |
build/
packages/
retention-days: 7
- name: 测试总结
run: |
echo ""
echo "🎉 所有测试通过!"
echo "================================"
echo "✅ Manifest 文件验证"
echo "✅ 必需文件检查"
echo "✅ 基础测试"
echo "✅ 代码检查"
echo "✅ Chrome 构建"
echo "✅ Firefox 构建"
echo "✅ 扩展包生成"
echo "✅ 构建结果验证"
echo "================================"
echo "🚀 准备发布!"
+16 -14
View File
@@ -1,16 +1,16 @@
# 隐私政策
## 生效日期:2025 年 7 月 12 日
## 生效日期:2025 年 8 月 10 日
## 概述
Xget for Chromium 是一个开源浏览器扩展,旨在通过 Xget 代理服务重定向下载链接来加速 GitHub、GitLab 和 Hugging Face 的下载。我们承诺保护您的隐私并确保数据处理的透明度。
Xget Now 是一个开源浏览器扩展,旨在通过 Xget 代理服务重定向下载链接来加速众多软件开发和研究平台的下载,包括 GitHub、GitLab、Hugging Face、npm、PyPI、Maven、RubyGems、CRAN 等 20+ 个平台。我们承诺保护您的隐私并确保数据处理的透明度。
## 我们的隐私承诺
**我们不收集、存储或传输您的个人数据。**
Xget for Chromium 扩展遵循“隐私优先”的设计原则,所有处理都在您的浏览器本地进行。
Xget Now 扩展遵循“隐私优先”的设计原则,所有处理都在您的浏览器本地进行。
## 收集的信息
@@ -35,7 +35,7 @@ Xget for Chromium 扩展遵循“隐私优先”的设计原则,所有处理
- **Xget 域名配置**:您设置的自定义 Xget 服务域名
- **扩展启用状态**:扩展是否启用的开关状态
- **平台偏好设置**:为 GitHub、GitLab 和 Hugging Face 启用/禁用的选择
- **平台偏好设置**:为支持的各个平台(GitHub、GitLab、Hugging Face、npm、PyPI、Maven、RubyGems、CRAN 等)启用/禁用的选择
这些设置:
@@ -46,7 +46,7 @@ Xget for Chromium 扩展遵循“隐私优先”的设计原则,所有处理
### 数据同步
如果您已登录 Chrome/Microsoft Edge 并启用了扩展同步,您的扩展设置可能会在您的设备之间同步。这是由 Google/Microsoft 的 Chrome/Microsoft Edge 同步服务处理的,不涉及我们的服务器。
如果您已登录 Chrome/Edge 并启用了扩展同步,您的扩展设置可能会在您的设备之间同步。这是由 Google/Microsoft 的 Chrome/Edge 同步服务处理的,不涉及我们的服务器。
## 数据处理
@@ -84,9 +84,11 @@ Xget for Chromium 扩展遵循“隐私优先”的设计原则,所有处理
该扩展在以下平台上运行:
- GitHub (github.com)
- GitLab (gitlab.com)
- Hugging Face (huggingface.co)
- **代码托管平台**:GitHub、GitLab、Gitea、Codeberg、SourceForge、Android Source
- **包管理平台**:npm、PyPI、Anaconda、Maven Central、Gradle Plugin Portal、RubyGems、CRAN、CPAN、CTAN、Go Proxy、NuGet、Crates.io、Packagist
- **AI/ML 平台**:Hugging Face
- **学术平台**:arXiv
- **应用分发**:F-Droid
扩展不会与这些平台共享任何数据,也不会修改您与这些平台的交互,除了重定向下载链接。
@@ -99,14 +101,14 @@ Xget for Chromium 扩展遵循“隐私优先”的设计原则,所有处理
1. **`downloads`**:检测和管理下载以进行重定向
2. **`storage`**:在本地存储您的扩展设置
3. **`activeTab`**:在当前标签页中显示通知
4. **`host_permissions`**:访问受支持平台以检测下载链接
4. **`host_permissions`**:访问受支持的 20+ 个平台以检测下载链接
### 权限使用
- **下载权限**:仅用于检测下载并将其重定向到 Xget
- **存储权限**:仅用于保存您的扩展设置
- **标签页权限**:仅用于显示下载重定向通知
- **主机权限**:仅用于在受支持平台上检测下载链接
- **主机权限**:仅用于在受支持的各个平台上检测下载链接
我们不使用这些权限来收集数据、跟踪您的活动或任何其他目的。
@@ -121,7 +123,7 @@ Xget for Chromium 扩展遵循“隐私优先”的设计原则,所有处理
### 开源透明性
- 扩展是完全开源的
- 源代码可在 [GitHub](https://github.com/xixu-me/Xget-for-Chromium) 上获得
- 源代码可在 [GitHub](https://github.com/xixu-me/Xget-Now) 上获得
- 您可以审查、验证和审计所有代码
## 您的权利
@@ -168,7 +170,7 @@ Xget for Chromium 扩展遵循“隐私优先”的设计原则,所有处理
如果您对此隐私政策有任何问题、疑虑或建议,请:
- **GitHub Issues**:在我们的 [GitHub 存储库](https://github.com/xixu-me/Xget-for-Chromium/issues) 上打开一个 issue
- **GitHub Issues**:在我们的 [GitHub 存储库](https://github.com/xixu-me/Xget-Now/issues) 上打开一个 issue
- **电子邮件**:通过我们的 GitHub 配置文件联系维护者
## 法律合规
@@ -210,7 +212,7 @@ Xget for Chromium 扩展遵循“隐私优先”的设计原则,所有处理
---
**最后更新**:2025 年 7 月 18 日
**联系方式**:[GitHub Issues](https://github.com/xixu-me/Xget-for-Chromium/issues)
**最后更新**:2025 年 8 月 10 日
**联系方式**:[GitHub Issues](https://github.com/xixu-me/Xget-Now/issues)
此隐私政策是我们对透明度和用户隐私承诺的一部分。我们相信您有权了解您使用的软件如何处理您的数据,以及在这种情况下,确认它不会处理您的个人数据。
+117 -78
View File
@@ -1,24 +1,24 @@
# Xget for Chromium
# Xget Now
一个强大的 Chromium 浏览器扩展,通过自动将下载重定向到 [Xget](https://github.com/xixu-me/Xget) 代理服务,从而加速 GitHub、GitLab 和 Hugging Face 的下载。
为 Chromium 和 Firefox 浏览器提供的 [Xget](https://github.com/xixu-me/Xget) 下载加速扩展,支持 GitHub、GitLab、npm、PyPI 等众多平台的文件下载加速。
## 🚀 功能特性
- **🎯 自动下载加速**:无缝重定向下载通过 Xget 获得更快速度
- **🌐 多平台支持**:支持 GitHub、GitLab 和 Hugging Face
- **⚙️ 可配置设置**:自定义你的 Xget 域名和平台偏好
- **🔔 智能通知**:下载重定向时的可视化反馈
- **🛡️ 隐私优先**:所有处理都在你的浏览器本地进行
- **🎛️ 按平台控制**:为特定平台启用/禁用加速
- **🌐 跨浏览器支持**:同时支持 Chromium 和 Firefox 浏览器
## 📦 安装
### 应用商店可用性
### 商店可用性
| 商店 | 链接 |
|-------|------|
| **Chrome 网上应用店** | [![Available in the Chrome Web Store](https://developer.chrome.com/static/docs/webstore/branding/image/UV4C4ybeBTsZt43U4xis.png)](https://chromewebstore.google.com/detail/ajiejgobfcifcikbahpijopolfjoodgf) |
| **Microsoft Edge 加载项** | [Available in the Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/jigpfhbegabdenhihpplcjhpfdcgnalc) |
- [Chrome 应用商店](https://chromewebstore.google.com/detail/ajiejgobfcifcikbahpijopolfjoodgf?hl=zh-CN)
- [Chrome 应用商店镜像](https://chromewebstore.xi-xu.me/detail/ajiejgobfcifcikbahpijopolfjoodgf)
- [Firefox 附加组件](https://addons.mozilla.org/zh-CN/firefox/addon/xget-now/)
- [Edge 加载项](https://microsoftedge.microsoft.com/addons/detail/jigpfhbegabdenhihpplcjhpfdcgnalc?hl=zh-CN&gl=CN)
### 手动安装
@@ -27,41 +27,73 @@
#### 选项 1:从 GitHub Releases 安装(推荐)
1. **下载扩展**
- 前往 [Releases 页面](https://github.com/xixu-me/Xget-for-Chromium/releases)
- 下载最新发布文件:
- `chrome-extension.zip` - 适用于 Chrome 浏览器
- `edge-extension.zip` - 适用于 Microsoft Edge
- `extension.crx` - 适用于所有基于 Chromium 的浏览器的预打包扩展文件
- 前往 [Releases 页面](https://github.com/xixu-me/Xget-Now/releases/latest)
- 下载对应浏览器的扩展文件:
- `Xget-Now_x.x.x.chromium.zip` - 适用于所有基于 Chromium 的浏览器(Chrome、Edge、Opera 等)
- `Xget-Now_x.x.x.firefox.zip` - 适用于 Firefox 浏览器
2. **在 Chrome 中安装**
- 解压下载的 Chromium 版本 ZIP 文件
- 打开 Chrome 并前往 `chrome://extensions/`
- 启用「开发者模式」(右上角的切换开关)
- 点击「加载已解压的扩展程序」并选择从 ZIP 文件解压的文件夹
- 或者直接将 `.crx` 文件拖放到扩展页面
- 点击「加载已解压的扩展程序」并选择解压的文件夹
3. **在 Microsoft Edge 中安装**
- 打开 Microsoft Edge 并前往 `edge://extensions/`
3. **在 Firefox 中安装**
- 打开 Firefox 并前往 `about:debugging`
- 点击「此 Firefox」
- 点击「临时载入附加组件」
- 选择下载的 Firefox 版本 ZIP 文件或解压后的 `manifest.json` 文件
4. **在 Edge 中安装**
- 解压下载的 Chromium 版本 ZIP 文件
- 打开 Edge 并前往 `edge://extensions/`
- 启用「开发人员模式」(左侧边栏的切换开关)
- 点击「加载解压缩的扩展」并选择从 ZIP 文件解压的文件夹
- 或者直接将 `.crx` 文件拖放到扩展页面
- 点击「加载解压缩的扩展」并选择解压的文件夹
4. **在其他基于 Chromium 的浏览器中安装**
- 遵循与 Chrome 或 Microsoft Edge 类似的步骤,确保首先启用「开发者模式」
- 直接将 `.crx` 文件拖放到扩展页面
5. **在其他基于 Chromium 的浏览器中安装**
- 使用 Chromium 版本的扩展包
- 遵循与 Chrome 或 Edge 类似的步骤,确保首先启用「开发者模式」
- 按照相应浏览器的扩展安装指南进行操作
#### 选项 2:从源码安装
1. **克隆存储库**
```bash
git clone https://github.com/xixu-me/Xget-for-Chromium.git
cd Xget-for-Chromium
git clone https://github.com/xixu-me/Xget-Now.git
cd Xget-Now
```
2. **在浏览器中加载**
- 打开浏览器的扩展管理页面
2. **构建扩展(可选)**
如果你想使用优化过的版本:
```bash
# 安装 Python 3.7+
pip install -r requirements.txt # 如果有依赖文件
# 构建特定浏览器版本
python build.py --platform chrome # 构建 Chrome 版本
python build.py --platform firefox # 构建 Firefox 版本
python build.py --platform all # 构建所有版本
```
3. **在浏览器中加载**
**Chromium 浏览器:**
- 打开浏览器的扩展管理页面(`chrome://extensions/` 或 `edge://extensions/`)
- 启用「开发者模式」
- 点击「加载已解压的扩展程序」并选择克隆的文件夹
- 点击「加载已解压的扩展程序」并选择:
- 源码根目录(如果使用原始源码)
- `build/chrome/` 目录(如果使用构建版本)
**Firefox 浏览器:**
- 打开 `about:debugging`
- 点击「此 Firefox」
- 点击「临时载入附加组件」
- 选择以下文件之一:
- 源码根目录中的 `manifest-firefox.json`
- `build/firefox/` 目录中的 `manifest.json`(如果使用构建版本)
#### 验证安装
@@ -74,29 +106,15 @@
- 输入你的 Xget 域名(例如 `xget.xi-xu.me`)
- 启用扩展
2. **选择平台**(可选)
2. **选择平台**
- 根据需要切换各个平台的开关
- 默认启用所有平台(GitHub、GitLab、Hugging Face)
- 默认启用所有平台
3. **开始下载**
- 访问任何受支持的平台
- 像往常一样点击下载链接
- 下载将自动通过 Xget 加速
## 🎯 支持的平台
| 平台 | 状态 | URL 模式 | 扩展 ID |
|----------|---------|-------------|--------------|
| **GitHub** | ✅ 支持 | `github.com/*` | `gh` |
| **GitLab** | ✅ 支持 | `gitlab.com/*` | `gl` |
| **Hugging Face** | ✅ 支持 | `huggingface.co/*` | `hf` |
### 支持的下载类型
- **GitHub**:发布资源、存储库存档、原始文件、Git LFS 文件
- **GitLab**:项目存档、发布下载、存储库导出
- **Hugging Face**:模型文件、数据集文件、模型卡片、分词器
## 🔧 工作原理
1. **检测**:扩展监控受支持平台上的下载链接
@@ -104,19 +122,6 @@
3. **重定向**:下载通过你配置的 Xget 域名路由
4. **加速**:Xget 的全球 CDN 和优化功能提供更快的下载速度
### URL 转换示例
```text
原始: https://github.com/user/repo/archive/main.zip
Xget: https://xget.xi-xu.me/gh/user/repo/archive/main.zip
原始: https://gitlab.com/user/repo/-/archive/main.zip
Xget: https://xget.xi-xu.me/gl/user/repo/-/archive/main.zip
原始: https://huggingface.co/user/model/resolve/main/model.bin
Xget: https://xget.xi-xu.me/hf/user/model/resolve/main/model.bin
```
### 为什么 Xget 让下载更快
- **边缘缓存**:文件在 Cloudflare 的全球边缘位置进行缓存
@@ -135,29 +140,33 @@ Xget: https://xget.xi-xu.me/hf/user/model/resolve/main/model.bin
- **不稳定连接**:自动重试机制处理网络问题
- **并发下载**:与直接平台下载相比,没有速率限制
### 基准测试
| 文件类型 | 原始速度 | 使用 Xget | 提升 |
|-----------|----------------|-----------|-------------|
| GitHub Release (100MB) | 2.5 MB/s | 12.8 MB/s | **快 5.1 倍** |
| 存储库存档 (50MB) | 1.8 MB/s | 8.2 MB/s | **快 4.6 倍** |
| Hugging Face 模型 (500MB) | 3.2 MB/s | 15.1 MB/s | **快 4.7 倍** |
> **注意**:结果可能因位置、网络条件和服务器负载而异
## 📋 要求
- **Chrome 浏览器**:版本 88+(Manifest V3 支持)
### 浏览器支持
**Chromium 浏览器:**
- **Chrome**:版本 88+(Manifest V3 支持)
- **Edge**:版本 88+
- **Opera**:版本 74+
- **其他基于 Chromium 的浏览器**:支持 Manifest V3 的版本
**Firefox 浏览器:**
- **Firefox**:版本 109+(Manifest V2 支持)
- **Firefox ESR**:版本 109+
### Xget 服务要求
- **Xget 服务**:访问 Xget 实例
- 使用公共实例:`xget.xi-xu.me`
- 或部署你自己的:[Xget 存储库](https://github.com/xixu-me/Xget)(推荐)
- 或部署你自己的:[Xget 存储库](https://github.com/xixu-me/Xget)
### 关于 Xget 服务
Xget 是基于 Cloudflare Workers 构建的高性能、安全的代理服务,提供:
- **全球边缘分发**:通过 Cloudflare 的全球网络加快下载速度
- **多平台支持**:针对 GitHub、GitLab 和 Hugging Face 进行优化
- **高级功能**:HTTP/3 支持、智能缓存、自动重试
- **安全性**:全面的安全标头和内容保护
- **可靠性**:内置超时保护和性能监控
@@ -205,17 +214,35 @@ Xget 是基于 Cloudflare Workers 构建的高性能、安全的代理服务,
- 验证目标平台的服务器是否响应
- 清除浏览器缓存并重新加载扩展
**Firefox 特定问题?**
- 确保 Firefox 版本为 109+
- 在 `about:debugging` 中检查扩展是否正确加载
- Firefox 可能需要重启浏览器才能完全应用扩展设置
- 检查 Firefox 的「隐私和安全」设置是否阻止了扩展功能
### 调试模式
**Chromium 浏览器:**
启用 Chrome 开发者工具并检查控制台选项卡中的调试消息:
- 扩展加载:「Xget for Chromium:内容脚本已加载」
- 下载重定向:「重定向下载:[原始] -> [xget]」
**Firefox 浏览器:**
1. 打开 `about:debugging`
2. 点击「此 Firefox」
3. 找到 Xget Now 扩展并点击「检查」
4. 在打开的开发者工具中查看控制台消息
**通用调试消息:**
- 扩展加载:「Xget Now:内容脚本已加载」
- 下载重定向:「重定向下载:[原始] -> [Xget]」
- 设置更改:「设置已更新!点击刷新页面」
## 📄 许可证
本项目采用 GPL-3.0 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
本存储库采用 GPL-3.0 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
## ❓ 常见问题
@@ -223,10 +250,6 @@ Xget 是基于 Cloudflare Workers 构建的高性能、安全的代理服务,
是的,扩展完全免费且在 GPL-3.0 许可证下开源。
### 这对私有存储库有效吗?
扩展适用于任何可下载链接,但你需要对私有存储库有适当的访问权限。
### 我可以使用自己的 Xget 服务器吗?
当然可以!你可以使用 [Xget 存储库](https://github.com/xixu-me/Xget) 部署自己的 Xget 实例,并配置扩展使用你的域名。
@@ -247,11 +270,27 @@ Xget 是基于 Cloudflare Workers 构建的高性能、安全的代理服务,
是的,使用扩展弹出窗口中的按平台切换开关来禁用特定平台的加速。
### 为什么 Chrome 和 Firefox 版本不同?
由于浏览器架构差异,我们针对不同浏览器进行了优化:
- **Chrome 版本**:使用 Manifest V3,支持最新的扩展 API 和安全特性
- **Firefox 版本**:使用 Manifest V2,确保与 Firefox 扩展系统的最佳兼容性
两个版本的核心功能完全相同,只是技术实现略有不同。
### 可以同时在 Chrome 和 Firefox 中使用吗?
当然可以!你可以在多个浏览器中同时安装和使用 Xget Now,它们会独立工作,各自维护自己的设置。
## 🌟 支持我们
如果你觉得这个扩展有用,请:
- ⭐ 为此存储库点星
- 📝 在 [Chrome 网上应用店](https://chromewebstore.google.com/detail/ajiejgobfcifcikbahpijopolfjoodgf) 留下评价
- 🐛 通过 [GitHub Issues](https://github.com/xixu-me/Xget-for-Chromium/issues) 报告错误或建议功能
- 📝 留下评价:
- [Chrome 应用商店](https://chromewebstore.google.com/detail/ajiejgobfcifcikbahpijopolfjoodgf?hl=zh-CN)
- [Firefox 附加组件](https://addons.mozilla.org/zh-CN/firefox/addon/xget-now/)
- [Edge 加载项](https://microsoftedge.microsoft.com/addons/detail/jigpfhbegabdenhihpplcjhpfdcgnalc?hl=zh-CN&gl=CN)
- 🐛 通过 [GitHub Issues](https://github.com/xixu-me/Xget-Now/issues) 报告错误或建议功能
- 📢 与其他可能受益于更快下载的人分享
+3 -3
View File
@@ -2,7 +2,7 @@
## 支持的版本
我们积极支持 Xget for Chromium 的最新版本。为以下版本提供安全更新:
我们积极支持 Xget Now 的最新版本。为以下版本提供安全更新:
| 版本 | 支持状态 |
| ------- | ------------------ |
@@ -10,7 +10,7 @@
## 报告漏洞
如果你在 Xget for Chromium 中发现安全漏洞,请负责任地报告:
如果你在 Xget Now 中发现安全漏洞,请负责任地报告:
### 如何报告
@@ -63,4 +63,4 @@
- 更新时审查权限
- 报告可疑行为
感谢你帮助保持 Xget for Chromium 的安全!
感谢你帮助保持 Xget Now 的安全!
+361 -67
View File
@@ -1,95 +1,347 @@
// 与 worker.js 匹配的平台配置
/**
* Xget Now - 扩展后台脚本
*
* 功能:
* - 处理扩展设置管理
* - 监听标签页更新事件
* - 处理来自内容脚本的消息
* - 管理扩展状态和配置
*/
// 对于Firefox Manifest V2,webext-compat.js 通过manifest加载
// 对于Chrome Manifest V3,通过importScripts加载
if (typeof importScripts !== "undefined") {
try {
importScripts("webext-compat.js");
} catch (e) {
console.log("Running in Firefox/Manifest V2 environment");
}
}
// 确保兼容层可用
if (typeof webext === "undefined") {
console.error("WebExt compatibility layer not found");
// 尝试使用全局的browser或chrome API
if (typeof browser !== "undefined") {
console.log("Using Firefox browser API directly");
self.webext = browser;
self.webextFlavor = { soup: new Set(["firefox"]) };
} else if (typeof chrome !== "undefined") {
console.log("Using Chrome API directly");
self.webext = chrome;
self.webextFlavor = { soup: new Set(["chromium"]) };
}
}
// 确认API可用性
console.log("Background script initializing...");
console.log("WebExt API available:", typeof webext !== "undefined");
console.log("Storage API available:", webext && webext.storage ? "yes" : "no");
console.log("Runtime API available:", webext && webext.runtime ? "yes" : "no");
/**
* 平台配置定义
* 支持的下载加速平台列表
*/
const PLATFORMS = {
// 代码托管平台
gh: {
base: "https://github.com",
name: "GitHub",
transform: (path) => path.replace(/^\/gh\//, "/"),
pattern: /^https:\/\/github\.com\//,
},
gl: {
base: "https://gitlab.com",
name: "GitLab",
transform: (path) => path.replace(/^\/gl\//, "/"),
pattern: /^https:\/\/gitlab\.com\//,
},
gitea: {
base: "https://gitea.com",
name: "Gitea",
pattern: /^https:\/\/gitea\.com\//,
},
codeberg: {
base: "https://codeberg.org",
name: "Codeberg",
pattern: /^https:\/\/codeberg\.org\//,
},
sf: {
base: "https://sourceforge.net",
name: "SourceForge",
pattern: /^https:\/\/sourceforge\.net\//,
},
aosp: {
base: "https://android.googlesource.com",
name: "AOSP",
pattern: /^https:\/\/android\.googlesource\.com\//,
},
// AI/ML 平台
hf: {
base: "https://huggingface.co",
name: "Hugging Face",
transform: (path) => path.replace(/^\/hf\//, "/"),
pattern: /^https:\/\/huggingface\.co\//,
},
// 包管理平台
npm: {
base: "https://registry.npmjs.org",
name: "npm",
pattern: /^https:\/\/registry\.npmjs\.org\//,
},
pypi: {
base: "https://pypi.org",
name: "PyPI",
pattern: /^https:\/\/pypi\.org\//,
},
"pypi-files": {
base: "https://files.pythonhosted.org",
name: "PyPI Files",
pattern: /^https:\/\/files\.pythonhosted\.org\//,
},
conda: {
base: "https://repo.anaconda.com",
name: "Conda",
pattern: /^https:\/\/repo\.anaconda\.com\//,
},
"conda-community": {
base: "https://conda.anaconda.org",
name: "Conda Community",
pattern: /^https:\/\/conda\.anaconda\.org\//,
},
maven: {
base: "https://repo1.maven.org",
name: "Maven",
pattern: /^https:\/\/repo1\.maven\.org\//,
},
apache: {
base: "https://downloads.apache.org",
name: "Apache",
pattern: /^https:\/\/downloads\.apache\.org\//,
},
gradle: {
base: "https://plugins.gradle.org",
name: "Gradle",
pattern: /^https:\/\/plugins\.gradle\.org\//,
},
rubygems: {
base: "https://rubygems.org",
name: "RubyGems",
pattern: /^https:\/\/rubygems\.org\//,
},
cran: {
base: "https://cran.r-project.org",
name: "CRAN",
pattern: /^https:\/\/cran\.r-project\.org\//,
},
cpan: {
base: "https://www.cpan.org",
name: "CPAN",
pattern: /^https:\/\/www\.cpan\.org\//,
},
ctan: {
base: "https://tug.ctan.org",
name: "CTAN",
pattern: /^https:\/\/tug\.ctan\.org\//,
},
golang: {
base: "https://proxy.golang.org",
name: "Go Modules",
pattern: /^https:\/\/proxy\.golang\.org\//,
},
nuget: {
base: "https://api.nuget.org",
name: "NuGet",
pattern: /^https:\/\/api\.nuget\.org\//,
},
crates: {
base: "https://crates.io",
name: "Crates.io",
pattern: /^https:\/\/crates\.io\//,
},
packagist: {
base: "https://repo.packagist.org",
name: "Packagist",
pattern: /^https:\/\/repo\.packagist\.org\//,
},
// 其他平台
arxiv: {
base: "https://arxiv.org",
name: "arXiv",
pattern: /^https:\/\/arxiv\.org\//,
},
fdroid: {
base: "https://f-droid.org",
name: "F-Droid",
pattern: /^https:\/\/f-droid\.org\//,
},
};
// 默认设置
// 转换URL为Xget格式
function transformUrl(url, xgetDomain, enabledPlatforms) {
try {
const platform = detectPlatform(url);
if (!platform || !enabledPlatforms[platform]) {
return null;
}
const urlObj = new URL(url);
const path = urlObj.pathname + urlObj.search + urlObj.hash;
return `https://${xgetDomain}/${platform}${path}`;
} catch (error) {
console.error("转换 URL 时出错:", error);
return null;
}
}
// 检测平台
function detectPlatform(url) {
for (const [key, platform] of Object.entries(PLATFORMS)) {
if (platform.pattern.test(url)) {
return key;
}
}
return null;
}
const DEFAULT_SETTINGS = {
enabled: true,
xgetDomain: "xget.xi-xu.me",
enabledPlatforms: {
// 代码托管平台
gh: true,
gl: true,
gitea: true,
codeberg: true,
sf: true,
aosp: true,
// AI/ML 平台
hf: true,
// 包管理平台
npm: true,
pypi: true,
"pypi-files": true,
conda: true,
"conda-community": true,
maven: true,
apache: true,
gradle: true,
rubygems: true,
cran: true,
cpan: true,
ctan: true,
golang: true,
nuget: true,
crates: true,
packagist: true,
// 其他平台
arxiv: true,
fdroid: true,
},
};
// 初始化扩展
chrome.runtime.onInstalled.addListener(async () => {
console.log("Xget for Chromium 已安装");
webext.runtime.onInstalled.addListener(async () => {
console.log("Xget Now 已安装");
// 如果尚未设置,则设置默认设置
const settings = await chrome.storage.sync.get(DEFAULT_SETTINGS);
await chrome.storage.sync.set(settings);
// 对于Firefox,优先使用local存储,因为sync可能不可用或有限制
const isFirefox =
webextFlavor && webextFlavor.soup && webextFlavor.soup.has("firefox");
const storageAPI = isFirefox
? webext.storage.local
: webext.storage.sync || webext.storage.local;
try {
// 如果尚未设置,则设置默认设置
const settings = await storageAPI.get(DEFAULT_SETTINGS);
await storageAPI.set(settings);
} catch (error) {
console.error("初始化设置时出错:", error);
// 尝试使用local storage作为后备
try {
await webext.storage.local.set(DEFAULT_SETTINGS);
} catch (localError) {
console.error("使用本地存储初始化设置时出错:", localError);
}
}
});
// 防止监听器被多次注册
let downloadListenerAdded = false;
// 监听下载事件
chrome.downloads.onDeterminingFilename.addListener((downloadItem, suggest) => {
handleDownload(downloadItem, suggest);
});
if (!downloadListenerAdded && webext.downloads.onDeterminingFilename) {
webext.downloads.onDeterminingFilename.addListener(handleDownload);
downloadListenerAdded = true;
}
async function handleDownload(downloadItem, suggest) {
function handleDownload(downloadItem, suggest) {
// 立即调用 suggest 来防止多次调用错误
suggest();
// 然后异步处理重定向逻辑
processDownloadRedirect(downloadItem);
}
async function processDownloadRedirect(downloadItem) {
try {
const settings = await chrome.storage.sync.get(DEFAULT_SETTINGS);
// 对于Firefox,优先使用local存储
const isFirefox =
webextFlavor && webextFlavor.soup && webextFlavor.soup.has("firefox");
const storageAPI = isFirefox
? webext.storage.local
: webext.storage.sync || webext.storage.local;
const settings = await storageAPI.get(DEFAULT_SETTINGS);
// 检查扩展是否已启用并配置了域名
if (!settings.enabled || !settings.xgetDomain) {
suggest();
return;
}
const url = downloadItem.url;
const redirectedUrl = transformUrl(url, settings);
const redirectedUrl = transformUrl(
url,
settings.xgetDomain,
settings.enabledPlatforms
);
if (redirectedUrl && redirectedUrl !== url) {
console.log("重定向下载:", url, "->", redirectedUrl);
// 取消原始下载
chrome.downloads.cancel(downloadItem.id);
// 使用重定向的 URL 开始新下载
chrome.downloads.download({
url: redirectedUrl,
filename: downloadItem.filename || undefined,
conflictAction: "uniquify",
});
// 通过内容脚本显示通知
try {
await chrome.tabs.sendMessage(downloadItem.tabId, {
action: "showNotification",
message: "下载已通过 Xget 重定向",
// 取消原始下载
await webext.downloads.cancel(downloadItem.id);
// 使用重定向的 URL 开始新下载
await webext.downloads.download({
url: redirectedUrl,
filename: downloadItem.filename || undefined,
conflictAction: "uniquify",
});
// 通过内容脚本显示通知
try {
await webext.tabs.sendMessage(downloadItem.tabId, {
action: "showNotification",
message: "下载已通过 Xget 重定向",
});
} catch (error) {
console.log("无法向标签页发送通知");
}
} catch (error) {
console.log("无法向标签页发送通知");
console.error("重定向下载时出错:", error);
}
} else {
suggest();
}
} catch (error) {
console.error("处理下载时出错:", error);
suggest();
console.error("处理下载重定向时出错:", error);
}
}
function transformUrl(url, settings) {
function transformUrlLegacy(url, settings) {
try {
// 找到匹配的平台
for (const [platformKey, platform] of Object.entries(PLATFORMS)) {
@@ -113,39 +365,81 @@ function transformUrl(url, settings) {
}
// 监听来自弹出窗口/选项的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
const messageListener = (request, sender, sendResponse) => {
console.log("Received message:", request.action);
// 对于Firefox,优先使用local存储,因为sync可能不可用或有限制
const isFirefox =
webextFlavor && webextFlavor.soup && webextFlavor.soup.has("firefox");
const storageAPI = isFirefox
? webext.storage.local
: webext.storage.sync || webext.storage.local;
if (request.action === "getSettings") {
chrome.storage.sync.get(DEFAULT_SETTINGS).then(sendResponse);
return true;
} else if (request.action === "saveSettings") {
chrome.storage.sync.set(request.settings).then(async () => {
// 通知相关标签页刷新
// 使用Promise处理异步操作
(async () => {
try {
const tabs = await chrome.tabs.query({
url: [
"https://github.com/*",
"https://gitlab.com/*",
"https://huggingface.co/*",
],
});
for (const tab of tabs) {
try {
await chrome.tabs.sendMessage(tab.id, {
action: "showNotification",
message: "设置已更新!点击刷新页面",
showRefreshButton: true,
});
} catch (error) {
// 标签页可能没有加载内容脚本,忽略
}
}
const settings = await storageAPI.get(DEFAULT_SETTINGS);
console.log("Settings retrieved:", settings);
sendResponse(settings || DEFAULT_SETTINGS);
} catch (error) {
console.log("无法通知标签页设置更新");
console.error("获取设置时出错:", error);
sendResponse(DEFAULT_SETTINGS);
}
})();
return true; // 保持消息通道开放
} else if (request.action === "saveSettings") {
// 确保设置对象有效
const settingsToSave = {
...DEFAULT_SETTINGS,
...request.settings,
};
sendResponse({ success: true });
});
return true;
(async () => {
try {
await storageAPI.set(settingsToSave);
console.log("Settings saved successfully");
// 通知相关标签页刷新
try {
const tabs = await webext.tabs.query({
url: Object.values(PLATFORMS).map(
(platform) => platform.base + "/*"
),
});
for (const tab of tabs) {
try {
await webext.tabs.sendMessage(tab.id, {
action: "showNotification",
message: "设置已更新!点击刷新页面",
showRefreshButton: true,
});
} catch (error) {
// 标签页可能没有加载内容脚本,忽略
}
}
} catch (error) {
console.log("无法通知标签页设置更新");
}
sendResponse({ success: true });
} catch (error) {
console.error("保存设置时出错:", error);
sendResponse({ success: false, error: error.message });
}
})();
return true; // 保持消息通道开放
}
});
// 未知消息
return false;
};
// 添加消息监听器
if (webext && webext.runtime && webext.runtime.onMessage) {
webext.runtime.onMessage.addListener(messageListener);
console.log("Message listener registered");
} else {
console.error("Cannot register message listener - runtime API not available");
}
+284
View File
@@ -0,0 +1,284 @@
#!/usr/bin/env python3
"""
构建脚本,用于生成不同浏览器版本的扩展包
支持平台:
- Chromium(Manifest V3)
- Firefox(Manifest V2)
使用方法:
python build.py --platform all --package
"""
import argparse
import json
import os
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): 目标构建目录
"""
common_files = [
"webext-compat.js",
"background.js",
"content.js",
"popup.js",
"popup.html",
"platforms.js",
"LICENSE",
"README.md",
"PRIVACY_POLICY.md",
"SECURITY.md",
]
for file in common_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 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")
# 修改 background.js 为 Chrome 优化
optimize_for_chrome(build_dir)
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
optimize_for_firefox(build_dir)
print(f"Firefox 版本构建完成: {build_dir}")
return 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 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"✓ {platform} manifest 验证通过")
return True
except Exception as e:
print(f"✗ {platform} manifest 验证失败: {e}")
return False
def main():
parser = argparse.ArgumentParser(description="构建 Xget Now 扩展")
parser.add_argument(
"--platform",
choices=["chrome", "firefox", "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"]
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()
# 验证 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"✓ {platform}: {build_dir}")
if args.package:
print("\n扩展包位于 packages/ 目录")
if __name__ == "__main__":
main()
+233 -6
View File
@@ -1,7 +1,24 @@
// 内容脚本,在支持的平台上注入下载拦截功能
/**
* Xget Now - 内容脚本
*
* 功能:
* - 在支持的平台页面上注入下载拦截功能
* - 监听页面上的下载链接点击事件
* - 将下载请求重定向到加速服务
* - 处理不同平台的特殊下载逻辑
*/
// 平台检测和 URL 转换
// 确保兼容层可用
if (typeof webext === "undefined") {
console.error("WebExt compatibility layer not found in content script");
}
/**
* 平台配置定义
* 与后台脚本保持同步的平台列表
*/
const PLATFORMS = {
// 代码托管平台
gh: {
base: "https://github.com",
name: "GitHub",
@@ -12,19 +29,135 @@ const PLATFORMS = {
name: "GitLab",
pattern: /^https:\/\/gitlab\.com\//,
},
gitea: {
base: "https://gitea.com",
name: "Gitea",
pattern: /^https:\/\/gitea\.com\//,
},
codeberg: {
base: "https://codeberg.org",
name: "Codeberg",
pattern: /^https:\/\/codeberg\.org\//,
},
sf: {
base: "https://sourceforge.net",
name: "SourceForge",
pattern: /^https:\/\/sourceforge\.net\//,
},
aosp: {
base: "https://android.googlesource.com",
name: "AOSP",
pattern: /^https:\/\/android\.googlesource\.com\//,
},
// AI/ML 平台
hf: {
base: "https://huggingface.co",
name: "Hugging Face",
pattern: /^https:\/\/huggingface\.co\//,
},
// 包管理平台
npm: {
base: "https://registry.npmjs.org",
name: "npm",
pattern: /^https:\/\/registry\.npmjs\.org\//,
},
pypi: {
base: "https://pypi.org",
name: "PyPI",
pattern: /^https:\/\/pypi\.org\//,
},
"pypi-files": {
base: "https://files.pythonhosted.org",
name: "PyPI Files",
pattern: /^https:\/\/files\.pythonhosted\.org\//,
},
conda: {
base: "https://repo.anaconda.com",
name: "Conda",
pattern: /^https:\/\/repo\.anaconda\.com\//,
},
"conda-community": {
base: "https://conda.anaconda.org",
name: "Conda Community",
pattern: /^https:\/\/conda\.anaconda\.org\//,
},
maven: {
base: "https://repo1.maven.org",
name: "Maven",
pattern: /^https:\/\/repo1\.maven\.org\//,
},
apache: {
base: "https://downloads.apache.org",
name: "Apache",
pattern: /^https:\/\/downloads\.apache\.org\//,
},
gradle: {
base: "https://plugins.gradle.org",
name: "Gradle",
pattern: /^https:\/\/plugins\.gradle\.org\//,
},
rubygems: {
base: "https://rubygems.org",
name: "RubyGems",
pattern: /^https:\/\/rubygems\.org\//,
},
cran: {
base: "https://cran.r-project.org",
name: "CRAN",
pattern: /^https:\/\/cran\.r-project\.org\//,
},
cpan: {
base: "https://www.cpan.org",
name: "CPAN",
pattern: /^https:\/\/www\.cpan\.org\//,
},
ctan: {
base: "https://tug.ctan.org",
name: "CTAN",
pattern: /^https:\/\/tug\.ctan\.org\//,
},
golang: {
base: "https://proxy.golang.org",
name: "Go Modules",
pattern: /^https:\/\/proxy\.golang\.org\//,
},
nuget: {
base: "https://api.nuget.org",
name: "NuGet",
pattern: /^https:\/\/api\.nuget\.org\//,
},
crates: {
base: "https://crates.io",
name: "Crates.io",
pattern: /^https:\/\/crates\.io\//,
},
packagist: {
base: "https://repo.packagist.org",
name: "Packagist",
pattern: /^https:\/\/repo\.packagist\.org\//,
},
// 其他平台
arxiv: {
base: "https://arxiv.org",
name: "arXiv",
pattern: /^https:\/\/arxiv\.org\//,
},
fdroid: {
base: "https://f-droid.org",
name: "F-Droid",
pattern: /^https:\/\/f-droid\.org\//,
},
};
// 初始化内容脚本
(async function () {
console.log("Xget for Chromium:内容脚本已加载");
console.log("Xget Now:内容脚本已加载");
// 监听来自后台脚本的消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
webext.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.action === "showNotification") {
showNotification(request.message, request.showRefreshButton);
sendResponse({ success: true });
@@ -53,7 +186,7 @@ const PLATFORMS = {
async function getSettings() {
try {
return await new Promise((resolve) => {
chrome.runtime.sendMessage({ action: "getSettings" }, resolve);
webext.runtime.sendMessage({ action: "getSettings" }, resolve);
});
} catch (error) {
console.error("获取设置时出错:", error);
@@ -221,7 +354,101 @@ function isDownloadLink(link) {
console.error("无效的 URL:", href, e);
}
// 第七检查:明确的下载文本指示器(更具体)
// 第六检查:npm 包下载
const allowedNpmHosts = ["registry.npmjs.org"];
try {
const parsedUrl = new URL(href);
if (allowedNpmHosts.includes(parsedUrl.host)) {
// npm tarball URL 包含 /-/
if (pathname.includes("/-/") && pathname.endsWith(".tgz")) {
return true;
}
}
} catch (e) {
console.error("无效的 URL:", href, e);
}
// 第七检查:PyPI 包下载
const allowedPypiHosts = ["pypi.org", "files.pythonhosted.org"];
try {
const parsedUrl = new URL(href);
if (allowedPypiHosts.includes(parsedUrl.host)) {
// PyPI 包文件下载
if (
pathname.includes("/packages/") &&
(pathname.endsWith(".tar.gz") ||
pathname.endsWith(".whl") ||
pathname.endsWith(".egg"))
) {
return true;
}
}
} catch (e) {
console.error("无效的 URL:", href, e);
}
// 第八检查:SourceForge 下载
const allowedSourceForgeHosts = ["sourceforge.net"];
try {
const parsedUrl = new URL(href);
if (allowedSourceForgeHosts.includes(parsedUrl.host)) {
// SourceForge 下载 URL 包含 /download
if (
pathname.includes("/download") ||
url.searchParams.get("use_mirror")
) {
return true;
}
}
} catch (e) {
console.error("无效的 URL:", href, e);
}
// 第九检查:Conda 包下载
const allowedCondaHosts = ["repo.anaconda.com", "conda.anaconda.org"];
try {
const parsedUrl = new URL(href);
if (allowedCondaHosts.includes(parsedUrl.host)) {
// Conda 包文件
if (pathname.endsWith(".conda") || pathname.endsWith(".tar.bz2")) {
return true;
}
}
} catch (e) {
console.error("无效的 URL:", href, e);
}
// 第十检查:其他包管理平台
const packageManagerHosts = [
"rubygems.org",
"cran.r-project.org",
"crates.io",
"repo.packagist.org",
"api.nuget.org",
"proxy.golang.org",
];
try {
const parsedUrl = new URL(href);
if (packageManagerHosts.includes(parsedUrl.host)) {
// 各种包管理器的下载文件
const packageExtensions = [
".gem",
".tar.gz",
".crate",
".zip",
".nupkg",
".tgz",
".tar.bz2",
];
if (packageExtensions.some((ext) => pathname.endsWith(ext))) {
return true;
}
}
} catch (e) {
console.error("无效的 URL:", href, e);
}
// 第十一检查:明确的下载文本指示器(更具体)
const downloadTextIndicators = ["download", "download file", "get file"];
const linkText = link.textContent.toLowerCase().trim();
if (
+286
View File
@@ -0,0 +1,286 @@
#!/usr/bin/env python3
"""
开发和测试脚本
提供功能:
- 启动开发服务器
- 文件变化监控
- 代码质量检查
- 自动化测试
使用方法:
python dev.py test # 运行测试
python dev.py lint # 代码检查
python dev.py watch # 监控文件变化
python dev.py serve # 启动开发服务器
"""
import http.server
import json
import os
import socketserver
import subprocess
import time
def start_dev_server(port=8000):
"""
启动开发服务器用于测试扩展
Args:
port (int): 服务器端口号,默认 8000
"""
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=os.getcwd(), **kwargs)
def log_message(self, format, *args):
# 简化日志输出格式
print(f"[DEV] {format % args}")
with socketserver.TCPServer(("", port), Handler) as httpd:
print(f"开发服务器启动在 http://localhost:{port}")
print("按 Ctrl+C 停止服务器")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("\n开发服务器已停止")
def watch_files():
"""
监控文件变化并自动重新构建
监控的文件包括:
- manifest.json / manifest-firefox.json
- JavaScript 文件 (background.js, content.js, popup.js)
- HTML 文件 (popup.html)
- 兼容层文件 (webext-compat.js)
"""
import os
watch_files = [
"manifest.json",
"manifest-firefox.json",
"background.js",
"content.js",
"popup.js",
"popup.html",
"webext-compat.js",
]
file_times = {}
for file in watch_files:
if os.path.exists(file):
file_times[file] = os.path.getmtime(file)
print("开始监控文件变化...")
print(f"监控文件: {', '.join(watch_files)}")
while True:
try:
changed = False
for file in watch_files:
if os.path.exists(file):
current_time = os.path.getmtime(file)
if file not in file_times or current_time > file_times[file]:
print(f"检测到文件变化: {file}")
file_times[file] = current_time
changed = True
if changed:
print("重新构建...")
result = subprocess.run(
["python", "build.py", "--platform", "all"],
capture_output=True,
text=True,
)
if result.returncode == 0:
print("✓ 构建成功")
else:
print(f"✗ 构建失败: {result.stderr}")
time.sleep(1)
except KeyboardInterrupt:
print("\n停止文件监控")
break
def run_tests():
"""
运行基础测试套件
测试内容:
- 必需文件存在性检查
- Manifest 文件格式和内容验证
- 平台特定配置检查
Returns:
bool: 所有测试是否通过
"""
print("运行基础测试...")
def test_manifest(file_path, platform):
"""
测试 manifest 文件
Args:
file_path (str): manifest 文件路径
platform (str): 目标平台 (chrome/firefox)
Returns:
bool: 测试是否通过
"""
try:
with open(file_path, encoding="utf-8") as f:
manifest = json.load(f)
# 基础字段检查
required_fields = ["name", "version", "manifest_version"]
for field in required_fields:
assert field in manifest, f"缺少必需字段: {field}"
# 平台特定检查
if platform == "chrome":
assert manifest["manifest_version"] == 3, "Chrome 需要 Manifest V3"
assert "action" in manifest, "Chrome 需要 action 字段"
elif platform == "firefox":
assert manifest["manifest_version"] == 2, "Firefox 需要 Manifest V2"
assert "browser_action" in manifest, "Firefox 需要 browser_action 字段"
print(f"✓ {platform} manifest 测试通过")
return True
except Exception as e:
print(f"✗ {platform} manifest 测试失败: {e}")
return False
# 测试文件存在性
def test_files_exist():
required_files = [
"manifest.json",
"manifest-firefox.json",
"background.js",
"content.js",
"popup.js",
"popup.html",
"webext-compat.js",
]
all_exist = True
for file in required_files:
if os.path.exists(file):
print(f"✓ {file} 存在")
else:
print(f"✗ {file} 不存在")
all_exist = False
return all_exist
# 运行所有测试
results = []
results.append(test_files_exist())
results.append(test_manifest("manifest.json", "chrome"))
results.append(test_manifest("manifest-firefox.json", "firefox"))
# 总结
passed = sum(results)
total = len(results)
print(f"\n测试结果: {passed}/{total} 通过")
return passed == total
def lint_code():
"""
执行代码质量检查
检查内容:
- API 使用规范性 (优先使用 webext 兼容层)
- 错误处理最佳实践
- 接口导出规范性
特殊处理:
- webext-compat.js: 允许直接使用原生 API
- platform-detector.js: 允许直接使用原生 API
"""
print("运行代码检查...")
js_files = [
"background.js",
"content.js",
"popup.js",
"webext-compat.js",
"platform-detector.js",
]
for file in js_files:
if os.path.exists(file):
print(f"检查 {file}...")
# 读取文件内容进行检查
with open(file, encoding="utf-8") as f:
content = f.read()
# 检查常见问题
issues = []
# 底层文件允许直接使用原生 API
bottom_layer_files = ["webext-compat.js", "platform-detector.js"]
if file not in bottom_layer_files:
if "chrome." in content and "webext." not in content:
issues.append("发现直接使用 chrome API,应使用 webext 兼容层")
elif file == "webext-compat.js":
# 对于 webext-compat.js,检查是否正确导出了 webext 接口
if "window.webext" not in content and "module.exports" not in content:
issues.append("兼容层未正确导出 webext 接口")
elif file == "platform-detector.js":
# 对于 platform-detector.js,检查是否正确导出了平台检测接口
if "platformDetector" not in content:
issues.append("平台检测器未正确定义 platformDetector 接口")
# 检查错误处理
if "console.log(" in content and "console.error(" not in content:
# 对于底层文件,console.warn 也是可以接受的
if file in bottom_layer_files and "console.warn(" in content:
pass # 底层文件使用 console.warn 是合理的
else:
issues.append("建议使用 console.error 处理错误")
if issues:
print(f" ⚠ {file} 发现问题:")
for issue in issues:
print(f" - {issue}")
else:
print(f" ✓ {file} 无明显问题")
def main():
import argparse
parser = argparse.ArgumentParser(description="Xget Now 开发工具")
parser.add_argument(
"command", choices=["test", "lint", "watch", "serve"], help="要执行的命令"
)
parser.add_argument(
"--port", type=int, default=8000, help="开发服务器端口 (默认: 8000)"
)
args = parser.parse_args()
if args.command == "test":
success = run_tests()
exit(0 if success else 1)
elif args.command == "lint":
lint_code()
elif args.command == "watch":
watch_files()
elif args.command == "serve":
start_dev_server(args.port)
if __name__ == "__main__":
main()
+86
View File
@@ -0,0 +1,86 @@
/**
* Firefox 特定兼容性修复
* 直接使用 browser API 处理 Firefox 中的消息传递问题
*/
// 在 Firefox 中,直接使用 browser API
if (
typeof browser !== "undefined" &&
browser.runtime &&
browser.runtime.sendMessage
) {
console.log("Firefox environment detected, using native browser API");
// 为popup.js提供一个简化的API
window.firefoxAPI = {
sendMessage: function (message) {
return new Promise((resolve, reject) => {
try {
browser.runtime.sendMessage(message, (response) => {
if (browser.runtime.lastError) {
console.error(
"Firefox sendMessage error:",
browser.runtime.lastError
);
reject(browser.runtime.lastError);
} else {
resolve(response);
}
});
} catch (error) {
console.error("Firefox sendMessage exception:", error);
reject(error);
}
});
},
getSettings: async function () {
try {
return await browser.storage.local.get({
enabled: true,
xgetDomain: "xget.xi-xu.me",
enabledPlatforms: {
gh: true,
gl: true,
gitea: true,
codeberg: true,
sf: true,
aosp: true,
hf: true,
npm: true,
pypi: true,
"pypi-files": true,
conda: true,
"conda-community": true,
maven: true,
apache: true,
gradle: true,
rubygems: true,
cran: true,
cpan: true,
ctan: true,
golang: true,
nuget: true,
crates: true,
packagist: true,
arxiv: true,
fdroid: true,
},
});
} catch (error) {
console.error("Failed to get settings from storage:", error);
throw error;
}
},
saveSettings: async function (settings) {
try {
await browser.storage.local.set(settings);
return { success: true };
} catch (error) {
console.error("Failed to save settings to storage:", error);
return { success: false, error: error.message };
}
},
};
}
+104
View File
@@ -0,0 +1,104 @@
{
"background": {
"persistent": true,
"scripts": [
"webext-compat.js",
"background.js"
]
},
"browser_action": {
"default_icon": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"default_popup": "popup.html",
"default_title": "Xget Now"
},
"browser_specific_settings": {
"gecko": {
"id": "i@xi-xu.me",
"strict_min_version": "109.0"
}
},
"content_scripts": [
{
"js": [
"webext-compat.js",
"content.js"
],
"matches": [
"https://github.com/*",
"https://gitlab.com/*",
"https://gitea.com/*",
"https://codeberg.org/*",
"https://sourceforge.net/*",
"https://android.googlesource.com/*",
"https://huggingface.co/*",
"https://registry.npmjs.org/*",
"https://pypi.org/*",
"https://files.pythonhosted.org/*",
"https://repo.anaconda.com/*",
"https://conda.anaconda.org/*",
"https://repo1.maven.org/*",
"https://downloads.apache.org/*",
"https://plugins.gradle.org/*",
"https://rubygems.org/*",
"https://cran.r-project.org/*",
"https://www.cpan.org/*",
"https://tug.ctan.org/*",
"https://proxy.golang.org/*",
"https://api.nuget.org/*",
"https://crates.io/*",
"https://repo.packagist.org/*",
"https://arxiv.org/*",
"https://f-droid.org/*"
],
"run_at": "document_end"
}
],
"description": "为 Firefox 浏览器提供的 Xget 下载加速扩展,支持 GitHub、GitLab、npm、PyPI 等众多平台的文件下载加速",
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"manifest_version": 2,
"name": "Xget Now",
"permissions": [
"downloads",
"storage",
"activeTab",
"https://github.com/*",
"https://gitlab.com/*",
"https://gitea.com/*",
"https://codeberg.org/*",
"https://sourceforge.net/*",
"https://android.googlesource.com/*",
"https://huggingface.co/*",
"https://registry.npmjs.org/*",
"https://pypi.org/*",
"https://files.pythonhosted.org/*",
"https://repo.anaconda.com/*",
"https://conda.anaconda.org/*",
"https://repo1.maven.org/*",
"https://downloads.apache.org/*",
"https://plugins.gradle.org/*",
"https://rubygems.org/*",
"https://cran.r-project.org/*",
"https://www.cpan.org/*",
"https://tug.ctan.org/*",
"https://proxy.golang.org/*",
"https://api.nuget.org/*",
"https://crates.io/*",
"https://repo.packagist.org/*",
"https://arxiv.org/*",
"https://f-droid.org/*"
],
"version": "2.0.5",
"web_accessible_resources": [
"icons/*"
]
}
+51 -6
View File
@@ -7,7 +7,7 @@
"128": "icons/icon128.png"
},
"default_popup": "popup.html",
"default_title": "Xget for Chromium"
"default_title": "Xget Now"
},
"background": {
"service_worker": "background.js"
@@ -15,21 +15,66 @@
"content_scripts": [
{
"js": [
"webext-compat.js",
"content.js"
],
"matches": [
"https://github.com/*",
"https://gitlab.com/*",
"https://huggingface.co/*"
"https://gitea.com/*",
"https://codeberg.org/*",
"https://sourceforge.net/*",
"https://android.googlesource.com/*",
"https://huggingface.co/*",
"https://registry.npmjs.org/*",
"https://pypi.org/*",
"https://files.pythonhosted.org/*",
"https://repo.anaconda.com/*",
"https://conda.anaconda.org/*",
"https://repo1.maven.org/*",
"https://downloads.apache.org/*",
"https://plugins.gradle.org/*",
"https://rubygems.org/*",
"https://cran.r-project.org/*",
"https://www.cpan.org/*",
"https://tug.ctan.org/*",
"https://proxy.golang.org/*",
"https://api.nuget.org/*",
"https://crates.io/*",
"https://repo.packagist.org/*",
"https://arxiv.org/*",
"https://f-droid.org/*"
],
"run_at": "document_end"
}
],
"description": "为 GitHub、GitLab 和 Hugging Face 提供更快的文件下载体验",
"description": "为 Chromium 浏览器提供的 Xget 下载加速扩展,支持 GitHub、GitLab、npm、PyPI 等众多平台的文件下载加速",
"host_permissions": [
"https://github.com/*",
"https://gitlab.com/*",
"https://huggingface.co/*"
"https://gitea.com/*",
"https://codeberg.org/*",
"https://sourceforge.net/*",
"https://android.googlesource.com/*",
"https://huggingface.co/*",
"https://registry.npmjs.org/*",
"https://pypi.org/*",
"https://files.pythonhosted.org/*",
"https://repo.anaconda.com/*",
"https://conda.anaconda.org/*",
"https://repo1.maven.org/*",
"https://downloads.apache.org/*",
"https://plugins.gradle.org/*",
"https://rubygems.org/*",
"https://cran.r-project.org/*",
"https://www.cpan.org/*",
"https://tug.ctan.org/*",
"https://proxy.golang.org/*",
"https://api.nuget.org/*",
"https://crates.io/*",
"https://repo.packagist.org/*",
"https://arxiv.org/*",
"https://f-droid.org/*"
],
"icons": {
"16": "icons/icon16.png",
@@ -38,13 +83,13 @@
"128": "icons/icon128.png"
},
"manifest_version": 3,
"name": "Xget for Chromium",
"name": "Xget Now",
"permissions": [
"downloads",
"storage",
"activeTab"
],
"version": "1.3.0",
"version": "2.0.5",
"web_accessible_resources": [
{
"matches": [
+45 -21
View File
@@ -1,39 +1,63 @@
{
"author": "Xi Xu",
"browser": "chrome",
"bugs": {
"url": "https://github.com/xixu-me/Xget-for-Chromium/issues"
"url": "https://github.com/xixu-me/Xget-Now/issues"
},
"dependencies": {},
"description": "为 GitHub、GitLab 和 Hugging Face 提供更快的文件下载体验",
"devDependencies": {},
"homepage": "https://github.com/xixu-me/Xget-for-Chromium#readme",
"description": "为 Chromium 浏览器提供的 Xget 下载加速扩展,支持 GitHub、GitLab、npm、PyPI 等众多平台的文件下载加速",
"engines": {
"chrome": ">=88"
},
"homepage": "https://github.com/xixu-me/Xget-Now",
"keywords": [
"chrome-extension",
"download-accelerator",
"xget",
"proxy",
"github",
"gitlab",
"huggingface"
"npm",
"pypi",
"xget",
"download-proxy"
],
"license": "GPL-3.0",
"main": "background.js",
"manifest_version": 3,
"name": "xget-for-chromium",
"manifest": {
"platforms": [
"github.com",
"gitlab.com",
"gitea.com",
"codeberg.org",
"sourceforge.net",
"android.googlesource.com",
"huggingface.co",
"registry.npmjs.org",
"pypi.org",
"files.pythonhosted.org",
"repo.anaconda.com",
"conda.anaconda.org",
"repo1.maven.org",
"downloads.apache.org",
"plugins.gradle.org",
"rubygems.org",
"cran.r-project.org",
"www.cpan.org",
"tug.ctan.org",
"proxy.golang.org",
"api.nuget.org",
"crates.io",
"repo.packagist.org",
"arxiv.org",
"f-droid.org"
],
"version": 3
},
"name": "xget-now",
"repository": {
"type": "git",
"url": "https://github.com/xixu-me/Xget-for-Chromium.git"
"url": "https://github.com/xixu-me/Xget-Now.git"
},
"scripts": {
"build": "不需要构建过程 - 扩展直接从源码运行",
"clean": "rm -f *.zip",
"dev": "打开 chrome://extensions/ 并加载解压的扩展",
"lint": "未配置代码检查 - 建议为 JavaScript 文件添加 ESLint",
"package": "zip -r extension.zip . -x '*.git*' 'node_modules/*' '.github/*' '*.md' 'LICENSE' '*.zip'",
"release": "npm run validate && npm run package",
"test": "在 Chrome 开发者模式下加载扩展进行测试",
"validate": "node -e \"JSON.parse(require('fs').readFileSync('manifest.json', 'utf8')); console.log('✅ manifest.json 格式有效')\""
"build": "echo 'No build process needed for Chrome extension'",
"test": "echo 'No tests specified'"
},
"version": "1.3.0"
"version": "2.0.5"
}
+164
View File
@@ -0,0 +1,164 @@
/**
* 平台特定的配置和检测
*
* 提供浏览器平台检测和特性识别功能:
* - 检测当前运行的浏览器类型和版本
* - 识别 Manifest 版本 (V2/V3)
* - 检测移动端环境
* - 识别开发版本
*/
/**
* 平台检测器
* 提供浏览器环境检测的核心功能
*/
const platformDetector = {
/**
* 检测当前浏览器平台
*
* @returns {Object} 平台检测结果
* @property {string} platform - 浏览器类型 (firefox/chrome/edge/opera/safari/unknown)
* @property {number} version - 浏览器主版本号
* @property {number} manifestVersion - Manifest 版本号
* @property {boolean} isMobile - 是否为移动端
* @property {boolean} isDevBuild - 是否为开发版本
*/
detect() {
const manifest =
typeof browser !== "undefined" && browser.runtime
? browser.runtime.getManifest()
: chrome.runtime.getManifest();
const ua = navigator.userAgent;
const extURL =
typeof browser !== "undefined" && browser.runtime
? browser.runtime.getURL("")
: chrome.runtime.getURL("");
let platform = "unknown";
let version = 0;
// 根据扩展 URL 前缀检测浏览器类型
if (extURL.startsWith("moz-extension://")) {
platform = "firefox";
const match = /Firefox\/(\d+)/.exec(ua);
version = match ? parseInt(match[1], 10) : 115;
} else if (extURL.startsWith("safari-web-extension://")) {
platform = "safari";
const match = /Version\/(\d+)/.exec(ua);
version = match ? parseInt(match[1], 10) : 15;
} else if (/\bEdg\/\b/.test(ua)) {
platform = "edge";
const match = /Edg\/(\d+)/.exec(ua);
version = match ? parseInt(match[1], 10) : 120;
} else if (/\bOPR\/\b/.test(ua)) {
platform = "opera";
const match = /OPR\/(\d+)/.exec(ua);
version = match ? parseInt(match[1], 10) : 120;
} else {
platform = "chrome";
const match = /Chrome\/(\d+)/.exec(ua);
version = match ? parseInt(match[1], 10) : 120;
}
return {
platform,
version,
manifestVersion: manifest.manifest_version,
isMobile: /\bMobile\b/.test(ua),
isDevBuild: /^\d+\.\d+\.\d+\D/.test(manifest.version),
};
},
/**
* 获取平台特定的能力
*/
getCapabilities(platformInfo = null) {
const info = platformInfo || this.detect();
const capabilities = {
// 基础能力
downloads: true,
storage: true,
tabs: true,
// 高级能力
declarativeNetRequest: false,
offscreen: false,
scripting: false,
// 特殊能力
popupResize: true,
contextMenus: true,
notifications: true,
};
// Firefox 特定能力
if (info.platform === "firefox") {
capabilities.webRequest = true;
capabilities.contentSecurityPolicy = false; // 在 Manifest V2 中有限制
capabilities.popupResize = false; // Firefox 不支持动态调整弹窗大小
}
// Chromium 特定能力
if (
info.platform === "chrome" ||
info.platform === "edge" ||
info.platform === "opera"
) {
if (info.manifestVersion === 3) {
capabilities.declarativeNetRequest = true;
capabilities.offscreen = info.version >= 109;
capabilities.scripting = true;
capabilities.webRequest = false; // MV3 中 webRequest 被限制
} else {
capabilities.webRequest = true;
}
}
// Safari 特定能力
if (info.platform === "safari") {
capabilities.notifications = false; // Safari 扩展通知支持有限
capabilities.contextMenus = false; // Safari 扩展上下文菜单支持有限
}
return capabilities;
},
/**
* 获取平台特定的限制
*/
getLimitations(platformInfo = null) {
const info = platformInfo || this.detect();
const limitations = {
maxStorageSize: Infinity,
maxDownloadConcurrency: 10,
requiresUserGesture: false,
contentScriptLimitations: [],
};
if (info.platform === "firefox") {
limitations.maxStorageSize = 5 * 1024 * 1024; // 5MB for local storage
limitations.contentScriptLimitations = [
"limited-eval", // eval() 受限
"no-dynamic-import", // 动态导入受限
];
}
if (info.platform === "safari") {
limitations.maxStorageSize = 10 * 1024 * 1024; // 10MB
limitations.requiresUserGesture = true; // 许多操作需要用户手势
limitations.maxDownloadConcurrency = 3; // 下载并发限制更严格
}
return limitations;
},
};
// 导出
if (typeof module !== "undefined" && module.exports) {
module.exports = { platformDetector };
} else {
window.platformDetector = platformDetector;
}
+215
View File
@@ -0,0 +1,215 @@
/**
* 平台配置对象,包含各个平台的基础URL和名称
* @type {Object.<string, {base: string, name: string, pattern: RegExp}>}
*/
export const PLATFORMS = {
// 代码托管平台
gh: {
base: "https://github.com",
name: "GitHub",
pattern: /^https:\/\/github\.com\//,
},
gl: {
base: "https://gitlab.com",
name: "GitLab",
pattern: /^https:\/\/gitlab\.com\//,
},
gitea: {
base: "https://gitea.com",
name: "Gitea",
pattern: /^https:\/\/gitea\.com\//,
},
codeberg: {
base: "https://codeberg.org",
name: "Codeberg",
pattern: /^https:\/\/codeberg\.org\//,
},
sf: {
base: "https://sourceforge.net",
name: "SourceForge",
pattern: /^https:\/\/sourceforge\.net\//,
},
aosp: {
base: "https://android.googlesource.com",
name: "AOSP",
pattern: /^https:\/\/android\.googlesource\.com\//,
},
// AI/ML 平台
hf: {
base: "https://huggingface.co",
name: "Hugging Face",
pattern: /^https:\/\/huggingface\.co\//,
},
// 包管理平台
npm: {
base: "https://registry.npmjs.org",
name: "npm",
pattern: /^https:\/\/registry\.npmjs\.org\//,
},
pypi: {
base: "https://pypi.org",
name: "PyPI",
pattern: /^https:\/\/pypi\.org\//,
},
"pypi-files": {
base: "https://files.pythonhosted.org",
name: "PyPI Files",
pattern: /^https:\/\/files\.pythonhosted\.org\//,
},
conda: {
base: "https://repo.anaconda.com",
name: "Conda",
pattern: /^https:\/\/repo\.anaconda\.com\//,
},
"conda-community": {
base: "https://conda.anaconda.org",
name: "Conda Community",
pattern: /^https:\/\/conda\.anaconda\.org\//,
},
maven: {
base: "https://repo1.maven.org",
name: "Maven",
pattern: /^https:\/\/repo1\.maven\.org\//,
},
apache: {
base: "https://downloads.apache.org",
name: "Apache",
pattern: /^https:\/\/downloads\.apache\.org\//,
},
gradle: {
base: "https://plugins.gradle.org",
name: "Gradle",
pattern: /^https:\/\/plugins\.gradle\.org\//,
},
rubygems: {
base: "https://rubygems.org",
name: "RubyGems",
pattern: /^https:\/\/rubygems\.org\//,
},
cran: {
base: "https://cran.r-project.org",
name: "CRAN",
pattern: /^https:\/\/cran\.r-project\.org\//,
},
cpan: {
base: "https://www.cpan.org",
name: "CPAN",
pattern: /^https:\/\/www\.cpan\.org\//,
},
ctan: {
base: "https://tug.ctan.org",
name: "CTAN",
pattern: /^https:\/\/tug\.ctan\.org\//,
},
golang: {
base: "https://proxy.golang.org",
name: "Go Modules",
pattern: /^https:\/\/proxy\.golang\.org\//,
},
nuget: {
base: "https://api.nuget.org",
name: "NuGet",
pattern: /^https:\/\/api\.nuget\.org\//,
},
crates: {
base: "https://crates.io",
name: "Crates.io",
pattern: /^https:\/\/crates\.io\//,
},
packagist: {
base: "https://repo.packagist.org",
name: "Packagist",
pattern: /^https:\/\/repo\.packagist\.org\//,
},
// 其他平台
arxiv: {
base: "https://arxiv.org",
name: "arXiv",
pattern: /^https:\/\/arxiv\.org\//,
},
fdroid: {
base: "https://f-droid.org",
name: "F-Droid",
pattern: /^https:\/\/f-droid\.org\//,
},
};
/**
* 统一的路径转换函数
* @param {string} path - 原始路径
* @param {string} platformKey - 平台键值
* @returns {string} - 转换后的路径
*/
export function transformPath(path, platformKey) {
if (!PLATFORMS[platformKey]) {
return path;
}
const prefix = `/${platformKey.replace(/-/g, "/")}/`;
let transformedPath = path.replace(
new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`),
"/"
);
// 特殊处理 crates.io API 路径
if (platformKey === "crates") {
// 转换路径以包含 API 前缀
if (transformedPath.startsWith("/")) {
// 处理不同的 API 端点:
// /serde/1.0.0/download -> /api/v1/crates/serde/1.0.0/download
// /serde -> /api/v1/crates/serde
// /?q=query -> /api/v1/crates?q=query
if (transformedPath === "/" || transformedPath.startsWith("/?")) {
// 搜索端点
transformedPath = transformedPath.replace("/", "/api/v1/crates");
} else {
// 特定 crate 端点
transformedPath = `/api/v1/crates${transformedPath}`;
}
}
}
return transformedPath;
}
/**
* 根据URL检测平台
* @param {string} url - 要检测的URL
* @returns {string|null} - 平台键值或null
*/
export function detectPlatform(url) {
for (const [key, platform] of Object.entries(PLATFORMS)) {
if (platform.pattern.test(url)) {
return key;
}
}
return null;
}
/**
* 转换URL为Xget格式
* @param {string} url - 原始URL
* @param {string} xgetDomain - Xget域名
* @param {Object} enabledPlatforms - 启用的平台配置
* @returns {string|null} - 转换后的URL或null
*/
export function transformUrl(url, xgetDomain, enabledPlatforms) {
try {
const platform = detectPlatform(url);
if (!platform || !enabledPlatforms[platform]) {
return null;
}
const urlObj = new URL(url);
const path = urlObj.pathname + urlObj.search + urlObj.hash;
const transformedPath = transformPath(path, platform);
return `https://${xgetDomain}/${platform}${transformedPath}`;
} catch (error) {
console.error("转换 URL 时出错:", error);
return null;
}
}
+413 -89
View File
@@ -3,61 +3,193 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Xget for Chromium</title>
<title>Xget Now</title>
<style>
:root {
--primary-accent-color: #8a2be2;
--success-color: #4caf50;
--container-bg: rgba(255, 255, 255, 0.1);
--container-border: rgba(255, 255, 255, 0.2);
--text-color: white;
--text-secondary-color: rgba(255, 255, 255, 0.7);
--input-bg: rgba(0, 0, 0, 0.2);
--hover-bg: rgba(255, 255, 255, 0.2);
}
html {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
overflow: hidden; /* Prevents scrollbars from appearing on the html element */
}
body {
width: 380px;
min-height: 280px;
min-height: 400px;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
color: var(--text-color);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
background-color: rgba(40, 40, 40, 0.3);
border-radius: 16px;
overflow: hidden;
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.header {
padding: 20px;
text-align: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
border-bottom: 1px solid var(--container-border);
background: rgba(255, 255, 255, 0.05);
}
.header .link-item {
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
padding: 6px 12px;
font-size: 12px;
opacity: 0.8;
transition: opacity 0.2s ease;
border-radius: 6px;
background: var(--container-bg);
border: 1px solid var(--container-border);
text-decoration: none;
color: var(--text-color);
}
.header .link-item:hover {
opacity: 1;
background: var(--hover-bg);
border-color: rgba(255, 255, 255, 0.3);
transform: translateY(-1px);
}
.header .link-item .link-icon {
margin-right: 6px;
font-size: 14px;
}
.header .link-item .link-text {
font-size: 12px;
}
.header h1 {
margin: 0;
font-size: 18px;
font-size: 20px;
font-weight: 600;
}
.header .subtitle {
margin: 5px 0 0 0;
font-size: 12px;
opacity: 0.8;
letter-spacing: 0.5px;
}
.content {
padding: 20px;
max-height: calc(400px - 70px); /* Fixed height instead of viewport */
overflow-y: auto;
scroll-behavior: smooth;
}
/* Enhanced Custom Scrollbar */
.content::-webkit-scrollbar,
.platforms::-webkit-scrollbar {
width: 6px;
}
.content::-webkit-scrollbar-track,
.platforms::-webkit-scrollbar-track {
background: transparent;
border-radius: 3px;
}
.content::-webkit-scrollbar-thumb,
.platforms::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
transition: background 0.2s ease;
}
.content::-webkit-scrollbar-thumb:hover,
.platforms::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.4);
}
.content::-webkit-scrollbar-thumb:active,
.platforms::-webkit-scrollbar-thumb:active {
background: rgba(255, 255, 255, 0.6);
}
/* Scrollbar corner for better appearance */
.content::-webkit-scrollbar-corner,
.platforms::-webkit-scrollbar-corner {
background: transparent;
}
/* Smooth scrolling support for Firefox */
.content,
.platforms {
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}
/* Auto-hide scrollbar behavior */
.content:not(:hover):not(:focus-within),
.platforms:not(:hover):not(:focus-within) {
scrollbar-width: none;
}
.content:not(:hover):not(:focus-within)::-webkit-scrollbar,
.platforms:not(:hover):not(:focus-within)::-webkit-scrollbar {
width: 0;
}
.setting-group {
margin-bottom: 25px;
padding: 15px;
background: var(--container-bg);
border: 1px solid var(--container-border);
border-radius: 12px;
transition: all 0.3s ease;
}
.setting-group.compact {
padding: 10px 15px;
margin-bottom: 20px;
}
.setting-group:last-child {
margin-bottom: 0;
.setting-group:hover {
transform: translateY(-2px);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}
.setting-label {
display: block;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
margin-bottom: 12px;
color: var(--text-secondary-color);
}
.setting-label.compact {
margin-bottom: 0;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
height: 28px;
}
.toggle-switch input {
@@ -73,101 +205,160 @@
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.2);
transition: 0.3s;
border-radius: 24px;
background-color: rgba(0, 0, 0, 0.3);
transition: 0.4s;
border-radius: 28px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
height: 22px;
width: 22px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.3s;
transition: 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
border-radius: 50%;
}
input:checked + .slider {
background-color: #4caf50;
background-color: var(--primary-accent-color);
}
input:checked + .slider:before {
transform: translateX(26px);
transform: translateX(22px);
}
.domain-input {
width: 100%;
padding: 10px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
background: rgba(255, 255, 255, 0.1);
color: white;
padding: 12px;
border: 1px solid transparent;
border-radius: 8px;
background: var(--input-bg);
color: var(--text-color);
font-size: 14px;
box-sizing: border-box;
transition: all 0.3s ease;
}
.domain-input::placeholder {
color: rgba(255, 255, 255, 0.6);
color: var(--text-secondary-color);
}
.domain-input:focus {
outline: none;
border-color: rgba(255, 255, 255, 0.4);
background: rgba(255, 255, 255, 0.15);
border-color: var(--primary-accent-color);
background: rgba(0, 0, 0, 0.3);
box-shadow: 0 0 0 3px rgba(138, 43, 226, 0.3);
}
.platforms {
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
margin-top: 8px;
padding: 5px;
border-radius: 8px;
background: rgba(0, 0, 0, 0.1);
scroll-behavior: smooth;
scrollbar-gutter: stable;
}
.platform-category {
padding: 8px 12px 0 12px;
margin-bottom: 10px;
}
.category-title {
font-size: 11px;
font-weight: 600;
color: var(--text-secondary-color);
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 0.8px;
padding-left: 5px;
}
.platform-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 8px;
gap: 8px;
margin-bottom: 8px;
}
.platform-item {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(255, 255, 255, 0.1);
background: rgba(255, 255, 255, 0.05);
padding: 8px 12px;
border-radius: 6px;
border-radius: 8px;
font-size: 13px;
border: 1px solid transparent;
transition: all 0.2s ease-in-out;
}
.platform-item:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2);
}
.status {
margin-top: 15px;
padding: 10px;
border-radius: 6px;
font-size: 12px;
padding: 12px;
border-radius: 8px;
font-size: 13px;
text-align: center;
border: 1px solid transparent;
animation: status-pop-in 0.3s ease;
}
@keyframes status-pop-in {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.status.hiding {
animation: fadeOut 0.3s ease forwards;
}
@keyframes fadeOut {
to {
opacity: 0;
}
}
.status.success {
background: rgba(76, 175, 80, 0.2);
border: 1px solid rgba(76, 175, 80, 0.3);
background: rgba(76, 175, 80, 0.25);
border-color: rgba(76, 175, 80, 0.4);
}
.status.error {
background: rgba(244, 67, 54, 0.2);
border: 1px solid rgba(244, 67, 54, 0.3);
background: rgba(244, 67, 54, 0.25);
border-color: rgba(244, 67, 54, 0.4);
}
.mini-toggle {
width: 32px;
height: 16px;
width: 36px;
height: 20px;
}
.mini-toggle .slider {
border-radius: 16px;
border-radius: 20px;
}
.mini-toggle .slider:before {
height: 12px;
width: 12px;
left: 2px;
bottom: 2px;
height: 14px;
width: 14px;
left: 3px;
bottom: 3px;
}
.mini-toggle input:checked + .slider:before {
@@ -177,55 +368,65 @@
.links {
display: flex;
flex-direction: column;
gap: 8px;
gap: 10px;
}
.link-item {
display: flex;
align-items: center;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.1);
border-radius: 6px;
padding: 12px 15px;
background: var(--container-bg);
border-radius: 8px;
text-decoration: none;
color: white;
color: var(--text-color);
transition: all 0.2s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
border: 1px solid var(--container-border);
}
.link-item:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.2);
transform: translateY(-1px);
background: var(--hover-bg);
border-color: rgba(255, 255, 255, 0.3);
transform: translateY(-2px) scale(1.02);
}
.link-icon {
margin-right: 10px;
font-size: 16px;
margin-right: 12px;
font-size: 18px;
opacity: 0.8;
}
.link-text {
font-size: 13px;
font-size: 14px;
font-weight: 500;
}
</style>
</head>
<body>
<div class="header">
<h1>Xget for Chromium</h1>
<div class="subtitle">下载加速器</div>
<a
href="https://xuc.xi-xu.me/"
target="_blank"
class="link-item"
style="margin-bottom: 10px; display: inline-block"
>
<span class="link-icon">🔗</span>
<span class="link-text">URL 转换器</span>
</a>
<h1>Xget Now</h1>
</div>
<div class="content">
<div class="setting-group">
<label class="setting-label">
<div class="setting-group compact">
<label class="setting-label compact">
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
min-height: 28px;
"
>
启用扩展
<span>启用扩展</span>
<label class="toggle-switch">
<input type="checkbox" id="enabledToggle" />
<span class="slider"></span>
@@ -240,33 +441,154 @@
type="text"
id="domainInput"
class="domain-input"
placeholder="你的-xget-域名.com"
placeholder="your-xget-domain.com"
/>
</div>
<div class="setting-group">
<label class="setting-label">启用的平台</label>
<div class="platforms">
<div class="platform-item">
<span>GitHub</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="ghToggle" />
<span class="slider"></span>
</label>
<div class="platform-category">
<div class="category-title">代码托管</div>
<div class="platform-grid">
<div class="platform-item">
<span>GitHub</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="ghToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>GitLab</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="glToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>Gitea</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="giteaToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>Codeberg</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="codebergToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>SourceForge</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="sfToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>AOSP</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="aospToggle" />
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div class="platform-item">
<span>GitLab</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="glToggle" />
<span class="slider"></span>
</label>
<div class="platform-category">
<div class="category-title">AI/ML</div>
<div class="platform-grid">
<div class="platform-item">
<span>Hugging Face</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="hfToggle" />
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div class="platform-item">
<span>Hugging Face</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="hfToggle" />
<span class="slider"></span>
</label>
<div class="platform-category">
<div class="category-title">包管理</div>
<div class="platform-grid">
<div class="platform-item">
<span>npm</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="npmToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>PyPI</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="pypiToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>Conda</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="condaToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>Maven</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="mavenToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>RubyGems</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="rubygemsToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>Crates.io</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="cratesToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>NuGet</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="nugetToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>Go Modules</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="golangToggle" />
<span class="slider"></span>
</label>
</div>
</div>
</div>
<div class="platform-category">
<div class="category-title">其他</div>
<div class="platform-grid">
<div class="platform-item">
<span>arXiv</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="arxivToggle" />
<span class="slider"></span>
</label>
</div>
<div class="platform-item">
<span>F-Droid</span>
<label class="toggle-switch mini-toggle">
<input type="checkbox" id="fdroidToggle" />
<span class="slider"></span>
</label>
</div>
</div>
</div>
</div>
</div>
@@ -279,15 +601,15 @@
class="link-item"
>
<span class="link-icon">🚀</span>
<span class="link-text">部署你自己的 Xget 服务</span>
<span class="link-text">Xget 存储库</span>
</a>
<a
href="https://github.com/xixu-me/Xget-for-Chromium"
href="https://github.com/xixu-me/Xget-Now"
target="_blank"
class="link-item"
>
<span class="link-icon">📖</span>
<span class="link-text">源码和文档</span>
<span class="link-text">扩展存储库</span>
</a>
</div>
</div>
@@ -295,6 +617,8 @@
<div id="status" class="status" style="display: none"></div>
</div>
<script src="webext-compat.js"></script>
<script src="firefox-compat.js"></script>
<script src="popup.js"></script>
</body>
</html>
+324 -24
View File
@@ -1,16 +1,80 @@
document.addEventListener("DOMContentLoaded", async () => {
// 加载当前设置
await loadSettings();
/**
* Xget Now - 弹窗脚本
*
* 功能:
* - 管理扩展设置界面
* - 处理用户交互事件
* - 同步设置到后台脚本
* - 提供平台开关控制
*/
// 设置事件监听器
setupEventListeners();
document.addEventListener("DOMContentLoaded", async () => {
// 确保有可用的API
if (
typeof webext === "undefined" &&
typeof browser === "undefined" &&
!window.firefoxAPI
) {
console.error("No compatible extension API found");
showStatus("扩展API加载失败", "error");
return;
}
// 延迟执行以确保background脚本已准备就绪
setTimeout(async () => {
try {
// 加载当前设置
await loadSettings();
// 设置事件监听器
setupEventListeners();
} catch (error) {
console.error("Initialization error:", error);
showStatus("初始化失败,请重新打开扩展", "error");
}
}, 100);
});
/**
* 加载并应用当前设置到 UI
*/
async function loadSettings() {
try {
const settings = await chrome.runtime.sendMessage({
action: "getSettings",
});
console.log("Loading settings...");
let settings;
// Firefox 特殊处理
if (window.firefoxAPI && typeof browser !== "undefined") {
console.log("Using Firefox direct storage API");
try {
// 首先尝试消息传递
settings = await window.firefoxAPI.sendMessage({
action: "getSettings",
});
} catch (msgError) {
console.log(
"Message passing failed, using direct storage access:",
msgError
);
// 如果消息传递失败,直接从存储读取
settings = await window.firefoxAPI.getSettings();
}
} else if (webext && webext.runtime && webext.runtime.sendMessage) {
// 标准 webext API
console.log("Using standard webext API");
settings = await webext.runtime.sendMessage({
action: "getSettings",
});
} else {
throw new Error("No compatible API available");
}
console.log("Settings received:", settings);
if (!settings) {
throw new Error("No response from background script");
}
// 使用当前设置更新 UI
const domainValue = settings.xgetDomain || "";
@@ -20,10 +84,44 @@ async function loadSettings() {
const enabledValue = settings.enabled && domainValue.trim() !== "";
document.getElementById("enabledToggle").checked = enabledValue;
// 更新平台切换
document.getElementById("ghToggle").checked = settings.enabledPlatforms.gh;
document.getElementById("glToggle").checked = settings.enabledPlatforms.gl;
document.getElementById("hfToggle").checked = settings.enabledPlatforms.hf;
// 更新平台切换状态
const platformToggles = {
// 代码托管平台
gh: document.getElementById("ghToggle"),
gl: document.getElementById("glToggle"),
gitea: document.getElementById("giteaToggle"),
codeberg: document.getElementById("codebergToggle"),
sf: document.getElementById("sfToggle"),
aosp: document.getElementById("aospToggle"),
// AI/ML 平台
hf: document.getElementById("hfToggle"),
// 包管理平台
npm: document.getElementById("npmToggle"),
pypi: document.getElementById("pypiToggle"),
conda: document.getElementById("condaToggle"),
maven: document.getElementById("mavenToggle"),
rubygems: document.getElementById("rubygemsToggle"),
crates: document.getElementById("cratesToggle"),
nuget: document.getElementById("nugetToggle"),
golang: document.getElementById("golangToggle"),
// 其他平台
arxiv: document.getElementById("arxivToggle"),
fdroid: document.getElementById("fdroidToggle"),
};
// 设置平台切换状态
Object.entries(platformToggles).forEach(([platform, toggle]) => {
if (
toggle &&
settings.enabledPlatforms &&
settings.enabledPlatforms[platform] !== undefined
) {
toggle.checked = settings.enabledPlatforms[platform];
}
});
// 如果缺少域名则显示状态
if (!domainValue && settings.enabled) {
@@ -52,9 +150,144 @@ function setupEventListeners() {
.addEventListener("blur", validateDomain);
// 平台切换
document.getElementById("ghToggle").addEventListener("change", saveSettings);
document.getElementById("glToggle").addEventListener("change", saveSettings);
document.getElementById("hfToggle").addEventListener("change", saveSettings);
const platformToggles = [
"ghToggle",
"glToggle",
"giteaToggle",
"codebergToggle",
"sfToggle",
"aospToggle",
"hfToggle",
"npmToggle",
"pypiToggle",
"condaToggle",
"mavenToggle",
"rubygemsToggle",
"cratesToggle",
"nugetToggle",
"golangToggle",
"arxivToggle",
"fdroidToggle",
];
platformToggles.forEach((toggleId) => {
const element = document.getElementById(toggleId);
if (element) {
element.addEventListener("change", saveSettings);
}
});
// 滚动优化逻辑
setupScrollOptimization();
}
function setupScrollOptimization() {
const contentElement = document.querySelector(".content");
const platformsElement = document.querySelector(".platforms");
// 为主内容区域添加滚动优化
if (contentElement) {
let scrollTimeout;
contentElement.addEventListener("scroll", () => {
// 添加滚动中的视觉反馈
contentElement.style.scrollbarWidth = "auto";
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
// 滚动结束后隐藏滚动条(仅在不悬停时)
if (!contentElement.matches(":hover")) {
contentElement.style.scrollbarWidth = "thin";
}
}, 1000);
});
// 鼠标悬停时显示滚动条
contentElement.addEventListener("mouseenter", () => {
contentElement.style.scrollbarWidth = "auto";
});
contentElement.addEventListener("mouseleave", () => {
contentElement.style.scrollbarWidth = "thin";
});
}
// 为平台列表添加滚动优化
if (platformsElement) {
let scrollTimeout;
platformsElement.addEventListener("scroll", () => {
platformsElement.style.scrollbarWidth = "auto";
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
if (!platformsElement.matches(":hover")) {
platformsElement.style.scrollbarWidth = "thin";
}
}, 1000);
});
platformsElement.addEventListener("mouseenter", () => {
platformsElement.style.scrollbarWidth = "auto";
});
platformsElement.addEventListener("mouseleave", () => {
platformsElement.style.scrollbarWidth = "thin";
});
}
// 添加键盘导航支持
document.addEventListener("keydown", (e) => {
const activeElement = document.activeElement;
const isInScrollableArea =
activeElement &&
(contentElement.contains(activeElement) ||
platformsElement.contains(activeElement));
if (isInScrollableArea) {
switch (e.key) {
case "ArrowUp":
if (e.ctrlKey) {
e.preventDefault();
const scrollable = contentElement.contains(activeElement)
? contentElement
: platformsElement;
scrollable.scrollBy({ top: -50, behavior: "smooth" });
}
break;
case "ArrowDown":
if (e.ctrlKey) {
e.preventDefault();
const scrollable = contentElement.contains(activeElement)
? contentElement
: platformsElement;
scrollable.scrollBy({ top: 50, behavior: "smooth" });
}
break;
case "Home":
if (e.ctrlKey) {
e.preventDefault();
const scrollable = contentElement.contains(activeElement)
? contentElement
: platformsElement;
scrollable.scrollTo({ top: 0, behavior: "smooth" });
}
break;
case "End":
if (e.ctrlKey) {
e.preventDefault();
const scrollable = contentElement.contains(activeElement)
? contentElement
: platformsElement;
scrollable.scrollTo({
top: scrollable.scrollHeight,
behavior: "smooth",
});
}
break;
}
}
});
}
async function saveSettings() {
@@ -73,9 +306,38 @@ async function saveSettings() {
enabled: document.getElementById("enabledToggle").checked,
xgetDomain: domainValue,
enabledPlatforms: {
gh: document.getElementById("ghToggle").checked,
gl: document.getElementById("glToggle").checked,
hf: document.getElementById("hfToggle").checked,
// 代码托管平台
gh: document.getElementById("ghToggle")?.checked || false,
gl: document.getElementById("glToggle")?.checked || false,
gitea: document.getElementById("giteaToggle")?.checked || false,
codeberg: document.getElementById("codebergToggle")?.checked || false,
sf: document.getElementById("sfToggle")?.checked || false,
aosp: document.getElementById("aospToggle")?.checked || false,
// AI/ML 平台
hf: document.getElementById("hfToggle")?.checked || false,
// 包管理平台
npm: document.getElementById("npmToggle")?.checked || false,
pypi: document.getElementById("pypiToggle")?.checked || false,
"pypi-files": true, // 默认开启
conda: document.getElementById("condaToggle")?.checked || false,
"conda-community": true, // 默认开启
maven: document.getElementById("mavenToggle")?.checked || false,
apache: true, // 默认开启
gradle: true, // 默认开启
rubygems: document.getElementById("rubygemsToggle")?.checked || false,
cran: true, // 默认开启
cpan: true, // 默认开启
ctan: true, // 默认开启
golang: document.getElementById("golangToggle")?.checked || false,
nuget: document.getElementById("nugetToggle")?.checked || false,
crates: document.getElementById("cratesToggle")?.checked || false,
packagist: true, // 默认开启
// 其他平台
arxiv: document.getElementById("arxivToggle")?.checked || false,
fdroid: document.getElementById("fdroidToggle")?.checked || false,
},
};
@@ -84,12 +346,34 @@ async function saveSettings() {
settings.xgetDomain = cleanupDomain(settings.xgetDomain);
}
const response = await chrome.runtime.sendMessage({
action: "saveSettings",
settings: settings,
});
let response;
if (response.success) {
// Firefox 特殊处理
if (window.firefoxAPI && typeof browser !== "undefined") {
console.log("Saving settings using Firefox API");
try {
// 首先尝试消息传递
response = await window.firefoxAPI.sendMessage({
action: "saveSettings",
settings: settings,
});
} catch (msgError) {
console.log("Message passing failed, using direct storage:", msgError);
// 如果消息传递失败,直接写入存储
response = await window.firefoxAPI.saveSettings(settings);
}
} else if (webext && webext.runtime && webext.runtime.sendMessage) {
// 标准 webext API
console.log("Saving settings using standard webext API");
response = await webext.runtime.sendMessage({
action: "saveSettings",
settings: settings,
});
} else {
throw new Error("No compatible API available for saving settings");
}
if (response && response.success) {
// 显示适当的状态
if (!settings.xgetDomain && settings.enabled) {
showStatus("请配置你的 Xget 域名", "error");
@@ -98,6 +382,13 @@ async function saveSettings() {
} else {
showStatus("✅ 设置已保存!查看页面通知中的刷新按钮", "success");
}
} else if (response && response.error) {
console.error("保存设置失败:", response.error);
showStatus("保存设置时出错: " + response.error, "error");
} else {
// 如果没有响应,可能是消息传递失败
console.error("未收到后台脚本的响应");
showStatus("保存设置时出错:无法与后台脚本通信", "error");
}
} catch (error) {
console.error("保存设置时出错:", error);
@@ -147,6 +438,7 @@ function showStatus(message, type) {
statusElement.textContent = message;
statusElement.className = `status ${type}`;
statusElement.style.display = "block";
statusElement.classList.remove("hiding");
// 自动隐藏成功消息
if (type === "success") {
@@ -156,7 +448,15 @@ function showStatus(message, type) {
function hideStatus() {
const statusElement = document.getElementById("status");
statusElement.style.display = "none";
statusElement.classList.add("hiding");
statusElement.addEventListener(
"animationend",
() => {
statusElement.style.display = "none";
statusElement.classList.remove("hiding");
},
{ once: true }
);
}
function debounce(func, wait) {
+444
View File
@@ -0,0 +1,444 @@
/**
* WebExtension API 兼容层
*
* 提供跨浏览器的统一 API 接口,解决不同浏览器间的差异:
* - Firefox 使用 browser API,返回 Promise
* - Chromium 使用 chrome API,使用回调函数
* - 不同版本的 Manifest API 差异 (V2 vs V3)
*
* 功能:
* - 自动检测当前浏览器环境
* - 提供统一的 Promise 化 API 接口
* - 处理 API 调用失败的情况
* - 兼容不同浏览器的特性差异
*/
/**
* 检测当前浏览器环境
*
* @returns {Object} 浏览器特性信息
* @property {number} major - 浏览器主版本号
* @property {Set<string>} soup - 浏览器特性标识集合
*/
const detectBrowser = () => {
const ua = navigator.userAgent;
let manifest;
try {
manifest = chrome.runtime.getManifest();
} catch (error) {
console.warn("Could not get manifest in current context:", error);
manifest = { version: "0.0.0" }; // 默认版本
}
const flavor = {
major: 0,
soup: new Set(["webext"]),
};
// 检测 Firefox
if (
typeof browser !== "undefined" &&
browser.runtime &&
browser.runtime.getURL("").startsWith("moz-extension://")
) {
flavor.soup.add("firefox");
const match = /Firefox\/(\d+)/.exec(ua);
flavor.major = match ? parseInt(match[1], 10) : 115;
// Firefox 特性检测
if (CSS.supports("selector(a:has(b))")) {
flavor.soup.add("native_css_has");
}
}
// 检测基于 Chromium 的浏览器
else {
const match = /\bChrom(?:e|ium)\/(\d+)/.exec(ua);
if (match !== null) {
flavor.soup.add("chromium");
}
flavor.major = match ? parseInt(match[1], 10) : 120;
// 检测具体浏览器
if (/\bEdg\/\b/.test(ua)) {
flavor.soup.add("edge");
} else if (/\bOPR\/\b/.test(ua)) {
flavor.soup.add("opera");
} else {
flavor.soup.add("chrome");
}
}
// 检测移动端
if (/\bMobile\b/.test(ua)) {
flavor.soup.add("mobile");
}
// 检测开发版本
if (/^\d+\.\d+\.\d+\D/.test(manifest.version)) {
flavor.soup.add("devbuild");
}
return flavor;
};
// 浏览器特性检测
const webextFlavor = detectBrowser();
// 统一的 WebExtension API
const webext = (() => {
// Firefox 使用 browser API,Chrome 使用 chrome API
let api;
try {
api = typeof browser !== "undefined" && browser.runtime ? browser : chrome;
if (!api || !api.runtime) {
throw new Error("No WebExtension API available");
}
} catch (error) {
console.error("Failed to initialize WebExtension API:", error);
// 创建一个最小的 API 存根
api = {
runtime: { getManifest: () => ({ version: "0.0.0" }) },
storage: { local: {}, sync: {} },
tabs: {},
downloads: {}
};
}
// Promise 化函数的包装器
const promisify = (context, methodName) => {
const method = context[methodName];
if (!method) {
throw new Error(`Method ${methodName} not found`);
}
return function (...args) {
return new Promise((resolve, reject) => {
method.call(context, ...args, (result) => {
if (api.runtime.lastError) {
reject(new Error(api.runtime.lastError.message));
} else {
resolve(result);
}
});
});
};
};
// 无失败的 Promise 包装器
const promisifyNoFail = (context, methodName, defaultValue = undefined) => {
try {
const promisified = promisify(context, methodName);
return async function (...args) {
try {
return await promisified(...args);
} catch (error) {
console.warn(`WebExt API call failed: ${methodName}`, error);
return defaultValue;
}
};
} catch (error) {
// 如果方法不存在,返回空函数
return async function () {
console.warn(`WebExt API method not available: ${methodName}`);
return defaultValue;
};
}
};
// 专门的 sendMessage 包装器
const createSendMessage = (runtimeAPI) => {
if (!runtimeAPI || !runtimeAPI.sendMessage) {
return async () => {
console.warn("runtime.sendMessage not available");
return null;
};
}
return function (...args) {
return new Promise((resolve, reject) => {
try {
if (args.length === 1) {
// sendMessage(message)
runtimeAPI.sendMessage(args[0], (response) => {
if (api.runtime.lastError) {
console.warn("SendMessage failed:", api.runtime.lastError.message);
resolve(null);
} else {
resolve(response);
}
});
} else if (args.length === 2) {
// 检查第二个参数是否是函数(回调)
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) {
// sendMessage(extensionId, message)
runtimeAPI.sendMessage(args[0], args[1], (response) => {
if (api.runtime.lastError) {
console.warn("SendMessage failed:", api.runtime.lastError.message);
resolve(null);
} else {
resolve(response);
}
});
} else {
// sendMessage(message, options)
runtimeAPI.sendMessage(args[0], args[1], (response) => {
if (api.runtime.lastError) {
console.warn("SendMessage failed:", api.runtime.lastError.message);
resolve(null);
} else {
resolve(response);
}
});
}
} else if (args.length === 3) {
// 检查第三个参数是否是函数(回调)
if (typeof args[2] === 'function') {
// sendMessage(extensionId, message, callback) 或 sendMessage(message, options, callback)
runtimeAPI.sendMessage(args[0], args[1], args[2]);
resolve(undefined); // 立即解析 Promise
} else {
// 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);
resolve(null);
} else {
resolve(response);
}
});
}
} 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
} else {
console.warn("Invalid arguments for sendMessage:", args);
resolve(null);
}
} catch (error) {
console.warn("SendMessage error:", error);
resolve(null);
}
});
};
};
// 专门的 tabs.sendMessage 包装器
const createTabsSendMessage = (tabsAPI) => {
if (!tabsAPI || !tabsAPI.sendMessage) {
return async () => {
console.warn("tabs.sendMessage not available");
return null;
};
}
return function (tabId, message, options = {}) {
return new Promise((resolve, reject) => {
try {
if (arguments.length === 2) {
// sendMessage(tabId, message)
tabsAPI.sendMessage(tabId, message, (response) => {
if (api.runtime.lastError) {
console.warn("Tabs sendMessage failed:", api.runtime.lastError.message);
resolve(null);
} else {
resolve(response);
}
});
} else {
// sendMessage(tabId, message, options)
tabsAPI.sendMessage(tabId, message, options, (response) => {
if (api.runtime.lastError) {
console.warn("Tabs sendMessage failed:", api.runtime.lastError.message);
resolve(null);
} else {
resolve(response);
}
});
}
} catch (error) {
console.warn("Tabs sendMessage error:", error);
resolve(null);
}
});
};
};
return {
// Runtime API
runtime: {
getManifest: () => api.runtime.getManifest(),
getURL: (path) => api.runtime.getURL(path),
sendMessage: createSendMessage(api.runtime),
connect: (connectInfo) => api.runtime.connect(connectInfo),
onMessage: api.runtime.onMessage,
onConnect: api.runtime.onConnect,
onInstalled: api.runtime.onInstalled,
onStartup: api.runtime.onStartup,
lastError: api.runtime.lastError,
id: api.runtime.id,
},
// 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: () => {} },
},
// Storage API
storage: {
local: {
get: promisifyNoFail(api.storage.local, "get", {}),
set: promisifyNoFail(api.storage.local, "set"),
remove: promisifyNoFail(api.storage.local, "remove"),
clear: promisifyNoFail(api.storage.local, "clear"),
getBytesInUse: promisifyNoFail(api.storage.local, "getBytesInUse", 0),
},
sync: api.storage.sync
? {
get: promisifyNoFail(api.storage.sync, "get", {}),
set: promisifyNoFail(api.storage.sync, "set"),
remove: promisifyNoFail(api.storage.sync, "remove"),
clear: promisifyNoFail(api.storage.sync, "clear"),
getBytesInUse: promisifyNoFail(
api.storage.sync,
"getBytesInUse",
0
),
}
: null,
},
// 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: () => {} },
},
// Browser Action / Action API (兼容不同版本)
action: (() => {
// Manifest V3 使用 action,V2 使用 browserAction
const actionAPI = api.action || api.browserAction;
if (!actionAPI) return null;
return {
setTitle: promisifyNoFail(actionAPI, "setTitle"),
getTitle: promisifyNoFail(actionAPI, "getTitle"),
setIcon: promisifyNoFail(actionAPI, "setIcon"),
setPopup: promisifyNoFail(actionAPI, "setPopup"),
getPopup: promisifyNoFail(actionAPI, "getPopup"),
setBadgeText: promisifyNoFail(actionAPI, "setBadgeText"),
getBadgeText: promisifyNoFail(actionAPI, "getBadgeText"),
setBadgeBackgroundColor: promisifyNoFail(
actionAPI,
"setBadgeBackgroundColor"
),
getBadgeBackgroundColor: promisifyNoFail(
actionAPI,
"getBadgeBackgroundColor"
),
setBadgeTextColor: promisifyNoFail(actionAPI, "setBadgeTextColor"),
getBadgeTextColor: promisifyNoFail(actionAPI, "getBadgeTextColor"),
enable: promisifyNoFail(actionAPI, "enable"),
disable: promisifyNoFail(actionAPI, "disable"),
onClicked: actionAPI.onClicked,
};
})(),
// Permissions API
permissions: {
contains: promisifyNoFail(api.permissions, "contains", false),
getAll: promisifyNoFail(api.permissions, "getAll", {
origins: [],
permissions: [],
}),
request: promisifyNoFail(api.permissions, "request", false),
remove: promisifyNoFail(api.permissions, "remove", false),
onAdded: api.permissions?.onAdded,
onRemoved: api.permissions?.onRemoved,
},
// i18n API
i18n: {
getMessage: (messageName, substitutions) =>
api.i18n.getMessage(messageName, substitutions),
getAcceptLanguages: promisifyNoFail(api.i18n, "getAcceptLanguages", []),
getUILanguage: () => api.i18n.getUILanguage(),
detectLanguage: promisifyNoFail(api.i18n, "detectLanguage"),
},
};
})();
// 导出接口
if (typeof module !== "undefined" && module.exports) {
// Node.js 环境
module.exports = { webext, webextFlavor };
} else if (typeof window !== "undefined") {
// 浏览器环境(内容脚本、弹出窗口等)
window.webext = webext;
window.webextFlavor = webextFlavor;
console.log("WebExt compatibility layer loaded in browser environment");
} else if (typeof self !== "undefined") {
// 服务工作器环境
self.webext = webext;
self.webextFlavor = webextFlavor;
console.log("WebExt compatibility layer loaded in service worker environment");
} else {
// 全局环境
this.webext = webext;
this.webextFlavor = webextFlavor;
console.log("WebExt compatibility layer loaded in global environment");
}