From d49854f267cfed215ed55cb74f90e79f43567d0f Mon Sep 17 00:00:00 2001 From: BROBIRD <7692707+BROBIRD@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:54:47 +0800 Subject: [PATCH] try add autoupdate --- .github/workflows/update.yml | 104 +++++++++++++++++++++++++ scripts/gfwlist_parser.py | 146 +++++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 .github/workflows/update.yml create mode 100644 scripts/gfwlist_parser.py diff --git a/.github/workflows/update.yml b/.github/workflows/update.yml new file mode 100644 index 0000000..4df5e90 --- /dev/null +++ b/.github/workflows/update.yml @@ -0,0 +1,104 @@ +name: Update GFWList Rules + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + update: + runs-on: ubuntu-slim + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Parse GFWList + run: python scripts/gfwlist_parser.py + + - name: Download mihomo + run: | + MAX_RETRIES=5 + RETRY_DELAY=10 + for ((i=1; i<=MAX_RETRIES; i++)); do + echo "Attempt $i/$MAX_RETRIES to download mihomo..." + if TAG=$(curl -s --fail https://api.github.com/repos/MetaCubeX/mihomo/releases/latest | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/'); then + echo "Latest mihomo version: $TAG" + if curl -L --fail -o mihomo.tar.gz "https://github.com/MetaCubeX/mihomo/releases/download/${TAG}/mihomo-linux-amd64-${TAG}.gz"; then + echo "Download succeeded" + if tar -xzf mihomo.tar.gz; then + chmod +x mihomo + rm -f mihomo.tar.gz + echo "mihomo extracted and ready" + exit 0 + else + echo "Failed to extract mihomo.tar.gz" + fi + else + echo "Download failed (HTTP error)" + fi + else + echo "Failed to fetch latest release info" + fi + if [ $i -lt $MAX_RETRIES ]; then + echo "Retrying in $RETRY_DELAY seconds..." + sleep $RETRY_DELAY + fi + done + echo "ERROR: All download attempts failed" + exit 1 + + - name: Convert to mrs format + run: | + mkdir -p Clash/mrs + convert_file() { + local input_file=$1 + local output_file=$2 + local MAX_RETRIES=3 + for ((i=1; i<=MAX_RETRIES; i++)); do + echo "[Attempt $i/$MAX_RETRIES] Converting $input_file" + if ./mihomo -f "$input_file" -in clash -out mrs -o "$output_file"; then + echo "Success: $input_file -> $output_file" + return 0 + else + echo "Warning: Conversion failed for $input_file (Attempt $i/$MAX_RETRIES)" + if [ $i -lt $MAX_RETRIES ]; then + sleep 3 + fi + fi + done + echo "ERROR: Failed to convert $input_file after $MAX_RETRIES attempts" + return 1 + } + + for file in Clash/Providers/*.yaml; do + if [ -f "$file" ]; then + filename=$(basename "$file" .yaml) + convert_file "$file" "Clash/mrs/${filename}.mrs" + fi + done + + for file in Clash/Ruleset/*.list; do + if [ -f "$file" ]; then + filename=$(basename "$file" .list) + convert_file "$file" "Clash/mrs/${filename}.mrs" + fi + done + + echo "Conversion completed" + + - name: Commit and push changes + uses: EndBug/add-and-commit@v9 + with: + message: '[AutoUpdate] Update GFWList rules and mrs files - $(date -u +"%Y-%m-%dT%H:%M:%SZ")' + push: true + branch: ${{ github.head_ref || github.ref_name }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/scripts/gfwlist_parser.py b/scripts/gfwlist_parser.py new file mode 100644 index 0000000..b285434 --- /dev/null +++ b/scripts/gfwlist_parser.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +import base64 +import re +import os +import sys + +def fetch_gfwlist(url="https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt"): + try: + import urllib.request + with urllib.request.urlopen(url) as response: + return response.read().decode('utf-8') + except Exception as e: + print(f"Error fetching GFWList: {e}") + sys.exit(1) + +def extract_domain_from_url(url): + url = url.strip() + if url.endswith('^'): + url = url[:-1] + url = url.rstrip('/') + + domain_match = re.search(r'(?:https?://)?(?:www\.)?([^/:]+)', url) + if domain_match: + return domain_match.group(1) + return url + +def parse_gfwlist(content): + blacklist = [] + whitelist = [] + + try: + decoded = base64.b64decode(content).decode('utf-8') + except: + decoded = content + + for line in decoded.split('\n'): + line = line.strip() + + if not line or line.startswith('!') or line.startswith('['): + continue + + if line.startswith('@@||'): + domain = line[4:] + if domain.endswith('^'): + domain = domain[:-1] + whitelist.append(domain) + elif line.startswith('||'): + domain = line[2:] + if domain.endswith('^'): + domain = domain[:-1] + blacklist.append(domain) + elif line.startswith('@@|') and len(line) > 3: + domain = extract_domain_from_url(line[3:]) + if domain: + whitelist.append(domain) + elif line.startswith('|') and len(line) > 1: + domain = extract_domain_from_url(line[1:]) + if domain: + blacklist.append(domain) + + return blacklist, whitelist + +def format_domain_suffix_rules(domains): + rules = [] + for domain in sorted(set(domains)): + if domain: + rules.append(f"- DOMAIN-SUFFIX,{domain}") + return rules + +def format_acl_rules(domains): + rules = [] + for domain in sorted(set(domains)): + if domain: + rules.append(f"(^|\\.){domain}$") + return rules + +def write_file(filepath, content): + os.makedirs(os.path.dirname(filepath), exist_ok=True) + with open(filepath, 'w', encoding='utf-8') as f: + f.write(content) + +def generate_acl_file(domains, filename, title="GFWList Rules"): + header = f"""#********************************************************************** +# {title} +# Generated from GFWList +# +# 更新记录 https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/more/New.md +# +#********************************************************************** + +[bypass_all] +### 默认直连 自己可以自定义 +### [outbound_block_list] 禁止访问列表 +### [bypass_list] 直连列表 禁止访问列表 +### [proxy_list] 代理列表 + +#********************************************************************** +[proxy_list] + +# GFWList +""" + rules = format_acl_rules(domains) + content = header + '\n'.join(rules) + '\n' + write_file(filename, content) + +def generate_clash_provider_yaml(domains, filename, title="Proxy"): + unique_domains = sorted(set(domains)) + content = f"{title}:\n - https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/Ruleset/ProxyGFWlist.list\n" + for domain in unique_domains: + content += f" - DOMAIN-SUFFIX,{domain}\n" + write_file(filename, content) + +def generate_clash_ruleset_list(domains, filename, title="GFWList"): + unique_domains = sorted(set(domains)) + content = f"# 内容:{title}\n# 数量:{len(unique_domains)}条\n" + for domain in unique_domains: + content += f"DOMAIN-SUFFIX,{domain}\n" + write_file(filename, content) + +def main(): + print("Fetching GFWList...") + content = fetch_gfwlist() + + print("Parsing GFWList...") + blacklist, whitelist = parse_gfwlist(content) + + print(f"Blacklist entries: {len(blacklist)}") + print(f"Whitelist entries: {len(whitelist)}") + + generate_acl_file(blacklist, 'Acl/fullgfwlist.acl', "GFWList Blacklist") + print("Generated: Acl/fullgfwlist.acl") + + generate_clash_provider_yaml(blacklist, 'Clash/Providers/ProxyGFWlist.yaml', 'payload') + print("Generated: Clash/Providers/ProxyGFWlist.yaml") + + generate_clash_ruleset_list(blacklist, 'Clash/Ruleset/ProxyGFWlist.list', 'GFWList 黑名单') + print("Generated: Clash/Ruleset/ProxyGFWlist.list") + + generate_clash_provider_yaml(whitelist, 'Clash/Providers/UnBan.yaml', 'payload') + print("Generated: Clash/Providers/UnBan.yaml") + + generate_clash_ruleset_list(whitelist, 'Clash/Ruleset/UnBan.list', 'GFWList 白名单') + print("Generated: Clash/Ruleset/UnBan.list") + +if __name__ == "__main__": + main() \ No newline at end of file