30 条 DOMAIN-SUFFIX 覆盖不到的含 github 域名(githubmemory.com、 github.global.ssl.fastly.net、各类 github.* 镜像站…)会一路穿到 ProxyGFWlist 的 DOMAIN-KEYWORD,github,落进 🌍 海外访问,造成 GitHub 流量被劈成两个组。 GitHub.list 补回 DOMAIN-KEYWORD,github 兜底;它误伤的两条在 ini 里于 🐙 Github ruleset 之前提前豁免: kgithub.com -> DIRECT (国内镜像,原属 ChinaOnly.list) collector.githubapp.com -> 💩 广告 (遥测,原属 BanProgramAD.list, 建组时被 githubapp.com 抢走,此处修回) 本地按 ini 顺序重放 19837 条规则校验:全部 GitHub 域名首个命中均为 🐙 Github,两条例外保持原分组,AI 域名不受影响。 via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GNghjXGrjt64G32W7t6Bhu
152 lines
5.1 KiB
Python
152 lines
5.1 KiB
Python
#!/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()
|