mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 05:36:17 -08:00
fix(stalker): restore playback command compatibility
This commit is contained in:
1 parent
9163f2d515
commit
da49820e80
7 files changed
+101
-5
No files matched your search
@@ -0,0 +1,6 @@
|
||||
---
|
||||
type: fix
|
||||
area: stalker
|
||||
---
|
||||
|
||||
Stalker movies, series, live TV, and radio now start correctly when portals return empty compatibility error fields or require literal `/` separators in playback commands.
|
||||
@@ -96,6 +96,36 @@ describe('StalkerHttpSession', () => {
|
||||
expect(config.headers).not.toHaveProperty('Authorization');
|
||||
expect(config.headers).not.toHaveProperty('Cookie');
|
||||
expect(config.headers).not.toHaveProperty('Proxy-Authorization');
|
||||
expect(config.paramsSerializer).toBeUndefined();
|
||||
});
|
||||
|
||||
it('preserves only cmd slashes while encoding reserved query characters', async () => {
|
||||
axiosMock.mockResolvedValueOnce({
|
||||
data: Buffer.from('{}'),
|
||||
headers: {},
|
||||
status: 200,
|
||||
});
|
||||
const session = new StalkerHttpSession(
|
||||
new StalkerCookieJar(),
|
||||
DEFAULT_TRANSPORT
|
||||
);
|
||||
|
||||
await session.request({
|
||||
mode: STALKER_HTTP_REQUEST_MODES.Anonymous,
|
||||
params: {
|
||||
type: 'vod',
|
||||
action: 'create_link',
|
||||
cmd: '/media/file 4?quality=hd&label=x=y#preview.mpg',
|
||||
search: '/catalog value&mode=all#anchor',
|
||||
page: 2,
|
||||
},
|
||||
url: 'http://192.168.1.20/server/load.php',
|
||||
});
|
||||
|
||||
const config = axiosMock.mock.calls[0][0];
|
||||
expect(axios.getUri(config)).toBe(
|
||||
'http://192.168.1.20/server/load.php?type=vod&action=create_link&cmd=/media/file%204%3Fquality%3Dhd%26label%3Dx%3Dy%23preview.mpg&search=%2Fcatalog%20value%26mode%3Dall%23anchor&page=2'
|
||||
);
|
||||
});
|
||||
|
||||
it('blocks an anonymous public-to-private redirect before target contact', async () => {
|
||||
|
||||
@@ -118,6 +118,11 @@ export class StalkerHttpSession {
|
||||
request.params === undefined
|
||||
? undefined
|
||||
: { ...request.params },
|
||||
paramsSerializer: hasOwnCommandParameter(request.params)
|
||||
? {
|
||||
serialize: serializeStalkerHttpParameters,
|
||||
}
|
||||
: undefined,
|
||||
redirectHooks: {
|
||||
beforeValidatedRedirect: (redirect) => {
|
||||
validateRedirectTarget(
|
||||
@@ -194,6 +199,30 @@ export class StalkerHttpSession {
|
||||
}
|
||||
}
|
||||
|
||||
function serializeStalkerHttpParameters(
|
||||
parameters: StalkerHttpParameters
|
||||
): string {
|
||||
return Object.entries(parameters)
|
||||
.map(([key, value]) => {
|
||||
const encodedValue = encodeURIComponent(String(value));
|
||||
const compatibleValue =
|
||||
key === 'cmd'
|
||||
? encodedValue.replace(/%2F/gi, '/')
|
||||
: encodedValue;
|
||||
return `${encodeURIComponent(key)}=${compatibleValue}`;
|
||||
})
|
||||
.join('&');
|
||||
}
|
||||
|
||||
function hasOwnCommandParameter(
|
||||
parameters: StalkerHttpParameters | undefined
|
||||
): boolean {
|
||||
return (
|
||||
parameters !== undefined &&
|
||||
Object.prototype.hasOwnProperty.call(parameters, 'cmd')
|
||||
);
|
||||
}
|
||||
|
||||
function assertTransportConfig(config: StalkerTransportConfig): void {
|
||||
if (
|
||||
!isPositiveFiniteBound(
|
||||
|
||||
@@ -673,7 +673,10 @@
|
||||
"kind": "json",
|
||||
"value": {
|
||||
"js": {
|
||||
"cmd": "/stream/ready.m3u8"
|
||||
"cmd": "/stream/ready.m3u8",
|
||||
"error": "",
|
||||
"load": "",
|
||||
"streamer_id": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -65,6 +65,12 @@ Stalker has two deliberately separate request paths:
|
||||
`STALKER_REQUEST`/HTTP adapter. They do not acquire a main-owned full
|
||||
session.
|
||||
|
||||
Stalker query serialization is a compatibility contract: `cmd` values retain
|
||||
literal forward slashes while query separators and all other parameter data
|
||||
remain percent-encoded. Do not replace this behavior with Axios default
|
||||
parameter serialization; some Ministra portals inspect the raw `create_link`
|
||||
query and reject encoded playback-path separators.
|
||||
|
||||
The primary ownership points are:
|
||||
|
||||
- pure URL, response, identity, state-machine, and request-policy rules:
|
||||
@@ -134,6 +140,11 @@ type remains fail-closed for ordinary candidates; only a persisted learned
|
||||
endpoint may treat a body rejected solely by media type as a stale-hint miss
|
||||
and continue bounded discovery.
|
||||
|
||||
Some successful Stalker responses, including `create_link`, carry an exact
|
||||
empty `error` string alongside their payload. The response classifier treats
|
||||
that compatibility sentinel as no error; non-empty error strings and all other
|
||||
error indicators remain fail-closed.
|
||||
|
||||
Persisted compatibility fields are:
|
||||
|
||||
- `stalkerSourceUrl`
|
||||
|
||||
@@ -242,6 +242,21 @@ describe('Stalker response classifier', () => {
|
||||
).toEqual({ kind: 'none' });
|
||||
});
|
||||
|
||||
it('accepts the empty error field returned by successful create_link responses', () => {
|
||||
expect(
|
||||
classifyStalkerResponseFailure({
|
||||
httpStatus: 200,
|
||||
value: {
|
||||
js: {
|
||||
cmd: 'ffmpeg http://media.invalid/movie.ts',
|
||||
error: '',
|
||||
load: '',
|
||||
},
|
||||
},
|
||||
})
|
||||
).toEqual({ kind: 'none' });
|
||||
});
|
||||
|
||||
it.each([
|
||||
['Authorization failed', { kind: 'token-rejected' }],
|
||||
['Access denied', { kind: 'failure', reason: 'account-access-denied' }],
|
||||
|
||||
@@ -344,11 +344,13 @@ function extractBodyError(
|
||||
): string | undefined {
|
||||
const envelope = asRecord(value);
|
||||
const js = asRecord(envelope?.['js']);
|
||||
if (typeof js?.['error'] === 'string') {
|
||||
return js['error'];
|
||||
const jsError = js?.['error'];
|
||||
if (typeof jsError === 'string' && jsError !== '') {
|
||||
return jsError;
|
||||
}
|
||||
if (typeof envelope?.['error'] === 'string') {
|
||||
return envelope['error'];
|
||||
const envelopeError = envelope?.['error'];
|
||||
if (typeof envelopeError === 'string' && envelopeError !== '') {
|
||||
return envelopeError;
|
||||
}
|
||||
if (value !== undefined) {
|
||||
return undefined;
|
||||
|
||||
Reference in new issue
Block a user