♻️ refactor(substore): 重构 regex 缓存机制并升级公共脚本版本
This commit is contained in:
+1
-1
@@ -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
|
||||
```
|
||||
|
||||
## 推荐使用顺序
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
+46
-19
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+30
-21
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user