fix(playlist): address bot review — no identity cross-pairing across MACs, honest caps, password in dedupe key

- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
This commit is contained in:
Claude committed 2026-08-15 10:18:10 +00:00
1 parent e17a4ba641
commit 98c3b442f0
3 files changed
+63 -10

No files matched your search

@@ -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);
});
});
});
@@ -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('|');
@@ -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;