From c67f0f8eb48a5821633fce852b806f78d16c0cb1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 19:12:18 +0000 Subject: [PATCH] fix(playlist): DOM-safety caps far beyond real handouts, port-aware portal ambiguity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The MAC and candidate caps are raised to 64/96: they exist to keep a pathological paste (a log full of MAC-shaped hex) from rendering thousands of cards, not to bound real handouts — scanner dumps top out around a couple dozen accounts, so no real multi-account message is truncated. - The multi-MAC portal ambiguity guard compares URL origins instead of hostnames, so two panels on one DNS name but different ports no longer cross-pair; URL normalization drops default ports, keeping the Real/Panel ':80'-vs-bare pairs of scanner dumps unambiguous. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4 --- .../provider-import-detection.util.spec.ts | 24 ++++++++++++++++--- .../src/lib/provider-import-detection.util.ts | 13 ++++++---- .../src/lib/provider-import-scan.util.ts | 10 ++++---- 3 files changed, 36 insertions(+), 11 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 bad0e4477..13af6d886 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 @@ -699,6 +699,23 @@ describe('detectProviderImportCandidates', () => { } }); + it('treats same-host portals on different ports as different panels', () => { + const candidates = detectProviderImportCandidates( + [ + 'http://panel.example.com:8080/c/', + 'MAC: 00:1A:79:AA:AA:AA', + 'http://panel.example.com:9090/c/', + 'MAC: 00:1A:79:BB:BB:BB', + ].join('\n') + ); + + const stalker = only(candidates, 'stalker'); + expect(stalker).toHaveLength(2); + for (const candidate of stalker) { + expect(candidate.portalUrl).toBeUndefined(); + } + }); + it('treats several MACs with generic URLs on different hosts as ambiguous too', () => { // Without portal-shaped URLs the picker falls back to a generic // URL — the ambiguity guard must cover that pool as well, or two @@ -721,14 +738,15 @@ describe('detectProviderImportCandidates', () => { 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}` + { length: 20 }, + (_, index) => + `00:1A:79:${String(index).padStart(2, '0')}:11:22` ); const candidates = detectProviderImportCandidates( ['http://long.example.com/c/', ...macs].join('\n') ); - expect(only(candidates, 'stalker')).toHaveLength(6); + expect(only(candidates, 'stalker')).toHaveLength(20); }); it('gives labeled credentials to the stalker candidate, not a competing xtream guess', () => { 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 326994822..299907735 100644 --- a/libs/shared/interfaces/src/lib/provider-import-detection.util.ts +++ b/libs/shared/interfaces/src/lib/provider-import-detection.util.ts @@ -69,9 +69,10 @@ export interface ProviderImportCandidate { const GET_PHP_PATH_PATTERN = /\/get\.php$/i; const EXTM3U_HEADER_PATTERN = /^\s*#EXTM3U/i; -// 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; +// DOM-safety guard against pathological pastes, not a business limit: sized +// above the scanners' combined caps (64 MACs + 16 URLs), so every candidate +// a real multi-account handout produces survives the final slice. +const MAX_CANDIDATES = 96; /** * Scans a pasted provider message and returns ranked import candidates. @@ -132,9 +133,13 @@ export function detectProviderImportCandidates( : labeledHostUrl(labeled) !== undefined ? [] : urls.filter((url) => url.role === 'generic'); + // Compared by ORIGIN, not hostname: two panels on one DNS name but + // different ports are different panels, while URL normalization drops a + // default port, so the Real/Panel `:80`-vs-bare pairs of scanner dumps + // still compare equal. const portalAmbiguous = macs.length > 1 && - new Set(portalPool.map((url) => url.parsed.hostname)).size > 1; + new Set(portalPool.map((url) => url.parsed.origin)).size > 1; const portal = portalAmbiguous ? null : pickStalkerPortalUrl(urls, labeled); 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 92be049e6..d86585f06 100644 --- a/libs/shared/interfaces/src/lib/provider-import-scan.util.ts +++ b/libs/shared/interfaces/src/lib/provider-import-scan.util.ts @@ -52,11 +52,13 @@ const HOSTISH_PATTERN = /^[a-z0-9][a-z0-9.-]*(?::\d{2,5})?$/i; const WRAPPING_CHARS = /^["'`«<([]+|["'`»>)\]]+$/g; -// Both caps guard against pathological pastes only, and stay aligned with -// the assembler's candidate cap so a real multi-link or multi-account -// handout is never truncated by the scanner before assembly even sees it. +// Both caps are DOM-safety guards against pathological pastes (a log file +// full of MAC-shaped hex would otherwise render thousands of cards), sized +// far beyond any real handout — reseller and scanner dumps top out around a +// couple dozen accounts. The assembler's candidate cap leaves headroom for +// both scanners together, so neither truncates real input before assembly. const MAX_URLS = 16; -const MAX_MACS = 12; +const MAX_MACS = 64; interface FieldMatcher { field: keyof LabeledFields;