重构:模块化 RRPP Lite 并更新入口

This commit is contained in:
RoyirinRosmontis
2026-09-16 23:13:28 +08:00
parent c37d8af821
commit 02d36c72f7
35 changed files with 2286 additions and 1079 deletions
@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
"""更新 pac_domains.json:从 v2fly/domain-list-community (geosite) 拉取境外服务
分类,与现有手工条目合并,生成新的强制走隧道域名后缀表。
用法:
python update_pac_domains.py # 拉取并更新(先自动备份)
python update_pac_domains.py --dry-run # 只统计,不写文件
geosite 数据格式(data/<分类>,每行一条规则):
include:xxx 引用其他分类(递归解析)
example.com 匹配域名及其子域(与我们的后缀匹配一致)
+example.com 同上(additive)
.example.com 仅精确匹配(不匹配子域)-> 跳过
-example.com 排除 -> 跳过
/regex/ 正则 -> 跳过
example.com @cn 属性标签 -> 去掉标签
无点的单标签(如 chrome)-> 无实际主机名会命中,跳过
"""
import argparse
import json
import sys
import urllib.request
from datetime import datetime
from pathlib import Path
BASE_URL = "https://raw.githubusercontent.com/v2fly/domain-list-community/master/data/"
# 需要强制走隧道的境外服务分类(geosite 分类名),可按需增删
CATEGORIES = ["google", "apple", "telegram", "openai", "twitter", "facebook",
"github", "netflix", "discord", "spotify", "whatsapp", "paypal"]
SCRIPT_DIR = Path(__file__).resolve().parent
LITE_DIR = SCRIPT_DIR.parent.parent
PAC_FILE = LITE_DIR / "pac_domains.json"
BACKUP_DIR = LITE_DIR / "backups"
# 本地 pac_domains.json 已不再随项目保留:缺失时从 CDN 拉当前版本作合并基底
CDN_URL = "https://cdn.rrrosmontis.icu/RRPP/PAC/pac_domains.json"
def fetch(cat, tries=2):
for i in range(tries):
try:
with urllib.request.urlopen(BASE_URL + cat, timeout=20) as r:
return r.read().decode("utf-8", "replace").splitlines()
except Exception:
if i == tries - 1:
raise
def resolve(cat, seen=None):
"""递归展开 include:,返回原始行列表;循环引用用 seen 防死循环。"""
seen = seen or set()
if cat in seen:
return []
seen.add(cat)
lines = []
try:
for raw in fetch(cat):
line = raw.strip()
if line.startswith("include:"):
lines += resolve(line[len("include:"):].strip(), seen)
else:
lines.append(line)
except Exception as e:
print(f" [warn] 拉取 {cat} 失败: {e}")
return lines
def parse_domains(lines):
"""过滤 geosite 行,只保留可映射为后缀匹配的纯域名。"""
out = set()
for line in lines:
s = line.strip()
if not s or s.startswith("#") or s.startswith("/"):
continue
if s.startswith("-") or s.startswith("."):
continue
s = s.lstrip("+").split("@")[0].strip()
if "." not in s:
continue
out.add(s.lower())
return out
def main():
parser = argparse.ArgumentParser(description="更新 pac_domains.json")
parser.add_argument("--dry-run", action="store_true", help="只统计,不写文件")
args = parser.parse_args()
if PAC_FILE.is_file():
cur = set(json.loads(PAC_FILE.read_text("utf-8"))["domains"])
else:
# 项目目录已不保留该文件:从 CDN 拉当前版本作合并基底(保留其中的手工条目)
print(f"本地未找到 {PAC_FILE},从 CDN 拉取当前版本作为合并基底")
try:
with urllib.request.urlopen(CDN_URL, timeout=20) as r:
cur = set(json.loads(r.read().decode("utf-8"))["domains"])
except Exception as e:
print(f"拉取失败: {e}")
sys.exit(1)
print(f"拉取 geosite 分类: {', '.join(CATEGORIES)}")
merged = set()
for cat in CATEGORIES:
doms = parse_domains(resolve(cat))
merged |= doms
print(f" {cat:<12} -> {len(doms):>5} 条")
new = merged - cur
kept = cur & merged
only_cur = cur - merged
print(f"\n合并后总数: {len(merged | cur)}(geosite {len(merged)} + 现有 {len(cur)})")
print(f"新增 {len(new)},与现有重叠 {len(kept)},现有手工条目 {len(only_cur)} 条全部保留")
if args.dry_run:
print("--dry-run: 未写文件")
return
final = sorted(merged | cur)
now = datetime.now().strftime("%Y-%m-%d %H:%M")
comment = (
f"强制走代理隧道的域名后缀(IP 智能分流的补充规则):命中这些域名的连接即使"
f"解析到国内 IP 也强制走隧道。匹配规则:host == 条目 或 host 以 \".条目\" 结尾。"
f"由 others/update_pac_domains/update_pac_domains.py 从 v2fly geosite "
f"({', '.join(CATEGORIES)}) 生成于 {now},手工条目已保留;"
f"客户端按文件内 version/date 从 CDN 在线更新到 %LOCALAPPDATA%\\RRPP。"
)
out = {"_comment": comment,
"version": datetime.now().strftime("%Y%m%d%H%M"),
"date": datetime.now().strftime("%Y-%m-%d"),
"domains": final}
BACKUP_DIR.mkdir(exist_ok=True)
bak = BACKUP_DIR / f"pac_domains.json.bak_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
bak.write_bytes(PAC_FILE.read_bytes())
print(f"已备份: {bak}")
PAC_FILE.write_text(json.dumps(out, ensure_ascii=False, indent=2) + "\n", "utf-8")
size = PAC_FILE.stat().st_size / 1024
print(f"已写入: {PAC_FILE}({len(final)} 条,{size:.0f} KB)")
if __name__ == "__main__":
main()