From 9c9b78ded9c3b88032d37afb106507ca16606e0a Mon Sep 17 00:00:00 2001 From: Orion Date: Mon, 8 Jun 2026 10:23:12 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(substore):=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84=20regex=20=E7=BC=93=E5=AD=98=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E5=B9=B6=E5=8D=87=E7=BA=A7=E5=85=AC=E5=85=B1=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- substore/README.md | 2 +- substore/alphaair-rename.js | 5 +-- substore/common.js | 65 ++++++++++++++++++++++++++----------- substore/liangxin-rename.js | 2 +- substore/peiqian-rename.js | 2 +- substore/rename.js | 51 +++++++++++++++++------------ substore/sntp-rename.js | 2 +- substore/yuetong-rename.js | 2 +- 8 files changed, 84 insertions(+), 47 deletions(-) diff --git a/substore/README.md b/substore/README.md index ef170a1..699e297 100644 --- a/substore/README.md +++ b/substore/README.md @@ -5,7 +5,7 @@ `common.js` 是公共工具库,各处理脚本会按下面的 raw 地址自动加载: ```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 ``` ## 推荐使用顺序 diff --git a/substore/alphaair-rename.js b/substore/alphaair-rename.js index 0b23a45..e8f25e0 100644 --- a/substore/alphaair-rename.js +++ b/substore/alphaair-rename.js @@ -6,7 +6,7 @@ * - 不追加订阅后缀,整合订阅最后仍交给 rename.js 处理 */ // 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 hasCurrentRenameUtils = utils => Boolean( utils && utils.version === COMMON_SCRIPT_VERSION @@ -132,13 +132,14 @@ async function operator(proxies) { }; const countryOrder = Object.create(null); + let countryOrderCount = 0; const normalizedItems = proxies.map((proxy, proxyIndex) => { const normalizedItem = normalizeProxy(proxy, proxyIndex); if ( normalizedItem.countryCode && countryOrder[normalizedItem.countryCode] === undefined ) { - countryOrder[normalizedItem.countryCode] = Object.keys(countryOrder).length; + countryOrder[normalizedItem.countryCode] = countryOrderCount++; } return normalizedItem; }); diff --git a/substore/common.js b/substore/common.js index 4c9b9d0..5de7384 100644 --- a/substore/common.js +++ b/substore/common.js @@ -2,14 +2,16 @@ * 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) { - const VERSION = '20260603-7'; + const VERSION = '20260608-1'; const regexSpecialChars = new Set(['.', '*', '+', '?', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\']); const leadingFlagRegex = /^([\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 regionSequenceRegexCache = Object.create(null); + const lineDescriptorRegexCache = Object.create(null); const escapeRegex = value => [...String(value)] .map(char => regexSpecialChars.has(char) ? `\\${char}` : char) @@ -112,6 +114,14 @@ const createRegionResolver = 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 cleanName = normalizeLeadingMojibakeFlag(name); @@ -119,9 +129,9 @@ if (leadingFlagMatch) return flagToRegionCode(leadingFlagMatch[1]); const cleanNameLower = cleanName.toLowerCase(); - for (const [alias, replacement, aliasLower] of sortedAliases) { - if (/^[A-Z]{2}$/.test(alias)) { - const codeMatch = cleanName.match(new RegExp(`(^|[^A-Za-z])${escapeRegex(alias)}([^A-Za-z]|$)`, 'i')); + for (const [alias, replacement, aliasLower, codeRegex] of compiledAliases) { + if (codeRegex) { + const codeMatch = cleanName.match(codeRegex); if (codeMatch) return replacement; continue; } @@ -134,7 +144,7 @@ const aliasRegionPrefixOf = name => { const cleanNameLower = stripLeadingMojibakeFlag(name).toLowerCase(); - for (const [, replacement, aliasLower] of sortedAliases) { + for (const [, replacement, aliasLower] of compiledAliases) { if (cleanNameLower.includes(aliasLower)) return replacement; } @@ -145,7 +155,7 @@ const cleanName = stripLeadingMojibakeFlag(name); const cleanNameLower = cleanName.toLowerCase(); - for (const [alias, replacement, aliasLower] of sortedAliases) { + for (const [alias, replacement, aliasLower] of compiledAliases) { if (!cleanNameLower.startsWith(aliasLower)) continue; const rest = cleanName.slice(alias.length); @@ -198,12 +208,22 @@ const normalizeRegionSequence = (name, options) => { const allowCityCode = Boolean(options && options.allowCityCode); - const regionPattern = allowCityCode - ? '[A-Z]{2}(?:\\s+[A-Z]{3})?' - : '[A-Z]{2}'; + const cacheKey = allowCityCode ? 'city' : 'country'; + const cachedRegexes = regionSequenceRegexCache[cacheKey] || ( + 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) - .replace(new RegExp(`(^|\\s)(${regionPattern})[\\s|\\-]*(\\d{1,2})(?=$|[\\s|\\-[\\]])`, 'g'), ( + .replace(cachedRegexes.sequenceRegex, ( match, prefix, regionCode, @@ -221,7 +241,7 @@ 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, ' ') .trim(); }; @@ -284,19 +304,26 @@ }; const stripLineDescriptors = (name, descriptorKeywords) => { - const descriptorPattern = descriptorKeywords.map(escapeRegex).join('|'); - const descriptorOnlyRegex = new RegExp(`^(?:${descriptorPattern})+$`, 'i'); - const embeddedDescriptorRegex = new RegExp(`\\s*(?:${descriptorPattern})(?=\\s|\\d|$)`, 'gi'); - const bracketDescriptorRegex = new RegExp(`\\s*\\[(?:${descriptorPattern})(?:\\s+(?:${descriptorPattern}))*]\\s*`, 'gi'); + const descriptorCacheKey = descriptorKeywords.join('\u0000'); + const descriptorRegexes = lineDescriptorRegexCache[descriptorCacheKey] || ( + lineDescriptorRegexCache[descriptorCacheKey] = (() => { + 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 keptSegments = segments .map(segment => segment.trim()) - .filter(segment => segment && !descriptorOnlyRegex.test(segment)); + .filter(segment => segment && !descriptorRegexes.descriptorOnlyRegex.test(segment)); const cleanHead = head - .replace(bracketDescriptorRegex, ' ') - .replace(embeddedDescriptorRegex, '') + .replace(descriptorRegexes.bracketDescriptorRegex, ' ') + .replace(descriptorRegexes.embeddedDescriptorRegex, '') .replace(leadingFlagRegex, '$1 ') .replace(/\s{2,}/g, ' ') .trim(); diff --git a/substore/liangxin-rename.js b/substore/liangxin-rename.js index 6a42fa2..6155991 100644 --- a/substore/liangxin-rename.js +++ b/substore/liangxin-rename.js @@ -3,7 +3,7 @@ * 负责去掉高速、BGP、CMCU 等 LiangXin 线路描述,保留地区、序号和倍率。 */ // 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 hasCurrentRenameUtils = utils => Boolean( utils && utils.version === COMMON_SCRIPT_VERSION diff --git a/substore/peiqian-rename.js b/substore/peiqian-rename.js index 827e212..b11ad1e 100644 --- a/substore/peiqian-rename.js +++ b/substore/peiqian-rename.js @@ -3,7 +3,7 @@ * 负责清理推荐、用途、hy2、倍率、线路特征和提供商等 PeiQian 节点文案。 */ // 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 hasCurrentRenameUtils = utils => Boolean( utils && utils.version === COMMON_SCRIPT_VERSION diff --git a/substore/rename.js b/substore/rename.js index 41d1599..2869aac 100644 --- a/substore/rename.js +++ b/substore/rename.js @@ -5,7 +5,7 @@ * - 基于归一化后的完整名称做精准去重 */ // 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 hasCurrentRenameUtils = utils => Boolean( utils && utils.version === COMMON_SCRIPT_VERSION @@ -608,6 +608,18 @@ async function operator(proxies) { ['ISIF', 'ISIF'], ['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) => { if (!subName) return name.trim(); @@ -670,45 +682,42 @@ async function operator(proxies) { const providerOf = name => { const cleanName = String(name); - for (const [alias, provider] of providerAliases) { - 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'); + for (const [provider, aliasRegex] of providerAliasMatchers) { if (aliasRegex.test(cleanName)) return provider; } return ''; }; const embeddedRegionPrefixOf = (name, fallbackRegionCode) => { - const cleanName = String(name); - const candidates = []; - if (fallbackRegionCode) { - candidates.push({ index: -1, region: fallbackRegionCode }); - } + if (fallbackRegionCode) return fallbackRegionCode; - for (const [alias, replacement, aliasLower] of sortedRegionAliases) { - const aliasRegex = /^[A-Za-z0-9 ]+$/.test(alias) - ? new RegExp(`(^|[^A-Za-z])${escapeRegex(alias)}(?=$|[^A-Za-z])`, 'i') - : new RegExp(escapeRegex(alias), 'i'); + const cleanName = String(name); + let bestIndex = Number.POSITIVE_INFINITY; + let bestRegion = ''; + + for (const [replacement, aliasRegex] of embeddedRegionAliasMatchers) { const match = cleanName.match(aliasRegex); if (!match) continue; 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)) { const regionCode = match[2].toUpperCase(); if (!knownRegionCodes.has(regionCode)) continue; - candidates.push({ - index: match.index + (match[1] ? match[1].length : 0), - region: regionCode - }); + const matchIndex = match.index + (match[1] ? match[1].length : 0); + if (matchIndex < bestIndex) { + bestIndex = matchIndex; + bestRegion = regionCode; + } } - candidates.sort((firstCandidate, secondCandidate) => firstCandidate.index - secondCandidate.index); - return candidates.length ? candidates[0].region : ''; + return bestRegion; }; const normalizeProviderRegionName = (name, fallbackRegionCode) => { diff --git a/substore/sntp-rename.js b/substore/sntp-rename.js index 2477077..fd669ca 100644 --- a/substore/sntp-rename.js +++ b/substore/sntp-rename.js @@ -4,7 +4,7 @@ * 维护说明:仅需修改 featureMap 字典即可,底部逻辑永远无需改动。 */ // 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 hasCurrentRenameUtils = utils => Boolean( utils && utils.version === COMMON_SCRIPT_VERSION diff --git a/substore/yuetong-rename.js b/substore/yuetong-rename.js index e67c8f7..d2a3917 100644 --- a/substore/yuetong-rename.js +++ b/substore/yuetong-rename.js @@ -4,7 +4,7 @@ * 按地区稳定归并后重新连续编号,避免专属/普通节点撞号。 */ // 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 hasCurrentRenameUtils = utils => Boolean( utils && utils.version === COMMON_SCRIPT_VERSION