fix(now-playing): Орфей — чиним кодировку (двойная мойибейк cp1251) + режем хекс-хвосты

status-json смешанной кодировки: часть тайтлов нормальный UTF-8, часть — cp1251-
байты, прочитанные как latin1 и завёрнутые в UTF-8 (мойибейк «Íèêîëà»→«Никола»).
fixEncoding: реальную кириллицу не трогаем, мойибейк (À-ÿ) восстанавливаем
latin1→windows-1251. Срезаем приклеенный служебный id трека (- f0098627), фильтр
hex/числовых плейсхолдеров усилен. Каналы без реального произведения — без подписи.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nk
2026-06-06 09:01:03 +03:00
parent c87a0caa5c
commit 3c4f349f71

View File

@@ -89,22 +89,52 @@ export class OrpheusNowPlayingService {
return m ? m[1] : null; return m ? m[1] : null;
} }
/** Разбирает title «Артист — Произведение», отсекая мусор. */ /**
* Чинит кодировку: реальную кириллицу (U+0400-04FF) не трогаем; «двойную
* мойибейк» (cp1251-байты, прочитанные как latin1 и завёрнутые в UTF-8 —
* признак латиницы-1 À-ÿ) восстанавливаем latin1→windows-1251.
*/
private fixEncoding(s: string): string {
if (/[Ѐ-ӿ]/.test(s)) return s; // уже корректная кириллица
if (/[À-ÿ]/.test(s)) {
try {
return new TextDecoder('windows-1251').decode(Buffer.from(s, 'latin1'));
} catch {
return s;
}
}
return s;
}
/** Разбирает title «Артист — Произведение», чиня кодировку и отсекая мусор/служебные id. */
private parseTitle(title?: string): { artist: string; song: string } | null { private parseTitle(title?: string): { artist: string; song: string } | null {
if (!title) return null; if (!title) return null;
const t = title.trim(); let t = this.fixEncoding(title).replace(/\s+/g, ' ').trim();
if (!t || t === 'undefined') return null; if (!t || t.toLowerCase() === 'undefined') return null;
// hex-плейсхолдеры (0003b6d2), URL-источники (http://fonotron.ru), JSON // Срезаем хвостовой служебный id трека: « - f0098627», « - 147-1-10», « - 263-2-01»
if (/^[0-9a-f]{6,}$/i.test(t) || t.startsWith('http') || t.startsWith('{')) { t = t
.replace(/\s*[-—]\s*[0-9a-f]{6,}\s*$/i, '')
.replace(/\s*[-—]\s*\d+-\d+(?:-\d+)?\s*$/, '')
.trim();
if (!t) return null;
// Целиком мусор: hex-плейсхолдер, числовой код, URL, JSON
if (
/^[0-9a-f]{4,}$/i.test(t) ||
/^\d+-\d+/.test(t) ||
t.startsWith('http') ||
t.startsWith('{')
) {
return null; return null;
} }
// Разделитель — длинное тире или дефис с пробелами // Разделитель — длинное тире или дефис с пробелами (оба длиной 3 символа: « X »)
const idx = t.indexOf(' — ') >= 0 ? t.indexOf(' — ') : t.indexOf(' - '); const emIdx = t.indexOf(' — ');
const idx = emIdx >= 0 ? emIdx : t.indexOf(' - ');
if (idx < 0) return null; if (idx < 0) return null;
const sepLen = t.indexOf(' — ') >= 0 ? 3 : 3;
const artist = t.slice(0, idx).trim(); const artist = t.slice(0, idx).trim();
const song = t.slice(idx + sepLen).trim(); const song = t.slice(idx + 3).trim();
if (!artist || !song) return null; if (!artist || !song) return null;
// Часть после чистки всё ещё мусорная
if (/^[0-9a-f]{4,}$/i.test(song) || /^[0-9a-f]{4,}$/i.test(artist)) return null;
return { artist, song }; return { artist, song };
} }
} }