♻️ refactor(substore): 提取重构公共工具 common.js 并新增 liangxin 脚本
This commit is contained in:
@@ -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);
|
||||
Reference in New Issue
Block a user