♻️ refactor(substore): 提取重构公共工具 common.js 并新增 liangxin 脚本
This commit is contained in:
+27
-2
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
这个目录放的是一组 Sub-Store 节点处理脚本。推荐把“单订阅专属规则”和“整合订阅全局规则”分开使用,避免一个机场的关键词误伤其他机场。
|
这个目录放的是一组 Sub-Store 节点处理脚本。推荐把“单订阅专属规则”和“整合订阅全局规则”分开使用,避免一个机场的关键词误伤其他机场。
|
||||||
|
|
||||||
|
`common.js` 是公共工具库,各处理脚本会按下面的 raw 地址自动加载:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://git.orionc.me/orion/script/raw/branch/main/substore/common.js
|
||||||
|
```
|
||||||
|
|
||||||
## 推荐使用顺序
|
## 推荐使用顺序
|
||||||
|
|
||||||
单订阅脚本先跑,整合订阅最后跑:
|
单订阅脚本先跑,整合订阅最后跑:
|
||||||
@@ -10,7 +16,8 @@
|
|||||||
2. `yuetong-rename.js`:只清理 YueTong 的品牌前缀和装饰符号。
|
2. `yuetong-rename.js`:只清理 YueTong 的品牌前缀和装饰符号。
|
||||||
3. `peiqian-rename.js`:只清理 PeiQian 的推荐/用途描述。
|
3. `peiqian-rename.js`:只清理 PeiQian 的推荐/用途描述。
|
||||||
4. `alphaair-rename.js`:只处理 Alpha Air 的供应商压缩、倍率排序和连续编号。
|
4. `alphaair-rename.js`:只处理 Alpha Air 的供应商压缩、倍率排序和连续编号。
|
||||||
5. `rename.js`:整合订阅最终重命名,负责国家短码、国旗归位、订阅后缀和去重。
|
5. `liangxin-rename.js`:只清理 LiangXin 的高速、BGP、CMCU 等线路描述。
|
||||||
|
6. `rename.js`:整合订阅最终重命名,负责国家短码、国旗归位、订阅后缀和去重。
|
||||||
|
|
||||||
如果某个订阅没有对应的专属脚本,直接进入最后的 `rename.js` 即可。
|
如果某个订阅没有对应的专属脚本,直接进入最后的 `rename.js` 即可。
|
||||||
|
|
||||||
@@ -22,7 +29,9 @@
|
|||||||
| `yuetong-rename.js` | YueTong 单订阅 | 去掉 `悦·`、`💎`,把 `AI解锁` 压成 `AI` |
|
| `yuetong-rename.js` | YueTong 单订阅 | 去掉 `悦·`、`💎`,把 `AI解锁` 压成 `AI` |
|
||||||
| `peiqian-rename.js` | PeiQian 单订阅 | 压短 `电信联通推荐`、`移动联通推荐`、`高速专线`、`hy2`、倍率和用途描述 |
|
| `peiqian-rename.js` | PeiQian 单订阅 | 压短 `电信联通推荐`、`移动联通推荐`、`高速专线`、`hy2`、倍率和用途描述 |
|
||||||
| `alphaair-rename.js` | Alpha Air 单订阅 | 去掉提供者/三网/家宽文案,普通节点保留地区,高级非倍率节点保留供应商,倍率节点保留倍率并连续编号 |
|
| `alphaair-rename.js` | Alpha Air 单订阅 | 去掉提供者/三网/家宽文案,普通节点保留地区,高级非倍率节点保留供应商,倍率节点保留倍率并连续编号 |
|
||||||
|
| `liangxin-rename.js` | LiangXin 单订阅 | 去掉 `高速`、`BGP`、`CMCU`、`CM`、`CU`、`CT` 等线路描述 |
|
||||||
| `rename.js` | 整合订阅 | 统一国家/城市短码,移动国旗到首部,追加 `- _subName`,处理重名编号 |
|
| `rename.js` | 整合订阅 | 统一国家/城市短码,移动国旗到首部,追加 `- _subName`,处理重名编号 |
|
||||||
|
| `common.js` | 公共依赖 | 被各处理脚本远程加载,不需要单独配置为处理器 |
|
||||||
|
|
||||||
## SNTP 规则
|
## SNTP 规则
|
||||||
|
|
||||||
@@ -110,6 +119,22 @@
|
|||||||
=> 🇭🇰 HK 01
|
=> 🇭🇰 HK 01
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## LiangXin 规则
|
||||||
|
|
||||||
|
`liangxin-rename.js` 建议挂在 LiangXin 单条订阅里使用。它会删除内嵌的线路描述,以及 `|` 分隔出来的纯线路标签:
|
||||||
|
|
||||||
|
```text
|
||||||
|
🇭🇰香港高速01|BGP|CMCU
|
||||||
|
=> 🇭🇰 香港01
|
||||||
|
```
|
||||||
|
|
||||||
|
再经过 `rename.js` 后:
|
||||||
|
|
||||||
|
```text
|
||||||
|
🇭🇰 香港01
|
||||||
|
=> 🇭🇰 HK 01 - LiangXin
|
||||||
|
```
|
||||||
|
|
||||||
## 全局重命名
|
## 全局重命名
|
||||||
|
|
||||||
`rename.js` 适合放在整合订阅最后。它做的事情更通用:
|
`rename.js` 适合放在整合订阅最后。它做的事情更通用:
|
||||||
@@ -140,6 +165,6 @@ Pro-🇭🇰 Sharon|守夜人 - Alpha 8K Snell
|
|||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
- `sntp-rename.js` 不带 `_subName` 判断,适合挂在 SNTP 单订阅里;如果放进整合订阅,可能会误伤其他带 `BGP`、`D`、`CT` 的节点。
|
- `sntp-rename.js` 不带 `_subName` 判断,适合挂在 SNTP 单订阅里;如果放进整合订阅,可能会误伤其他带 `BGP`、`D`、`CT` 的节点。
|
||||||
- `yuetong-rename.js` 和 `peiqian-rename.js` 也不带 `_subName` 判断,适合挂在对应单订阅里;如果放进整合订阅,需要确认规则不会误伤其他订阅。
|
- `yuetong-rename.js`、`peiqian-rename.js` 和 `liangxin-rename.js` 也不带 `_subName` 判断,适合挂在对应单订阅里;如果放进整合订阅,需要确认规则不会误伤其他订阅。
|
||||||
- `rename.js` 会剥离并重新追加 `- _subName`,重复运行不会叠加订阅后缀。
|
- `rename.js` 会剥离并重新追加 `- _subName`,重复运行不会叠加订阅后缀。
|
||||||
- 如果新增订阅里出现很短、很泛的关键词,例如 `D`、`CT`、`BGP`、`S1`,或像 Alpha Air 这种强绑定单机场的供应商压缩规则,优先拆成单订阅脚本,不建议放进全局 `rename.js`。
|
- 如果新增订阅里出现很短、很泛的关键词,例如 `D`、`CT`、`BGP`、`S1`,或像 Alpha Air 这种强绑定单机场的供应商压缩规则,优先拆成单订阅脚本,不建议放进全局 `rename.js`。
|
||||||
|
|||||||
+28
-63
@@ -6,7 +6,27 @@
|
|||||||
* - 不追加订阅后缀,整合订阅最后仍交给 rename.js 处理
|
* - 不追加订阅后缀,整合订阅最后仍交给 rename.js 处理
|
||||||
*/
|
*/
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
function operator(proxies) {
|
const COMMON_SCRIPT_URL = 'https://git.orionc.me/orion/script/raw/branch/main/substore/common.js';
|
||||||
|
|
||||||
|
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();
|
||||||
const regionAliases = [
|
const regionAliases = [
|
||||||
['美国夏威夷', 'US HNL'],
|
['美国夏威夷', 'US HNL'],
|
||||||
['家宽夏威夷', 'US HNL'],
|
['家宽夏威夷', 'US HNL'],
|
||||||
@@ -34,62 +54,12 @@ function operator(proxies) {
|
|||||||
['United Kingdom', 'UK'],
|
['United Kingdom', 'UK'],
|
||||||
['英国', 'UK']
|
['英国', 'UK']
|
||||||
];
|
];
|
||||||
const sortedRegionAliases = regionAliases
|
const regionResolver = utils.createRegionResolver(regionAliases);
|
||||||
.map(([alias, replacement]) => [alias, replacement, alias.toLowerCase()])
|
|
||||||
.sort((firstAlias, secondAlias) => secondAlias[0].length - firstAlias[0].length);
|
|
||||||
const leadingFlagRegex = /^([\u{1F1E6}-\u{1F1FF}]{2})\s*/u;
|
|
||||||
const inlineFlagRegex = /^(.{1,24}?)([\u{1F1E6}-\u{1F1FF}]{2})\s*(.*)$/u;
|
|
||||||
|
|
||||||
const flagToRegionCode = flag => {
|
|
||||||
if (!flag) return '';
|
|
||||||
const codePoints = [...flag].map(char => char.codePointAt(0));
|
|
||||||
if (
|
|
||||||
codePoints.length !== 2 ||
|
|
||||||
codePoints.some(codePoint => codePoint < 0x1F1E6 || codePoint > 0x1F1FF)
|
|
||||||
) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const regionCode = codePoints
|
|
||||||
.map(codePoint => String.fromCharCode(codePoint - 0x1F1E6 + 65))
|
|
||||||
.join('');
|
|
||||||
return regionCode === 'GB' ? 'UK' : regionCode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const regionCodeToFlag = regionCode => {
|
|
||||||
const flagCode = regionCode === 'UK' ? 'GB' : regionCode;
|
|
||||||
if (!/^[A-Z]{2}$/.test(flagCode)) return '';
|
|
||||||
|
|
||||||
return flagCode
|
|
||||||
.split('')
|
|
||||||
.map(char => String.fromCodePoint(char.charCodeAt(0) - 65 + 0x1F1E6))
|
|
||||||
.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const stripExistingSuffix = name => name
|
const stripExistingSuffix = name => name
|
||||||
.replace(/\s+-\s*Alpha Air(?:\s+\d{2})?\s*$/i, '')
|
.replace(/\s+-\s*Alpha Air(?:\s+\d{2})?\s*$/i, '')
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
const stripFlag = name => {
|
|
||||||
const leadingFlagMatch = name.match(leadingFlagRegex);
|
|
||||||
if (leadingFlagMatch) {
|
|
||||||
return {
|
|
||||||
flag: leadingFlagMatch[1],
|
|
||||||
text: name.slice(leadingFlagMatch[0].length).trim()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const inlineFlagMatch = name.match(inlineFlagRegex);
|
|
||||||
if (!inlineFlagMatch) return { flag: '', text: name.trim() };
|
|
||||||
|
|
||||||
const prefix = inlineFlagMatch[1].trim();
|
|
||||||
const rest = inlineFlagMatch[3].trim();
|
|
||||||
return {
|
|
||||||
flag: inlineFlagMatch[2],
|
|
||||||
text: rest ? `${prefix} ${rest}`.trim() : prefix
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const multiplierOf = name => {
|
const multiplierOf = name => {
|
||||||
const match = name.match(/\b(\d+(?:\.\d+)?)(?:\s*[xX]|倍)\b/i);
|
const match = name.match(/\b(\d+(?:\.\d+)?)(?:\s*[xX]|倍)\b/i);
|
||||||
return match ? `${match[1]}X` : '';
|
return match ? `${match[1]}X` : '';
|
||||||
@@ -108,25 +78,20 @@ function operator(proxies) {
|
|||||||
return match[2] ? `${match[1]} ${match[2]}` : match[1];
|
return match[2] ? `${match[1]} ${match[2]}` : match[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
const aliasRegionPrefixOf = name => {
|
const alphaAirRegionPrefixOf = name => {
|
||||||
const cleanNameLower = name.toLowerCase();
|
|
||||||
for (const [, replacement, aliasLower] of sortedRegionAliases) {
|
|
||||||
if (cleanNameLower.includes(aliasLower)) return replacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/\bhk\b/i.test(name)) return 'HK';
|
if (/\bhk\b/i.test(name)) return 'HK';
|
||||||
if (/\bsg\b/i.test(name)) return 'SG';
|
if (/\bsg\b/i.test(name)) return 'SG';
|
||||||
if (/\bjp\b/i.test(name)) return 'JP';
|
if (/\bjp\b/i.test(name)) return 'JP';
|
||||||
if (/\btw\b/i.test(name)) return 'TW';
|
if (/\btw\b/i.test(name)) return 'TW';
|
||||||
if (/\bus\b/i.test(name)) return 'US';
|
if (/\bus\b/i.test(name)) return 'US';
|
||||||
|
|
||||||
return '';
|
return regionResolver.aliasRegionPrefixOf(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
const regionPrefixOf = (name, flagRegionCode) => {
|
const regionPrefixOf = (name, flagRegionCode) => {
|
||||||
if (/Hinet|Seednet/i.test(name)) return 'TW';
|
if (/Hinet|Seednet/i.test(name)) return 'TW';
|
||||||
|
|
||||||
return aliasRegionPrefixOf(name) ||
|
return alphaAirRegionPrefixOf(name) ||
|
||||||
leadingRegionPrefixOf(name) ||
|
leadingRegionPrefixOf(name) ||
|
||||||
flagRegionCode ||
|
flagRegionCode ||
|
||||||
'';
|
'';
|
||||||
@@ -135,14 +100,14 @@ function operator(proxies) {
|
|||||||
const normalizeProxy = (proxy, originalIndex) => {
|
const normalizeProxy = (proxy, originalIndex) => {
|
||||||
const rawName = typeof proxy.name === 'string' ? proxy.name : '';
|
const rawName = typeof proxy.name === 'string' ? proxy.name : '';
|
||||||
const cleanName = stripExistingSuffix(rawName);
|
const cleanName = stripExistingSuffix(rawName);
|
||||||
const { flag, text } = stripFlag(cleanName);
|
const { flag, text } = utils.splitLeadingOrInlineFlag(cleanName);
|
||||||
const flagRegionCode = flagToRegionCode(flag);
|
const flagRegionCode = utils.flagToRegionCode(flag);
|
||||||
const regionPrefix = regionPrefixOf(text, flagRegionCode);
|
const regionPrefix = regionPrefixOf(text, flagRegionCode);
|
||||||
const regionCode = regionPrefix.split(/\s+/)[0];
|
const regionCode = regionPrefix.split(/\s+/)[0];
|
||||||
const cityCode = regionPrefix.split(/\s+/)[1] || '';
|
const cityCode = regionPrefix.split(/\s+/)[1] || '';
|
||||||
const multiplier = multiplierOf(text);
|
const multiplier = multiplierOf(text);
|
||||||
const provider = multiplier ? '' : providerOf(text);
|
const provider = multiplier ? '' : providerOf(text);
|
||||||
const normalizedFlag = regionCodeToFlag(regionCode);
|
const normalizedFlag = utils.regionCodeToFlag(regionCode);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
proxy,
|
proxy,
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
/**
|
||||||
|
* Sub-Store 节点命名公共工具
|
||||||
|
*
|
||||||
|
* 远程引用地址:
|
||||||
|
* https://git.orionc.me/orion/script/raw/branch/main/substore/common.js
|
||||||
|
*/
|
||||||
|
(function (root) {
|
||||||
|
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 escapeRegex = value => [...String(value)]
|
||||||
|
.map(char => regexSpecialChars.has(char) ? `\\${char}` : char)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
const normalizeWhitespace = value => String(value)
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
const normalizeLeadingFlagSpacing = value => String(value)
|
||||||
|
.replace(leadingFlagRegex, '$1 ')
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
const flagToRegionCode = (flag, overrides) => {
|
||||||
|
if (!flag) return '';
|
||||||
|
if (overrides && overrides[flag]) return overrides[flag];
|
||||||
|
|
||||||
|
const codePoints = [...flag].map(char => char.codePointAt(0));
|
||||||
|
if (
|
||||||
|
codePoints.length !== 2 ||
|
||||||
|
codePoints.some(codePoint => codePoint < 0x1F1E6 || codePoint > 0x1F1FF)
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const regionCode = codePoints
|
||||||
|
.map(codePoint => String.fromCharCode(codePoint - 0x1F1E6 + 65))
|
||||||
|
.join('');
|
||||||
|
return regionCode === 'GB' ? 'UK' : regionCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const regionCodeToFlag = regionCode => {
|
||||||
|
const flagCode = regionCode === 'UK' ? 'GB' : regionCode;
|
||||||
|
if (!/^[A-Z]{2}$/.test(flagCode)) return '';
|
||||||
|
|
||||||
|
return flagCode
|
||||||
|
.split('')
|
||||||
|
.map(char => String.fromCodePoint(char.charCodeAt(0) - 65 + 0x1F1E6))
|
||||||
|
.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const splitLeadingFlag = name => {
|
||||||
|
const leadingFlagMatch = String(name).match(leadingFlagRegex);
|
||||||
|
if (!leadingFlagMatch) return { flag: '', text: String(name).trim() };
|
||||||
|
|
||||||
|
return {
|
||||||
|
flag: leadingFlagMatch[1],
|
||||||
|
text: String(name).slice(leadingFlagMatch[0].length).trim()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const splitLeadingOrInlineFlag = name => {
|
||||||
|
const cleanName = String(name);
|
||||||
|
const leadingFlagMatch = cleanName.match(leadingFlagRegex);
|
||||||
|
if (leadingFlagMatch) {
|
||||||
|
return {
|
||||||
|
flag: leadingFlagMatch[1],
|
||||||
|
text: cleanName.slice(leadingFlagMatch[0].length).trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const inlineFlagMatch = cleanName.match(inlineFlagRegex);
|
||||||
|
if (!inlineFlagMatch) return { flag: '', text: cleanName.trim() };
|
||||||
|
|
||||||
|
const prefix = inlineFlagMatch[1].trim();
|
||||||
|
const rest = inlineFlagMatch[3].trim();
|
||||||
|
return {
|
||||||
|
flag: inlineFlagMatch[2],
|
||||||
|
text: rest ? `${prefix} ${rest}`.trim() : prefix
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortRegionAliases = aliases => aliases
|
||||||
|
.map(([alias, replacement]) => [alias, replacement, String(alias).toLowerCase()])
|
||||||
|
.sort((firstAlias, secondAlias) => secondAlias[0].length - firstAlias[0].length);
|
||||||
|
|
||||||
|
const createRegionResolver = aliases => {
|
||||||
|
const sortedAliases = sortRegionAliases(aliases);
|
||||||
|
|
||||||
|
const regionCodeOf = name => {
|
||||||
|
const cleanName = String(name);
|
||||||
|
const leadingFlagMatch = cleanName.match(leadingFlagRegex);
|
||||||
|
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'));
|
||||||
|
if (codeMatch) return replacement;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanNameLower.includes(aliasLower)) return replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const aliasRegionPrefixOf = name => {
|
||||||
|
const cleanNameLower = String(name).toLowerCase();
|
||||||
|
for (const [, replacement, aliasLower] of sortedAliases) {
|
||||||
|
if (cleanNameLower.includes(aliasLower)) return replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeLeadingRegion = name => {
|
||||||
|
const cleanName = String(name).trim();
|
||||||
|
const cleanNameLower = cleanName.toLowerCase();
|
||||||
|
|
||||||
|
for (const [alias, replacement, aliasLower] of sortedAliases) {
|
||||||
|
if (!cleanNameLower.startsWith(aliasLower)) continue;
|
||||||
|
|
||||||
|
const rest = cleanName.slice(alias.length);
|
||||||
|
if (/^[A-Za-z]/.test(alias) && rest && !/^[\s\d[|\-.x]/i.test(rest)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return joinAlias(replacement, rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanName;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sortedAliases,
|
||||||
|
regionCodeOf,
|
||||||
|
aliasRegionPrefixOf,
|
||||||
|
normalizeLeadingRegion
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const joinAlias = (replacement, rest) => {
|
||||||
|
const cleanRest = String(rest || '').trim();
|
||||||
|
if (!cleanRest) return replacement;
|
||||||
|
if (/^[|/\-]/.test(cleanRest)) {
|
||||||
|
return `${replacement}${cleanRest}`;
|
||||||
|
}
|
||||||
|
return `${replacement} ${cleanRest}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeMultiplierText = value => String(value)
|
||||||
|
.replace(/-\s*(\d+(?:\.\d+)?)倍/gi, ' $1X')
|
||||||
|
.replace(/(\d+(?:\.\d+)?)倍/gi, '$1X')
|
||||||
|
.replace(/\b(\d+(?:\.\d+)?)x\b/gi, '$1X');
|
||||||
|
|
||||||
|
const moveMultiplierToEnd = name => {
|
||||||
|
const multipliers = [];
|
||||||
|
const nameWithoutMultiplier = String(name)
|
||||||
|
.replace(/\b(\d+(?:\.\d+)?X)\b/gi, multiplier => {
|
||||||
|
multipliers.push(multiplier.toUpperCase());
|
||||||
|
return ' ';
|
||||||
|
})
|
||||||
|
.replace(/\s+\[/g, ' [')
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (!multipliers.length) return name;
|
||||||
|
return `${nameWithoutMultiplier} ${multipliers.join(' ')}`.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeRegionSequence = (name, options) => {
|
||||||
|
const allowCityCode = Boolean(options && options.allowCityCode);
|
||||||
|
const regionPattern = allowCityCode
|
||||||
|
? '[A-Z]{2}(?:\\s+[A-Z]{3})?'
|
||||||
|
: '[A-Z]{2}';
|
||||||
|
|
||||||
|
return String(name)
|
||||||
|
.replace(new RegExp(`(^|\\s)(${regionPattern})[\\s|\\-]*(\\d{1,2})(?=$|[\\s|\\-[\\]])`, 'g'), (
|
||||||
|
match,
|
||||||
|
prefix,
|
||||||
|
regionCode,
|
||||||
|
sequenceNumber
|
||||||
|
) => {
|
||||||
|
return `${prefix}${regionCode} ${sequenceNumber.padStart(2, '0')}`;
|
||||||
|
})
|
||||||
|
.replace(new RegExp(`(\\b${regionPattern} \\d{2})-\\s*(?=\\d+(?:\\.\\d+)?X\\b)`, 'g'), '$1 ')
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
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 [head, ...segments] = String(name).split('|');
|
||||||
|
const keptSegments = segments
|
||||||
|
.map(segment => segment.trim())
|
||||||
|
.filter(segment => segment && !descriptorOnlyRegex.test(segment));
|
||||||
|
|
||||||
|
const cleanHead = head
|
||||||
|
.replace(bracketDescriptorRegex, ' ')
|
||||||
|
.replace(embeddedDescriptorRegex, '')
|
||||||
|
.replace(leadingFlagRegex, '$1 ')
|
||||||
|
.replace(/\s{2,}/g, ' ')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
return [cleanHead, ...keptSegments]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('|')
|
||||||
|
.replace(/\s+\|/g, '|')
|
||||||
|
.replace(/\|\s+/g, '|')
|
||||||
|
.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
root.SubStoreRenameUtils = {
|
||||||
|
createRegionResolver,
|
||||||
|
escapeRegex,
|
||||||
|
flagToRegionCode,
|
||||||
|
joinAlias,
|
||||||
|
moveMultiplierToEnd,
|
||||||
|
normalizeLeadingFlagSpacing,
|
||||||
|
normalizeMultiplierText,
|
||||||
|
normalizeRegionSequence,
|
||||||
|
normalizeWhitespace,
|
||||||
|
regionCodeToFlag,
|
||||||
|
sortRegionAliases,
|
||||||
|
splitLeadingFlag,
|
||||||
|
splitLeadingOrInlineFlag,
|
||||||
|
stripLineDescriptors
|
||||||
|
};
|
||||||
|
})(typeof globalThis !== 'undefined' ? globalThis : this);
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* LiangXin 专用节点清理脚本
|
||||||
|
* 负责去掉高速、BGP、CMCU 等 LiangXin 线路描述,保留地区、序号和倍率。
|
||||||
|
*/
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
const COMMON_SCRIPT_URL = 'https://git.orionc.me/orion/script/raw/branch/main/substore/common.js';
|
||||||
|
|
||||||
|
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();
|
||||||
|
const lineDescriptorKeywords = [
|
||||||
|
'CMCU',
|
||||||
|
'CUCM',
|
||||||
|
'CMCT',
|
||||||
|
'CTCM',
|
||||||
|
'CUCT',
|
||||||
|
'CTCU',
|
||||||
|
'BGP',
|
||||||
|
'CMI',
|
||||||
|
'CUG',
|
||||||
|
'CN2',
|
||||||
|
'IEPL',
|
||||||
|
'IPLC',
|
||||||
|
'9929',
|
||||||
|
'4837',
|
||||||
|
'CM',
|
||||||
|
'CU',
|
||||||
|
'CT',
|
||||||
|
'高速',
|
||||||
|
'极速',
|
||||||
|
'专线',
|
||||||
|
'中转',
|
||||||
|
'直连',
|
||||||
|
'隧道',
|
||||||
|
'三网',
|
||||||
|
'移动',
|
||||||
|
'联通',
|
||||||
|
'电信'
|
||||||
|
];
|
||||||
|
|
||||||
|
return proxies.map(proxy => {
|
||||||
|
if (typeof proxy.name !== 'string') {
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy.name = utils.stripLineDescriptors(proxy.name, lineDescriptorKeywords);
|
||||||
|
return proxy;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -3,7 +3,28 @@
|
|||||||
* 负责清理推荐、用途、hy2、倍率、线路特征和提供商等 PeiQian 节点文案。
|
* 负责清理推荐、用途、hy2、倍率、线路特征和提供商等 PeiQian 节点文案。
|
||||||
*/
|
*/
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
function operator(proxies) {
|
const COMMON_SCRIPT_URL = 'https://git.orionc.me/orion/script/raw/branch/main/substore/common.js';
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
return proxies.map(proxy => {
|
return proxies.map(proxy => {
|
||||||
if (typeof proxy.name !== 'string') {
|
if (typeof proxy.name !== 'string') {
|
||||||
return proxy;
|
return proxy;
|
||||||
@@ -11,7 +32,6 @@ function operator(proxies) {
|
|||||||
|
|
||||||
proxy.name = proxy.name
|
proxy.name = proxy.name
|
||||||
.replace(/AWS\s*(?=新加坡)/gi, '')
|
.replace(/AWS\s*(?=新加坡)/gi, '')
|
||||||
.replace(/^([\u{1F1E6}-\u{1F1FF}]{2}|🏴☠️)\s*/u, '$1 ')
|
|
||||||
.replace(/\s*\|\s*合适下载使用\s*-\s*(\d+(?:\.\d+)?)倍/gi, ' $1x')
|
.replace(/\s*\|\s*合适下载使用\s*-\s*(\d+(?:\.\d+)?)倍/gi, ' $1x')
|
||||||
.replace(/\s*\|\s*合适下载使用/gi, '')
|
.replace(/\s*\|\s*合适下载使用/gi, '')
|
||||||
.replace(/\s*\|\s*避免晚高峰使用/gi, '')
|
.replace(/\s*\|\s*避免晚高峰使用/gi, '')
|
||||||
@@ -21,8 +41,8 @@ function operator(proxies) {
|
|||||||
.replace(/(\d+(?:\.\d+)?)倍/gi, '$1x')
|
.replace(/(\d+(?:\.\d+)?)倍/gi, '$1x')
|
||||||
.replace(/\s*\[(?:专线|移联|电联|三网|HY2)(?:\s+(?:专线|移联|电联|三网|HY2))*\]/gi, '')
|
.replace(/\s*\[(?:专线|移联|电联|三网|HY2)(?:\s+(?:专线|移联|电联|三网|HY2))*\]/gi, '')
|
||||||
.replace(/\s+\[/g, ' [')
|
.replace(/\s+\[/g, ' [')
|
||||||
.replace(/\s{2,}/g, ' ')
|
.replace(/\s{2,}/g, ' ');
|
||||||
.trim();
|
proxy.name = utils.normalizeLeadingFlagSpacing(proxy.name);
|
||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
});
|
});
|
||||||
|
|||||||
+26
-55
@@ -5,7 +5,27 @@
|
|||||||
* - 基于归一化后的完整名称做精准去重
|
* - 基于归一化后的完整名称做精准去重
|
||||||
*/
|
*/
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
function operator(proxies) {
|
const COMMON_SCRIPT_URL = 'https://git.orionc.me/orion/script/raw/branch/main/substore/common.js';
|
||||||
|
|
||||||
|
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();
|
||||||
const globalCount = Object.create(null);
|
const globalCount = Object.create(null);
|
||||||
const currentCount = Object.create(null);
|
const currentCount = Object.create(null);
|
||||||
const baseNames = new Array(proxies.length);
|
const baseNames = new Array(proxies.length);
|
||||||
@@ -557,29 +577,11 @@ function operator(proxies) {
|
|||||||
);
|
);
|
||||||
Object.values(flagCodeOverrides).forEach(code => knownRegionCodes.add(code));
|
Object.values(flagCodeOverrides).forEach(code => knownRegionCodes.add(code));
|
||||||
|
|
||||||
const regexSpecialChars = new Set(['.', '*', '+', '?', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\']);
|
const escapeRegex = utils.escapeRegex;
|
||||||
const escapeRegex = value => [...value]
|
|
||||||
.map(char => regexSpecialChars.has(char) ? `\\${char}` : char)
|
|
||||||
.join('');
|
|
||||||
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 flagToRegionCode = flag => {
|
const flagToRegionCode = flag => utils.flagToRegionCode(flag, flagCodeOverrides);
|
||||||
if (!flag) return '';
|
|
||||||
if (flagCodeOverrides[flag]) return flagCodeOverrides[flag];
|
|
||||||
|
|
||||||
const codePoints = [...flag].map(char => char.codePointAt(0));
|
|
||||||
if (
|
|
||||||
codePoints.length !== 2 ||
|
|
||||||
codePoints.some(codePoint => codePoint < 0x1F1E6 || codePoint > 0x1F1FF)
|
|
||||||
) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return codePoints
|
|
||||||
.map(codePoint => String.fromCharCode(codePoint - 0x1F1E6 + 65))
|
|
||||||
.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const stripExistingSuffix = (name, subName) => {
|
const stripExistingSuffix = (name, subName) => {
|
||||||
if (!subName) return name.trim();
|
if (!subName) return name.trim();
|
||||||
@@ -589,14 +591,7 @@ function operator(proxies) {
|
|||||||
return name.replace(suffixRegex, '').trim();
|
return name.replace(suffixRegex, '').trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
const joinAlias = (replacement, rest) => {
|
const joinAlias = utils.joinAlias;
|
||||||
const cleanRest = rest.trim();
|
|
||||||
if (!cleanRest) return replacement;
|
|
||||||
if (/^[|/\-]/.test(cleanRest)) {
|
|
||||||
return `${replacement}${cleanRest}`;
|
|
||||||
}
|
|
||||||
return `${replacement} ${cleanRest}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeLeadingRegion = name => {
|
const normalizeLeadingRegion = name => {
|
||||||
const cleanName = name.trim();
|
const cleanName = name.trim();
|
||||||
@@ -665,33 +660,9 @@ function operator(proxies) {
|
|||||||
.trim();
|
.trim();
|
||||||
};
|
};
|
||||||
|
|
||||||
const moveMultiplierToEnd = name => {
|
const moveMultiplierToEnd = utils.moveMultiplierToEnd;
|
||||||
const multipliers = [];
|
|
||||||
const nameWithoutMultiplier = name
|
|
||||||
.replace(/\b(\d+(?:\.\d+)?X)\b/gi, multiplier => {
|
|
||||||
multipliers.push(multiplier.toUpperCase());
|
|
||||||
return ' ';
|
|
||||||
})
|
|
||||||
.replace(/\s+\[/g, ' [')
|
|
||||||
.replace(/\s{2,}/g, ' ')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
if (!multipliers.length) return name;
|
const normalizeRegionSequence = name => utils.normalizeRegionSequence(name, { allowCityCode: true });
|
||||||
return `${nameWithoutMultiplier} ${multipliers.join(' ')}`.trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeRegionSequence = name => name
|
|
||||||
.replace(/(^|\s)([A-Z]{2}(?:\s+[A-Z]{3})?)[\s|\-]*(\d{1,2})(?=$|[\s|\-[\]])/g, (
|
|
||||||
match,
|
|
||||||
prefix,
|
|
||||||
regionCode,
|
|
||||||
sequenceNumber
|
|
||||||
) => {
|
|
||||||
return `${prefix}${regionCode} ${sequenceNumber.padStart(2, '0')}`;
|
|
||||||
})
|
|
||||||
.replace(/(\b[A-Z]{2}(?:\s+[A-Z]{3})? \d{2})-\s*(?=\d+(?:\.\d+)?X\b)/g, '$1 ')
|
|
||||||
.replace(/\s{2,}/g, ' ')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const regionSequenceOf = name => {
|
const regionSequenceOf = name => {
|
||||||
const sequenceMatch = name.match(/^((?:[\u{1F1E6}-\u{1F1FF}]{2}|🏴☠️)\s*)?([A-Z]{2}(?:\s+[A-Z]{3})?)(?:\s+(\d{2}))?(\s.*)?$/u);
|
const sequenceMatch = name.match(/^((?:[\u{1F1E6}-\u{1F1FF}]{2}|🏴☠️)\s*)?([A-Z]{2}(?:\s+[A-Z]{3})?)(?:\s+(\d{2}))?(\s.*)?$/u);
|
||||||
|
|||||||
+27
-13
@@ -4,7 +4,28 @@
|
|||||||
* 维护说明:仅需修改 featureMap 字典即可,底部逻辑永远无需改动。
|
* 维护说明:仅需修改 featureMap 字典即可,底部逻辑永远无需改动。
|
||||||
*/
|
*/
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
function operator(proxies) {
|
const COMMON_SCRIPT_URL = 'https://git.orionc.me/orion/script/raw/branch/main/substore/common.js';
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
// ================= 唯一配置区 =================
|
// ================= 唯一配置区 =================
|
||||||
// 在这里新增或修改关键词。格式:"节点关键词": "想要注入的标签"
|
// 在这里新增或修改关键词。格式:"节点关键词": "想要注入的标签"
|
||||||
// 支持大小写混合,支持包含空格或特殊符号。
|
// 支持大小写混合,支持包含空格或特殊符号。
|
||||||
@@ -38,16 +59,12 @@ function operator(proxies) {
|
|||||||
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 normalizeHiddenFeatureKeyword = keyword => keyword.trim().replace(/\s+\d+(?:\.\d+)?x$/i, '');
|
||||||
const regexSpecialChars = new Set(['.', '*', '+', '?', '^', '$', '{', '}', '(', ')', '|', '[', ']', '\\']);
|
|
||||||
const escapeRegexKeyword = keyword => [...keyword]
|
|
||||||
.map(char => regexSpecialChars.has(char) ? `\\${char}` : char)
|
|
||||||
.join('');
|
|
||||||
for (const featureKeyword of featureKeywords) {
|
for (const featureKeyword of featureKeywords) {
|
||||||
normalizedFeatureMap[normalizeFeatureKeyword(featureKeyword)] = featureMap[featureKeyword];
|
normalizedFeatureMap[normalizeFeatureKeyword(featureKeyword)] = featureMap[featureKeyword];
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildKeywordPattern = keyword => {
|
const buildKeywordPattern = keyword => {
|
||||||
const keywordPattern = keyword.trim().split(/\s+/).filter(Boolean).map(escapeRegexKeyword).join('\\s*');
|
const keywordPattern = keyword.trim().split(/\s+/).filter(Boolean).map(utils.escapeRegex).join('\\s*');
|
||||||
const trimmedKeyword = keyword.trim();
|
const trimmedKeyword = keyword.trim();
|
||||||
if (/^[A-Za-z0-9]/.test(trimmedKeyword) && /[A-Za-z0-9]$/.test(trimmedKeyword)) {
|
if (/^[A-Za-z0-9]/.test(trimmedKeyword) && /[A-Za-z0-9]$/.test(trimmedKeyword)) {
|
||||||
return `\\b${keywordPattern}\\b`;
|
return `\\b${keywordPattern}\\b`;
|
||||||
@@ -68,18 +85,15 @@ function operator(proxies) {
|
|||||||
.replace(/^-+|-+$/g, '')
|
.replace(/^-+|-+$/g, '')
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
const normalizeRegionSequence = proxyName => proxyName
|
|
||||||
.replace(/(^|\s)([A-Z]{2})[-\s]?(\d{1,2})(?=$|[\s-])/g, (match, prefix, regionCode, sequenceNumber) => {
|
|
||||||
return `${prefix}${regionCode} ${sequenceNumber.padStart(2, '0')}`;
|
|
||||||
})
|
|
||||||
.replace(/(\b[A-Z]{2} \d{2})-(?=\d+(?:\.\d+)?x\b)/g, '$1 ');
|
|
||||||
|
|
||||||
const abbreviateDedicatedLineProvider = proxyName => proxyName.replace(
|
const abbreviateDedicatedLineProvider = proxyName => proxyName.replace(
|
||||||
/\b([A-Za-z])[A-Za-z]*\s*专线/g,
|
/\b([A-Za-z])[A-Za-z]*\s*专线/g,
|
||||||
(match, providerInitial) => `${providerInitial.toUpperCase()}专线`
|
(match, providerInitial) => `${providerInitial.toUpperCase()}专线`
|
||||||
);
|
);
|
||||||
|
|
||||||
const normalizeDisplayName = proxyName => abbreviateDedicatedLineProvider(normalizeRegionSequence(proxyName));
|
const normalizeDisplayName = proxyName => abbreviateDedicatedLineProvider(
|
||||||
|
utils.normalizeRegionSequence(proxyName.replace(/\b(\d+(?:\.\d+)?)x\b/gi, '$1X'))
|
||||||
|
.replace(/\b(\d+(?:\.\d+)?)X\b/g, '$1x')
|
||||||
|
);
|
||||||
|
|
||||||
// 2. 动态构建复合正则表达式 (核心引擎)
|
// 2. 动态构建复合正则表达式 (核心引擎)
|
||||||
// 按字符串长度降序排序,彻底解决 "短路匹配" (Short-Circuit) 问题
|
// 按字符串长度降序排序,彻底解决 "短路匹配" (Short-Circuit) 问题
|
||||||
|
|||||||
+27
-55
@@ -4,7 +4,27 @@
|
|||||||
* 按地区稳定归并后重新连续编号,避免专属/普通节点撞号。
|
* 按地区稳定归并后重新连续编号,避免专属/普通节点撞号。
|
||||||
*/
|
*/
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
function operator(proxies) {
|
const COMMON_SCRIPT_URL = 'https://git.orionc.me/orion/script/raw/branch/main/substore/common.js';
|
||||||
|
|
||||||
|
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();
|
||||||
const regionAliases = [
|
const regionAliases = [
|
||||||
['Hong Kong', 'HK'],
|
['Hong Kong', 'HK'],
|
||||||
['香港', 'HK'],
|
['香港', 'HK'],
|
||||||
@@ -47,57 +67,10 @@ function operator(proxies) {
|
|||||||
['荷蘭', 'NL'],
|
['荷蘭', 'NL'],
|
||||||
['NL', 'NL']
|
['NL', 'NL']
|
||||||
];
|
];
|
||||||
const sortedRegionAliases = regionAliases
|
const { regionCodeOf } = utils.createRegionResolver(regionAliases);
|
||||||
.map(([alias, replacement]) => [alias, replacement, alias.toLowerCase()])
|
|
||||||
.sort((firstAlias, secondAlias) => secondAlias[0].length - firstAlias[0].length);
|
|
||||||
const leadingFlagRegex = /^([\u{1F1E6}-\u{1F1FF}]{2})\s*/u;
|
|
||||||
|
|
||||||
const flagToRegionCode = flag => {
|
|
||||||
if (!flag) return '';
|
|
||||||
const codePoints = [...flag].map(char => char.codePointAt(0));
|
|
||||||
if (
|
|
||||||
codePoints.length !== 2 ||
|
|
||||||
codePoints.some(codePoint => codePoint < 0x1F1E6 || codePoint > 0x1F1FF)
|
|
||||||
) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const regionCode = codePoints
|
|
||||||
.map(codePoint => String.fromCharCode(codePoint - 0x1F1E6 + 65))
|
|
||||||
.join('');
|
|
||||||
return regionCode === 'GB' ? 'UK' : regionCode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const regionCodeOf = name => {
|
|
||||||
const leadingFlagMatch = name.match(leadingFlagRegex);
|
|
||||||
if (leadingFlagMatch) return flagToRegionCode(leadingFlagMatch[1]);
|
|
||||||
|
|
||||||
const nameLower = name.toLowerCase();
|
|
||||||
for (const [alias, replacement, aliasLower] of sortedRegionAliases) {
|
|
||||||
if (/^[A-Z]{2}$/.test(alias)) {
|
|
||||||
const codeMatch = name.match(new RegExp(`(^|[^A-Za-z])${alias}([^A-Za-z]|$)`, 'i'));
|
|
||||||
if (codeMatch) return replacement;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nameLower.includes(aliasLower)) return replacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const splitLeadingFlag = name => {
|
|
||||||
const leadingFlagMatch = name.match(leadingFlagRegex);
|
|
||||||
if (!leadingFlagMatch) return { flag: '', text: name.trim() };
|
|
||||||
|
|
||||||
return {
|
|
||||||
flag: leadingFlagMatch[1],
|
|
||||||
text: name.slice(leadingFlagMatch[0].length).trim()
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const withFreshRegionSequence = (name, sequenceNumber) => {
|
const withFreshRegionSequence = (name, sequenceNumber) => {
|
||||||
const { flag, text } = splitLeadingFlag(name);
|
const { flag, text } = utils.splitLeadingFlag(name);
|
||||||
const sequenceText = String(sequenceNumber);
|
const sequenceText = String(sequenceNumber);
|
||||||
let core = text.trim();
|
let core = text.trim();
|
||||||
let feature = '';
|
let feature = '';
|
||||||
@@ -115,10 +88,9 @@ function operator(proxies) {
|
|||||||
|
|
||||||
core = core
|
core = core
|
||||||
.replace(/\s*\d+(?!\d)(?!\s*(?:[xX倍]|\.\d))(?=\s|$)/, '')
|
.replace(/\s*\d+(?!\d)(?!\s*(?:[xX倍]|\.\d))(?=\s|$)/, '')
|
||||||
.replace(/\s{2,}/g, ' ')
|
.replace(/\s{2,}/g, ' ');
|
||||||
.trim();
|
|
||||||
|
|
||||||
return `${flag}${core}${sequenceText}${feature ? ` ${feature}` : ''}`.trim();
|
return utils.normalizeWhitespace(`${flag}${core}${sequenceText}${feature ? ` ${feature}` : ''}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizedItems = proxies.map((proxy, originalIndex) => {
|
const normalizedItems = proxies.map((proxy, originalIndex) => {
|
||||||
@@ -136,8 +108,8 @@ function operator(proxies) {
|
|||||||
.replace(/\s*(?:专属|家宽|高速|专线)(?:\s*(\d+)(?!\s*(?:[xX倍]|\.\d)))?/g, '$1')
|
.replace(/\s*(?:专属|家宽|高速|专线)(?:\s*(\d+)(?!\s*(?:[xX倍]|\.\d)))?/g, '$1')
|
||||||
.replace(/💎/gu, '')
|
.replace(/💎/gu, '')
|
||||||
.replace(/([\u4e00-\u9fff])AI\b/g, '$1 AI')
|
.replace(/([\u4e00-\u9fff])AI\b/g, '$1 AI')
|
||||||
.replace(/\s{2,}/g, ' ')
|
.replace(/\s{2,}/g, ' ');
|
||||||
.trim();
|
proxy.name = utils.normalizeWhitespace(proxy.name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
proxy,
|
proxy,
|
||||||
|
|||||||
Reference in New Issue
Block a user