重构:模块化 RRPP Lite 并更新入口
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user