diff --git a/.gitea/workflows/publish-rules.yml b/.gitea/workflows/publish-rules.yml index c73d23415..6744cdb6f 100644 --- a/.gitea/workflows/publish-rules.yml +++ b/.gitea/workflows/publish-rules.yml @@ -56,6 +56,7 @@ jobs: SOURCE_REF="${SOURCE_REF:-main}" SOURCE_ROOT="${SOURCE_ROOT:-rule/Surge}" CLASH_NO_RESOLVE="${CLASH_NO_RESOLVE:-false}" + MIHOMO_NO_RESOLVE="${MIHOMO_NO_RESOLVE:-false}" cat > config.runtime.json </.list`) - 输出仅包含你要的两种格式: - `dist/surge/.list` + - `dist/loon/.list` - `dist/clash/.yaml` + - `dist/mihomo/.yaml` - 当前策略:**只使用 Surge 作为上游源,Clash 由 Surge 规则转换生成** ## 为什么这个方案适合你 @@ -88,10 +90,17 @@ USER-AGENT,*youtube* ## 转换规则说明 - Surge 输出:保留源规则(去重、清理空白) +- Loon 输出:基于 Surge 规则直接输出(同样去重、清理空白) - Clash 输出: - 自动移除注释/空行 - `USER-AGENT`、`URL-REGEX` 默认跳过(并在头部记录 `SKIPPED-*`) + - `DEST-PORT` 自动映射为 `DST-PORT` - `IP-CIDR`/`IP-CIDR6` 可通过 `clash_no_resolve` 控制是否追加 `no-resolve` +- Mihomo 输出: + - 使用与 Clash 相同的 provider YAML 结构 + - `DEST-PORT` 自动映射为 `DST-PORT` + - `USER-AGENT`、`URL-REGEX` 默认跳过(并在头部记录 `SKIPPED-*`) + - `IP-CIDR`/`IP-CIDR6` 可通过 `mihomo_no_resolve` 控制是否追加 `no-resolve` ## 定时更新建议 @@ -140,5 +149,6 @@ USER-AGENT,*youtube* - `SOURCE_ROOT`(默认 `rule/Surge`) - `TARGET_BRANCH`(默认 `main`) - `CLASH_NO_RESOLVE`(默认 `false`) + - `MIHOMO_NO_RESOLVE`(默认 `false`) 该工作流当前定时为 `15 3 * * *`(UTC),对应北京时间每天 `11:15`,也支持手动触发。 diff --git a/config.example.json b/config.example.json index e7bd54862..944a20318 100644 --- a/config.example.json +++ b/config.example.json @@ -14,6 +14,7 @@ }, "output": { "dir": "dist", - "clash_no_resolve": false + "clash_no_resolve": false, + "mihomo_no_resolve": false } } diff --git a/config.example.toml b/config.example.toml index 6a016bf31..10989368e 100644 --- a/config.example.toml +++ b/config.example.toml @@ -16,3 +16,5 @@ exclude_categories = [] dir = "dist" # true: IP-CIDR/IP-CIDR6 in Clash payload include no-resolve clash_no_resolve = false +# true: IP-CIDR/IP-CIDR6 in Mihomo payload include no-resolve +mihomo_no_resolve = false diff --git a/config.json b/config.json index 6e9dc57ca..5bb16ce9f 100644 --- a/config.json +++ b/config.json @@ -682,6 +682,7 @@ }, "output": { "dir": "dist", - "clash_no_resolve": false + "clash_no_resolve": false, + "mihomo_no_resolve": false } } diff --git a/dist/clash/YouTube.yaml b/dist/clash/YouTube.yaml index 038a5cc7c..bf0c19408 100644 --- a/dist/clash/YouTube.yaml +++ b/dist/clash/YouTube.yaml @@ -1,7 +1,7 @@ # NAME: YouTube # AUTHOR: gitea-shunt-rules # SOURCE: rule/Surge/YouTube/YouTube.list -# UPDATED: 2026-04-06 05:01:32 UTC +# UPDATED: 2026-04-06 05:38:50 UTC # DOMAIN-KEYWORD: 1 # DOMAIN-SUFFIX: 179 # IP-CIDR: 2 diff --git a/dist/loon/YouTube.list b/dist/loon/YouTube.list new file mode 100644 index 000000000..2833f1c81 --- /dev/null +++ b/dist/loon/YouTube.list @@ -0,0 +1,200 @@ +# NAME: YouTube +# AUTHOR: gitea-shunt-rules +# SOURCE: rule/Surge/YouTube/YouTube.list +# UPDATED: 2026-04-06 05:38:50 UTC +# DOMAIN-KEYWORD: 1 +# DOMAIN-SUFFIX: 179 +# IP-CIDR: 2 +# IP-CIDR6: 1 +# USER-AGENT: 7 +# TOTAL: 190 +DOMAIN-SUFFIX,ggpht.cn +DOMAIN-SUFFIX,ggpht.com +DOMAIN-SUFFIX,googlevideo.com +DOMAIN-SUFFIX,gvt1.com +DOMAIN-SUFFIX,gvt2.com +DOMAIN-SUFFIX,video.google.com +DOMAIN-SUFFIX,wide-youtube.l.google.com +DOMAIN-SUFFIX,withyoutube.com +DOMAIN-SUFFIX,youtu.be +DOMAIN-SUFFIX,youtube +DOMAIN-SUFFIX,youtube-nocookie.com +DOMAIN-SUFFIX,youtube-ui.l.google.com +DOMAIN-SUFFIX,youtube.ae +DOMAIN-SUFFIX,youtube.al +DOMAIN-SUFFIX,youtube.am +DOMAIN-SUFFIX,youtube.at +DOMAIN-SUFFIX,youtube.az +DOMAIN-SUFFIX,youtube.ba +DOMAIN-SUFFIX,youtube.be +DOMAIN-SUFFIX,youtube.bg +DOMAIN-SUFFIX,youtube.bh +DOMAIN-SUFFIX,youtube.bo +DOMAIN-SUFFIX,youtube.by +DOMAIN-SUFFIX,youtube.ca +DOMAIN-SUFFIX,youtube.cat +DOMAIN-SUFFIX,youtube.ch +DOMAIN-SUFFIX,youtube.cl +DOMAIN-SUFFIX,youtube.co +DOMAIN-SUFFIX,youtube.co.ae +DOMAIN-SUFFIX,youtube.co.at +DOMAIN-SUFFIX,youtube.co.cr +DOMAIN-SUFFIX,youtube.co.hu +DOMAIN-SUFFIX,youtube.co.id +DOMAIN-SUFFIX,youtube.co.il +DOMAIN-SUFFIX,youtube.co.in +DOMAIN-SUFFIX,youtube.co.jp +DOMAIN-SUFFIX,youtube.co.ke +DOMAIN-SUFFIX,youtube.co.kr +DOMAIN-SUFFIX,youtube.co.ma +DOMAIN-SUFFIX,youtube.co.nz +DOMAIN-SUFFIX,youtube.co.th +DOMAIN-SUFFIX,youtube.co.tz +DOMAIN-SUFFIX,youtube.co.ug +DOMAIN-SUFFIX,youtube.co.uk +DOMAIN-SUFFIX,youtube.co.ve +DOMAIN-SUFFIX,youtube.co.za +DOMAIN-SUFFIX,youtube.co.zw +DOMAIN-SUFFIX,youtube.com +DOMAIN-SUFFIX,youtube.com.ar +DOMAIN-SUFFIX,youtube.com.au +DOMAIN-SUFFIX,youtube.com.az +DOMAIN-SUFFIX,youtube.com.bd +DOMAIN-SUFFIX,youtube.com.bh +DOMAIN-SUFFIX,youtube.com.bo +DOMAIN-SUFFIX,youtube.com.br +DOMAIN-SUFFIX,youtube.com.by +DOMAIN-SUFFIX,youtube.com.co +DOMAIN-SUFFIX,youtube.com.do +DOMAIN-SUFFIX,youtube.com.ec +DOMAIN-SUFFIX,youtube.com.ee +DOMAIN-SUFFIX,youtube.com.eg +DOMAIN-SUFFIX,youtube.com.es +DOMAIN-SUFFIX,youtube.com.gh +DOMAIN-SUFFIX,youtube.com.gr +DOMAIN-SUFFIX,youtube.com.gt +DOMAIN-SUFFIX,youtube.com.hk +DOMAIN-SUFFIX,youtube.com.hn +DOMAIN-SUFFIX,youtube.com.hr +DOMAIN-SUFFIX,youtube.com.jm +DOMAIN-SUFFIX,youtube.com.jo +DOMAIN-SUFFIX,youtube.com.kw +DOMAIN-SUFFIX,youtube.com.lb +DOMAIN-SUFFIX,youtube.com.lv +DOMAIN-SUFFIX,youtube.com.ly +DOMAIN-SUFFIX,youtube.com.mk +DOMAIN-SUFFIX,youtube.com.mt +DOMAIN-SUFFIX,youtube.com.mx +DOMAIN-SUFFIX,youtube.com.my +DOMAIN-SUFFIX,youtube.com.ng +DOMAIN-SUFFIX,youtube.com.ni +DOMAIN-SUFFIX,youtube.com.om +DOMAIN-SUFFIX,youtube.com.pa +DOMAIN-SUFFIX,youtube.com.pe +DOMAIN-SUFFIX,youtube.com.ph +DOMAIN-SUFFIX,youtube.com.pk +DOMAIN-SUFFIX,youtube.com.pt +DOMAIN-SUFFIX,youtube.com.py +DOMAIN-SUFFIX,youtube.com.qa +DOMAIN-SUFFIX,youtube.com.ro +DOMAIN-SUFFIX,youtube.com.sa +DOMAIN-SUFFIX,youtube.com.sg +DOMAIN-SUFFIX,youtube.com.sv +DOMAIN-SUFFIX,youtube.com.tn +DOMAIN-SUFFIX,youtube.com.tr +DOMAIN-SUFFIX,youtube.com.tw +DOMAIN-SUFFIX,youtube.com.ua +DOMAIN-SUFFIX,youtube.com.uy +DOMAIN-SUFFIX,youtube.com.ve +DOMAIN-SUFFIX,youtube.cr +DOMAIN-SUFFIX,youtube.cz +DOMAIN-SUFFIX,youtube.de +DOMAIN-SUFFIX,youtube.dk +DOMAIN-SUFFIX,youtube.ee +DOMAIN-SUFFIX,youtube.es +DOMAIN-SUFFIX,youtube.fi +DOMAIN-SUFFIX,youtube.fr +DOMAIN-SUFFIX,youtube.ge +DOMAIN-SUFFIX,youtube.googleapis.com +DOMAIN-SUFFIX,youtube.gr +DOMAIN-SUFFIX,youtube.gt +DOMAIN-SUFFIX,youtube.hk +DOMAIN-SUFFIX,youtube.hr +DOMAIN-SUFFIX,youtube.hu +DOMAIN-SUFFIX,youtube.ie +DOMAIN-SUFFIX,youtube.in +DOMAIN-SUFFIX,youtube.iq +DOMAIN-SUFFIX,youtube.is +DOMAIN-SUFFIX,youtube.it +DOMAIN-SUFFIX,youtube.jo +DOMAIN-SUFFIX,youtube.jp +DOMAIN-SUFFIX,youtube.kr +DOMAIN-SUFFIX,youtube.kz +DOMAIN-SUFFIX,youtube.la +DOMAIN-SUFFIX,youtube.lk +DOMAIN-SUFFIX,youtube.lt +DOMAIN-SUFFIX,youtube.lu +DOMAIN-SUFFIX,youtube.lv +DOMAIN-SUFFIX,youtube.ly +DOMAIN-SUFFIX,youtube.ma +DOMAIN-SUFFIX,youtube.md +DOMAIN-SUFFIX,youtube.me +DOMAIN-SUFFIX,youtube.mk +DOMAIN-SUFFIX,youtube.mn +DOMAIN-SUFFIX,youtube.mx +DOMAIN-SUFFIX,youtube.my +DOMAIN-SUFFIX,youtube.ng +DOMAIN-SUFFIX,youtube.ni +DOMAIN-SUFFIX,youtube.nl +DOMAIN-SUFFIX,youtube.no +DOMAIN-SUFFIX,youtube.pa +DOMAIN-SUFFIX,youtube.pe +DOMAIN-SUFFIX,youtube.ph +DOMAIN-SUFFIX,youtube.pk +DOMAIN-SUFFIX,youtube.pl +DOMAIN-SUFFIX,youtube.pr +DOMAIN-SUFFIX,youtube.pt +DOMAIN-SUFFIX,youtube.qa +DOMAIN-SUFFIX,youtube.ro +DOMAIN-SUFFIX,youtube.rs +DOMAIN-SUFFIX,youtube.ru +DOMAIN-SUFFIX,youtube.sa +DOMAIN-SUFFIX,youtube.se +DOMAIN-SUFFIX,youtube.sg +DOMAIN-SUFFIX,youtube.si +DOMAIN-SUFFIX,youtube.sk +DOMAIN-SUFFIX,youtube.sn +DOMAIN-SUFFIX,youtube.soy +DOMAIN-SUFFIX,youtube.sv +DOMAIN-SUFFIX,youtube.tn +DOMAIN-SUFFIX,youtube.tv +DOMAIN-SUFFIX,youtube.ua +DOMAIN-SUFFIX,youtube.ug +DOMAIN-SUFFIX,youtube.uy +DOMAIN-SUFFIX,youtube.vn +DOMAIN-SUFFIX,youtubeeducation.com +DOMAIN-SUFFIX,youtubeembeddedplayer.googleapis.com +DOMAIN-SUFFIX,youtubefanfest.com +DOMAIN-SUFFIX,youtubegaming.com +DOMAIN-SUFFIX,youtubego.co.id +DOMAIN-SUFFIX,youtubego.co.in +DOMAIN-SUFFIX,youtubego.com +DOMAIN-SUFFIX,youtubego.com.br +DOMAIN-SUFFIX,youtubego.id +DOMAIN-SUFFIX,youtubego.in +DOMAIN-SUFFIX,youtubei.googleapis.com +DOMAIN-SUFFIX,youtubekids.com +DOMAIN-SUFFIX,youtubemobilesupport.com +DOMAIN-SUFFIX,yt.be +DOMAIN-SUFFIX,ytimg.com +DOMAIN-KEYWORD,youtube +IP-CIDR,172.110.32.0/21,no-resolve +IP-CIDR,216.73.80.0/20,no-resolve +IP-CIDR6,2620:120:e000::/40,no-resolve +USER-AGENT,*YouTubeMusic* +USER-AGENT,*com.google.ios.youtubemusic* +USER-AGENT,*youtube* +USER-AGENT,YouTube* +USER-AGENT,YouTubeMusic* +USER-AGENT,com.google.ios.youtube* +USER-AGENT,com.google.ios.youtubemusic* diff --git a/dist/mihomo/YouTube.yaml b/dist/mihomo/YouTube.yaml new file mode 100644 index 000000000..a7a9320b6 --- /dev/null +++ b/dist/mihomo/YouTube.yaml @@ -0,0 +1,194 @@ +# NAME: YouTube +# AUTHOR: gitea-shunt-rules-mihomo +# SOURCE: rule/Surge/YouTube/YouTube.list +# UPDATED: 2026-04-06 05:38:50 UTC +# DOMAIN-KEYWORD: 1 +# DOMAIN-SUFFIX: 179 +# IP-CIDR: 2 +# IP-CIDR6: 1 +# SKIPPED-USER-AGENT: 7 +# TOTAL: 183 +payload: + - DOMAIN-SUFFIX,ggpht.cn + - DOMAIN-SUFFIX,ggpht.com + - DOMAIN-SUFFIX,googlevideo.com + - DOMAIN-SUFFIX,gvt1.com + - DOMAIN-SUFFIX,gvt2.com + - DOMAIN-SUFFIX,video.google.com + - DOMAIN-SUFFIX,wide-youtube.l.google.com + - DOMAIN-SUFFIX,withyoutube.com + - DOMAIN-SUFFIX,youtu.be + - DOMAIN-SUFFIX,youtube + - DOMAIN-SUFFIX,youtube-nocookie.com + - DOMAIN-SUFFIX,youtube-ui.l.google.com + - DOMAIN-SUFFIX,youtube.ae + - DOMAIN-SUFFIX,youtube.al + - DOMAIN-SUFFIX,youtube.am + - DOMAIN-SUFFIX,youtube.at + - DOMAIN-SUFFIX,youtube.az + - DOMAIN-SUFFIX,youtube.ba + - DOMAIN-SUFFIX,youtube.be + - DOMAIN-SUFFIX,youtube.bg + - DOMAIN-SUFFIX,youtube.bh + - DOMAIN-SUFFIX,youtube.bo + - DOMAIN-SUFFIX,youtube.by + - DOMAIN-SUFFIX,youtube.ca + - DOMAIN-SUFFIX,youtube.cat + - DOMAIN-SUFFIX,youtube.ch + - DOMAIN-SUFFIX,youtube.cl + - DOMAIN-SUFFIX,youtube.co + - DOMAIN-SUFFIX,youtube.co.ae + - DOMAIN-SUFFIX,youtube.co.at + - DOMAIN-SUFFIX,youtube.co.cr + - DOMAIN-SUFFIX,youtube.co.hu + - DOMAIN-SUFFIX,youtube.co.id + - DOMAIN-SUFFIX,youtube.co.il + - DOMAIN-SUFFIX,youtube.co.in + - DOMAIN-SUFFIX,youtube.co.jp + - DOMAIN-SUFFIX,youtube.co.ke + - DOMAIN-SUFFIX,youtube.co.kr + - DOMAIN-SUFFIX,youtube.co.ma + - DOMAIN-SUFFIX,youtube.co.nz + - DOMAIN-SUFFIX,youtube.co.th + - DOMAIN-SUFFIX,youtube.co.tz + - DOMAIN-SUFFIX,youtube.co.ug + - DOMAIN-SUFFIX,youtube.co.uk + - DOMAIN-SUFFIX,youtube.co.ve + - DOMAIN-SUFFIX,youtube.co.za + - DOMAIN-SUFFIX,youtube.co.zw + - DOMAIN-SUFFIX,youtube.com + - DOMAIN-SUFFIX,youtube.com.ar + - DOMAIN-SUFFIX,youtube.com.au + - DOMAIN-SUFFIX,youtube.com.az + - DOMAIN-SUFFIX,youtube.com.bd + - DOMAIN-SUFFIX,youtube.com.bh + - DOMAIN-SUFFIX,youtube.com.bo + - DOMAIN-SUFFIX,youtube.com.br + - DOMAIN-SUFFIX,youtube.com.by + - DOMAIN-SUFFIX,youtube.com.co + - DOMAIN-SUFFIX,youtube.com.do + - DOMAIN-SUFFIX,youtube.com.ec + - DOMAIN-SUFFIX,youtube.com.ee + - DOMAIN-SUFFIX,youtube.com.eg + - DOMAIN-SUFFIX,youtube.com.es + - DOMAIN-SUFFIX,youtube.com.gh + - DOMAIN-SUFFIX,youtube.com.gr + - DOMAIN-SUFFIX,youtube.com.gt + - DOMAIN-SUFFIX,youtube.com.hk + - DOMAIN-SUFFIX,youtube.com.hn + - DOMAIN-SUFFIX,youtube.com.hr + - DOMAIN-SUFFIX,youtube.com.jm + - DOMAIN-SUFFIX,youtube.com.jo + - DOMAIN-SUFFIX,youtube.com.kw + - DOMAIN-SUFFIX,youtube.com.lb + - DOMAIN-SUFFIX,youtube.com.lv + - DOMAIN-SUFFIX,youtube.com.ly + - DOMAIN-SUFFIX,youtube.com.mk + - DOMAIN-SUFFIX,youtube.com.mt + - DOMAIN-SUFFIX,youtube.com.mx + - DOMAIN-SUFFIX,youtube.com.my + - DOMAIN-SUFFIX,youtube.com.ng + - DOMAIN-SUFFIX,youtube.com.ni + - DOMAIN-SUFFIX,youtube.com.om + - DOMAIN-SUFFIX,youtube.com.pa + - DOMAIN-SUFFIX,youtube.com.pe + - DOMAIN-SUFFIX,youtube.com.ph + - DOMAIN-SUFFIX,youtube.com.pk + - DOMAIN-SUFFIX,youtube.com.pt + - DOMAIN-SUFFIX,youtube.com.py + - DOMAIN-SUFFIX,youtube.com.qa + - DOMAIN-SUFFIX,youtube.com.ro + - DOMAIN-SUFFIX,youtube.com.sa + - DOMAIN-SUFFIX,youtube.com.sg + - DOMAIN-SUFFIX,youtube.com.sv + - DOMAIN-SUFFIX,youtube.com.tn + - DOMAIN-SUFFIX,youtube.com.tr + - DOMAIN-SUFFIX,youtube.com.tw + - DOMAIN-SUFFIX,youtube.com.ua + - DOMAIN-SUFFIX,youtube.com.uy + - DOMAIN-SUFFIX,youtube.com.ve + - DOMAIN-SUFFIX,youtube.cr + - DOMAIN-SUFFIX,youtube.cz + - DOMAIN-SUFFIX,youtube.de + - DOMAIN-SUFFIX,youtube.dk + - DOMAIN-SUFFIX,youtube.ee + - DOMAIN-SUFFIX,youtube.es + - DOMAIN-SUFFIX,youtube.fi + - DOMAIN-SUFFIX,youtube.fr + - DOMAIN-SUFFIX,youtube.ge + - DOMAIN-SUFFIX,youtube.googleapis.com + - DOMAIN-SUFFIX,youtube.gr + - DOMAIN-SUFFIX,youtube.gt + - DOMAIN-SUFFIX,youtube.hk + - DOMAIN-SUFFIX,youtube.hr + - DOMAIN-SUFFIX,youtube.hu + - DOMAIN-SUFFIX,youtube.ie + - DOMAIN-SUFFIX,youtube.in + - DOMAIN-SUFFIX,youtube.iq + - DOMAIN-SUFFIX,youtube.is + - DOMAIN-SUFFIX,youtube.it + - DOMAIN-SUFFIX,youtube.jo + - DOMAIN-SUFFIX,youtube.jp + - DOMAIN-SUFFIX,youtube.kr + - DOMAIN-SUFFIX,youtube.kz + - DOMAIN-SUFFIX,youtube.la + - DOMAIN-SUFFIX,youtube.lk + - DOMAIN-SUFFIX,youtube.lt + - DOMAIN-SUFFIX,youtube.lu + - DOMAIN-SUFFIX,youtube.lv + - DOMAIN-SUFFIX,youtube.ly + - DOMAIN-SUFFIX,youtube.ma + - DOMAIN-SUFFIX,youtube.md + - DOMAIN-SUFFIX,youtube.me + - DOMAIN-SUFFIX,youtube.mk + - DOMAIN-SUFFIX,youtube.mn + - DOMAIN-SUFFIX,youtube.mx + - DOMAIN-SUFFIX,youtube.my + - DOMAIN-SUFFIX,youtube.ng + - DOMAIN-SUFFIX,youtube.ni + - DOMAIN-SUFFIX,youtube.nl + - DOMAIN-SUFFIX,youtube.no + - DOMAIN-SUFFIX,youtube.pa + - DOMAIN-SUFFIX,youtube.pe + - DOMAIN-SUFFIX,youtube.ph + - DOMAIN-SUFFIX,youtube.pk + - DOMAIN-SUFFIX,youtube.pl + - DOMAIN-SUFFIX,youtube.pr + - DOMAIN-SUFFIX,youtube.pt + - DOMAIN-SUFFIX,youtube.qa + - DOMAIN-SUFFIX,youtube.ro + - DOMAIN-SUFFIX,youtube.rs + - DOMAIN-SUFFIX,youtube.ru + - DOMAIN-SUFFIX,youtube.sa + - DOMAIN-SUFFIX,youtube.se + - DOMAIN-SUFFIX,youtube.sg + - DOMAIN-SUFFIX,youtube.si + - DOMAIN-SUFFIX,youtube.sk + - DOMAIN-SUFFIX,youtube.sn + - DOMAIN-SUFFIX,youtube.soy + - DOMAIN-SUFFIX,youtube.sv + - DOMAIN-SUFFIX,youtube.tn + - DOMAIN-SUFFIX,youtube.tv + - DOMAIN-SUFFIX,youtube.ua + - DOMAIN-SUFFIX,youtube.ug + - DOMAIN-SUFFIX,youtube.uy + - DOMAIN-SUFFIX,youtube.vn + - DOMAIN-SUFFIX,youtubeeducation.com + - DOMAIN-SUFFIX,youtubeembeddedplayer.googleapis.com + - DOMAIN-SUFFIX,youtubefanfest.com + - DOMAIN-SUFFIX,youtubegaming.com + - DOMAIN-SUFFIX,youtubego.co.id + - DOMAIN-SUFFIX,youtubego.co.in + - DOMAIN-SUFFIX,youtubego.com + - DOMAIN-SUFFIX,youtubego.com.br + - DOMAIN-SUFFIX,youtubego.id + - DOMAIN-SUFFIX,youtubego.in + - DOMAIN-SUFFIX,youtubei.googleapis.com + - DOMAIN-SUFFIX,youtubekids.com + - DOMAIN-SUFFIX,youtubemobilesupport.com + - DOMAIN-SUFFIX,yt.be + - DOMAIN-SUFFIX,ytimg.com + - DOMAIN-KEYWORD,youtube + - IP-CIDR,172.110.32.0/21 + - IP-CIDR,216.73.80.0/20 + - IP-CIDR6,2620:120:e000::/40 diff --git a/dist/surge/YouTube.list b/dist/surge/YouTube.list index 7ce267179..2833f1c81 100644 --- a/dist/surge/YouTube.list +++ b/dist/surge/YouTube.list @@ -1,7 +1,7 @@ # NAME: YouTube # AUTHOR: gitea-shunt-rules # SOURCE: rule/Surge/YouTube/YouTube.list -# UPDATED: 2026-04-06 05:01:32 UTC +# UPDATED: 2026-04-06 05:38:50 UTC # DOMAIN-KEYWORD: 1 # DOMAIN-SUFFIX: 179 # IP-CIDR: 2 diff --git a/main.py b/main.py index 4864224ad..552057b23 100644 --- a/main.py +++ b/main.py @@ -24,7 +24,10 @@ DEFAULT_LIMIT = 100 UNSUPPORTED_CLASH_TYPES = { "USER-AGENT", "URL-REGEX", - "DEST-PORT", # Surge alias, Clash usually uses DST-PORT +} +UNSUPPORTED_MIHOMO_TYPES = { + "USER-AGENT", + "URL-REGEX", } @@ -41,6 +44,7 @@ class Config: include_categories: list[str] exclude_categories: list[str] clash_no_resolve: bool + mihomo_no_resolve: bool @dataclass(frozen=True) @@ -152,6 +156,7 @@ def load_config(path: Path) -> Config: include_categories=source.get("include_categories", []), exclude_categories=source.get("exclude_categories", []), clash_no_resolve=output.get("clash_no_resolve", False), + mihomo_no_resolve=output.get("mihomo_no_resolve", False), ) @@ -177,19 +182,25 @@ def parse_rules(content: str) -> list[RuleLine]: return rules -def to_clash_payload_line(rule: RuleLine, no_resolve: bool) -> str | None: +def to_payload_line( + rule: RuleLine, + no_resolve: bool, + unsupported_types: set[str], + type_mapping: dict[str, str] | None = None, +) -> str | None: parts = [p.strip() for p in rule.raw.split(",") if p.strip()] if not parts: return None rule_type = parts[0].upper() - parts[0] = rule_type + mapped_type = (type_mapping or {}).get(rule_type, rule_type) + parts[0] = mapped_type - if rule_type in UNSUPPORTED_CLASH_TYPES: + if rule_type in unsupported_types: return None - if rule_type in {"IP-CIDR", "IP-CIDR6"}: - payload = [rule_type, parts[1]] if len(parts) >= 2 else parts + if mapped_type in {"IP-CIDR", "IP-CIDR6"}: + payload = [mapped_type, parts[1]] if len(parts) >= 2 else parts if no_resolve: payload.append("no-resolve") return ",".join(payload) @@ -217,7 +228,33 @@ def format_surge(name: str, rules: list[RuleLine], source_path: str) -> str: return "\n".join(header + body) + "\n" -def format_clash(name: str, rules: list[RuleLine], source_path: str, no_resolve: bool) -> str: +def format_loon(name: str, rules: list[RuleLine], source_path: str) -> str: + now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") + count = Counter(rule.rule_type for rule in rules) + + header = [ + f"# NAME: {name}", + "# AUTHOR: gitea-shunt-rules", + f"# SOURCE: {source_path}", + f"# UPDATED: {now}", + ] + for k in sorted(count): + header.append(f"# {k}: {count[k]}") + header.append(f"# TOTAL: {len(rules)}") + + body = [rule.raw for rule in rules] + return "\n".join(header + body) + "\n" + + +def format_yaml_payload( + name: str, + rules: list[RuleLine], + source_path: str, + no_resolve: bool, + unsupported_types: set[str], + type_mapping: dict[str, str] | None = None, + author_name: str = "gitea-shunt-rules", +) -> tuple[str, int]: now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") payload: list[str] = [] @@ -225,7 +262,12 @@ def format_clash(name: str, rules: list[RuleLine], source_path: str, no_resolve: skipped: Counter[str] = Counter() for rule in rules: - converted = to_clash_payload_line(rule, no_resolve=no_resolve) + converted = to_payload_line( + rule, + no_resolve=no_resolve, + unsupported_types=unsupported_types, + type_mapping=type_mapping, + ) if converted is None: skipped[rule.rule_type] += 1 continue @@ -234,7 +276,7 @@ def format_clash(name: str, rules: list[RuleLine], source_path: str, no_resolve: lines = [ f"# NAME: {name}", - "# AUTHOR: gitea-shunt-rules", + f"# AUTHOR: {author_name}", f"# SOURCE: {source_path}", f"# UPDATED: {now}", ] @@ -246,7 +288,7 @@ def format_clash(name: str, rules: list[RuleLine], source_path: str, no_resolve: lines.append("payload:") lines.extend(f" - {item}" for item in payload) - return "\n".join(lines) + "\n" + return "\n".join(lines) + "\n", len(payload) def should_include_category(name: str, cfg: Config, cli_names: set[str]) -> bool: @@ -285,7 +327,7 @@ def find_categories(client: GiteaClient, cfg: Config, cli_names: set[str]) -> li return sorted(categories) -def build_one_category(client: GiteaClient, cfg: Config, name: str, base_out: Path) -> tuple[int, int]: +def build_one_category(client: GiteaClient, cfg: Config, name: str, base_out: Path) -> tuple[int, int, int, int]: filename_base = cfg.source_filename_pattern.format(name=name) candidate_filenames = [ filename_base, @@ -318,21 +360,48 @@ def build_one_category(client: GiteaClient, cfg: Config, name: str, base_out: Pa rules = parse_rules(source_content) surge_out = base_out / "surge" / f"{name}.list" + loon_out = base_out / "loon" / f"{name}.list" clash_out = base_out / "clash" / f"{name}.yaml" + mihomo_out = base_out / "mihomo" / f"{name}.yaml" surge_out.parent.mkdir(parents=True, exist_ok=True) + loon_out.parent.mkdir(parents=True, exist_ok=True) clash_out.parent.mkdir(parents=True, exist_ok=True) + mihomo_out.parent.mkdir(parents=True, exist_ok=True) surge_out.write_text(format_surge(name, rules, source_rel_path), encoding="utf-8") - clash_out.write_text( - format_clash(name, rules, source_rel_path, no_resolve=cfg.clash_no_resolve), + loon_out.write_text(format_loon(name, rules, source_rel_path), encoding="utf-8") + + clash_text, clash_cnt = format_yaml_payload( + name, + rules, + source_rel_path, + no_resolve=cfg.clash_no_resolve, + unsupported_types=UNSUPPORTED_CLASH_TYPES, + type_mapping={"DEST-PORT": "DST-PORT"}, + author_name="gitea-shunt-rules", + ) + clash_out.write_text(clash_text, encoding="utf-8") + + mihomo_text, mihomo_cnt = format_yaml_payload( + name, + rules, + source_rel_path, + no_resolve=cfg.mihomo_no_resolve, + unsupported_types=UNSUPPORTED_MIHOMO_TYPES, + type_mapping={"DEST-PORT": "DST-PORT"}, + author_name="gitea-shunt-rules-mihomo", + ) + mihomo_out.write_text( + mihomo_text, encoding="utf-8", ) - return len(rules), sum(1 for r in rules if to_clash_payload_line(r, no_resolve=cfg.clash_no_resolve) is not None) + # source and loon keep the same parsed set + return len(rules), len(rules), clash_cnt, mihomo_cnt def parse_args() -> argparse.Namespace: - p = argparse.ArgumentParser(description="Generate Surge/Clash rules from Gitea source repo.") + p = argparse.ArgumentParser(description="Generate Surge/Loon/Clash/Mihomo rules from Gitea source repo.") p.add_argument("--config", default="config.toml", help="Path to config TOML file") p.add_argument("--names", default="", help="Comma-separated category names, e.g. YouTube,Netflix") return p.parse_args() @@ -354,19 +423,30 @@ def main() -> int: out_dir.mkdir(parents=True, exist_ok=True) total_source = 0 + total_loon = 0 total_clash = 0 + total_mihomo = 0 print(f"Found {len(categories)} categories under {cfg.source_root}") for idx, name in enumerate(categories, start=1): try: - s_cnt, c_cnt = build_one_category(client, cfg, name, out_dir) + s_cnt, l_cnt, c_cnt, m_cnt = build_one_category(client, cfg, name, out_dir) total_source += s_cnt + total_loon += l_cnt total_clash += c_cnt - print(f"[{idx}/{len(categories)}] {name}: source={s_cnt}, clash={c_cnt}") + total_mihomo += m_cnt + print(f"[{idx}/{len(categories)}] {name}: source={s_cnt}, loon={l_cnt}, clash={c_cnt}, mihomo={m_cnt}") except Exception as exc: print(f"[{idx}/{len(categories)}] {name}: failed: {exc}", file=sys.stderr) - print(f"Done. source_rules={total_source}, clash_rules={total_clash}, output={out_dir.resolve()}") + print( + "Done. " + f"source_rules={total_source}, " + f"loon_rules={total_loon}, " + f"clash_rules={total_clash}, " + f"mihomo_rules={total_mihomo}, " + f"output={out_dir.resolve()}" + ) return 0