✨ feat(substore): 引入隐藏已映射标签的节点重命名重构
This commit is contained in:
+44
-15
@@ -8,6 +8,7 @@ function operator(proxies) {
|
|||||||
// ================= 唯一配置区 =================
|
// ================= 唯一配置区 =================
|
||||||
// 在这里新增或修改关键词。格式:"节点关键词": "想要注入的标签"
|
// 在这里新增或修改关键词。格式:"节点关键词": "想要注入的标签"
|
||||||
// 支持大小写混合,支持包含空格或特殊符号。
|
// 支持大小写混合,支持包含空格或特殊符号。
|
||||||
|
// 日后如果要加 IEPL,只需在这里写 "IEPL": "专线"
|
||||||
const featureMap = {
|
const featureMap = {
|
||||||
"GTM 0.5x": "三网",
|
"GTM 0.5x": "三网",
|
||||||
"GTM": "三网",
|
"GTM": "三网",
|
||||||
@@ -18,8 +19,14 @@ function operator(proxies) {
|
|||||||
"S4": "广联",
|
"S4": "广联",
|
||||||
"BGP": "cn2",
|
"BGP": "cn2",
|
||||||
"D": "直连",
|
"D": "直连",
|
||||||
"Anytls": "直连" // 日后如果要加 IEPL,只需在这里写 "IEPL": "专线"
|
"Anytls": "直连",
|
||||||
|
"Hy2": "直连"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 已经由标签表达含义的特征码,会自动从展示名中移除。
|
||||||
|
// 维护方式:只改 featureMap 即可,这里不需要单独配置隐藏词。
|
||||||
|
// 示例:🇭🇰 HK06-GTM Oilguy专线 [三网] - SNTP
|
||||||
|
// 输出:🇭🇰 HK06 Oilguy专线 [三网]-SNTP
|
||||||
// ==============================================
|
// ==============================================
|
||||||
|
|
||||||
|
|
||||||
@@ -30,6 +37,7 @@ function operator(proxies) {
|
|||||||
const normalizedFeatureMap = {};
|
const normalizedFeatureMap = {};
|
||||||
const featureKeywords = Object.keys(featureMap);
|
const featureKeywords = Object.keys(featureMap);
|
||||||
const normalizeFeatureKeyword = keyword => keyword.toUpperCase().replace(/\s+/g, '');
|
const normalizeFeatureKeyword = keyword => keyword.toUpperCase().replace(/\s+/g, '');
|
||||||
|
const normalizeHiddenFeatureKeyword = keyword => keyword.trim().replace(/\s+\d+(?:\.\d+)?x$/i, '');
|
||||||
const regexSpecialChars = new Set(['.', '*', '+', '?', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\']);
|
const regexSpecialChars = new Set(['.', '*', '+', '?', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\']);
|
||||||
const escapeRegexKeyword = keyword => [...keyword]
|
const escapeRegexKeyword = keyword => [...keyword]
|
||||||
.map(char => regexSpecialChars.has(char) ? `\\${char}` : char)
|
.map(char => regexSpecialChars.has(char) ? `\\${char}` : char)
|
||||||
@@ -38,6 +46,28 @@ function operator(proxies) {
|
|||||||
normalizedFeatureMap[normalizeFeatureKeyword(featureKeyword)] = featureMap[featureKeyword];
|
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. 动态构建复合正则表达式 (核心引擎)
|
// 2. 动态构建复合正则表达式 (核心引擎)
|
||||||
// 按字符串长度降序排序,彻底解决 "短路匹配" (Short-Circuit) 问题
|
// 按字符串长度降序排序,彻底解决 "短路匹配" (Short-Circuit) 问题
|
||||||
const sortedFeatureKeywords = [...featureKeywords].sort(
|
const sortedFeatureKeywords = [...featureKeywords].sort(
|
||||||
@@ -46,31 +76,27 @@ function operator(proxies) {
|
|||||||
|
|
||||||
const featureRegexParts = sortedFeatureKeywords.map(featureKeyword => {
|
const featureRegexParts = sortedFeatureKeywords.map(featureKeyword => {
|
||||||
// 自动转义正则特殊字符 (防注入报错)
|
// 自动转义正则特殊字符 (防注入报错)
|
||||||
const escapedKeywordPattern = featureKeyword.trim().split(/\s+/).filter(Boolean).map(escapeRegexKeyword).join('\\s*');
|
return buildKeywordPattern(featureKeyword);
|
||||||
|
|
||||||
// 智能边界处理:如果关键词首尾都是字母或数字,就追加 \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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 动态拼接出类似: /(GTM\s*0\.5x|\bAnytls\b|\bBGP\b|...)/i
|
// 动态拼接出类似: /(GTM\s*0\.5x|\bAnytls\b|\bBGP\b|...)/i
|
||||||
const featureRegex = new RegExp(`(${featureRegexParts.join('|')})`, '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. 执行节点遍历与注入
|
// 3. 执行节点遍历与注入
|
||||||
return proxies.map(proxy => {
|
return proxies.map(proxy => {
|
||||||
const proxyName = proxy.name;
|
if (typeof proxy.name !== 'string') return proxy;
|
||||||
if (typeof proxyName !== 'string') return proxy;
|
let proxyName = normalizeSntpSuffixSpacing(proxy.name);
|
||||||
|
|
||||||
// 单次复合正则扫描提取
|
// 单次复合正则扫描提取
|
||||||
const featureMatch = proxyName.match(featureRegex);
|
const featureMatch = proxyName.match(featureRegex);
|
||||||
if (!featureMatch) return proxy; // 未命中配置库,直接放行
|
if (!featureMatch) {
|
||||||
|
proxy.name = proxyName;
|
||||||
|
return proxy; // 未命中配置库,仅保留缩写结果后放行
|
||||||
|
}
|
||||||
|
|
||||||
// 清洗提取到的特征词,并去 O(1) 字典中取值
|
// 清洗提取到的特征词,并去 O(1) 字典中取值
|
||||||
const normalizedMatchedKeyword = normalizeFeatureKeyword(featureMatch[1]);
|
const normalizedMatchedKeyword = normalizeFeatureKeyword(featureMatch[1]);
|
||||||
@@ -78,15 +104,18 @@ function operator(proxies) {
|
|||||||
|
|
||||||
if (!injectedFeatureLabel) return proxy; // 兜底安全校验
|
if (!injectedFeatureLabel) return proxy; // 兜底安全校验
|
||||||
|
|
||||||
|
proxyName = stripHiddenFeatureKeywords(proxyName);
|
||||||
|
|
||||||
// 格式化组装与幂等校验 (防重复注入 Bug)
|
// 格式化组装与幂等校验 (防重复注入 Bug)
|
||||||
const formattedFeatureLabel = ` [${injectedFeatureLabel}]`;
|
const formattedFeatureLabel = ` [${injectedFeatureLabel}]`;
|
||||||
if (proxyName.includes(formattedFeatureLabel)) {
|
if (proxyName.includes(formattedFeatureLabel)) {
|
||||||
|
proxy.name = proxyName;
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 实施中缀注入 (Infix Injection)
|
// 实施中缀注入 (Infix Injection)
|
||||||
if (suffixRegex.test(proxyName)) {
|
if (suffixRegex.test(proxyName)) {
|
||||||
proxy.name = proxyName.replace(suffixRegex, `${formattedFeatureLabel}$1`);
|
proxy.name = proxyName.replace(suffixRegex, `${formattedFeatureLabel}-$1`);
|
||||||
} else {
|
} else {
|
||||||
// 纯净节点无后缀时,直接挂载在末尾
|
// 纯净节点无后缀时,直接挂载在末尾
|
||||||
proxy.name = proxyName + formattedFeatureLabel;
|
proxy.name = proxyName + formattedFeatureLabel;
|
||||||
|
|||||||
Reference in New Issue
Block a user