mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
fix(playlist): do not fold real flag emoji in auto-detect label folding
Regional-indicator runs of exactly two are real flag emoji (🇳🇱); folding them glued letters onto adjacent text and could break a following label's word boundary. Only runs of three or more — fancy-text words like 🇺🇸🇪🇷 — fold to ASCII now. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
This commit is contained in:
2 files changed
+64
-4
No files matched your search
@@ -187,6 +187,29 @@ describe('detectProviderImportCandidates', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('folds regional-indicator fancy text but leaves real flag emoji alone', () => {
|
||||
// 🇺🇸🇪🇷 / 🇵🇦🇸🇸 are runs of 3+ regional indicators — fancy text
|
||||
// for USER / PASS. 🇹🇷 is a run of exactly two — a real flag that
|
||||
// must not fold, or the glued "TR" would break the PORT label's
|
||||
// word boundary.
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
'Server: iptv.example.tv',
|
||||
'🇹🇷PORT: 8080',
|
||||
'🇺🇸🇪🇷 : carol',
|
||||
'🇵🇦🇸🇸 : pa55',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
const xtream = only(candidates, 'xtream');
|
||||
expect(xtream).toHaveLength(1);
|
||||
expect(xtream[0]).toMatchObject({
|
||||
serverUrl: 'http://iptv.example.tv:8080',
|
||||
username: 'carol',
|
||||
password: 'pa55',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not read a hyphenated word as a labeled value', () => {
|
||||
expect(
|
||||
detectProviderImportCandidates(
|
||||
|
||||
@@ -98,19 +98,56 @@ const isHexIdentity = (value: string) => HEX_IDENTITY_PATTERN.test(value);
|
||||
* ASCII.
|
||||
*/
|
||||
export function foldDecorativeAlphabets(text: string): string {
|
||||
const chars = Array.from(text);
|
||||
let result = '';
|
||||
for (const char of text) {
|
||||
result +=
|
||||
foldDecorativeCodePoint(char.codePointAt(0) as number) ?? char;
|
||||
for (let index = 0; index < chars.length; ) {
|
||||
const code = chars[index].codePointAt(0) as number;
|
||||
if (isRegionalIndicator(code)) {
|
||||
// Regional indicators are ambiguous: a run of EXACTLY two is a
|
||||
// real flag emoji (🇳🇱) that must survive untouched — folding it
|
||||
// would glue "NL" onto whatever follows and break an adjacent
|
||||
// label's word boundary. Fancy-text words (🇺🇸🇪🇷, 🇺🇷🇱) are runs
|
||||
// of three or more, so only those fold to letters.
|
||||
let end = index;
|
||||
while (
|
||||
end < chars.length &&
|
||||
isRegionalIndicator(chars[end].codePointAt(0) as number)
|
||||
) {
|
||||
end += 1;
|
||||
}
|
||||
const run = chars.slice(index, end);
|
||||
result +=
|
||||
run.length >= 3
|
||||
? run
|
||||
.map((char) =>
|
||||
String.fromCharCode(
|
||||
0x41 +
|
||||
((char.codePointAt(0) as number) -
|
||||
REGIONAL_INDICATOR_A)
|
||||
)
|
||||
)
|
||||
.join('')
|
||||
: run.join('');
|
||||
index = end;
|
||||
continue;
|
||||
}
|
||||
result += foldDecorativeCodePoint(code) ?? chars[index];
|
||||
index += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const REGIONAL_INDICATOR_A = 0x1f1e6;
|
||||
const REGIONAL_INDICATOR_Z = 0x1f1ff;
|
||||
|
||||
function isRegionalIndicator(code: number): boolean {
|
||||
return code >= REGIONAL_INDICATOR_A && code <= REGIONAL_INDICATOR_Z;
|
||||
}
|
||||
|
||||
const DECORATIVE_LATIN_RANGES: Array<[number, number]> = [
|
||||
[0x1f130, 0x1f149], // 🄰 squared A–Z
|
||||
[0x1f150, 0x1f169], // 🅐 negative circled A–Z
|
||||
[0x1f170, 0x1f189], // 🅰 negative squared A–Z
|
||||
[0x1f1e6, 0x1f1ff], // 🇦 regional indicator A–Z
|
||||
];
|
||||
|
||||
const DECORATIVE_DIGIT_RANGES: Array<[number, number]> = [
|
||||
|
||||
Reference in new issue
Block a user