Refactor icon and country detection rules in external-proxies-sanitizer.js for improved clarity and maintainability

This commit is contained in:
2026-01-05 13:14:27 +03:00
parent 82e97b2f57
commit 8f535becc2

View File

@@ -43,20 +43,20 @@ const NOISE_PATTERNS = [
// 2) Keyword -> icon tags (if found in original name, icon is added; the keyword is removed from base name) // 2) Keyword -> icon tags (if found in original name, icon is added; the keyword is removed from base name)
const ICON_RULES = [ const ICON_RULES = [
{ regex: /TEST/gi, icon: "🧪" }, { regex: /TEST/gi, icon: "🧪" },
{ regex: /\b⚡Low Ping\b/gi, icon: "⚡️" }, { regex: uWordBoundaryGroup("Low Ping"), icon: "⚡️" },
{ regex: /\b⚡10 Gbit \b/gi, icon: "🛤️" }, { regex: uWordBoundaryGroup("10 Gbit"), icon: "🛤️" },
{ regex: /\bYT\b|\bRussia\b|\bРоссия\b/gi, icon: "📺" }, { regex: uWordBoundaryGroup("YT|Russia|Россия"), icon: "📺" },
{ regex: /\bIPv6\b/gi, icon: "6" }, { regex: uWordBoundaryGroup("IPv6"), icon: "🔷" },
{ regex: /\bGemini\b|\bAI Studio\b/gi, icon: "🤖" }, { regex: uWordBoundaryGroup("Gemini|AI Studio"), icon: "🤖" },
{ regex: /\bTorrent✅\b|Torrent|\bP2P\b|\bP2P-Torrents\b/gi, icon: "🧲" } { regex: uWordBoundaryGroup("Torrent|P2P|P2P-Torrents"), icon: "🧲" }
]; ];
// 3) Optional “network” tag rules based on NAME text (not $server.network) // 3) Optional “network” tag rules based on NAME text (not $server.network)
// (Useful if providers shove "BGP/IPLC" into the node name) // (Useful if providers shove "BGP/IPLC" into the node name)
const NAME_NETWORK_TAGS = [ const NAME_NETWORK_TAGS = [
{ regex: /\bIPLC\b/gi, tag: "🛰️" }, { regex: uWordBoundaryGroup("IPLC"), tag: "🛰️" },
{ regex: /\bBGP\b/gi, tag: "🧭" }, { regex: uWordBoundaryGroup("BGP"), tag: "🧭" },
{ regex: /\bAnycast\b/gi, tag: "🌍" } { regex: uWordBoundaryGroup("Anycast"), tag: "🌍" }
]; ];
// 4) Country detection rules by NAME (regex). First match wins (priority = lower is earlier) // 4) Country detection rules by NAME (regex). First match wins (priority = lower is earlier)
@@ -132,30 +132,114 @@ const ISO2_TO_ISO3 = {
// 6) Protocol icons (based on proxy.type) // 6) Protocol icons (based on proxy.type)
const PROTOCOL_ICONS = { const PROTOCOL_ICONS = {
ss: "🔒", ss: "",
ssr: "☂️", ssr: "",
vmess: "🪁", vmess: "",
vless: "🌌", vless: "",
trojan: "🐎", trojan: "",
http: "🌐", http: "",
socks5: "🧦", socks5: "",
snell: "🐌", snell: "",
wireguard: "🐲", wireguard: "",
hysteria: "🤪", hysteria: "",
hysteria2: "", hysteria2: "",
tuic: "🚅" tuic: ""
};
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: 5,
padChar: "0",
superscripts: {
"0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", "5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹",
},
}; };
/////////////////////// ///////////////////////
// HELPERS // HELPERS
/////////////////////// ///////////////////////
function normalizeToken(s) {
return String(s || "").trim().toLowerCase();
}
function uWordBoundaryGroup(inner) { function uWordBoundaryGroup(inner) {
// Match if surrounded by non-letter/non-digit (Unicode-aware) // Match if surrounded by non-letter/non-digit (Unicode-aware)
// We don't use lookbehind for max compatibility. // We don't use lookbehind for max compatibility.
return new RegExp(`(?:^|[^\\p{L}\\p{N}])(?:${inner})(?=$|[^\\p{L}\\p{N}])`, "iu"); return new RegExp(`(?:^|[^\\p{L}\\p{N}])(?:${inner})(?=$|[^\\p{L}\\p{N}])`, "iu");
} }
function portToSuperscript(port) {
let p = String(port ?? "").trim();
// keep only digits, because providers love putting garbage here
p = p.replace(/[^\d]/g, "");
if (!p) p = "0";
// 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.superscripts[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 = portToSuperscript(port);
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) { function isIPv4(str) {
if (typeof str !== "string") return false; if (typeof str !== "string") return false;
const m = str.match(/^(\d{1,3})(\.\d{1,3}){3}$/); const m = str.match(/^(\d{1,3})(\.\d{1,3}){3}$/);
@@ -253,7 +337,6 @@ function detectCountryByName(name) {
if (n.includes("🇺🇸")) return { iso3: "USA", flag: "🇺🇸", priority: 1, source: "flag" }; if (n.includes("🇺🇸")) return { iso3: "USA", flag: "🇺🇸", priority: 1, source: "flag" };
if (n.includes("🇻🇳")) return { iso3: "VNM", flag: "🇻🇳", priority: 1, source: "flag" }; if (n.includes("🇻🇳")) return { iso3: "VNM", flag: "🇻🇳", priority: 1, source: "flag" };
const sorted = COUNTRY_RULES.slice().sort((a, b) => a.priority - b.priority); const sorted = COUNTRY_RULES.slice().sort((a, b) => a.priority - b.priority);
for (const c of sorted) { for (const c of sorted) {
if (c.regex.test(n)) return { iso3: c.iso3, flag: c.flag, priority: c.priority, source: "name" }; if (c.regex.test(n)) return { iso3: c.iso3, flag: c.flag, priority: c.priority, source: "name" };
@@ -316,13 +399,10 @@ function operator(proxies, targetPlatform, utils) {
const country = byName || byGeo || { iso3: "UNK", flag: "🏴‍☠️", priority: 9999, source: "fallback" }; const country = byName || byGeo || { iso3: "UNK", flag: "🏴‍☠️", priority: 9999, source: "fallback" };
// 4) Protocol icon (based on type) // 4) Protocol icon (based on type)
const proto = PROTOCOL_ICONS[(proxy && proxy.type) || ""] || "🔌"; const proto = PROTOCOL_ICONS[(proxy && proxy.type) || ""] || PROTOCOL_ICON_DEFAULT;
// 5) Network/type/port tag (from proxy fields) // 5) Network/type/port tag (from proxy fields)
const net = safeStr(proxy && proxy.network) || "net?"; const metaTag = buildMetaTag(proxy);
const typ = safeStr(proxy && proxy.type) || "type?";
const port = safeStr(proxy && proxy.port) || "port?";
const metaTag = `▫️${net}/${typ}/${port}`;
// 6) Prepare bucket key // 6) Prepare bucket key
const key = country.iso3; const key = country.iso3;
@@ -382,7 +462,7 @@ function operator(proxies, targetPlatform, utils) {
// Final name format: // Final name format:
// 🇩🇪 DEU-03 🌌 📺 ❻ ▫ws/vless/443 // 🇩🇪 DEU-03 🌌 📺 ❻ ▫ws/vless/443
p.name = `${group.country.flag} ${group.country.iso3}-${num} ${item._meta.proto}${tagStr} ${item._meta.metaTag}${debugSuffix}` p.name = `${group.country.flag} ${group.country.iso3}-${num} ${item._meta.metaTag} ${item._meta.proto}${tagStr} ${debugSuffix}`
.replace(/\s+/g, " ") .replace(/\s+/g, " ")
.trim(); .trim();