admin / Sub Store clash脚本

Last active 1 hour ago

Like 0

Sub Store clash分流策略脚本

Sub_Store_clash_config.js Raw
1/**
2 * ============================================================
3 * Sub-Store → Mihomo / Clash Meta 配置生成器
4 * ============================================================
5 *
6 * 当前版本:v1.2.0
7 * 更新时间:2026-08-23
8 *
9 * v1.2.0
10 * ────────────────────────────────────────────────────────────
11 * 1. 重构地区节点分类
12 * - 每个节点只进行一次地区分类
13 * - 保持香港 / 台湾 / 日本 / 新加坡 / 美国 / 韩国 / 其他
14 * 的优先级顺序
15 *
16 * 2. 修复 AI 节点兜底逻辑
17 * - 香港永远不会进入人工智能策略
18 * - 无非香港地区节点时,AI 使用 DIRECT
19 * - 不再通过自动选择间接引入香港
20 *
21 * 3. 优化策略组排序
22 * - 🚀 节点选择
23 * - ♻️ 自动选择
24 * - 🔯 故障转移
25 * 放置在最顶部
26 *
27 * 4. 统一策略组创建逻辑
28 * - 减少重复代码
29 * - 统一候选节点去重
30 * - 降低后续维护难度
31 *
32 * 5. 优化空策略组处理
33 * - 没有节点的地区自动隐藏
34 * - 🇨🇳 其他节点为空时自动隐藏
35 *
36 * 6. 保留原有功能
37 * - TUN
38 * - DNS
39 * - Sniffer
40 * - Fake-IP
41 * - Rule Provider
42 * - AI
43 * - 海外服务
44 * - 网络测试
45 * - 广告拦截
46 * - 代理域名
47 * - 全球直连
48 * - 最终兜底
49 *
50 * 7. 保留原测速参数
51 * - URL:https://www.gstatic.com/generate_204
52 * - interval:600
53 * - timeout:3000
54 * - tolerance:80
55 * - lazy:true
56 * - max-failed-times:3
57 *
58 * 版本:v1.1.0
59 * 更新时间:2026-08-22
60 *
61 * v1.1.0
62 * ────────────────────────────────────────────────────────────
63 * 1. 重构“🇨🇳 其他节点”
64 * - 仅作为真正的地区兜底节点组
65 * - 不属于香港 / 台湾 / 日本 / 新加坡 / 美国 / 韩国
66 * 的节点全部进入这里
67 * - 没有其他节点时自动隐藏
68 *
69 * 2. “💬 GPT专用”更名为“🤖 人工智能”
70 * - 不再只针对 GPT
71 * - 香港节点不加入人工智能节点
72 * - 台湾 / 日本 / 新加坡 / 美国 / 韩国 / 其他节点
73 * 均可参与人工智能节点选择
74 *
75 * 3. “🐟 漏网之鱼”更名为“🛟 兜底策略”
76 * - MATCH 最终兜底策略名称同步修改
77 *
78 * 4. 合并重复策略组
79 * - 微软服务
80 * - 苹果服务
81 * - 游戏平台
82 * - 国内 IP
83 * - 直连域名
84 * - 直连软件
85 * - 私有网络
86 * 等不再单独创建重复 Select 组
87 *
88 * 5. 统一直连策略
89 * - 所有确定应该 DIRECT 的规则统一进入:
90 * 🎯 全球直连
91 *
92 * 6. 减少策略组数量
93 * - 保留真正有实际选择意义的策略组
94 * - 降低 Clash Meta Android 界面复杂度
95 *
96 * 7. 优化自动选择
97 * - 地区内部先选择最佳节点
98 * - 自动选择再选择最佳地区
99 * - 避免直接对所有节点重复测速
100 *
101 * 8. 优化人工智能节点
102 * - 使用地区组作为候选
103 * - 避免人工智能策略组重复测速所有具体节点
104 *
105 * 9. 优化 Android 后台运行
106 * - URL-Test:600 秒
107 * - Fallback:600 秒
108 * - tolerance:80ms
109 * - lazy:true
110 *
111 * ============================================================
112 */
113
114
115/* ============================================================
116 * 1. 基础配置
117 * ============================================================ */
118
119const generalConfig = {
120
121 "mode": "rule",
122
123 /*
124 * 关闭 IPv6。
125 *
126 * 移动网络环境下优先保证稳定性。
127 */
128 "ipv6": false,
129
130 "log-level": "silent",
131
132 "allow-lan": true,
133
134 /*
135 * 统一延迟计算。
136 */
137 "unified-delay": true,
138
139 /*
140 * TCP 并发连接。
141 */
142 "tcp-concurrent": true,
143
144 /*
145 * 严格进程匹配。
146 */
147 "find-process-mode": "strict",
148
149 /*
150 * Chrome TLS 指纹。
151 */
152 "global-client-fingerprint": "chrome",
153
154 "profile": {
155 "store-selected": true,
156 "store-fake-ip": true
157 }
158};
159
160
161/* ============================================================
162 * 2. Sniffer
163 * ============================================================ */
164
165const snifferConfig = {
166
167 "enable": true,
168
169 "parse-pure-ip": true,
170
171 "sniff": {
172
173 "HTTP": {
174 "ports": [
175 80,
176 "8080-8880"
177 ],
178 "override-destination": true
179 },
180
181 "TLS": {
182 "ports": [
183 443,
184 8443
185 ]
186 },
187
188 "QUIC": {
189 "ports": [
190 443,
191 8443
192 ]
193 }
194 },
195
196 "skip-domain": [
197 "Mijia Cloud"
198 ]
199};
200
201
202/* ============================================================
203 * 3. TUN
204 * ============================================================ */
205
206const tunConfig = {
207
208 "enable": true,
209
210 "stack": "system",
211
212 "dns-hijack": [
213 "any:53"
214 ],
215
216 "auto-route": true,
217
218 "auto-detect-interface": true,
219
220 "strict-route": true
221};
222
223
224/* ============================================================
225 * 4. DNS
226 * ============================================================ */
227
228const dnsConfig = {
229
230 "enable": true,
231
232 "ipv6": false,
233
234 /*
235 * 移动网络环境下优先稳定性。
236 */
237 "prefer-h3": false,
238
239 "listen": "0.0.0.0:53",
240
241 "fake-ip-range": "198.18.0.1/16",
242
243 "enhanced-mode": "fake-ip",
244
245 /*
246 * 主 DNS。
247 */
248 "nameserver": [
249 "https://dns.alidns.com/dns-query",
250 "https://doh.pub/dns-query"
251 ],
252
253 /*
254 * DNS 备用。
255 */
256 "fallback": [
257 "223.5.5.5",
258 "119.29.29.29"
259 ],
260
261 /*
262 * 启动阶段 DNS。
263 */
264 "default-nameserver": [
265 "223.5.5.5",
266 "114.114.114.114"
267 ],
268
269 /*
270 * DNS 回退过滤。
271 */
272 "fallback-filter": {
273
274 "geoip": true,
275
276 "geoip-code": "CN",
277
278 "geosite": [
279 "gfw"
280 ],
281
282 "ipcidr": [
283 "240.0.0.0/4",
284 "0.0.0.0/32",
285 "127.0.0.1/32"
286 ]
287 },
288
289 /*
290 * Fake-IP 排除列表。
291 */
292 "fake-ip-filter": [
293
294 "*.lan",
295 "*.localdomain",
296 "*.example",
297 "*.invalid",
298 "*.localhost",
299 "*.test",
300 "*.local",
301 "*.home.arpa",
302
303 "*.woozooo.com",
304 "*.kiid.top",
305 "43.142.112.88",
306
307 "time.*.com",
308 "time.*.gov",
309 "time.*.edu.cn",
310 "time.*.apple.com",
311 "time-ios.apple.com",
312
313 "time1.*.com",
314 "time2.*.com",
315 "time3.*.com",
316 "time4.*.com",
317 "time5.*.com",
318 "time6.*.com",
319 "time7.*.com",
320
321 "ntp.*.com",
322 "ntp1.*.com",
323 "ntp2.*.com",
324 "ntp3.*.com",
325 "ntp4.*.com",
326 "ntp5.*.com",
327 "ntp6.*.com",
328 "ntp7.*.com",
329
330 "*.time.edu.cn",
331 "*.ntp.org.cn",
332 "+.pool.ntp.org",
333 "time1.cloud.tencent.com",
334
335 "music.163.com",
336 "*.music.163.com",
337 "*.126.net",
338
339 "musicapi.taihe.com",
340 "music.taihe.com",
341
342 "songsearch.kugou.com",
343 "trackercdn.kugou.com",
344
345 "*.kuwo.cn",
346 "api-jooxtt.sanook.com",
347 "api.joox.com",
348 "joox.com",
349
350 "y.qq.com",
351 "*.y.qq.com",
352 "streamoc.music.tc.qq.com",
353 "mobileoc.music.tc.qq.com",
354 "isure.stream.qqmusic.qq.com",
355 "dl.stream.qq.com",
356 "aqqmusic.tc.qq.com",
357 "amobile.music.tc.qq.com",
358
359 "*.xiami.com",
360 "*.music.migu.cn",
361 "music.migu.cn",
362
363 "+.msftconnecttest.com",
364 "+.msftncsi.com",
365
366 "localhost.ptlogin2.qq.com",
367 "localhost.sec.qq.com",
368 "+.qq.com",
369 "+.tencent.com",
370
371 "+.srv.nintendo.net",
372 "*.n.n.srv.nintendo.net",
373 "+.stun.playstation.net",
374
375 "xbox.*.*.microsoft.com",
376 "*.*.xboxlive.com",
377 "xbox.*.microsoft.com",
378 "xnotify.xboxlive.com",
379
380 "+.battlenet.com.cn",
381 "+.wotgame.cn",
382 "+.wggames.cn",
383 "+.wowsgame.cn",
384 "+.wargaming.net",
385
386 "proxy.golang.org",
387
388 "stun.*.*",
389 "stun.*.*.*",
390 "+.stun.*.*",
391 "+.stun.*.*.*",
392 "+.stun.*.*.*.*",
393 "+.stun.*.*.*.*.*",
394
395 "heartbeat.belkin.com",
396 "*.linksys.com",
397 "*.linksyssmartwifi.com",
398 "*.router.asus.com",
399
400 "mesu.apple.com",
401 "swscan.apple.com",
402 "swquery.apple.com",
403 "swdownload.apple.com",
404 "swdist.apple.com",
405
406 "lens.l.google.com",
407 "stun.l.google.com",
408 "na.b.g-tun.com",
409
410 "+.nflxvideo.net",
411
412 "*.square-enix.com",
413 "*.finalfantasyxiv.com",
414 "*.ffxiv.com",
415 "*.ff14.sdo.com",
416 "ff.dorado.sdo.com",
417
418 "*.mcdn.bilivideo.cn",
419 "+.media.dssott.com",
420
421 "shark007.net",
422 "Mijia Cloud",
423
424 "+.cmbchina.com",
425 "+.cmbimg.com",
426
427 "adguardteam.github.io",
428 "adrules.top",
429 "anti-ad.net",
430 "local.adguard.org",
431 "static.adtidy.org",
432
433 "+.sandai.net",
434 "+.n0808.com",
435 "+.3gppnetwork.org"
436 ]
437};
438
439
440/* ============================================================
441 * 5. 节点名称过滤
442 * ============================================================ */
443
444/*
445 * 排除:
446 *
447 * - 回国
448 * - 校园
449 * - 游戏
450 * - 通知
451 * - 流量
452 * - 到期
453 * - 官网
454 * - 客服
455 * - 机场
456 * - 邀请
457 * - 返利
458 * - 循环
459 * - 备用
460 * - 过期
461 * - 已用
462 * - 联系
463 * - 邮箱
464 * - 工单
465 * - 贩卖
466 * - 倒卖
467 * - 防止
468 * - 国内
469 * - 地址
470 * - 频道
471 * - 无法
472 * - 说明
473 * - 使用
474 * - 提示
475 * - 特别
476 * - 访问
477 * - 支持
478 * - 教程
479 * - 关注
480 * - 更新
481 * - 作者
482 * - 加入
483 * - 超时
484 * - 收藏
485 * - 福利
486 * - 失联
487 * 等非实际代理节点。
488 */
489
490const nodeNameExcludeReg =
491 /回国|校园|游戏|通知|流量|到期|官网|客服|机场|邀请|返利|循环|备用|过期|已用|联系|邮箱|工单|贩卖|倒卖|防止|国内|地址|频道|无法|说明|使用|提示|特别|访问|支持|教程|关注|更新|作者|加入|超时|收藏|福利|失联|\b(USE|USED|TOTAL|EXPIRE|EMAIL|PANEL|CHANNEL|AUTHOR|TRAFFIC)\b|compatible/i;
492
493
494/* ============================================================
495 * 6. 地区定义
496 * ============================================================ */
497
498/*
499 * 地区匹配优先级:
500 *
501 * 1. 香港
502 * 2. 台湾
503 * 3. 日本
504 * 4. 新加坡
505 * 5. 美国
506 * 6. 韩国
507 * 7. 其他
508 *
509 * 其他没有 pattern,
510 * 只负责接收没有匹配任何已知地区的节点。
511 */
512
513const regionDefinitions = [
514
515 {
516 key: "hongKong",
517 groupName: "🇭🇰 香港节点",
518 pattern: /🇭🇰|香港|\b(HK|HKG|Hong Kong)\b/i
519 },
520
521 {
522 key: "taiwan",
523 groupName: "🇹🇼 台湾节点",
524 pattern: /🇹🇼|台湾|\b(TW|TWN|Taiwan)\b/i
525 },
526
527 {
528 key: "japan",
529 groupName: "🇯🇵 日本节点",
530 pattern: /🇯🇵|日本|\b(JP|JPN|Japan)\b/i
531 },
532
533 {
534 key: "singapore",
535 groupName: "🇸🇬 新加坡节点",
536 pattern: /🇸🇬|新加坡|狮城|\b(SG|SGP|Singapore)\b/i
537 },
538
539 {
540 key: "usa",
541 groupName: "🇺🇸 美国节点",
542 pattern: /🇺🇸|美国|\b(US|USA|United States)\b/i
543 },
544
545 {
546 key: "korea",
547 groupName: "🇰🇷 韩国节点",
548 pattern: /🇰🇷|韩国|首尔|\b(KR|KOR|Korea)\b/i
549 },
550
551 {
552 key: "other",
553 groupName: "🇨🇳 其他节点",
554 pattern: null
555 }
556];
557
558
559/* ============================================================
560 * 7. AI 地区
561 * ============================================================ */
562
563/*
564 * 香港明确排除。
565 *
566 * AI 可以使用:
567 *
568 * 台湾
569 * 日本
570 * 新加坡
571 * 美国
572 * 韩国
573 * 其他
574 */
575
576const aiRegionKeys = [
577
578 "taiwan",
579
580 "japan",
581
582 "singapore",
583
584 "usa",
585
586 "korea",
587
588 "other"
589];
590
591
592/* ============================================================
593 * 8. 测速配置
594 * ============================================================ */
595
596const speedTestUrl =
597 "https://www.gstatic.com/generate_204";
598
599
600/*
601 * 所有 URL-Test 统一使用此参数。
602 */
603
604const urlTestDefaults = {
605
606 "interval": 600,
607
608 "timeout": 3000,
609
610 "url": speedTestUrl,
611
612 "lazy": true,
613
614 "max-failed-times": 3,
615
616 "tolerance": 80
617};
618
619
620/* ============================================================
621 * 9. Rule Provider
622 * ============================================================ */
623
624const mirrorBaseUrl =
625 "https://ghproxy.net/";
626
627
628const ruleProviders = {
629
630 "ads": {
631 "type": "http",
632 "behavior": "domain",
633 "format": "text",
634 "path": "./rules/adss.list",
635 "url": mirrorBaseUrl +
636 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/ads.list",
637 "interval": 86400
638 },
639
640 "applications": {
641 "type": "http",
642 "behavior": "classical",
643 "format": "text",
644 "path": "./rules/applicationss.list",
645 "url": mirrorBaseUrl +
646 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/applications.list",
647 "interval": 86400
648 },
649
650 "private": {
651 "type": "http",
652 "behavior": "domain",
653 "format": "text",
654 "path": "./rules/privates.list",
655 "url": mirrorBaseUrl +
656 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/private.list",
657 "filter": "^((?!openai).)*$",
658 "interval": 86400
659 },
660
661 "microsoft-cn": {
662 "type": "http",
663 "behavior": "domain",
664 "format": "text",
665 "path": "./rules/microsoft-cns.list",
666 "url": mirrorBaseUrl +
667 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/microsoft-cn.list",
668 "interval": 86400
669 },
670
671 "openai": {
672 "type": "http",
673 "behavior": "domain",
674 "format": "text",
675 "path": "./rules/openais.list",
676 "url": mirrorBaseUrl +
677 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/mihomo-ruleset/ai.list",
678 "interval": 86400
679 },
680
681 "apple-cn": {
682 "type": "http",
683 "behavior": "domain",
684 "format": "text",
685 "path": "./rules/apple-cns.list",
686 "url": mirrorBaseUrl +
687 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/apple-cn.list",
688 "interval": 86400
689 },
690
691 "google-cn": {
692 "type": "http",
693 "behavior": "domain",
694 "format": "text",
695 "path": "./rules/google-cns.list",
696 "url": mirrorBaseUrl +
697 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/google-cn.list",
698 "interval": 86400
699 },
700
701 "games-cn": {
702 "type": "http",
703 "behavior": "domain",
704 "format": "text",
705 "path": "./rules/games-cns.list",
706 "url": mirrorBaseUrl +
707 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/games-cn.list",
708 "interval": 86400
709 },
710
711 "networktest": {
712 "type": "http",
713 "behavior": "classical",
714 "format": "text",
715 "path": "./rules/networktests.list",
716 "url": mirrorBaseUrl +
717 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/networktest.list",
718 "interval": 86400
719 },
720
721 "proxy": {
722 "type": "http",
723 "behavior": "domain",
724 "format": "text",
725 "path": "./rules/proxys.list",
726 "url": mirrorBaseUrl +
727 "https://raw.githubusercontent.com/y7478729/ruleset_geodata/clash-ruleset/proxy.list",
728 "interval": 86400
729 },
730
731 "cn": {
732 "type": "http",
733 "behavior": "domain",
734 "format": "text",
735 "path": "./rules/cns.list",
736 "url": mirrorBaseUrl +
737 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/cn.list",
738 "interval": 86400
739 },
740
741 "telegramip": {
742 "type": "http",
743 "behavior": "ipcidr",
744 "format": "text",
745 "path": "./rules/telegramips.list",
746 "url": mirrorBaseUrl +
747 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/telegramip.list",
748 "interval": 86400
749 },
750
751 "privateip": {
752 "type": "http",
753 "behavior": "ipcidr",
754 "format": "text",
755 "path": "./rules/privateips.list",
756 "url": mirrorBaseUrl +
757 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/privateip.list",
758 "interval": 86400
759 },
760
761 "cnip": {
762 "type": "http",
763 "behavior": "ipcidr",
764 "format": "text",
765 "path": "./rules/cnips.list",
766 "url": mirrorBaseUrl +
767 "https://raw.githubusercontent.com/DustinWin/ruleset_geodata/clash-ruleset/cnip.list",
768 "interval": 86400
769 }
770};
771
772
773/* ============================================================
774 * 10. 分流规则
775 * ============================================================ */
776
777const rules = [
778
779 /*
780 * AI
781 */
782 "RULE-SET,openai,🤖 人工智能",
783
784 /*
785 * 广告
786 */
787 "RULE-SET,ads,🆎 广告拦截",
788
789 /*
790 * 本地应用
791 */
792 "RULE-SET,applications,🎯 全球直连",
793
794 /*
795 * 私有网络
796 */
797 "RULE-SET,private,🎯 全球直连",
798
799 /*
800 * 微软
801 */
802 "RULE-SET,microsoft-cn,🎯 全球直连",
803
804 /*
805 * Apple
806 */
807 "RULE-SET,apple-cn,🎯 全球直连",
808
809 /*
810 * Google
811 */
812 "RULE-SET,google-cn,🌐 海外服务",
813
814 /*
815 * 游戏
816 */
817 "RULE-SET,games-cn,🎯 全球直连",
818
819 /*
820 * 网络测试
821 */
822 "RULE-SET,networktest,📈 网络测试",
823
824 /*
825 * 已知代理域名
826 */
827 "RULE-SET,proxy,🪜 代理域名",
828
829 /*
830 * 中国域名
831 */
832 "RULE-SET,cn,🎯 全球直连",
833
834 /*
835 * Telegram IP
836 */
837 "RULE-SET,telegramip,🌐 海外服务,no-resolve",
838
839 /*
840 * 私有 IP
841 */
842 "RULE-SET,privateip,🎯 全球直连,no-resolve",
843
844 /*
845 * 中国 IP
846 */
847 "RULE-SET,cnip,🎯 全球直连,no-resolve",
848
849 /*
850 * 最终兜底
851 */
852 "MATCH,🛟 兜底策略"
853];
854
855
856/* ============================================================
857 * 11. 通用工具函数
858 * ============================================================ */
859
860
861/*
862 * 数组去重,同时保持原始顺序。
863 */
864function unique(items) {
865
866 return [
867 ...new Set(
868 items.filter(Boolean)
869 )
870 ];
871}
872
873
874/*
875 * 安全读取节点名称。
876 */
877function getProxyName(proxy) {
878
879 if (
880 !proxy ||
881 typeof proxy.name !== "string"
882 ) {
883 return "";
884 }
885
886 return proxy.name.trim();
887}
888
889
890/*
891 * 判断是否为有效节点。
892 */
893function isValidProxy(proxy) {
894
895 const name =
896 getProxyName(proxy);
897
898 if (!name) {
899 return false;
900 }
901
902 return !nodeNameExcludeReg.test(
903 name
904 );
905}
906
907
908/* ============================================================
909 * 12. 获取原始节点
910 * ============================================================ */
911
912function getRawProxies(config) {
913
914 /*
915 * 优先使用 Sub-Store 传入的 config.proxies。
916 */
917 if (
918 config &&
919 Array.isArray(config.proxies)
920 ) {
921 return config.proxies;
922 }
923
924 /*
925 * 兼容 Sub-Store $proxies。
926 */
927 if (
928 typeof $proxies !== "undefined" &&
929 Array.isArray($proxies)
930 ) {
931 return $proxies;
932 }
933
934 return [];
935}
936
937
938/* ============================================================
939 * 13. 过滤有效节点
940 * ============================================================ */
941
942function filterValidProxies(proxies) {
943
944 return unique(
945 proxies
946 .filter(isValidProxy)
947 .map(proxy => ({
948 ...proxy,
949 name: getProxyName(proxy)
950 }))
951 );
952}
953
954
955/* ============================================================
956 * 14. 节点地区分类
957 * ============================================================ */
958
959/*
960 * 与旧版本不同:
961 *
962 * 不再:
963 *
964 * 地区 1 → 遍历全部节点
965 * 地区 2 → 遍历全部节点
966 * 地区 3 → 遍历全部节点
967 *
968 * 而是:
969 *
970 * 节点 1 → 找到所属地区
971 * 节点 2 → 找到所属地区
972 * 节点 3 → 找到所属地区
973 *
974 * 每个节点只分类一次。
975 */
976
977function classifyProxies(
978 proxies
979) {
980
981 const regionProxyMap = {};
982
983 /*
984 * 初始化所有地区。
985 */
986 regionDefinitions.forEach(
987 region => {
988
989 regionProxyMap[
990 region.key
991 ] = [];
992 }
993 );
994
995
996 /*
997 * 每个节点只处理一次。
998 */
999 proxies.forEach(proxy => {
1000
1001 const name =
1002 getProxyName(proxy);
1003
1004 /*
1005 * 从前往后寻找第一个匹配地区。
1006 *
1007 * other 没有 pattern,
1008 * 因此最终自然成为兜底。
1009 */
1010 const matchedRegion =
1011 regionDefinitions.find(
1012 region => {
1013
1014 if (!region.pattern) {
1015 return true;
1016 }
1017
1018 return region.pattern.test(
1019 name
1020 );
1021 }
1022 );
1023
1024
1025 /*
1026 * 理论上不会出现,
1027 * 因为 other 永远存在。
1028 */
1029 if (!matchedRegion) {
1030 return;
1031 }
1032
1033
1034 regionProxyMap[
1035 matchedRegion.key
1036 ].push(name);
1037 });
1038
1039
1040 /*
1041 * 最终统一去重。
1042 */
1043 Object.keys(
1044 regionProxyMap
1045 ).forEach(key => {
1046
1047 regionProxyMap[key] =
1048 unique(
1049 regionProxyMap[key]
1050 );
1051 });
1052
1053
1054 return regionProxyMap;
1055}
1056
1057
1058/* ============================================================
1059 * 15. 创建地区测速组
1060 * ============================================================ */
1061
1062function createRegionUrlTestGroup(
1063 region,
1064 proxyNames
1065) {
1066
1067 return {
1068
1069 ...urlTestDefaults,
1070
1071 "name": region.groupName,
1072
1073 "type": "url-test",
1074
1075 "proxies": unique(
1076 proxyNames
1077 ),
1078
1079 /*
1080 * 地区组只允许真实代理节点。
1081 */
1082 "exclude-type": "direct"
1083 };
1084}
1085
1086
1087/* ============================================================
1088 * 16. 创建 Select 组
1089 * ============================================================ */
1090
1091function createSelectGroup(
1092 name,
1093 proxies,
1094 options = {}
1095) {
1096
1097 return {
1098
1099 "name": name,
1100
1101 "type": "select",
1102
1103 "proxies": unique(
1104 proxies
1105 ),
1106
1107 ...options
1108 };
1109}
1110
1111
1112/* ============================================================
1113 * 17. 获取地区组名称
1114 * ============================================================ */
1115
1116function getActiveRegionNames(
1117 regionDefinitions,
1118 regionProxyMap
1119) {
1120
1121 return regionDefinitions
1122 .filter(region => {
1123
1124 const nodes =
1125 regionProxyMap[
1126 region.key
1127 ];
1128
1129 return (
1130 Array.isArray(nodes) &&
1131 nodes.length > 0
1132 );
1133 })
1134 .map(region =>
1135 region.groupName
1136 );
1137}
1138
1139
1140/* ============================================================
1141 * 18. 获取 AI 候选地区
1142 * ============================================================ */
1143
1144function getAiCandidates(
1145 regionDefinitions,
1146 regionProxyMap
1147) {
1148
1149 const candidates = [];
1150
1151
1152 aiRegionKeys.forEach(
1153 regionKey => {
1154
1155 const region =
1156 regionDefinitions.find(
1157 item =>
1158 item.key === regionKey
1159 );
1160
1161 if (!region) {
1162 return;
1163 }
1164
1165
1166 const nodes =
1167 regionProxyMap[
1168 regionKey
1169 ];
1170
1171
1172 if (
1173 !Array.isArray(nodes) ||
1174 nodes.length === 0
1175 ) {
1176 return;
1177 }
1178
1179
1180 candidates.push(
1181 region.groupName
1182 );
1183 }
1184 );
1185
1186
1187 /*
1188 * 非香港地区全部不存在时:
1189 *
1190 * 不允许回退到自动选择,
1191 * 因为自动选择可能包含香港。
1192 *
1193 * 使用 DIRECT 保证:
1194 *
1195 * 1. 策略组始终存在
1196 * 2. 配置始终合法
1197 * 3. 香港永远不会进入 AI
1198 */
1199 return candidates.length > 0
1200 ? unique(candidates)
1201 : ["DIRECT"];
1202}
1203
1204
1205/* ============================================================
1206 * 19. 创建自动选择
1207 * ============================================================ */
1208
1209function createAutoSelectGroup(
1210 candidates
1211) {
1212
1213 return {
1214
1215 ...urlTestDefaults,
1216
1217 "name": "♻️ 自动选择",
1218
1219 "type": "url-test",
1220
1221 "proxies": unique(
1222 candidates
1223 )
1224 };
1225}
1226
1227
1228/* ============================================================
1229 * 20. 创建故障转移
1230 * ============================================================ */
1231
1232function createFallbackGroup(
1233 candidates
1234) {
1235
1236 return {
1237
1238 "name": "🔯 故障转移",
1239
1240 "type": "fallback",
1241
1242 "proxies": unique(
1243 candidates
1244 ),
1245
1246 "url": speedTestUrl,
1247
1248 "interval": 600,
1249
1250 "timeout": 3000,
1251
1252 "lazy": true
1253 };
1254}
1255
1256
1257/* ============================================================
1258 * 21. 主函数
1259 * ============================================================ */
1260
1261function main(config = {}) {
1262
1263
1264 /* ----------------------------------------------------------
1265 * 21.1 获取原始节点
1266 * ---------------------------------------------------------- */
1267
1268 const rawProxies =
1269 getRawProxies(config);
1270
1271
1272 /* ----------------------------------------------------------
1273 * 21.2 过滤有效节点
1274 * ---------------------------------------------------------- */
1275
1276 const filteredProxies =
1277 filterValidProxies(
1278 rawProxies
1279 );
1280
1281
1282 /*
1283 * 保留过滤后的节点。
1284 */
1285 config.proxies =
1286 filteredProxies;
1287
1288
1289 /* ----------------------------------------------------------
1290 * 21.3 地区分类
1291 * ---------------------------------------------------------- */
1292
1293 const regionProxyMap =
1294 classifyProxies(
1295 filteredProxies
1296 );
1297
1298
1299 /* ----------------------------------------------------------
1300 * 21.4 获取实际存在的地区
1301 * ---------------------------------------------------------- */
1302
1303 const activeRegions =
1304 getActiveRegionNames(
1305 regionDefinitions,
1306 regionProxyMap
1307 );
1308
1309
1310 /* ----------------------------------------------------------
1311 * 21.5 创建地区测速组
1312 * ---------------------------------------------------------- */
1313
1314 const regionUrlTestGroups = [];
1315
1316
1317 regionDefinitions.forEach(
1318 region => {
1319
1320 const proxyNames =
1321 regionProxyMap[
1322 region.key
1323 ];
1324
1325
1326 /*
1327 * 没有节点则隐藏。
1328 */
1329 if (
1330 !Array.isArray(proxyNames) ||
1331 proxyNames.length === 0
1332 ) {
1333 return;
1334 }
1335
1336
1337 regionUrlTestGroups.push(
1338
1339 createRegionUrlTestGroup(
1340 region,
1341 proxyNames
1342 )
1343 );
1344 }
1345 );
1346
1347
1348 /* ----------------------------------------------------------
1349 * 21.6 自动选择候选
1350 * ---------------------------------------------------------- */
1351
1352 const autoSelectCandidates =
1353 activeRegions.length > 0
1354 ? activeRegions
1355 : ["DIRECT"];
1356
1357
1358 const autoSelectGroup =
1359 createAutoSelectGroup(
1360 autoSelectCandidates
1361 );
1362
1363
1364 /* ----------------------------------------------------------
1365 * 21.7 故障转移
1366 * ---------------------------------------------------------- */
1367
1368 const fallbackGroup =
1369 createFallbackGroup(
1370 autoSelectCandidates
1371 );
1372
1373
1374 /* ----------------------------------------------------------
1375 * 21.8 AI
1376 * ---------------------------------------------------------- */
1377
1378 const aiCandidates =
1379 getAiCandidates(
1380 regionDefinitions,
1381 regionProxyMap
1382 );
1383
1384
1385 const aiGroup = {
1386
1387 "name": "🤖 人工智能",
1388
1389 "type": "url-test",
1390
1391 "proxies": aiCandidates,
1392
1393 "url": speedTestUrl,
1394
1395 "interval": 600,
1396
1397 "timeout": 3000,
1398
1399 "lazy": true,
1400
1401 "tolerance": 80,
1402
1403 "max-failed-times": 3
1404 };
1405
1406
1407 /* ----------------------------------------------------------
1408 * 21.9 主节点选择
1409 * ---------------------------------------------------------- */
1410
1411 const nodeSelectCandidates =
1412 unique([
1413
1414 "♻️ 自动选择",
1415
1416 "🔯 故障转移",
1417
1418 ...activeRegions
1419 ]);
1420
1421
1422 /* ----------------------------------------------------------
1423 * 21.10 构建策略组
1424 * ---------------------------------------------------------- */
1425
1426 const proxyGroups = [
1427
1428 /*
1429 * ========================================================
1430 * 核心策略组
1431 * ========================================================
1432 *
1433 * 按重要程度放在最顶部。
1434 */
1435
1436 createSelectGroup(
1437
1438 "🚀 节点选择",
1439
1440 nodeSelectCandidates
1441 ),
1442
1443
1444 autoSelectGroup,
1445
1446
1447 fallbackGroup,
1448
1449
1450 /*
1451 * ========================================================
1452 * 地区自动测速
1453 * ========================================================
1454 */
1455
1456 ...regionUrlTestGroups,
1457
1458
1459 /*
1460 * ========================================================
1461 * 人工智能
1462 * ========================================================
1463 */
1464
1465 aiGroup,
1466
1467
1468 /*
1469 * ========================================================
1470 * 海外服务
1471 * ========================================================
1472 */
1473
1474 createSelectGroup(
1475
1476 "🌐 海外服务",
1477
1478 [
1479 "🚀 节点选择",
1480 "♻️ 自动选择",
1481 "🔯 故障转移",
1482 ...activeRegions
1483 ]
1484 ),
1485
1486
1487 /*
1488 * ========================================================
1489 * 代理域名
1490 * ========================================================
1491 */
1492
1493 createSelectGroup(
1494
1495 "🪜 代理域名",
1496
1497 [
1498 "🚀 节点选择",
1499 "🎯 全球直连"
1500 ]
1501 ),
1502
1503
1504 /*
1505 * ========================================================
1506 * 网络测试
1507 * ========================================================
1508 */
1509
1510 createSelectGroup(
1511
1512 "📈 网络测试",
1513
1514 [
1515 "🎯 全球直连",
1516 "🚀 节点选择",
1517 "♻️ 自动选择",
1518 "🔯 故障转移"
1519 ]
1520 ),
1521
1522
1523 /*
1524 * ========================================================
1525 * 广告拦截
1526 * ========================================================
1527 */
1528
1529 createSelectGroup(
1530
1531 "🆎 广告拦截",
1532
1533 [
1534 "REJECT"
1535 ]
1536 ),
1537
1538
1539 /*
1540 * ========================================================
1541 * 最终兜底
1542 * ========================================================
1543 */
1544
1545 createSelectGroup(
1546
1547 "🛟 兜底策略",
1548
1549 [
1550 "🚀 节点选择",
1551 "🎯 全球直连"
1552 ]
1553 ),
1554
1555
1556 /*
1557 * ========================================================
1558 * 全球直连
1559 * ========================================================
1560 */
1561
1562 createSelectGroup(
1563
1564 "🎯 全球直连",
1565
1566 [
1567 "DIRECT"
1568 ]
1569 )
1570 ];
1571
1572
1573 /* ==========================================================
1574 * 22. 写入最终配置
1575 * ========================================================== */
1576
1577 Object.assign(
1578 config,
1579 generalConfig
1580 );
1581
1582
1583 config["sniffer"] =
1584 snifferConfig;
1585
1586
1587 config["tun"] =
1588 tunConfig;
1589
1590
1591 config["dns"] =
1592 dnsConfig;
1593
1594
1595 config["proxy-groups"] =
1596 proxyGroups;
1597
1598
1599 config["rule-providers"] =
1600 ruleProviders;
1601
1602
1603 config["rules"] =
1604 rules;
1605
1606
1607 /* ==========================================================
1608 * 23. 返回
1609 * ========================================================== */
1610
1611 return config;
1612}