#!/usr/bin/env python3 """ 从 GitHub 官方 meta API (https://api.github.com/meta) 拉取 IP 段, 生成/更新 Rules/GitHub.list 中的 IP-CIDR 规则块。 域名规则由人工维护在标记块之外, 本脚本只重写标记块内的 IP 规则, 不会覆盖手工补充的域名条目。 """ import ipaddress import json import urllib.request from pathlib import Path META_URL = "https://api.github.com/meta" OUTPUT_FILE = Path("Rules/GitHub.list") BEGIN_MARK = "# === BEGIN AUTO-GENERATED IP RULES (generate_github_rules.py) ===" END_MARK = "# === END AUTO-GENERATED IP RULES ===" # 只取这几个服务的网段。 # 刻意排除: # actions / actions_macos / codespaces —— 是 Azure 大段(actions 近 7000 条), # 会把大量非 GitHub 流量误判进代理组 # copilot —— Copilot 域名已归本组(见 ini 规则顺序), 但其 IP 段与 AI 服务商重叠, # 按 IP 兜底易误伤, 域名规则已足够 # hooks / importer / github_enterprise_importer —— 入站或企业迁移用, 客户端不需要 INCLUDED_KEYS = ["git", "web", "api", "packages", "pages"] # 域名骨架, 仅在 Rules/GitHub.list 不存在时写入 DEFAULT_DOMAIN_SECTION = """\ # GitHub 全站规则 # 域名部分基于 https://github.com/blackmatrix7/ios_rule_script rule/Clash/GitHub/GitHub.list # 本地改动: # - 保留 DOMAIN-KEYWORD,github —— 兜住下面 30 条 suffix 漏掉的一切含 github 的域名 # 被它误伤的两条已在 ini 里于本 ruleset 之前提前豁免: # kgithub.com -> DIRECT / collector.githubapp.com -> 广告 # - githubcopilot.com / githubnext.com 归本组: ruleset 已排在所有 AI ruleset 之前 # IP 部分由 generate_github_rules.py 从 https://api.github.com/meta 生成, 见文件末尾标记块 DOMAIN-KEYWORD,github DOMAIN-SUFFIX,atom.io DOMAIN-SUFFIX,dependabot.com DOMAIN-SUFFIX,ghcr.io DOMAIN-SUFFIX,git.io DOMAIN-SUFFIX,github-atom-io-herokuapp-com.freetls.fastly.net DOMAIN-SUFFIX,github-avatars.oss-cn-hongkong.aliyuncs.com DOMAIN-SUFFIX,github-cloud.s3.amazonaws.com DOMAIN-SUFFIX,github.blog DOMAIN-SUFFIX,github.com DOMAIN-SUFFIX,github.community DOMAIN-SUFFIX,github.dev DOMAIN-SUFFIX,github.io DOMAIN-SUFFIX,githubapp.com DOMAIN-SUFFIX,githubassets.com DOMAIN-SUFFIX,githubcopilot.com DOMAIN-SUFFIX,githubhackathon.com DOMAIN-SUFFIX,githubnext.com DOMAIN-SUFFIX,githubpreview.dev DOMAIN-SUFFIX,githubstatus.com DOMAIN-SUFFIX,githubuniverse.com DOMAIN-SUFFIX,githubusercontent.com DOMAIN-SUFFIX,myoctocat.com DOMAIN-SUFFIX,npm.community DOMAIN-SUFFIX,npmjs.com DOMAIN-SUFFIX,npmjs.org DOMAIN-SUFFIX,opensource.guide DOMAIN-SUFFIX,rawgit.com DOMAIN-SUFFIX,rawgithub.com DOMAIN-SUFFIX,repo.new DOMAIN-SUFFIX,thegithubshop.com """ def fetch_meta(): """拉取 GitHub meta API""" req = urllib.request.Request(META_URL, headers={"User-Agent": "curl/8"}) with urllib.request.urlopen(req, timeout=30) as resp: return json.load(resp) def extract_networks(meta): """按 INCLUDED_KEYS 取网段并去重, IPv4 / IPv6 分开返回""" v4, v6 = set(), set() for key in INCLUDED_KEYS: for cidr in meta.get(key, []): try: net = ipaddress.ip_network(cidr, strict=False) except ValueError: print(f"跳过无法解析的网段: {key} -> {cidr}") continue (v4 if net.version == 4 else v6).add(net) # collapse_addresses 会合并相邻/包含关系的网段, git/web/api 高度重叠 return ( sorted(ipaddress.collapse_addresses(v4)), sorted(ipaddress.collapse_addresses(v6)), ) def build_ip_block(v4, v6): """生成标记块内容。no-resolve 必须保留: 没有它, 每条 IP 规则都会触发一次 DNS 解析, 拖慢整条规则链, 且这些规则的用途只是兜住已经是 IP 的连接 (git@github.com 的 SSH 推拉在 redir-host 模式下嗅探不到域名)。""" lines = [ BEGIN_MARK, f"# 来源: {META_URL} 的 {' / '.join(INCLUDED_KEYS)} 字段", "# 请勿手工编辑本块, 运行 python3 generate_github_rules.py 重新生成", "", ] lines += [f"IP-CIDR,{net},no-resolve" for net in v4] if v6: lines.append("") lines += [f"IP-CIDR6,{net},no-resolve" for net in v6] lines += ["", END_MARK] return "\n".join(lines) + "\n" def write_rules(ip_block, output_file): """保留域名部分, 只替换标记块""" if output_file.exists(): text = output_file.read_text(encoding="utf-8") else: text = DEFAULT_DOMAIN_SECTION if BEGIN_MARK in text and END_MARK in text: head = text.split(BEGIN_MARK)[0] tail = text.split(END_MARK, 1)[1] text = head + ip_block + tail.lstrip("\n") else: text = text.rstrip("\n") + "\n\n" + ip_block output_file.parent.mkdir(exist_ok=True) output_file.write_text(text, encoding="utf-8") def main(): meta = fetch_meta() v4, v6 = extract_networks(meta) print(f"合并后 IPv4 网段 {len(v4)} 条, IPv6 网段 {len(v6)} 条") write_rules(build_ip_block(v4, v6), OUTPUT_FILE) print(f"已更新 {OUTPUT_FILE}") if __name__ == "__main__": main()