Refactor icon and country detection rules in external-proxies-sanitizer.js for improved clarity and maintainability
This commit is contained in:
@@ -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)
|
||||
const ICON_RULES = [
|
||||
{ regex: /TEST/gi, icon: "🧪" },
|
||||
{ regex: /\b⚡️Low Ping\b/gi, icon: "⚡️" },
|
||||
{ regex: /\b⚡️10 Gbit \b/gi, icon: "🛤️" },
|
||||
{ regex: /\bYT\b|\bRussia\b|\bРоссия\b/gi, icon: "📺" },
|
||||
{ regex: /\bIPv6\b/gi, icon: "6️⃣" },
|
||||
{ regex: /\bGemini\b|\bAI Studio\b/gi, icon: "🤖" },
|
||||
{ regex: /\bTorrent✅\b|Torrent|\bP2P\b|\bP2P-Torrents\b/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: "🧲" }
|
||||
];
|
||||
|
||||
// 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: "🌍" }
|
||||
{ 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)
|
||||
@@ -132,30 +132,114 @@ const ISO2_TO_ISO3 = {
|
||||
|
||||
// 6) Protocol icons (based on proxy.type)
|
||||
const PROTOCOL_ICONS = {
|
||||
ss: "🔒",
|
||||
ssr: "☂️",
|
||||
vmess: "🪁",
|
||||
vless: "🌌",
|
||||
trojan: "🐎",
|
||||
http: "🌐",
|
||||
socks5: "🧦",
|
||||
snell: "🐌",
|
||||
wireguard: "🐲",
|
||||
hysteria: "🤪",
|
||||
hysteria2: "⚡",
|
||||
tuic: "🚅"
|
||||
ss: "",
|
||||
ssr: "",
|
||||
vmess: "",
|
||||
vless: "",
|
||||
trojan: "",
|
||||
http: "",
|
||||
socks5: "",
|
||||
snell: "",
|
||||
wireguard: "",
|
||||
hysteria: "",
|
||||
hysteria2: "",
|
||||
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
|
||||
///////////////////////
|
||||
|
||||
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 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) {
|
||||
if (typeof str !== "string") return false;
|
||||
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: "VNM", flag: "🇻🇳", priority: 1, 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" };
|
||||
@@ -316,13 +399,10 @@ function operator(proxies, targetPlatform, utils) {
|
||||
const country = byName || byGeo || { iso3: "UNK", flag: "🏴☠️", priority: 9999, source: "fallback" };
|
||||
|
||||
// 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)
|
||||
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}`;
|
||||
const metaTag = buildMetaTag(proxy);
|
||||
|
||||
// 6) Prepare bucket key
|
||||
const key = country.iso3;
|
||||
@@ -382,7 +462,7 @@ function operator(proxies, targetPlatform, utils) {
|
||||
|
||||
// Final name format:
|
||||
// 🇩🇪 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, " ")
|
||||
.trim();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user