From 9662175e93fe33f6b99d585ce5c2c2b91a6f0789 Mon Sep 17 00:00:00 2001 From: Orion Date: Wed, 3 Jun 2026 10:55:31 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(substore):=20=E5=BC=95?= =?UTF-8?q?=E5=85=A5=E9=9A=90=E8=97=8F=E5=B7=B2=E6=98=A0=E5=B0=84=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E7=9A=84=E8=8A=82=E7=82=B9=E9=87=8D=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- substore/sntp-rename.js | 61 ++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/substore/sntp-rename.js b/substore/sntp-rename.js index ea05690..3d13874 100644 --- a/substore/sntp-rename.js +++ b/substore/sntp-rename.js @@ -8,6 +8,7 @@ function operator(proxies) { // ================= 唯一配置区 ================= // 在这里新增或修改关键词。格式:"节点关键词": "想要注入的标签" // 支持大小写混合,支持包含空格或特殊符号。 + // 日后如果要加 IEPL,只需在这里写 "IEPL": "专线" const featureMap = { "GTM 0.5x": "三网", "GTM": "三网", @@ -18,8 +19,14 @@ function operator(proxies) { "S4": "广联", "BGP": "cn2", "D": "直连", - "Anytls": "直连" // 日后如果要加 IEPL,只需在这里写 "IEPL": "专线" + "Anytls": "直连", + "Hy2": "直连" }; + + // 已经由标签表达含义的特征码,会自动从展示名中移除。 + // 维护方式:只改 featureMap 即可,这里不需要单独配置隐藏词。 + // 示例:🇭🇰 HK06-GTM Oilguy专线 [三网] - SNTP + // 输出:🇭🇰 HK06 Oilguy专线 [三网]-SNTP // ============================================== @@ -30,6 +37,7 @@ function operator(proxies) { const normalizedFeatureMap = {}; const featureKeywords = Object.keys(featureMap); const normalizeFeatureKeyword = keyword => keyword.toUpperCase().replace(/\s+/g, ''); + const normalizeHiddenFeatureKeyword = keyword => keyword.trim().replace(/\s+\d+(?:\.\d+)?x$/i, ''); const regexSpecialChars = new Set(['.', '*', '+', '?', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\']); const escapeRegexKeyword = keyword => [...keyword] .map(char => regexSpecialChars.has(char) ? `\\${char}` : char) @@ -38,6 +46,28 @@ function operator(proxies) { normalizedFeatureMap[normalizeFeatureKeyword(featureKeyword)] = featureMap[featureKeyword]; } + const buildKeywordPattern = keyword => { + const keywordPattern = keyword.trim().split(/\s+/).filter(Boolean).map(escapeRegexKeyword).join('\\s*'); + const trimmedKeyword = keyword.trim(); + if (/^[A-Za-z0-9]/.test(trimmedKeyword) && /[A-Za-z0-9]$/.test(trimmedKeyword)) { + return `\\b${keywordPattern}\\b`; + } + return keywordPattern; + }; + + const hiddenFeatureKeywords = [...new Set(featureKeywords.map(normalizeHiddenFeatureKeyword))]; + const hiddenFeatureRegexList = [...hiddenFeatureKeywords] + .sort((firstKeyword, secondKeyword) => secondKeyword.length - firstKeyword.length) + .map(hiddenFeatureKeyword => new RegExp(`(^|[\\s-]+)${buildKeywordPattern(hiddenFeatureKeyword)}(?=$|[\\s-]+)`, 'ig')); + + const stripHiddenFeatureKeywords = proxyName => hiddenFeatureRegexList + .reduce((strippedName, hiddenFeatureRegex) => strippedName.replace(hiddenFeatureRegex, ''), proxyName) + .replace(/\s{2,}/g, ' ') + .replace(/\s+-/g, '-') + .replace(/-\s+/g, ' ') + .replace(/^-+|-+$/g, '') + .trim(); + // 2. 动态构建复合正则表达式 (核心引擎) // 按字符串长度降序排序,彻底解决 "短路匹配" (Short-Circuit) 问题 const sortedFeatureKeywords = [...featureKeywords].sort( @@ -46,31 +76,27 @@ function operator(proxies) { const featureRegexParts = sortedFeatureKeywords.map(featureKeyword => { // 自动转义正则特殊字符 (防注入报错) - const escapedKeywordPattern = featureKeyword.trim().split(/\s+/).filter(Boolean).map(escapeRegexKeyword).join('\\s*'); - - // 智能边界处理:如果关键词首尾都是字母或数字,就追加 \b 边界 - // 这样能防止配置的 "S1" 错误匹配到 "US1" 或 "TLS1.3" - const trimmedFeatureKeyword = featureKeyword.trim(); - if (/^[A-Za-z0-9]/.test(trimmedFeatureKeyword) && /[A-Za-z0-9]$/.test(trimmedFeatureKeyword)) { - return `\\b${escapedKeywordPattern}\\b`; - } - return escapedKeywordPattern; + return buildKeywordPattern(featureKeyword); }); // 动态拼接出类似: /(GTM\s*0\.5x|\bAnytls\b|\bBGP\b|...)/i const featureRegex = new RegExp(`(${featureRegexParts.join('|')})`, 'i'); // 缓存后缀匹配正则,避免循环内重复创建 - const suffixRegex = /(\s*-\s*SNTP.*)$/i; + const suffixRegex = /\s*-\s*(SNTP.*)$/i; + const normalizeSntpSuffixSpacing = proxyName => proxyName.replace(suffixRegex, '-$1'); // 3. 执行节点遍历与注入 return proxies.map(proxy => { - const proxyName = proxy.name; - if (typeof proxyName !== 'string') return proxy; + if (typeof proxy.name !== 'string') return proxy; + let proxyName = normalizeSntpSuffixSpacing(proxy.name); // 单次复合正则扫描提取 const featureMatch = proxyName.match(featureRegex); - if (!featureMatch) return proxy; // 未命中配置库,直接放行 + if (!featureMatch) { + proxy.name = proxyName; + return proxy; // 未命中配置库,仅保留缩写结果后放行 + } // 清洗提取到的特征词,并去 O(1) 字典中取值 const normalizedMatchedKeyword = normalizeFeatureKeyword(featureMatch[1]); @@ -78,15 +104,18 @@ function operator(proxies) { if (!injectedFeatureLabel) return proxy; // 兜底安全校验 + proxyName = stripHiddenFeatureKeywords(proxyName); + // 格式化组装与幂等校验 (防重复注入 Bug) const formattedFeatureLabel = ` [${injectedFeatureLabel}]`; if (proxyName.includes(formattedFeatureLabel)) { + proxy.name = proxyName; return proxy; } // 实施中缀注入 (Infix Injection) if (suffixRegex.test(proxyName)) { - proxy.name = proxyName.replace(suffixRegex, `${formattedFeatureLabel}$1`); + proxy.name = proxyName.replace(suffixRegex, `${formattedFeatureLabel}-$1`); } else { // 纯净节点无后缀时,直接挂载在末尾 proxy.name = proxyName + formattedFeatureLabel; @@ -94,4 +123,4 @@ function operator(proxies) { return proxy; }); -} +} \ No newline at end of file