fix: group beIN Sports 1 variants and clarify Laya language warning (#15)

beIN Sports 1 variants stayed separated after projection because
names with region prefixes (C Car, C Reu, Thai), time-shift suffixes
(2h/4h/8h/12h) and language markers (En, Fr, Thai) produced distinct
fingerprints. Extend normalization in the server projection service
and the frontend grouping fallback to fold these into one logical
channel, with tests on the exact reported names. Clarify the Laya
language detection warning so a language-matching failure is not
mistaken for a grouping failure, and bump script versions for cache
refresh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-02 16:22:57 +02:00
parent dcec7648da
commit dbd5b7fea3
7 changed files with 106 additions and 6 deletions

View File

@ -1204,10 +1204,10 @@
<script src="/js/components/VideoPlayer.js?v=5"></script>
<script src="/js/services/channelLanguageDetection.js?v=1"></script>
<script src="/js/services/channelLanguageFusion.js?v=1"></script>
<script src="/js/services/layaStatusPresentation.js?v=4"></script>
<script src="/js/services/layaStatusPresentation.js?v=5"></script>
<script src="/js/services/liveAnalysisPresentation.js?v=1"></script>
<script src="/js/services/liveChannelProjection.js?v=1"></script>
<script src="/js/services/channelVariantGrouping.js?v=6"></script>
<script src="/js/services/channelVariantGrouping.js?v=7"></script>
<script src="/js/components/ChannelList.js?v=11"></script>
<script src="/js/components/SourceManager.js?v=2"></script>
<script src="/js/components/EpgGuide.js?v=2"></script>

View File

@ -15,6 +15,8 @@
]);
const REGION_PREFIX = /^(?:(?:\|\s*[a-z]{2,3}\s*\|)|(?:[a-z]{2,3}(?:-[a-z]{2,3})?\s*\|))\s*/i;
const BEIN_SUFFIX_TOKENS = new Set(['fr', 'en', 'thai', 'tod', 'appel', 'app']);
const BEIN_TIMESHIFT_TOKENS = new Set(['2h', '4h', '8h', '12h']);
function words(value) {
return String(value || '')
@ -28,7 +30,23 @@
}
function normalizeChannelName(name) {
return words(name).filter(token => !QUALITY_TOKENS.has(token)).join(' ').trim();
const tokens = words(name).filter(token => !QUALITY_TOKENS.has(token));
const brandIndex = tokens.findIndex((token, index) => token === 'bein' && tokens[index + 1] === 'sports');
if (brandIndex >= 0) {
const branded = tokens.slice(brandIndex);
const numberedBaseLength = branded[2] === 'max' && /^\d+$/.test(branded[3])
? 4
: (/^\d+$/.test(branded[2]) ? 3 : 0);
if (numberedBaseLength) {
const suffix = branded.slice(numberedBaseLength);
const removable = suffix.every(token =>
BEIN_SUFFIX_TOKENS.has(token) || BEIN_TIMESHIFT_TOKENS.has(token)
);
if (removable) return branded.slice(0, numberedBaseLength).join(' ');
}
return branded.join(' ').trim();
}
return tokens.join(' ').trim();
}
function displayNameForFingerprint(fingerprint) {

View File

@ -80,7 +80,7 @@
function channelListWarning(mode, status = {}, batchFailed = false) {
if (mode === 'algo') return '';
if (batchFailed) {
return 'Local AI language matching failed. Standard channel matching is being used.';
return 'Local AI language detection failed. Channels were grouped with Standard matching and remain available.';
}
if (status.modelPresent !== true || status.runtimeAvailable !== true) {
return 'Local AI service is unavailable. Standard channel matching is being used.';