From 28bf47b9cf5124f758fe4b1340c4dd5f941c41b6 Mon Sep 17 00:00:00 2001 From: userness Date: Tue, 12 May 2026 20:35:03 -0400 Subject: [PATCH 1/2] Update popr.ts Credit to Gemini and @EndOverdosing on vyla-entertainment --- src/providers/popr/popr.ts | 67 ++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 7 deletions(-) diff --git a/src/providers/popr/popr.ts b/src/providers/popr/popr.ts index e3a0776..1b8621e 100644 --- a/src/providers/popr/popr.ts +++ b/src/providers/popr/popr.ts @@ -68,6 +68,51 @@ export class PoprProvider extends BaseProvider { } } + private async checkStreamType( + url: string, + headers: Record = {} + ): Promise<{ isValid: boolean; type: SourceType }> { + try { + const res = await fetch(url, { + headers: { ...this.HEADERS, ...headers }, + signal: AbortSignal.timeout(5000), + redirect: 'follow' + }); + if (!res.ok) return { isValid: false, type: 'mp4' }; + + const contentType = res.headers.get('content-type') || ''; + if ( + contentType.includes('video/mp4') || + contentType.includes('video/webm') + ) { + return { isValid: true, type: 'mp4' }; + } + + const text = await res.text(); + const trimmed = text.trim(); + + if (trimmed.startsWith('#EXTM3U')) { + const segmentLines = trimmed.split('\n').filter((l) => { + const t = l.trim(); + return t && !t.startsWith('#'); + }); + return { isValid: segmentLines.length > 0, type: 'hls' }; + } + + + if ( + trimmed.toLowerCase().includes('') || + trimmed.toLowerCase().includes(' Date: Tue, 12 May 2026 21:56:58 -0400 Subject: [PATCH 2/2] Update popr.ts --- src/providers/popr/popr.ts | 45 ++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/providers/popr/popr.ts b/src/providers/popr/popr.ts index 1b8621e..1c1a053 100644 --- a/src/providers/popr/popr.ts +++ b/src/providers/popr/popr.ts @@ -24,9 +24,6 @@ export class PoprProvider extends BaseProvider { supportedContentTypes: ['movies', 'tv'] }; - /** - * Fetch movie sources - */ async getMovieSources(media: ProviderMediaObject): Promise { try { let movieSource = await this.fetchSource(media, 'movie'); @@ -46,9 +43,6 @@ export class PoprProvider extends BaseProvider { } } - /** - * Fetch TV episode sources - */ async getTVSources(media: ProviderMediaObject): Promise { try { let tvSource = await this.fetchSource(media, 'tv'); @@ -70,7 +64,8 @@ export class PoprProvider extends BaseProvider { private async checkStreamType( url: string, - headers: Record = {} + headers: Record = {}, + serverName: string ): Promise<{ isValid: boolean; type: SourceType }> { try { const res = await fetch(url, { @@ -78,7 +73,11 @@ export class PoprProvider extends BaseProvider { signal: AbortSignal.timeout(5000), redirect: 'follow' }); - if (!res.ok) return { isValid: false, type: 'mp4' }; + + if (!res.ok) { + this.console.log(`[Popr] [${serverName}] Validation failed: HTTP ${res.status}`); + return { isValid: false, type: 'mp4' }; + } const contentType = res.headers.get('content-type') || ''; if ( @@ -96,24 +95,30 @@ export class PoprProvider extends BaseProvider { const t = l.trim(); return t && !t.startsWith('#'); }); - return { isValid: segmentLines.length > 0, type: 'hls' }; + + if (segmentLines.length === 0) { + this.console.log(`[Popr] [${serverName}] Validation failed: Empty M3U8 playlist.`); + return { isValid: false, type: 'hls' }; + } + + return { isValid: true, type: 'hls' }; } - if ( trimmed.toLowerCase().includes('') || trimmed.toLowerCase().includes(' { if (res.status !== 200) return null; + const data = (await res.json()) as VidnestResponse; const stream = data?.results?.[0]?.streams?.[0]; + if (!stream?.url) return null; const streamHeaders = stream.headers || {}; const { isValid, type } = await this.checkStreamType( stream.url, - streamHeaders + streamHeaders, + server ); if (!isValid) return null; @@ -168,7 +176,6 @@ export class PoprProvider extends BaseProvider { const QUALITIES = ['Hindi', 'English']; const languages = QUALITIES.includes(quality); - const proxyHeaders = { ...this.HEADERS, ...streamHeaders @@ -197,7 +204,10 @@ export class PoprProvider extends BaseProvider { subtitles: data.results?.[0]?.subtitles || [] }; }) - .catch(() => null) // swallow per-request errors + .catch((error) => { + this.console.error(`[Popr] [${server}] Request failed: ${error instanceof Error ? error.message : 'Unknown'}`); + return null; + }) ); const results = await Promise.allSettled(requests); @@ -213,7 +223,6 @@ export class PoprProvider extends BaseProvider { for (const sub of res.value.subtitles) { if (!sub?.url) continue; - // dedupe subtitles by URL if (!subtitlesMap.has(sub.url)) { subtitlesMap.set(sub.url, { url: this.createProxyUrl(sub.url), @@ -247,9 +256,7 @@ export class PoprProvider extends BaseProvider { ] }; } - /** - * Health check - */ + async healthCheck(): Promise { try { const response = await fetch(this.BASE_URL, {