mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-09 17:36:15 -08:00
fix(playlist): apply separately labeled ports to detected API and portal URLs
'Portal: http://host/…' plus 'Port: 8080' on its own line is a real handout shape; the labeled port now completes port-less Xtream API URLs, shaped Stalker portals, and the generic-URL fallback alike. A URL stating its own port explicitly is never overridden. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4
This commit is contained in:
2 files changed
+74
-4
No files matched your search
@@ -315,6 +315,36 @@ describe('detectProviderImportCandidates', () => {
|
||||
expect(xtream[0].serverUrl).toBe('http://panel.example.io:8080');
|
||||
});
|
||||
|
||||
it('completes a port-less API URL with the separately labeled port', () => {
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
'Server: http://panel.example.org/player_api.php',
|
||||
'Port: 8080',
|
||||
'User: alice',
|
||||
'Pass: s3cret',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
const xtream = only(candidates, 'xtream');
|
||||
expect(xtream).toHaveLength(1);
|
||||
expect(xtream[0].serverUrl).toBe('http://panel.example.org:8080');
|
||||
});
|
||||
|
||||
it('does not let a labeled port override an explicit one', () => {
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
'Server: http://panel.example.org:2500/player_api.php',
|
||||
'Port: 8080',
|
||||
'User: alice',
|
||||
'Pass: s3cret',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
expect(only(candidates, 'xtream')[0].serverUrl).toBe(
|
||||
'http://panel.example.org:2500'
|
||||
);
|
||||
});
|
||||
|
||||
it('strips panel_api.php from the derived server URL', () => {
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
@@ -713,6 +743,20 @@ describe('detectProviderImportCandidates', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('completes a port-less shaped portal with the separately labeled port', () => {
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
'Portal: http://stb.example.com/c/',
|
||||
'Port: 8080',
|
||||
'MAC: 00:1A:79:12:34:56',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
const stalker = only(candidates, 'stalker');
|
||||
expect(stalker).toHaveLength(1);
|
||||
expect(stalker[0].portalUrl).toBe('http://stb.example.com:8080/c/');
|
||||
});
|
||||
|
||||
it('treats tenant installs under different base paths as different panels', () => {
|
||||
const candidates = detectProviderImportCandidates(
|
||||
[
|
||||
|
||||
@@ -178,11 +178,14 @@ export function detectProviderImportCandidates(
|
||||
const password =
|
||||
creds?.password ??
|
||||
(macs.length === 0 ? labeled.password : undefined);
|
||||
// A separately labeled "Port:" completes a port-less API URL, same
|
||||
// as the labeled-host fallback already does.
|
||||
const completedRaw = completeWithLabeledPort(url, labeled.port);
|
||||
candidates.push(
|
||||
compact({
|
||||
kind: 'xtream',
|
||||
confidence: username && password ? 'high' : 'medium',
|
||||
serverUrl: safeXtreamServerUrl(url.raw),
|
||||
serverUrl: safeXtreamServerUrl(completedRaw),
|
||||
username,
|
||||
password,
|
||||
suggestedTitle: url.parsed.hostname,
|
||||
@@ -195,7 +198,7 @@ export function detectProviderImportCandidates(
|
||||
candidates.push({
|
||||
kind: 'm3u-url',
|
||||
confidence: 'low',
|
||||
url: url.raw,
|
||||
url: completedRaw,
|
||||
suggestedTitle: url.parsed.hostname,
|
||||
});
|
||||
}
|
||||
@@ -291,13 +294,33 @@ function portalInstallationKey(url: DetectedUrl): string {
|
||||
return `${url.parsed.origin}${base}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes a found URL with a separately labeled port — "Portal: http://x/…"
|
||||
* plus "Port: 8080" on its own line is a real handout shape (corpus #5).
|
||||
* Applied only when the URL itself states no explicit port, so a written
|
||||
* `:80` is never overridden by the label.
|
||||
*/
|
||||
function completeWithLabeledPort(
|
||||
url: DetectedUrl,
|
||||
port: string | undefined
|
||||
): string {
|
||||
if (!port || /^https?:\/\/[^/]*:\d+/i.test(url.raw)) {
|
||||
return url.raw;
|
||||
}
|
||||
const { protocol, hostname, pathname, search } = url.parsed;
|
||||
return `${protocol}//${hostname}:${port}${pathname}${search}`;
|
||||
}
|
||||
|
||||
function pickStalkerPortalUrl(
|
||||
urls: DetectedUrl[],
|
||||
labeled: LabeledFields
|
||||
): { url: string; confidence: ProviderImportConfidence } | null {
|
||||
const shaped = urls.find((url) => url.role === 'stalker');
|
||||
if (shaped) {
|
||||
return { url: shaped.raw, confidence: 'high' };
|
||||
return {
|
||||
url: completeWithLabeledPort(shaped, labeled.port),
|
||||
confidence: 'high',
|
||||
};
|
||||
}
|
||||
const labeledUrl = labeledHostUrl(labeled);
|
||||
if (labeledUrl) {
|
||||
@@ -305,7 +328,10 @@ function pickStalkerPortalUrl(
|
||||
}
|
||||
const generic = urls.find((url) => url.role === 'generic');
|
||||
if (generic) {
|
||||
return { url: generic.raw, confidence: 'medium' };
|
||||
return {
|
||||
url: completeWithLabeledPort(generic, labeled.port),
|
||||
confidence: 'medium',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user