Files
script/substore/sntp-rename.js
T

158 lines
6.7 KiB
JavaScript

/**
* Sub-Store 节点名称属性注入脚本 (全自动配置驱动版)
* 核心架构:动态按长度排序编译正则 + 智能边界识别 + O(1) 映射提取
* 维护说明:仅需修改 featureMap 字典即可,底部逻辑永远无需改动。
*/
// noinspection JSUnusedGlobalSymbols
const COMMON_SCRIPT_URL = 'https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=20260603-2';
async function loadRenameUtils() {
if (typeof globalThis !== 'undefined' && globalThis.SubStoreRenameUtils) {
return globalThis.SubStoreRenameUtils;
}
const cache = typeof scriptResourceCache !== 'undefined' ? scriptResourceCache : null;
let script = cache && cache.get(COMMON_SCRIPT_URL);
if (!script) {
const response = await $substore.http.get({ url: COMMON_SCRIPT_URL });
script = response && (response.body || response.data || response);
if (cache) cache.set(COMMON_SCRIPT_URL, script);
}
new Function(String(script))();
return globalThis.SubStoreRenameUtils;
}
async function operator(proxies) {
const utils = await loadRenameUtils();
// ================= 唯一配置区 =================
// 在这里新增或修改关键词。格式:"节点关键词": "想要注入的标签"
// 支持大小写混合,支持包含空格或特殊符号。
// 日后如果要加 IEPL,只需在这里写 "IEPL": "专线"
const featureMap = {
"GTM 0.5x": "三网",
"GTM": "三网",
"CT": "CFW",
"S1": "广移",
"S2": "广电",
"S3": "广移",
"S4": "广联",
"BGP": "cn2",
"D": "直连",
"Anytls": "直连",
"Hy2": "直连"
};
// 已经由标签表达含义的特征码,会自动从展示名中移除。
// 维护方式:只改 featureMap 即可,这里不需要单独配置隐藏词。
// 示例:🇭🇰 HK06-GTM Oilguy专线 [三网] - SNTP
// 输出:🇭🇰 HK 06 O专线 [三网]-SNTP
// ==============================================
// ================= 核心逻辑区 (无需修改) =================
// 1. 初始化预处理:生成稳定的查找字典 (全大写+去空格)
// 目的:无论节点名叫 "gtm 0.5x" 还是 "GTM0.5X",都能稳定映射
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, '');
for (const featureKeyword of featureKeywords) {
normalizedFeatureMap[normalizeFeatureKeyword(featureKeyword)] = featureMap[featureKeyword];
}
const buildKeywordPattern = keyword => {
const keywordPattern = keyword.trim().split(/\s+/).filter(Boolean).map(utils.escapeRegex).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();
const abbreviateDedicatedLineProvider = proxyName => proxyName.replace(
/\b([A-Za-z])[A-Za-z]*\s*专线/g,
(match, providerInitial) => `${providerInitial.toUpperCase()}专线`
);
const normalizeDisplayName = proxyName => abbreviateDedicatedLineProvider(
utils.normalizeRegionSequence(proxyName.replace(/\b(\d+(?:\.\d+)?)x\b/gi, '$1X'))
.replace(/\b(\d+(?:\.\d+)?)X\b/g, '$1x')
);
// 2. 动态构建复合正则表达式 (核心引擎)
// 按字符串长度降序排序,彻底解决 "短路匹配" (Short-Circuit) 问题
const sortedFeatureKeywords = [...featureKeywords].sort(
(firstFeatureKeyword, secondFeatureKeyword) => secondFeatureKeyword.length - firstFeatureKeyword.length
);
const featureRegexParts = sortedFeatureKeywords.map(featureKeyword => {
// 自动转义正则特殊字符 (防注入报错)
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 normalizeSntpSuffixSpacing = proxyName => proxyName.replace(suffixRegex, '-$1');
const normalizeCloudflareLabel = proxyName => proxyName
.replace(/\s*\[(?:CF|Cloudflare)]\s*/gi, ' [CFW] ')
.replace(/\s{2,}/g, ' ')
.trim();
// 3. 执行节点遍历与注入
return proxies.map(proxy => {
if (typeof proxy.name !== 'string') return proxy;
let proxyName = normalizeCloudflareLabel(normalizeSntpSuffixSpacing(proxy.name));
// 单次复合正则扫描提取
const featureMatch = proxyName.match(featureRegex);
if (!featureMatch) {
proxy.name = normalizeDisplayName(proxyName);
return proxy; // 未命中配置库,仅保留缩写结果后放行
}
// 清洗提取到的特征词,并去 O(1) 字典中取值
const normalizedMatchedKeyword = normalizeFeatureKeyword(featureMatch[1]);
const injectedFeatureLabel = normalizedFeatureMap[normalizedMatchedKeyword];
if (!injectedFeatureLabel) return proxy; // 兜底安全校验
proxyName = normalizeDisplayName(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`);
} else {
// 纯净节点无后缀时,直接挂载在末尾
proxy.name = proxyName + formattedFeatureLabel;
}
return proxy;
});
}