♻️ refactor(substore): 重构 regex 缓存机制并升级公共脚本版本

This commit is contained in:
2026-06-08 10:23:12 +08:00
parent afc8c70b95
commit 9c9b78ded9
8 changed files with 84 additions and 47 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
`common.js` 是公共工具库,各处理脚本会按下面的 raw 地址自动加载: `common.js` 是公共工具库,各处理脚本会按下面的 raw 地址自动加载:
```text ```text
https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=20260603-7 https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=20260608-1
``` ```
## 推荐使用顺序 ## 推荐使用顺序
+3 -2
View File
@@ -6,7 +6,7 @@
* - 不追加订阅后缀,整合订阅最后仍交给 rename.js 处理 * - 不追加订阅后缀,整合订阅最后仍交给 rename.js 处理
*/ */
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
const COMMON_SCRIPT_VERSION = '20260603-7'; const COMMON_SCRIPT_VERSION = '20260608-1';
const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`; const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`;
const hasCurrentRenameUtils = utils => Boolean( const hasCurrentRenameUtils = utils => Boolean(
utils && utils.version === COMMON_SCRIPT_VERSION utils && utils.version === COMMON_SCRIPT_VERSION
@@ -132,13 +132,14 @@ async function operator(proxies) {
}; };
const countryOrder = Object.create(null); const countryOrder = Object.create(null);
let countryOrderCount = 0;
const normalizedItems = proxies.map((proxy, proxyIndex) => { const normalizedItems = proxies.map((proxy, proxyIndex) => {
const normalizedItem = normalizeProxy(proxy, proxyIndex); const normalizedItem = normalizeProxy(proxy, proxyIndex);
if ( if (
normalizedItem.countryCode && normalizedItem.countryCode &&
countryOrder[normalizedItem.countryCode] === undefined countryOrder[normalizedItem.countryCode] === undefined
) { ) {
countryOrder[normalizedItem.countryCode] = Object.keys(countryOrder).length; countryOrder[normalizedItem.countryCode] = countryOrderCount++;
} }
return normalizedItem; return normalizedItem;
}); });
+46 -19
View File
@@ -2,14 +2,16 @@
* Sub-Store 节点命名公共工具 * Sub-Store 节点命名公共工具
* *
* 远程引用地址: * 远程引用地址:
* https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=20260603-7 * https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=20260608-1
*/ */
(function (root) { (function (root) {
const VERSION = '20260603-7'; const VERSION = '20260608-1';
const regexSpecialChars = new Set(['.', '*', '+', '?', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\']); const regexSpecialChars = new Set(['.', '*', '+', '?', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\']);
const leadingFlagRegex = /^([\u{1F1E6}-\u{1F1FF}]{2}|🏴‍☠️)\s*/u; const leadingFlagRegex = /^([\u{1F1E6}-\u{1F1FF}]{2}|🏴‍☠️)\s*/u;
const inlineFlagRegex = /^(.{1,24}?)([\u{1F1E6}-\u{1F1FF}]{2}|🏴‍☠️)\s*(.*)$/u; const inlineFlagRegex = /^(.{1,24}?)([\u{1F1E6}-\u{1F1FF}]{2}|🏴‍☠️)\s*(.*)$/u;
const leadingMojibakeFlagRegex = /^((?:ð[\u0080-\u00BF]{3}){2})\s*/; const leadingMojibakeFlagRegex = /^((?:ð[\u0080-\u00BF]{3}){2})\s*/;
const regionSequenceRegexCache = Object.create(null);
const lineDescriptorRegexCache = Object.create(null);
const escapeRegex = value => [...String(value)] const escapeRegex = value => [...String(value)]
.map(char => regexSpecialChars.has(char) ? `\\${char}` : char) .map(char => regexSpecialChars.has(char) ? `\\${char}` : char)
@@ -112,6 +114,14 @@
const createRegionResolver = aliases => { const createRegionResolver = aliases => {
const sortedAliases = sortRegionAliases(aliases); const sortedAliases = sortRegionAliases(aliases);
const compiledAliases = sortedAliases.map(([alias, replacement, aliasLower]) => [
alias,
replacement,
aliasLower,
/^[A-Z]{2}$/.test(alias)
? new RegExp(`(^|[^A-Za-z])${escapeRegex(alias)}([^A-Za-z]|$)`, 'i')
: null
]);
const regionCodeOf = name => { const regionCodeOf = name => {
const cleanName = normalizeLeadingMojibakeFlag(name); const cleanName = normalizeLeadingMojibakeFlag(name);
@@ -119,9 +129,9 @@
if (leadingFlagMatch) return flagToRegionCode(leadingFlagMatch[1]); if (leadingFlagMatch) return flagToRegionCode(leadingFlagMatch[1]);
const cleanNameLower = cleanName.toLowerCase(); const cleanNameLower = cleanName.toLowerCase();
for (const [alias, replacement, aliasLower] of sortedAliases) { for (const [alias, replacement, aliasLower, codeRegex] of compiledAliases) {
if (/^[A-Z]{2}$/.test(alias)) { if (codeRegex) {
const codeMatch = cleanName.match(new RegExp(`(^|[^A-Za-z])${escapeRegex(alias)}([^A-Za-z]|$)`, 'i')); const codeMatch = cleanName.match(codeRegex);
if (codeMatch) return replacement; if (codeMatch) return replacement;
continue; continue;
} }
@@ -134,7 +144,7 @@
const aliasRegionPrefixOf = name => { const aliasRegionPrefixOf = name => {
const cleanNameLower = stripLeadingMojibakeFlag(name).toLowerCase(); const cleanNameLower = stripLeadingMojibakeFlag(name).toLowerCase();
for (const [, replacement, aliasLower] of sortedAliases) { for (const [, replacement, aliasLower] of compiledAliases) {
if (cleanNameLower.includes(aliasLower)) return replacement; if (cleanNameLower.includes(aliasLower)) return replacement;
} }
@@ -145,7 +155,7 @@
const cleanName = stripLeadingMojibakeFlag(name); const cleanName = stripLeadingMojibakeFlag(name);
const cleanNameLower = cleanName.toLowerCase(); const cleanNameLower = cleanName.toLowerCase();
for (const [alias, replacement, aliasLower] of sortedAliases) { for (const [alias, replacement, aliasLower] of compiledAliases) {
if (!cleanNameLower.startsWith(aliasLower)) continue; if (!cleanNameLower.startsWith(aliasLower)) continue;
const rest = cleanName.slice(alias.length); const rest = cleanName.slice(alias.length);
@@ -198,12 +208,22 @@
const normalizeRegionSequence = (name, options) => { const normalizeRegionSequence = (name, options) => {
const allowCityCode = Boolean(options && options.allowCityCode); const allowCityCode = Boolean(options && options.allowCityCode);
const regionPattern = allowCityCode const cacheKey = allowCityCode ? 'city' : 'country';
? '[A-Z]{2}(?:\\s+[A-Z]{3})?' const cachedRegexes = regionSequenceRegexCache[cacheKey] || (
: '[A-Z]{2}'; regionSequenceRegexCache[cacheKey] = (() => {
const regionPattern = allowCityCode
? '[A-Z]{2}(?:\\s+[A-Z]{3})?'
: '[A-Z]{2}';
return {
sequenceRegex: new RegExp(`(^|\\s)(${regionPattern})[\\s|\\-]*(\\d{1,2})(?=$|[\\s|\\-[\\]])`, 'g'),
multiplierHyphenRegex: new RegExp(`(\\b${regionPattern} \\d{2})-\\s*(?=\\d+(?:\\.\\d+)?X\\b)`, 'g')
};
})()
);
return String(name) return String(name)
.replace(new RegExp(`(^|\\s)(${regionPattern})[\\s|\\-]*(\\d{1,2})(?=$|[\\s|\\-[\\]])`, 'g'), ( .replace(cachedRegexes.sequenceRegex, (
match, match,
prefix, prefix,
regionCode, regionCode,
@@ -221,7 +241,7 @@
return `${prefix}${regionCode} ${sequenceNumber.padStart(2, '0')}`; return `${prefix}${regionCode} ${sequenceNumber.padStart(2, '0')}`;
}) })
.replace(new RegExp(`(\\b${regionPattern} \\d{2})-\\s*(?=\\d+(?:\\.\\d+)?X\\b)`, 'g'), '$1 ') .replace(cachedRegexes.multiplierHyphenRegex, '$1 ')
.replace(/\s{2,}/g, ' ') .replace(/\s{2,}/g, ' ')
.trim(); .trim();
}; };
@@ -284,19 +304,26 @@
}; };
const stripLineDescriptors = (name, descriptorKeywords) => { const stripLineDescriptors = (name, descriptorKeywords) => {
const descriptorPattern = descriptorKeywords.map(escapeRegex).join('|'); const descriptorCacheKey = descriptorKeywords.join('\u0000');
const descriptorOnlyRegex = new RegExp(`^(?:${descriptorPattern})+$`, 'i'); const descriptorRegexes = lineDescriptorRegexCache[descriptorCacheKey] || (
const embeddedDescriptorRegex = new RegExp(`\\s*(?:${descriptorPattern})(?=\\s|\\d|$)`, 'gi'); lineDescriptorRegexCache[descriptorCacheKey] = (() => {
const bracketDescriptorRegex = new RegExp(`\\s*\\[(?:${descriptorPattern})(?:\\s+(?:${descriptorPattern}))*]\\s*`, 'gi'); const descriptorPattern = descriptorKeywords.map(escapeRegex).join('|');
return {
descriptorOnlyRegex: new RegExp(`^(?:${descriptorPattern})+$`, 'i'),
embeddedDescriptorRegex: new RegExp(`\\s*(?:${descriptorPattern})(?=\\s|\\d|$)`, 'gi'),
bracketDescriptorRegex: new RegExp(`\\s*\\[(?:${descriptorPattern})(?:\\s+(?:${descriptorPattern}))*]\\s*`, 'gi')
};
})()
);
const [head, ...segments] = stripLeadingMojibakeFlag(name).split('|'); const [head, ...segments] = stripLeadingMojibakeFlag(name).split('|');
const keptSegments = segments const keptSegments = segments
.map(segment => segment.trim()) .map(segment => segment.trim())
.filter(segment => segment && !descriptorOnlyRegex.test(segment)); .filter(segment => segment && !descriptorRegexes.descriptorOnlyRegex.test(segment));
const cleanHead = head const cleanHead = head
.replace(bracketDescriptorRegex, ' ') .replace(descriptorRegexes.bracketDescriptorRegex, ' ')
.replace(embeddedDescriptorRegex, '') .replace(descriptorRegexes.embeddedDescriptorRegex, '')
.replace(leadingFlagRegex, '$1 ') .replace(leadingFlagRegex, '$1 ')
.replace(/\s{2,}/g, ' ') .replace(/\s{2,}/g, ' ')
.trim(); .trim();
+1 -1
View File
@@ -3,7 +3,7 @@
* 负责去掉高速、BGP、CMCU 等 LiangXin 线路描述,保留地区、序号和倍率。 * 负责去掉高速、BGP、CMCU 等 LiangXin 线路描述,保留地区、序号和倍率。
*/ */
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
const COMMON_SCRIPT_VERSION = '20260603-7'; const COMMON_SCRIPT_VERSION = '20260608-1';
const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`; const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`;
const hasCurrentRenameUtils = utils => Boolean( const hasCurrentRenameUtils = utils => Boolean(
utils && utils.version === COMMON_SCRIPT_VERSION utils && utils.version === COMMON_SCRIPT_VERSION
+1 -1
View File
@@ -3,7 +3,7 @@
* 负责清理推荐、用途、hy2、倍率、线路特征和提供商等 PeiQian 节点文案。 * 负责清理推荐、用途、hy2、倍率、线路特征和提供商等 PeiQian 节点文案。
*/ */
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
const COMMON_SCRIPT_VERSION = '20260603-7'; const COMMON_SCRIPT_VERSION = '20260608-1';
const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`; const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`;
const hasCurrentRenameUtils = utils => Boolean( const hasCurrentRenameUtils = utils => Boolean(
utils && utils.version === COMMON_SCRIPT_VERSION utils && utils.version === COMMON_SCRIPT_VERSION
+30 -21
View File
@@ -5,7 +5,7 @@
* - 基于归一化后的完整名称做精准去重 * - 基于归一化后的完整名称做精准去重
*/ */
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
const COMMON_SCRIPT_VERSION = '20260603-7'; const COMMON_SCRIPT_VERSION = '20260608-1';
const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`; const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`;
const hasCurrentRenameUtils = utils => Boolean( const hasCurrentRenameUtils = utils => Boolean(
utils && utils.version === COMMON_SCRIPT_VERSION utils && utils.version === COMMON_SCRIPT_VERSION
@@ -608,6 +608,18 @@ async function operator(proxies) {
['ISIF', 'ISIF'], ['ISIF', 'ISIF'],
['RFC', 'RFC'] ['RFC', 'RFC']
]; ];
const providerAliasMatchers = providerAliases.map(([alias, provider]) => [
provider,
/^[A-Za-z0-9.]+$/.test(alias)
? new RegExp(`(^|[^A-Za-z0-9])${escapeRegex(alias)}([^A-Za-z0-9]|$)`, 'i')
: new RegExp(escapeRegex(alias), 'i')
]);
const embeddedRegionAliasMatchers = sortedRegionAliases.map(([alias, replacement]) => [
replacement,
/^[A-Za-z0-9 ]+$/.test(alias)
? new RegExp(`(^|[^A-Za-z])${escapeRegex(alias)}(?=$|[^A-Za-z])`, 'i')
: new RegExp(escapeRegex(alias), 'i')
]);
const stripExistingSuffix = (name, subName) => { const stripExistingSuffix = (name, subName) => {
if (!subName) return name.trim(); if (!subName) return name.trim();
@@ -670,45 +682,42 @@ async function operator(proxies) {
const providerOf = name => { const providerOf = name => {
const cleanName = String(name); const cleanName = String(name);
for (const [alias, provider] of providerAliases) { for (const [provider, aliasRegex] of providerAliasMatchers) {
const aliasRegex = /^[A-Za-z0-9.]+$/.test(alias)
? new RegExp(`(^|[^A-Za-z0-9])${escapeRegex(alias)}([^A-Za-z0-9]|$)`, 'i')
: new RegExp(escapeRegex(alias), 'i');
if (aliasRegex.test(cleanName)) return provider; if (aliasRegex.test(cleanName)) return provider;
} }
return ''; return '';
}; };
const embeddedRegionPrefixOf = (name, fallbackRegionCode) => { const embeddedRegionPrefixOf = (name, fallbackRegionCode) => {
const cleanName = String(name); if (fallbackRegionCode) return fallbackRegionCode;
const candidates = [];
if (fallbackRegionCode) {
candidates.push({ index: -1, region: fallbackRegionCode });
}
for (const [alias, replacement, aliasLower] of sortedRegionAliases) { const cleanName = String(name);
const aliasRegex = /^[A-Za-z0-9 ]+$/.test(alias) let bestIndex = Number.POSITIVE_INFINITY;
? new RegExp(`(^|[^A-Za-z])${escapeRegex(alias)}(?=$|[^A-Za-z])`, 'i') let bestRegion = '';
: new RegExp(escapeRegex(alias), 'i');
for (const [replacement, aliasRegex] of embeddedRegionAliasMatchers) {
const match = cleanName.match(aliasRegex); const match = cleanName.match(aliasRegex);
if (!match) continue; if (!match) continue;
const matchIndex = match.index + (match[1] ? match[1].length : 0); const matchIndex = match.index + (match[1] ? match[1].length : 0);
candidates.push({ index: matchIndex, region: replacement }); if (matchIndex < bestIndex) {
bestIndex = matchIndex;
bestRegion = replacement;
}
} }
for (const match of cleanName.matchAll(/(^|[^A-Za-z])([A-Z]{2})(?:\d{1,2})?(?=$|[^A-Za-z])/g)) { for (const match of cleanName.matchAll(/(^|[^A-Za-z])([A-Z]{2})(?:\d{1,2})?(?=$|[^A-Za-z])/g)) {
const regionCode = match[2].toUpperCase(); const regionCode = match[2].toUpperCase();
if (!knownRegionCodes.has(regionCode)) continue; if (!knownRegionCodes.has(regionCode)) continue;
candidates.push({ const matchIndex = match.index + (match[1] ? match[1].length : 0);
index: match.index + (match[1] ? match[1].length : 0), if (matchIndex < bestIndex) {
region: regionCode bestIndex = matchIndex;
}); bestRegion = regionCode;
}
} }
candidates.sort((firstCandidate, secondCandidate) => firstCandidate.index - secondCandidate.index); return bestRegion;
return candidates.length ? candidates[0].region : '';
}; };
const normalizeProviderRegionName = (name, fallbackRegionCode) => { const normalizeProviderRegionName = (name, fallbackRegionCode) => {
+1 -1
View File
@@ -4,7 +4,7 @@
* 维护说明:仅需修改 featureMap 字典即可,底部逻辑永远无需改动。 * 维护说明:仅需修改 featureMap 字典即可,底部逻辑永远无需改动。
*/ */
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
const COMMON_SCRIPT_VERSION = '20260603-7'; const COMMON_SCRIPT_VERSION = '20260608-1';
const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`; const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`;
const hasCurrentRenameUtils = utils => Boolean( const hasCurrentRenameUtils = utils => Boolean(
utils && utils.version === COMMON_SCRIPT_VERSION utils && utils.version === COMMON_SCRIPT_VERSION
+1 -1
View File
@@ -4,7 +4,7 @@
* 按地区稳定归并后重新连续编号,避免专属/普通节点撞号。 * 按地区稳定归并后重新连续编号,避免专属/普通节点撞号。
*/ */
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
const COMMON_SCRIPT_VERSION = '20260603-7'; const COMMON_SCRIPT_VERSION = '20260608-1';
const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`; const COMMON_SCRIPT_URL = `https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=${COMMON_SCRIPT_VERSION}`;
const hasCurrentRenameUtils = utils => Boolean( const hasCurrentRenameUtils = utils => Boolean(
utils && utils.version === COMMON_SCRIPT_VERSION utils && utils.version === COMMON_SCRIPT_VERSION