From 98c3b442f0db75110cd169d97b1aad9867ed770c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 10:18:10 +0000 Subject: [PATCH] =?UTF-8?q?fix(playlist):=20address=20bot=20review=20?= =?UTF-8?q?=E2=80=94=20no=20identity=20cross-pairing=20across=20MACs,=20ho?= =?UTF-8?q?nest=20caps,=20password=20in=20dedupe=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Multi-MAC messages no longer attach globally scanned serial/device-ID/ signature/credential labels to every MAC: with several accounts in one message there is no reliable owner for a first-match label, and a device ID submitted with the wrong MAC is pinned by the portal permanently. Identity fields now attach only when exactly one MAC is present. - MAC and candidate caps raised (12/16) so real multi-account lists are never silently truncated; the caps remain only as pathological-paste guards. - The candidate dedupe key includes the password, so two accounts sharing a server and username (e.g. a rotation message) both stay selectable. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4 --- .../provider-import-detection.util.spec.ts | 40 ++++++++++++++++++- .../src/lib/provider-import-detection.util.ts | 29 ++++++++++---- .../src/lib/provider-import-scan.util.ts | 4 +- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/libs/shared/interfaces/src/lib/provider-import-detection.util.spec.ts b/libs/shared/interfaces/src/lib/provider-import-detection.util.spec.ts index d5f415239..66c95b0ab 100644 --- a/libs/shared/interfaces/src/lib/provider-import-detection.util.spec.ts +++ b/libs/shared/interfaces/src/lib/provider-import-detection.util.spec.ts @@ -652,6 +652,44 @@ describe('detectProviderImportCandidates', () => { expect(stalker[0].deviceId2).toBeUndefined(); }); + it('does not cross-pair global identity labels onto multiple MACs', () => { + // First-match-wins labels cannot say WHOSE serial/device ID they + // are once a second MAC appears — and a device ID submitted with + // the wrong MAC gets pinned by the portal permanently. Multi-MAC + // candidates must therefore carry portal + MAC only. + const candidates = detectProviderImportCandidates( + [ + 'Portal: http://multi.example.com/c/', + 'MAC: 00:1A:79:11:11:11', + `Device ID: ${'a'.repeat(64)}`, + 'SN: AAAA1111BBBB2', + 'MAC: 00:1A:79:22:22:22', + ].join('\n') + ); + + const stalker = only(candidates, 'stalker'); + expect(stalker).toHaveLength(2); + for (const candidate of stalker) { + expect(candidate.portalUrl).toBe( + 'http://multi.example.com/c/' + ); + expect(candidate.deviceId1).toBeUndefined(); + expect(candidate.serialNumber).toBeUndefined(); + } + }); + + it('surfaces every MAC of a long multi-account list', () => { + const macs = Array.from( + { length: 6 }, + (_, index) => `00:1A:79:0${index}:0${index}:0${index}` + ); + const candidates = detectProviderImportCandidates( + ['http://long.example.com/c/', ...macs].join('\n') + ); + + expect(only(candidates, 'stalker')).toHaveLength(6); + }); + it('gives labeled credentials to the stalker candidate, not a competing xtream guess', () => { const candidates = detectProviderImportCandidates( [ @@ -749,7 +787,7 @@ describe('detectProviderImportCandidates', () => { expect( detectProviderImportCandidates(urls).length - ).toBeLessThanOrEqual(6); + ).toBeLessThanOrEqual(16); }); }); }); diff --git a/libs/shared/interfaces/src/lib/provider-import-detection.util.ts b/libs/shared/interfaces/src/lib/provider-import-detection.util.ts index b033d581a..6f7c98032 100644 --- a/libs/shared/interfaces/src/lib/provider-import-detection.util.ts +++ b/libs/shared/interfaces/src/lib/provider-import-detection.util.ts @@ -69,7 +69,9 @@ export interface ProviderImportCandidate { const GET_PHP_PATH_PATTERN = /\/get\.php$/i; const EXTM3U_HEADER_PATTERN = /^\s*#EXTM3U/i; -const MAX_CANDIDATES = 6; +// Guard against pathological pastes, not against real handouts: multi-account +// MAC lists with a dozen entries are legitimate and must all surface. +const MAX_CANDIDATES = 16; /** * Scans a pasted provider message and returns ranked import candidates. @@ -106,7 +108,15 @@ export function detectProviderImportCandidates( // ── Stalker: a MAC address is the strongest signal there is. Labeled // credentials attach here (Stalker portals can require a login too) // rather than seeding a competing Xtream guess. + // + // Identity fields attach ONLY when the message names exactly one account: + // labeled extraction is first-match-wins over the whole text, so with + // several MACs there is no reliable way to tell whose serial/device ID a + // label belongs to — and a device ID submitted with the wrong MAC is + // pinned by the portal permanently. Multi-MAC candidates therefore carry + // portal + MAC only, and the user supplies identity per account. const portal = pickStalkerPortalUrl(urls, labeled); + const identity: LabeledFields = macs.length === 1 ? labeled : {}; for (const macAddress of macs) { candidates.push( compact({ @@ -114,13 +124,13 @@ export function detectProviderImportCandidates( confidence: portal?.confidence ?? 'low', portalUrl: portal?.url, macAddress, - serialNumber: labeled.serialNumber, - deviceId1: labeled.deviceId1, - deviceId2: labeled.deviceId2, - signature1: labeled.signature1, - signature2: labeled.signature2, - username: labeled.username, - password: labeled.password, + serialNumber: identity.serialNumber, + deviceId1: identity.deviceId1, + deviceId2: identity.deviceId2, + signature1: identity.signature1, + signature2: identity.signature2, + username: identity.username, + password: identity.password, suggestedTitle: hostnameOf(portal?.url), }) ); @@ -322,6 +332,9 @@ function dedupe( candidate.url ?? '', candidate.serverUrl ?? '', candidate.username ?? '', + // Same server + username with a DIFFERENT password is a distinct + // account (e.g. a rotation message) — never collapse it away. + candidate.password ?? '', candidate.portalUrl ?? '', candidate.macAddress ?? '', ].join('|'); diff --git a/libs/shared/interfaces/src/lib/provider-import-scan.util.ts b/libs/shared/interfaces/src/lib/provider-import-scan.util.ts index 95868132d..9584ad776 100644 --- a/libs/shared/interfaces/src/lib/provider-import-scan.util.ts +++ b/libs/shared/interfaces/src/lib/provider-import-scan.util.ts @@ -53,7 +53,9 @@ const HOSTISH_PATTERN = /^[a-z0-9][a-z0-9.-]*(?::\d{2,5})?$/i; const WRAPPING_CHARS = /^["'`«<([]+|["'`»>)\]]+$/g; const MAX_URLS = 8; -const MAX_MACS = 4; +// High enough that a real multi-account handout is never silently truncated; +// only a pathological paste (hex dumps, logs) hits it. +const MAX_MACS = 12; interface FieldMatcher { field: keyof LabeledFields;