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

This commit is contained in:
2026-01-04 20:31:45 +03:00
parent 75439cf6df
commit 82e97b2f57

View File

@@ -42,14 +42,12 @@ 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: /\bYT\b/gi, icon: "📺" }, { regex: /TEST/gi, icon: "🧪" },
{ regex: /\bIPv6\b/gi, icon: "" }, { regex: /\bLow Ping\b/gi, icon: "⚡️" },
{ regex: /\bNetflix\b|\bNF\b/gi, icon: "🎬" }, { regex: /\b10 Gbit \b/gi, icon: "🛤️" },
{ regex: /\bDisney\+?\b|\bDSNY\b/gi, icon: "🏰" }, { regex: /\bYT\b|\bRussia\b|\bРоссия\b/gi, icon: "📺" },
{ regex: /\bHBO\b/gi, icon: "📼" }, { regex: /\bIPv6\b/gi, icon: "6" },
{ regex: /\bPrime\b|\bAmazon\b/gi, icon: "📦" }, { regex: /\bGemini\b|\bAI Studio\b/gi, icon: "🤖" },
{ regex: /\bChatGPT\b|\bOpenAI\b/gi, icon: "🤖" },
{ regex: /\bSteam\b/gi, icon: "🎮" },
{ regex: /\bTorrent✅\b|Torrent|\bP2P\b|\bP2P-Torrents\b/gi, icon: "🧲" } { regex: /\bTorrent✅\b|Torrent|\bP2P\b|\bP2P-Torrents\b/gi, icon: "🧲" }
]; ];
@@ -63,9 +61,9 @@ const NAME_NETWORK_TAGS = [
// 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)
const COUNTRY_RULES = [ const COUNTRY_RULES = [
{ regex: /\b(Эстония|EE|EST|ESTONIA|TALLINN)\b/i, iso3: "EST", flag: "🇪🇪", priority: 110 }, // Estonia { regex: uWordBoundaryGroup("(Эстония|EE|EST|ESTONIA|TALLINN)"), iso3: "EST", flag: "🇪🇪", priority: 110 },
{ regex: /\b(Финляндия|FI|FIN|FINLAND|HELSINKI)\b/i, iso3: "FIN", flag: "🇫🇮", priority: 70 }, // Finland { regex: uWordBoundaryGroup("(Финляндия|FI|FIN|FINLAND|HELSINKI)"), iso3: "FIN", flag: "🇫🇮", priority: 70 },
{ regex: /\b(Япония|JP|JPN|JAPAN|TOKYO|OSAKA)\b/i, iso3: "JPN", flag: "🇯🇵", priority: 210 }, // Japan { regex: uWordBoundaryGroup("(Япония|JP|JPN|JAPAN|TOKYO|OSAKA)"), iso3: "JPN", flag: "🇯🇵", priority: 210 },
{ regex: /\b(Гонконг|HK|HKG|HONG\s*KONG)\b/i, iso3: "HKG", flag: "🇭🇰", priority: 230 }, // Hong Kong { regex: /\b(Гонконг|HK|HKG|HONG\s*KONG)\b/i, iso3: "HKG", flag: "🇭🇰", priority: 230 }, // Hong Kong
{ regex: /\b(Германия|DE|DEU|GER(MANY)?|FRANKFURT|BERLIN|MUNICH)\b/i, iso3: "DEU", flag: "🇩🇪", priority: 20 }, // Germany { regex: /\b(Германия|DE|DEU|GER(MANY)?|FRANKFURT|BERLIN|MUNICH)\b/i, iso3: "DEU", flag: "🇩🇪", priority: 20 }, // Germany
{ regex: /\b(Франция|FR|FRA|FRANCE|PARIS|MARSEILLE)\b/i, iso3: "FRA", flag: "🇫🇷", priority: 50 }, // France { regex: /\b(Франция|FR|FRA|FRANCE|PARIS|MARSEILLE)\b/i, iso3: "FRA", flag: "🇫🇷", priority: 50 }, // France
@@ -77,11 +75,11 @@ const COUNTRY_RULES = [
{ regex: /\b(Сингапур|SG|SGP|SINGAPORE)\b/i, iso3: "SGP", flag: "🇸🇬", priority: 200 }, // Singapore { regex: /\b(Сингапур|SG|SGP|SINGAPORE)\b/i, iso3: "SGP", flag: "🇸🇬", priority: 200 }, // Singapore
{ regex: /\b(South Korea|Корея|KR|KOR|KOREA|SEOUL)\b/i, iso3: "KOR", flag: "🇰🇷", priority: 220 }, // South Korea { regex: /\b(South Korea|Корея|KR|KOR|KOREA|SEOUL)\b/i, iso3: "KOR", flag: "🇰🇷", priority: 220 }, // South Korea
{ regex: /\b(Швеция|SE|SWE|SWEDEN|STOCKHOLM)\b/i, iso3: "SWE", flag: "🇸🇪", priority: 80 }, // Sweden { regex: /\b(Швеция|SE|SWE|SWEDEN|STOCKHOLM)\b/i, iso3: "SWE", flag: "🇸🇪", priority: 80 }, // Sweden
{ regex: /\b(Швейцария|CH|CHE|SWITZERLAND|ZURICH|GENEVA)\b/i, iso3: "CHE", flag: "🇨🇭", priority: 100 }, // Switzerland { regex: /\b(Швейцария|CH|CHE|SWITZERLAND|Switzerl)\b/i, iso3: "CHE", flag: "🇨🇭", priority: 100 }, // Switzerland
{ regex: /\b(Турция|TR|TUR|TURKEY|ISTANBUL)\b/i, iso3: "TUR", flag: "🇹🇷", priority: 140 }, // Turkey { regex: /\b(Турция|TR|TUR|TURKEY|ISTANBUL)\b/i, iso3: "TUR", flag: "🇹🇷", priority: 140 }, // Turkey
{ regex: /\b(Великобритания|Англия|England|UK|GB|GBR|UNITED\s*KINGDOM)\b/i, iso3: "GBR", flag: "🇬🇧", priority: 40 }, // UK { regex: /\b(Великобритания|Англия|England|UK|GB|GBR|UNITED\s*KINGDOM)\b/i, iso3: "GBR", flag: "🇬🇧", priority: 40 }, // UK
{ regex: /\b(США|USA|US|UNITED\s*STATES|AMERICA|NEW\s*YORK|NYC)\b/i, iso3: "USA", flag: "🇺🇸", priority: 10 }, // USA { regex: /\b(США|USA|US|UNITED\s*STATES|AMERICA|NEW\s*YORK|NYC)\b/i, iso3: "USA", flag: "🇺🇸", priority: 10 }, // USA
{ regex: /\b(Argentina|AR|ARG|ARGENTINA|BUENOS\s*AIRES)\b/i, iso3: "ARG", flag: "🇦🇷", priority: 240 }, // Argentina { regex: /\b(Аргентина|Argentina|AR|ARG|ARGENTINA|BUENOS\s*AIRES)\b/i, iso3: "ARG", flag: "🇦🇷", priority: 240 }, // Argentina
{ regex: /\b(Australia|AU|AUS|AUSTRALIA|SYDNEY)\b/i, iso3: "AUS", flag: "🇦🇺", priority: 250 }, // Australia { regex: /\b(Australia|AU|AUS|AUSTRALIA|SYDNEY)\b/i, iso3: "AUS", flag: "🇦🇺", priority: 250 }, // Australia
{ regex: /\b(Austria|AT|AUT|AUSTRIA|VIENNA)\b/i, iso3: "AUT", flag: "🇦🇹", priority: 260 }, // Austria { regex: /\b(Austria|AT|AUT|AUSTRIA|VIENNA)\b/i, iso3: "AUT", flag: "🇦🇹", priority: 260 }, // Austria
{ regex: /\b(Brazil|BR|BRA|BRAZIL|SAO\s*PAULO)\b/i, iso3: "BRA", flag: "🇧🇷", priority: 270 }, // Brazil { regex: /\b(Brazil|BR|BRA|BRAZIL|SAO\s*PAULO)\b/i, iso3: "BRA", flag: "🇧🇷", priority: 270 }, // Brazil
@@ -152,6 +150,12 @@ const PROTOCOL_ICONS = {
// HELPERS // HELPERS
/////////////////////// ///////////////////////
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 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}$/);
@@ -201,6 +205,55 @@ function extractIconTagsAndStrip(name) {
function detectCountryByName(name) { function detectCountryByName(name) {
const n = String(name || ""); const n = String(name || "");
// Order by priority, then first match wins // 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: 1, source: "flag" };
if (n.includes("🇦🇹")) return { iso3: "AUT", flag: "🇦🇹", priority: 1, source: "flag" };
if (n.includes("🇦🇺")) return { iso3: "AUS", flag: "🇦🇺", priority: 1, source: "flag" };
if (n.includes("🇧🇬")) return { iso3: "BGR", flag: "🇧🇬", priority: 1, source: "flag" };
if (n.includes("🇧🇷")) return { iso3: "BRA", flag: "🇧🇷", priority: 1, source: "flag" };
if (n.includes("🇨🇦")) return { iso3: "CAN", flag: "🇨🇦", priority: 1, source: "flag" };
if (n.includes("🇨🇭")) return { iso3: "CHE", flag: "🇨🇭", priority: 1, source: "flag" };
if (n.includes("🇨🇿")) return { iso3: "CZE", flag: "🇨🇿", priority: 1, source: "flag" };
if (n.includes("🇩🇪")) return { iso3: "DEU", flag: "🇩🇪", priority: 1, source: "flag" };
if (n.includes("🇩🇰")) return { iso3: "DNK", flag: "🇩🇰", priority: 1, source: "flag" };
if (n.includes("🇪🇪")) return { iso3: "EST", flag: "🇪🇪", priority: 1, source: "flag" };
if (n.includes("🇪🇬")) return { iso3: "EGY", flag: "🇪🇬", priority: 1, source: "flag" };
if (n.includes("🇪🇸")) return { iso3: "ESP", flag: "🇪🇸", priority: 1, source: "flag" };
if (n.includes("🇫🇮")) return { iso3: "FIN", flag: "🇫🇮", priority: 1, source: "flag" };
if (n.includes("🇫🇷")) return { iso3: "FRA", flag: "🇫🇷", priority: 1, source: "flag" };
if (n.includes("🇬🇧")) return { iso3: "GBR", flag: "🇬🇧", priority: 1, source: "flag" };
if (n.includes("🇬🇪")) return { iso3: "GEO", flag: "🇬🇪", priority: 1, source: "flag" };
if (n.includes("🇭🇰")) return { iso3: "HKG", flag: "🇭🇰", priority: 1, source: "flag" };
if (n.includes("🇮🇪")) return { iso3: "IRL", flag: "🇮🇪", priority: 1, source: "flag" };
if (n.includes("🇮🇱")) return { iso3: "ISR", flag: "🇮🇱", priority: 1, source: "flag" };
if (n.includes("🇮🇳")) return { iso3: "IND", flag: "🇮🇳", priority: 1, source: "flag" };
if (n.includes("🇮🇹")) return { iso3: "ITA", flag: "🇮🇹", priority: 1, source: "flag" };
if (n.includes("🇯🇵")) return { iso3: "JPN", flag: "🇯🇵", priority: 1, source: "flag" };
if (n.includes("🇰🇷")) return { iso3: "KOR", flag: "🇰🇷", priority: 1, source: "flag" };
if (n.includes("🇰🇿")) return { iso3: "KAZ", flag: "🇰🇿", priority: 1, source: "flag" };
if (n.includes("🇱🇹")) return { iso3: "LTU", flag: "🇱🇹", priority: 1, source: "flag" };
if (n.includes("🇱🇻")) return { iso3: "LVA", flag: "🇱🇻", priority: 1, source: "flag" };
if (n.includes("🇲🇩")) return { iso3: "MDA", flag: "🇲🇩", priority: 1, source: "flag" };
if (n.includes("🇲🇾")) return { iso3: "MYS", flag: "🇲🇾", priority: 1, source: "flag" };
if (n.includes("🇳🇬")) return { iso3: "NGA", flag: "🇳🇬", priority: 1, source: "flag" };
if (n.includes("🇳🇱")) return { iso3: "NLD", flag: "🇳🇱", priority: 1, source: "flag" };
if (n.includes("🇳🇴")) return { iso3: "NOR", flag: "🇳🇴", priority: 1, source: "flag" };
if (n.includes("🇵🇭")) return { iso3: "PHL", flag: "🇵🇭", priority: 1, source: "flag" };
if (n.includes("🇵🇱")) return { iso3: "POL", flag: "🇵🇱", priority: 1, source: "flag" };
if (n.includes("🇵🇹")) return { iso3: "PRT", flag: "🇵🇹", priority: 1, source: "flag" };
if (n.includes("🇷🇴")) return { iso3: "ROU", flag: "🇷🇴", priority: 1, source: "flag" };
if (n.includes("🇷🇺")) return { iso3: "RUS", flag: "🇷🇺", priority: 1, source: "flag" };
if (n.includes("🇸🇪")) return { iso3: "SWE", flag: "🇸🇪", priority: 1, source: "flag" };
if (n.includes("🇸🇬")) return { iso3: "SGP", flag: "🇸🇬", priority: 1, source: "flag" };
if (n.includes("🇹🇭")) return { iso3: "THA", flag: "🇹🇭", priority: 1, source: "flag" };
if (n.includes("🇹🇷")) return { iso3: "TUR", flag: "🇹🇷", priority: 1, source: "flag" };
if (n.includes("🇹🇼")) return { iso3: "TWN", 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" };
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" };