56 Commits
Author SHA1 Message Date
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
GitHub Action 385e0ab2fa chore: 更新版本到 1.3.0 [skip ci] 2025-07-19 06:53:33 +00:00
xixu-me 18a863a73b Update extension description and manifest formatting
Revised the extension description in both manifest.json and package.json to clarify supported sites and improve user understanding. Also updated manifest.json formatting for consistency and readability.
2025-07-19 14:52:42 +08:00
GitHub Action 2e2dc34dd0 chore: 更新版本到 1.3.0 [skip ci] 2025-07-18 12:14:55 +00:00
xixu-me 599d0c6217 Update GitHub links to Xget-for-Chromium
Replaces all incorrect or broken GitHub URLs referencing 'Xget for Chromium' with the correct 'Xget-for-Chromium' format across documentation, workflow, package.json, and popup.html. This ensures consistency and prevents navigation errors for users and contributors.
2025-07-18 20:13:04 +08:00
xixu-me 37a326486b Rename extension references from Chrome to Chromium
Updated all documentation, configuration, and code references from 'Xget for Chrome' to 'Xget for Chromium' for consistency with the supported browser family. This includes issue templates, workflow files, privacy and security policies, README, manifest, package metadata, background and content scripts, and popup UI.
2025-07-18 20:09:45 +08:00
xixu-me f3830e66d4 Update privacy policy for Edge support and date
Clarified that extension settings sync applies to both Chrome and Microsoft Edge, and updated the last modified date to 2025-07-18.
2025-07-18 20:07:37 +08:00
xixu-me d24429fdfa Revert "Rename extension references to Chromium"
This reverts commit ef7aca33f7.
2025-07-18 20:04:44 +08:00
xixu-me ef7aca33f7 Rename extension references to Chromium
Updated all documentation, configuration, and code references from 'Xget for Chrome' to 'Xget for Chromium' for consistency with supported browsers. This includes issue templates, workflow files, privacy and security policies, source code logs, manifest, and popup UI.
2025-07-18 20:03:21 +08:00
xixu-me 36e6eea15b Update manual install guide link in workflow
Changed the manual installation guide link in publish-extension.yml to use the correct anchor for installation instructions in Chinese.
2025-07-12 18:23:11 +08:00
xixu-me a4c5a681d5 Merge branch 'main' of https://github.com/xixu-me/Xget-for-Chrome 2025-07-12 18:19:30 +08:00
xixu-me 1c80aedf67 Update publish message formatting in workflow
Removed the 'v' prefix from the version variable in the success notification message to ensure consistent formatting when publishing the Chrome extension.
2025-07-12 18:19:27 +08:00
GitHub Action 337be3e62e chore: 更新版本到 1.2.0 [skip ci] 2025-07-12 10:17:01 +00:00
xixu-me de7b76b150 Localize publish workflow to Chinese
Translated all workflow names, step descriptions, comments, and release notes in .github/workflows/publish-extension.yml from English to Chinese to improve accessibility for Chinese-speaking developers.
2025-07-12 18:16:24 +08:00
xixu-me d37e74e53f Update privacy policy and README wording
Revised PRIVACY_POLICY.md for clarity, transparency, and updated effective date. README.md wording changed from '仓库' to '存储库' for consistency, and other minor language improvements for accuracy and readability.
2025-07-12 10:57:37 +08:00
xixu-me a1e258a4b8 Localize documentation and extension messages to Chinese
Converted all major documentation files (README, PRIVACY_POLICY, SECURITY, pull request template) to Chinese. Updated extension source files (background.js, content.js, popup.html, popup.js) to use Chinese comments and user-facing messages for improved localization and user experience.
2025-07-12 10:49:56 +08:00
xixu-me 40103d8a05 Update README.md 2025-07-11 22:32:54 +08:00
xixu-me bc0772f292 Update Microsoft Edge Add-ons store link
Changed the Microsoft Edge Add-ons store link in both the workflow and README to reflect the new extension URL.
2025-07-11 19:08:07 +08:00
xixu-me 3094b7970f Update release notes formatting in workflow
Improved the formatting of the release notes in the publish-extension workflow by changing headings to use '##' and removing redundant version references. Also removed the 'Changes' section to streamline the release body.
2025-07-11 18:40:56 +08:00
xixu-me e52c4c3ec6 Add dynamic tag and name to GitHub Release step
The GitHub Release workflow now sets the release tag and name using the extension version output, improving release traceability and automation.
2025-07-11 18:35:55 +08:00
20 changed files with 3561 additions and 778 deletions

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ body:
id: version
attributes:
label: Extension Version
description: What version of Xget for Chrome are you using?
description: What version of Xget Now are you using?
placeholder: "1.0.0"
validations:
required: true
+1 -1
View File
@@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: |
Have a question about Xget for Chrome? We're here to help!
Have a question about Xget Now? We're here to help!
- type: dropdown
id: category
+41 -41
View File
@@ -1,62 +1,62 @@
# Pull Request
## Description
<!-- Provide a brief description of the changes in this PR -->
## 描述
<!-- 简要描述此 PR 中的更改 -->
## Type of Change
<!-- Mark the relevant option with an "x" -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
- [ ] Code refactoring (no functional changes)
- [ ] Performance improvement
## 更改类型
<!-- 用 "x" 标记相关选项 -->
- [ ] Bug 修复(修复问题的非破坏性更改)
- [ ] 新功能(添加功能的非破坏性更改)
- [ ] 破坏性更改(会导致现有功能无法正常工作的修复或功能)
- [ ] 文档更新
- [ ] 代码重构(无功能更改)
- [ ] 性能改进
## Related Issues
<!-- Link any related issues using #issue_number -->
Fixes #(issue_number)
## 相关问题
<!-- 使用 #issue_number 链接任何相关问题 -->
修复 #(问题编号)
## Changes Made
<!-- Describe the specific changes made in this PR -->
## 所做更改
<!-- 描述此 PR 中的具体更改 -->
-
-
-
## Testing
<!-- Describe how you tested these changes -->
- [ ] Tested on GitHub
- [ ] Tested on GitLab
- [ ] Tested on Hugging Face
- [ ] Tested extension popup/settings
- [ ] Tested with multiple file types
- [ ] Verified no console errors
## 测试
<!-- 描述你如何测试这些更改 -->
- [ ] 在 GitHub 上测试
- [ ] 在 GitLab 上测试
- [ ] 在 Hugging Face 上测试
- [ ] 测试扩展弹出窗口/设置
- [ ] 测试多种文件类型
- [ ] 验证无控制台错误
## Platform Compatibility
<!-- Check all platforms where you've tested the changes -->
## 平台兼容性
<!-- 检查你测试更改的所有平台 -->
- [ ] GitHub
- [ ] GitLab
- [ ] Hugging Face
## Browser Testing
<!-- Check browsers where you've tested -->
## 浏览器测试
<!-- 检查你测试过的浏览器 -->
- [ ] Chrome
- [ ] Microsoft Edge
- [ ] Brave
- [ ] Other: ____________
- [ ] 其他:____________
## Screenshots/Videos
<!-- Add screenshots or videos if applicable -->
## 截图/视频
<!-- 如果适用,添加截图或视频 -->
## Checklist
## 检查清单
- [ ] I have read and followed the project guidelines
- [ ] My code follows the existing code style
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] My changes generate no new console errors or warnings
- [ ] I have tested that existing functionality still works
- [ ] Any new features include appropriate error handling
- [ ] The extension manifest version has been updated if needed
- [ ] 我已阅读并遵循项目指南
- [ ] 我的代码遵循现有代码风格
- [ ] 我已对我的代码进行自我审查
- [ ] 我已对我的代码进行注释,特别是在难以理解的地方
- [ ] 我的更改不会产生新的控制台错误或警告
- [ ] 我已测试现有功能仍然正常工作
- [ ] 任何新功能都包含适当的错误处理
- [ ] 如果需要,扩展清单版本已更新
## Additional Notes
<!-- Add any additional notes or context for reviewers -->
## 附加说明
<!-- 为审查者添加任何附加说明或上下文 -->
+142 -117
View File
@@ -1,230 +1,255 @@
name: Publish Extension to Multiple Stores
name: 发布扩展到多个应用商店
# 工作流触发条件:手动触发,允许指定版本号
on:
workflow_dispatch:
inputs:
version:
description: "Version number (e.g., 1.0.0)"
description: "版本号 (例如: 1.0.0)"
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: Checkout code
uses: actions/checkout@v4
# 第一步:检出源代码
- name: 检出代码
uses: actions/checkout@v5
- name: Setup Node.js
# 第二步:设置 Python 环境用于构建脚本
- name: 设置 Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
# 第三步:设置 Node.js 环境用于 JSON 验证
- name: 设置 Node.js
uses: actions/setup-node@v4
with:
node-version: "18"
- name: Validate manifest.json
# 第四步:验证 manifest 文件格式
- name: 验证 manifest 文件
run: |
# Validate that manifest.json is valid JSON
# 验证 Chrome manifest.json 是否为有效的 JSON
node -e "JSON.parse(require('fs').readFileSync('manifest.json', 'utf8'))"
echo "✅ manifest.json is valid"
echo "✅ Chrome manifest.json 格式有效"
- name: Set version from input
# 验证 Firefox manifest 是否为有效的 JSON
node -e "JSON.parse(require('fs').readFileSync('manifest-firefox.json', 'utf8'))"
echo "✅ Firefox manifest.json 格式有效"
# 第五步:设置版本号
- name: 从输入设置版本
id: version
run: |
# Use the manually provided version
# 从工作流输入获取版本号
VERSION="${{ inputs.version }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Manually set version: $VERSION"
echo "手动设置版本: $VERSION"
- name: Update manifest version
# 第六步:更新所有配置文件中的版本号
- name: 更新 manifest 版本
run: |
# Update version in manifest.json to match the tag
# 更新 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('Updated manifest.json version to:', manifest.version);
console.log('已更新 Chrome manifest.json 版本为:', manifest.version);
"
- name: Update package.json 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: |
# Update version in package.json to match the tag
# 更新 package.json 中的版本以保持一致性
node -e "
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
pkg.version = '${{ steps.version.outputs.version }}';
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
console.log('Updated package.json version to:', pkg.version);
console.log('已更新 package.json 版本为:', pkg.version);
"
- name: Commit and push version updates
# 第八步:提交版本更改并创建 Git 标签
- name: 提交并推送版本更新
run: |
# Configure git user for the commit
# 配置 Git 用户信息用于自动提交
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
# Add the updated files
git add manifest.json package.json
# 添加更新的文件
git add manifest.json manifest-firefox.json package.json
# Check if there are changes to commit
# 检查是否有需要提交的更改
if git diff --staged --quiet; then
echo "No version changes to commit"
echo "没有版本更改需要提交"
else
# Commit the version updates
git commit -m "chore: update version to ${{ steps.version.outputs.version }} [skip ci]"
# 提交版本更新
git commit -m "chore: 更新版本到 ${{ steps.version.outputs.version }} [skip ci]"
# Create and push a new tag for this version
# 为此版本创建并推送新标签
git tag "v${{ steps.version.outputs.version }}"
# Push to the main branch with the tag
# 推送到主分支并包含标签
git push origin HEAD:main
git push origin "v${{ steps.version.outputs.version }}"
echo "✅ Version updates committed and pushed to main branch with tag"
echo "✅ 版本更新已提交并推送到主分支,包含标签"
fi
- name: Create extension packages
- name: 构建扩展包
run: |
# Create Chrome extension zip
zip -r chrome-extension.zip . \
-x "*.git*" \
-x "node_modules/*" \
-x ".github/*" \
-x "*.md" \
-x "LICENSE" \
-x "*.zip" \
-x "*.crx"
echo "🏗️ 开始构建扩展包..."
# Create Microsoft Edge extension zip (same as Chrome for MV3)
cp chrome-extension.zip edge-extension.zip
# 运行构建脚本构建所有平台
python build.py --platform all --package
echo "📦 Created extension packages"
ls -la *.zip
echo "📦 扩展包构建完成"
ls -la packages/ || echo "packages 目录不存在"
- name: Create CRX file for GitHub release
- name: 运行测试
run: |
# Install chrome-extension-packer
npm install -g crx3
echo "🧪 运行基础测试..."
python dev.py test
# Create private key for CRX signing (for distribution only)
openssl genrsa -out key.pem 2048
echo "🔍 运行代码检查..."
python dev.py lint
# Verify manifest.json exists
ls -la manifest.json
echo "Current directory: $(pwd)"
echo "Contents:"
ls -la
# Create temporary directory for extension files
mkdir -p extension-temp
# Copy extension files to temp directory
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/
# Verify temp directory contents
echo "Extension temp directory contents:"
ls -la extension-temp/
# Create CRX file from temp directory
cd extension-temp
crx3 -o ../extension.crx -p ../key.pem .
cd ..
echo "📦 Created extension.crx"
ls -la extension.crx
# Clean up
rm -rf extension-temp
- name: Publish to Chrome Web Store
- 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 # Set to false if you want to upload without publishing
publish: true
- name: Publish to Microsoft Edge Add-ons
- name: 发布到 Microsoft 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: "Automatic release v${{ steps.version.outputs.version }} - ${{ github.sha }}"
notes-for-certification: "自动发布 v${{ steps.version.outputs.version }} - ${{ github.sha }}"
- name: Create GitHub Release
- 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 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: |
## Xget for Chrome v${{ steps.version.outputs.version }}
## 📦 扩展包下载
This release has been automatically published to multiple extension stores.
### Chromium
- **文件**: `Xget-Now_${{ steps.version.outputs.version }}.chromium.zip`
- **支持**: Chrome、Microsoft Edge 等基于 Chromium 的浏览器
- **Manifest**: v3
### 📦 Installation Options
### Firefox
- **文件**: `Xget-Now_${{ steps.version.outputs.version }}.firefox.xpi`
- **支持**: Firefox 或其他基于 Firefox 的浏览器
- **Manifest**: v2
- **Chrome Web Store**: [Install from Chrome Web Store](https://chromewebstore.google.com/detail/ajiejgobfcifcikbahpijopolfjoodgf)
- **Microsoft Edge Add-ons**: [Install from Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/hbfflpongojnfojbgadppjgnkabkpjea)
- **Manual Installation**: Download the files below or see the [Installation Guide](https://github.com/xixu-me/Xget-for-Chrome?tab=readme-ov-file#-installation) for detailed instructions
## 🚀 安装方法
### 📁 Available Files
详细的安装说明请参考:[手动安装指南](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` - For Chrome browsers
- `edge-extension.zip` - For Microsoft Edge
- `extension.crx` - Pre-packaged extension file for all Chromium-based browsers
## 📝 更新日志
详细的更改内容请查看下方的自动生成的发布说明。
### Changes
- See commit history for detailed changes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check publication results
- name: 检查发布结果
run: |
echo "📊 Publication Results Summary:"
echo "📊 发布结果摘要:"
echo "================================"
echo "📦 版本: ${{ steps.version.outputs.version }}"
echo ""
if [ "${{ steps.chrome-publish.outcome }}" = "success" ]; then
echo "✅ Chrome Web Store: Successfully published"
echo "✅ Chrome 应用商店: 发布成功"
elif [ "${{ steps.chrome-publish.outcome }}" = "failure" ]; then
echo "❌ Chrome Web Store: Publication failed"
echo "❌ Chrome 应用商店: 发布失败"
else
echo "⚠️ Chrome Web Store: Skipped (missing secrets)"
echo "⚠️ Chrome 应用商店: 已跳过(缺少密钥或其他原因)"
fi
if [ "${{ steps.edge-publish.outcome }}" = "success" ]; then
echo "✅ Microsoft Edge Add-ons: Successfully published"
echo "✅ Microsoft Edge 加载项: 发布成功"
elif [ "${{ steps.edge-publish.outcome }}" = "failure" ]; then
echo "❌ Microsoft Edge Add-ons: Publication failed"
echo "❌ Microsoft Edge 加载项: 发布失败"
else
echo "⚠️ Microsoft Edge Add-ons: Skipped (missing secrets)"
echo "⚠️ Microsoft Edge 加载项: 已跳过(缺少密钥或其他原因)"
fi
echo "✅ GitHub Release: Created with extension files"
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: Notify success
- name: 通知成功
run: |
echo "🎉 Successfully published Xget for Chrome v${{ steps.version.outputs.version }} to multiple stores!"
echo "🔗 Chrome Web Store: https://chrome.google.com/webstore/detail/${{ secrets.CHROME_EXTENSION_ID }}"
echo "🔗 Microsoft Edge Add-ons: Check the releases page for manual installation files"
echo "📦 GitHub Release: https://github.com/${{ github.repository }}/releases/tag/v${{ steps.version.outputs.version }}"
echo ""
echo "🎉 成功发布 Xget Now ${{ steps.version.outputs.version }}!"
echo "================================"
echo "🔗 Chrome 应用商店: https://chrome.google.com/webstore/detail/${{ secrets.CHROME_EXTENSION_ID }}"
echo "🔗 Microsoft 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@v5
with:
python-version: "3.11"
- name: 设置 Node.js
uses: actions/setup-node@v4
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@v4
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 "🚀 准备发布!"
+142 -126
View File
@@ -1,202 +1,218 @@
# Privacy Policy for Xget for Chrome
# 隐私政策
## Last updated: July 9, 2025
## 生效日期:2025 年 8 月 10 日
## Overview
## 概述
Xget for Chrome is a browser extension that accelerates downloads by redirecting them through Xget proxy servers. This privacy policy explains how we collect, use, and protect your information when you use our extension.
Xget Now 是一个开源浏览器扩展,旨在通过 Xget 代理服务重定向下载链接来加速众多软件开发和研究平台的下载,包括 GitHub、GitLab、Hugging Face、npm、PyPI、Maven、RubyGems、CRAN 等 20+ 个平台。我们承诺保护您的隐私并确保数据处理的透明度。
## Information We Collect
## 我们的隐私承诺
### 1. User Settings and Configuration Data
**我们不收集、存储或传输您的个人数据。**
We collect and store the following information locally on your device:
Xget Now 扩展遵循“隐私优先”的设计原则,所有处理都在您的浏览器本地进行。
- **Xget Domain Configuration**: The domain you configure for the Xget service (e.g., "xget.xi-xu.me")
- **Extension Enable/Disable Status**: Whether the extension is currently enabled
- **Platform Preferences**: Your preferences for which platforms (GitHub, GitLab, Hugging Face) to use with the extension
## 收集的信息
### 2. Download Activity Data
### 我们收集的信息
The extension processes the following data during operation:
**无**。该扩展不会收集、记录或传输任何个人身份信息、浏览历史记录或用户数据到外部服务器。
- **Download URLs**: URLs of files you attempt to download from supported platforms
- **Referrer Information**: The webpage from which downloads are initiated
- **File Information**: Basic file metadata like filenames when available
### 我们不收集的信息
### 3. Browser Interaction Data
- 个人身份信息(姓名、电子邮件、地址等)
- 浏览历史记录或访问的网站
- 下载的文件或下载历史
- IP 地址或位置信息
- 设备信息或浏览器指纹
- 任何形式的分析或使用统计
- **Tab Information**: Limited tab data to send notifications about redirected downloads
- **Click Events**: Detection of clicks on download links to trigger redirection
## 本地数据存储
## How We Use Your Information
### 扩展设置
### Local Processing Only
该扩展仅在您的浏览器本地存储以下设置:
- All settings and preferences are stored locally in your browser using Chrome's storage API
- Download URL transformation happens locally within the extension
- No personal data is transmitted to our servers
- **Xget 域名配置**:您设置的自定义 Xget 服务域名
- **扩展启用状态**:扩展是否启用的开关状态
- **平台偏好设置**:为支持的各个平台(GitHub、GitLab、Hugging Face、npm、PyPI、Maven、RubyGems、CRAN 等)启用/禁用的选择
### Download Redirection
这些设置:
- We modify download URLs to redirect through your configured Xget domain
- The original download URLs are processed locally to create Xget-compatible URLs
- Redirected downloads are handled by the Xget service according to their privacy policy
- 仅存储在您的浏览器中
- 不会发送到我们的服务器或任何第三方
- 可以随时通过扩展设置进行修改或删除
- 在卸载扩展时会被完全清除
### Notifications
### 数据同步
- We display local notifications to inform you when downloads are redirected
- These notifications appear only in your browser and are not logged or transmitted
如果您已登录 Chrome/Microsoft Edge 并启用了扩展同步,您的扩展设置可能会在您的设备之间同步。这是由 Google/Microsoft 的 Chrome/Microsoft Edge 同步服务处理的,不涉及我们的服务器。
## Data Storage and Security
## 数据处理
### Local Storage
### URL 转换处理
- All extension settings are stored using Chrome's synchronized storage API
- Settings may sync across your Chrome browsers if you're signed into Chrome
- No sensitive personal information is stored
当您点击支持平台上的下载链接时,扩展会:
### Data Retention
1. **在本地检测**下载链接
2. **在您的浏览器中转换** URL 格式以与 Xget 兼容
3. **重定向**您到转换后的 Xget URL
- Settings persist until you uninstall the extension or manually clear them
- Download URLs are not stored; they are processed in real-time only
- No browsing history or personal files are retained
**重要**:此 URL 转换完全在您的浏览器中进行。原始 URL 和转换后的 URL 都不会发送给我们。
### Security Measures
### 与 Xget 服务的交互
- All data processing occurs locally within your browser
- No external communication except for redirected downloads through your configured Xget domain
- Extension follows Chrome's security model and permissions system
当您使用扩展时:
## Third-Party Services
- 您的下载请求直接发送到您配置的 Xget 服务(如 `xget.xi-xu.me`)
- 我们不充当中介或收集有关这些请求的任何信息
- Xget 服务有自己的隐私政策,管理其数据处理实践
### Xget Service
## 第三方服务
- When you download files, they are redirected through the Xget service you configure
- The Xget service may have its own privacy policy governing how they handle downloads
- We recommend reviewing the privacy policy of your chosen Xget service provider
### Xget 代理服务
### Supported Platforms
该扩展将下载重定向到 Xget 代理服务。请注意:
The extension operates on these platforms:
- **默认服务**:`xget.xi-xu.me`(如果使用)
- **自定义服务**:您可以配置任何兼容的 Xget 实例
- **数据处理**:您的下载请求由 Xget 服务处理,不由此扩展处理
- **GitHub** (github.com)
- **GitLab** (gitlab.com)
- **Hugging Face** (huggingface.co)
**重要**:请查看您使用的 Xget 服务的隐私政策,因为您的下载请求将由该服务处理。
We do not collect or store any account information from these platforms.
### 受支持的平台
## Permissions Explained
该扩展在以下平台上运行:
### Why We Need These Permissions
- **代码托管平台**: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
**Downloads Permission**:
扩展不会与这些平台共享任何数据,也不会修改您与这些平台的交互,除了重定向下载链接。
- Required to intercept and redirect download requests
- Allows the extension to cancel original downloads and start redirected ones
## 权限说明
**Storage Permission**:
### 请求的权限
- Used to save your extension settings and preferences locally
- Enables settings synchronization across your Chrome browsers
该扩展请求以下浏览器权限:
**Active Tab Permission**:
1. **`downloads`**:检测和管理下载以进行重定向
2. **`storage`**:在本地存储您的扩展设置
3. **`activeTab`**:在当前标签页中显示通知
4. **`host_permissions`**:访问受支持的 20+ 个平台以检测下载链接
- Needed to send notifications about redirected downloads
- Does not allow reading page content or personal information
### 权限使用
**Host Permissions** (github.com, gitlab.com, huggingface.co):
- **下载权限**:仅用于检测下载并将其重定向到 Xget
- **存储权限**:仅用于保存您的扩展设置
- **标签页权限**:仅用于显示下载重定向通知
- **主机权限**:仅用于在受支持的各个平台上检测下载链接
- Required to run the extension on supported platforms
- Allows detection and redirection of download links
我们不使用这些权限来收集数据、跟踪您的活动或任何其他目的。
**Web Request Permissions**:
## 数据安全
- Used for download interception functionality
- Does not monitor general browsing activity
### 本地处理
## Your Privacy Rights
- 所有扩展逻辑在您的浏览器中本地运行
- 不建立到我们服务器的网络连接
- 没有数据传输到外部分析服务
### Control Over Your Data
### 开源透明性
- You can disable the extension at any time through the popup interface
- You can configure which platforms to use with the extension
- You can change or remove your Xget domain configuration
- Uninstalling the extension removes all stored settings
- 扩展是完全开源的
- 源代码可在 [GitHub](https://github.com/xixu-me/Xget-Now) 上获得
- 您可以审查、验证和审计所有代码
### Data Portability
## 您的权利
- Settings are stored in standard Chrome storage format
- You can export/backup Chrome extension settings through Chrome's sync feature
### 数据控制
### Data Deletion
您可以完全控制扩展存储的有限数据:
- Clear extension data by uninstalling the extension
- Reset settings through the extension popup interface
- Clear Chrome's extension storage to remove all traces
- **查看**:通过扩展弹出窗口查看您的设置
- **修改**:随时更改您的设置
- **删除**:卸载扩展以删除所有存储的数据
## Children's Privacy
### 选择退出
This extension is not intended for use by children under 13 years of age. We do not knowingly collect personal information from children under 13.
您可以随时:
## Changes to This Privacy Policy
- 在扩展设置中禁用扩展
- 为特定平台禁用重定向
- 完全卸载扩展
We may update this privacy policy from time to time. When we do, we will:
## 儿童隐私
- Update the "Last updated" date at the top of this policy
- Notify users through the extension interface if there are material changes
- Maintain previous versions for reference
该扩展不会有意收集 13 岁以下儿童的信息。实际上,该扩展不会收集任何用户的任何个人信息。
## Compliance
## 国际数据传输
This extension is designed to comply with:
由于扩展不收集或传输任何个人数据,因此不涉及国际数据传输。您配置的任何 Xget 服务都将根据其自己的隐私政策处理数据传输。
- Chrome Web Store Developer Policies
- General Data Protection Regulation (GDPR)
- California Consumer Privacy Act (CCPA)
- Other applicable privacy laws
## 政策更新
## Technical Implementation
### 更改通知
### Data Minimization
我们可能会不时更新此隐私政策。任何更改将:
- We collect only the minimum data necessary for functionality
- No analytics, tracking, or telemetry data is collected
- No user identification or profiling occurs
- 在此页面上发布
- 在更新的策略顶部包含新的生效日期
- 如果是重大更改,将通过扩展更新或其他方式通知
### Transparency
### 持续使用
- All source code is available for review
- Extension behavior is predictable and documented
- No hidden data collection or processing
在任何更改后继续使用扩展即表示您接受更新的隐私政策。
## Contact Information
## 联系我们
If you have any questions about this privacy policy or the extension's data practices:
如果您对此隐私政策有任何问题、疑虑或建议,请:
- **Developer**: Xi Xu
- **Repository**: [Xget for Chrome on GitHub](https://github.com/xixu-me/Xget-for-Chrome)
- **Issues**: [Report Issues](https://github.com/xixu-me/Xget-for-Chrome/issues)
- **GitHub Issues**:在我们的 [GitHub 存储库](https://github.com/xixu-me/Xget-Now/issues) 上打开一个 issue
- **电子邮件**:通过我们的 GitHub 配置文件联系维护者
## Open Source Commitment
## 法律合规
This extension is open source software licensed under GPL-3.0. You can:
### 数据保护法律
- Review the complete source code
- Verify our privacy practices
- Submit issues or improvements
- Fork and modify the code
由于该扩展不收集个人数据,因此它符合主要的数据保护法规,包括:
## Data Protection Summary
- **GDPR**(欧盟一般数据保护条例)
- **CCPA**(加州消费者隐私法)
- **其他地区数据保护法律**
| Data Type | Collection | Storage | Sharing | Retention |
|-----------|------------|---------|---------|-----------|
| Extension Settings | Local only | Browser storage | Chrome sync only | Until uninstall |
| Download URLs | Processed locally | Not stored | Redirected only | Real-time only |
| Personal Information | None | None | None | None |
| Browsing History | None | None | None | None |
| User Identity | None | None | None | None |
### 最小数据实践
该扩展遵循数据最小化原则:
- 仅存储功能绝对必要的数据
- 不收集可识别或敏感信息
- 实施最小权限访问
## 技术实施
### 隐私设计
该扩展实施了以下隐私保护措施:
- **无网络通信**:除了重定向下载外,不进行任何网络调用
- **本地处理**:所有逻辑在浏览器中执行
- **无跟踪**:不包含分析、跟踪或遥测代码
- **最小权限**:仅请求功能所需的权限
### 代码审计
作为开源项目:
- 所有代码都是可验证的
- 社区可以审计隐私实践
- 不包含隐藏或混淆的功能
---
By using Xget for Chrome, you acknowledge that you have read and understood this privacy policy and agree to the data practices described herein.
**最后更新**:2025 年 8 月 10 日
**联系方式**:[GitHub Issues](https://github.com/xixu-me/Xget-Now/issues)
此隐私政策是我们对透明度和用户隐私承诺的一部分。我们相信您有权了解您使用的软件如何处理您的数据,以及在这种情况下,确认它不会处理您的个人数据。
+210 -172
View File
@@ -1,257 +1,295 @@
# Xget for Chrome
# Xget Now
A powerful Chrome extension that accelerates downloads from GitHub, GitLab, and Hugging Face by automatically redirecting them through the [Xget](https://github.com/xixu-me/Xget) proxy service.
为 Chromium 和 Firefox 浏览器提供的 [Xget](https://github.com/xixu-me/Xget) 下载加速扩展,支持 GitHub、GitLab、npm、PyPI 等众多平台的文件下载加速。
## 🚀 Features
## 🚀 功能特性
- **🎯 Automatic Download Acceleration**: Seamlessly redirects downloads through Xget for faster speeds
- **🌐 Multi-Platform Support**: Works with GitHub, GitLab, and Hugging Face
- **⚙️ Configurable Settings**: Customize your Xget domain and platform preferences
- **🔔 Smart Notifications**: Visual feedback when downloads are redirected
- **🛡️ Privacy-First**: All processing happens locally in your browser
- **🎛️ Per-Platform Control**: Enable/disable acceleration for specific platforms
- **🎯 自动下载加速**:无缝重定向下载通过 Xget 获得更快速度
- **⚙️ 可配置设置**:自定义你的 Xget 域名和平台偏好
- **🔔 智能通知**:下载重定向时的可视化反馈
- **🛡️ 隐私优先**:所有处理都在你的浏览器本地进行
- **🎛️ 按平台控制**:为特定平台启用/禁用加速
- **🌐 跨浏览器支持**:同时支持 Chromium 和 Firefox 浏览器
## 📦 Installation
## 📦 安装
### Store Availability
### 商店可用性
| Store | Link |
|-------|------|
| **Chrome Web Store** | [![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 Add-ons** | [Available in the Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/hbfflpongojnfojbgadppjgnkabkpjea) |
- [Chrome 应用商店](https://chromewebstore.google.com/detail/ajiejgobfcifcikbahpijopolfjoodgf?hl=zh-CN)
- [Firefox 附加组件](https://addons.mozilla.org/zh-CN/firefox/addon/xget-now/)
- [Microsoft Edge 加载项](https://microsoftedge.microsoft.com/addons/detail/jigpfhbegabdenhihpplcjhpfdcgnalc?hl=zh-CN&gl=CN)
### Manual Installation
### 手动安装
If you prefer to install the extension manually or the store versions are not available, you can download and install it directly:
如果你更喜欢手动安装扩展或应用商店版本不可用,你可以直接下载并安装:
#### Option 1: Install from GitHub Releases (Recommended)
#### 选项 1:从 GitHub Releases 安装(推荐)
1. **Download the Extension**
- Go to the [Releases page](https://github.com/xixu-me/Xget-for-Chrome/releases)
- Download the latest release file:
- `chrome-extension.zip` - For Chrome browsers
- `edge-extension.zip` - For Microsoft Edge
- `extension.crx` - Pre-packaged extension file for all Chromium-based browsers
1. **下载扩展**
- 前往 [Releases 页面](https://github.com/xixu-me/Xget-Now/releases/latest)
- 下载对应浏览器的扩展文件:
- `Xget-Now_x.x.x.chromium.zip` - 适用于所有基于 Chromium 的浏览器(Chrome、Microsoft Edge、Opera 等)
- `Xget-Now_x.x.x.firefox.zip` - 适用于 Firefox 浏览器
2. **Install in Chrome**
- Open Chrome and go to `chrome://extensions/`
- Enable "Developer mode" (toggle in top right)
- Click "Load unpacked" and select the extracted folder from the ZIP file
- Or drag and drop the `.crx` file directly onto the extensions page
2. **在 Chrome 中安装**
- 解压下载的 Chromium 版本 ZIP 文件
- 打开 Chrome 并前往 `chrome://extensions/`
- 启用「开发者模式」(右上角的切换开关)
- 点击「加载已解压的扩展程序」并选择解压的文件夹
3. **Install in Microsoft Edge**
- Open Microsoft Edge and go to `edge://extensions/`
- Enable "Developer mode" (toggle in left sidebar)
- Click "Load unpacked" and select the extracted folder from the ZIP file
- Or drag and drop the `.crx` file directly onto the extensions page
3. **在 Firefox 中安装**
- 打开 Firefox 并前往 `about:debugging`
- 点击「此 Firefox」
- 点击「临时载入附加组件」
- 选择下载的 Firefox 版本 ZIP 文件或解压后的 `manifest.json` 文件
4. **Install in other Chromium-based browsers**
- Follow similar steps as Chrome or Microsoft Edge, ensuring you enable "Developer mode" first
- Drag and drop the `.crx` file directly onto the extensions page
4. **在 Microsoft Edge 中安装**
- 解压下载的 Chromium 版本 ZIP 文件
- 打开 Microsoft Edge 并前往 `edge://extensions/`
- 启用「开发人员模式」(左侧边栏的切换开关)
- 点击「加载解压缩的扩展」并选择解压的文件夹
#### Option 2: Install from Source Code
5. **在其他基于 Chromium 的浏览器中安装**
- 使用 Chromium 版本的扩展包
- 遵循与 Chrome 或 Microsoft Edge 类似的步骤,确保首先启用「开发者模式」
- 按照相应浏览器的扩展安装指南进行操作
1. **Clone the Repository**
#### 选项 2:从源码安装
1. **克隆存储库**
```bash
git clone https://github.com/xixu-me/Xget-for-Chrome.git
cd Xget-for-Chrome
git clone https://github.com/xixu-me/Xget-Now.git
cd Xget-Now
```
2. **Load in Browser**
- Open your browser's extension management page
- Enable "Developer mode"
- Click "Load unpacked" and select the cloned folder
2. **构建扩展(可选)**
#### Verify Installation
如果你想使用优化过的版本:
After installation, you should see the Xget extension icon in your browser toolbar. Click it to configure your Xget domain and start accelerating downloads!
```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 # 构建所有版本
```
## ⚙️ Setup
3. **在浏览器中加载**
1. **Configure Xget Domain**
- Click the extension icon in your toolbar
- Enter your Xget domain (e.g., `xget.xi-xu.me`)
- Enable the extension
**Chromium 浏览器:**
- 打开浏览器的扩展管理页面(`chrome://extensions/` 或 `edge://extensions/`)
- 启用「开发者模式」
- 点击「加载已解压的扩展程序」并选择:
- 源码根目录(如果使用原始源码)
- `build/chrome/` 目录(如果使用构建版本)
2. **Choose Platforms** (Optional)
- Toggle individual platforms on/off as needed
- All platforms (GitHub, GitLab, Hugging Face) are enabled by default
**Firefox 浏览器:**
- 打开 `about:debugging`
- 点击「此 Firefox」
- 点击「临时载入附加组件」
- 选择以下文件之一:
- 源码根目录中的 `manifest-firefox.json`
- `build/firefox/` 目录中的 `manifest.json`(如果使用构建版本)
3. **Start Downloading**
- Visit any supported platform
- Click download links as usual
- Downloads will be automatically accelerated through Xget
#### 验证安装
## 🎯 Supported Platforms
安装后,你应该在浏览器工具栏中看到 Xget 扩展图标。点击它来配置你的 Xget 域名并开始加速下载!
| Platform | Status | URL Pattern | Extension ID |
|----------|---------|-------------|--------------|
| **GitHub** | ✅ Supported | `github.com/*` | `gh` |
| **GitLab** | ✅ Supported | `gitlab.com/*` | `gl` |
| **Hugging Face** | ✅ Supported | `huggingface.co/*` | `hf` |
## ⚙️ 设置
### Supported Download Types
1. **配置 Xget 域名**
- 点击工具栏中的扩展图标
- 输入你的 Xget 域名(例如 `xget.xi-xu.me`)
- 启用扩展
- **GitHub**: Release assets, repository archives, raw files, Git LFS files
- **GitLab**: Project archives, release downloads, repository exports
- **Hugging Face**: Model files, dataset files, model cards, tokenizers
2. **选择平台**
- 根据需要切换各个平台的开关
- 默认启用所有平台
## 🔧 How It Works
3. **开始下载**
- 访问任何受支持的平台
- 像往常一样点击下载链接
- 下载将自动通过 Xget 加速
1. **Detection**: The extension monitors download links on supported platforms
2. **Transformation**: URLs are automatically converted to Xget-compatible format
3. **Redirection**: Downloads are routed through your configured Xget domain
4. **Acceleration**: Xget's global CDN and optimization features deliver faster download speeds
## 🔧 工作原理
### URL Transformation Examples
1. **检测**:扩展监控受支持平台上的下载链接
2. **转换**:URL 自动转换为 Xget 兼容格式
3. **重定向**:下载通过你配置的 Xget 域名路由
4. **加速**:Xget 的全球 CDN 和优化功能提供更快的下载速度
```text
Original: https://github.com/user/repo/archive/main.zip
Xget: https://xget.xi-xu.me/gh/user/repo/archive/main.zip
### 为什么 Xget 让下载更快
Original: https://gitlab.com/user/repo/-/archive/main.zip
Xget: https://xget.xi-xu.me/gl/user/repo/-/archive/main.zip
- **边缘缓存**:文件在 Cloudflare 的全球边缘位置进行缓存
- **HTTP/3 支持**:使用最新协议提高性能
- **智能路由**:自动通过最快的可用服务器路由
- **压缩**:自动内容压缩减少传输时间
- **连接优化**:持久连接和预连接功能
Original: https://huggingface.co/user/model/resolve/main/model.bin
Xget: https://xget.xi-xu.me/hf/user/model/resolve/main/model.bin
```
## 📈 性能优势
### Why Xget Makes Downloads Faster
使用 Xget 的实际性能改进:
- **Edge Caching**: Files are cached at Cloudflare's global edge locations
- **HTTP/3 Support**: Uses the latest protocol for improved performance
- **Intelligent Routing**: Automatically routes through the fastest available server
- **Compression**: Automatic content compression reduces transfer times
- **Connection Optimization**: Persistent connections and preconnection features
- **GitHub 发布**:下载速度提升高达 5 倍
- **大型存储库**:多 GB 存档的显著改进
- **国际用户**:通过边缘缓存显著加快下载速度
- **不稳定连接**:自动重试机制处理网络问题
- **并发下载**:与直接平台下载相比,没有速率限制
## 📈 Performance Benefits
## 📋 要求
Real-world performance improvements with Xget:
### 浏览器支持
- **GitHub releases**: Up to 5x faster download speeds
- **Large repositories**: Significant improvement for multi-GB archives
- **International users**: Dramatically faster downloads through edge caching
- **Unstable connections**: Automatic retry mechanisms handle network issues
- **Concurrent downloads**: No rate limiting compared to direct platform downloads
**Chromium 浏览器:**
### Benchmarks
- **Chrome**:版本 88+(Manifest V3 支持)
- **Microsoft Edge**:版本 88+
- **Opera**:版本 74+
- **其他基于 Chromium 的浏览器**:支持 Manifest V3 的版本
| File Type | Original Speed | With Xget | Improvement |
|-----------|----------------|-----------|-------------|
| GitHub Release (100MB) | 2.5 MB/s | 12.8 MB/s | **5.1x faster** |
| Repository Archive (50MB) | 1.8 MB/s | 8.2 MB/s | **4.6x faster** |
| Hugging Face Model (500MB) | 3.2 MB/s | 15.1 MB/s | **4.7x faster** |
**Firefox 浏览器:**
> **Note**: Results may vary based on location, network conditions, and server load
- **Firefox**:版本 109+(Manifest V2 支持)
- **Firefox ESR**:版本 109+
## 📋 Requirements
### Xget 服务要求
- **Chrome Browser**: Version 88+ (Manifest V3 support)
- **Xget Service**: Access to an Xget instance
- Use the public instance: `xget.xi-xu.me`
- Or deploy your own: [Xget Repository](https://github.com/xixu-me/Xget) (Recommended)
- **Xget 服务**:访问 Xget 实例
- 使用公共实例:`xget.xi-xu.me`
- 或部署你自己的:[Xget 存储库](https://github.com/xixu-me/Xget)
### About Xget Service
### 关于 Xget 服务
Xget is a high-performance, secure proxy service built on Cloudflare Workers that provides:
Xget 是基于 Cloudflare Workers 构建的高性能、安全的代理服务,提供:
- **Global Edge Distribution**: Faster downloads through Cloudflare's global network
- **Multi-Platform Support**: Optimized for GitHub, GitLab, and Hugging Face
- **Advanced Features**: HTTP/3 support, intelligent caching, automatic retries
- **Security**: Comprehensive security headers and content protection
- **Reliability**: Built-in timeout protection and performance monitoring
- **全球边缘分发**:通过 Cloudflare 的全球网络加快下载速度
- **高级功能**:HTTP/3 支持、智能缓存、自动重试
- **安全性**:全面的安全标头和内容保护
- **可靠性**:内置超时保护和性能监控
Learn more at [xget.xi-xu.me](https://xget.xi-xu.me) or the [Xget repository](https://github.com/xixu-me/Xget).
在 [xget.xi-xu.me](https://xget.xi-xu.me) 或 [Xget 存储库](https://github.com/xixu-me/Xget) 了解更多。
## 🔒 Privacy & Security
## 🔒 隐私与安全
- **Local Processing**: All URL transformations happen in your browser
- **No Data Collection**: The extension doesn't collect or transmit personal data
- **Minimal Permissions**: Only requests necessary permissions for functionality
- **Open Source**: Full source code available for inspection
- **本地处理**:所有 URL 转换都在你的浏览器中进行
- **无数据收集**:扩展不收集或传输个人数据
- **最小权限**:仅请求功能所需的必要权限
- **开源**:完整源码可供检查
See our [Privacy Policy](PRIVACY_POLICY.md) for complete details.
查看我们的 [隐私政策](PRIVACY_POLICY.md) 了解完整详情。
## 🐛 Troubleshooting
## 🐛 故障排除
### Common Issues
### 常见问题
**Extension not working?**
**扩展不工作?**
- Ensure you've configured a valid Xget domain
- Check that the extension is enabled in the popup
- Verify the target platform is enabled
- Try refreshing the page after changing settings
- 确保你已配置有效的 Xget 域名
- 检查扩展在弹出窗口中是否已启用
- 验证目标平台是否已启用
- 更改设置后尝试刷新页面
**Downloads not being redirected?**
**下载没有被重定向?**
- Refresh the page after changing settings
- Check browser console for error messages (F12 → Console)
- Ensure the link is a recognized download type
- Verify you're clicking actual download links, not navigation links
- 更改设置后刷新页面
- 检查浏览器控制台的错误消息(F12 → 控制台)
- 确保链接是被识别的下载类型
- 验证你点击的是实际的下载链接,而不是导航链接
**Xget domain issues?**
**Xget 域名问题?**
- Domain should be without `https://` protocol
- Example: `xget.xi-xu.me` (not `https://xget.xi-xu.me`)
- Check that the domain is accessible in your browser
- Try using the default public instance: `xget.xi-xu.me`
- 域名应该不带 `https://` 协议
- 示例:`xget.xi-xu.me`(不是 `https://xget.xi-xu.me`)
- 检查域名在浏览器中是否可访问
- 尝试使用默认的公共实例:`xget.xi-xu.me`
**Performance issues?**
**性能问题?**
- Check your network connection
- Try a different Xget domain if available
- Verify the target platform's servers are responsive
- Clear browser cache and reload the extension
- 检查你的网络连接
- 如果可用,尝试其他 Xget 域名
- 验证目标平台的服务器是否响应
- 清除浏览器缓存并重新加载扩展
### Debug Mode
**Firefox 特定问题?**
Enable Chrome Developer Tools and check the Console tab for debug messages:
- 确保 Firefox 版本为 109+
- 在 `about:debugging` 中检查扩展是否正确加载
- Firefox 可能需要重启浏览器才能完全应用扩展设置
- 检查 Firefox 的「隐私和安全」设置是否阻止了扩展功能
- Extension loading: "Xget for Chrome: Content script loaded"
- Download redirection: "Redirecting download: [original] -> [xget]"
- Settings changes: "Settings updated! Click to refresh page"
### 调试模式
## 📄 License
**Chromium 浏览器:**
This project is licensed under the GPL-3.0 License - see the [LICENSE](LICENSE) file for details.
启用 Chrome 开发者工具并检查控制台选项卡中的调试消息:
## ❓ Frequently Asked Questions
**Firefox 浏览器:**
### Is this extension free?
1. 打开 `about:debugging`
2. 点击「此 Firefox」
3. 找到 Xget Now 扩展并点击「检查」
4. 在打开的开发者工具中查看控制台消息
Yes, the extension is completely free and open source under the GPL-3.0 license.
**通用调试消息:**
### Does this work with private repositories?
- 扩展加载:「Xget Now:内容脚本已加载」
- 下载重定向:「重定向下载:[原始] -> [Xget]」
- 设置更改:「设置已更新!点击刷新页面」
The extension works with any downloadable link, but you need appropriate access permissions for private repositories.
## 📄 许可证
### Can I use my own Xget server?
本存储库采用 GPL-3.0 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情。
Absolutely! You can deploy your own Xget instance using the [Xget repository](https://github.com/xixu-me/Xget) and configure the extension to use your domain.
## ❓ 常见问题
### Why do some downloads still go through the original servers?
### 这个扩展是免费的吗?
The extension only redirects recognized download links. Navigation links, preview links, and some dynamic content may not be redirected.
是的,扩展完全免费且在 GPL-3.0 许可证下开源。
### Is my browsing data collected?
### 我可以使用自己的 Xget 服务器吗?
No, the extension operates entirely locally. No browsing data is collected or transmitted. See our [Privacy Policy](PRIVACY_POLICY.md) for details.
当然可以!你可以使用 [Xget 存储库](https://github.com/xixu-me/Xget) 部署自己的 Xget 实例,并配置扩展使用你的域名。
### How much faster are downloads?
### 为什么有些下载仍然通过原始服务器?
Speed improvements vary based on your location, network, and file size. Typical improvements range from 2x to 10x faster.
扩展只重定向被识别的下载链接。导航链接、预览链接和一些动态内容可能不会被重定向。
### Can I disable the extension for specific sites?
### 我的浏览数据会被收集吗?
Yes, use the per-platform toggles in the extension popup to disable acceleration for specific platforms.
不会,扩展完全在本地运行。不收集或传输浏览数据。查看我们的 [隐私政策](PRIVACY_POLICY.md) 了解详情。
## 🌟 Show Your Support
### 下载能快多少?
If you find this extension helpful, please:
速度提升因你的位置、网络和文件大小而异。典型的改进范围从 2 倍到 10 倍。
- ⭐ Star this repository
- 📝 Leave a review on the [Chrome Web Store](https://chromewebstore.google.com/detail/ajiejgobfcifcikbahpijopolfjoodgf)
- 🐛 Report bugs or suggest features via [GitHub Issues](https://github.com/xixu-me/Xget-for-Chrome/issues)
- 📢 Share with others who might benefit from faster downloads
### 我可以为特定网站禁用扩展吗?
是的,使用扩展弹出窗口中的按平台切换开关来禁用特定平台的加速。
### 为什么 Chrome 和 Firefox 版本不同?
由于浏览器架构差异,我们针对不同浏览器进行了优化:
- **Chrome 版本**:使用 Manifest V3,支持最新的扩展 API 和安全特性
- **Firefox 版本**:使用 Manifest V2,确保与 Firefox 扩展系统的最佳兼容性
两个版本的核心功能完全相同,只是技术实现略有不同。
### 可以同时在 Chrome 和 Firefox 中使用吗?
当然可以!你可以在多个浏览器中同时安装和使用 Xget Now,它们会独立工作,各自维护自己的设置。
## 🌟 支持我们
如果你觉得这个扩展有用,请:
- ⭐ 为此存储库点星
- 📝 留下评价:
- [Chrome 应用商店](https://chromewebstore.google.com/detail/ajiejgobfcifcikbahpijopolfjoodgf?hl=zh-CN)
- [Firefox 附加组件](https://addons.mozilla.org/zh-CN/firefox/addon/xget-now/)
- [Microsoft Edge 加载项](https://microsoftedge.microsoft.com/addons/detail/jigpfhbegabdenhihpplcjhpfdcgnalc?hl=zh-CN&gl=CN)
- 🐛 通过 [GitHub Issues](https://github.com/xixu-me/Xget-Now/issues) 报告错误或建议功能
- 📢 与其他可能受益于更快下载的人分享
+43 -43
View File
@@ -1,66 +1,66 @@
# Security Policy
# 安全政策
## Supported Versions
## 支持的版本
We actively support the latest version of Xget for Chrome. Security updates are provided for:
我们积极支持 Xget Now 的最新版本。为以下版本提供安全更新:
| Version | Supported |
| 版本 | 支持状态 |
| ------- | ------------------ |
| 1.x.x | :white_check_mark: |
## Reporting a Vulnerability
## 报告漏洞
If you discover a security vulnerability in Xget for Chrome, please report it responsibly:
如果你在 Xget Now 中发现安全漏洞,请负责任地报告:
### How to Report
### 如何报告
1. **Do not** create a public GitHub issue for security vulnerabilities
2. Email the maintainer directly or create a private security advisory
3. Include as much detail as possible:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
1. **不要** 为安全漏洞创建公开的 GitHub 问题
2. 直接发邮件给维护者或创建私人安全咨询
3. 包含尽可能多的详细信息:
- 漏洞描述
- 重现步骤
- 潜在影响
- 建议修复(如果有)
### What to Expect
### 期望结果
- **Response Time**: We aim to respond within 48 hours
- **Investigation**: We will investigate and assess the severity
- **Fix Timeline**: Critical vulnerabilities will be patched within 7 days
- **Disclosure**: We will coordinate responsible disclosure with you
- **响应时间**:我们目标在 48 小时内响应
- **调查**:我们将调查并评估严重性
- **修复时间表**:关键漏洞将在 7 天内修补
- **披露**:我们将与你协调负责任的披露
### Security Considerations
### 安全考虑
This extension:
此扩展:
- Processes URLs locally in your browser
- Does not collect or transmit personal data
- Uses Chrome's standard extension APIs
- Redirects downloads through user-configured Xget domains
- 在你的浏览器中本地处理 URL
- 不收集或传输个人数据
- 使用 Chrome 的标准扩展 API
- 通过用户配置的 Xget 域名重定向下载
### Scope
### 范围
Security issues we consider in scope:
我们认为在范围内的安全问题:
- Code injection vulnerabilities
- Privilege escalation
- Data leakage or privacy violations
- Malicious URL handling
- Extension permission abuse
- 代码注入漏洞
- 权限提升
- 数据泄露或隐私违规
- 恶意 URL 处理
- 扩展权限滥用
Out of scope:
超出范围:
- Issues with third-party Xget servers
- Browser-level vulnerabilities
- Social engineering attacks
- 第三方 Xget 服务器的问题
- 浏览器级别的漏洞
- 社会工程攻击
## Security Best Practices
## 安全最佳实践
When using this extension:
使用此扩展时:
- Only configure trusted Xget domains
- Keep the extension updated
- Review permissions when updating
- Report suspicious behavior
- 仅配置可信的 Xget 域名
- 保持扩展更新
- 更新时审查权限
- 报告可疑行为
Thank you for helping keep Xget for Chrome secure!
感谢你帮助保持 Xget Now 的安全!
+269 -60
View File
@@ -1,97 +1,308 @@
// Platform configurations matching the worker.js
/**
* Xget Now - 扩展后台脚本
*
* 功能:
* - 处理扩展设置管理
* - 监听标签页更新事件
* - 处理来自内容脚本的消息
* - 管理扩展状态和配置
*/
// 导入兼容层
importScripts("webext-compat.js");
// 确保兼容层可用
if (typeof webext === "undefined") {
// 在非模块环境中,兼容层应该已经通过脚本标签加载
console.error("WebExt compatibility layer not found");
}
/**
* 平台配置定义
* 支持的下载加速平台列表
*/
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\//,
},
};
// Default settings
// 转换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,
},
};
// Initialize extension
chrome.runtime.onInstalled.addListener(async () => {
console.log("Xget for Chrome installed");
// 初始化扩展
webext.runtime.onInstalled.addListener(async () => {
console.log("Xget Now 已安装");
// Set default settings if not already set
const settings = await chrome.storage.sync.get(DEFAULT_SETTINGS);
await chrome.storage.sync.set(settings);
// 使用本地存储而不是同步存储以确保兼容性
const storageAPI = webext.storage.sync || webext.storage.local;
// 如果尚未设置,则设置默认设置
const settings = await storageAPI.get(DEFAULT_SETTINGS);
await storageAPI.set(settings);
});
// Listen for download events
chrome.downloads.onDeterminingFilename.addListener((downloadItem, suggest) => {
handleDownload(downloadItem, suggest);
});
// 防止监听器被多次注册
let downloadListenerAdded = false;
async function handleDownload(downloadItem, suggest) {
// 监听下载事件
if (!downloadListenerAdded && webext.downloads.onDeterminingFilename) {
webext.downloads.onDeterminingFilename.addListener(handleDownload);
downloadListenerAdded = true;
}
function handleDownload(downloadItem, suggest) {
// 立即调用 suggest 来防止多次调用错误
suggest();
// 然后异步处理重定向逻辑
processDownloadRedirect(downloadItem);
}
async function processDownloadRedirect(downloadItem) {
try {
const settings = await chrome.storage.sync.get(DEFAULT_SETTINGS);
const storageAPI = webext.storage.sync || webext.storage.local;
const settings = await storageAPI.get(DEFAULT_SETTINGS);
// Check if extension is enabled and domain is configured
// 检查扩展是否已启用并配置了域名
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("Redirecting download:", url, "->", redirectedUrl);
console.log("重定向下载:", url, "->", redirectedUrl);
// Cancel the original download
chrome.downloads.cancel(downloadItem.id);
// Start new download with redirected URL
chrome.downloads.download({
url: redirectedUrl,
filename: downloadItem.filename || undefined,
conflictAction: "uniquify",
});
// Show notification via content script
try {
await chrome.tabs.sendMessage(downloadItem.tabId, {
action: "showNotification",
message: "Download redirected through 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("Could not send notification to tab");
console.error("重定向下载时出错:", error);
}
} else {
suggest();
}
} catch (error) {
console.error("Error handling download:", error);
suggest();
console.error("处理下载重定向时出错:", error);
}
}
function transformUrl(url, settings) {
function transformUrlLegacy(url, settings) {
try {
// Find matching platform
// 找到匹配的平台
for (const [platformKey, platform] of Object.entries(PLATFORMS)) {
if (!settings.enabledPlatforms[platformKey]) continue;
@@ -99,49 +310,47 @@ function transformUrl(url, settings) {
const urlObj = new URL(url);
const path = urlObj.pathname + urlObj.search + urlObj.hash;
// Transform the URL using Xget domain (add https:// protocol)
// 使用 Xget 域名转换 URL(添加 https:// 协议)
const xgetUrl = `https://${settings.xgetDomain}/${platformKey}${path}`;
return xgetUrl;
}
}
return null; // No transformation needed
return null; // 不需要转换
} catch (error) {
console.error("Error transforming URL:", error);
console.error("转换 URL 时出错:", error);
return null;
}
}
// Listen for messages from popup/options
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
// 监听来自弹出窗口/选项的消息
webext.runtime.onMessage.addListener((request, sender, sendResponse) => {
const storageAPI = webext.storage.sync || webext.storage.local;
if (request.action === "getSettings") {
chrome.storage.sync.get(DEFAULT_SETTINGS).then(sendResponse);
storageAPI.get(DEFAULT_SETTINGS).then(sendResponse);
return true;
} else if (request.action === "saveSettings") {
chrome.storage.sync.set(request.settings).then(async () => {
// Notify relevant tabs to refresh
storageAPI.set(request.settings).then(async () => {
// 通知相关标签页刷新
try {
const tabs = await chrome.tabs.query({
url: [
"https://github.com/*",
"https://gitlab.com/*",
"https://huggingface.co/*",
],
const tabs = await webext.tabs.query({
url: Object.values(PLATFORMS).map((platform) => platform.base + "/*"),
});
for (const tab of tabs) {
try {
await chrome.tabs.sendMessage(tab.id, {
await webext.tabs.sendMessage(tab.id, {
action: "showNotification",
message: "Settings updated! Click to refresh page",
message: "设置已更新!点击刷新页面",
showRefreshButton: true,
});
} catch (error) {
// Tab might not have content script loaded, ignore
// 标签页可能没有加载内容脚本,忽略
}
}
} catch (error) {
console.log("Could not notify tabs about settings update");
console.log("无法通知标签页设置更新");
}
sendResponse({ success: true });
+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()
+275 -48
View File
@@ -1,7 +1,24 @@
// Content script to inject download interception on supported platforms
/**
* Xget Now - 内容脚本
*
* 功能:
* - 在支持的平台页面上注入下载拦截功能
* - 监听页面上的下载链接点击事件
* - 将下载请求重定向到加速服务
* - 处理不同平台的特殊下载逻辑
*/
// Platform detection and URL transformation
// 确保兼容层可用
if (typeof webext === "undefined") {
console.error("WebExt compatibility layer not found in content script");
}
/**
* 平台配置定义
* 与后台脚本保持同步的平台列表
*/
const PLATFORMS = {
// 代码托管平台
gh: {
base: "https://github.com",
name: "GitHub",
@@ -12,51 +29,167 @@ 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\//,
},
};
// Initialize content script
// 初始化内容脚本
(async function () {
console.log("Xget for Chrome: Content script loaded");
console.log("Xget Now:内容脚本已加载");
// Listen for messages from background script
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 });
}
});
// Check if extension is enabled and configured
// 检查扩展是否已启用并配置
const settings = await getSettings();
if (!settings.enabled || !settings.xgetDomain) {
return;
}
// Find current platform
// 找到当前平台
const currentPlatform = detectPlatform(window.location.href);
if (!currentPlatform || !settings.enabledPlatforms[currentPlatform]) {
return;
}
// Add download interception
// 添加下载拦截
interceptDownloadLinks();
// Monitor for dynamically added content
// 监控动态添加的内容
observePageChanges();
})();
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 getting settings:", error);
console.error("获取设置时出错:", error);
return { enabled: false };
}
}
@@ -71,14 +204,14 @@ function detectPlatform(url) {
}
function interceptDownloadLinks() {
// Intercept clicks on download links
// 拦截下载链接的点击事件
document.addEventListener(
"click",
async (event) => {
const link = event.target.closest("a");
if (!link || !link.href) return;
// Check if this is a download link
// 检查这是否是下载链接
if (isDownloadLink(link)) {
event.preventDefault();
event.stopPropagation();
@@ -95,12 +228,12 @@ function isDownloadLink(link) {
const url = new URL(link.href);
const pathname = url.pathname.toLowerCase();
// First check: explicit download attributes
// 第一检查:明确的下载属性
if (link.download || link.hasAttribute("download")) {
return true;
}
// Second check: file extensions that indicate downloadable files
// 第二检查:表示可下载文件的文件扩展名
const fileExtensions = [
".zip",
".tar.gz",
@@ -152,50 +285,50 @@ function isDownloadLink(link) {
".nupkg",
];
// Check if URL ends with a file extension
// 检查 URL 是否以文件扩展名结尾
if (fileExtensions.some((ext) => pathname.endsWith(ext))) {
return true;
}
// Third check: GitHub-specific patterns
// 第三检查:GitHub 特定模式
const allowedGitHubHosts = ["github.com"];
try {
const parsedUrl = new URL(href);
if (allowedGitHubHosts.includes(parsedUrl.host)) {
// GitHub release asset download URLs follow pattern: /releases/download/
// GitHub 发布资源下载 URL 遵循模式:/releases/download/
if (pathname.includes("/releases/download/")) {
return true;
}
// GitHub archive download URLs
// GitHub 存档下载 URL
if (
pathname.includes("/archive/") &&
(pathname.endsWith(".zip") || pathname.endsWith(".tar.gz"))
) {
return true;
}
// GitHub raw file URLs - NEW: support for raw file links
// GitHub 原始文件 URL - 新增:支持原始文件链接
if (pathname.includes("/raw/")) {
return true;
}
// Exclude navigation to releases page (just /releases or /releases/)
// 排除导航到发布页面(仅 /releases 或 /releases/)
if (pathname.endsWith("/releases") || pathname.endsWith("/releases/")) {
return false;
}
}
} catch (e) {
console.error("Invalid URL:", href, e);
console.error("无效的 URL:", href, e);
}
// Fourth check: GitLab-specific patterns
// 第四检查:GitLab 特定模式
const allowedGitLabHosts = ["gitlab.com"];
try {
const parsedUrl = new URL(href);
if (allowedGitLabHosts.includes(parsedUrl.host)) {
// GitLab archive downloads
// GitLab 存档下载
if (pathname.includes("/-/archive/")) {
return true;
}
// GitLab release downloads
// GitLab 发布下载
if (
pathname.includes("/-/releases/") &&
pathname.includes("/downloads/")
@@ -204,24 +337,118 @@ function isDownloadLink(link) {
}
}
} catch (e) {
console.error("Invalid URL:", href, e);
console.error("无效的 URL:", href, e);
}
// Fifth check: Hugging Face file downloads
// 第五检查:Hugging Face 文件下载
const allowedHuggingFaceHosts = ["huggingface.co"];
try {
const parsedUrl = new URL(href);
if (allowedHuggingFaceHosts.includes(parsedUrl.host)) {
// HF file download URLs contain /resolve/
// HF 文件下载 URL 包含 /resolve/
if (pathname.includes("/resolve/")) {
return true;
}
}
} catch (e) {
console.error("Invalid URL:", href, e);
console.error("无效的 URL:", href, e);
}
// Seventh check: explicit download text indicators (be more specific)
// 第六检查: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 (
@@ -233,7 +460,7 @@ function isDownloadLink(link) {
return true;
}
// Default: not a download link
// 默认:不是下载链接
return false;
}
@@ -244,14 +471,14 @@ async function handleDownloadLink(url) {
const transformedUrl = transformUrl(url, settings);
if (transformedUrl) {
// Show notification
showNotification(`Download redirected through Xget`);
// 显示通知
showNotification(`下载已通过 Xget 重定向`);
// Trigger download
// 触发下载
window.location.href = transformedUrl;
}
} catch (error) {
console.error("Error handling download:", error);
console.error("处理下载时出错:", error);
}
}
@@ -267,14 +494,14 @@ function transformUrl(url, settings) {
return `https://${settings.xgetDomain}/${platform}${path}`;
} catch (error) {
console.error("Error transforming URL:", error);
console.error("转换 URL 时出错:", error);
return null;
}
}
function observePageChanges() {
const observer = new MutationObserver(() => {
// Monitor for page changes (no additional actions needed)
// 监控页面变化(无需额外操作)
});
observer.observe(document.body, {
@@ -285,25 +512,25 @@ function observePageChanges() {
function showNotification(message, showRefreshButton = false) {
try {
// Remove any existing notifications first
// 首先删除所有现有通知
const existingNotifications =
document.querySelectorAll(".xget-notification");
existingNotifications.forEach((notification) => notification.remove());
// Create a simple notification
// 创建一个简单的通知
const notification = document.createElement("div");
notification.className = "xget-notification";
// Create message container
// 创建消息容器
const messageDiv = document.createElement("div");
messageDiv.textContent = message;
messageDiv.style.marginBottom = showRefreshButton ? "8px" : "0";
notification.appendChild(messageDiv);
// Add refresh button if needed
// 如果需要,添加刷新按钮
if (showRefreshButton) {
const refreshButton = document.createElement("button");
refreshButton.textContent = "🔄 Refresh Page";
refreshButton.textContent = "🔄 刷新页面";
refreshButton.style.cssText = `
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.3);
@@ -350,7 +577,7 @@ function showNotification(message, showRefreshButton = false) {
word-wrap: break-word;
`;
// Add animation styles if not already present
// 如果尚未存在则添加动画样式
if (!document.getElementById("xget-notification-styles")) {
const style = document.createElement("style");
style.id = "xget-notification-styles";
@@ -369,7 +596,7 @@ function showNotification(message, showRefreshButton = false) {
document.body.appendChild(notification);
// Remove after longer time if has refresh button, shorter if not
// 如果有刷新按钮则在较长时间后删除,否则在较短时间后删除
const removeDelay = showRefreshButton ? 8000 : 4000;
setTimeout(() => {
notification.style.animation = "xgetSlideOut 0.3s ease-in";
@@ -380,8 +607,8 @@ function showNotification(message, showRefreshButton = false) {
}, 300);
}, removeDelay);
} catch (error) {
console.error("Error showing notification:", error);
// Fallback to console log if DOM manipulation fails
console.log("Xget notification:", message);
console.error("显示通知时出错:", error);
// 如果 DOM 操作失败则回退到控制台日志
console.log("Xget 通知:", message);
}
}
+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()
+104
View File
@@ -0,0 +1,104 @@
{
"manifest_version": 2,
"name": "Xget Now",
"version": "2.0.3",
"description": "为 Firefox 浏览器提供的 Xget 下载加速扩展,支持 GitHub、GitLab、npm、PyPI 等众多平台的文件下载加速",
"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"
},
"background": {
"scripts": [
"webext-compat.js",
"background.js"
],
"persistent": false
},
"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"
}
],
"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/*"
],
"icons": {
"16": "icons/icon16.png",
"32": "icons/icon32.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},
"web_accessible_resources": [
"icons/*"
],
"browser_specific_settings": {
"gecko": {
"id": "i@xi-xu.me",
"strict_min_version": "109.0"
}
}
}
+51 -6
View File
@@ -7,7 +7,7 @@
"128": "icons/icon128.png"
},
"default_popup": "popup.html",
"default_title": "Xget for Chrome"
"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": "Download accelerator that redirects downloads through Xget for faster speeds",
"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 Chrome",
"name": "Xget Now",
"permissions": [
"downloads",
"storage",
"activeTab"
],
"version": "1.1.0",
"version": "2.0.3",
"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-Chrome/issues"
"url": "https://github.com/xixu-me/Xget-Now/issues"
},
"dependencies": {},
"description": "Chrome extension that accelerates downloads by redirecting them through Xget",
"devDependencies": {},
"homepage": "https://github.com/xixu-me/Xget-for-Chrome#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-chrome",
"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-Chrome.git"
"url": "https://github.com/xixu-me/Xget-Now.git"
},
"scripts": {
"build": "echo 'No build process required - extension runs from source'",
"clean": "rm -f *.zip",
"dev": "echo 'Open chrome://extensions/ and load unpacked extension'",
"lint": "echo 'No linting configured - consider adding ESLint for JavaScript files'",
"package": "zip -r extension.zip . -x '*.git*' 'node_modules/*' '.github/*' '*.md' 'LICENSE' '*.zip'",
"release": "npm run validate && npm run package",
"test": "echo 'Load extension in Chrome Developer Mode to test'",
"validate": "node -e \"JSON.parse(require('fs').readFileSync('manifest.json', 'utf8')); console.log('✅ manifest.json is valid')\""
"build": "echo 'No build process needed for Chrome extension'",
"test": "echo 'No tests specified'"
},
"version": "1.1.0"
"version": "2.0.3"
}
+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;
}
}
+376 -92
View File
@@ -3,61 +3,161 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Xget for Chrome</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 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 +173,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 +336,56 @@
.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 Chrome</h1>
<div class="subtitle">Download Accelerator</div>
<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;
"
>
Enable Extension
<span>启用扩展</span>
<label class="toggle-switch">
<input type="checkbox" id="enabledToggle" />
<span class="slider"></span>
@@ -235,9 +395,7 @@
</div>
<div class="setting-group">
<label class="setting-label" for="domainInput"
>Xget Domain (Required)</label
>
<label class="setting-label" for="domainInput">Xget 域名(必填)</label>
<input
type="text"
id="domainInput"
@@ -247,28 +405,149 @@
</div>
<div class="setting-group">
<label class="setting-label">Enabled Platforms</label>
<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>
@@ -281,15 +560,19 @@
class="link-item"
>
<span class="link-icon">🚀</span>
<span class="link-text">Deploy Your Own Xget Service</span>
<span class="link-text">Xget 存储库</span>
</a>
<a
href="https://github.com/xixu-me/Xget-for-Chrome"
href="https://github.com/xixu-me/Xget-Now"
target="_blank"
class="link-item"
>
<span class="link-icon">📖</span>
<span class="link-text">Source Code & Documentation</span>
<span class="link-text">扩展存储库</span>
</a>
<a href="https://xuc.xi-xu.me/" target="_blank" class="link-item">
<span class="link-icon">🔗</span>
<span class="link-text">URL 转换器</span>
</a>
</div>
</div>
@@ -297,6 +580,7 @@
<div id="status" class="status" style="display: none"></div>
</div>
<script src="webext-compat.js"></script>
<script src="popup.js"></script>
</body>
</html>
+270 -50
View File
@@ -1,49 +1,102 @@
/**
* Xget Now - 弹窗脚本
*
* 功能:
* - 管理扩展设置界面
* - 处理用户交互事件
* - 同步设置到后台脚本
* - 提供平台开关控制
*/
document.addEventListener("DOMContentLoaded", async () => {
// Load current settings
// 确保兼容层可用
if (typeof webext === "undefined") {
console.error("WebExt compatibility layer not found in popup");
return;
}
// 加载当前设置
await loadSettings();
// Set up event listeners
// 设置事件监听器
setupEventListeners();
});
/**
* 加载并应用当前设置到 UI
*/
async function loadSettings() {
try {
const settings = await chrome.runtime.sendMessage({
const settings = await webext.runtime.sendMessage({
action: "getSettings",
});
// Update UI with current settings
// 使用当前设置更新 UI
const domainValue = settings.xgetDomain || "";
document.getElementById("domainInput").value = domainValue;
// Only enable the toggle if domain is set
// 仅在设置了域名时启用切换
const enabledValue = settings.enabled && domainValue.trim() !== "";
document.getElementById("enabledToggle").checked = enabledValue;
// Update platform toggles
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"),
// Show status if domain is missing
// 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) {
showStatus("Please configure your Xget domain", "error");
showStatus("请配置你的 Xget 域名", "error");
} else if (domainValue && enabledValue) {
showStatus("Extension is active and ready", "success");
showStatus("扩展已激活并准备就绪", "success");
}
} catch (error) {
console.error("Error loading settings:", error);
showStatus("Error loading settings", "error");
console.error("加载设置时出错:", error);
showStatus("加载设置时出错", "error");
}
}
function setupEventListeners() {
// Enable/disable toggle
// 启用/禁用切换
document
.getElementById("enabledToggle")
.addEventListener("change", handleEnableToggle);
// Domain input
// 域名输入
document
.getElementById("domainInput")
.addEventListener("input", debounce(handleDomainChange, 500));
@@ -51,10 +104,145 @@ function setupEventListeners() {
.getElementById("domainInput")
.addEventListener("blur", validateDomain);
// Platform toggles
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() {
@@ -62,10 +250,10 @@ async function saveSettings() {
const domainValue = document.getElementById("domainInput").value.trim();
const enabledValue = document.getElementById("enabledToggle").checked;
// Prevent enabling if no domain is set
// 如果没有设置域名则防止启用
if (enabledValue && !domainValue) {
document.getElementById("enabledToggle").checked = false;
showStatus("Cannot enable extension without a domain", "error");
showStatus("没有域名不能启用扩展", "error");
return;
}
@@ -73,66 +261,89 @@ 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,
},
};
// Clean up domain URL
// 清理域名 URL
if (settings.xgetDomain) {
settings.xgetDomain = cleanupDomain(settings.xgetDomain);
}
const response = await chrome.runtime.sendMessage({
const response = await webext.runtime.sendMessage({
action: "saveSettings",
settings: settings,
});
if (response.success) {
// Show appropriate status
// 显示适当的状态
if (!settings.xgetDomain && settings.enabled) {
showStatus("Please configure your Xget domain", "error");
showStatus("请配置你的 Xget 域名", "error");
} else if (settings.xgetDomain && settings.enabled) {
showStatus(
"✅ Settings saved! Check page notifications for refresh button",
"success"
);
showStatus("✅ 设置已保存!查看页面通知中的刷新按钮", "success");
} else {
showStatus(
"✅ Settings saved! Check page notifications for refresh button",
"success"
);
showStatus("✅ 设置已保存!查看页面通知中的刷新按钮", "success");
}
}
} catch (error) {
console.error("Error saving settings:", error);
showStatus("Error saving settings", "error");
console.error("保存设置时出错:", error);
showStatus("保存设置时出错", "error");
}
}
function validateDomain() {
const domain = document.getElementById("domainInput").value.trim();
if (domain && !isValidDomain(domain)) {
showStatus("Invalid domain format", "error");
showStatus("域名格式无效", "error");
}
}
function cleanupDomain(domain) {
// Remove any protocol if accidentally included
// 如果意外包含协议,则删除它
domain = domain.replace(/^https?:\/\//, "");
// Remove trailing slash
// 删除末尾斜杠
domain = domain.replace(/\/$/, "");
return domain;
}
function isValidDomain(domain) {
// Remove protocol if present
// 如果存在协议则删除它
domain = domain.replace(/^https?:\/\//, "");
// Basic domain validation pattern
// 基本域名验证模式
const domainPattern =
/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
@@ -153,8 +364,9 @@ function showStatus(message, type) {
statusElement.textContent = message;
statusElement.className = `status ${type}`;
statusElement.style.display = "block";
statusElement.classList.remove("hiding");
// Auto-hide success messages
// 自动隐藏成功消息
if (type === "success") {
setTimeout(hideStatus, 3000);
}
@@ -162,7 +374,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) {
@@ -182,9 +402,9 @@ async function handleEnableToggle() {
const domainInput = document.getElementById("domainInput");
if (enabledToggle.checked && !domainInput.value.trim()) {
// Prevent enabling if no domain is set
// 如果没有设置域名则防止启用
enabledToggle.checked = false;
showStatus("Please configure your Xget domain before enabling", "error");
showStatus("启用前请配置你的 Xget 域名", "error");
return;
}
@@ -195,10 +415,10 @@ async function handleDomainChange() {
const domainInput = document.getElementById("domainInput");
const enabledToggle = document.getElementById("enabledToggle");
// If domain is cleared and extension is enabled, disable it
// 如果域名被清空且扩展已启用,则禁用它
if (!domainInput.value.trim() && enabledToggle.checked) {
enabledToggle.checked = false;
showStatus("Extension disabled: domain cleared", "error");
showStatus("扩展已禁用:域名已清空", "error");
}
await saveSettings();
+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");
}