mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 01:56:16 -08:00
fix(playlist): strip panel_api.php endpoints, path-aware portal ambiguity for tenant installs
- normalizeXtreamServerUrl strips panel_api.php like the other API endpoints, so a panel_api.php handout (pasted or auto-detected) prefills the server base instead of a URL the transport would double-suffix. - The multi-MAC portal ambiguity key is origin plus the installation base path: tenant installs sharing one origin (/a/stalker_portal/c/ vs /b/...) no longer collapse, while alternate endpoints of one install (/c/, portal.php, server/load.php, Real/Panel pairs) still compare equal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
This commit is contained in:
3 files changed
+74
-6
No files matched your search
@@ -315,6 +315,20 @@ describe('detectProviderImportCandidates', () => {
|
||||
expect(xtream[0].serverUrl).toBe('http://panel.example.io:8080');
|
||||
});
|
||||
|
||||
it('strips panel_api.php from the derived server URL', () => {
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
'http://panel.example.org/panel_api.php',
|
||||
'User: alice',
|
||||
'Pass: s3cret',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
const xtream = only(candidates, 'xtream');
|
||||
expect(xtream).toHaveLength(1);
|
||||
expect(xtream[0].serverUrl).toBe('http://panel.example.org');
|
||||
});
|
||||
|
||||
it('treats credentials in the query of a generic URL as xtream', () => {
|
||||
const candidates = detectProviderImportCandidates(
|
||||
'http://portal.example.io/api?username=eve&password=pw&foo=bar'
|
||||
@@ -699,6 +713,42 @@ describe('detectProviderImportCandidates', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('treats tenant installs under different base paths as different panels', () => {
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
'http://panel.example.com/a/stalker_portal/c/',
|
||||
'MAC: 00:1A:79:AA:AA:AA',
|
||||
'http://panel.example.com/b/stalker_portal/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('keeps alternate endpoints of one install unambiguous for a MAC list', () => {
|
||||
// /c/ and portal.php are two doors into the same installation —
|
||||
// a MAC list next to both must still get the shared portal.
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
'http://panel.example.com/c/',
|
||||
'http://panel.example.com/portal.php',
|
||||
'MAC: 00:1A:79:AA:AA:AA',
|
||||
'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).toBe('http://panel.example.com/c/');
|
||||
}
|
||||
});
|
||||
|
||||
it('treats same-host portals on different ports as different panels', () => {
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
|
||||
@@ -135,13 +135,9 @@ 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.origin)).size > 1;
|
||||
new Set(portalPool.map(portalInstallationKey)).size > 1;
|
||||
const portal = portalAmbiguous
|
||||
? null
|
||||
: pickStalkerPortalUrl(urls, labeled);
|
||||
@@ -276,6 +272,25 @@ export function detectProviderImportCandidates(
|
||||
return sortByConfidence(dedupe(candidates)).slice(0, MAX_CANDIDATES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ambiguity key for a portal candidate: origin plus the installation's base
|
||||
* path. Origin (not hostname) separates panels on different ports while URL
|
||||
* normalization drops default ports, so the Real/Panel `:80`-vs-bare pairs
|
||||
* of scanner dumps compare equal. The base path separates tenant installs
|
||||
* sharing one origin (`/a/stalker_portal/c/` vs `/b/…`), while the known
|
||||
* endpoint suffixes of ONE install (`/c/`, `portal.php`, `server/load.php`)
|
||||
* are stripped so its alternate endpoints reduce to the same key.
|
||||
*/
|
||||
function portalInstallationKey(url: DetectedUrl): string {
|
||||
const base = url.parsed.pathname
|
||||
.replace(/\/(?:stalker_portal\/)?c\/?$/i, '')
|
||||
.replace(/\/stalker_portal\/?$/i, '')
|
||||
.replace(/\/portal\.php$/i, '')
|
||||
.replace(/\/(?:stalker_portal\/)?server\/load\.php$/i, '')
|
||||
.replace(/\/+$/, '');
|
||||
return `${url.parsed.origin}${base}`;
|
||||
}
|
||||
|
||||
function pickStalkerPortalUrl(
|
||||
urls: DetectedUrl[],
|
||||
labeled: LabeledFields
|
||||
|
||||
@@ -14,7 +14,10 @@ export interface XtreamCredentialsFromUrl {
|
||||
username: string;
|
||||
}
|
||||
|
||||
const XTREAM_API_ENDPOINT_PATTERN = /\/(?:get|player_api)\.php$/i;
|
||||
// panel_api.php is the legacy admin-flavored endpoint some panels hand out;
|
||||
// it identifies an Xtream server just like the other two and must be
|
||||
// stripped the same way, or the transport would append /player_api.php to it.
|
||||
const XTREAM_API_ENDPOINT_PATTERN = /\/(?:get|player_api|panel_api)\.php$/i;
|
||||
|
||||
// Some Xtream panels challenge generic Node HTTP clients while allowing
|
||||
// established IPTV players. Keep API, probe, and download requests aligned.
|
||||
|
||||
Reference in new issue
Block a user