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)
|
// 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();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user