Compare commits

...

19 Commits

Author SHA1 Message Date
1c8d49a636 feat: Refactor Mihomo setup script to improve interface handling and add new rule logic 2026-04-12 13:05:06 +03:00
71b2b4e6e5 feat: Update Mihomo URL to the latest alpha release in config-warpgate-alpine.sh 2026-04-11 19:44:23 +03:00
89b9a125c8 feat: Update resource URLs in config-warpgate-alpine.sh for solar.yaml and iptables script 2026-04-11 19:36:27 +03:00
6b5480586a feat: Add solar.yaml configuration for proxy and health check settings 2026-04-11 19:34:28 +03:00
2e9aeba3c0 Merge branch 'main' of https://gitea.shamanlanding.org/DaTekShaman/clash-rules 2026-04-11 19:32:05 +03:00
95230c6349 feat: Add Mihomo and TProxy setup scripts for Alpine and legacy systems
- Introduced `iptables-mihomo-setup-mark2.sh` for advanced TProxy configuration.
- Created `iptables-mihomo-setup.sh` for legacy iptables management.
- Added `dnssec-test.sh` for DNSSEC interception testing.
- Implemented `config-warpgate-alpine.sh` for comprehensive Warpgate setup.
- Developed `iptables-mihomo-setup-alpine-mark2.sh` for refined TProxy rules on Alpine.
- Added `iptables-mihomo-setup-alpine.sh` for basic TProxy setup on Alpine.
- Created `update-core-and-dash.sh` for automated updates of Mihomo core and Zashboard UI.
2026-04-11 19:32:05 +03:00
38e0c100be 🤖AS: Auto-compiled rules update 2026-04-05 09:53:53 +00:00
3e22a60e2f Enhance icon tagging rules by adding new patterns for faster detection in external-proxies-sanitizer.js 2026-04-04 17:23:33 +03:00
27e6dc44d9 Enhance icon tagging rules and add new patterns for country detection in external-proxies-sanitizer.js 2026-04-04 14:52:53 +03:00
e9f85eaa93 Add country detection for Iceland in external-proxies-sanitizer.js 2026-04-04 14:42:52 +03:00
01c3079aa6 Fix country flag code for Italy in external-proxies-sanitizer.js 2026-04-04 14:41:20 +03:00
ed4bf7ef60 Add TCP Trojan support and enhance country detection in external-proxies-sanitizer.js 2026-04-04 14:39:08 +03:00
2b7b7b5410 Update SRC-IP-CIDR entry in testzone-b.yaml 2026-03-07 15:27:00 +03:00
71b1a68073 🤖AS: Auto-compiled rules update 2026-03-07 11:47:38 +00:00
4bc00204e7 🤖AS: Auto-compiled rules update 2026-03-01 19:00:40 +00:00
ef2c5a776c 🤖AS: Auto-compiled rules update 2026-03-01 11:08:22 +00:00
22c76f3e4b Add DNSSEC interception test script 2026-03-01 14:08:27 +03:00
491d05d88c Merge branch 'main' of https://gitea.shamanlanding.org/DaTekShaman/clash-rules 2026-02-28 19:29:06 +03:00
dbd1d4cb48 Add SRC-IP-CIDR entry for DTS-S24 in testzone-a.yaml 2026-02-28 19:29:06 +03:00
27 changed files with 911 additions and 2375 deletions

View File

@@ -0,0 +1,408 @@
anchors:
default-rule-provider-config: &default_rule_provider_config
type: http
behavior: classical
interval: 86400
# # ———————————————————————————————— health checks ———————————————————————————————— #
proxy_provider_substore: &proxy_provider_substore
type: http
interval: 3600
proxy: DIRECT
# # ———————————————————————————————— health checks ———————————————————————————————— #
health-check-providers: &health_check_providers
health-check:
enable: true
interval: 1200
expected-status: 204
timeout: 5000
url: https://www.gstatic.com/generate_204
health-check-groups: &health_check_groups
health-check:
enable: true
interval: 600
expected-status: 204
timeout: 5000
url: https://www.gstatic.com/generate_204
# # ————————————————————————————————— proxy lists ————————————————————————————————— #
use-all: &use_all
use:
- 🐦 fallback package
- 🚪 local tunnels
- 🫂 neighborhood tunnels
- 📺 youtube tunnels
- 🕊️ clear tunnels
- 🪨 default package / 📺
- 🪨 default package / 👠
- 🪨 default package
- 💎 premium package / 📺
- 💎 premium package / 👠
- 💎 premium package
- 🌉 private relays
- ♨️ private vpns
# # ————————————————————————————————— proxy types ————————————————————————————————— #
p-selector-udp: &p_selector_udp
type: select
disable-udp: false
# ————————————————————————————————————————————————————— LOCAL PROXY —————————————————————————————————————————————————————vs
port: 7890
socks-port: 7891
redir-port: 7892
tproxy-port: 7893
mixed-port: 7894
allow-lan: true
lan-allowed-ips:
- 0.0.0.0/0
bind-address: "*"
# ————————————————————————————————————————————————— EXTERNAL CONTROLLER —————————————————————————————————————————————————
external-controller: 0.0.0.0:9090
external-controller-tls: 0.0.0.0:9443
secret: '314159271828'
external-ui: ./ui
external-ui-name: zashboard
external-ui-url: "https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip"
# ——————————————————————————————————————————————————————— GENERAL ———————————————————————————————————————————————————————
mode: rule
ipv6: false
unified-delay: true
log-level: info
disable-keep-alive: false
keep-alive-interval: 15
keep-alive-idle: 600
find-process-mode: "off" # Options: always, strict, off
interface-name: eth0 # Outbound interface name
routing-mark: 1337
# global-client-fingerprint: random # Options: chrome, firefox, safari, iOS, android, edge, 360, qq, random
# tcp-concurrent: true # Enable TCP concurrent connections, which will use all IP addresses resolved by DNS for connections, using the first successful connection.
# ————————————————— GEO DATA CONFIGURATION ————————————————— https://github.com/runetfreedom/russia-v2ray-rules-dat —————
geodata-mode: true
geodata-loader: standard
geo-auto-update: true
geo-update-interval: 24
geox-url:
geoip: https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/geoip.dat
geosite: https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/release/geosite.dat
mmdb: https://testingcf.jsdelivr.net/gh/alecthw/mmdb_china_ip_list@release/Country.mmdb
asn: https://testingcf.jsdelivr.net/gh/xishang0128/geoip@release/GeoLite2-ASN.mmdb
global-ua: clash.meta
etag-support: true
listeners:
- name: socks-inbound
type: socks
port: 7891
listen: 0.0.0.0
udp: true
users:
- username: testuser1
password: testuser1
- username: testuser2
password: testuser2
- username: testuser3
password: testuser3
- username: testuser4
password: testuser4
sniffer:
enable: true
parse-pure-ip: true
override-destination: true
sniff:
HTTP:
ports: [80, 8080-8880]
override-destination: true
TLS:
ports: [443, 8443]
QUIC:
ports: [443, 8443]
skip-domain:
- '+.dts'
- '+.webway.dts'
- '+.netbird.selfhosted'
- '+.shamanlanding.org'
- '+.shamanlanding.com'
- "Mijia Cloud" # Xiaomi Smart Home (Mijia). Uses non-standard TLS headers.
- "dlg.io.mi.com" # Xiaomi IoT logging/telemetry.
- "+.push.apple.com" # Apple Push Notification Service (APNS). Critical for iOS.
- "+.apple.com" # (Optional) Broader Apple bypass. Safer for iCloud sync.
dns:
enable: true
enhanced-mode: fake-ip
cache-algorithm: arc
ipv6: false
listen: 0.0.0.0:53
prefer-h3: false
respect-rules: true
use-hosts: true
use-system-hosts: false
fake-ip-range: 198.18.0.1/16
fake-ip-filter-mode: blacklist
fake-ip-filter:
# ———————————————————— self-hosted domains ———————————————————
- '*.lan'
- '*.local'
- '+.dts'
- '+.webway.dts'
- '+.netbird.selfhosted'
- '+.shamanlanding.org'
# ————————————————————————— ru domains ———————————————————————
- '+.ru'
- '+.рф'
- '+.su'
- '+.ntp.org'
- '+.pool.ntp.org'
- 'time.apple.com'
- 'time.nist.gov'
- 'time.windows.com'
- 'time.google.com'
# ————————————————————— connectivity checks ——————————————————
- 'dns.msftncsi.com'
- 'www.msftncsi.com'
- 'www.msftconnecttest.com'
- 'connectivitycheck.gstatic.com'
- 'connectivitycheck.android.com'
- 'clients3.google.com'
- 'captive.apple.com'
- '+.hotspot.msn.com'
default-nameserver: # Resolving the domain names of DNS servers.
- 1.1.1.1
- 1.0.0.1
- 8.8.8.8
- 8.8.4.4
- 9.9.9.9
- 208.67.222.222
- 208.67.220.220
nameserver: # Default domain name resolution server.
- 'tls://kavanah.shamanlanding.org'
# - https://d.adguard-dns.com/dns-query/5ffb7de2
proxy-server-nameserver: # Resolving the domain names of proxy nodes.
- 'tls://kavanah.shamanlanding.org'
# - https://d.adguard-dns.com/dns-query/5ffb7de2
hosts:
# 'solar.shamanlanding.org': 192.168.25.8
#
# 'battlescribe.shamanlanding.org': 192.168.25.8
# 'kavanah.shamanlanding.org': 192.168.25.8
# 'loremaster.shamanlanding.org': 192.168.25.8
# 'omnissiah.shamanlanding.org': 192.168.25.8
# 'sanctum.shamanlanding.org': 192.168.25.8
# 'tesseract.shamanlanding.org': 192.168.25.8
# 'synaxis.shamanlanding.org': 192.168.25.8
#
# '+.solar.shamanlanding.org': 192.168.25.8
proxy-providers:
🐦 fallback package:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/fallback"
path: "./proxy_provider/fallback.txt"
<<: [*health_check_providers, *proxy_provider_substore]
# ———————————————————————————————— tunnels ———————————————————————————————— #
🚪 local tunnels:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/own-package-solar"
path: "./proxy_provider/webway-local-tunnels.txt"
filter: "🚪"
exclude-filter: "✨"
<<: [*health_check_providers, *proxy_provider_substore]
🫂 neighborhood tunnels:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/own-package-solar"
path: "./proxy_provider/webway-neighborhood-tunnels.txt"
filter: "🫂"
exclude-filter: "✨"
<<: [*health_check_providers, *proxy_provider_substore]
📺 youtube tunnels:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/own-package-solar"
path: "./proxy_provider/webway-tunnels-youtube.txt"
filter: "📺"
exclude-filter: "✨"
<<: [*health_check_providers, *proxy_provider_substore]
🕊️ clear tunnels:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/own-package-solar"
path: "./proxy_provider/webway-tunnels-clear.txt"
filter: "🕊️"
exclude-filter: "✨"
<<: [*health_check_providers, *proxy_provider_substore]
# ———————————————————————————————— левые впнки ———————————————————————————————— #
🪨 default package:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/ext-package-solar"
path: "./proxy_provider/webway-class-b.txt"
exclude-filter: "📺|👠"
<<: [*health_check_providers, *proxy_provider_substore]
🪨 default package / 📺:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/ext-package-solar"
path: "./proxy_provider/webway-class-b-youtube.txt"
filter: "📺"
<<: [*health_check_providers, *proxy_provider_substore]
🪨 default package / 👠:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/ext-package-solar"
path: "./proxy_provider/webway-class-b-capri.txt"
filter: "👠"
<<: [*health_check_providers, *proxy_provider_substore]
💎 premium package:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/prm-package-solar"
path: "./proxy_provider/webway-class-a.txt"
exclude-filter: "📺|👠"
<<: [*health_check_providers, *proxy_provider_substore]
💎 premium package / 📺:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/prm-package-solar"
path: "./proxy_provider/webway-class-a-youtube.txt"
filter: "📺"
<<: [*health_check_providers, *proxy_provider_substore]
💎 premium package / 👠:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/prm-package-solar"
path: "./proxy_provider/webway-class-a-capri.txt"
filter: "👠"
<<: [*health_check_providers, *proxy_provider_substore]
# ———————————————————————————————— хорошие впнки ———————————————————————————————— #
♨️ private vpns:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/own-package-solar"
path: "./proxy_provider/webway-private-vpns.txt"
filter: "♨️"
<<: [*health_check_providers, *proxy_provider_substore]
🌉 private relays:
url: "https://synaxis.shamanlanding.org/webway-subscription-provider/download/collection/own-package-solar"
path: "./proxy_provider/webway-private-relays.txt"
filter: "🌉"
<<: [*health_check_providers, *proxy_provider_substore]
proxy-groups:
- name: RU-зона локально
proxies:
- DIRECT
- PASS
- REJECT
- REJECT-DROP
<<: [*p_selector_udp]
- name: RU-зона через webway
proxies:
- REJECT
- REJECT-DROP
- DIRECT
- PASS
<<: [*p_selector_udp]
- name: Testzone A
filter: ""
exclude-filter: ""
exclude-type: ""
proxies:
- PASS
- Заблокированные сайты
- Личный список
<<: [*health_check_groups, *use_all, *p_selector_udp]
- name: Testzone B
filter: ""
exclude-filter: ""
exclude-type: ""
proxies:
- PASS
- Заблокированные сайты
- Личный список
<<: [*health_check_groups, *use_all, *p_selector_udp]
- name: Заблокированные сайты
filter: ""
exclude-filter: ""
exclude-type: ""
<<: [*health_check_groups, *use_all, *p_selector_udp]
- name: Личный список
filter: ""
exclude-filter: ""
exclude-type: ""
<<: [*health_check_groups, *use_all, *p_selector_udp]
rule-providers:
📃 Solar Proxy Domain List:
url: https://antifilter.solar.shamanlanding.org/proxy-domain.yaml
path: "./rule_provider/consolidated-lists-private/adaptation-solar-domain-proxy.yaml"
<<: *default_rule_provider_config
📃 Solar Proxy IP List:
url: https://antifilter.solar.shamanlanding.org/proxy-ip.yaml
path: "./rule_provider/consolidated-lists-private/adaptation-solar-ip-proxy.yaml"
<<: *default_rule_provider_config
📃 Shared Proxy Domain List:
url: https://antifilter.scarus.shamanlanding.org/proxy-domain.yaml
path: "./rule_provider/consolidated-lists-private/adaptation-scarus-domain-proxy.yaml"
<<: *default_rule_provider_config
📃 Shared Proxy IP List:
url: https://antifilter.scarus.shamanlanding.org/proxy-ip.yaml
path: "./rule_provider/consolidated-lists-private/adaptation-scarus-ip-proxy.yaml"
<<: *default_rule_provider_config
🛝 Testzone A:
url: https://gitea.shamanlanding.org/DaTekShaman/clash-rules/raw/branch/main/rule-provider/consolidated-lists-private/testzone-a.yaml
path: "./rule_provider/services/consolidated-lists-private/testzone-a.yaml"
<<: *default_rule_provider_config
🛝 Testzone B:
url: https://gitea.shamanlanding.org/DaTekShaman/clash-rules/raw/branch/main/rule-provider/consolidated-lists-private/testzone-b.yaml
path: "./rule_provider/services/consolidated-lists-private/testzone-b.yaml"
<<: *default_rule_provider_config
🛜 Webway Unprivileged:
url: https://gitea.shamanlanding.org/DaTekShaman/clash-rules/raw/branch/main/rule-provider/consolidated-lists-private/webway-unprivileged.yaml
path: "./rule_provider/services/consolidated-lists-private/webway-unprivileged.yaml"
<<: *default_rule_provider_config
🛜 VLAN10:
url: https://gitea.shamanlanding.org/DaTekShaman/clash-rules/raw/branch/main/rule-provider/consolidated-lists-private/vlan10.yaml
path: "./rule_provider/services/consolidated-lists-private/vlan10.yaml"
<<: *default_rule_provider_config
🛜 VLAN40:
url: https://gitea.shamanlanding.org/DaTekShaman/clash-rules/raw/branch/main/rule-provider/consolidated-lists-private/vlan40.yaml
path: "./rule_provider/services/consolidated-lists-private/vlan40.yaml"
<<: *default_rule_provider_config
👥 Current Antifilter/Refilter:
url: https://gitea.shamanlanding.org/DaTekShaman/clash-rules/raw/branch/main/rule-provider/consolidated-lists-public/current-public-set.yaml
path: "./rule_provider/consolidated-lists-public/current-public-set.yaml"
<<: *default_rule_provider_config
📦 RU Services Manual:
url: https://gitea.shamanlanding.org/DaTekShaman/clash-rules/raw/branch/main/rule-provider/consolidated-services/ru-services.yaml
path: "./rule_provider/consolidated-services/ru-services.yaml"
<<: *default_rule_provider_config
rules:
- SUB-RULE,(OR,((RULE-SET,📦 RU Services Manual),(GEOIP,RU),(GEOSITE,category-ru))),russian_internet
- RULE-SET,🛝 Testzone A,Testzone A
- RULE-SET,🛝 Testzone B,Testzone B
- RULE-SET,📃 Solar Proxy Domain List,Личный список
- RULE-SET,📃 Solar Proxy IP List,Личный список,no-resolve
- RULE-SET,📃 Shared Proxy Domain List,Заблокированные сайты
- RULE-SET,📃 Shared Proxy IP List,Заблокированные сайты,no-resolve
- RULE-SET,👥 Current Antifilter/Refilter,Заблокированные сайты
- MATCH,DIRECT
sub-rules:
russian_internet:
- DOMAIN-SUFFIX,shamanlanding.org,DIRECT
- SRC-IP-CIDR,100.98.0.0/16,RU-зона через webway
- SRC-IP-CIDR,10.10.0.0/16,RU-зона локально
- SRC-IP-CIDR,10.40.0.0/16,RU-зона локально
- MATCH,REJECT

View File

@@ -1,233 +0,0 @@
/**
* SUB STORE YAML ASSEMBLER (v4: Fix Duplicate Headers)
* * Arguments:
* - clear-comments=true
* - clear-manifest=true
* - clear-replacements=true
* * Requires: Header "# @file: filename.yaml" in input files.
*/
// --- OPTIONS PARSING ---
function normalizeOptions() {
const args = (typeof $arguments !== "undefined" && $arguments) ? $arguments : {};
const asBool = (v, def = false) => {
if (v === undefined || v === null || v === "") return def;
if (typeof v === "boolean") return v;
const s = String(v).toLowerCase().trim();
if (["1", "true", "yes", "y", "on"].includes(s)) return true;
if (["0", "false", "no", "n", "off"].includes(s)) return false;
return def;
};
return {
clearComments: asBool(args['clear-comments'], false),
clearManifest: asBool(args['clear-manifest'], false),
clearReplacements: asBool(args['clear-replacements'], false),
};
}
// --- UTILS ---
function normalizeFiles(rawFiles) {
const map = new Map();
if (!rawFiles || !Array.isArray(rawFiles)) return map;
rawFiles.forEach((content) => {
if (typeof content !== 'string') return;
const match = content.match(/^#\s*@file:\s*(.+?)(\s|$)/m);
if (match) map.set(match[1].trim(), content);
});
return map;
}
function cleanText(text) {
return String(text || "").replace(/\r\n/g, "\n");
}
function extractName(block) {
const match = block.match(/^ {4}name:\s*(?:["']?)(.*?)(?:["']?)\s*(?:#.*)?$/m);
return match ? match[1].trim() : null;
}
function extractKey(block, indentLevel) {
const spaceStr = " ".repeat(indentLevel);
const re = new RegExp(`^${spaceStr}([^#\\s][^:]+):`);
const match = block.match(re);
return match ? match[1].trim().replace(/['"]/g, "") : null;
}
function stripFullLineComments(text) {
return text.split('\n')
.filter(line => !line.trim().startsWith('#'))
.join('\n');
}
function splitBlocks(text, type) {
const lines = cleanText(text).split("\n");
const blocks = [];
let currentBuf = [];
const isListStart = (l) => l.match(/^ {2}-\s/);
const isMapStart2 = (l) => l.match(/^ {2}[^ \-#][^:]*:/);
const isMapStart0 = (l) => l.match(/^[^ \-#][^:]*:/);
const isStart = (l) => {
if (type === 'list') return isListStart(l);
if (type === 'map2') return isMapStart2(l);
if (type === 'map0') return isMapStart0(l);
return false;
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.match(/^#\s*@file:/)) continue;
if (isStart(line)) {
if (currentBuf.length > 0) {
blocks.push(currentBuf.join("\n"));
currentBuf = [];
}
}
if (currentBuf.length === 0 && !line.trim()) continue;
currentBuf.push(line);
}
if (currentBuf.length > 0) blocks.push(currentBuf.join("\n"));
return blocks;
}
// --- MERGE LOGIC ---
function processSection(sectionName, manifestEntries, fileMap, opts) {
let sectionOutput = [];
const seenKeys = new Set();
// Добавляем заголовок секции
if (!['root', 'x-substore'].includes(sectionName)) {
sectionOutput.push(`${sectionName}:`);
}
if (sectionName === 'x-substore') {
sectionOutput.push(`x-substore:`);
}
for (const entry of manifestEntries) {
let content = fileMap.get(entry.file);
if (!content) throw new Error(`CRITICAL: File "${entry.file}" not found.`);
if (!opts.clearComments) {
sectionOutput.push(`\n# --- source: ${entry.file} | mode: ${entry.mode || "concat"} ---`);
}
if (opts.clearComments) {
content = stripFullLineComments(content);
}
// [FIX] Удаляем дублирующий заголовок x-substore из контента файла,
// но оставляем содержимое (оно уже имеет правильный отступ 2 пробела)
if (sectionName === 'x-substore') {
content = content.replace(/^x-substore:\s*(?:#.*)?$/m, '');
}
const mode = entry.mode || "concat";
if (mode === 'concat') {
const lines = cleanText(content).split('\n').filter(l => !l.match(/^#\s*@file:/));
sectionOutput.push(lines.join('\n'));
continue;
}
if (mode === 'first_wins') {
let blockType = 'map2';
if (sectionName === 'root') blockType = 'map0';
if (['proxies', 'proxy-groups'].includes(sectionName)) blockType = 'list';
const blocks = splitBlocks(content, blockType);
if (blocks.length === 0 && !opts.clearComments) {
sectionOutput.push(`# WARNING: No valid blocks found in ${entry.file}`);
}
for (const block of blocks) {
let id = null;
if (blockType === 'list') {
id = extractName(block);
} else if (blockType === 'map0') {
id = extractKey(block, 0);
} else {
id = extractKey(block, 2);
}
// Apply cleaning options for x-substore content
if (sectionName === 'x-substore' && id) {
if (opts.clearManifest && id === 'manifest') continue;
if (opts.clearReplacements && id === 'replacements') continue;
}
if (id) {
if (seenKeys.has(id)) {
if (!opts.clearComments) sectionOutput.push(`# [SKIP] Duplicate "${id}" ignored`);
continue;
}
seenKeys.add(id);
}
sectionOutput.push(block);
}
}
}
return sectionOutput.join("\n");
}
// --- MAIN EXECUTION ---
try {
const opts = normalizeOptions();
const fileMap = normalizeFiles($files);
let manifestKey = null;
for (const k of fileMap.keys()) {
if (k.startsWith("00-manifest")) {
manifestKey = k;
break;
}
}
if (!manifestKey) throw new Error("Manifest file (00-manifest-...) not found.");
const manifestRaw = fileMap.get(manifestKey);
const manifestObj = ProxyUtils.yaml.safeLoad(manifestRaw);
if (!manifestObj?.['x-substore']?.manifest) {
throw new Error("Invalid Manifest structure.");
}
const manifestList = manifestObj['x-substore'].manifest;
const replacements = manifestObj['x-substore'].replacements || [];
const sectionOrder = [
"x-substore", "root", "hosts", "sniffer", "tun", "dns",
"proxies", "proxy-providers", "proxy-groups", "rule-providers", "rules"
];
const plan = {};
sectionOrder.forEach(s => plan[s] = []);
manifestList.forEach(entry => {
if (!plan[entry.section]) plan[entry.section] = [];
plan[entry.section].push(entry);
});
const finalChunks = [];
for (const sec of sectionOrder) {
if (!plan[sec] || plan[sec].length === 0) continue;
const secStr = processSection(sec, plan[sec], fileMap, opts);
finalChunks.push(secStr);
}
let result = finalChunks.join("\n\n");
if (Array.isArray(replacements)) {
replacements.forEach(rep => {
if (rep.from && rep.to) {
result = result.split(rep.from).join(rep.to);
}
});
}
$content = result;
} catch (err) {
$content = `# CRITICAL ERROR:\n# ${err.message}\n# Stack: ${err.stack}`;
}

View File

@@ -1,239 +0,0 @@
/**
* SUB STORE YAML ASSEMBLER (Content-Tag Aware)
* * Требование: Каждый файл должен начинаться с комментария:
* # @file: filename.yaml
*/
// --- UTILS ---
// Функция нормализации теперь парсит содержимое, чтобы найти имя файла
function normalizeFiles(rawFiles) {
const map = new Map();
if (!rawFiles || !Array.isArray(rawFiles)) return map;
rawFiles.forEach((content, index) => {
if (typeof content !== 'string') return;
// Ищем магический тег: # @file: filename.yaml
const match = content.match(/^#\s*@file:\s*(.+?)(\s|$)/m);
if (match) {
const filename = match[1].trim();
map.set(filename, content);
} else {
// Если тега нет, файл остается "анонимным" и недоступным через манифест,
// но мы можем логировать это.
// console.log(`File at index ${index} has no @file tag`);
}
});
return map;
}
function cleanText(text) {
return String(text || "").replace(/\r\n/g, "\n");
}
// Извлечение name из элемента списка
function extractName(block) {
// Ищем name: value с учетом отступов (4 пробела)
const match = block.match(/^ {4}name:\s*(?:["']?)(.*?)(?:["']?)\s*(?:#.*)?$/m);
return match ? match[1].trim() : null;
}
// Извлечение ключа из map (0 или 2 пробела)
function extractKey(block, indentLevel) {
const spaceStr = " ".repeat(indentLevel);
const re = new RegExp(`^${spaceStr}([^#\\s][^:]+):`);
const match = block.match(re);
return match ? match[1].trim().replace(/['"]/g, "") : null;
}
// Разбивка текста на логические блоки
function splitBlocks(text, type) {
const lines = cleanText(text).split("\n");
const blocks = [];
let currentBuf = [];
// Детекторы начала блока
const isListStart = (l) => l.match(/^ {2}-\s/); // " - "
const isMapStart2 = (l) => l.match(/^ {2}[^ \-#][^:]*:/); // " key:"
const isMapStart0 = (l) => l.match(/^[^ \-#][^:]*:/); // "key:" (root)
const isStart = (l) => {
if (type === 'list') return isListStart(l);
if (type === 'map2') return isMapStart2(l);
if (type === 'map0') return isMapStart0(l);
return false;
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Если строка начинается с # @file:, пропускаем ее, чтобы не мусорить в конфиге
if (line.match(/^#\s*@file:/)) continue;
if (isStart(line)) {
if (currentBuf.length > 0) {
blocks.push(currentBuf.join("\n"));
currentBuf = [];
}
}
// Пропуск пустых строк в начале, если буфер пуст
if (currentBuf.length === 0 && !line.trim()) continue;
currentBuf.push(line);
}
if (currentBuf.length > 0) {
blocks.push(currentBuf.join("\n"));
}
return blocks;
}
// --- MERGE LOGIC ---
function processSection(sectionName, manifestEntries, fileMap) {
let sectionOutput = [];
const seenKeys = new Set();
// Добавляем заголовок секции (кроме root, x-substore и rules)
if (!['root', 'x-substore'].includes(sectionName)) {
sectionOutput.push(`${sectionName}:`);
}
if (sectionName === 'x-substore') {
sectionOutput.push(`x-substore:`);
}
for (const entry of manifestEntries) {
const content = fileMap.get(entry.file);
if (!content) {
// FAIL-FAST: Если файл из манифеста не найден (нет тега или не загружен)
throw new Error(`CRITICAL: File "${entry.file}" not found inside input bundle. Did you add '# @file: ${entry.file}' header?`);
}
const mode = entry.mode || "concat";
sectionOutput.push(`\n# --- source: ${entry.file} | mode: ${mode} ---`);
if (mode === 'concat') {
// Просто чистим от тега @file при вставке
const lines = cleanText(content).split('\n').filter(l => !l.match(/^#\s*@file:/));
sectionOutput.push(lines.join('\n'));
continue;
}
if (mode === 'first_wins') {
let blockType = 'map2';
if (sectionName === 'root') blockType = 'map0';
if (['proxies', 'proxy-groups'].includes(sectionName)) blockType = 'list';
const blocks = splitBlocks(content, blockType);
if (blocks.length === 0) {
sectionOutput.push(`# WARNING: No valid blocks found in ${entry.file}`);
}
for (const block of blocks) {
let id = null;
if (blockType === 'list') {
id = extractName(block);
} else if (blockType === 'map0') {
id = extractKey(block, 0);
} else {
id = extractKey(block, 2);
}
if (id) {
if (seenKeys.has(id)) {
sectionOutput.push(`# [SKIP] Duplicate "${id}" ignored`);
continue;
}
seenKeys.add(id);
}
sectionOutput.push(block);
}
}
}
return sectionOutput.join("\n");
}
// --- MAIN EXECUTION ---
try {
// 1. Создаем карту файлов на основе тегов # @file:
const fileMap = normalizeFiles($files);
// 2. Ищем Манифест
let manifestKey = null;
// Ищем файл, чье имя (из тега) начинается с 00-manifest
for (const k of fileMap.keys()) {
if (k.startsWith("00-manifest")) {
manifestKey = k;
break;
}
}
if (!manifestKey) {
const foundFiles = Array.from(fileMap.keys()).join(", ");
throw new Error(`Manifest file (00-manifest-...) not found in headers. Found files: [${foundFiles}]`);
}
// 3. Парсим Манифест
const manifestRaw = fileMap.get(manifestKey);
const manifestObj = ProxyUtils.yaml.safeLoad(manifestRaw);
if (!manifestObj?.['x-substore']?.manifest) {
throw new Error("Invalid Manifest: missing x-substore.manifest structure.");
}
const manifestList = manifestObj['x-substore'].manifest;
const replacements = manifestObj['x-substore'].replacements || [];
// 4. Планирование порядка секций
const sectionOrder = [
"x-substore", "root", "hosts", "sniffer", "tun", "dns",
"proxies", "proxy-providers", "proxy-groups", "rule-providers", "rules"
];
const plan = {};
sectionOrder.forEach(s => plan[s] = []);
manifestList.forEach(entry => {
// Если секция в манифесте есть, а в нашем плане нет - добавляем динамически (на всякий случай)
if (!plan[entry.section]) plan[entry.section] = [];
plan[entry.section].push(entry);
});
// 5. Сборка
const finalChunks = [];
for (const sec of sectionOrder) {
if (!plan[sec] || plan[sec].length === 0) continue;
// Обработка секции
const secStr = processSection(sec, plan[sec], fileMap);
finalChunks.push(secStr);
}
let result = finalChunks.join("\n\n");
// 6. Replacements (Literal)
if (Array.isArray(replacements)) {
replacements.forEach(rep => {
if (rep.from && rep.to) {
result = result.split(rep.from).join(rep.to);
}
});
}
$content = result;
} catch (err) {
$content = `# CRITICAL ERROR in Assembler:\n# ${err.message}\n\n# Stack:\n${err.stack}`;
}

View File

@@ -1,235 +0,0 @@
/**********************
* Defaults (AmneziaWG)
* Если в исходнике нет параметра, берём отсюда.
* Если итоговое значение == 0, параметр пропускаем в amnezia-wg-option.
**********************/
const AMZ_DEFAULTS = {
Jc: 4,
Jmin: 10,
Jmax: 50,
S1: 110,
S2: 120,
H1: 0,
H2: 0,
H3: 0,
H4: 0,
};
/**********************
* Options from Sub Store
* Example URL:
* .../convert-awg-to-clash.js#dns=false&ipv6=false#noCache
*
* Требования:
* - dns=false => remote-dns-resolve: false (вне зависимости от входа)
* - ipv6=false => удалить IPv6 из allowed-ips (и вообще не добавлять ipv6-части)
**********************/
function normalizeOptions() {
const args = (typeof $arguments !== "undefined" && $arguments) ? $arguments : {};
const asBool = (v, def = true) => {
if (v === undefined || v === null || v === "") return def;
if (typeof v === "boolean") return v;
const s = String(v).toLowerCase().trim();
if (["1", "true", "yes", "y", "on"].includes(s)) return true;
if (["0", "false", "no", "n", "off"].includes(s)) return false;
return def;
};
return {
dns: asBool(args.dns, true),
ipv6: asBool(args.ipv6, true),
};
}
/**********************
* Parsing WG INI blocks
**********************/
function cleanLines(text) {
return String(text ?? "")
.replace(/\r\n/g, "\n")
.split("\n");
}
// Парсим один INI-фрагмент с [Interface] и [Peer] (один peer)
function parseIniOne(text) {
const lines = cleanLines(text)
.map((l) => l.trim())
.filter((l) => l.length > 0 && !l.startsWith("#") && !l.startsWith(";"));
let section = null;
const data = { Interface: {}, Peer: {} };
for (const line of lines) {
const mSec = line.match(/^\[(.+?)\]$/);
if (mSec) {
section = mSec[1];
continue;
}
const mKV = line.match(/^([^=]+?)\s*=\s*(.+)$/);
if (!mKV || !section) continue;
const key = mKV[1].trim();
const value = mKV[2].trim();
if (section === "Interface") data.Interface[key] = value;
else if (section === "Peer") data.Peer[key] = value;
}
return data;
}
// Разбиваем весь файл на блоки по заголовкам "##### ..."
function splitByHeaders(fullText) {
const lines = cleanLines(fullText);
const blocks = [];
let current = { name: "amz-wg", buf: [] };
const headerRe = /^#{5}\s*(.+)\s*$/;
for (const line of lines) {
const mh = line.match(headerRe);
if (mh) {
// закрываем предыдущий блок, если там что-то есть
if (current.buf.join("\n").trim().length > 0) blocks.push(current);
current = { name: mh[1].trim(), buf: [] };
continue;
}
current.buf.push(line);
}
if (current.buf.join("\n").trim().length > 0) blocks.push(current);
return blocks;
}
function splitList(val) {
return String(val || "")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
}
function parseEndpoint(endpoint) {
// Поддержка:
// - host:port
// - [ipv6]:port
const s = String(endpoint || "").trim();
const v6 = s.match(/^\[(.+?)\]:(\d+)$/);
if (v6) return { host: v6[1], port: Number(v6[2]) };
const v4 = s.match(/^(.+?):(\d+)$/);
if (v4) return { host: v4[1], port: Number(v4[2]) };
return { host: "", port: 0 };
}
function toNumberOrNull(v) {
const s = String(v ?? "").trim();
if (s === "") return null;
if (/^-?\d+$/.test(s)) return Number(s);
return null;
}
function buildAmzOptions(interfaceObj) {
// Правило:
// - если в файле есть параметр => используем его
// - иначе берём из AMZ_DEFAULTS
// - если итог == 0 => пропускаем
const out = {};
const keys = Object.keys(AMZ_DEFAULTS);
for (const K of keys) {
const fromFile = interfaceObj[K];
const fileNum = toNumberOrNull(fromFile);
const fallback = AMZ_DEFAULTS[K];
const finalVal =
fileNum !== null ? fileNum : (fallback ?? 0);
if (Number(finalVal) !== 0) {
out[K.toLowerCase()] = Number(finalVal);
}
}
return out;
}
function filterAllowedIPs(allowed, enableIPv6) {
if (enableIPv6) return allowed;
// выкидываем всё, что похоже на IPv6
return allowed.filter((cidr) => !cidr.includes(":"));
}
function buildProxy(blockName, wg, options) {
const i = wg.Interface || {};
const p = wg.Peer || {};
const address = i.Address || "";
const dnsList = splitList(i.DNS);
const ep = parseEndpoint(p.Endpoint);
let allowed = splitList(p.AllowedIPs);
allowed = filterAllowedIPs(allowed, options.ipv6);
const proxy = {
name: blockName || "amz-wg",
type: "wireguard",
ip: address,
// ipv6 поле в твоём примере закомментировано, так что не добавляем вообще
"private-key": i.PrivateKey || "",
peers: [
{
server: ep.host,
port: ep.port,
"public-key": p.PublicKey || "",
...(p.PresharedKey ? { "pre-shared-key": p.PresharedKey } : {}),
"allowed-ips": allowed,
},
],
udp: true,
// dns=false => принудительно false
"remote-dns-resolve": options.dns ? true : false,
...(dnsList.length ? { dns: dnsList } : {}),
};
const amz = buildAmzOptions(i);
if (Object.keys(amz).length) {
proxy["amnezia-wg-option"] = amz;
}
return proxy;
}
/**********************
* ENTRYPOINT
**********************/
const opts = normalizeOptions();
// Вход: чаще всего $content, но на всякий пожарный берём $files[0]
const input = String($content ?? ($files && $files[0]) ?? "");
// Разбиваем по заголовкам ##### ...
const blocks = splitByHeaders(input);
// Для каждого блока парсим INI и строим proxy
const proxies = [];
for (const b of blocks) {
const iniText = b.buf.join("\n").trim();
if (!iniText) continue;
const wg = parseIniOne(iniText);
// минимальная валидация: нужны ключи
if (!wg.Interface?.PrivateKey || !wg.Peer?.PublicKey || !wg.Peer?.Endpoint) {
// пропускаем мусорные блоки, чтобы не ронять весь конвертер
continue;
}
proxies.push(buildProxy(b.name, wg, opts));
}
// Финальный YAML
$content = ProxyUtils.yaml.safeDump({ proxies });

View File

@@ -1,28 +0,0 @@
// Example:
// Script Operator
// 1. backend version(>2.14.88):
$server.name = 'prefix-' + $server.name
$server.ecn = true
$server['test-url'] = 'http://1.0.0.1/generate_204'
// 2. operator function
function operator(proxies, targetPlatform, context) {
return proxies.map( proxy => {
// Change proxy information here
return proxy;
});
}
// Script Filter
// 1. backend version(>2.14.119):
const port = Number($server.port)
return [80].includes(port)
// 2. filter function
function filter(proxies, targetPlatform) {
return proxies.map( proxy => {
// Return true if the current proxy is selected
return true;
});
}

View File

@@ -1,59 +0,0 @@
/**
* 节点名改为花里胡哨字体,仅支持英文字符和数字
*
* 【字体】
* 可参考https://www.dute.org/weird-fonts
* serif-bold, serif-italic, serif-bold-italic, sans-serif-regular, sans-serif-bold-italic, script-regular, script-bold, fraktur-regular, fraktur-bold, monospace-regular, double-struck-bold, circle-regular, square-regular, modifier-letter(小写没有 q, 用 ᵠ 替代. 大写缺的太多, 用小写替代)
*
* 【示例】
* 1⃣ 设置所有格式为 "serif-bold"
* #type=serif-bold
*
* 2⃣ 设置字母格式为 "serif-bold",数字格式为 "circle-regular"
* #type=serif-bold&num=circle-regular
*/
global.$arguments = { type: "serif-bold" };
function operator(proxies) {
const { type, num } = $arguments;
const TABLE = {
"serif-bold": ["𝟎","𝟏","𝟐","𝟑","𝟒","𝟓","𝟔","𝟕","𝟖","𝟗","𝐚","𝐛","𝐜","𝐝","𝐞","𝐟","𝐠","𝐡","𝐢","𝐣","𝐤","𝐥","𝐦","𝐧","𝐨","𝐩","𝐪","𝐫","𝐬","𝐭","𝐮","𝐯","𝐰","𝐱","𝐲","𝐳","𝐀","𝐁","𝐂","𝐃","𝐄","𝐅","𝐆","𝐇","𝐈","𝐉","𝐊","𝐋","𝐌","𝐍","𝐎","𝐏","𝐐","𝐑","𝐒","𝐓","𝐔","𝐕","𝐖","𝐗","𝐘","𝐙"],
"serif-italic": ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "𝑎", "𝑏", "𝑐", "𝑑", "𝑒", "𝑓", "𝑔", "", "𝑖", "𝑗", "𝑘", "𝑙", "𝑚", "𝑛", "𝑜", "𝑝", "𝑞", "𝑟", "𝑠", "𝑡", "𝑢", "𝑣", "𝑤", "𝑥", "𝑦", "𝑧", "𝐴", "𝐵", "𝐶", "𝐷", "𝐸", "𝐹", "𝐺", "𝐻", "𝐼", "𝐽", "𝐾", "𝐿", "𝑀", "𝑁", "𝑂", "𝑃", "𝑄", "𝑅", "𝑆", "𝑇", "𝑈", "𝑉", "𝑊", "𝑋", "𝑌", "𝑍"],
"serif-bold-italic": ["0","1","2","3","4","5","6","7","8","9","𝒂","𝒃","𝒄","𝒅","𝒆","𝒇","𝒈","𝒉","𝒊","𝒋","𝒌","𝒍","𝒎","𝒏","𝒐","𝒑","𝒒","𝒓","𝒔","𝒕","𝒖","𝒗","𝒘","𝒙","𝒚","𝒛","𝑨","𝑩","𝑪","𝑫","𝑬","𝑭","𝑮","𝑯","𝑰","𝑱","𝑲","𝑳","𝑴","𝑵","𝑶","𝑷","𝑸","𝑹","𝑺","𝑻","𝑼","𝑽","𝑾","𝑿","𝒀","𝒁"],
"sans-serif-regular": ["𝟢", "𝟣", "𝟤", "𝟥", "𝟦", "𝟧", "𝟨", "𝟩", "𝟪", "𝟫", "𝖺", "𝖻", "𝖼", "𝖽", "𝖾", "𝖿", "𝗀", "𝗁", "𝗂", "𝗃", "𝗄", "𝗅", "𝗆", "𝗇", "𝗈", "𝗉", "𝗊", "𝗋", "𝗌", "𝗍", "𝗎", "𝗏", "𝗐", "𝗑", "𝗒", "𝗓", "𝖠", "𝖡", "𝖢", "𝖣", "𝖤", "𝖥", "𝖦", "𝖧", "𝖨", "𝖩", "𝖪", "𝖫", "𝖬", "𝖭", "𝖮", "𝖯", "𝖰", "𝖱", "𝖲", "𝖳", "𝖴", "𝖵", "𝖶", "𝖷", "𝖸", "𝖹"],
"sans-serif-bold": ["𝟬","𝟭","𝟮","𝟯","𝟰","𝟱","𝟲","𝟳","𝟴","𝟵","𝗮","𝗯","𝗰","𝗱","𝗲","𝗳","𝗴","𝗵","𝗶","𝗷","𝗸","𝗹","𝗺","𝗻","𝗼","𝗽","𝗾","𝗿","𝘀","𝘁","𝘂","𝘃","𝘄","𝘅","𝘆","𝘇","𝗔","𝗕","𝗖","𝗗","𝗘","𝗙","𝗚","𝗛","𝗜","𝗝","𝗞","𝗟","𝗠","𝗡","𝗢","𝗣","𝗤","𝗥","𝗦","𝗧","𝗨","𝗩","𝗪","𝗫","𝗬","𝗭"],
"sans-serif-italic": ["0","1","2","3","4","5","6","7","8","9","𝘢","𝘣","𝘤","𝘥","𝘦","𝘧","𝘨","𝘩","𝘪","𝘫","𝘬","𝘭","𝘮","𝘯","𝘰","𝘱","𝘲","𝘳","𝘴","𝘵","𝘶","𝘷","𝘸","𝘹","𝘺","𝘻","𝘈","𝘉","𝘊","𝘋","𝘌","𝘍","𝘎","𝘏","𝘐","𝘑","𝘒","𝘓","𝘔","𝘕","𝘖","𝘗","𝘘","𝘙","𝘚","𝘛","𝘜","𝘝","𝘞","𝘟","𝘠","𝘡"],
"sans-serif-bold-italic": ["0","1","2","3","4","5","6","7","8","9","𝙖","𝙗","𝙘","𝙙","𝙚","𝙛","𝙜","𝙝","𝙞","𝙟","𝙠","𝙡","𝙢","𝙣","𝙤","𝙥","𝙦","𝙧","𝙨","𝙩","𝙪","𝙫","𝙬","𝙭","𝙮","𝙯","𝘼","𝘽","𝘾","𝘿","𝙀","𝙁","𝙂","𝙃","𝙄","𝙅","𝙆","𝙇","𝙈","𝙉","𝙊","𝙋","𝙌","𝙍","𝙎","𝙏","𝙐","𝙑","𝙒","𝙓","𝙔","𝙕"],
"script-regular": ["0","1","2","3","4","5","6","7","8","9","𝒶","𝒷","𝒸","𝒹","","𝒻","","𝒽","𝒾","𝒿","𝓀","𝓁","𝓂","𝓃","","𝓅","𝓆","𝓇","𝓈","𝓉","𝓊","𝓋","𝓌","𝓍","𝓎","𝓏","𝒜","","𝒞","𝒟","","","𝒢","","","𝒥","𝒦","","","𝒩","𝒪","𝒫","𝒬","","𝒮","𝒯","𝒰","𝒱","𝒲","𝒳","𝒴","𝒵"],
"script-bold": ["0","1","2","3","4","5","6","7","8","9","𝓪","𝓫","𝓬","𝓭","𝓮","𝓯","𝓰","𝓱","𝓲","𝓳","𝓴","𝓵","𝓶","𝓷","𝓸","𝓹","𝓺","𝓻","𝓼","𝓽","𝓾","𝓿","𝔀","𝔁","𝔂","𝔃","𝓐","𝓑","𝓒","𝓓","𝓔","𝓕","𝓖","𝓗","𝓘","𝓙","𝓚","𝓛","𝓜","𝓝","𝓞","𝓟","𝓠","𝓡","𝓢","𝓣","𝓤","𝓥","𝓦","𝓧","𝓨","𝓩"],
"fraktur-regular": ["0","1","2","3","4","5","6","7","8","9","𝔞","𝔟","𝔠","𝔡","𝔢","𝔣","𝔤","𝔥","𝔦","𝔧","𝔨","𝔩","𝔪","𝔫","𝔬","𝔭","𝔮","𝔯","𝔰","𝔱","𝔲","𝔳","𝔴","𝔵","𝔶","𝔷","𝔄","𝔅","","𝔇","𝔈","𝔉","𝔊","","","𝔍","𝔎","𝔏","𝔐","𝔑","𝔒","𝔓","𝔔","","𝔖","𝔗","𝔘","𝔙","𝔚","𝔛","𝔜",""],
"fraktur-bold": ["0","1","2","3","4","5","6","7","8","9","𝖆","𝖇","𝖈","𝖉","𝖊","𝖋","𝖌","𝖍","𝖎","𝖏","𝖐","𝖑","𝖒","𝖓","𝖔","𝖕","𝖖","𝖗","𝖘","𝖙","𝖚","𝖛","𝖜","𝖝","𝖞","𝖟","𝕬","𝕭","𝕮","𝕯","𝕰","𝕱","𝕲","𝕳","𝕴","𝕵","𝕶","𝕷","𝕸","𝕹","𝕺","𝕻","𝕼","𝕽","𝕾","𝕿","𝖀","𝖁","𝖂","𝖃","𝖄","𝖅"],
"monospace-regular": ["𝟶","𝟷","𝟸","𝟹","𝟺","𝟻","𝟼","𝟽","𝟾","𝟿","𝚊","𝚋","𝚌","𝚍","𝚎","𝚏","𝚐","𝚑","𝚒","𝚓","𝚔","𝚕","𝚖","𝚗","𝚘","𝚙","𝚚","𝚛","𝚜","𝚝","𝚞","𝚟","𝚠","𝚡","𝚢","𝚣","𝙰","𝙱","𝙲","𝙳","𝙴","𝙵","𝙶","𝙷","𝙸","𝙹","𝙺","𝙻","𝙼","𝙽","𝙾","𝙿","𝚀","𝚁","𝚂","𝚃","𝚄","𝚅","𝚆","𝚇","𝚈","𝚉"],
"double-struck-bold": ["𝟘","𝟙","𝟚","𝟛","𝟜","𝟝","𝟞","𝟟","𝟠","𝟡","𝕒","𝕓","𝕔","𝕕","𝕖","𝕗","𝕘","𝕙","𝕚","𝕛","𝕜","𝕝","𝕞","𝕟","𝕠","𝕡","𝕢","𝕣","𝕤","𝕥","𝕦","𝕧","𝕨","𝕩","𝕪","𝕫","𝔸","𝔹","","𝔻","𝔼","𝔽","𝔾","","𝕀","𝕁","𝕂","𝕃","𝕄","","𝕆","","","","𝕊","𝕋","𝕌","𝕍","𝕎","𝕏","𝕐",""],
"circle-regular": ["⓪","①","②","③","④","⑤","⑥","⑦","⑧","⑨","ⓐ","ⓑ","ⓒ","ⓓ","ⓔ","ⓕ","ⓖ","ⓗ","ⓘ","ⓙ","ⓚ","ⓛ","ⓜ","ⓝ","ⓞ","ⓟ","ⓠ","ⓡ","ⓢ","ⓣ","ⓤ","ⓥ","ⓦ","ⓧ","ⓨ","ⓩ","Ⓐ","Ⓑ","Ⓒ","Ⓓ","Ⓔ","Ⓕ","Ⓖ","Ⓗ","Ⓘ","Ⓙ","Ⓚ","Ⓛ","Ⓜ","Ⓝ","Ⓞ","Ⓟ","Ⓠ","Ⓡ","Ⓢ","Ⓣ","Ⓤ","Ⓥ","Ⓦ","Ⓧ","Ⓨ","Ⓩ"],
"square-regular": ["0","1","2","3","4","5","6","7","8","9","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉","🄰","🄱","🄲","🄳","🄴","🄵","🄶","🄷","🄸","🄹","🄺","🄻","🄼","🄽","🄾","🄿","🅀","🅁","🅂","🅃","🅄","🅅","🅆","🅇","🅈","🅉"],
"modifier-letter": ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", "ᵃ", "ᵇ", "ᶜ", "ᵈ", "ᵉ", "ᶠ", "ᵍ", "ʰ", "ⁱ", "ʲ", "ᵏ", "ˡ", "ᵐ", "ⁿ", "ᵒ", "ᵖ", "ᵠ", "ʳ", "ˢ", "ᵗ", "ᵘ", "ᵛ", "ʷ", "ˣ", "ʸ", "ᶻ", "ᴬ", "ᴮ", "ᶜ", "ᴰ", "ᴱ", "ᶠ", "ᴳ", "ʰ", "ᴵ", "ᴶ", "ᴷ", "ᴸ", "ᴹ", "ᴺ", "ᴼ", "ᴾ", "ᵠ", "ᴿ", "ˢ", "ᵀ", "ᵁ", "ᵛ", "ᵂ", "ˣ", "ʸ", "ᶻ"],
};
// charCode => index in `TABLE`
const INDEX = { "48": 0, "49": 1, "50": 2, "51": 3, "52": 4, "53": 5, "54": 6, "55": 7, "56": 8, "57": 9, "65": 36, "66": 37, "67": 38, "68": 39, "69": 40, "70": 41, "71": 42, "72": 43, "73": 44, "74": 45, "75": 46, "76": 47, "77": 48, "78": 49, "79": 50, "80": 51, "81": 52, "82": 53, "83": 54, "84": 55, "85": 56, "86": 57, "87": 58, "88": 59, "89": 60, "90": 61, "97": 10, "98": 11, "99": 12, "100": 13, "101": 14, "102": 15, "103": 16, "104": 17, "105": 18, "106": 19, "107": 20, "108": 21, "109": 22, "110": 23, "111": 24, "112": 25, "113": 26, "114": 27, "115": 28, "116": 29, "117": 30, "118": 31, "119": 32, "120": 33, "121": 34, "122": 35 };
return proxies.map(p => {
p.name = [...p.name].map(c => {
if (/[a-zA-Z0-9]/.test(c)) {
const code = c.charCodeAt(0);
const index = INDEX[code];
if (isNumber(code) && num) {
return TABLE[num][index];
} else {
return TABLE[type][index];
}
}
return c;
}).join("");
return p;
})
}
function isNumber(code) { return code >= 48 && code <= 57; }

View File

@@ -1,328 +0,0 @@
/**
* Sub-Store operator: Normalize + tag + country detect + per-country numbering
*
* Output format (default):
* 🇺🇸 USA-01 🔒 📺 ❻ ▫ws/vless/443
*
* Notes:
* - Numbering is computed per-country AFTER grouping the full list.
* - Tags (icons) do NOT affect numbering order.
* - GeoIP is optional and only used when server is an IP and utils.geoip.lookup exists.
*/
///////////////////////
// CONFIG (EDIT ME)
///////////////////////
// 1) Remove these patterns (marketing noise, brackets, separators, etc.)
const NOISE_PATTERNS = [
/\[[^\]]*]/g, // [ ... ]
/\([^)]*\)/g, // ( ... )
/\{[^}]*}/g, // { ... }
/\btraffic\b/gi,
/\bfree\b/gi,
/\bwebsite\b/gi,
/\bexpire\b/gi,
/\blow\s*ping\b/gi,
/\bai\s*studio\b/gi,
/\bno\s*p2p\b/gi,
/\b10\s*gbit\b/gi,
/\bvless\b/gi, // you said you don't want it in the visible name
/\bvmess\b/gi,
/\bssr?\b/gi,
/\btrojan\b/gi,
/\bhysteria2?\b/gi,
/\btuic\b/gi,
/[|]/g,
/[_]+/g,
/[-]{2,}/g
];
// 2) Keyword -> icon tags (if found in original name, icon is added; the keyword is removed from base name)
const ICON_RULES = [
{ regex: /\bYT\b/gi, icon: "📺" },
{ regex: /\bIPv6\b/gi, icon: "❻" },
{ regex: /\bNetflix\b|\bNF\b/gi, icon: "🎬" },
{ regex: /\bDisney\+?\b|\bDSNY\b/gi, icon: "🏰" },
{ regex: /\bHBO\b/gi, icon: "📼" },
{ regex: /\bPrime\b|\bAmazon\b/gi, icon: "📦" },
{ regex: /\bChatGPT\b|\bOpenAI\b/gi, icon: "🤖" },
{ regex: /\bSteam\b/gi, icon: "🎮" },
];
// 3) Optional “network” tag rules based on NAME text (not $server.network)
// (Useful if providers shove "BGP/IPLC" into the node name)
const NAME_NETWORK_TAGS = [
{ regex: /\bIPLC\b/gi, tag: "🛰️" },
{ regex: /\bBGP\b/gi, tag: "🧭" },
{ regex: /\bAnycast\b/gi, tag: "🌍" }
];
// 4) Country detection rules by NAME (regex). First match wins (priority = lower is earlier)
const COUNTRY_RULES = [
// USA
{ regex: /\b(USA|US|UNITED\s*STATES|AMERICA|NEW\s*YORK|NYC|LOS\s*ANGELES|LA|DALLAS|CHI(CAGO)?)\b/i, iso3: "USA", flag: "🇺🇸", priority: 10 },
// Germany
{ regex: /\b(DE|DEU|GER(MANY)?|FRANKFURT|BERLIN|MUNICH|MÜNCHEN)\b/i, iso3: "DEU", flag: "🇩🇪", priority: 20 },
// Netherlands
{ regex: /\b(NL|NLD|NETHERLANDS|HOLLAND|AMSTERDAM)\b/i, iso3: "NLD", flag: "🇳🇱", priority: 30 },
// UK
{ regex: /\b(UK|GB|GBR|UNITED\s*KINGDOM|LONDON|MANCHESTER)\b/i, iso3: "GBR", flag: "🇬🇧", priority: 40 },
// France
{ regex: /\b(FR|FRA|FRANCE|PARIS|MARSEILLE)\b/i, iso3: "FRA", flag: "🇫🇷", priority: 50 },
// Poland
{ regex: /\b(PL|POL|POLAND|WARSAW|WARSZAWA)\b/i, iso3: "POL", flag: "🇵🇱", priority: 60 },
// Finland
{ regex: /\b(FI|FIN|FINLAND|HELSINKI)\b/i, iso3: "FIN", flag: "🇫🇮", priority: 70 },
// Sweden
{ regex: /\b(SE|SWE|SWEDEN|STOCKHOLM)\b/i, iso3: "SWE", flag: "🇸🇪", priority: 80 },
// Norway
{ regex: /\b(NO|NOR|NORWAY|OSLO)\b/i, iso3: "NOR", flag: "🇳🇴", priority: 90 },
// Switzerland
{ regex: /\b(CH|CHE|SWITZERLAND|ZURICH|GENEVA)\b/i, iso3: "CHE", flag: "🇨🇭", priority: 100 },
// Estonia / Latvia / Lithuania
{ regex: /\b(EE|EST|ESTONIA|TALLINN)\b/i, iso3: "EST", flag: "🇪🇪", priority: 110 },
{ regex: /\b(LV|LVA|LATVIA|RIGA)\b/i, iso3: "LVA", flag: "🇱🇻", priority: 120 },
{ regex: /\b(LT|LTU|LITHUANIA|VILNIUS)\b/i, iso3: "LTU", flag: "🇱🇹", priority: 130 },
// Turkey
{ regex: /\b(TR|TUR|TURKEY|ISTANBUL)\b/i, iso3: "TUR", flag: "🇹🇷", priority: 140 },
// Singapore / Japan / Korea / Hong Kong
{ regex: /\b(SG|SGP|SINGAPORE)\b/i, iso3: "SGP", flag: "🇸🇬", priority: 200 },
{ regex: /\b(JP|JPN|JAPAN|TOKYO|OSAKA)\b/i, iso3: "JPN", flag: "🇯🇵", priority: 210 },
{ regex: /\b(KR|KOR|KOREA|SEOUL)\b/i, iso3: "KOR", flag: "🇰🇷", priority: 220 },
{ regex: /\b(HK|HKG|HONG\s*KONG)\b/i, iso3: "HKG", flag: "🇭🇰", priority: 230 },
];
// 5) GeoIP mapping (ISO2 -> ISO3 + flag) used only if utils.geoip.lookup(ip) returns ISO2
const ISO2_TO_ISO3 = {
US: { iso3: "USA", flag: "🇺🇸" },
DE: { iso3: "DEU", flag: "🇩🇪" },
NL: { iso3: "NLD", flag: "🇳🇱" },
GB: { iso3: "GBR", flag: "🇬🇧" },
FR: { iso3: "FRA", flag: "🇫🇷" },
PL: { iso3: "POL", flag: "🇵🇱" },
FI: { iso3: "FIN", flag: "🇫🇮" },
SE: { iso3: "SWE", flag: "🇸🇪" },
NO: { iso3: "NOR", flag: "🇳🇴" },
CH: { iso3: "CHE", flag: "🇨🇭" },
EE: { iso3: "EST", flag: "🇪🇪" },
LV: { iso3: "LVA", flag: "🇱🇻" },
LT: { iso3: "LTU", flag: "🇱🇹" },
TR: { iso3: "TUR", flag: "🇹🇷" },
SG: { iso3: "SGP", flag: "🇸🇬" },
JP: { iso3: "JPN", flag: "🇯🇵" },
KR: { iso3: "KOR", flag: "🇰🇷" },
HK: { iso3: "HKG", flag: "🇭🇰" },
};
// 6) Protocol icons (based on proxy.type)
const PROTOCOL_ICONS = {
ss: "🔒",
ssr: "☂️",
vmess: "🪁",
vless: "🌌",
trojan: "🐎",
http: "🌐",
socks5: "🧦",
snell: "🐌",
wireguard: "🐲",
hysteria: "🤪",
hysteria2: "⚡",
tuic: "🚅"
};
///////////////////////
// HELPERS
///////////////////////
function isIPv4(str) {
if (typeof str !== "string") return false;
const m = str.match(/^(\d{1,3})(\.\d{1,3}){3}$/);
if (!m) return false;
return str.split(".").every(oct => {
const n = Number(oct);
return n >= 0 && n <= 255 && String(n) === oct.replace(/^0+(\d)/, "$1"); // avoids "001" weirdness
});
}
function uniq(arr) {
return [...new Set(arr.filter(Boolean))];
}
function sanitizeBaseName(name) {
let s = String(name || "");
// Remove noise patterns
for (const re of NOISE_PATTERNS) s = s.replace(re, " ");
// Collapse spaces
s = s.replace(/\s+/g, " ").trim();
return s;
}
function extractIconTagsAndStrip(name) {
let s = String(name || "");
const tags = [];
for (const r of ICON_RULES) {
if (r.regex.test(s)) {
tags.push(r.icon);
s = s.replace(r.regex, " ");
}
}
for (const t of NAME_NETWORK_TAGS) {
if (t.regex.test(s)) {
tags.push(t.tag);
s = s.replace(t.regex, " ");
}
}
return { stripped: s.replace(/\s+/g, " ").trim(), tags: uniq(tags) };
}
function detectCountryByName(name) {
const n = String(name || "");
// Order by priority, then first match wins
const sorted = COUNTRY_RULES.slice().sort((a, b) => a.priority - b.priority);
for (const c of sorted) {
if (c.regex.test(n)) return { iso3: c.iso3, flag: c.flag, priority: c.priority, source: "name" };
}
return null;
}
function detectCountryByGeoIP(server, utils) {
if (!isIPv4(server)) return null;
if (!utils || !utils.geoip || typeof utils.geoip.lookup !== "function") return null;
try {
const geo = utils.geoip.lookup(server);
const iso2 = geo && (geo.country || geo.country_code || geo.iso_code);
if (!iso2 || typeof iso2 !== "string") return null;
const key = iso2.toUpperCase();
const mapped = ISO2_TO_ISO3[key];
if (mapped) return { iso3: mapped.iso3, flag: mapped.flag, priority: 900, source: "geoip" };
// Unknown ISO2: keep something sane
return { iso3: key, flag: "🏳️", priority: 950, source: "geoip" };
} catch (e) {
return null;
}
}
function pad2(n) {
const x = Number(n);
return x < 10 ? `0${x}` : String(x);
}
function safeStr(v) {
return (v === undefined || v === null) ? "" : String(v);
}
///////////////////////
// OPERATOR
///////////////////////
function operator(proxies, targetPlatform, utils) {
// Sub-Store sometimes passes utils as global $utils; sometimes as 3rd arg; sometimes not at all.
// We'll accept any of them without whining.
const U = utils || (typeof $utils !== "undefined" ? $utils : null);
const buckets = Object.create(null);
for (const proxy of proxies) {
const originalName = safeStr(proxy && proxy.name);
// 1) Extract tags (icons) from ORIGINAL name, then strip those keywords out
const iconStage = extractIconTagsAndStrip(originalName);
// 2) Sanitize remaining base name (remove marketing trash, brackets, etc.)
const cleanBase = sanitizeBaseName(iconStage.stripped);
// 3) Detect country (name first, then GeoIP)
const byName = detectCountryByName(originalName);
const byGeo = detectCountryByGeoIP(proxy && proxy.server, U);
const country = byName || byGeo || { iso3: "UNK", flag: "🏴‍☠️", priority: 9999, source: "fallback" };
// 4) Protocol icon (based on type)
const proto = PROTOCOL_ICONS[(proxy && proxy.type) || ""] || "🔌";
// 5) Network/type/port tag (from proxy fields)
const net = safeStr(proxy && proxy.network) || "net?";
const typ = safeStr(proxy && proxy.type) || "type?";
const port = safeStr(proxy && proxy.port) || "port?";
const metaTag = `▫️${net}/${typ}/${port}`;
// 6) Prepare bucket key
const key = country.iso3;
if (!buckets[key]) {
buckets[key] = {
country,
list: []
};
}
// Keep meta used for sorting and final formatting
buckets[key].list.push({
proxy,
_meta: {
cleanBase,
iconTags: iconStage.tags,
proto,
metaTag
}
});
}
// 7) Sort buckets by priority
const bucketKeys = Object.keys(buckets).sort((a, b) => {
return (buckets[a].country.priority || 9999) - (buckets[b].country.priority || 9999);
});
// 8) Sort inside each country bucket and rename with per-country numbering
const result = [];
for (const key of bucketKeys) {
const group = buckets[key];
group.list.sort((A, B) => {
// Tags do not affect numbering: sort only by sanitized base + server:port as tie-breaker
const an = A._meta.cleanBase.toLowerCase();
const bn = B._meta.cleanBase.toLowerCase();
if (an !== bn) return an.localeCompare(bn);
const as = `${safeStr(A.proxy.server)}:${safeStr(A.proxy.port)}`;
const bs = `${safeStr(B.proxy.server)}:${safeStr(B.proxy.port)}`;
return as.localeCompare(bs);
});
for (let i = 0; i < group.list.length; i++) {
const item = group.list[i];
const p = item.proxy;
const num = pad2(i + 1);
const tagStr = item._meta.iconTags.length ? ` ${item._meta.iconTags.join(" ")}` : "";
// Final name format:
// 🇩🇪 DEU-03 🌌 📺 ❻ ▫ws/vless/443
p.name = `${group.country.flag} ${group.country.iso3}-${num} ${item._meta.proto}${tagStr} ${item._meta.metaTag}`.replace(/\s+/g, " ").trim();
result.push(p);
}
}
return result;
}

View File

@@ -1,540 +0,0 @@
/**
* Sub-Store operator: Normalize + tag + country detect + per-country numbering
*
* Output format (default):
* 🇺🇸 USA-01 🔒 📺 ❻ ▫ws/vless/443
*
* Notes:
* - Numbering is computed per-country AFTER grouping the full list.
* - Tags (icons) do NOT affect numbering order.
* - GeoIP is optional and only used when server is an IP and utils.geoip.lookup exists.
*/
// --- OPTIONS PARSING ---
function normalizeOptions() {
const args = (typeof $arguments !== "undefined" && $arguments) ? $arguments : {};
const asBool = (v, def = false) => {
if (v === undefined || v === null || v === "") return def;
if (typeof v === "boolean") return v;
const s = String(v).toLowerCase().trim();
if (["1", "true", "yes", "y", "on"].includes(s)) return true;
if (["0", "false", "no", "n", "off"].includes(s)) return false;
return def;
};
return {
appendOriginalName: asBool(args['append-original'], false),
};
}
///////////////////////
// CONFIG (EDIT ME)
///////////////////////
const DEBUG_APPEND_ORIGINAL_NAME = false; // set true to enable debug mode (appends original name as comment)
// 1) Remove these patterns (marketing noise, brackets, separators, etc.)
const NOISE_PATTERNS = [
/\[[^\]]*]/g, // [ ... ]
/\([^)]*\)/g, // ( ... )
/\{[^}]*}/g, // { ... }
/\btraffic\b/gi,
/\bfree\b/gi,
/\bwebsite\b/gi,
/\bexpire\b/gi,
/\blow\s*ping\b/gi,
/\bai\s*studio\b/gi,
/\bno\s*p2p\b/gi,
/\b10\s*gbit\b/gi,
/\bvless\b/gi,
/\bvmess\b/gi,
/\bssr?\b/gi,
/\btrojan\b/gi,
/\bhysteria2?\b/gi,
/\btuic\b/gi,
/[|]/g,
/[_]+/g,
/[-]{2,}/g
];
// 2) Keyword -> icon tags (if found in original name, icon is added; the keyword is removed from base name)
// 🇫‌🇿‌ 🇺‌🇳‌ 🇩‌🇻‌ 🇻‌🇿‌ 🇵‌🇷‌ 🇦‌🇿‌ 🇬‌🇺‌🇦‌🇷‌🇩‌
// 🌀 - double hop
const ICON_RULES = [
{ regex: /TEST/gi, icon: "🧪" },
{ regex: uWordBoundaryGroup("Low Ping"), icon: "⚡️" },
{ regex: uWordBoundaryGroup("YT|Russia|Россия"), icon: "📺" },
{ regex: uWordBoundaryGroup("IPv6"), icon: "🎱" },
{ regex: uWordBoundaryGroup("Gemini|AI Studio"), icon: "🤖" },
{ regex: uWordBoundaryGroup("Torrent|P2P|P2P-Torrents"), icon: "🧲" },
{ regex: uWordBoundaryGroup("local"), icon: "🚪" },
{ regex: uWordBoundaryGroup("neighbourhood"), icon: "🫂" },
{ regex: uWordBoundaryGroup("🌀|Мост⚡|Мост|-Мост⚡"), icon: "🌀" },
{ regex: uWordBoundaryGroup("Авто|Balance"), icon: "⚖️" },
{ regex: uWordBoundaryGroup("xfizz|x-fizz"), icon: " 🇫‌" },
{ regex: uWordBoundaryGroup("uncd|unicade"), icon: " 🇺‌" },
{ regex: uWordBoundaryGroup("vzdh|vezdehod"), icon: " 🇻‌" },
{ regex: uWordBoundaryGroup("dvpn|d-vpn"), icon: " 🇩‌" },
{ regex: uWordBoundaryGroup("ovsc|oversecure"), icon: " 🇴" },
{ regex: uWordBoundaryGroup("snow|snowy") , icon: " 🇸" },
{ regex: uWordBoundaryGroup("proton"), icon: " 🇵‌" },
{ regex: uWordBoundaryGroup("amnezia"), icon: " 🇦‌" },
{ regex: uWordBoundaryGroup("adguard"), icon: " 🇬‌‌" },
];
// 3) Optional “network” tag rules based on NAME text (not $server.network)
// (Useful if providers shove "BGP/IPLC" into the node name)
const NAME_NETWORK_TAGS = [
{ regex: uWordBoundaryGroup("IPLC"), tag: "🛰️" },
{ regex: uWordBoundaryGroup("BGP"), tag: "🧭" },
{ regex: uWordBoundaryGroup("Anycast"), tag: "🌍" }
];
// 4) Country detection rules by NAME (regex). First match wins (priority = lower is earlier)
const COUNTRY_RULES = [
{ regex: uWordBoundaryGroup("(Аргентина|Argentina|AR|ARG|ARGENTINA|BUENOS\s*AIRES)"), iso3: "ARG", flag: "🇦🇷", priority: 100 }, // Argentina
{ regex: uWordBoundaryGroup("(Australia|AU|AUS|AUSTRALIA|SYDNEY)"), iso3: "AUS", flag: "🇦🇺", priority: 110 }, // Australia
{ regex: uWordBoundaryGroup("(Austria|AT|AUT|AUSTRIA|VIENNA)"), iso3: "AUT", flag: "🇦🇹", priority: 120 }, // Austria
{ regex: uWordBoundaryGroup("(Беларусь|Белоруссия|BELARUS)"), iso3: "BLR", flag: "🇧🇾", priority: 130 }, // Belarus
{ regex: uWordBoundaryGroup("(Brazil|BR|BRA|BRAZIL|SAO\s*PAULO)"), iso3: "BRA", flag: "🇧🇷", priority: 140 }, // Brazil
{ regex: uWordBoundaryGroup("(Bulgaria|BG|BGR|BULGARIA|SOFIA)"), iso3: "BGR", flag: "🇧🇬", priority: 150 }, // Bulgaria
{ regex: uWordBoundaryGroup("(Canada|CA|CAN|CANADA|TORONTO)"), iso3: "CAN", flag: "🇨🇦", priority: 160 }, // Canada
{ regex: uWordBoundaryGroup("(КИТАЙ|China)"), iso3: "CHN", flag: "🇨🇳", priority: 170 }, // China
{ regex: uWordBoundaryGroup("(Czech\s*Republic|CZ|CZE|CZECH|PRAGUE)"), iso3: "CZE", flag: "🇨🇿", priority: 180 }, // Czech Republic
{ regex: uWordBoundaryGroup("(Denmark|DK|DNK|DENMARK|COPENHAGEN)"), iso3: "DNK", flag: "🇩🇰", priority: 190 }, // Denmark
{ regex: uWordBoundaryGroup("(Egypt|EG|EGY|EGYPT|CAIRO)"), iso3: "EGY", flag: "🇪🇬", priority: 200 }, // Egypt
{ regex: uWordBoundaryGroup("(Эстония|EE|EST|ESTONIA|TALLINN)"), iso3: "EST", flag: "🇪🇪", priority: 210 }, // Estonia
{ regex: uWordBoundaryGroup("(Финляндия|FI|FIN|FINLAND|HELSINKI)"), iso3: "FIN", flag: "🇫🇮", priority: 220 }, // Finland
{ regex: uWordBoundaryGroup("(Франция|FR|FRA|FRANCE|PARIS|MARSEILLE)"), iso3: "FRA", flag: "🇫🇷", priority: 230 }, // France
{ regex: uWordBoundaryGroup("(Georgia|GE|GEO|GEORGIA|TBILISI)"), iso3: "GEO", flag: "🇬🇪", priority: 240 }, // Georgia
{ regex: uWordBoundaryGroup("(Германия|DE|DEU|GER(MANY)?|FRANKFURT|BERLIN|MUNICH)"), iso3: "DEU", flag: "🇩🇪", priority: 250 }, // Germany
{ regex: uWordBoundaryGroup("(Гонконг|HK|HKG|HONG\s*KONG)"), iso3: "HKG", flag: "🇭🇰", priority: 260 }, // Hong Kong
{ regex: uWordBoundaryGroup("(India|IN|IND|INDIA|MUMBAI)"), iso3: "IND", flag: "🇮🇳", priority: 270 }, // India
{ regex: uWordBoundaryGroup("(Ireland|IE|IRL|IRELAND|DUBLIN)"), iso3: "IRL", flag: "🇮🇪", priority: 280 }, // Ireland
{ regex: uWordBoundaryGroup("(Israel|IL|ISR|ISRAEL|TEL\s*AVIV)"), iso3: "ISR", flag: "🇮🇱", priority: 290 }, // Israel
{ regex: uWordBoundaryGroup("(Italy|IT|ITA|ITALY|ROME)"), iso3: "ITA", flag: "🇮🇹", priority: 300 }, // Italy
{ regex: uWordBoundaryGroup("(Япония|JP|JPN|JAPAN|TOKYO|OSAKA)"), iso3: "JPN", flag: "🇯🇵", priority: 310 }, // Japan
{ regex: uWordBoundaryGroup("(Kazakhstan|KZ|KAZ|KAZAKHSTAN|ALMATY)"), iso3: "KAZ", flag: "🇰🇿", priority: 320 }, // Kazakhstan
{ regex: uWordBoundaryGroup("(Латвия|LV|LVA|LATVIA|RIGA)"), iso3: "LVA", flag: "🇱🇻", priority: 330 }, // Latvia
{ regex: uWordBoundaryGroup("(Литва|LT|LTU|LITHUANIA|VILNIUS)"), iso3: "LTU", flag: "🇱🇹", priority: 340 }, // Lithuania
{ regex: uWordBoundaryGroup("(Malaysia|MY|MYS|MALAYSIA|KUALA\s*LUMPUR)"), iso3: "MYS", flag: "🇲🇾", priority: 350 }, // Malaysia
{ regex: uWordBoundaryGroup("(Moldova|MD|MDA|MOLDOVA|CHISINAU)"), iso3: "MDA", flag: "🇲🇩", priority: 360 }, // Moldova
{ regex: uWordBoundaryGroup("(Нидерланды|NL|NLD|NETHERLANDS|HOLLAND|AMSTERDAM)"), iso3: "NLD", flag: "🇳🇱", priority: 370 }, // Netherlands
{ regex: uWordBoundaryGroup("(Nigeria|NG|NGA|NIGERIA|LAGOS)"), iso3: "NGA", flag: "🇳🇬", priority: 380 }, // Nigeria
{ regex: uWordBoundaryGroup("(Норвегия|NO|NOR|NORWAY|OSLO)"), iso3: "NOR", flag: "🇳🇴", priority: 390 }, // Norway
{ regex: uWordBoundaryGroup("(Philippines|PH|PHL|PHILIPPINES|MANILA)"), iso3: "PHL", flag: "🇵🇭", priority: 400 }, // Philippines
{ regex: uWordBoundaryGroup("(Польша|PL|POL|POLAND|WARSAW|WARSZAWA)"), iso3: "POL", flag: "🇵🇱", priority: 410 }, // Poland
{ regex: uWordBoundaryGroup("(Portugal|PT|PRT|PORTUGAL|LISBON)"), iso3: "PRT", flag: "🇵🇹", priority: 420 }, // Portugal
{ regex: uWordBoundaryGroup("(Romania|RO|ROU|ROMANIA|BUCHAREST)"), iso3: "ROU", flag: "🇷🇴", priority: 430 }, // Romania
{ regex: uWordBoundaryGroup("(Russia|RU|RUS|RUSSIA|MOSCOW)"), iso3: "RUS", flag: "🇷🇺", priority: 440 }, // Russia
{ regex: uWordBoundaryGroup("(Сингапур|SG|SGP|SINGAPORE)"), iso3: "SGP", flag: "🇸🇬", priority: 200 }, // Singapore
{ regex: uWordBoundaryGroup("(South Korea|Корея|KR|KOR|KOREA|SEOUL)"), iso3: "KOR", flag: "🇰🇷", priority: 450 }, // South Korea
{ regex: uWordBoundaryGroup("(Spain|ES|ESP|SPAIN|MADRID)"), iso3: "ESP", flag: "🇪🇸", priority: 460 }, // Spain
{ regex: uWordBoundaryGroup("(Швеция|SE|SWE|SWEDEN|STOCKHOLM)"), iso3: "SWE", flag: "🇸🇪", priority: 470 }, // Sweden
{ regex: uWordBoundaryGroup("(Швейцария|CH|CHE|SWITZERLAND|Switzerl)"), iso3: "CHE", flag: "🇨🇭", priority: 480 }, // Switzerland
{ regex: uWordBoundaryGroup("(Taiwan|TW|TWN|TAIWAN|TAIPEI)"), iso3: "TWN", flag: "🇹🇼", priority: 490 }, // Taiwan
{ regex: uWordBoundaryGroup("(Thailand|TH|THA|THAILAND|BANGKOK)"), iso3: "THA", flag: "🇹🇭", priority: 500 }, // Thailand
{ regex: uWordBoundaryGroup("(Турция|TR|TUR|TURKEY|ISTANBUL)"), iso3: "TUR", flag: "🇹🇷", priority: 510 }, // Turkey
{ regex: uWordBoundaryGroup("(UAE|United\s*Arab\s*Emirates|AE|ARE|DUBAI)"), iso3: "ARE", flag: "🇦🇪", priority: 520 }, // UAE
{ regex: uWordBoundaryGroup("(Великобритания|Англия|England|UK|GB|GBR|UNITED\s*KINGDOM)"), iso3: "GBR", flag: "🇬🇧", priority: 530 }, // UK
{ regex: uWordBoundaryGroup("(США|USA|US|UNITED\s*STATES|AMERICA|NEW\s*YORK|NYC)"), iso3: "USA", flag: "🇺🇸", priority: 540 }, // USA
{ regex: uWordBoundaryGroup("(Vietnam|VN|VNM|VIETNAM|HANOI)"), iso3: "VNM", flag: "🇻🇳", priority: 500 } // Vietnam
];
// 5) GeoIP mapping (ISO2 -> ISO3 + flag) used only if utils.geoip.lookup(ip) returns ISO2
const ISO2_TO_ISO3 = {
US: { iso3: "USA", flag: "🇺🇸" },
DE: { iso3: "DEU", flag: "🇩🇪" },
NL: { iso3: "NLD", flag: "🇳🇱" },
GB: { iso3: "GBR", flag: "🇬🇧" },
FR: { iso3: "FRA", flag: "🇫🇷" },
PL: { iso3: "POL", flag: "🇵🇱" },
FI: { iso3: "FIN", flag: "🇫🇮" },
SE: { iso3: "SWE", flag: "🇸🇪" },
NO: { iso3: "NOR", flag: "🇳🇴" },
CH: { iso3: "CHE", flag: "🇨🇭" },
EE: { iso3: "EST", flag: "🇪🇪" },
LV: { iso3: "LVA", flag: "🇱🇻" },
LT: { iso3: "LTU", flag: "🇱🇹" },
TR: { iso3: "TUR", flag: "🇹🇷" },
SG: { iso3: "SGP", flag: "🇸🇬" },
JP: { iso3: "JPN", flag: "🇯🇵" },
KR: { iso3: "KOR", flag: "🇰🇷" },
HK: { iso3: "HKG", flag: "🇭🇰" },
};
// 6) Protocol icons (based on proxy.type)
const PROTOCOL_ICONS = {
ss: "",
ssr: "",
vmess: "",
vless: "",
trojan: "",
http: "",
socks5: "",
snell: "",
wireguard: "",
hysteria: "",
hysteria2: "",
tuic: ""
};
const STANDARD_PORTS_BY_TYPE = {
wireguard: new Set(["51820"]),
vless: new Set(["443"]),
trojan: new Set(["443"]),
ss: new Set(["443"]),
};
const PROTOCOL_ICON_DEFAULT = ""; // fallback icon if type is unknown
const METATAG_RULES = {
// Keys are "network/type" OR "/type" (network-agnostic) OR "network/" (type-agnostic)
// Matching priority: exact "network/type" -> "/type" -> "network/" -> default
// 🅶🆃 🆃🆂 🆃🆅 🆆🆅 🆇🆅 🆆🅶 🅽🅸
pairMap: {
"grpc/trojan": "🅶🆃",
"tcp/trojan": "🆃🆃",
"tcp/ss": "🆃🆂‌",
"tcp/vless": "🆃🆅",
"ws/vless": "🆆🆅",
"xhttp/vless": "🆇🆅",
"grpc/vless": "🅶🆅",
"/wireguard": "🆆🅶‌",
"/naive": "🅽🅸",
},
defaultPair: "▫️", // fallback if nothing matches
includeFallbackText: false, // if true, append "(net/type)" when defaultPair is used
};
// Port formatting: superscript digits with left padding to 4 chars
// 𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗
const PORT_FORMAT = {
padLeftTo: 3,
padChar: "0",
fancy: {
"0": "𝟎", "1": "𝟏", "2": "𝟐", "3": "𝟑", "4": "𝟒", "5": "𝟓", "6": "𝟔", "7": "𝟕", "8": "𝟖", "9": "𝟗",
},
};
///////////////////////
// HELPERS
///////////////////////
function normalizeToken(s) {
return String(s || "").trim().toLowerCase();
}
function uWordBoundaryGroup(inner) {
// Match if surrounded by non-letter/non-digit (Unicode-aware)
// We don't use lookbehind for max compatibility.
return new RegExp(`(?:^|[^\\p{L}\\p{N}])(?:${inner})(?=$|[^\\p{L}\\p{N}])`, "iu");
}
function portToFancy(port, type) {
let p = String(port ?? "").trim();
p = p.replace(/[^\d]/g, "");
if (!p) return "";
if (STANDARD_PORTS_BY_TYPE[type]?.has(p)) {
return "";
}
// left pad to fixed width
//if (PORT_FORMAT.padLeftTo && p.length < PORT_FORMAT.padLeftTo) {
// p = p.padStart(PORT_FORMAT.padLeftTo, PORT_FORMAT.padChar);
//}
// map digits
//let out = "";
//for (const ch of p) out += PORT_FORMAT.fancy[ch] ?? ch;
out = "✳️"
return out;
}
function buildMetaTag(proxy) {
const net = safeStr(proxy && proxy.network) || "";
const typ = safeStr(proxy && proxy.type) || "";
const port = safeStr(proxy && proxy.port);
const { icon, matched } = metaPairIcon(net, typ);
const portSup = portToFancy(port, typ);
if (icon === METATAG_RULES.defaultPair && METATAG_RULES.includeFallbackText) {
return `${icon}(${normalizeToken(net)}/${normalizeToken(typ)})`;
}
return `${icon}`;
}
function metaPairIcon(network, type) {
const net = normalizeToken(network);
const typ = normalizeToken(type);
const exact = `${net}/${typ}`;
const typeOnly = `/${typ}`;
const netOnly = `${net}/`;
const m = METATAG_RULES.pairMap;
if (m[exact]) return { icon: m[exact], matched: exact };
if (m[typeOnly]) return { icon: m[typeOnly], matched: typeOnly };
if (m[netOnly]) return { icon: m[netOnly], matched: netOnly };
return { icon: METATAG_RULES.defaultPair, matched: null };
}
function isIPv4(str) {
if (typeof str !== "string") return false;
const m = str.match(/^(\d{1,3})(\.\d{1,3}){3}$/);
if (!m) return false;
return str.split(".").every(oct => {
const n = Number(oct);
return n >= 0 && n <= 255 && String(n) === oct.replace(/^0+(\d)/, "$1"); // avoids "001" weirdness
});
}
function uniq(arr) {
return [...new Set(arr.filter(Boolean))];
}
function sanitizeBaseName(name) {
let s = String(name || "");
// Remove noise patterns
for (const re of NOISE_PATTERNS) s = s.replace(re, " ");
// Collapse spaces
s = s.replace(/\s+/g, " ").trim();
return s;
}
function extractIconTagsAndStrip(name, proxy) {
let s = String(name || "");
const tags = [];
const typ = safeStr(proxy && proxy.type) || "";
const port = safeStr(proxy && proxy.port);
tags.push(portToFancy(port, typ))
for (const r of ICON_RULES) {
if (r.regex.test(s)) {
tags.push(r.icon);
s = s.replace(r.regex, " ");
}
}
for (const t of NAME_NETWORK_TAGS) {
if (t.regex.test(s)) {
tags.push(t.tag);
s = s.replace(t.regex, " ");
}
}
return { stripped: s.replace(/\s+/g, " ").trim(), tags: uniq(tags) };
}
function detectCountryByName(name) {
const n = String(name || "");
// Order by priority, then first match wins
// Fast path: flag emoji
if (n.includes("🇦🇪")) return { iso3: "ARE", flag: "🇦🇪", priority: 1, source: "flag" };
if (n.includes("🇦🇱")) return { iso3: "ALB", flag: "🇦🇱", priority: 2, source: "flag" };
if (n.includes("🇦🇲")) return { iso3: "ARM", flag: "🇦🇲", priority: 2, source: "flag" };
if (n.includes("🇦🇷")) return { iso3: "ARG", flag: "🇦🇷", priority: 2, source: "flag" };
if (n.includes("🇦🇹")) return { iso3: "AUT", flag: "🇦🇹", priority: 3, source: "flag" };
if (n.includes("🇦🇺")) return { iso3: "AUS", flag: "🇦🇺", priority: 4, source: "flag" };
if (n.includes("🇧🇪")) return { iso3: "BEL", flag: "🇧🇪", priority: 5, source: "flag" };
if (n.includes("🇧🇬")) return { iso3: "BGR", flag: "🇧🇬", priority: 5, source: "flag" };
if (n.includes("🇧🇾")) return { iso3: "BLR", flag: "🇧🇾", priority: 6, source: "flag" };
if (n.includes("🇧🇷")) return { iso3: "BRA", flag: "🇧🇷", priority: 7, source: "flag" };
if (n.includes("🇨🇦")) return { iso3: "CAN", flag: "🇨🇦", priority: 8, source: "flag" };
if (n.includes("🇨🇭")) return { iso3: "CHE", flag: "🇨🇭", priority: 9, source: "flag" };
if (n.includes("🇨🇳")) return { iso3: "CHN", flag: "🇨🇳", priority: 10, source: "flag" };
if (n.includes("🇨🇾")) return { iso3: "CYP", flag: "🇨🇾", priority: 11, source: "flag" };
if (n.includes("🇨🇿")) return { iso3: "CZE", flag: "🇨🇿", priority: 11, source: "flag" };
if (n.includes("🇩🇪")) return { iso3: "DEU", flag: "🇩🇪", priority: 12, source: "flag" };
if (n.includes("🇩🇰")) return { iso3: "DNK", flag: "🇩🇰", priority: 13, source: "flag" };
if (n.includes("🇪🇪")) return { iso3: "EST", flag: "🇪🇪", priority: 14, source: "flag" };
if (n.includes("🇪🇬")) return { iso3: "EGY", flag: "🇪🇬", priority: 15, source: "flag" };
if (n.includes("🇪🇸")) return { iso3: "ESP", flag: "🇪🇸", priority: 16, source: "flag" };
if (n.includes("🇫🇮")) return { iso3: "FIN", flag: "🇫🇮", priority: 17, source: "flag" };
if (n.includes("🇫🇷")) return { iso3: "FRA", flag: "🇫🇷", priority: 18, source: "flag" };
if (n.includes("🇬🇧")) return { iso3: "GBR", flag: "🇬🇧", priority: 19, source: "flag" };
if (n.includes("🇬🇪")) return { iso3: "GEO", flag: "🇬🇪", priority: 20, source: "flag" };
if (n.includes("🇬🇷")) return { iso3: "GRC", flag: "🇬🇷", priority: 2, source: "flag" };
if (n.includes("🇭🇰")) return { iso3: "HKG", flag: "🇭🇰", priority: 21, source: "flag" };
if (n.includes("🇭🇷")) return { iso3: "HRV", flag: "🇭🇷", priority: 21, source: "flag" };
if (n.includes("🇭🇺")) return { iso3: "HUN", flag: "🇭🇺", priority: 1, source: "flag" };
if (n.includes("🇮🇪")) return { iso3: "IRL", flag: "🇮🇪", priority: 22, source: "flag" };
if (n.includes("🇮🇱")) return { iso3: "ISR", flag: "🇮🇱", priority: 23, source: "flag" };
if (n.includes("🇮🇳")) return { iso3: "IND", flag: "🇮🇳", priority: 24, source: "flag" };
if (n.includes("🇮🇹")) return { iso3: "ITA", flag: "🇮🇹", priority: 25, source: "flag" };
if (n.includes("🇮🇸")) return { iso3: "ISL", flag: "🇮🇸", priority: 1, source: "flag" };
if (n.includes("🇯🇵")) return { iso3: "JPN", flag: "🇯🇵", priority: 26, source: "flag" };
if (n.includes("🇰🇷")) return { iso3: "KOR", flag: "🇰🇷", priority: 27, source: "flag" };
if (n.includes("🇰🇿")) return { iso3: "KAZ", flag: "🇰🇿", priority: 28, source: "flag" };
if (n.includes("🇱🇹")) return { iso3: "LTU", flag: "🇱🇹", priority: 29, source: "flag" };
if (n.includes("🇱🇻")) return { iso3: "LVA", flag: "🇱🇻", priority: 30, source: "flag" };
if (n.includes("🇲🇩")) return { iso3: "MDA", flag: "🇲🇩", priority: 31, source: "flag" };
if (n.includes("🇲🇰")) return { iso3: "MKD", flag: "🇲🇰", priority: 2, source: "flag" };
if (n.includes("🇲🇾")) return { iso3: "MYS", flag: "🇲🇾", priority: 32, source: "flag" };
if (n.includes("🇳🇬")) return { iso3: "NGA", flag: "🇳🇬", priority: 33, source: "flag" };
if (n.includes("🇳🇱")) return { iso3: "NLD", flag: "🇳🇱", priority: 34, source: "flag" };
if (n.includes("🇳🇴")) return { iso3: "NOR", flag: "🇳🇴", priority: 35, source: "flag" };
if (n.includes("🇵🇭")) return { iso3: "PHL", flag: "🇵🇭", priority: 36, source: "flag" };
if (n.includes("🇵🇱")) return { iso3: "POL", flag: "🇵🇱", priority: 37, source: "flag" };
if (n.includes("🇵🇹")) return { iso3: "PRT", flag: "🇵🇹", priority: 38, source: "flag" };
if (n.includes("🇷🇴")) return { iso3: "ROU", flag: "🇷🇴", priority: 39, source: "flag" };
if (n.includes("🇷🇺")) return { iso3: "RUS", flag: "🇷🇺", priority: 40, source: "flag" };
if (n.includes("🇸🇪")) return { iso3: "SWE", flag: "🇸🇪", priority: 41, source: "flag" };
if (n.includes("🇸🇬")) return { iso3: "SGP", flag: "🇸🇬", priority: 42, source: "flag" };
if (n.includes("🇹🇭")) return { iso3: "THA", flag: "🇹🇭", priority: 43, source: "flag" };
if (n.includes("🇹🇷")) return { iso3: "TUR", flag: "🇹🇷", priority: 44, source: "flag" };
if (n.includes("🇹🇼")) return { iso3: "TWN", flag: "🇹🇼", priority: 45, source: "flag" };
if (n.includes("🇺🇦")) return { iso3: "UKR", flag: "🇺🇦", priority: 2, source: "flag" };
if (n.includes("🇺🇸")) return { iso3: "USA", flag: "🇺🇸", priority: 46, source: "flag" };
if (n.includes("🇻🇳")) return { iso3: "VNM", flag: "🇻🇳", priority: 47, source: "flag" };
const sorted = COUNTRY_RULES.slice().sort((a, b) => a.priority - b.priority);
for (const c of sorted) {
if (c.regex.test(n)) return { iso3: c.iso3, flag: c.flag, priority: c.priority, source: "name" };
}
return null;
}
function detectCountryByGeoIP(server, utils) {
if (!isIPv4(server)) return null;
if (!utils || !utils.geoip || typeof utils.geoip.lookup !== "function") return null;
try {
const geo = utils.geoip.lookup(server);
const iso2 = geo && (geo.country || geo.country_code || geo.iso_code);
if (!iso2 || typeof iso2 !== "string") return null;
const key = iso2.toUpperCase();
const mapped = ISO2_TO_ISO3[key];
if (mapped) return { iso3: mapped.iso3, flag: mapped.flag, priority: 900, source: "geoip" };
// Unknown ISO2: keep something sane
return { iso3: key, flag: "🏳️", priority: 950, source: "geoip" };
} catch (e) {
return null;
}
}
function pad2(n) {
const x = Number(n);
return x < 10 ? `0${x}` : String(x);
}
function safeStr(v) {
return (v === undefined || v === null) ? "" : String(v);
}
///////////////////////
// OPERATOR
///////////////////////
function operator(proxies, targetPlatform, utils) {
// Sub-Store sometimes passes utils as global $utils; sometimes as 3rd arg; sometimes not at all.
// We'll accept any of them without whining.
const opts = normalizeOptions();
const U = utils || (typeof $utils !== "undefined" ? $utils : null);
const buckets = Object.create(null);
for (const proxy of proxies) {
const originalName = safeStr(proxy && proxy.name);
// 1) Extract tags (icons) from ORIGINAL name, then strip those keywords out
const iconStage = extractIconTagsAndStrip(originalName, proxy);
// 2) Sanitize remaining base name (remove marketing trash, brackets, etc.)
const cleanBase = sanitizeBaseName(iconStage.stripped);
// 3) Detect country (name first, then GeoIP)
const byName = detectCountryByName(originalName);
const byGeo = detectCountryByGeoIP(proxy && proxy.server, U);
const country = byName || byGeo || { iso3: "UNK", flag: "🏴‍☠️", priority: 9999, source: "fallback" };
// 4) Protocol icon (based on type)
const proto = PROTOCOL_ICONS[(proxy && proxy.type) || ""] || PROTOCOL_ICON_DEFAULT;
// 5) Network/type/port tag (from proxy fields)
const metaTag = buildMetaTag(proxy);
// 6) Prepare bucket key
const key = country.iso3;
if (!buckets[key]) {
buckets[key] = {
country,
list: []
};
}
// Keep meta used for sorting and final formatting
buckets[key].list.push({
proxy,
_meta: {
originalName,
cleanBase,
iconTags: iconStage.tags,
proto,
metaTag
}
});
}
// 7) Sort buckets by priority
const bucketKeys = Object.keys(buckets).sort((a, b) => {
return (buckets[a].country.priority || 9999) - (buckets[b].country.priority || 9999);
});
// 8) Sort inside each country bucket and rename with per-country numbering
const result = [];
for (const key of bucketKeys) {
const group = buckets[key];
group.list.sort((A, B) => {
// Tags do not affect numbering: sort only by sanitized base + server:port as tie-breaker
const an = A._meta.cleanBase.toLowerCase();
const bn = B._meta.cleanBase.toLowerCase();
if (an !== bn) return an.localeCompare(bn);
const as = `${safeStr(A.proxy.server)}:${safeStr(A.proxy.port)}`;
const bs = `${safeStr(B.proxy.server)}:${safeStr(B.proxy.port)}`;
return as.localeCompare(bs);
});
for (let i = 0; i < group.list.length; i++) {
const item = group.list[i];
const p = item.proxy;
const num = pad2(i + 1);
const debugSuffix = opts.appendOriginalName
? `${item._meta.originalName}`
: "";
const tagStr = item._meta.iconTags.length ? ` ${item._meta.iconTags.join("")}` : "";
p.name = `${group.country.flag}${item._meta.metaTag} ${group.country.iso3}-${num} ${item._meta.proto}${tagStr} ${debugSuffix}`
.replace(/\s+/g, " ")
.trim();
result.push(p);
}
}
return result;
}

View File

@@ -1,499 +0,0 @@
/**
* Sub-Store operator: Normalize + tag + country detect + per-country numbering
*
* Output format (default):
* 🇺🇸 USA-01 🔒 📺 ❻ ▫ws/vless/443
*
* Notes:
* - Numbering is computed per-country AFTER grouping the full list.
* - Tags (icons) do NOT affect numbering order.
* - GeoIP is optional and only used when server is an IP and utils.geoip.lookup exists.
*/
///////////////////////
// CONFIG (EDIT ME)
///////////////////////
const DEBUG_APPEND_ORIGINAL_NAME = false; // set true to enable debug mode (appends original name as comment)
// 1) Remove these patterns (marketing noise, brackets, separators, etc.)
const NOISE_PATTERNS = [
/\[[^\]]*]/g, // [ ... ]
/\([^)]*\)/g, // ( ... )
/\{[^}]*}/g, // { ... }
/\btraffic\b/gi,
/\bfree\b/gi,
/\bwebsite\b/gi,
/\bexpire\b/gi,
/\blow\s*ping\b/gi,
/\bai\s*studio\b/gi,
/\bno\s*p2p\b/gi,
/\b10\s*gbit\b/gi,
/\bvless\b/gi,
/\bvmess\b/gi,
/\bssr?\b/gi,
/\btrojan\b/gi,
/\bhysteria2?\b/gi,
/\btuic\b/gi,
/[|]/g,
/[_]+/g,
/[-]{2,}/g
];
// 2) Keyword -> icon tags (if found in original name, icon is added; the keyword is removed from base name)
// 🇫‌🇿‌ 🇺‌🇳‌ 🇩‌🇻‌ 🇻‌🇿‌ 🇵‌🇷‌ 🇦‌🇿‌ 🇬‌🇺‌🇦‌🇷‌🇩‌
const ICON_RULES = [
{ regex: /TEST/gi, icon: "🧪" },
{ regex: uWordBoundaryGroup("Low Ping"), icon: "⚡️" },
{ regex: uWordBoundaryGroup("10 Gbit"), icon: "🛤️" },
{ regex: uWordBoundaryGroup("YT|Russia|Россия"), icon: "📺" },
{ regex: uWordBoundaryGroup("IPv6"), icon: "🎱" },
{ regex: uWordBoundaryGroup("Gemini|AI Studio"), icon: "🤖" },
{ regex: uWordBoundaryGroup("Torrent|P2P|P2P-Torrents"), icon: "🧲" },
{ regex: uWordBoundaryGroup("local"), icon: "🚪" },
{ regex: uWordBoundaryGroup("neighbourhood"), icon: "🫂" },
{ regex: uWordBoundaryGroup("xfizz|x-fizz"), icon: "🇫‌" },
{ regex: uWordBoundaryGroup("unicade|uncd"), icon: "🇺‌" },
{ regex: uWordBoundaryGroup("vzdh|vezdehod"), icon: "🇻‌" },
{ regex: uWordBoundaryGroup("dvpn|d-vpn"), icon: "🇩‌" },
{ regex: uWordBoundaryGroup("proton"), icon: "🇵‌" },
{ regex: uWordBoundaryGroup("amnezia"), icon: "🇦‌" },
{ regex: uWordBoundaryGroup("adguard"), icon: "🇬‌‌" },
];
// 3) Optional “network” tag rules based on NAME text (not $server.network)
// (Useful if providers shove "BGP/IPLC" into the node name)
const NAME_NETWORK_TAGS = [
{ regex: uWordBoundaryGroup("IPLC"), tag: "🛰️" },
{ regex: uWordBoundaryGroup("BGP"), tag: "🧭" },
{ regex: uWordBoundaryGroup("Anycast"), tag: "🌍" }
];
// 4) Country detection rules by NAME (regex). First match wins (priority = lower is earlier)
const COUNTRY_RULES = [
{ regex: uWordBoundaryGroup("(Аргентина|Argentina|AR|ARG|ARGENTINA|BUENOS\s*AIRES)"), iso3: "ARG", flag: "🇦🇷", priority: 100 }, // Argentina
{ regex: uWordBoundaryGroup("(Australia|AU|AUS|AUSTRALIA|SYDNEY)"), iso3: "AUS", flag: "🇦🇺", priority: 110 }, // Australia
{ regex: uWordBoundaryGroup("(Austria|AT|AUT|AUSTRIA|VIENNA)"), iso3: "AUT", flag: "🇦🇹", priority: 120 }, // Austria
{ regex: uWordBoundaryGroup("(Беларусь|Белоруссия|BELARUS)"), iso3: "BLR", flag: "🇧🇾", priority: 130 }, // Belarus
{ regex: uWordBoundaryGroup("(Brazil|BR|BRA|BRAZIL|SAO\s*PAULO)"), iso3: "BRA", flag: "🇧🇷", priority: 140 }, // Brazil
{ regex: uWordBoundaryGroup("(Bulgaria|BG|BGR|BULGARIA|SOFIA)"), iso3: "BGR", flag: "🇧🇬", priority: 150 }, // Bulgaria
{ regex: uWordBoundaryGroup("(Canada|CA|CAN|CANADA|TORONTO)"), iso3: "CAN", flag: "🇨🇦", priority: 160 }, // Canada
{ regex: uWordBoundaryGroup("(КИТАЙ|China)"), iso3: "CHN", flag: "🇨🇳", priority: 170 }, // China
{ regex: uWordBoundaryGroup("(Czech\s*Republic|CZ|CZE|CZECH|PRAGUE)"), iso3: "CZE", flag: "🇨🇿", priority: 180 }, // Czech Republic
{ regex: uWordBoundaryGroup("(Denmark|DK|DNK|DENMARK|COPENHAGEN)"), iso3: "DNK", flag: "🇩🇰", priority: 190 }, // Denmark
{ regex: uWordBoundaryGroup("(Egypt|EG|EGY|EGYPT|CAIRO)"), iso3: "EGY", flag: "🇪🇬", priority: 200 }, // Egypt
{ regex: uWordBoundaryGroup("(Эстония|EE|EST|ESTONIA|TALLINN)"), iso3: "EST", flag: "🇪🇪", priority: 210 }, // Estonia
{ regex: uWordBoundaryGroup("(Финляндия|FI|FIN|FINLAND|HELSINKI)"), iso3: "FIN", flag: "🇫🇮", priority: 220 }, // Finland
{ regex: uWordBoundaryGroup("(Франция|FR|FRA|FRANCE|PARIS|MARSEILLE)"), iso3: "FRA", flag: "🇫🇷", priority: 230 }, // France
{ regex: uWordBoundaryGroup("(Georgia|GE|GEO|GEORGIA|TBILISI)"), iso3: "GEO", flag: "🇬🇪", priority: 240 }, // Georgia
{ regex: uWordBoundaryGroup("(Германия|DE|DEU|GER(MANY)?|FRANKFURT|BERLIN|MUNICH)"), iso3: "DEU", flag: "🇩🇪", priority: 250 }, // Germany
{ regex: uWordBoundaryGroup("(Гонконг|HK|HKG|HONG\s*KONG)"), iso3: "HKG", flag: "🇭🇰", priority: 260 }, // Hong Kong
{ regex: uWordBoundaryGroup("(India|IN|IND|INDIA|MUMBAI)"), iso3: "IND", flag: "🇮🇳", priority: 270 }, // India
{ regex: uWordBoundaryGroup("(Ireland|IE|IRL|IRELAND|DUBLIN)"), iso3: "IRL", flag: "🇮🇪", priority: 280 }, // Ireland
{ regex: uWordBoundaryGroup("(Israel|IL|ISR|ISRAEL|TEL\s*AVIV)"), iso3: "ISR", flag: "🇮🇱", priority: 290 }, // Israel
{ regex: uWordBoundaryGroup("(Italy|IT|ITA|ITALY|ROME)"), iso3: "ITA", flag: "🇮🇹", priority: 300 }, // Italy
{ regex: uWordBoundaryGroup("(Япония|JP|JPN|JAPAN|TOKYO|OSAKA)"), iso3: "JPN", flag: "🇯🇵", priority: 310 }, // Japan
{ regex: uWordBoundaryGroup("(Kazakhstan|KZ|KAZ|KAZAKHSTAN|ALMATY)"), iso3: "KAZ", flag: "🇰🇿", priority: 320 }, // Kazakhstan
{ regex: uWordBoundaryGroup("(Латвия|LV|LVA|LATVIA|RIGA)"), iso3: "LVA", flag: "🇱🇻", priority: 330 }, // Latvia
{ regex: uWordBoundaryGroup("(Литва|LT|LTU|LITHUANIA|VILNIUS)"), iso3: "LTU", flag: "🇱🇹", priority: 340 }, // Lithuania
{ regex: uWordBoundaryGroup("(Malaysia|MY|MYS|MALAYSIA|KUALA\s*LUMPUR)"), iso3: "MYS", flag: "🇲🇾", priority: 350 }, // Malaysia
{ regex: uWordBoundaryGroup("(Moldova|MD|MDA|MOLDOVA|CHISINAU)"), iso3: "MDA", flag: "🇲🇩", priority: 360 }, // Moldova
{ regex: uWordBoundaryGroup("(Нидерланды|NL|NLD|NETHERLANDS|HOLLAND|AMSTERDAM)"), iso3: "NLD", flag: "🇳🇱", priority: 370 }, // Netherlands
{ regex: uWordBoundaryGroup("(Nigeria|NG|NGA|NIGERIA|LAGOS)"), iso3: "NGA", flag: "🇳🇬", priority: 380 }, // Nigeria
{ regex: uWordBoundaryGroup("(Норвегия|NO|NOR|NORWAY|OSLO)"), iso3: "NOR", flag: "🇳🇴", priority: 390 }, // Norway
{ regex: uWordBoundaryGroup("(Philippines|PH|PHL|PHILIPPINES|MANILA)"), iso3: "PHL", flag: "🇵🇭", priority: 400 }, // Philippines
{ regex: uWordBoundaryGroup("(Польша|PL|POL|POLAND|WARSAW|WARSZAWA)"), iso3: "POL", flag: "🇵🇱", priority: 410 }, // Poland
{ regex: uWordBoundaryGroup("(Portugal|PT|PRT|PORTUGAL|LISBON)"), iso3: "PRT", flag: "🇵🇹", priority: 420 }, // Portugal
{ regex: uWordBoundaryGroup("(Romania|RO|ROU|ROMANIA|BUCHAREST)"), iso3: "ROU", flag: "🇷🇴", priority: 430 }, // Romania
{ regex: uWordBoundaryGroup("(Russia|RU|RUS|RUSSIA|MOSCOW)"), iso3: "RUS", flag: "🇷🇺", priority: 440 }, // Russia
{ regex: uWordBoundaryGroup("(Сингапур|SG|SGP|SINGAPORE)"), iso3: "SGP", flag: "🇸🇬", priority: 200 }, // Singapore
{ regex: uWordBoundaryGroup("(South Korea|Корея|KR|KOR|KOREA|SEOUL)"), iso3: "KOR", flag: "🇰🇷", priority: 450 }, // South Korea
{ regex: uWordBoundaryGroup("(Spain|ES|ESP|SPAIN|MADRID)"), iso3: "ESP", flag: "🇪🇸", priority: 460 }, // Spain
{ regex: uWordBoundaryGroup("(Швеция|SE|SWE|SWEDEN|STOCKHOLM)"), iso3: "SWE", flag: "🇸🇪", priority: 470 }, // Sweden
{ regex: uWordBoundaryGroup("(Швейцария|CH|CHE|SWITZERLAND|Switzerl)"), iso3: "CHE", flag: "🇨🇭", priority: 480 }, // Switzerland
{ regex: uWordBoundaryGroup("(Taiwan|TW|TWN|TAIWAN|TAIPEI)"), iso3: "TWN", flag: "🇹🇼", priority: 490 }, // Taiwan
{ regex: uWordBoundaryGroup("(Thailand|TH|THA|THAILAND|BANGKOK)"), iso3: "THA", flag: "🇹🇭", priority: 500 }, // Thailand
{ regex: uWordBoundaryGroup("(Турция|TR|TUR|TURKEY|ISTANBUL)"), iso3: "TUR", flag: "🇹🇷", priority: 510 }, // Turkey
{ regex: uWordBoundaryGroup("(UAE|United\s*Arab\s*Emirates|AE|ARE|DUBAI)"), iso3: "ARE", flag: "🇦🇪", priority: 520 }, // UAE
{ regex: uWordBoundaryGroup("(Великобритания|Англия|England|UK|GB|GBR|UNITED\s*KINGDOM)"), iso3: "GBR", flag: "🇬🇧", priority: 530 }, // UK
{ regex: uWordBoundaryGroup("(США|USA|US|UNITED\s*STATES|AMERICA|NEW\s*YORK|NYC)"), iso3: "USA", flag: "🇺🇸", priority: 540 }, // USA
{ regex: uWordBoundaryGroup("(Vietnam|VN|VNM|VIETNAM|HANOI)"), iso3: "VNM", flag: "🇻🇳", priority: 500 } // Vietnam
];
// 5) GeoIP mapping (ISO2 -> ISO3 + flag) used only if utils.geoip.lookup(ip) returns ISO2
const ISO2_TO_ISO3 = {
US: { iso3: "USA", flag: "🇺🇸" },
DE: { iso3: "DEU", flag: "🇩🇪" },
NL: { iso3: "NLD", flag: "🇳🇱" },
GB: { iso3: "GBR", flag: "🇬🇧" },
FR: { iso3: "FRA", flag: "🇫🇷" },
PL: { iso3: "POL", flag: "🇵🇱" },
FI: { iso3: "FIN", flag: "🇫🇮" },
SE: { iso3: "SWE", flag: "🇸🇪" },
NO: { iso3: "NOR", flag: "🇳🇴" },
CH: { iso3: "CHE", flag: "🇨🇭" },
EE: { iso3: "EST", flag: "🇪🇪" },
LV: { iso3: "LVA", flag: "🇱🇻" },
LT: { iso3: "LTU", flag: "🇱🇹" },
TR: { iso3: "TUR", flag: "🇹🇷" },
SG: { iso3: "SGP", flag: "🇸🇬" },
JP: { iso3: "JPN", flag: "🇯🇵" },
KR: { iso3: "KOR", flag: "🇰🇷" },
HK: { iso3: "HKG", flag: "🇭🇰" },
};
// 6) Protocol icons (based on proxy.type)
const PROTOCOL_ICONS = {
ss: "",
ssr: "",
vmess: "",
vless: "",
trojan: "",
http: "",
socks5: "",
snell: "",
wireguard: "",
hysteria: "",
hysteria2: "",
tuic: ""
};
const STANDARD_PORTS_BY_TYPE = {
wireguard: new Set(["51820"]),
vless: new Set(["443"]),
trojan: new Set(["443"]),
ss: new Set(["443"]),
};
const PROTOCOL_ICON_DEFAULT = ""; // fallback icon if type is unknown
const METATAG_RULES = {
// Keys are "network/type" OR "/type" (network-agnostic) OR "network/" (type-agnostic)
// Matching priority: exact "network/type" -> "/type" -> "network/" -> default
// 🅶🆃 🆃🆂 🆃🆅 🆆🆅 🆇🆅 🆆🅶 🅽🅸
pairMap: {
"grpc/trojan": "🅶🆃",
"tcp/ss": "🆃🆂‌",
"tcp/vless": "🆃🆅",
"ws/vless": "🆆🆅",
"xhttp/vless": "🆇🆅",
"/wireguard": "🆆🅶‌",
"/naive": "🅽🅸",
},
defaultPair: "▫️", // fallback if nothing matches
includeFallbackText: false, // if true, append "(net/type)" when defaultPair is used
};
// Port formatting: superscript digits with left padding to 4 chars
// 𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗
const PORT_FORMAT = {
padLeftTo: 3,
padChar: "0",
fancy: {
"0": "𝟎", "1": "𝟏", "2": "𝟐", "3": "𝟑", "4": "𝟒", "5": "𝟓", "6": "𝟔", "7": "𝟕", "8": "𝟖", "9": "𝟗",
},
};
///////////////////////
// HELPERS
///////////////////////
function normalizeToken(s) {
return String(s || "").trim().toLowerCase();
}
function uWordBoundaryGroup(inner) {
// Match if surrounded by non-letter/non-digit (Unicode-aware)
// We don't use lookbehind for max compatibility.
return new RegExp(`(?:^|[^\\p{L}\\p{N}])(?:${inner})(?=$|[^\\p{L}\\p{N}])`, "iu");
}
function portToFancy(port, type) {
let p = String(port ?? "").trim();
p = p.replace(/[^\d]/g, "");
if (!p) return "";
if (STANDARD_PORTS_BY_TYPE[type]?.has(p)) {
return "";
}
// left pad to fixed width
if (PORT_FORMAT.padLeftTo && p.length < PORT_FORMAT.padLeftTo) {
p = p.padStart(PORT_FORMAT.padLeftTo, PORT_FORMAT.padChar);
}
// map digits
let out = "";
for (const ch of p) out += PORT_FORMAT.fancy[ch] ?? ch;
return out;
}
function buildMetaTag(proxy) {
const net = safeStr(proxy && proxy.network) || "";
const typ = safeStr(proxy && proxy.type) || "";
const port = safeStr(proxy && proxy.port);
const { icon, matched } = metaPairIcon(net, typ);
const portSup = portToFancy(port, typ);
if (icon === METATAG_RULES.defaultPair && METATAG_RULES.includeFallbackText) {
return `${icon}${portSup}(${normalizeToken(net)}/${normalizeToken(typ)})`;
}
return `${icon}${portSup}`;
}
function metaPairIcon(network, type) {
const net = normalizeToken(network);
const typ = normalizeToken(type);
const exact = `${net}/${typ}`;
const typeOnly = `/${typ}`;
const netOnly = `${net}/`;
const m = METATAG_RULES.pairMap;
if (m[exact]) return { icon: m[exact], matched: exact };
if (m[typeOnly]) return { icon: m[typeOnly], matched: typeOnly };
if (m[netOnly]) return { icon: m[netOnly], matched: netOnly };
return { icon: METATAG_RULES.defaultPair, matched: null };
}
function isIPv4(str) {
if (typeof str !== "string") return false;
const m = str.match(/^(\d{1,3})(\.\d{1,3}){3}$/);
if (!m) return false;
return str.split(".").every(oct => {
const n = Number(oct);
return n >= 0 && n <= 255 && String(n) === oct.replace(/^0+(\d)/, "$1"); // avoids "001" weirdness
});
}
function uniq(arr) {
return [...new Set(arr.filter(Boolean))];
}
function sanitizeBaseName(name) {
let s = String(name || "");
// Remove noise patterns
for (const re of NOISE_PATTERNS) s = s.replace(re, " ");
// Collapse spaces
s = s.replace(/\s+/g, " ").trim();
return s;
}
function extractIconTagsAndStrip(name) {
let s = String(name || "");
const tags = [];
for (const r of ICON_RULES) {
if (r.regex.test(s)) {
tags.push(r.icon);
s = s.replace(r.regex, " ");
}
}
for (const t of NAME_NETWORK_TAGS) {
if (t.regex.test(s)) {
tags.push(t.tag);
s = s.replace(t.regex, " ");
}
}
return { stripped: s.replace(/\s+/g, " ").trim(), tags: uniq(tags) };
}
function detectCountryByName(name) {
const n = String(name || "");
// Order by priority, then first match wins
// Fast path: flag emoji
if (n.includes("🇦🇪")) return { iso3: "ARE", flag: "🇦🇪", priority: 1, source: "flag" };
if (n.includes("🇦🇷")) return { iso3: "ARG", flag: "🇦🇷", priority: 2, source: "flag" };
if (n.includes("🇦🇹")) return { iso3: "AUT", flag: "🇦🇹", priority: 3, source: "flag" };
if (n.includes("🇦🇺")) return { iso3: "AUS", flag: "🇦🇺", priority: 4, source: "flag" };
if (n.includes("🇧🇬")) return { iso3: "BGR", flag: "🇧🇬", priority: 5, source: "flag" };
if (n.includes("🇧🇾")) return { iso3: "BLR", flag: "🇧🇾", priority: 6, source: "flag" };
if (n.includes("🇧🇷")) return { iso3: "BRA", flag: "🇧🇷", priority: 7, source: "flag" };
if (n.includes("🇨🇦")) return { iso3: "CAN", flag: "🇨🇦", priority: 8, source: "flag" };
if (n.includes("🇨🇭")) return { iso3: "CHE", flag: "🇨🇭", priority: 9, source: "flag" };
if (n.includes("🇨🇳")) return { iso3: "CHN", flag: "🇨🇳", priority: 10, source: "flag" };
if (n.includes("🇨🇿")) return { iso3: "CZE", flag: "🇨🇿", priority: 11, source: "flag" };
if (n.includes("🇩🇪")) return { iso3: "DEU", flag: "🇩🇪", priority: 12, source: "flag" };
if (n.includes("🇩🇰")) return { iso3: "DNK", flag: "🇩🇰", priority: 13, source: "flag" };
if (n.includes("🇪🇪")) return { iso3: "EST", flag: "🇪🇪", priority: 14, source: "flag" };
if (n.includes("🇪🇬")) return { iso3: "EGY", flag: "🇪🇬", priority: 15, source: "flag" };
if (n.includes("🇪🇸")) return { iso3: "ESP", flag: "🇪🇸", priority: 16, source: "flag" };
if (n.includes("🇫🇮")) return { iso3: "FIN", flag: "🇫🇮", priority: 17, source: "flag" };
if (n.includes("🇫🇷")) return { iso3: "FRA", flag: "🇫🇷", priority: 18, source: "flag" };
if (n.includes("🇬🇧")) return { iso3: "GBR", flag: "🇬🇧", priority: 19, source: "flag" };
if (n.includes("🇬🇪")) return { iso3: "GEO", flag: "🇬🇪", priority: 20, source: "flag" };
if (n.includes("🇭🇰")) return { iso3: "HKG", flag: "🇭🇰", priority: 21, source: "flag" };
if (n.includes("🇮🇪")) return { iso3: "IRL", flag: "🇮🇪", priority: 22, source: "flag" };
if (n.includes("🇮🇱")) return { iso3: "ISR", flag: "🇮🇱", priority: 23, source: "flag" };
if (n.includes("🇮🇳")) return { iso3: "IND", flag: "🇮🇳", priority: 24, source: "flag" };
if (n.includes("🇮🇹")) return { iso3: "ITA", flag: "🇮🇹", priority: 25, source: "flag" };
if (n.includes("🇯🇵")) return { iso3: "JPN", flag: "🇯🇵", priority: 26, source: "flag" };
if (n.includes("🇰🇷")) return { iso3: "KOR", flag: "🇰🇷", priority: 27, source: "flag" };
if (n.includes("🇰🇿")) return { iso3: "KAZ", flag: "🇰🇿", priority: 28, source: "flag" };
if (n.includes("🇱🇹")) return { iso3: "LTU", flag: "🇱🇹", priority: 29, source: "flag" };
if (n.includes("🇱🇻")) return { iso3: "LVA", flag: "🇱🇻", priority: 30, source: "flag" };
if (n.includes("🇲🇩")) return { iso3: "MDA", flag: "🇲🇩", priority: 31, source: "flag" };
if (n.includes("🇲🇾")) return { iso3: "MYS", flag: "🇲🇾", priority: 32, source: "flag" };
if (n.includes("🇳🇬")) return { iso3: "NGA", flag: "🇳🇬", priority: 33, source: "flag" };
if (n.includes("🇳🇱")) return { iso3: "NLD", flag: "🇳🇱", priority: 34, source: "flag" };
if (n.includes("🇳🇴")) return { iso3: "NOR", flag: "🇳🇴", priority: 35, source: "flag" };
if (n.includes("🇵🇭")) return { iso3: "PHL", flag: "🇵🇭", priority: 36, source: "flag" };
if (n.includes("🇵🇱")) return { iso3: "POL", flag: "🇵🇱", priority: 37, source: "flag" };
if (n.includes("🇵🇹")) return { iso3: "PRT", flag: "🇵🇹", priority: 38, source: "flag" };
if (n.includes("🇷🇴")) return { iso3: "ROU", flag: "🇷🇴", priority: 39, source: "flag" };
if (n.includes("🇷🇺")) return { iso3: "RUS", flag: "🇷🇺", priority: 40, source: "flag" };
if (n.includes("🇸🇪")) return { iso3: "SWE", flag: "🇸🇪", priority: 41, source: "flag" };
if (n.includes("🇸🇬")) return { iso3: "SGP", flag: "🇸🇬", priority: 42, source: "flag" };
if (n.includes("🇹🇭")) return { iso3: "THA", flag: "🇹🇭", priority: 43, source: "flag" };
if (n.includes("🇹🇷")) return { iso3: "TUR", flag: "🇹🇷", priority: 44, source: "flag" };
if (n.includes("🇹🇼")) return { iso3: "TWN", flag: "🇹🇼", priority: 45, source: "flag" };
if (n.includes("🇺🇸")) return { iso3: "USA", flag: "🇺🇸", priority: 46, source: "flag" };
if (n.includes("🇻🇳")) return { iso3: "VNM", flag: "🇻🇳", priority: 47, source: "flag" };
const sorted = COUNTRY_RULES.slice().sort((a, b) => a.priority - b.priority);
for (const c of sorted) {
if (c.regex.test(n)) return { iso3: c.iso3, flag: c.flag, priority: c.priority, source: "name" };
}
return null;
}
function detectCountryByGeoIP(server, utils) {
if (!isIPv4(server)) return null;
if (!utils || !utils.geoip || typeof utils.geoip.lookup !== "function") return null;
try {
const geo = utils.geoip.lookup(server);
const iso2 = geo && (geo.country || geo.country_code || geo.iso_code);
if (!iso2 || typeof iso2 !== "string") return null;
const key = iso2.toUpperCase();
const mapped = ISO2_TO_ISO3[key];
if (mapped) return { iso3: mapped.iso3, flag: mapped.flag, priority: 900, source: "geoip" };
// Unknown ISO2: keep something sane
return { iso3: key, flag: "🏳️", priority: 950, source: "geoip" };
} catch (e) {
return null;
}
}
function pad2(n) {
const x = Number(n);
return x < 10 ? `0${x}` : String(x);
}
function safeStr(v) {
return (v === undefined || v === null) ? "" : String(v);
}
///////////////////////
// OPERATOR
///////////////////////
function operator(proxies, targetPlatform, utils) {
// Sub-Store sometimes passes utils as global $utils; sometimes as 3rd arg; sometimes not at all.
// We'll accept any of them without whining.
const U = utils || (typeof $utils !== "undefined" ? $utils : null);
const buckets = Object.create(null);
for (const proxy of proxies) {
const originalName = safeStr(proxy && proxy.name);
// 1) Extract tags (icons) from ORIGINAL name, then strip those keywords out
const iconStage = extractIconTagsAndStrip(originalName);
// 2) Sanitize remaining base name (remove marketing trash, brackets, etc.)
const cleanBase = sanitizeBaseName(iconStage.stripped);
// 3) Detect country (name first, then GeoIP)
const byName = detectCountryByName(originalName);
const byGeo = detectCountryByGeoIP(proxy && proxy.server, U);
const country = byName || byGeo || { iso3: "UNK", flag: "🏴‍☠️", priority: 9999, source: "fallback" };
// 4) Protocol icon (based on type)
const proto = PROTOCOL_ICONS[(proxy && proxy.type) || ""] || PROTOCOL_ICON_DEFAULT;
// 5) Network/type/port tag (from proxy fields)
const metaTag = buildMetaTag(proxy);
// 6) Prepare bucket key
const key = country.iso3;
if (!buckets[key]) {
buckets[key] = {
country,
list: []
};
}
// Keep meta used for sorting and final formatting
buckets[key].list.push({
proxy,
_meta: {
originalName,
cleanBase,
iconTags: iconStage.tags,
proto,
metaTag
}
});
}
// 7) Sort buckets by priority
const bucketKeys = Object.keys(buckets).sort((a, b) => {
return (buckets[a].country.priority || 9999) - (buckets[b].country.priority || 9999);
});
// 8) Sort inside each country bucket and rename with per-country numbering
const result = [];
for (const key of bucketKeys) {
const group = buckets[key];
group.list.sort((A, B) => {
// Tags do not affect numbering: sort only by sanitized base + server:port as tie-breaker
const an = A._meta.cleanBase.toLowerCase();
const bn = B._meta.cleanBase.toLowerCase();
if (an !== bn) return an.localeCompare(bn);
const as = `${safeStr(A.proxy.server)}:${safeStr(A.proxy.port)}`;
const bs = `${safeStr(B.proxy.server)}:${safeStr(B.proxy.port)}`;
return as.localeCompare(bs);
});
for (let i = 0; i < group.list.length; i++) {
const item = group.list[i];
const p = item.proxy;
const num = pad2(i + 1);
const debugSuffix = DEBUG_APPEND_ORIGINAL_NAME
? `${item._meta.originalName}`
: "";
const tagStr = item._meta.iconTags.length ? ` ${item._meta.iconTags.join(" ")}` : "";
p.name = `${group.country.flag}${item._meta.metaTag} ${group.country.iso3}-${num} ${item._meta.proto}${tagStr} ${debugSuffix}`
.replace(/\s+/g, " ")
.trim();
result.push(p);
}
}
return result;
}

View File

@@ -1,110 +0,0 @@
function safeStringify(obj) {
const seen = new WeakSet();
return JSON.stringify(
obj,
(k, v) => {
if (typeof v === "object" && v !== null) {
if (seen.has(v)) return "[Circular]";
seen.add(v);
}
if (typeof v === "function") return `[Function: ${v.name || "anonymous"}]`;
if (typeof v === "bigint") return v.toString();
return v;
},
2
);
}
function pickEnvSample() {
try {
const env = (typeof process !== "undefined" && process && process.env) ? process.env : null;
if (!env) return null;
// only show safe-ish keys, no full dump
const keys = Object.keys(env).sort();
const filtered = keys.filter(k =>
k.toLowerCase().includes("sub") ||
k.toLowerCase().includes("store") ||
k.toLowerCase().includes("script") ||
k.toLowerCase().includes("url") ||
k.toLowerCase().includes("option") ||
k.toLowerCase().includes("param")
);
const sample = {};
for (const k of filtered.slice(0, 50)) sample[k] = env[k];
return { keysCount: keys.length, filteredKeys: filtered.slice(0, 100), sample };
} catch (e) {
return { error: String(e) };
}
}
function getGlobalDollarKeys() {
try {
return Object.getOwnPropertyNames(globalThis).filter(k => k.startsWith("$")).sort();
} catch {
return [];
}
}
// Safe "typeof" probes: never throws even if variable doesn't exist
const probes = {
$content: typeof $content,
$files: typeof $files,
$options: typeof $options,
$params: typeof $params,
$args: typeof $args,
$arguments: typeof $arguments,
$argument: typeof $argument,
$argv: typeof $argv,
$ctx: typeof $ctx,
$context: typeof $context,
$request: typeof $request,
$req: typeof $req,
$url: typeof $url,
$scriptUrl: typeof $scriptUrl,
$script_url: typeof $script_url,
ProxyUtils: typeof ProxyUtils,
produceArtifact: typeof produceArtifact,
process: typeof process,
};
const values = {};
function maybeSet(name, getter) {
try {
const v = getter();
// Avoid huge outputs
if (typeof v === "string") values[name] = v.length > 800 ? v.slice(0, 800) + "…(truncated)" : v;
else values[name] = v;
} catch (e) {
values[name] = { error: String(e) };
}
}
maybeSet("$options", () => (typeof $options !== "undefined" ? $options : null));
maybeSet("$params", () => (typeof $params !== "undefined" ? $params : null));
maybeSet("$args", () => (typeof $args !== "undefined" ? $args : null));
maybeSet("$arguments", () => (typeof $arguments !== "undefined" ? $arguments : null));
maybeSet("$argument", () => (typeof $argument !== "undefined" ? $argument : null));
maybeSet("$ctx", () => (typeof $ctx !== "undefined" ? $ctx : null));
maybeSet("$request", () => (typeof $request !== "undefined" ? $request : null));
maybeSet("$url", () => (typeof $url !== "undefined" ? $url : null));
maybeSet("$scriptUrl", () => (typeof $scriptUrl !== "undefined" ? $scriptUrl : null));
maybeSet("$script_url", () => (typeof $script_url !== "undefined" ? $script_url : null));
maybeSet("$contentPreview", () => (typeof $content === "string" ? $content.slice(0, 300) : $content));
maybeSet("$contentLength", () => (typeof $content === "string" ? $content.length : null));
maybeSet("$files", () => (typeof $files !== "undefined" ? $files : null));
const report = {
probes,
values,
globalDollarKeys: getGlobalDollarKeys(),
envSample: pickEnvSample(),
};
$content = safeStringify(report);

View File

@@ -16,4 +16,10 @@ depend() {
need net need net
use dns use dns
after firewall after firewall
}
start_pre() {
# Жестко выдаем права перед каждым запуском.
# Подавляем ошибки, чтобы не ломать старт, если ФС вдруг смонтирована в read-only
setcap 'cap_net_admin,cap_net_bind_service=+ep' /usr/local/bin/mihomo 2>/dev/null || true
} }

View File

@@ -25,3 +25,4 @@ payload:
# VISA # VISA
- DOMAIN-SUFFIX,worldline-solutions.com - DOMAIN-SUFFIX,worldline-solutions.com
- DOMAIN-SUFFIX,dragonpass.com - DOMAIN-SUFFIX,dragonpass.com
- DOMAIN-SUFFIX,visaconcierge.eu

View File

@@ -5,3 +5,5 @@ payload:
# 🤖 Arcadia Servitor: Manually added payload: # 🤖 Arcadia Servitor: Manually added payload:
# ------------------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------------------
- SRC-IP-CIDR,192.168.24.22/32 - SRC-IP-CIDR,192.168.24.22/32
- SRC-IP-CIDR,192.168.10.209/32
- SRC-IP-CIDR,100.98.120.158/32

View File

@@ -44,7 +44,7 @@ payload:
- DOMAIN-SUFFIX,generativeai.google - DOMAIN-SUFFIX,generativeai.google
- DOMAIN-SUFFIX,proactivebackend-pa.googleapis.com - DOMAIN-SUFFIX,proactivebackend-pa.googleapis.com
# 🤖 Arcadia Servitor: Fetched 69 records from https://github.com/dler-io/Rules/raw/refs/heads/main/Clash/Provider/AI%20Suite.yaml # 🤖 Arcadia Servitor: Fetched 73 records from https://github.com/dler-io/Rules/raw/refs/heads/main/Clash/Provider/AI%20Suite.yaml
# ------------------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------------------
- DOMAIN-SUFFIX,augment.com - DOMAIN-SUFFIX,augment.com
- DOMAIN-SUFFIX,augmentcode.com - DOMAIN-SUFFIX,augmentcode.com
@@ -63,6 +63,7 @@ payload:
- DOMAIN-SUFFIX,chorus.sh - DOMAIN-SUFFIX,chorus.sh
# - DOMAIN-SUFFIX,anthropic.com 🤖AS: Duplicated # - DOMAIN-SUFFIX,anthropic.com 🤖AS: Duplicated
# - DOMAIN-SUFFIX,claude.ai 🤖AS: Duplicated # - DOMAIN-SUFFIX,claude.ai 🤖AS: Duplicated
- DOMAIN-SUFFIX,claude.com
- DOMAIN-SUFFIX,claudeusercontent.com - DOMAIN-SUFFIX,claudeusercontent.com
- DOMAIN,gateway.ai.cloudflare.com - DOMAIN,gateway.ai.cloudflare.com
- DOMAIN-SUFFIX,githubcopilot.com - DOMAIN-SUFFIX,githubcopilot.com
@@ -87,6 +88,7 @@ payload:
- DOMAIN-SUFFIX,aisandbox-pa.googleapis.com - DOMAIN-SUFFIX,aisandbox-pa.googleapis.com
- DOMAIN-SUFFIX,apis.google.com - DOMAIN-SUFFIX,apis.google.com
# - DOMAIN-SUFFIX,bard.google.com 🤖AS: Duplicated # - DOMAIN-SUFFIX,bard.google.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,gemini.google
# - DOMAIN-SUFFIX,gemini.google.com 🤖AS: Duplicated # - DOMAIN-SUFFIX,gemini.google.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,robinfrontend-pa.googleapis.com - DOMAIN-SUFFIX,robinfrontend-pa.googleapis.com
- DOMAIN-SUFFIX,notebooklm.google - DOMAIN-SUFFIX,notebooklm.google
@@ -111,6 +113,8 @@ payload:
- DOMAIN-SUFFIX,smoot.apple.com - DOMAIN-SUFFIX,smoot.apple.com
- DOMAIN-SUFFIX,sora.com - DOMAIN-SUFFIX,sora.com
- DOMAIN,sora-cdn.oaistatic.com - DOMAIN,sora-cdn.oaistatic.com
- DOMAIN-SUFFIX,trae.ai
- DOMAIN-SUFFIX,traeapi.us
- DOMAIN-SUFFIX,windsurf.com - DOMAIN-SUFFIX,windsurf.com
- DOMAIN-SUFFIX,codeium.com - DOMAIN-SUFFIX,codeium.com
- DOMAIN-SUFFIX,codeiumdata.com - DOMAIN-SUFFIX,codeiumdata.com

View File

@@ -14,3 +14,4 @@ payload:
- DOMAIN-SUFFIX,verizon.com - DOMAIN-SUFFIX,verizon.com
- DOMAIN-SUFFIX,avaloncommunities.com - DOMAIN-SUFFIX,avaloncommunities.com
- DOMAIN-SUFFIX,avalonbay.com - DOMAIN-SUFFIX,avalonbay.com
- DOMAIN-SUFFIX,kernelkeepers.com # ВПС

View File

@@ -567,14 +567,11 @@ payload:
- DOMAIN-KEYWORD,steambroadcast - DOMAIN-KEYWORD,steambroadcast
- DOMAIN-KEYWORD,steamstore - DOMAIN-KEYWORD,steamstore
- DOMAIN-KEYWORD,steamuserimages - DOMAIN-KEYWORD,steamuserimages
- IP-CIDR,103.4.115.248/32,no-resolve
- IP-CIDR,182.162.135.1/32,no-resolve - IP-CIDR,182.162.135.1/32,no-resolve
- IP-CIDR,185.60.112.157/32,no-resolve
- IP-CIDR,185.60.112.158/32,no-resolve
- IP-CIDR,210.242.235.6/32,no-resolve - IP-CIDR,210.242.235.6/32,no-resolve
- IP-CIDR,24.105.30.129/32,no-resolve
- IP-CIDR,54.94.196.47/32,no-resolve - IP-CIDR,54.94.196.47/32,no-resolve
- IP-CIDR,182.162.132.1/32,no-resolve - IP-CIDR,182.162.132.1/32,no-resolve
- IP-CIDR,103.4.114.233/32,no-resolve
- IP-CIDR,182.162.116.1/32,no-resolve - IP-CIDR,182.162.116.1/32,no-resolve
- IP-CIDR,202.9.67.59/32,no-resolve - IP-CIDR,202.9.67.59/32,no-resolve
- IP-CIDR,203.69.111.4/32,no-resolve - IP-CIDR,203.69.111.4/32,no-resolve
@@ -582,8 +579,8 @@ payload:
- IP-CIDR,35.192.0.0/12,no-resolve - IP-CIDR,35.192.0.0/12,no-resolve
- IP-CIDR,203.66.81.98/32,no-resolve - IP-CIDR,203.66.81.98/32,no-resolve
- IP-CIDR,211.234.110.1/32,no-resolve - IP-CIDR,211.234.110.1/32,no-resolve
- IP-CIDR,24.105.30.129/32,no-resolve
- IP-CIDR,54.207.107.12/32,no-resolve - IP-CIDR,54.207.107.12/32,no-resolve
- IP-CIDR,103.4.114.233/32,no-resolve
- IP-CIDR,202.9.67.254/32,no-resolve - IP-CIDR,202.9.67.254/32,no-resolve
- IP-CIDR,222.231.22.1/32,no-resolve - IP-CIDR,222.231.22.1/32,no-resolve
- IP-CIDR,13.210.25.233/32,no-resolve - IP-CIDR,13.210.25.233/32,no-resolve
@@ -608,7 +605,10 @@ payload:
- IP-CIDR,52.50.131.212/32,no-resolve - IP-CIDR,52.50.131.212/32,no-resolve
- IP-CIDR,54.207.168.84/32,no-resolve - IP-CIDR,54.207.168.84/32,no-resolve
- IP-CIDR,54.248.64.192/32,no-resolve - IP-CIDR,54.248.64.192/32,no-resolve
- IP-CIDR,103.4.115.248/32,no-resolve
- IP-CIDR,137.221.105.2/32,no-resolve - IP-CIDR,137.221.105.2/32,no-resolve
- IP-CIDR,185.60.112.157/32,no-resolve
- IP-CIDR,185.60.112.158/32,no-resolve
- IP-CIDR,185.60.114.159/32,no-resolve - IP-CIDR,185.60.114.159/32,no-resolve
- IP-CIDR,210.71.148.11/32,no-resolve - IP-CIDR,210.71.148.11/32,no-resolve
- IP-CIDR,211.115.104.1/32,no-resolve - IP-CIDR,211.115.104.1/32,no-resolve

View File

@@ -150,10 +150,12 @@ payload:
- DOMAIN-SUFFIX,www.all4nothin.net.php - DOMAIN-SUFFIX,www.all4nothin.net.php
- DOMAIN-SUFFIX,www.wareztorrent.com - DOMAIN-SUFFIX,www.wareztorrent.com
# 🤖 Arcadia Servitor: Fetched 269 records from https://github.com/blackmatrix7/ios_rule_script/raw/refs/heads/master/rule/Clash/PrivateTracker/PrivateTracker_No_Resolve.yaml # 🤖 Arcadia Servitor: Fetched 263 records from https://github.com/blackmatrix7/ios_rule_script/raw/refs/heads/master/rule/Clash/PrivateTracker/PrivateTracker_No_Resolve.yaml
# ------------------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------------------
- DOMAIN,004430.xyz
- DOMAIN,0d.kebhana.mx - DOMAIN,0d.kebhana.mx
# - DOMAIN,1337.abcvg.info 🤖AS: Duplicated # - DOMAIN,1337.abcvg.info 🤖AS: Duplicated
# - DOMAIN,6ahddutb1ucc3cp.ru 🤖AS: Duplicated
- DOMAIN,aboutbeautifulgallopinghorsesinthegreenpasture.online - DOMAIN,aboutbeautifulgallopinghorsesinthegreenpasture.online
- DOMAIN,admin.52ywp.com - DOMAIN,admin.52ywp.com
- DOMAIN,bandito.byterunner.io - DOMAIN,bandito.byterunner.io
@@ -165,63 +167,62 @@ payload:
- DOMAIN,buny.uk - DOMAIN,buny.uk
- DOMAIN,bvarf.tracker.sh - DOMAIN,bvarf.tracker.sh
- DOMAIN,cny.fan - DOMAIN,cny.fan
- DOMAIN,colibri.parrot.run
# - DOMAIN,d40969.acod.regrucolo.ru 🤖AS: Duplicated # - DOMAIN,d40969.acod.regrucolo.ru 🤖AS: Duplicated
# - DOMAIN,evan.im 🤖AS: Duplicated # - DOMAIN,evan.im 🤖AS: Duplicated
# - DOMAIN,exodus.desync.com 🤖AS: Duplicated # - DOMAIN,exodus.desync.com 🤖AS: Duplicated
- DOMAIN,explodie.org - DOMAIN,explodie.org
# - DOMAIN,extracker.dahrkael.net 🤖AS: Duplicated - DOMAIN,ftp.pet
- DOMAIN,home.yxgz.club - DOMAIN,http1.torrust-tracker-demo.com
- DOMAIN,ibksturm.synology.me
# - DOMAIN,ipv4.rer.lol 🤖AS: Duplicated
- DOMAIN,ipv4announce.sktorrent.eu - DOMAIN,ipv4announce.sktorrent.eu
# - DOMAIN,ipv6.rer.lol 🤖AS: Duplicated
- DOMAIN,jvavav.com
- DOMAIN,leet-tracker.moe - DOMAIN,leet-tracker.moe
- DOMAIN,lucke.fenesisu.moe - DOMAIN,lucke.fenesisu.moe
# - DOMAIN,martin-gebhardt.eu 🤖AS: Duplicated # - DOMAIN,martin-gebhardt.eu 🤖AS: Duplicated
- DOMAIN,navegador.click - DOMAIN,node01.madtia.cc
- DOMAIN,node01.torrentonline.cc
- DOMAIN,node01.trackerstatus.live - DOMAIN,node01.trackerstatus.live
- DOMAIN,node02.madtia.cc
- DOMAIN,ns575949.ip-51-222-82.net - DOMAIN,ns575949.ip-51-222-82.net
# - DOMAIN,nyaa.tracker.wf 🤖AS: Duplicated # - DOMAIN,nyaa.tracker.wf 🤖AS: Duplicated
- DOMAIN,open.acgtracker.com - DOMAIN,open.acgtracker.com
# - DOMAIN,open.demonii.com 🤖AS: Duplicated # - DOMAIN,open.demonii.com 🤖AS: Duplicated
- DOMAIN,open.demonoid.ch
- DOMAIN,open.dstud.io - DOMAIN,open.dstud.io
# - DOMAIN,open.stealth.si 🤖AS: Duplicated # - DOMAIN,open.stealth.si 🤖AS: Duplicated
# - DOMAIN,open.trackerlist.xyz 🤖AS: Duplicated # - DOMAIN,open.trackerlist.xyz 🤖AS: Duplicated
# - DOMAIN,opentor.org 🤖AS: Duplicated # - DOMAIN,opentor.org 🤖AS: Duplicated
- DOMAIN,opentracker.8880085.xyz
# - DOMAIN,opentracker.io 🤖AS: Duplicated # - DOMAIN,opentracker.io 🤖AS: Duplicated
# - DOMAIN,p4p.arenabg.com 🤖AS: Duplicated # - DOMAIN,p4p.arenabg.com 🤖AS: Duplicated
# - DOMAIN,pybittrack.retiolus.net 🤖AS: Duplicated
- DOMAIN,rekcart.duckdns.org - DOMAIN,rekcart.duckdns.org
# - DOMAIN,retracker.hotplug.ru 🤖AS: Duplicated # - DOMAIN,retracker.hotplug.ru 🤖AS: Duplicated
# - DOMAIN,retracker.lanta.me 🤖AS: Duplicated
- DOMAIN,retracker.spark-rostov.ru - DOMAIN,retracker.spark-rostov.ru
- DOMAIN,seeders-paradise.org - DOMAIN,seedpeer.net
- DOMAIN,servandroidkino.ru
- DOMAIN,shahidrazi.online - DOMAIN,shahidrazi.online
- DOMAIN,speedtest.iperson.xyz
# - DOMAIN,t.213891.xyz 🤖AS: Duplicated # - DOMAIN,t.213891.xyz 🤖AS: Duplicated
- DOMAIN,t.overflow.biz - DOMAIN,t.overflow.biz
# - DOMAIN,torrent.hificode.in 🤖AS: Duplicated
- DOMAIN,torrent.tracker.durukanbal.com - DOMAIN,torrent.tracker.durukanbal.com
- DOMAIN,torrentclub.online - DOMAIN,torrentclub.online
# - DOMAIN,torrentsmd.com 🤖AS: Duplicated # - DOMAIN,torrentsmd.com 🤖AS: Duplicated
- DOMAIN,torrentvpn.club
# - DOMAIN,tr.abiir.top 🤖AS: Duplicated # - DOMAIN,tr.abiir.top 🤖AS: Duplicated
- DOMAIN,tr.cili001.com
- DOMAIN,tr.highstar.shop - DOMAIN,tr.highstar.shop
- DOMAIN,tr.kxmp.cf - DOMAIN,tr.kxmp.cf
# - DOMAIN,tr.nyacat.pw 🤖AS: Duplicated # - DOMAIN,tr.nyacat.pw 🤖AS: Duplicated
- DOMAIN,tr.zukizuki.org
# - DOMAIN,tr4ck3r.duckdns.org 🤖AS: Duplicated # - DOMAIN,tr4ck3r.duckdns.org 🤖AS: Duplicated
# - DOMAIN,tracker-udp.gbitt.info 🤖AS: Duplicated # - DOMAIN,tracker-udp.gbitt.info 🤖AS: Duplicated
- DOMAIN,tracker-zhuqiy.xn--1r3au8b.space
- DOMAIN,tracker.004430.xyz
- DOMAIN,tracker.1h.is - DOMAIN,tracker.1h.is
- DOMAIN,tracker.alaskantf.com - DOMAIN,tracker.7471.top
- DOMAIN,tracker.bittor.pw - DOMAIN,tracker.bittor.pw
- DOMAIN,tracker.bluefrog.pw - DOMAIN,tracker.bluefrog.pw
- DOMAIN,tracker.breizh.pm - DOMAIN,tracker.breizh.pm
- DOMAIN,tracker.bt-hash.com - DOMAIN,tracker.bt-hash.com
- DOMAIN,tracker.bt4g.com - DOMAIN,tracker.bt4g.com
- DOMAIN,tracker.cloudbase.store
- DOMAIN,tracker.corpscorp.online - DOMAIN,tracker.corpscorp.online
- DOMAIN,tracker.cutie.dating
- DOMAIN,tracker.darkness.services - DOMAIN,tracker.darkness.services
- DOMAIN,tracker.ddunlimited.net - DOMAIN,tracker.ddunlimited.net
- DOMAIN,tracker.dhitechnical.com - DOMAIN,tracker.dhitechnical.com
@@ -229,68 +230,61 @@ payload:
- DOMAIN,tracker.dler.org - DOMAIN,tracker.dler.org
- DOMAIN,tracker.dmcomic.org - DOMAIN,tracker.dmcomic.org
- DOMAIN,tracker.ducks.party - DOMAIN,tracker.ducks.party
- DOMAIN,tracker.exe.in.th
- DOMAIN,tracker.filemail.com - DOMAIN,tracker.filemail.com
- DOMAIN,tracker.flatuslifir.is - DOMAIN,tracker.flatuslifir.is
# - DOMAIN,tracker.fnix.net 🤖AS: Duplicated # - DOMAIN,tracker.fnix.net 🤖AS: Duplicated
# - DOMAIN,tracker.foreverpirates.co 🤖AS: Duplicated
- DOMAIN,tracker.gcrenwp.top
- DOMAIN,tracker.ghostchu-services.top - DOMAIN,tracker.ghostchu-services.top
# - DOMAIN,tracker.gmi.gd 🤖AS: Duplicated # - DOMAIN,tracker.gmi.gd 🤖AS: Duplicated
- DOMAIN,tracker.huadongblower.xyz
- DOMAIN,tracker.iochimari.moe
- DOMAIN,tracker.iperson.xyz - DOMAIN,tracker.iperson.xyz
- DOMAIN,tracker.ipv6tracker.org - DOMAIN,tracker.ipv6tracker.org
# - DOMAIN,tracker.ipv6tracker.ru 🤖AS: Duplicated
- DOMAIN,tracker.ixuexi.click - DOMAIN,tracker.ixuexi.click
# - DOMAIN,tracker.kuroy.me 🤖AS: Duplicated # - DOMAIN,tracker.kuroy.me 🤖AS: Duplicated
- DOMAIN,tracker.manager.v6.navy - DOMAIN,tracker.manager.v6.navy
- DOMAIN,tracker.memelords.xyz
# - DOMAIN,tracker.moeblog.cn 🤖AS: Duplicated # - DOMAIN,tracker.moeblog.cn 🤖AS: Duplicated
- DOMAIN,tracker.moxing.party - DOMAIN,tracker.moeking.me
# - DOMAIN,tracker.mywaifu.best 🤖AS: Duplicated # - DOMAIN,tracker.mywaifu.best 🤖AS: Duplicated
- DOMAIN,tracker.novy.vip - DOMAIN,tracker.nekomi.cn
- DOMAIN,tracker.nyaa.vc
- DOMAIN,tracker.opentorrent.top - DOMAIN,tracker.opentorrent.top
# - DOMAIN,tracker.opentrackr.org 🤖AS: Duplicated # - DOMAIN,tracker.opentrackr.org 🤖AS: Duplicated
# - DOMAIN,tracker.openwebtorrent.com 🤖AS: Duplicated # - DOMAIN,tracker.openwebtorrent.com 🤖AS: Duplicated
- DOMAIN,tracker.pcfreetime.com
- DOMAIN,tracker.playground.ru - DOMAIN,tracker.playground.ru
# - DOMAIN,tracker.plx.im 🤖AS: Duplicated # - DOMAIN,tracker.plx.im 🤖AS: Duplicated
- DOMAIN,tracker.pmman.tech
- DOMAIN,tracker.qingwapt.org - DOMAIN,tracker.qingwapt.org
# - DOMAIN,tracker.qu.ax 🤖AS: Duplicated # - DOMAIN,tracker.qu.ax 🤖AS: Duplicated
# - DOMAIN,tracker.renfei.net 🤖AS: Duplicated # - DOMAIN,tracker.renfei.net 🤖AS: Duplicated
- DOMAIN,tracker.riverarmy.xyz
- DOMAIN,tracker.sbsub.com
- DOMAIN,tracker.sharebro.in
# - DOMAIN,tracker.skyts.net 🤖AS: Duplicated # - DOMAIN,tracker.skyts.net 🤖AS: Duplicated
# - DOMAIN,tracker.srv00.com 🤖AS: Duplicated # - DOMAIN,tracker.srv00.com 🤖AS: Duplicated
- DOMAIN,tracker.startwork.cv
- DOMAIN,tracker.t-1.org - DOMAIN,tracker.t-1.org
- DOMAIN,tracker.theoks.net - DOMAIN,tracker.theoks.net
# - DOMAIN,tracker.therarbg.to 🤖AS: Duplicated # - DOMAIN,tracker.therarbg.to 🤖AS: Duplicated
# - DOMAIN,tracker.torrent.eu.org 🤖AS: Duplicated # - DOMAIN,tracker.torrent.eu.org 🤖AS: Duplicated
- DOMAIN,tracker.torrust-demo.com - DOMAIN,tracker.torrust-demo.com
- DOMAIN,tracker.tritan.gg
# - DOMAIN,tracker.tryhackx.org 🤖AS: Duplicated # - DOMAIN,tracker.tryhackx.org 🤖AS: Duplicated
# - DOMAIN,tracker.tvunderground.org.ru 🤖AS: Duplicated # - DOMAIN,tracker.tvunderground.org.ru 🤖AS: Duplicated
- DOMAIN,tracker.vanitycore.co - DOMAIN,tracker.vanitycore.co
- DOMAIN,tracker.waaa.moe - DOMAIN,tracker.waaa.moe
- DOMAIN,tracker.xiaoduola.xyz # - DOMAIN,tracker.wepzone.net 🤖AS: Duplicated
- DOMAIN,tracker.wildkat.net
- DOMAIN,tracker.xn--djrq4gl4hvoi.top - DOMAIN,tracker.xn--djrq4gl4hvoi.top
- DOMAIN,tracker.zhuqiy.com - DOMAIN,tracker.yggleak.top
# - DOMAIN,tracker.zupix.online 🤖AS: Duplicated
# - DOMAIN,tracker1.520.jp 🤖AS: Duplicated # - DOMAIN,tracker1.520.jp 🤖AS: Duplicated
- DOMAIN,tracker1.itzmx.com
# - DOMAIN,tracker2.dler.org 🤖AS: Duplicated # - DOMAIN,tracker2.dler.org 🤖AS: Duplicated
- DOMAIN,tracker2.itzmx.com - DOMAIN,tracker2.itzmx.com
- DOMAIN,tracker3.ctix.cn
- DOMAIN,tracker3.itzmx.com - DOMAIN,tracker3.itzmx.com
- DOMAIN,tracker4.itzmx.com - DOMAIN,tracker4.itzmx.com
- DOMAIN,tracker810.xyz - DOMAIN,tracker810.xyz
- DOMAIN,uabits.today - DOMAIN,uabits.today
# - DOMAIN,udp.tracker.projectk.org 🤖AS: Duplicated # - DOMAIN,udp.tracker.projectk.org 🤖AS: Duplicated
- DOMAIN,udp1.torrust-tracker-demo.com
- DOMAIN,utracker.ghostchu-services.top - DOMAIN,utracker.ghostchu-services.top
- DOMAIN,www.all4nothin.net # - DOMAIN,wepzone.net 🤖AS: Duplicated
- DOMAIN,www.genesis-sp.org - DOMAIN,www.genesis-sp.org
- DOMAIN,www.torrentsnipe.info - DOMAIN,www.nartlof.com
# - DOMAIN,www.wareztorrent.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,1ptba.com - DOMAIN-SUFFIX,1ptba.com
- DOMAIN-SUFFIX,2xfree.org - DOMAIN-SUFFIX,2xfree.org
- DOMAIN-SUFFIX,52pt.site - DOMAIN-SUFFIX,52pt.site

View File

@@ -68,7 +68,7 @@ payload:
- IP-CIDR,63.92.224.0/19 - IP-CIDR,63.92.224.0/19
- IP-CIDR,65.199.22.0/23 - IP-CIDR,65.199.22.0/23
# 🤖 Arcadia Servitor: Fetched 1592 records from https://github.com/blackmatrix7/ios_rule_script/raw/refs/heads/master/rule/Clash/Apple/Apple_Classical_No_Resolve.yaml # 🤖 Arcadia Servitor: Fetched 1593 records from https://github.com/blackmatrix7/ios_rule_script/raw/refs/heads/master/rule/Clash/Apple/Apple_Classical_No_Resolve.yaml
# ------------------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------------------
- DOMAIN,apple-events.akamaized.net - DOMAIN,apple-events.akamaized.net
- DOMAIN,appleworldwidedeveloper.hb-api.omtrdc.net - DOMAIN,appleworldwidedeveloper.hb-api.omtrdc.net
@@ -1207,6 +1207,7 @@ payload:
- DOMAIN-SUFFIX,app-store.wang - DOMAIN-SUFFIX,app-store.wang
- DOMAIN-SUFFIX,appe-store.com - DOMAIN-SUFFIX,appe-store.com
- DOMAIN-SUFFIX,apple-appstore.cn - DOMAIN-SUFFIX,apple-appstore.cn
- DOMAIN-SUFFIX,apple-icloud.cn
- DOMAIN-SUFFIX,apple-itunes.cn - DOMAIN-SUFFIX,apple-itunes.cn
- DOMAIN-SUFFIX,apple-pay.com - DOMAIN-SUFFIX,apple-pay.com
- DOMAIN-SUFFIX,apple-pay.rs - DOMAIN-SUFFIX,apple-pay.rs
@@ -1217,6 +1218,7 @@ payload:
- DOMAIN-SUFFIX,appleappstore.tv - DOMAIN-SUFFIX,appleappstore.tv
- DOMAIN-SUFFIX,appledarwin.com - DOMAIN-SUFFIX,appledarwin.com
- DOMAIN-SUFFIX,appledarwin.net - DOMAIN-SUFFIX,appledarwin.net
- DOMAIN-SUFFIX,appleicloud.cn
- DOMAIN-SUFFIX,appleid-applemx.com - DOMAIN-SUFFIX,appleid-applemx.com
- DOMAIN-SUFFIX,appleid-applemx.us - DOMAIN-SUFFIX,appleid-applemx.us
- DOMAIN-SUFFIX,appleid-iclou.com - DOMAIN-SUFFIX,appleid-iclou.com
@@ -1225,6 +1227,7 @@ payload:
- DOMAIN-SUFFIX,appleid.com - DOMAIN-SUFFIX,appleid.com
- DOMAIN-SUFFIX,appleid.hamburg - DOMAIN-SUFFIX,appleid.hamburg
- DOMAIN-SUFFIX,appleid.hk - DOMAIN-SUFFIX,appleid.hk
- DOMAIN-SUFFIX,applemx-icloud.com
- DOMAIN-SUFFIX,appleone.audio - DOMAIN-SUFFIX,appleone.audio
- DOMAIN-SUFFIX,appleone.blog - DOMAIN-SUFFIX,appleone.blog
- DOMAIN-SUFFIX,appleone.chat - DOMAIN-SUFFIX,appleone.chat
@@ -1260,6 +1263,7 @@ payload:
- DOMAIN-SUFFIX,applepaysupplies.tv - DOMAIN-SUFFIX,applepaysupplies.tv
- DOMAIN-SUFFIX,applewallet.com - DOMAIN-SUFFIX,applewallet.com
- DOMAIN-SUFFIX,applewallet.tv - DOMAIN-SUFFIX,applewallet.tv
# - DOMAIN-SUFFIX,appsto.re 🤖AS: Duplicated
- DOMAIN-SUFFIX,appstore.co.id - DOMAIN-SUFFIX,appstore.co.id
- DOMAIN-SUFFIX,appstore.com - DOMAIN-SUFFIX,appstore.com
- DOMAIN-SUFFIX,appstore.hk - DOMAIN-SUFFIX,appstore.hk
@@ -1271,7 +1275,50 @@ payload:
- DOMAIN-SUFFIX,darwinsource.com - DOMAIN-SUFFIX,darwinsource.com
- DOMAIN-SUFFIX,darwinsource.org - DOMAIN-SUFFIX,darwinsource.org
- DOMAIN-SUFFIX,darwinsourcecode.com - DOMAIN-SUFFIX,darwinsourcecode.com
- DOMAIN-SUFFIX,icloud-apple.cn
# - DOMAIN-SUFFIX,icloud-content.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,icloud-isupport.com
- DOMAIN-SUFFIX,icloud-sandbox.com
- DOMAIN-SUFFIX,icloud.ch
# - DOMAIN-SUFFIX,icloud.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,icloud.com.cn
- DOMAIN-SUFFIX,icloud.de
- DOMAIN-SUFFIX,icloud.ee
- DOMAIN-SUFFIX,icloud.fi
- DOMAIN-SUFFIX,icloud.fr
- DOMAIN-SUFFIX,icloud.hu
- DOMAIN-SUFFIX,icloud.ie
- DOMAIN-SUFFIX,icloud.is
- DOMAIN-SUFFIX,icloud.jp
- DOMAIN-SUFFIX,icloud.lv
- DOMAIN-SUFFIX,icloud.net.cn
- DOMAIN-SUFFIX,icloud.om
- DOMAIN-SUFFIX,icloud.org
- DOMAIN-SUFFIX,icloud.pt
- DOMAIN-SUFFIX,icloud.ro
- DOMAIN-SUFFIX,icloud.se
- DOMAIN-SUFFIX,icloud.si
- DOMAIN-SUFFIX,icloud.sk
- DOMAIN-SUFFIX,icloud.vn
- DOMAIN-SUFFIX,icloudads.net
- DOMAIN-SUFFIX,icloudapple.cn
- DOMAIN-SUFFIX,icloudbox.net
- DOMAIN-SUFFIX,icloudbrowser.net
- DOMAIN-SUFFIX,icloude.com
- DOMAIN-SUFFIX,icloudhome.com
- DOMAIN-SUFFIX,icloudmail.net
- DOMAIN-SUFFIX,icloudmusic.net
- DOMAIN-SUFFIX,icloudnet.net
- DOMAIN-SUFFIX,icloudo.com
- DOMAIN-SUFFIX,icloudo.de
- DOMAIN-SUFFIX,icloudo.net
- DOMAIN-SUFFIX,icloudos.de
- DOMAIN-SUFFIX,icloudos.net
- DOMAIN-SUFFIX,icloudpay.net
- DOMAIN-SUFFIX,icloudsecure.net
- DOMAIN-SUFFIX,icloudsetup.com
- DOMAIN-SUFFIX,ids-apple.com - DOMAIN-SUFFIX,ids-apple.com
- DOMAIN-SUFFIX,ios-icloud.com
- DOMAIN-SUFFIX,itun.es - DOMAIN-SUFFIX,itun.es
- DOMAIN-SUFFIX,itunbes.com - DOMAIN-SUFFIX,itunbes.com
- DOMAIN-SUFFIX,ituneas.com - DOMAIN-SUFFIX,ituneas.com
@@ -1305,8 +1352,12 @@ payload:
- DOMAIN-SUFFIX,itunesu.com - DOMAIN-SUFFIX,itunesu.com
- DOMAIN-SUFFIX,itunesu.net - DOMAIN-SUFFIX,itunesu.net
- DOMAIN-SUFFIX,iutunes.com - DOMAIN-SUFFIX,iutunes.com
# - DOMAIN-SUFFIX,me.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,myappleid.com - DOMAIN-SUFFIX,myappleid.com
- DOMAIN-SUFFIX,myicloud.net
- DOMAIN-SUFFIX,mylcloud.net
- DOMAIN-SUFFIX,tvappstore.net - DOMAIN-SUFFIX,tvappstore.net
- DOMAIN-SUFFIX,wwwicloud.com
- DOMAIN-SUFFIX,wwwitunes.com - DOMAIN-SUFFIX,wwwitunes.com
- DOMAIN-SUFFIX,aplestore.com - DOMAIN-SUFFIX,aplestore.com
- DOMAIN-SUFFIX,aplleipods.com - DOMAIN-SUFFIX,aplleipods.com
@@ -1573,62 +1624,12 @@ payload:
- DOMAIN-SUFFIX,musickit.net - DOMAIN-SUFFIX,musickit.net
- DOMAIN-SUFFIX,wwwapplemusic.com - DOMAIN-SUFFIX,wwwapplemusic.com
- DOMAIN-SUFFIX,apple-dns.net - DOMAIN-SUFFIX,apple-dns.net
# - DOMAIN-SUFFIX,appsto.re 🤖AS: Duplicated
- DOMAIN-SUFFIX,digicert.com - DOMAIN-SUFFIX,digicert.com
# - DOMAIN-SUFFIX,apple-cloudkit.com 🤖AS: Duplicated # - DOMAIN-SUFFIX,apple-cloudkit.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,apple-icloud.cn
- DOMAIN-SUFFIX,apple-livephotoskit.com - DOMAIN-SUFFIX,apple-livephotoskit.com
- DOMAIN-SUFFIX,appleicloud.cn
- DOMAIN-SUFFIX,applemx-icloud.com
- DOMAIN-SUFFIX,apzones.com - DOMAIN-SUFFIX,apzones.com
# - DOMAIN-SUFFIX,cdn-apple.com 🤖AS: Duplicated # - DOMAIN-SUFFIX,cdn-apple.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,icloud-apple.cn - DOMAIN-SUFFIX,icloud.cn
# - DOMAIN-SUFFIX,icloud-content.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,icloud-isupport.com
- DOMAIN-SUFFIX,icloud-sandbox.com
- DOMAIN-SUFFIX,icloud.ch
# - DOMAIN-SUFFIX,icloud.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,icloud.com.cn
- DOMAIN-SUFFIX,icloud.de
- DOMAIN-SUFFIX,icloud.ee
- DOMAIN-SUFFIX,icloud.fi
- DOMAIN-SUFFIX,icloud.fr
- DOMAIN-SUFFIX,icloud.hu
- DOMAIN-SUFFIX,icloud.ie
- DOMAIN-SUFFIX,icloud.is
- DOMAIN-SUFFIX,icloud.jp
- DOMAIN-SUFFIX,icloud.lv
- DOMAIN-SUFFIX,icloud.net.cn
- DOMAIN-SUFFIX,icloud.om
- DOMAIN-SUFFIX,icloud.org
- DOMAIN-SUFFIX,icloud.pt
- DOMAIN-SUFFIX,icloud.ro
- DOMAIN-SUFFIX,icloud.se
- DOMAIN-SUFFIX,icloud.si
- DOMAIN-SUFFIX,icloud.sk
- DOMAIN-SUFFIX,icloud.vn
- DOMAIN-SUFFIX,icloudads.net
- DOMAIN-SUFFIX,icloudapple.cn
- DOMAIN-SUFFIX,icloudbox.net
- DOMAIN-SUFFIX,icloudbrowser.net
- DOMAIN-SUFFIX,icloude.com
- DOMAIN-SUFFIX,icloudhome.com
- DOMAIN-SUFFIX,icloudmail.net
- DOMAIN-SUFFIX,icloudmusic.net
- DOMAIN-SUFFIX,icloudnet.net
- DOMAIN-SUFFIX,icloudo.com
- DOMAIN-SUFFIX,icloudo.de
- DOMAIN-SUFFIX,icloudo.net
- DOMAIN-SUFFIX,icloudos.de
- DOMAIN-SUFFIX,icloudos.net
- DOMAIN-SUFFIX,icloudpay.net
- DOMAIN-SUFFIX,icloudsecure.net
- DOMAIN-SUFFIX,icloudsetup.com
- DOMAIN-SUFFIX,ios-icloud.com
# - DOMAIN-SUFFIX,me.com 🤖AS: Duplicated
- DOMAIN-SUFFIX,myicloud.net
- DOMAIN-SUFFIX,mylcloud.net
- DOMAIN-SUFFIX,wwwicloud.com
# - DOMAIN-KEYWORD,apple-support.akadns.net 🤖AS: Duplicated # - DOMAIN-KEYWORD,apple-support.akadns.net 🤖AS: Duplicated
# - DOMAIN-KEYWORD,apple.com.akadns.net 🤖AS: Duplicated # - DOMAIN-KEYWORD,apple.com.akadns.net 🤖AS: Duplicated
# - DOMAIN-KEYWORD,apple.com.edgekey.net 🤖AS: Duplicated # - DOMAIN-KEYWORD,apple.com.edgekey.net 🤖AS: Duplicated

View File

@@ -177,25 +177,25 @@ payload:
# - DOMAIN-SUFFIX,worldofwarcraft.com 🤖AS: Duplicated # - DOMAIN-SUFFIX,worldofwarcraft.com 🤖AS: Duplicated
# - DOMAIN-SUFFIX,wow.nosdn.127.net 🤖AS: Duplicated # - DOMAIN-SUFFIX,wow.nosdn.127.net 🤖AS: Duplicated
# - DOMAIN-SUFFIX,wowchina.com 🤖AS: Duplicated # - DOMAIN-SUFFIX,wowchina.com 🤖AS: Duplicated
# - IP-CIDR,103.4.115.248/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,182.162.135.1/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,182.162.135.1/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,185.60.112.157/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,185.60.112.158/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,210.242.235.6/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,210.242.235.6/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,24.105.30.129/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,54.94.196.47/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,54.94.196.47/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,182.162.132.1/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,182.162.132.1/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,103.4.114.233/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,182.162.116.1/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,182.162.116.1/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,202.9.67.59/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,202.9.67.59/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,203.69.111.4/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,203.69.111.4/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,54.207.104.145/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,54.207.104.145/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,203.66.81.98/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,203.66.81.98/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,211.234.110.1/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,211.234.110.1/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,24.105.30.129/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,54.207.107.12/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,54.207.107.12/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,103.4.114.233/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,202.9.67.254/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,202.9.67.254/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,222.231.22.1/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,222.231.22.1/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,103.4.115.248/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,137.221.105.2/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,137.221.105.2/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,185.60.112.157/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,185.60.112.158/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,185.60.114.159/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,185.60.114.159/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,210.71.148.11/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,210.71.148.11/32,no-resolve 🤖AS: Duplicated
# - IP-CIDR,211.115.104.1/32,no-resolve 🤖AS: Duplicated # - IP-CIDR,211.115.104.1/32,no-resolve 🤖AS: Duplicated

View File

@@ -0,0 +1,86 @@
#!/bin/bash
# Целевой DNS-сервер для тестирования
RESOLVER="127.0.0.1"
# Тестовая выборка доменов (20 штук + 2 эталонных)
DOMAINS=(
# --- Эталонные домены для проверки DNSSEC ---
"sigok.verteiltesysteme.net" # Должен быть NOERROR
"sigfail.verteiltesysteme.net" # Должен быть SERVFAIL (Broken DNSSEC)
"dnssec-failed.org" # Должен быть SERVFAIL (Broken DNSSEC)
# --- Телеметрия и трекеры (часто блочат дешевые хостеры) ---
"fe3cr.delivery.mp.microsoft.com"
"telemetry.microsoft.com"
"tracking.miui.com"
"app-measurement.com"
"google-analytics.com"
"doubleclick.net"
# --- Торренты и пиратство (копирайт фильтры) ---
"rutracker.org"
"thepiratebay.org"
"1337x.to"
"yts.mx"
# --- Контент для взрослых (Child-protection фильтры) ---
"hui.com"
"pornhub.com"
"xvideos.com"
# --- Политика и соцсети (Государственные DPI / РКН) ---
"twitter.com"
"facebook.com"
"instagram.com"
"bbc.com"
"meduza.io"
)
echo -e "Starting DPI & DNSSEC interception test on resolver $RESOLVER..."
echo -e "Date: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
echo "--------------------------------------------------------------------------------"
printf "%-35s | %-20s | %-20s\n" "DOMAIN" "STATUS" "RESOLVED IP (FIRST)"
echo "--------------------------------------------------------------------------------"
for domain in "${DOMAINS[@]}"; do
# Запрашиваем A-запись с таймаутом в 2 секунды
output=$(dig @"$RESOLVER" "$domain" A +time=2 +tries=1)
# Парсим статус ответа (NOERROR, SERVFAIL, NXDOMAIN и т.д.)
status=$(echo "$output" | grep -oP 'status: \K[A-Z]+')
# Вытаскиваем первый попавшийся IP-адрес из секции ANSWER
ip=$(echo "$output" | grep -v '^;' | grep -w 'A' | awk '{print $5}' | head -n 1)
if [ -z "$status" ]; then
status="TIMEOUT"
fi
if [ -z "$ip" ]; then
ip="NONE"
fi
# Цветовая индикация и логика
if [ "$status" == "NOERROR" ]; then
if [[ "$ip" == "0.0.0.0" || "$ip" == "127.0.0.1" ]]; then
color_status="\e[31m$status (FAKE IP)\e[0m"
else
color_status="\e[32m$status\e[0m"
fi
elif [ "$status" == "SERVFAIL" ]; then
# SERVFAIL ожидаем только для этих двух доменов
if [[ "$domain" == "sigfail.verteiltesysteme.net" || "$domain" == "dnssec-failed.org" ]]; then
color_status="\e[32m$status (EXPECTED)\e[0m"
else
color_status="\e[31m$status (INTERCEPTED)\e[0m"
fi
else
color_status="\e[33m$status\e[0m"
fi
printf "%-35s | %-30b | %-20s\n" "$domain" "$color_status" "$ip"
done
echo "--------------------------------------------------------------------------------"
echo "Test completed."

View File

@@ -29,12 +29,12 @@ NETBIRD_SETUP_KEY="7369BE4D-C485-4339-A7CA-C245FD95E857"
NETBIRD_MANAGEMENT_URL="https://webway.shamanlanding.org:443" NETBIRD_MANAGEMENT_URL="https://webway.shamanlanding.org:443"
# Mihomo Version (Alpha) # Mihomo Version (Alpha)
MIHOMO_URL="https://github.com/vernesong/mihomo/releases/download/Prerelease-Alpha/mihomo-linux-amd64-alpha-smart-ec7f445.gz" MIHOMO_URL="https://github.com/vernesong/mihomo/releases/download/Prerelease-Alpha/mihomo-linux-amd64-alpha-smart-26a9e08.gz"
# Remote Resources # Remote Resources
REPO_BASE="https://gitea.shamanlanding.org/DaTekShaman/clash-rules/raw/branch/main" REPO_BASE="https://gitea.shamanlanding.org/DaTekShaman/clash-rules/raw/branch/main"
URL_CONFIG_MIHOMO="${REPO_BASE}/config-clash/cadian/cadian.current.yaml" URL_CONFIG_MIHOMO="${REPO_BASE}/config-clash/solar/solar.yaml"
URL_SCRIPT_IPTABLES="${REPO_BASE}/scripts/iptables-mihomo-setup.sh" URL_SCRIPT_IPTABLES="${REPO_BASE}/scripts/warpgates/iptables-mihomo-setup-alpine-mark2.sh"
URL_INIT_MIHOMO="${REPO_BASE}/init-scripts/openrc/mihomo" URL_INIT_MIHOMO="${REPO_BASE}/init-scripts/openrc/mihomo"
URL_INIT_IPTABLES="${REPO_BASE}/init-scripts/openrc/mihomo-iptables" URL_INIT_IPTABLES="${REPO_BASE}/init-scripts/openrc/mihomo-iptables"
@@ -50,7 +50,7 @@ UI_DIR="/etc/mihomo/ui"
# ========================================== # ==========================================
echo ">>> [1/8] Updating system and installing dependencies..." echo ">>> [1/8] Updating system and installing dependencies..."
# Включаем community репозитории (обычно там лежит gcompat и прочее) # Включаем community репозитории (обычно там лежит gcompat и прочее)
sed -i 's/^#//g' /etc/apk/repositories sed -i '/v[0-9]\.[0-9]*\/community/s/^#//' /etc/apk/repositories
apk update apk update
apk add bash curl wget ca-certificates tar iptables ip6tables jq coreutils libcap bind-tools nano openrc openssh sudo shadow apk add bash curl wget ca-certificates tar iptables ip6tables jq coreutils libcap bind-tools nano openrc openssh sudo shadow
@@ -67,6 +67,7 @@ net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.wt0.rp_filter=0 net.ipv4.conf.wt0.rp_filter=0
EOF EOF
sysctl -p /etc/sysctl.d/99-warpgate.conf sysctl -p /etc/sysctl.d/99-warpgate.conf
rc-update add sysctl boot
# ========================================== # ==========================================
# 3. NETBIRD INSTALLATION # 3. NETBIRD INSTALLATION

View File

@@ -0,0 +1,133 @@
#!/bin/bash
set -euo pipefail
# ----------------------------
# Config
# ----------------------------
MIHOMO_UID="mihomo"
REDIR_PORT="7892" # TCP Redirect
TPROXY_PORT="7893" # UDP/TCP TProxy
FW_MARK="0x1"
ROUTE_TABLE="100"
EXCLUDE_IFACES=("tun0")
INCLUDE_IFACES=("wt0" "eth1" "eth2")
# ----------------------------
# Helpers
# ----------------------------
ipt() { iptables "$@"; }
del_loop() {
local table=$1
local chain=$2
shift 2
local rule_args="$@"
while iptables -t "$table" -C "$chain" $rule_args 2>/dev/null; do
echo "Deleting from $table/$chain: $rule_args"
iptables -t "$table" -D "$chain" $rule_args
done
}
ensure_ip_rule() {
while ip rule list | grep -q "fwmark ${FW_MARK} lookup ${ROUTE_TABLE}"; do
ip rule del fwmark ${FW_MARK} lookup ${ROUTE_TABLE} || true
done
ip rule add fwmark ${FW_MARK} lookup ${ROUTE_TABLE}
ip route replace local 0.0.0.0/0 dev lo table ${ROUTE_TABLE}
}
# ----------------------------
# CLEANUP PHASE
# ----------------------------
echo "--- Cleaning up old rules (Robust Mode) ---"
del_loop nat OUTPUT -p tcp -m comment --comment "MIHOMO-JUMP" -j MIHOMO_REDIR
del_loop nat PREROUTING -i wt0 -p tcp -m comment --comment "MIHOMO-REDIRECT" -j REDIRECT --to-port "${REDIR_PORT}"
del_loop mangle PREROUTING -i wt0 -m comment --comment "MIHOMO-JUMP" -j MIHOMO_TPROXY
del_loop mangle OUTPUT -p tcp -m comment --comment "MIHOMO-MARK" -j MARK --set-mark "${FW_MARK}"
del_loop mangle OUTPUT -p udp -m comment --comment "MIHOMO-MARK" -j MARK --set-mark "${FW_MARK}"
for IFACE in "${EXCLUDE_IFACES[@]}"; do
del_loop mangle OUTPUT -o "${IFACE}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
del_loop mangle PREROUTING -i "${IFACE}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
del_loop nat OUTPUT -o "${IFACE}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
done
del_loop mangle OUTPUT -m owner --uid-owner "${MIHOMO_UID}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
del_loop nat OUTPUT -m owner --uid-owner "${MIHOMO_UID}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
ipt -t mangle -F MIHOMO_TPROXY 2>/dev/null || true
ipt -t mangle -X MIHOMO_TPROXY 2>/dev/null || true
ipt -t nat -F MIHOMO_REDIR 2>/dev/null || true
ipt -t nat -X MIHOMO_REDIR 2>/dev/null || true
echo "--- Cleanup finished. Applying new rules ---"
# ----------------------------
# NAT (REDIRECT) - TCP
# ----------------------------
ipt -t nat -N MIHOMO_REDIR
# Exclusions for gateway's own traffic
ipt -t nat -A MIHOMO_REDIR -d 192.168.0.0/16 -j RETURN
ipt -t nat -A MIHOMO_REDIR -d 10.0.0.0/8 -j RETURN
ipt -t nat -A MIHOMO_REDIR -d 172.16.0.0/12 -j RETURN
ipt -t nat -A MIHOMO_REDIR -d 127.0.0.0/8 -j RETURN
ipt -t nat -A MIHOMO_REDIR -p tcp -j REDIRECT --to-ports "${REDIR_PORT}"
# Apply to OUTPUT (Local gateway traffic)
for IFACE in "${EXCLUDE_IFACES[@]}"; do
ipt -t nat -A OUTPUT -o "${IFACE}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
done
ipt -t nat -A OUTPUT -m owner --uid-owner "${MIHOMO_UID}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
ipt -t nat -A OUTPUT -p tcp -m comment --comment "MIHOMO-JUMP" -j MIHOMO_REDIR
# Apply to PREROUTING (wt0 Ingress) - Force Redir for NetBird (skips exclusions by design)
for IFACE in "${INCLUDE_IFACES[@]}"; do
if [ "$IFACE" = "wt0" ]; then
# wt0 (Netbird) пропускает исключения локальных подсетей по твоему дизайну
ipt -t nat -A PREROUTING -i "$IFACE" -p tcp -m comment --comment "MIHOMO-REDIRECT" -j REDIRECT --to-port "${REDIR_PORT}"
else
# LAN-трафик (eth1, eth2) должен прыгать в цепочку MIHOMO_REDIR для проверки исключений (192.168.x.x и т.д.)
ipt -t nat -A PREROUTING -i "$IFACE" -p tcp -m comment --comment "MIHOMO-JUMP-$IFACE" -j MIHOMO_REDIR
fi
done
# ----------------------------
# MANGLE (TPROXY) - UDP
# ----------------------------
ensure_ip_rule
ipt -t mangle -N MIHOMO_TPROXY
# Local exclusions: apply ONLY if traffic is NOT coming from NetBird (wt0)
ipt -t mangle -A MIHOMO_TPROXY ! -i wt0 -d 192.168.0.0/16 -j RETURN
ipt -t mangle -A MIHOMO_TPROXY ! -i wt0 -d 10.0.0.0/8 -j RETURN
ipt -t mangle -A MIHOMO_TPROXY ! -i wt0 -d 172.16.0.0/12 -j RETURN
ipt -t mangle -A MIHOMO_TPROXY ! -i wt0 -d 127.0.0.0/8 -j RETURN
# TProxy Targets (UDP only, TCP is handled by REDIRECT)
ipt -t mangle -A MIHOMO_TPROXY -p udp -j TPROXY --on-port "${TPROXY_PORT}" --tproxy-mark "${FW_MARK}/${FW_MARK}"
# Apply to OUTPUT (Local gateway traffic)
for IFACE in "${EXCLUDE_IFACES[@]}"; do
ipt -t mangle -A OUTPUT -o "${IFACE}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
done
ipt -t mangle -A OUTPUT -m owner --uid-owner "${MIHOMO_UID}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
# Mark local UDP packets
ipt -t mangle -A OUTPUT -p udp -m comment --comment "MIHOMO-MARK" -j MARK --set-mark "${FW_MARK}"
# Apply to PREROUTING (wt0 Ingress)
for IFACE in "${EXCLUDE_IFACES[@]}"; do
ipt -t mangle -A PREROUTING -i "${IFACE}" -m comment --comment "MIHOMO-EXCLUDE" -j RETURN
done
for IFACE in "${INCLUDE_IFACES[@]}"; do
ipt -t mangle -A PREROUTING -i "$IFACE" -m comment --comment "MIHOMO-JUMP-$IFACE" -j MIHOMO_TPROXY
done
echo "Done. Suboptimal hypervisor constraints bypassed successfully."

View File

@@ -0,0 +1,89 @@
#!/bin/bash
set -u
# ----------------------------
# Config
# ----------------------------
MIHOMO_UID="mihomo"
TPROXY_PORT="7893"
FW_MARK="0x1"
ROUTE_TABLE="100"
# Интерфейсы клиентов (откуда прилетают запросы)
LAN_IFACES=("wt0" "eth1" "eth2")
# Порты самого сервера, которые НЕ надо проксировать (Web UI, SSH)
LOCAL_PORTS="9090,22"
# ----------------------------
# Helpers
# ----------------------------
ipt() { iptables "$@"; }
cleanup_references() {
local chain=$1
iptables-save | grep "\-j $chain" | sed "s/^-A/-D/" | while read rule; do
iptables -t mangle $rule 2>/dev/null || true
done
}
ensure_ip_rule() {
# 1. Перехват трафика от клиентов в TProxy (то, что мы уже починили)
if ! ip rule list | grep -q "fwmark ${FW_MARK} lookup ${ROUTE_TABLE}"; then
ip rule add fwmark ${FW_MARK} lookup ${ROUTE_TABLE} pref 90
fi
if ! ip route show table ${ROUTE_TABLE} | grep -q "local default"; then
ip route add local 0.0.0.0/0 dev lo table ${ROUTE_TABLE}
fi
# 2. НОВОЕ: Выпуск трафика Mihomo в интернет в обход Netbird
if ! ip rule list | grep -q "fwmark 1337 lookup main"; then
ip rule add fwmark 1337 lookup main pref 80
fi
}
# ----------------------------
# 1. CLEANUP
# ----------------------------
echo "--- Cleaning up rules ---"
cleanup_references "MIHOMO_TPROXY"
ipt -t mangle -F MIHOMO_TPROXY 2>/dev/null || true
ipt -t mangle -X MIHOMO_TPROXY 2>/dev/null || true
# ----------------------------
# 2. SETUP
# ----------------------------
ensure_ip_rule
# --- CHAIN: PREROUTING (Для клиентов) ---
ipt -t mangle -N MIHOMO_TPROXY
# === 1. Исключения по Портам (CRITICAL FIX) ===
# Если стучатся в веб-морду или SSH - пропускаем мимо TProxy
ipt -t mangle -A MIHOMO_TPROXY -p tcp -m multiport --dports "${LOCAL_PORTS}" -j RETURN
# === 2. Исключения по IP (Bypass) ===
# RFC1918 Private Networks
ipt -t mangle -A MIHOMO_TPROXY -d 0.0.0.0/8 -j RETURN
ipt -t mangle -A MIHOMO_TPROXY -d 10.0.0.0/8 -j RETURN
ipt -t mangle -A MIHOMO_TPROXY -d 127.0.0.0/8 -j RETURN
ipt -t mangle -A MIHOMO_TPROXY -d 169.254.0.0/16 -j RETURN
ipt -t mangle -A MIHOMO_TPROXY -d 172.16.0.0/12 -j RETURN
ipt -t mangle -A MIHOMO_TPROXY -d 192.168.0.0/16 -j RETURN
# Multicast
ipt -t mangle -A MIHOMO_TPROXY -d 224.0.0.0/4 -j RETURN
ipt -t mangle -A MIHOMO_TPROXY -d 240.0.0.0/4 -j RETURN
# !!! NETBIRD / CGNAT (Fix for VPN access) !!!
ipt -t mangle -A MIHOMO_TPROXY -d 100.64.0.0/10 -j RETURN
# === 3. Заворачиваем в TProxy ===
ipt -t mangle -A MIHOMO_TPROXY -p tcp -j TPROXY --on-port "${TPROXY_PORT}" --tproxy-mark "${FW_MARK}"
ipt -t mangle -A MIHOMO_TPROXY -p udp -j TPROXY --on-port "${TPROXY_PORT}" --tproxy-mark "${FW_MARK}"
# ----------------------------
# 3. APPLY
# ----------------------------
for IFACE in "${LAN_IFACES[@]}"; do
echo "Adding TProxy rules for interface: $IFACE"
ipt -t mangle -A PREROUTING -i "$IFACE" -j MIHOMO_TPROXY
done

View File

@@ -0,0 +1,81 @@
#!/bin/sh
set -e
# Configuration
UI_URL="https://github.com/Zephyruso/zashboard/releases/latest/download/dist-cdn-fonts.zip"
BIN_DIR="/usr/local/bin"
UI_DIR="/etc/mihomo/ui/zashboard"
echo "[*] Resolving latest Alpha URL from vernesong/mihomo..."
CORE_URL=$(curl -sL "https://api.github.com/repos/vernesong/mihomo/releases/tags/Prerelease-Alpha" | grep -o 'https://[^"]*mihomo-linux-amd64-alpha-smart-[^"]*\.gz' | head -n 1)
if [ -z "$CORE_URL" ]; then
echo "[-] ERROR: Failed to resolve download URL."
exit 1
fi
echo "[+] Target URL: $CORE_URL"
# ==========================================
# ФАЗА 1: СЕТЕВЫЕ ОПЕРАЦИИ (пока жив DNS)
# ==========================================
echo "[*] Downloading Mihomo Core..."
curl -SLf -o /tmp/mihomo.gz "$CORE_URL"
if [ ! -s /tmp/mihomo.gz ]; then
echo "[-] ERROR: Downloaded core file is empty or missing!"
exit 1
fi
echo "[*] Downloading Zashboard UI..."
curl -SLf -o /tmp/zashboard.zip "$UI_URL"
if [ ! -s /tmp/zashboard.zip ]; then
echo "[-] ERROR: Downloaded UI file is empty or missing!"
exit 1
fi
# ==========================================
# ФАЗА 2: ЛОКАЛЬНЫЕ ОПЕРАЦИИ (остановка сервиса)
# ==========================================
echo "[*] Stopping mihomo service..."
rc-service mihomo stop
echo "[*] Unpacking and installing Mihomo Core..."
gzip -d -f /tmp/mihomo.gz
mv /tmp/mihomo "$BIN_DIR/mihomo"
chmod 755 "$BIN_DIR/mihomo"
chown root:root "$BIN_DIR/mihomo"
setcap 'cap_net_admin,cap_net_bind_service=+ep' "$BIN_DIR/mihomo"
echo "[*] Unpacking and installing Zashboard UI..."
# Создаем изолированную директорию для распаковки
mkdir -p /tmp/zash_temp
unzip -q -o /tmp/zashboard.zip -d /tmp/zash_temp/
# Динамически ищем, как GitHub назвал корневую папку внутри архива
EXTRACTED_DIR=$(find /tmp/zash_temp -mindepth 1 -maxdepth 1 -type d | head -n 1)
if [ -z "$EXTRACTED_DIR" ]; then
echo "[-] ERROR: Could not find extracted UI directory in the zip archive."
rc-service mihomo start
exit 1
fi
rm -rf "$UI_DIR"/*
# Копируем содержимое найденной папки
cp -r "$EXTRACTED_DIR"/* "$UI_DIR"/
chown -R root:root "$UI_DIR"
find "$UI_DIR" -type d -exec chmod 755 {} \;
find "$UI_DIR" -type f -exec chmod 644 {} \;
# Зачищаем следы
rm -rf /tmp/zashboard.zip /tmp/zash_temp
echo "[*] Starting mihomo service..."
rc-service mihomo start
echo "[+] Update completed successfully."