From e76466e250245358f8f7a5e607b0e2bb0dee3bad Mon Sep 17 00:00:00 2001 From: 4gray Date: Thu, 5 Mar 2026 23:33:07 +0100 Subject: [PATCH] feat: add support for additional HTTP headers in player and Stalker requests --- .../src/app/api/main.preload.ts | 13 +- .../src/app/events/player.events.ts | 174 +++++++++++++++--- .../src/app/events/stalker.events.ts | 86 +++++++-- apps/web/src/app/services/electron.service.ts | 6 +- apps/web/src/app/services/player.service.ts | 5 +- .../features/with-stalker-player.feature.ts | 77 ++++++-- .../features/with-stalker-portal.feature.ts | 77 ++++---- apps/web/src/typings.d.ts | 7 +- global.d.ts | 7 +- 9 files changed, 354 insertions(+), 98 deletions(-) diff --git a/apps/electron-backend/src/app/api/main.preload.ts b/apps/electron-backend/src/app/api/main.preload.ts index 732dcaf60..0a8665e5f 100644 --- a/apps/electron-backend/src/app/api/main.preload.ts +++ b/apps/electron-backend/src/app/api/main.preload.ts @@ -88,7 +88,8 @@ contextBridge.exposeInMainWorld('electron', { referer?: string, origin?: string, contentInfo?: any, - startTime?: number + startTime?: number, + headers?: Record ) => ipcRenderer.invoke( 'OPEN_MPV_PLAYER', @@ -98,7 +99,8 @@ contextBridge.exposeInMainWorld('electron', { referer, origin, contentInfo, - startTime + startTime, + headers ), openInVlc: ( url: string, @@ -107,7 +109,8 @@ contextBridge.exposeInMainWorld('electron', { referer?: string, origin?: string, contentInfo?: any, - startTime?: number + startTime?: number, + headers?: Record ) => ipcRenderer.invoke( 'OPEN_VLC_PLAYER', @@ -117,7 +120,8 @@ contextBridge.exposeInMainWorld('electron', { referer, origin, contentInfo, - startTime + startTime, + headers ), autoUpdatePlaylists: (playlists) => ipcRenderer.invoke('AUTO_UPDATE', playlists), @@ -146,6 +150,7 @@ contextBridge.exposeInMainWorld('electron', { macAddress: string; params: Record; token?: string; + serialNumber?: string; }) => ipcRenderer.invoke('STALKER_REQUEST', payload), xtreamRequest: (payload: { url: string; params: Record }) => ipcRenderer.invoke('XTREAM_REQUEST', payload), diff --git a/apps/electron-backend/src/app/events/player.events.ts b/apps/electron-backend/src/app/events/player.events.ts index 3ce558f4d..b085f00e5 100644 --- a/apps/electron-backend/src/app/events/player.events.ts +++ b/apps/electron-backend/src/app/events/player.events.ts @@ -17,6 +17,7 @@ import { ChildProcess, spawn } from 'child_process'; import { existsSync } from 'fs'; import { AddressInfo, createConnection, createServer } from 'net'; import path from 'path'; +import { getStalkerPlaybackContextHeaders } from '../services/stalker-playback-context.service'; export default class PlayerEvents { static bootstrapPlayerEvents(): Electron.IpcMain { @@ -154,6 +155,41 @@ function shouldIgnoreMpvStdoutLine(line: string): boolean { return /^(\(Paused\)\s*)?AV:\s/.test(line); } +function buildHttpHeaderFields( + origin?: string, + headers?: Record +): string[] { + const fields: string[] = []; + const normalizedHeaders = headers ?? {}; + + if ( + origin && + normalizedHeaders['Origin'] === undefined && + normalizedHeaders['origin'] === undefined + ) { + fields.push(`Origin: ${origin}`); + } + + Object.entries(normalizedHeaders).forEach(([name, value]) => { + if (!name || value === undefined || value === null) return; + const trimmedValue = String(value).trim(); + if (!trimmedValue) return; + fields.push(`${name}: ${trimmedValue}`); + }); + + return fields; +} + +function isStalkerDirectStreamProfile( + headers: Record +): boolean { + const icyMetaData = headers['Icy-MetaData'] ?? headers['icy-metadata']; + const userAgent = headers['User-Agent'] ?? headers['user-agent']; + + return String(icyMetaData).trim() === '1' && + String(userAgent).trim().toLowerCase() === 'ksplayer'; +} + function startPositionPolling(socketPath: string, contentInfo: any) { stopPositionPolling(); @@ -312,19 +348,47 @@ ipcMain.handle( referer?: string, origin?: string, contentInfo?: any, - startTime?: number + startTime?: number, + headers?: Record ) => { try { const mpvPath = getMpvPath(); const reuseInstance = store.get(MPV_REUSE_INSTANCE, false); + const fallbackHeaders = getStalkerPlaybackContextHeaders(url) ?? {}; + const mergedHeaders = isStalkerDirectStreamProfile(fallbackHeaders) + ? fallbackHeaders + : { + ...fallbackHeaders, + ...(headers ?? {}), + }; + const effectiveOrigin = + origin ?? + mergedHeaders['Origin'] ?? + mergedHeaders['origin'] ?? + undefined; + const effectiveReferer = + referer ?? + mergedHeaders['Referer'] ?? + mergedHeaders['referer'] ?? + undefined; + const effectiveUserAgent = + userAgent ?? + mergedHeaders['User-Agent'] ?? + mergedHeaders['user-agent'] ?? + undefined; + const headerFields = buildHttpHeaderFields( + effectiveOrigin, + mergedHeaders + ); console.log('[MPV] Opening player', { path: mpvPath, reuseInstance, stream: maskUrlForLogs(url), - hasUserAgent: Boolean(userAgent), - hasReferer: Boolean(referer), - hasOrigin: Boolean(origin), + hasUserAgent: Boolean(effectiveUserAgent), + hasReferer: Boolean(effectiveReferer), + hasOrigin: Boolean(effectiveOrigin), + headerCount: headerFields.length, hasContentInfo: Boolean(contentInfo), startTime: startTime ?? null, }); @@ -338,11 +402,38 @@ ipcMain.handle( ) { console.log('Reusing existing MPV instance'); try { - const loadFileArgs: Array = [url, 'replace']; + if (effectiveUserAgent) { + await sendMpvCommand('set_property', [ + 'user-agent', + effectiveUserAgent, + ]); + } + if (effectiveReferer) { + await sendMpvCommand('set_property', [ + 'referrer', + effectiveReferer, + ]); + } + if (headerFields.length > 0) { + await sendMpvCommand('set_property', [ + 'http-header-fields', + headerFields.join(','), + ]); + } + + const loadFileArgs: Array = [ + url, + 'replace', + ]; + const loadFileOptions: string[] = []; + + // loadfile args are: url, flags, index, options + // Index must be numeric; options are a comma-separated key=value list. if (title) { - // loadfile args are: url, flags, index, options - // Index must be numeric; use -1, then pass options as 4th arg. - loadFileArgs.push(-1, `force-media-title=${title}`); + loadFileOptions.push(`force-media-title=${title}`); + } + if (loadFileOptions.length > 0) { + loadFileArgs.push(-1, loadFileOptions.join(',')); } await sendMpvCommand('loadfile', loadFileArgs); @@ -388,22 +479,26 @@ ipcMain.handle( ? `\\\\.\\pipe\\mpv-${Date.now()}` : `/tmp/mpvsocket-${Date.now()}`; - const args = [`--input-ipc-server=${socketPath}`, '--idle=yes']; + const args = [ + `--input-ipc-server=${socketPath}`, + '--idle=yes', + // IPTV URLs often look like generic web pages to ytdl_hook and can fail with 403. + // Force MPV to open them directly instead of probing via yt-dlp/youtube-dl. + '--ytdl=no', + ]; // Add user agent if provided - if (userAgent) { - args.push(`--user-agent=${userAgent}`); + if (effectiveUserAgent) { + args.push(`--user-agent=${effectiveUserAgent}`); } // Add referer if provided - if (referer) { - args.push(`--referrer=${referer}`); + if (effectiveReferer) { + args.push(`--referrer=${effectiveReferer}`); } - // Add origin as custom HTTP header if provided - // MPV doesn't have a direct --origin flag, so we use --http-header-fields - if (origin) { - args.push(`--http-header-fields=Origin: ${origin}`); + if (headerFields.length > 0) { + args.push(`--http-header-fields=${headerFields.join(',')}`); } // Add title if provided @@ -587,10 +682,33 @@ ipcMain.handle( referer?: string, origin?: string, contentInfo?: any, - startTime?: number + startTime?: number, + headers?: Record ) => { try { const vlcPath = getVlcPath(); + const fallbackHeaders = getStalkerPlaybackContextHeaders(url) ?? {}; + const mergedHeaders = isStalkerDirectStreamProfile(fallbackHeaders) + ? fallbackHeaders + : { + ...fallbackHeaders, + ...(headers ?? {}), + }; + const effectiveOrigin = + origin ?? + mergedHeaders['Origin'] ?? + mergedHeaders['origin'] ?? + undefined; + const effectiveReferer = + referer ?? + mergedHeaders['Referer'] ?? + mergedHeaders['referer'] ?? + undefined; + const effectiveUserAgent = + userAgent ?? + mergedHeaders['User-Agent'] ?? + mergedHeaders['user-agent'] ?? + undefined; console.log('Opening VLC player with path:', vlcPath); console.log('URL:', url); // console.log('User-Agent:', userAgent); @@ -618,17 +736,29 @@ ipcMain.handle( } // Add user agent if provided (VLC uses :http-user-agent= format) - if (userAgent) { - args.push(`:http-user-agent=${userAgent}`); + if (effectiveUserAgent) { + args.push(`:http-user-agent=${effectiveUserAgent}`); } // Add referer if provided (VLC uses :http-referrer= format) - if (referer) { - args.push(`:http-referrer=${referer}`); + if (effectiveReferer) { + args.push(`:http-referrer=${effectiveReferer}`); } // Note: VLC doesn't have a direct origin option, but origin is typically // included in referer for most IPTV use cases + if (effectiveOrigin && !effectiveReferer) { + args.push(`:http-referrer=${effectiveOrigin}`); + } + + if (Object.keys(mergedHeaders).length > 0) { + Object.entries(mergedHeaders).forEach(([name, value]) => { + if (!name || value === undefined || value === null) return; + const trimmedValue = String(value).trim(); + if (!trimmedValue) return; + args.push(`:http-header=${name}: ${trimmedValue}`); + }); + } // Add start time if provided if (startTime) { diff --git a/apps/electron-backend/src/app/events/stalker.events.ts b/apps/electron-backend/src/app/events/stalker.events.ts index 20f600afd..a9c5405a6 100644 --- a/apps/electron-backend/src/app/events/stalker.events.ts +++ b/apps/electron-backend/src/app/events/stalker.events.ts @@ -4,8 +4,39 @@ */ import axios, { AxiosRequestConfig } from 'axios'; +import { createHash } from 'crypto'; import { ipcMain } from 'electron'; import { STALKER_REQUEST } from 'shared-interfaces'; +import { rememberStalkerPlaybackContext } from '../services/stalker-playback-context.service'; + +const LEGACY_DEFAULT_SERIAL = 'BEDACD4569BAF'; + +function deriveStalkerIdentity( + macAddress: string, + providedSerial?: string +): { serialNumber: string; cfduid: string } { + const normalizedMac = String(macAddress ?? '').trim().toUpperCase(); + const md5 = createHash('md5').update(normalizedMac).digest('hex'); + const derivedSerial = md5.slice(0, 13).toUpperCase(); + + const normalizedProvided = String(providedSerial ?? '') + .trim() + .toUpperCase(); + const useProvidedSerial = + normalizedProvided.length > 0 && + normalizedProvided !== LEGACY_DEFAULT_SERIAL; + + const serialNumber = useProvidedSerial ? normalizedProvided : derivedSerial; + + // Keep serial and __cfduid coherent: serial as prefix + stable MAC hash tail. + const serialPrefix = serialNumber.toLowerCase().replace(/[^a-f0-9]/g, ''); + const cfduid = `${serialPrefix}${md5.slice(serialPrefix.length)}`.slice( + 0, + 32 + ); + + return { serialNumber, cfduid }; +} export default class StalkerEvents { static bootstrapStalkerEvents(): Electron.IpcMain { @@ -30,6 +61,29 @@ ipcMain.handle( ) => { try { const { url, macAddress, params, token, serialNumber } = payload; + const identity = deriveStalkerIdentity(macAddress, serialNumber); + const effectiveSerialNumber = identity.serialNumber; + const requestParams = { ...params }; + + // Some providers validate sn/metrics strongly in get_profile. + if ( + requestParams.type === 'stb' && + requestParams.action === 'get_profile' + ) { + requestParams.sn = effectiveSerialNumber; + + if (typeof requestParams.metrics === 'string') { + try { + const parsedMetrics = JSON.parse(requestParams.metrics); + requestParams.metrics = JSON.stringify({ + ...(parsedMetrics ?? {}), + sn: effectiveSerialNumber, + }); + } catch { + // Keep original metrics payload when malformed. + } + } + } // Build URL with query parameters // Note: For 'cmd' parameter, we need to use encodeURI (not encodeURIComponent) @@ -37,7 +91,7 @@ ipcMain.handle( const urlObject = new URL(url); const queryParts: string[] = []; - Object.entries(params).forEach(([key, value]) => { + Object.entries(requestParams).forEach(([key, value]) => { if (key === 'cmd') { // Don't encode cmd - it's already a path like /media/12345.mpg // Encoding would break the path format expected by the server @@ -51,7 +105,7 @@ ipcMain.handle( }); // Always add JsHttpRequest parameter if not present (required by Stalker API) - if (!params['JsHttpRequest']) { + if (!requestParams['JsHttpRequest']) { queryParts.push('JsHttpRequest=1-xml'); } @@ -61,13 +115,7 @@ ipcMain.handle( // Build cookie string matching the working curl example format // Format: mac=XX:XX:XX:XX:XX:XX; stb_lang=de_DE; timezone=Europe/Berlin; __cfduid=... // The __cfduid cookie uses the serial number lowercase + random suffix - let cookieString = `mac=${macAddress}; stb_lang=de_DE; timezone=Europe/Berlin`; - if (serialNumber) { - // Generate __cfduid from serial number (lowercase) + random suffix - const cfduidBase = serialNumber.toLowerCase(); - const cfduidSuffix = 'e030245495acd6ebfc1'; // Static suffix matching working app - cookieString += `; __cfduid=${cfduidBase}${cfduidSuffix}`; - } + const cookieString = `mac=${macAddress}; stb_lang=en_US@rg=dezzzz; timezone=Europe/Berlin; __cfduid=${identity.cfduid}`; // Build headers - using MAG250 User-Agent that stalker-to-m3u uses const headers: Record = { @@ -83,8 +131,8 @@ ipcMain.handle( }; // Add SN (serial number) header if provided - required by some portals - if (serialNumber) { - headers['SN'] = serialNumber; + if (effectiveSerialNumber) { + headers['SN'] = effectiveSerialNumber; } // Add Authorization header if token is provided @@ -94,7 +142,7 @@ ipcMain.handle( // Determine timeout based on action type // create_link requests can take longer as server generates stream URL - const isCreateLink = params.action === 'create_link'; + const isCreateLink = requestParams.action === 'create_link'; const requestTimeout = isCreateLink ? 30000 : 15000; // Configure axios request @@ -122,6 +170,20 @@ ipcMain.handle( } // Return the response data + if ( + params.action === 'create_link' && + response.data?.js?.cmd && + typeof response.data.js.cmd === 'string' + ) { + rememberStalkerPlaybackContext({ + streamUrl: response.data.js.cmd, + portalUrl: url, + macAddress, + serialNumber: effectiveSerialNumber, + token, + }); + } + return response.data; } catch (error) { console.error('[StalkerEvents] Request error:', error); diff --git a/apps/web/src/app/services/electron.service.ts b/apps/web/src/app/services/electron.service.ts index bc1db5763..f5459f0ea 100644 --- a/apps/web/src/app/services/electron.service.ts +++ b/apps/web/src/app/services/electron.service.ts @@ -99,7 +99,8 @@ export class ElectronService extends DataService { data.referer ?? undefined, data.origin ?? undefined, data.contentInfo, - data.startTime + data.startTime, + data.headers ?? undefined ); /* thumbnail: data.thumbnail ?? '', */ } catch (error: any) { @@ -124,7 +125,8 @@ export class ElectronService extends DataService { data.referer ?? undefined, data.origin ?? undefined, data.contentInfo, - data.startTime + data.startTime, + data.headers ?? undefined ); } catch (error: any) { const errorMessage = error?.message || String(error); diff --git a/apps/web/src/app/services/player.service.ts b/apps/web/src/app/services/player.service.ts index 079525bc5..5291abd25 100644 --- a/apps/web/src/app/services/player.service.ts +++ b/apps/web/src/app/services/player.service.ts @@ -31,7 +31,8 @@ export class PlayerService { referer?: string, origin?: string, contentInfo?: any, - startTime?: number + startTime?: number, + headers?: Record ) { const player = this.settingsStore.player() ?? VideoPlayer.VideoJs; @@ -46,6 +47,7 @@ export class PlayerService { 'user-agent': userAgent, referer: referer, origin: origin, + headers, contentInfo, startTime, }); @@ -60,6 +62,7 @@ export class PlayerService { 'user-agent': userAgent, referer: referer, origin: origin, + headers, contentInfo, startTime, }); diff --git a/apps/web/src/app/stalker/stores/features/with-stalker-player.feature.ts b/apps/web/src/app/stalker/stores/features/with-stalker-player.feature.ts index e20e23f78..f6051f9f5 100644 --- a/apps/web/src/app/stalker/stores/features/with-stalker-player.feature.ts +++ b/apps/web/src/app/stalker/stores/features/with-stalker-player.feature.ts @@ -3,14 +3,18 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { signalStoreFeature, withMethods } from '@ngrx/signals'; import { TranslateService } from '@ngx-translate/core'; import { DataService, PlaylistsService, StalkerSessionService } from 'services'; -import { Playlist, STALKER_REQUEST, StalkerPortalActions } from 'shared-interfaces'; +import { + Playlist, + STALKER_REQUEST, + StalkerPortalActions, +} from 'shared-interfaces'; import { PlayerService } from '../../../services/player.service'; +import { createLogger } from '../../../shared/utils/logger'; import { StalkerContentTypes } from '../../stalker-content-types'; import { normalizeStalkerEntityId, normalizeStalkerEntityIdAsNumber, } from '../../stalker-vod.utils'; -import { createLogger } from '../../../shared/utils/logger'; type StalkerContentType = 'itv' | 'vod' | 'series'; @@ -61,6 +65,30 @@ export function withStalkerPlayer() { cmd: string, series?: number ) => { + const normalizeCmdValue = (value: string): string => { + const trimmed = String(value ?? '').trim(); + if (!trimmed) return ''; + + // Some portals prepend transport wrappers like: + // "ffmpeg http://...", "ffrt http://...", etc. + const splitAt = trimmed.indexOf(' '); + if (splitAt > 0) { + const candidate = trimmed + .slice(splitAt + 1) + .trim(); + if ( + candidate.startsWith('http://') || + candidate.startsWith('https://') || + candidate.startsWith('/') || + candidate.startsWith('?') + ) { + return candidate; + } + } + + return trimmed; + }; + const selectedContentType = storeState.selectedContentType(); const type = series ? 'vod' : selectedContentType; @@ -69,7 +97,8 @@ export function withStalkerPlayer() { // The server adds the required token for playback authorization // Note: cmd is already transformed during item processing (has_files items) const params = { - action: StalkerContentTypes[selectedContentType].getLink, + action: StalkerContentTypes[selectedContentType] + .getLink, cmd: cmd, type, disable_ad: '0', @@ -83,10 +112,11 @@ export function withStalkerPlayer() { let response: StalkerResponse; if (playlist?.isFullStalkerPortal) { // Full stalker portal - use authenticated request with retry - response = await stalkerSession.makeAuthenticatedRequest( - playlist, - params - ); + response = + await stalkerSession.makeAuthenticatedRequest( + playlist, + params + ); } else { // Simple stalker portal - no auth needed response = await dataService.sendIpcEvent( @@ -106,16 +136,13 @@ export function withStalkerPlayer() { throw new Error(errorMsg); } - let url = response.js.cmd as string; + let url = normalizeCmdValue(response.js.cmd as string); // If cmd is empty, the content is not available if (!url) { throw new Error('nothing_to_play'); } - if (url.startsWith('ffmpeg')) { - url = url.split(' ')[1]; - } // Handle incomplete URLs - some portals return just query params or relative paths if ( url && @@ -136,7 +163,8 @@ export function withStalkerPlayer() { pathParts[i] === 'portal' ) { basePath = - '/' + pathParts.slice(1, i + 1).join('/'); + '/' + + pathParts.slice(1, i + 1).join('/'); break; } } @@ -144,9 +172,17 @@ export function withStalkerPlayer() { // If url starts with ?, it's just query params // Combine with the original cmd path to form the complete streaming URL if (url.startsWith('?')) { + const normalizedCmd = normalizeCmdValue(cmd); // The streaming URL is: portal origin + base path + original cmd path + token query // e.g., http://portal.com + /stalker_portal + /media/12345.mpg + ?token=xxx - url = `${portalUrlObj.origin}${basePath}${cmd}${url}`; + if ( + normalizedCmd.startsWith('http://') || + normalizedCmd.startsWith('https://') + ) { + url = `${normalizedCmd}${url}`; + } else { + url = `${portalUrlObj.origin}${basePath}${normalizedCmd}${url}`; + } } else if (url.startsWith('/')) { // Relative path - prepend origin and base path url = `${portalUrlObj.origin}${basePath}${url}`; @@ -173,10 +209,11 @@ export function withStalkerPlayer() { let response: StalkerResponse; if (playlist.isFullStalkerPortal) { - response = await stalkerSession.makeAuthenticatedRequest( - playlist, - queryParams - ); + response = + await stalkerSession.makeAuthenticatedRequest( + playlist, + queryParams + ); } else { response = await dataService.sendIpcEvent( STALKER_REQUEST, @@ -205,8 +242,10 @@ export function withStalkerPlayer() { ) => { const playlistId = storeState.currentPlaylist()?._id; if (!playlistId) return; - const recentlyViewedItem: { id: string; title: string } & - Record = { + const recentlyViewedItem: { + id: string; + title: string; + } & Record = { ...item, id: normalizeStalkerEntityId(item?.id), cmd, diff --git a/apps/web/src/app/stalker/stores/features/with-stalker-portal.feature.ts b/apps/web/src/app/stalker/stores/features/with-stalker-portal.feature.ts index 69be4da51..4b3652275 100644 --- a/apps/web/src/app/stalker/stores/features/with-stalker-portal.feature.ts +++ b/apps/web/src/app/stalker/stores/features/with-stalker-portal.feature.ts @@ -64,42 +64,51 @@ export function withStalkerPortal() { }, }) ), - withMethods((store, dataService = inject(DataService)) => ({ - async setCurrentPlaylist(playlist: PlaylistMeta | undefined) { - patchState(store, { currentPlaylist: playlist }); + withMethods( + ( + store, + dataService = inject(DataService), + stalkerSession = inject(StalkerSessionService) + ) => ({ + async setCurrentPlaylist(playlist: PlaylistMeta | undefined) { + stalkerSession.setActiveWatchdogPlaylist( + (playlist as Playlist) || undefined + ); + patchState(store, { currentPlaylist: playlist }); - // Ensure Stalker playlist exists in SQLite for playback positions - // Only sync if this is actually a Stalker playlist (has macAddress and portalUrl) - if ( - playlist && - dataService.isElectron && - playlist._id && - playlist.macAddress && - playlist.portalUrl - ) { - try { - const playlistId = String(playlist._id); - // Check if playlist exists in SQLite - const existing = - await window.electron.dbGetPlaylist(playlistId); - if (!existing) { - // Create playlist in SQLite - await window.electron.dbCreatePlaylist({ - id: playlistId, - name: playlist.title || '', - macAddress: playlist.macAddress || '', - url: playlist.portalUrl || '', - type: 'stalker', - }); + // Ensure Stalker playlist exists in SQLite for playback positions + // Only sync if this is actually a Stalker playlist (has macAddress and portalUrl) + if ( + playlist && + dataService.isElectron && + playlist._id && + playlist.macAddress && + playlist.portalUrl + ) { + try { + const playlistId = String(playlist._id); + // Check if playlist exists in SQLite + const existing = + await window.electron.dbGetPlaylist(playlistId); + if (!existing) { + // Create playlist in SQLite + await window.electron.dbCreatePlaylist({ + id: playlistId, + name: playlist.title || '', + macAddress: playlist.macAddress || '', + url: playlist.portalUrl || '', + type: 'stalker', + }); + } + } catch (error) { + logger.error( + 'Error syncing Stalker playlist to SQLite', + error + ); } - } catch (error) { - logger.error( - 'Error syncing Stalker playlist to SQLite', - error - ); } - } - }, - })) + }, + }) + ) ); } diff --git a/apps/web/src/typings.d.ts b/apps/web/src/typings.d.ts index 852aded9e..27655b63a 100644 --- a/apps/web/src/typings.d.ts +++ b/apps/web/src/typings.d.ts @@ -34,7 +34,8 @@ declare global { referer?: string, origin?: string, contentInfo?: any, - startTime?: number + startTime?: number, + headers?: Record ) => void; openInVlc: ( url: string, @@ -43,7 +44,8 @@ declare global { referer?: string, origin?: string, contentInfo?: any, - startTime?: number + startTime?: number, + headers?: Record ) => void; autoUpdatePlaylists: (playlists: any[]) => Promise; fetchEpg: ( @@ -80,6 +82,7 @@ declare global { macAddress: string; params: Record; token?: string; + serialNumber?: string; }) => Promise; xtreamRequest: (payload: { url: string; diff --git a/global.d.ts b/global.d.ts index 525f31622..131b3c621 100644 --- a/global.d.ts +++ b/global.d.ts @@ -37,7 +37,8 @@ declare global { referer?: string, origin?: string, contentInfo?: any, - startTime?: number + startTime?: number, + headers?: Record ) => void; openInVlc: ( url: string, @@ -46,7 +47,8 @@ declare global { referer?: string, origin?: string, contentInfo?: any, - startTime?: number + startTime?: number, + headers?: Record ) => void; autoUpdatePlaylists: (playlists: Playlist[]) => Promise; fetchEpg: ( @@ -83,6 +85,7 @@ declare global { macAddress: string; params: Record; token?: string; + serialNumber?: string; }) => Promise; xtreamRequest: (payload: { url: string;