♻️ refactor(core): 重构节点重命名逻辑并简化功能实现
This commit is contained in:
+12
-26
@@ -19,7 +19,7 @@ https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=20260702
|
|||||||
5. `iriscloud-rename.js`:只处理 Iris Cloud 的入口/落地地区冲突和供应商特征。
|
5. `iriscloud-rename.js`:只处理 Iris Cloud 的入口/落地地区冲突和供应商特征。
|
||||||
6. `liangxin-rename.js`:只清理 LiangXin 的高速、BGP、CMCU 等线路描述。
|
6. `liangxin-rename.js`:只清理 LiangXin 的高速、BGP、CMCU 等线路描述。
|
||||||
7. `yinyun-rename.js`:只处理 YinYun 的中文地区名,并按地区连续编号。
|
7. `yinyun-rename.js`:只处理 YinYun 的中文地区名,并按地区连续编号。
|
||||||
8. `rename.js`:整合订阅最终重命名,负责国家短码、国旗归位、订阅后缀和去重。
|
8. `rename.js`:整合订阅最后追加订阅后缀,并对仍然重名的节点编号。
|
||||||
|
|
||||||
如果某个订阅没有对应的专属脚本,直接进入最后的 `rename.js` 即可。
|
如果某个订阅没有对应的专属脚本,直接进入最后的 `rename.js` 即可。
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=20260702
|
|||||||
| `iriscloud-rename.js` | Iris Cloud 单订阅 | 按 `SG`、`TYO`/`JP`、其余香港的专属规则重判落地地区,去掉入口线路和供应商文案并保留倍率 |
|
| `iriscloud-rename.js` | Iris Cloud 单订阅 | 按 `SG`、`TYO`/`JP`、其余香港的专属规则重判落地地区,去掉入口线路和供应商文案并保留倍率 |
|
||||||
| `liangxin-rename.js` | LiangXin 单订阅 | 去掉 `高速`、`BGP`、`CMCU`、`CM`、`CU`、`CT` 等线路描述 |
|
| `liangxin-rename.js` | LiangXin 单订阅 | 去掉 `高速`、`BGP`、`CMCU`、`CM`、`CU`、`CT` 等线路描述 |
|
||||||
| `yinyun-rename.js` | YinYun 单订阅 | 中文国家/地区名转短码,忽略 `优选`、原始编号和 `(T)`/`(R)`,按地区连续编号 |
|
| `yinyun-rename.js` | YinYun 单订阅 | 中文国家/地区名转短码,忽略 `优选`、原始编号和 `(T)`/`(R)`,按地区连续编号 |
|
||||||
| `rename.js` | 整合订阅 | 统一国家/城市短码,移动国旗到首部,追加 `- _subName`,处理重名编号 |
|
| `rename.js` | 整合订阅 | 保留原节点名,追加 `- _subName`,处理追加后仍然重名的节点 |
|
||||||
| `common.js` | 公共依赖 | 被各处理脚本远程加载,不需要单独配置为处理器 |
|
| `common.js` | 公共依赖 | 被各处理脚本远程加载,不需要单独配置为处理器 |
|
||||||
|
|
||||||
## SNTP 规则
|
## SNTP 规则
|
||||||
@@ -187,37 +187,23 @@ https://git.orionc.me/orion/script/raw/branch/main/substore/common.js?v=20260702
|
|||||||
=> 🇹🇭 TH 01 - YinYun
|
=> 🇹🇭 TH 01 - YinYun
|
||||||
```
|
```
|
||||||
|
|
||||||
## 全局重命名
|
## 全局后缀
|
||||||
|
|
||||||
`rename.js` 适合放在整合订阅最后。它做的事情更通用:
|
`rename.js` 适合放在整合订阅最后。它不会再修改节点原名、地区/线路标签或节点顺序,只处理订阅后缀和重名兜底:
|
||||||
|
|
||||||
全局规则只按节点名结构、地区词、线路词和通用格式匹配,不按机场名或 `_subName` 分支处理;以后修改某个订阅的 `_subName` 不应影响全局规则命中。
|
- 先移除本次订阅已经存在的 `- _subName`(含重跑时的两位编号),再追加一次,避免重复运行叠加后缀。
|
||||||
|
- 不同 `_subName` 的订阅即使原节点名相同,追加后缀后也会自然区分,不会互相编号。
|
||||||
- 国家/地区中文名、英文名转短码,例如 `香港` -> `HK`、`Japan` -> `JP`。
|
- 只有追加后仍然完全重名的节点,才在订阅后缀前追加两位编号。
|
||||||
- 常见城市转短码,例如 `Los Angeles` -> `LAX`、`San Jose` -> `SJC`、`Seattle` -> `SEA`、`东京` -> `TYO`。
|
|
||||||
- 国家/地区别名表覆盖常见英文、中文国家名;城市尽量使用三字 IATA/城市码,避免 `LA` 这类两字码被旗帜操作误识别为国家。
|
|
||||||
- 支持国旗在中间的节点名,例如 `Pro-🇭🇰 Sharon|守夜人` -> `🇭🇰 HK Pro-Sharon|守夜人`。
|
|
||||||
- 支持保留连字符命名里的入口/落地特征,例如 `HK-Go-05-HostHatch` -> `HK 05 GO-HostHatch`。
|
|
||||||
- 将倍率移动到节点核心末尾、订阅后缀之前,例如 `US 2X 02 - Example` -> `US 02 2X - Example`。
|
|
||||||
- 清理 `[DIP China-Hongkong]` 为 `[DIP]`。
|
|
||||||
- 清理 `IEPL 专线 3`、`标准 1`、`高级 1` 这类冗余线路尾号。
|
|
||||||
- 追加订阅后缀 `- _subName`。
|
|
||||||
- 对最终重名节点追加两位编号。
|
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
🇭🇰 Hong Kong 01 [DIP China-Hongkong] - Example
|
Hong Kong 01
|
||||||
=> 🇭🇰 HK 01 [DIP] - Example
|
=> Hong Kong 01 - Example
|
||||||
|
|
||||||
🇯🇵 日本高级 IEPL 专线 3 - Example
|
重复节点
|
||||||
=> 🇯🇵 JP 01 高级 - Example
|
=> 重复节点 01 - Example
|
||||||
|
=> 重复节点 02 - Example
|
||||||
Pro-🇭🇰 Sharon|守夜人 - Example
|
|
||||||
=> 🇭🇰 HK Pro-Sharon|守夜人 - Example
|
|
||||||
|
|
||||||
🇭🇰 HK-Go-05-HostHatch - Example
|
|
||||||
=> 🇭🇰 HK 05 GO-HostHatch - Example
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|||||||
+39
-917
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Sub-Store 节点重命名脚本
|
* Sub-Store 整合订阅后缀脚本
|
||||||
* - 统一节点前缀为地区短码,保留旗帜与订阅后缀
|
* - 保留节点原名,只追加当前订阅的 `_subName`
|
||||||
* - 清理常见冗余线路描述,例如 DIP 地区说明、IEPL 专线
|
* - 重复执行不会叠加相同的订阅后缀
|
||||||
* - 基于归一化后的完整名称做精准去重
|
* - 同名节点追加两位编号,避免节点名重复
|
||||||
*/
|
*/
|
||||||
// noinspection JSUnusedGlobalSymbols
|
// noinspection JSUnusedGlobalSymbols
|
||||||
const COMMON_SCRIPT_VERSION = '20260702-1';
|
const COMMON_SCRIPT_VERSION = '20260702-1';
|
||||||
@@ -33,931 +33,53 @@ async function loadRenameUtils() {
|
|||||||
|
|
||||||
async function operator(proxies) {
|
async function operator(proxies) {
|
||||||
const utils = await loadRenameUtils();
|
const utils = await loadRenameUtils();
|
||||||
const normalizeHyphenatedRegionFeatures = utils.normalizeHyphenatedRegionFeatures || (name => name);
|
const stripSubscriptionSuffix = utils.stripSubscriptionSuffix || ((name, subName) => {
|
||||||
const globalCount = Object.create(null);
|
const cleanSubName = String(subName || '').trim();
|
||||||
const currentCount = Object.create(null);
|
if (!cleanSubName) return String(name);
|
||||||
|
|
||||||
|
const escapeRegex = utils.escapeRegex || (value => String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
||||||
|
return String(name)
|
||||||
|
.replace(new RegExp(`\\s+-\\s*${escapeRegex(cleanSubName)}(?:\\s+\\d{2})?\\s*$`, 'i'), '')
|
||||||
|
.trim();
|
||||||
|
});
|
||||||
|
const nameCounts = Object.create(null);
|
||||||
|
const currentCounts = Object.create(null);
|
||||||
const baseNames = new Array(proxies.length);
|
const baseNames = new Array(proxies.length);
|
||||||
const finalCoreNames = new Array(proxies.length);
|
const coreNames = new Array(proxies.length);
|
||||||
const normalizedCoreNames = new Array(proxies.length);
|
|
||||||
const subNames = new Array(proxies.length);
|
const subNames = new Array(proxies.length);
|
||||||
const sequenceInfos = new Array(proxies.length);
|
|
||||||
const suffixRegexCache = Object.create(null);
|
|
||||||
|
|
||||||
const regionAliases = [
|
|
||||||
['United Arab Emirates', 'AE'],
|
|
||||||
['United States Los Angeles', 'US LAX'],
|
|
||||||
['United States San Jose', 'US SJC'],
|
|
||||||
['United States San Francisco', 'US SFO'],
|
|
||||||
['United States New York', 'US NYC'],
|
|
||||||
['United States Newark', 'US EWR'],
|
|
||||||
['United States Washington', 'US IAD'],
|
|
||||||
['United States Ashburn', 'US IAD'],
|
|
||||||
['United States Dallas', 'US DFW'],
|
|
||||||
['United States Chicago', 'US ORD'],
|
|
||||||
['United States Miami', 'US MIA'],
|
|
||||||
['United States Atlanta', 'US ATL'],
|
|
||||||
['United States Phoenix', 'US PHX'],
|
|
||||||
['United States Denver', 'US DEN'],
|
|
||||||
['United States Las Vegas', 'US LAS'],
|
|
||||||
['United States Portland', 'US PDX'],
|
|
||||||
['United States Boston', 'US BOS'],
|
|
||||||
['United States Seattle', 'US SEA'],
|
|
||||||
['United States Hawaii', 'US HNL'],
|
|
||||||
['Australia Sydney', 'AU SYD'],
|
|
||||||
['Australia Melbourne', 'AU MEL'],
|
|
||||||
['Australia Brisbane', 'AU BNE'],
|
|
||||||
['Australia Perth', 'AU PER'],
|
|
||||||
['Canada Toronto', 'CA TOR'],
|
|
||||||
['Canada Vancouver', 'CA YVR'],
|
|
||||||
['Canada Montreal', 'CA YUL'],
|
|
||||||
['Canada Calgary', 'CA YYC'],
|
|
||||||
['Japan Tokyo', 'JP TYO'],
|
|
||||||
['Japan Osaka', 'JP OSA'],
|
|
||||||
['Japan Nagoya', 'JP NGO'],
|
|
||||||
['South Korea Seoul', 'KR SEL'],
|
|
||||||
['Korea Seoul', 'KR SEL'],
|
|
||||||
['United Kingdom London', 'UK LON'],
|
|
||||||
['Germany Frankfurt', 'DE FRA'],
|
|
||||||
['Germany Berlin', 'DE BER'],
|
|
||||||
['Germany Munich', 'DE MUC'],
|
|
||||||
['France Paris', 'FR PAR'],
|
|
||||||
['Netherlands Amsterdam', 'NL AMS'],
|
|
||||||
['Switzerland Zurich', 'CH ZRH'],
|
|
||||||
['Sweden Stockholm', 'SE STO'],
|
|
||||||
['Norway Oslo', 'NO OSL'],
|
|
||||||
['Denmark Copenhagen', 'DK CPH'],
|
|
||||||
['Finland Helsinki', 'FI HEL'],
|
|
||||||
['Poland Warsaw', 'PL WAW'],
|
|
||||||
['Czech Prague', 'CZ PRG'],
|
|
||||||
['Austria Vienna', 'AT VIE'],
|
|
||||||
['Hungary Budapest', 'HU BUD'],
|
|
||||||
['Italy Milan', 'IT MIL'],
|
|
||||||
['Italy Rome', 'IT ROM'],
|
|
||||||
['Spain Madrid', 'ES MAD'],
|
|
||||||
['Spain Barcelona', 'ES BCN'],
|
|
||||||
['Ireland Dublin', 'IE DUB'],
|
|
||||||
['Portugal Lisbon', 'PT LIS'],
|
|
||||||
['Greece Athens', 'GR ATH'],
|
|
||||||
['Turkey Istanbul', 'TR IST'],
|
|
||||||
['Brazil Sao Paulo', 'BR SAO'],
|
|
||||||
['Brazil Rio de Janeiro', 'BR RIO'],
|
|
||||||
['Argentina Buenos Aires', 'AR BUE'],
|
|
||||||
['Chile Santiago', 'CL SCL'],
|
|
||||||
['Mexico Mexico City', 'MX MEX'],
|
|
||||||
['South Africa Johannesburg', 'ZA JNB'],
|
|
||||||
['South Africa Cape Town', 'ZA CPT'],
|
|
||||||
['UAE Dubai', 'AE DXB'],
|
|
||||||
['Russia St. Petersburg', 'RU LED'],
|
|
||||||
['Russia Moscow', 'RU MOW'],
|
|
||||||
['USA Los Angeles', 'US LAX'],
|
|
||||||
['USA San Jose', 'US SJC'],
|
|
||||||
['USA San Francisco', 'US SFO'],
|
|
||||||
['USA New York', 'US NYC'],
|
|
||||||
['USA Newark', 'US EWR'],
|
|
||||||
['USA Washington', 'US IAD'],
|
|
||||||
['USA Ashburn', 'US IAD'],
|
|
||||||
['USA Dallas', 'US DFW'],
|
|
||||||
['USA Chicago', 'US ORD'],
|
|
||||||
['USA Miami', 'US MIA'],
|
|
||||||
['USA Atlanta', 'US ATL'],
|
|
||||||
['USA Phoenix', 'US PHX'],
|
|
||||||
['USA Denver', 'US DEN'],
|
|
||||||
['USA Las Vegas', 'US LAS'],
|
|
||||||
['USA Portland', 'US PDX'],
|
|
||||||
['USA Boston', 'US BOS'],
|
|
||||||
['USA Seattle', 'US SEA'],
|
|
||||||
['USA Hawaii', 'US HNL'],
|
|
||||||
['United Kingdom', 'UK'],
|
|
||||||
['United States', 'US'],
|
|
||||||
['South Africa', 'ZA'],
|
|
||||||
['Saudi Arabia', 'SA'],
|
|
||||||
['New Zealand', 'NZ'],
|
|
||||||
['Hong Kong', 'HK'],
|
|
||||||
['Netherlands', 'NL'],
|
|
||||||
['Switzerland', 'CH'],
|
|
||||||
['Singapore', 'SG'],
|
|
||||||
['Indonesia', 'ID'],
|
|
||||||
['Philippines', 'PH'],
|
|
||||||
['Azerbaijan', 'AZ'],
|
|
||||||
['Kazakhstan', 'KZ'],
|
|
||||||
['Kyrgyzstan', 'KG'],
|
|
||||||
['Argentina', 'AR'],
|
|
||||||
['Australia', 'AU'],
|
|
||||||
['Macedonia', 'MK'],
|
|
||||||
['Lithuania', 'LT'],
|
|
||||||
['Colombia', 'CO'],
|
|
||||||
['Malaysia', 'MY'],
|
|
||||||
['Thailand', 'TH'],
|
|
||||||
['Vietnam', 'VN'],
|
|
||||||
['Romania', 'RO'],
|
|
||||||
['Nigeria', 'NG'],
|
|
||||||
['Bulgaria', 'BG'],
|
|
||||||
['Germany', 'DE'],
|
|
||||||
['Hungary', 'HU'],
|
|
||||||
['Austria', 'AT'],
|
|
||||||
['Ireland', 'IE'],
|
|
||||||
['Turkey', 'TR'],
|
|
||||||
['Taiwan', 'TW'],
|
|
||||||
['Canada', 'CA'],
|
|
||||||
['Brazil', 'BR'],
|
|
||||||
['Russia', 'RU'],
|
|
||||||
['France', 'FR'],
|
|
||||||
['Sweden', 'SE'],
|
|
||||||
['Belgium', 'BE'],
|
|
||||||
['Denmark', 'DK'],
|
|
||||||
['Iceland', 'IS'],
|
|
||||||
['Norway', 'NO'],
|
|
||||||
['Morocco', 'MA'],
|
|
||||||
['Mexico', 'MX'],
|
|
||||||
['Israel', 'IL'],
|
|
||||||
['Spain', 'ES'],
|
|
||||||
['Italy', 'IT'],
|
|
||||||
['Czech', 'CZ'],
|
|
||||||
['Egypt', 'EG'],
|
|
||||||
['Chile', 'CL'],
|
|
||||||
['India', 'IN'],
|
|
||||||
['Korea', 'KR'],
|
|
||||||
['Japan', 'JP'],
|
|
||||||
['Macau', 'MO'],
|
|
||||||
['Dubai', 'AE DXB'],
|
|
||||||
['Sydney', 'AU SYD'],
|
|
||||||
['Melbourne', 'AU MEL'],
|
|
||||||
['Brisbane', 'AU BNE'],
|
|
||||||
['Perth', 'AU PER'],
|
|
||||||
['Toronto', 'CA TOR'],
|
|
||||||
['Vancouver', 'CA YVR'],
|
|
||||||
['Montreal', 'CA YUL'],
|
|
||||||
['Calgary', 'CA YYC'],
|
|
||||||
['Casablanca', 'MA CMN'],
|
|
||||||
['Tokyo', 'JP TYO'],
|
|
||||||
['Osaka', 'JP OSA'],
|
|
||||||
['Nagoya', 'JP NGO'],
|
|
||||||
['Seoul', 'KR SEL'],
|
|
||||||
['London', 'UK LON'],
|
|
||||||
['Frankfurt', 'DE FRA'],
|
|
||||||
['Berlin', 'DE BER'],
|
|
||||||
['Munich', 'DE MUC'],
|
|
||||||
['Paris', 'FR PAR'],
|
|
||||||
['Amsterdam', 'NL AMS'],
|
|
||||||
['Zurich', 'CH ZRH'],
|
|
||||||
['Stockholm', 'SE STO'],
|
|
||||||
['Oslo', 'NO OSL'],
|
|
||||||
['Copenhagen', 'DK CPH'],
|
|
||||||
['Helsinki', 'FI HEL'],
|
|
||||||
['Warsaw', 'PL WAW'],
|
|
||||||
['Prague', 'CZ PRG'],
|
|
||||||
['Vienna', 'AT VIE'],
|
|
||||||
['Budapest', 'HU BUD'],
|
|
||||||
['Milan', 'IT MIL'],
|
|
||||||
['Rome', 'IT ROM'],
|
|
||||||
['Madrid', 'ES MAD'],
|
|
||||||
['Barcelona', 'ES BCN'],
|
|
||||||
['Dublin', 'IE DUB'],
|
|
||||||
['Lisbon', 'PT LIS'],
|
|
||||||
['Athens', 'GR ATH'],
|
|
||||||
['Istanbul', 'TR IST'],
|
|
||||||
['Sao Paulo', 'BR SAO'],
|
|
||||||
['Rio de Janeiro', 'BR RIO'],
|
|
||||||
['Buenos Aires', 'AR BUE'],
|
|
||||||
['Santiago', 'CL SCL'],
|
|
||||||
['Mexico City', 'MX MEX'],
|
|
||||||
['Johannesburg', 'ZA JNB'],
|
|
||||||
['Cape Town', 'ZA CPT'],
|
|
||||||
['USA', 'US'],
|
|
||||||
|
|
||||||
['Bosnia and Herzegovina', 'BA'],
|
|
||||||
['Trinidad and Tobago', 'TT'],
|
|
||||||
['Saint Vincent and the Grenadines', 'VC'],
|
|
||||||
['Antigua and Barbuda', 'AG'],
|
|
||||||
['Saint Kitts and Nevis', 'KN'],
|
|
||||||
['Sao Tome and Principe', 'ST'],
|
|
||||||
['Central African Republic', 'CF'],
|
|
||||||
['Democratic Republic of the Congo', 'CD'],
|
|
||||||
['Republic of the Congo', 'CG'],
|
|
||||||
['Dominican Republic', 'DO'],
|
|
||||||
['Papua New Guinea', 'PG'],
|
|
||||||
['Equatorial Guinea', 'GQ'],
|
|
||||||
['Cote d Ivoire', 'CI'],
|
|
||||||
['Cote d’Ivoire', 'CI'],
|
|
||||||
['Ivory Coast', 'CI'],
|
|
||||||
['Czech Republic', 'CZ'],
|
|
||||||
['North Macedonia', 'MK'],
|
|
||||||
['Cayman Islands', 'KY'],
|
|
||||||
['French Guiana', 'GF'],
|
|
||||||
['French Polynesia', 'PF'],
|
|
||||||
['New Caledonia', 'NC'],
|
|
||||||
['Puerto Rico', 'PR'],
|
|
||||||
['Virgin Islands', 'VI'],
|
|
||||||
['British Virgin Islands', 'VG'],
|
|
||||||
['Faroe Islands', 'FO'],
|
|
||||||
['Isle of Man', 'IM'],
|
|
||||||
['Greenland', 'GL'],
|
|
||||||
['Guam', 'GU'],
|
|
||||||
['Bermuda', 'BM'],
|
|
||||||
['Armenia', 'AM'],
|
|
||||||
['Albania', 'AL'],
|
|
||||||
['Andorra', 'AD'],
|
|
||||||
['Belarus', 'BY'],
|
|
||||||
['Croatia', 'HR'],
|
|
||||||
['Cyprus', 'CY'],
|
|
||||||
['Estonia', 'EE'],
|
|
||||||
['Finland', 'FI'],
|
|
||||||
['Greece', 'GR'],
|
|
||||||
['Latvia', 'LV'],
|
|
||||||
['Luxembourg', 'LU'],
|
|
||||||
['Malta', 'MT'],
|
|
||||||
['Moldova', 'MD'],
|
|
||||||
['Montenegro', 'ME'],
|
|
||||||
['Poland', 'PL'],
|
|
||||||
['Portugal', 'PT'],
|
|
||||||
['Serbia', 'RS'],
|
|
||||||
['Slovakia', 'SK'],
|
|
||||||
['Slovenia', 'SI'],
|
|
||||||
['Ukraine', 'UA'],
|
|
||||||
['Romania', 'RO'],
|
|
||||||
['UAE', 'AE'],
|
|
||||||
['Qatar', 'QA'],
|
|
||||||
['Kuwait', 'KW'],
|
|
||||||
['Bahrain', 'BH'],
|
|
||||||
['Oman', 'OM'],
|
|
||||||
['Jordan', 'JO'],
|
|
||||||
['Lebanon', 'LB'],
|
|
||||||
['Palestine', 'PS'],
|
|
||||||
['Pakistan', 'PK'],
|
|
||||||
['Bangladesh', 'BD'],
|
|
||||||
['Sri Lanka', 'LK'],
|
|
||||||
['Nepal', 'NP'],
|
|
||||||
['Maldives', 'MV'],
|
|
||||||
['Kazakhstan', 'KZ'],
|
|
||||||
['Uzbekistan', 'UZ'],
|
|
||||||
['Tajikistan', 'TJ'],
|
|
||||||
['Turkmenistan', 'TM'],
|
|
||||||
['Afghanistan', 'AF'],
|
|
||||||
['Cambodia', 'KH'],
|
|
||||||
['Myanmar', 'MM'],
|
|
||||||
['Laos', 'LA'],
|
|
||||||
['Brunei', 'BN'],
|
|
||||||
['Mongolia', 'MN'],
|
|
||||||
['Ethiopia', 'ET'],
|
|
||||||
['Kenya', 'KE'],
|
|
||||||
['Tanzania', 'TZ'],
|
|
||||||
['Uganda', 'UG'],
|
|
||||||
['Ghana', 'GH'],
|
|
||||||
['Senegal', 'SN'],
|
|
||||||
['Cameroon', 'CM'],
|
|
||||||
['Angola', 'AO'],
|
|
||||||
['Algeria', 'DZ'],
|
|
||||||
['Tunisia', 'TN'],
|
|
||||||
['Libya', 'LY'],
|
|
||||||
['Zimbabwe', 'ZW'],
|
|
||||||
['Zambia', 'ZM'],
|
|
||||||
['Botswana', 'BW'],
|
|
||||||
['Mauritius', 'MU'],
|
|
||||||
['Seychelles', 'SC'],
|
|
||||||
['Madagascar', 'MG'],
|
|
||||||
['Panama', 'PA'],
|
|
||||||
['Costa Rica', 'CR'],
|
|
||||||
['Guatemala', 'GT'],
|
|
||||||
['Honduras', 'HN'],
|
|
||||||
['El Salvador', 'SV'],
|
|
||||||
['Nicaragua', 'NI'],
|
|
||||||
['Jamaica', 'JM'],
|
|
||||||
['Bahamas', 'BS'],
|
|
||||||
['Barbados', 'BB'],
|
|
||||||
['Uruguay', 'UY'],
|
|
||||||
['Paraguay', 'PY'],
|
|
||||||
['Bolivia', 'BO'],
|
|
||||||
['Peru', 'PE'],
|
|
||||||
['Ecuador', 'EC'],
|
|
||||||
['Venezuela', 'VE'],
|
|
||||||
['Suriname', 'SR'],
|
|
||||||
['Guyana', 'GY'],
|
|
||||||
|
|
||||||
['美国圣何塞', 'US SJC'],
|
|
||||||
['美国洛杉矶', 'US LAX'],
|
|
||||||
['美国旧金山', 'US SFO'],
|
|
||||||
['美国纽约', 'US NYC'],
|
|
||||||
['美国纽瓦克', 'US EWR'],
|
|
||||||
['美国华盛顿', 'US IAD'],
|
|
||||||
['美国阿什本', 'US IAD'],
|
|
||||||
['美国达拉斯', 'US DFW'],
|
|
||||||
['美国芝加哥', 'US ORD'],
|
|
||||||
['美国迈阿密', 'US MIA'],
|
|
||||||
['美国亚特兰大', 'US ATL'],
|
|
||||||
['美国凤凰城', 'US PHX'],
|
|
||||||
['美国丹佛', 'US DEN'],
|
|
||||||
['美国拉斯维加斯', 'US LAS'],
|
|
||||||
['美国波特兰', 'US PDX'],
|
|
||||||
['美国波士顿', 'US BOS'],
|
|
||||||
['美国夏威夷', 'US HNL'],
|
|
||||||
['日本东京', 'JP TYO'],
|
|
||||||
['日本大阪', 'JP OSA'],
|
|
||||||
['日本名古屋', 'JP NGO'],
|
|
||||||
['韩国首尔', 'KR SEL'],
|
|
||||||
['英国伦敦', 'UK LON'],
|
|
||||||
['瑞士苏黎世', 'CH ZRH'],
|
|
||||||
['德国法兰克福', 'DE FRA'],
|
|
||||||
['德国柏林', 'DE BER'],
|
|
||||||
['德国慕尼黑', 'DE MUC'],
|
|
||||||
['法国巴黎', 'FR PAR'],
|
|
||||||
['荷兰阿姆斯特丹', 'NL AMS'],
|
|
||||||
['瑞典斯德哥尔摩', 'SE STO'],
|
|
||||||
['挪威奥斯陆', 'NO OSL'],
|
|
||||||
['丹麦哥本哈根', 'DK CPH'],
|
|
||||||
['芬兰赫尔辛基', 'FI HEL'],
|
|
||||||
['波兰华沙', 'PL WAW'],
|
|
||||||
['捷克布拉格', 'CZ PRG'],
|
|
||||||
['奥地利维也纳', 'AT VIE'],
|
|
||||||
['匈牙利布达佩斯', 'HU BUD'],
|
|
||||||
['意大利米兰', 'IT MIL'],
|
|
||||||
['意大利罗马', 'IT ROM'],
|
|
||||||
['西班牙马德里', 'ES MAD'],
|
|
||||||
['西班牙巴塞罗那', 'ES BCN'],
|
|
||||||
['爱尔兰都柏林', 'IE DUB'],
|
|
||||||
['葡萄牙里斯本', 'PT LIS'],
|
|
||||||
['希腊雅典', 'GR ATH'],
|
|
||||||
['土耳其伊斯坦布尔', 'TR IST'],
|
|
||||||
['巴西圣保罗', 'BR SAO'],
|
|
||||||
['巴西里约热内卢', 'BR RIO'],
|
|
||||||
['阿根廷布宜诺斯艾利斯', 'AR BUE'],
|
|
||||||
['智利圣地亚哥', 'CL SCL'],
|
|
||||||
['墨西哥墨西哥城', 'MX MEX'],
|
|
||||||
['南非约翰内斯堡', 'ZA JNB'],
|
|
||||||
['南非开普敦', 'ZA CPT'],
|
|
||||||
['印度尼西亚', 'ID'],
|
|
||||||
['所罗门群岛', 'SB'],
|
|
||||||
['波斯尼亚和黑塞哥维那', 'BA'],
|
|
||||||
['特立尼达和多巴哥', 'TT'],
|
|
||||||
['圣文森特和格林纳丁斯', 'VC'],
|
|
||||||
['安提瓜和巴布达', 'AG'],
|
|
||||||
['圣基茨和尼维斯', 'KN'],
|
|
||||||
['圣多美和普林西比', 'ST'],
|
|
||||||
['中非共和国', 'CF'],
|
|
||||||
['刚果民主共和国', 'CD'],
|
|
||||||
['刚果共和国', 'CG'],
|
|
||||||
['多米尼加共和国', 'DO'],
|
|
||||||
['巴布亚新几内亚', 'PG'],
|
|
||||||
['赤道几内亚', 'GQ'],
|
|
||||||
['沙特阿拉伯', 'SA'],
|
|
||||||
['捷克共和国', 'CZ'],
|
|
||||||
['北马其顿', 'MK'],
|
|
||||||
['开曼群岛', 'KY'],
|
|
||||||
['法属圭亚那', 'GF'],
|
|
||||||
['法属波利尼西亚', 'PF'],
|
|
||||||
['新喀里多尼亚', 'NC'],
|
|
||||||
['波多黎各', 'PR'],
|
|
||||||
['美属维尔京群岛', 'VI'],
|
|
||||||
['英属维尔京群岛', 'VG'],
|
|
||||||
['阿塞拜疆', 'AZ'],
|
|
||||||
['哈萨克斯坦', 'KZ'],
|
|
||||||
['吉尔吉斯斯坦', 'KG'],
|
|
||||||
['卡萨布兰卡', 'MA CMN'],
|
|
||||||
['澳大利亚', 'AU'],
|
|
||||||
['新西兰', 'NZ'],
|
|
||||||
['马来西亚', 'MY'],
|
|
||||||
['尼日利亚', 'NG'],
|
|
||||||
['摩尔多瓦', 'MD'],
|
|
||||||
['巴基斯坦', 'PK'],
|
|
||||||
['阿根廷', 'AR'],
|
|
||||||
['阿联酋', 'AE'],
|
|
||||||
['柬埔寨', 'KH'],
|
|
||||||
['东帝汶', 'TL'],
|
|
||||||
['梵蒂冈', 'VA'],
|
|
||||||
['百慕大', 'BM'],
|
|
||||||
['格陵兰', 'GL'],
|
|
||||||
['南极洲', 'AQ'],
|
|
||||||
['罗马尼亚', 'RO'],
|
|
||||||
['哥伦比亚', 'CO'],
|
|
||||||
['菲律宾', 'PH'],
|
|
||||||
['以色列', 'IL'],
|
|
||||||
['比利时', 'BE'],
|
|
||||||
['葡萄牙', 'PT'],
|
|
||||||
['波兰', 'PL'],
|
|
||||||
['希腊', 'GR'],
|
|
||||||
['芬兰', 'FI'],
|
|
||||||
['爱尔兰', 'IE'],
|
|
||||||
['克罗地亚', 'HR'],
|
|
||||||
['斯洛伐克', 'SK'],
|
|
||||||
['斯洛文尼亚', 'SI'],
|
|
||||||
['塞尔维亚', 'RS'],
|
|
||||||
['保加利亚', 'BG'],
|
|
||||||
['白俄罗斯', 'BY'],
|
|
||||||
['爱沙尼亚', 'EE'],
|
|
||||||
['拉脱维亚', 'LV'],
|
|
||||||
['卢森堡', 'LU'],
|
|
||||||
['塞浦路斯', 'CY'],
|
|
||||||
['马耳他', 'MT'],
|
|
||||||
['阿尔巴尼亚', 'AL'],
|
|
||||||
['黑山', 'ME'],
|
|
||||||
['安道尔', 'AD'],
|
|
||||||
['列支敦士登', 'LI'],
|
|
||||||
['摩纳哥', 'MC'],
|
|
||||||
['圣马力诺', 'SM'],
|
|
||||||
['直布罗陀', 'GI'],
|
|
||||||
['立陶宛', 'LT'],
|
|
||||||
['马其顿', 'MK'],
|
|
||||||
['俄罗斯', 'RU'],
|
|
||||||
['乌克兰', 'UA'],
|
|
||||||
['匈牙利', 'HU'],
|
|
||||||
['西班牙', 'ES'],
|
|
||||||
['土耳其', 'TR'],
|
|
||||||
['加拿大', 'CA'],
|
|
||||||
['意大利', 'IT'],
|
|
||||||
['奥地利', 'AT'],
|
|
||||||
['阿富汗', 'AF'],
|
|
||||||
['卡塔尔', 'QA'],
|
|
||||||
['索马里', 'SO'],
|
|
||||||
['新加坡', 'SG'],
|
|
||||||
['印度', 'IN'],
|
|
||||||
['印尼', 'ID'],
|
|
||||||
['日本', 'JP'],
|
|
||||||
['香港', 'HK'],
|
|
||||||
['台湾', 'TW'],
|
|
||||||
['美国', 'US'],
|
|
||||||
['韩国', 'KR'],
|
|
||||||
['英国', 'UK'],
|
|
||||||
['德国', 'DE'],
|
|
||||||
['荷兰', 'NL'],
|
|
||||||
['法国', 'FR'],
|
|
||||||
['美西', 'US'],
|
|
||||||
['巴西', 'BR'],
|
|
||||||
['智利', 'CL'],
|
|
||||||
['澳门', 'MO'],
|
|
||||||
['泰国', 'TH'],
|
|
||||||
['越南', 'VN'],
|
|
||||||
['瑞典', 'SE'],
|
|
||||||
['丹麦', 'DK'],
|
|
||||||
['多哥', 'TG'],
|
|
||||||
['埃及', 'EG'],
|
|
||||||
['冰岛', 'IS'],
|
|
||||||
['挪威', 'NO'],
|
|
||||||
['南非', 'ZA'],
|
|
||||||
['古巴', 'CU'],
|
|
||||||
['斐济', 'FJ'],
|
|
||||||
['关岛', 'GU'],
|
|
||||||
['缅甸', 'MM'],
|
|
||||||
['蒙古', 'MN'],
|
|
||||||
['老挝', 'LA'],
|
|
||||||
['捷克', 'CZ'],
|
|
||||||
['沙特', 'SA'],
|
|
||||||
['墨西哥', 'MX'],
|
|
||||||
['夏威夷', 'US HNL'],
|
|
||||||
['迪拜', 'AE DXB'],
|
|
||||||
['悉尼', 'AU SYD'],
|
|
||||||
['墨尔本', 'AU MEL'],
|
|
||||||
['布里斯班', 'AU BNE'],
|
|
||||||
['珀斯', 'AU PER'],
|
|
||||||
['多伦多', 'CA TOR'],
|
|
||||||
['温哥华', 'CA YVR'],
|
|
||||||
['蒙特利尔', 'CA YUL'],
|
|
||||||
['卡尔加里', 'CA YYC'],
|
|
||||||
['埃塞俄比亚', 'ET'],
|
|
||||||
['肯尼亚', 'KE'],
|
|
||||||
['坦桑尼亚', 'TZ'],
|
|
||||||
['乌干达', 'UG'],
|
|
||||||
['加纳', 'GH'],
|
|
||||||
['科特迪瓦', 'CI'],
|
|
||||||
['塞内加尔', 'SN'],
|
|
||||||
['喀麦隆', 'CM'],
|
|
||||||
['安哥拉', 'AO'],
|
|
||||||
['苏丹', 'SD'],
|
|
||||||
['阿尔及利亚', 'DZ'],
|
|
||||||
['突尼斯', 'TN'],
|
|
||||||
['利比亚', 'LY'],
|
|
||||||
['津巴布韦', 'ZW'],
|
|
||||||
['赞比亚', 'ZM'],
|
|
||||||
['博茨瓦纳', 'BW'],
|
|
||||||
['毛里求斯', 'MU'],
|
|
||||||
['塞舌尔', 'SC'],
|
|
||||||
['马达加斯加', 'MG'],
|
|
||||||
['尼泊尔', 'NP'],
|
|
||||||
['孟加拉国', 'BD'],
|
|
||||||
['斯里兰卡', 'LK'],
|
|
||||||
['哈萨克斯坦', 'KZ'],
|
|
||||||
['乌兹别克斯坦', 'UZ'],
|
|
||||||
['塔吉克斯坦', 'TJ'],
|
|
||||||
['土库曼斯坦', 'TM'],
|
|
||||||
['伊朗', 'IR'],
|
|
||||||
['伊拉克', 'IQ'],
|
|
||||||
['约旦', 'JO'],
|
|
||||||
['黎巴嫩', 'LB'],
|
|
||||||
['巴勒斯坦', 'PS'],
|
|
||||||
['科威特', 'KW'],
|
|
||||||
['巴林', 'BH'],
|
|
||||||
['阿曼', 'OM'],
|
|
||||||
['也门', 'YE'],
|
|
||||||
['叙利亚', 'SY'],
|
|
||||||
['卡塔尔', 'QA'],
|
|
||||||
['马尔代夫', 'MV'],
|
|
||||||
['文莱', 'BN'],
|
|
||||||
['新喀里多尼亚', 'NC'],
|
|
||||||
['巴拿马', 'PA'],
|
|
||||||
['哥斯达黎加', 'CR'],
|
|
||||||
['危地马拉', 'GT'],
|
|
||||||
['洪都拉斯', 'HN'],
|
|
||||||
['萨尔瓦多', 'SV'],
|
|
||||||
['尼加拉瓜', 'NI'],
|
|
||||||
['牙买加', 'JM'],
|
|
||||||
['海地', 'HT'],
|
|
||||||
['巴哈马', 'BS'],
|
|
||||||
['巴巴多斯', 'BB'],
|
|
||||||
['乌拉圭', 'UY'],
|
|
||||||
['巴拉圭', 'PY'],
|
|
||||||
['玻利维亚', 'BO'],
|
|
||||||
['秘鲁', 'PE'],
|
|
||||||
['厄瓜多尔', 'EC'],
|
|
||||||
['委内瑞拉', 'VE'],
|
|
||||||
['苏里南', 'SR'],
|
|
||||||
['圭亚那', 'GY']
|
|
||||||
];
|
|
||||||
|
|
||||||
const sortedRegionAliases = regionAliases
|
|
||||||
.map(([alias, replacement]) => [alias, replacement, alias.toLowerCase()])
|
|
||||||
.sort((firstAlias, secondAlias) => secondAlias[0].length - firstAlias[0].length);
|
|
||||||
|
|
||||||
const flagCodeOverrides = {
|
|
||||||
'🇬🇧': 'UK',
|
|
||||||
'🇼🇸': 'TW'
|
|
||||||
};
|
|
||||||
|
|
||||||
const knownRegionCodes = new Set(
|
|
||||||
regionAliases.map(([, replacement]) => replacement.split(/\s+/)[0])
|
|
||||||
);
|
|
||||||
Object.values(flagCodeOverrides).forEach(code => knownRegionCodes.add(code));
|
|
||||||
const knownCityCodes = new Set(
|
|
||||||
regionAliases
|
|
||||||
.map(([, replacement]) => replacement.split(/\s+/)[1])
|
|
||||||
.filter(code => /^[A-Z]{3}$/.test(code || ''))
|
|
||||||
);
|
|
||||||
|
|
||||||
const escapeRegex = utils.escapeRegex;
|
|
||||||
const leadingFlagRegex = /^([\u{1F1E6}-\u{1F1FF}]{2}|🏴☠️)\s*/u;
|
|
||||||
const inlineFlagRegex = /^(.{1,24}?)([\u{1F1E6}-\u{1F1FF}]{2}|🏴☠️)\s*(.*)$/u;
|
|
||||||
const normalizeLeadingMojibakeFlag = utils.normalizeLeadingMojibakeFlag
|
|
||||||
|| utils.stripLeadingMojibakeFlag
|
|
||||||
|| (name => String(name).trim());
|
|
||||||
|
|
||||||
const flagToRegionCode = flag => utils.flagToRegionCode(flag, flagCodeOverrides);
|
|
||||||
|
|
||||||
const providerAliases = [
|
|
||||||
['搬瓦工', 'BWH'],
|
|
||||||
['Bandwagon', 'BWH'],
|
|
||||||
['BWG', 'BWH'],
|
|
||||||
['BWH', 'BWH'],
|
|
||||||
['SkyStroll', 'SkyStroll'],
|
|
||||||
['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();
|
|
||||||
const suffixRegex = suffixRegexCache[subName] || (
|
|
||||||
suffixRegexCache[subName] = new RegExp(`\\s+-\\s*${escapeRegex(subName)}(?:\\s+\\d{2})?\\s*$`, 'i')
|
|
||||||
);
|
|
||||||
return name.replace(suffixRegex, '').trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
const joinAlias = utils.joinAlias;
|
|
||||||
|
|
||||||
const normalizeLeadingRegion = name => {
|
|
||||||
const cleanName = name.trim();
|
|
||||||
const cleanNameLower = cleanName.toLowerCase();
|
|
||||||
|
|
||||||
for (const [alias, replacement, aliasLower] of sortedRegionAliases) {
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const leadingCodeOf = name => {
|
|
||||||
const match = name.match(/^([A-Z]{2})(?=\b|\s|-|$|\d)/);
|
|
||||||
return match ? match[1] : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasKnownRegionCode = name => {
|
|
||||||
const leadingCode = leadingCodeOf(name);
|
|
||||||
if (knownRegionCodes.has(leadingCode)) return true;
|
|
||||||
|
|
||||||
const proMatch = name.match(/^Pro-\s*(.+)$/i);
|
|
||||||
return proMatch ? knownRegionCodes.has(leadingCodeOf(proMatch[1])) : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasSpecificRegionCode = (name, regionCode) => {
|
|
||||||
if (!regionCode) return false;
|
|
||||||
if (leadingCodeOf(name) === regionCode) return true;
|
|
||||||
|
|
||||||
const proMatch = name.match(/^Pro-\s*(.+)$/i);
|
|
||||||
return proMatch ? leadingCodeOf(proMatch[1]) === regionCode : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const moveProRegionToFront = (name, regionCode) => {
|
|
||||||
if (!regionCode) return name;
|
|
||||||
|
|
||||||
const proRegionMatch = name.match(/^Pro-\s*([A-Z]{2})(.*)$/i);
|
|
||||||
if (!proRegionMatch || proRegionMatch[1].toUpperCase() !== regionCode) return name;
|
|
||||||
|
|
||||||
const rest = proRegionMatch[2].trim();
|
|
||||||
return rest ? `${regionCode} Pro-${rest}` : regionCode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const providerOf = name => {
|
|
||||||
const cleanName = String(name);
|
|
||||||
for (const [provider, aliasRegex] of providerAliasMatchers) {
|
|
||||||
if (aliasRegex.test(cleanName)) return provider;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const embeddedRegionPrefixOf = (name, fallbackRegionCode) => {
|
|
||||||
if (fallbackRegionCode) return fallbackRegionCode;
|
|
||||||
|
|
||||||
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);
|
|
||||||
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;
|
|
||||||
|
|
||||||
const matchIndex = match.index + (match[1] ? match[1].length : 0);
|
|
||||||
if (matchIndex < bestIndex) {
|
|
||||||
bestIndex = matchIndex;
|
|
||||||
bestRegion = regionCode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestRegion;
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeProviderRegionName = (name, fallbackRegionCode) => {
|
|
||||||
if (/^[A-Z]{2}(?:\s+[A-Z]{3})?\s+\d{2}(?=$|\s)/.test(name)) return name;
|
|
||||||
|
|
||||||
const provider = providerOf(name);
|
|
||||||
if (!provider) return name;
|
|
||||||
|
|
||||||
const regionPrefix = embeddedRegionPrefixOf(name, fallbackRegionCode);
|
|
||||||
return regionPrefix ? `${regionPrefix} ${provider}` : name;
|
|
||||||
};
|
|
||||||
|
|
||||||
const simplifyCoreName = name => {
|
|
||||||
return name
|
|
||||||
.replace(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\uFE0F]/gu, '')
|
|
||||||
.replace(/\u200d/g, '')
|
|
||||||
.replace(/\s*\|\s*流媒体/gi, '')
|
|
||||||
.replace(/-\s*(\d+(?:\.\d+)?)倍/gi, ' $1X')
|
|
||||||
.replace(/(\d+(?:\.\d+)?)倍/gi, '$1X')
|
|
||||||
.replace(/\b(\d+(?:\.\d+)?)x\b/gi, '$1X')
|
|
||||||
.replace(/([A-Z]{2})\s*(专属|家宽|AI|高速|专线)(\d+)/g, '$1 $2 $3')
|
|
||||||
.replace(/\b(AI)(\d+)/g, '$1 $2')
|
|
||||||
.replace(/\b(Pro-)\s+/gi, '$1')
|
|
||||||
.replace(/^((?:Pro-\s*)?)([A-Z]{2})(?=[\u4e00-\u9fff]|\d)/u, '$1$2 ')
|
|
||||||
.replace(/\s*\|\s*/g, '|')
|
|
||||||
.replace(/\s+\[/g, ' [')
|
|
||||||
.replace(/\s{2,}/g, ' ')
|
|
||||||
.trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
const moveMultiplierToEnd = utils.moveMultiplierToEnd;
|
|
||||||
|
|
||||||
const normalizeRegionSequence = name => utils.normalizeRegionSequence(name, { allowCityCode: true });
|
|
||||||
|
|
||||||
const stripTrailingFeatureSequence = name => String(name)
|
|
||||||
.replace(/\bHY\s+(\d{1,2})(?=-[A-Z]{2}\d{2}\b)/i, (match, version) => `HY${Number(version)}`)
|
|
||||||
.replace(/^([A-Z]{2}(?:\s+[A-Z]{3})?)\s+([A-Z][A-Z0-9.]{1,15})\s+\d{1,2}$/i, (match, regionPrefix, featureName) => (
|
|
||||||
/^[A-Z]{2}$/.test(regionPrefix) && knownCityCodes.has(featureName.toUpperCase())
|
|
||||||
? match
|
|
||||||
: `${regionPrefix} ${featureName}`
|
|
||||||
))
|
|
||||||
.replace(/\s{2,}/g, ' ')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
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);
|
|
||||||
if (!sequenceMatch) return null;
|
|
||||||
|
|
||||||
const prefix = sequenceMatch[1] || '';
|
|
||||||
let regionCode = sequenceMatch[2];
|
|
||||||
const sequenceNumber = sequenceMatch[3] || '';
|
|
||||||
let rest = sequenceMatch[4] || '';
|
|
||||||
const cityCodeMatch = regionCode.match(/^([A-Z]{2})\s+([A-Z]{3})$/);
|
|
||||||
if (cityCodeMatch && !knownCityCodes.has(cityCodeMatch[2])) {
|
|
||||||
regionCode = cityCodeMatch[1];
|
|
||||||
rest = ` ${cityCodeMatch[2]}${rest}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
prefix,
|
|
||||||
regionCode,
|
|
||||||
sequenceNumber,
|
|
||||||
rest
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const withRegionSequence = (sequenceInfo, sequenceNumber) => {
|
|
||||||
return `${sequenceInfo.prefix}${sequenceInfo.regionCode} ${sequenceNumber}${sequenceInfo.rest}`
|
|
||||||
.replace(/\s{2,}/g, ' ')
|
|
||||||
.trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
const withInferredRegionSequence = sequenceInfo => {
|
|
||||||
const featureSequenceMatch = sequenceInfo.rest.match(/^\s+(专属|家宽|AI|高速|专线)\s+(\d{1,2})(?!\d)(?!\s*(?:[xX倍]|\.\d))(.*)$/i);
|
|
||||||
if (!featureSequenceMatch) return withRegionSequence(sequenceInfo, '01');
|
|
||||||
|
|
||||||
const featureName = featureSequenceMatch[1].toUpperCase() === 'AI'
|
|
||||||
? 'AI'
|
|
||||||
: featureSequenceMatch[1];
|
|
||||||
const inferredSequence = featureSequenceMatch[2].padStart(2, '0');
|
|
||||||
const remainingRest = featureSequenceMatch[3] || '';
|
|
||||||
|
|
||||||
return withRegionSequence(
|
|
||||||
{
|
|
||||||
...sequenceInfo,
|
|
||||||
rest: ` ${featureName}${remainingRest}`
|
|
||||||
},
|
|
||||||
inferredSequence
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const compactRegionSequences = () => {
|
|
||||||
const indexesByRegion = Object.create(null);
|
|
||||||
|
|
||||||
sequenceInfos.forEach((sequenceInfo, proxyIndex) => {
|
|
||||||
if (!sequenceInfo) return;
|
|
||||||
|
|
||||||
const groupKey = `${subNames[proxyIndex]}\u0000${sequenceInfo.prefix}|${sequenceInfo.regionCode}`;
|
|
||||||
const regionIndexes = indexesByRegion[groupKey] || (
|
|
||||||
indexesByRegion[groupKey] = []
|
|
||||||
);
|
|
||||||
regionIndexes.push(proxyIndex);
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(indexesByRegion).forEach(groupKey => {
|
|
||||||
const regionIndexes = indexesByRegion[groupKey];
|
|
||||||
regionIndexes.forEach((proxyIndex, sequenceIndex) => {
|
|
||||||
const sequenceInfo = sequenceInfos[proxyIndex];
|
|
||||||
const compactSequenceNumber = (sequenceIndex + 1)
|
|
||||||
.toString()
|
|
||||||
.padStart(2, '0');
|
|
||||||
const compactSequenceInfo = {
|
|
||||||
...sequenceInfo,
|
|
||||||
sequenceNumber: compactSequenceNumber
|
|
||||||
};
|
|
||||||
|
|
||||||
sequenceInfos[proxyIndex] = compactSequenceInfo;
|
|
||||||
normalizedCoreNames[proxyIndex] = withRegionSequence(compactSequenceInfo, compactSequenceNumber);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeBilingualName = name => {
|
|
||||||
const bilingualMatch = name.match(/^(.+?)\s*|\s*(.+)$/);
|
|
||||||
if (!bilingualMatch) return name;
|
|
||||||
|
|
||||||
const leftName = normalizeLeadingRegion(bilingualMatch[1]);
|
|
||||||
const rightName = normalizeLeadingRegion(bilingualMatch[2]);
|
|
||||||
const leftCode = leadingCodeOf(leftName);
|
|
||||||
const rightCode = leadingCodeOf(rightName);
|
|
||||||
|
|
||||||
if (leftCode && rightCode && leftCode === rightCode) {
|
|
||||||
return rightName === rightCode ? leftName : rightName;
|
|
||||||
}
|
|
||||||
if (rightCode) return rightName;
|
|
||||||
if (leftCode) return leftName;
|
|
||||||
|
|
||||||
return name.replace(/\s*|\s*/g, ' ');
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeCoreName = name => {
|
|
||||||
name = normalizeLeadingMojibakeFlag(name);
|
|
||||||
const leadingFlagMatch = name.match(leadingFlagRegex);
|
|
||||||
const inlineFlagMatch = leadingFlagMatch
|
|
||||||
? null
|
|
||||||
: name.match(inlineFlagRegex);
|
|
||||||
const flag = leadingFlagMatch ? leadingFlagMatch[1] : inlineFlagMatch ? inlineFlagMatch[2] : '';
|
|
||||||
let core = name.trim();
|
|
||||||
|
|
||||||
if (leadingFlagMatch) {
|
|
||||||
core = name.slice(leadingFlagMatch[0].length).trim();
|
|
||||||
} else if (inlineFlagMatch) {
|
|
||||||
const prefix = inlineFlagMatch[1].trim();
|
|
||||||
const rest = inlineFlagMatch[3].trim();
|
|
||||||
core = rest ? `${prefix}${prefix.endsWith('-') ? '' : ' '}${rest}` : prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
core = core
|
|
||||||
.replace(/\[DIP.*?]/gi, '[DIP]');
|
|
||||||
|
|
||||||
core = normalizeBilingualName(core);
|
|
||||||
core = normalizeLeadingRegion(core);
|
|
||||||
|
|
||||||
if (/^Pro-\s*/i.test(core)) {
|
|
||||||
const proMatch = core.match(/^(Pro-\s*)(.+)$/i);
|
|
||||||
if (proMatch) {
|
|
||||||
core = `${proMatch[1]}${normalizeLeadingRegion(proMatch[2])}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
core = core
|
|
||||||
.replace(/^([A-Z]{2})\s*(实验性|标准|高级)\s+IEPL\s+专线\s*\d+(.*)$/i, '$1 $2$3')
|
|
||||||
.replace(/\s{2,}/g, ' ')
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const flagRegionCode = flagToRegionCode(flag);
|
|
||||||
core = normalizeProviderRegionName(core, flagRegionCode);
|
|
||||||
|
|
||||||
core = moveProRegionToFront(core, flagRegionCode);
|
|
||||||
|
|
||||||
if (
|
|
||||||
flagRegionCode &&
|
|
||||||
!hasSpecificRegionCode(core, flagRegionCode) &&
|
|
||||||
!hasKnownRegionCode(core)
|
|
||||||
) {
|
|
||||||
core = joinAlias(flagRegionCode, core);
|
|
||||||
}
|
|
||||||
|
|
||||||
core = stripTrailingFeatureSequence(
|
|
||||||
normalizeRegionSequence(normalizeHyphenatedRegionFeatures(moveMultiplierToEnd(simplifyCoreName(core))))
|
|
||||||
)
|
|
||||||
.replace(/^([A-Z]{2}(?:\s+[A-Z]{3})?\s+\d{2}\s+(?:标准|高级))\s+1(?=$|\s)/i, '$1');
|
|
||||||
|
|
||||||
const coreRegionCode = leadingCodeOf(core);
|
|
||||||
const outputFlag = flag && knownRegionCodes.has(coreRegionCode)
|
|
||||||
? utils.regionCodeToFlag(coreRegionCode) || flag
|
|
||||||
: flag;
|
|
||||||
|
|
||||||
return outputFlag ? `${outputFlag} ${core}` : core;
|
|
||||||
};
|
|
||||||
|
|
||||||
proxies.forEach((proxy, proxyIndex) => {
|
proxies.forEach((proxy, proxyIndex) => {
|
||||||
const proxyName = typeof proxy.name === 'string' ? proxy.name : '';
|
// 非对象或非字符串名称不参与处理,避免改动无效节点数据。
|
||||||
const subName = typeof proxy._subName === 'string' ? proxy._subName.trim() : '';
|
if (!proxy || typeof proxy !== 'object' || typeof proxy.name !== 'string') {
|
||||||
const coreName = stripExistingSuffix(proxyName, subName);
|
baseNames[proxyIndex] = null;
|
||||||
const normalizedCoreName = normalizeCoreName(coreName);
|
return;
|
||||||
const sequenceInfo = regionSequenceOf(normalizedCoreName);
|
}
|
||||||
|
|
||||||
normalizedCoreNames[proxyIndex] = normalizedCoreName;
|
const proxyName = proxy.name;
|
||||||
subNames[proxyIndex] = subName;
|
const subName = proxy && typeof proxy._subName === 'string'
|
||||||
sequenceInfos[proxyIndex] = sequenceInfo;
|
? proxy._subName.trim()
|
||||||
});
|
: '';
|
||||||
|
// 没有订阅名时完全保留原字符串;公共工具的兼容实现会顺手 trim。
|
||||||
|
const coreName = subName ? stripSubscriptionSuffix(proxyName, subName) : proxyName;
|
||||||
|
const baseName = subName
|
||||||
|
? (coreName ? `${coreName} - ${subName}` : subName)
|
||||||
|
: coreName;
|
||||||
|
|
||||||
compactRegionSequences();
|
|
||||||
|
|
||||||
normalizedCoreNames.forEach((normalizedCoreName, proxyIndex) => {
|
|
||||||
const subName = subNames[proxyIndex];
|
|
||||||
const sequenceInfo = sequenceInfos[proxyIndex];
|
|
||||||
const needsLeadingSequence = sequenceInfo && !sequenceInfo.sequenceNumber;
|
|
||||||
const coreName = needsLeadingSequence
|
|
||||||
? withInferredRegionSequence(sequenceInfo)
|
|
||||||
: normalizedCoreName;
|
|
||||||
const baseName = subName ? `${coreName} - ${subName}` : coreName;
|
|
||||||
|
|
||||||
finalCoreNames[proxyIndex] = coreName;
|
|
||||||
baseNames[proxyIndex] = baseName;
|
baseNames[proxyIndex] = baseName;
|
||||||
globalCount[baseName] = (globalCount[baseName] || 0) + 1;
|
coreNames[proxyIndex] = coreName;
|
||||||
|
subNames[proxyIndex] = subName;
|
||||||
|
nameCounts[baseName] = (nameCounts[baseName] || 0) + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
return proxies.map((proxy, proxyIndex) => {
|
return proxies.map((proxy, proxyIndex) => {
|
||||||
const baseName = baseNames[proxyIndex];
|
const baseName = baseNames[proxyIndex];
|
||||||
const coreName = finalCoreNames[proxyIndex];
|
if (baseName === null || !proxy || typeof proxy !== 'object') return proxy;
|
||||||
const subName = subNames[proxyIndex];
|
|
||||||
const sequenceInfo = sequenceInfos[proxyIndex];
|
|
||||||
|
|
||||||
if (globalCount[baseName] > 1 && !sequenceInfo) {
|
if (nameCounts[baseName] > 1) {
|
||||||
currentCount[baseName] = (currentCount[baseName] || 0) + 1;
|
currentCounts[baseName] = (currentCounts[baseName] || 0) + 1;
|
||||||
const duplicateNumber = currentCount[baseName].toString().padStart(2, '0');
|
const duplicateNumber = String(currentCounts[baseName]).padStart(2, '0');
|
||||||
proxy.name = subName
|
proxy.name = subNames[proxyIndex]
|
||||||
? `${coreName} ${duplicateNumber} - ${subName}`
|
? `${coreNames[proxyIndex]} ${duplicateNumber} - ${subNames[proxyIndex]}`
|
||||||
: `${baseName} ${duplicateNumber}`;
|
: `${baseName} ${duplicateNumber}`;
|
||||||
} else {
|
} else {
|
||||||
proxy.name = baseName;
|
proxy.name = baseName;
|
||||||
|
|||||||
Reference in New Issue
Block a user