diff --git a/src/extractor/Voe.test.ts b/src/extractor/Voe.test.ts index 74dd956..c5967d9 100644 --- a/src/extractor/Voe.test.ts +++ b/src/extractor/Voe.test.ts @@ -21,4 +21,8 @@ describe('Voe', () => { test('encoding error', async () => { expect(await extractorRegistry.handle(ctx, new URL('https://voe.sx/e/c2yxvit4f6bz'))).toMatchSnapshot(); }); + + test('embed only urls which otherwise lead to 404', async () => { + expect(await extractorRegistry.handle(ctx, new URL('https://mikaylaarealike.com/e/gqlhm9hbobwu'))).toMatchSnapshot(); + }); }); diff --git a/src/extractor/Voe.ts b/src/extractor/Voe.ts index 96e6a38..3583e3f 100644 --- a/src/extractor/Voe.ts +++ b/src/extractor/Voe.ts @@ -3,7 +3,7 @@ import * as cheerio from 'cheerio'; import { NotFoundError } from '../error'; import { Context, Format, Meta, UrlResult } from '../types'; import { - buildMediaFlowProxyExtractorStreamUrl, + buildMediaFlowProxyExtractorStreamUrl, guessHeightFromPlaylist, supportsMediaFlowProxy, } from '../utils'; import { Extractor } from './Extractor'; @@ -81,6 +81,7 @@ export class Voe extends Extractor { 'maxfinishseveral.com', 'metagnathtuggers.com', 'michaelapplysome.com', + 'mikaylaarealike.com', 'nathanfromsubject.com', 'nectareousoverelate.com', 'nonesnanking.com', @@ -121,7 +122,18 @@ export class Voe extends Extractor { protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise { const headers = { Referer: meta.referer ?? url.href }; - const html = await this.fetcher.text(ctx, url, { headers }); + let html: string; + try { + html = await this.fetcher.text(ctx, url, { headers }); + } catch (error) { + /* istanbul ignore next */ + if (error instanceof NotFoundError && !url.href.includes('/e/')) { + return await this.extractInternal(ctx, new URL(`/e${url.pathname}`, url.origin), meta); + } + + /* istanbul ignore next */ + throw error; + } const redirectMatch = html.match(/window\.location\.href\s*=\s*'([^']+)/); if (redirectMatch && redirectMatch[1]) { @@ -136,10 +148,13 @@ export class Voe extends Extractor { const title = $('meta[name="description"]').attr('content')?.trim().replace(/^Watch /, '').replace(/ at VOE$/, '').trim(); const sizeMatch = html.matchAll(/[\d.]+ ?[GM]B/g).toArray().at(-1); - const heightMatch = html.match(/(\d{3,})p<\/b>/); + const size = sizeMatch ? bytes.parse(sizeMatch[0] as string) as number : /* istanbul ignore next */null; const playlistUrl = await buildMediaFlowProxyExtractorStreamUrl(ctx, this.fetcher, 'Voe', url, headers); + const heightMatch = html.match(/(\d{3,})p<\/b>/); + const height = heightMatch ? parseInt(heightMatch[1] as string) : await guessHeightFromPlaylist(ctx, this.fetcher, playlistUrl, url); + return [ { url: playlistUrl, @@ -149,9 +164,9 @@ export class Voe extends Extractor { ttl: this.ttl, meta: { ...meta, + height, title, - ...(sizeMatch && { bytes: bytes.parse(sizeMatch[0] as string) as number }), - ...(heightMatch && { height: parseInt(heightMatch[1] as string) }), + ...(size && size > 16777216 && { bytes: size }), }, }, ]; diff --git a/src/extractor/__fixtures__/Voe/https:mediaflow.test.org-cafbcf38a7db8a9fa26e61ab5818b00a b/src/extractor/__fixtures__/Voe/https:mediaflow.test.org-cafbcf38a7db8a9fa26e61ab5818b00a new file mode 100644 index 0000000..400f23f --- /dev/null +++ b/src/extractor/__fixtures__/Voe/https:mediaflow.test.org-cafbcf38a7db8a9fa26e61ab5818b00a @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2360882,RESOLUTION=1920x800,FRAME-RATE=24.000,CODECS="avc1.640028,mp4a.40.2",VIDEO-RANGE=SDR +https://mediaflow.test.org/proxy/hls/manifest.m3u8?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fmegakino.zone%2Ffilms%2F5643-weapons-die-stunde-des-verschwindens.html&d=https%3A%2F%2Fcdn-d26zysohjhaqkqbo.edgeon-bandwidth.com%2Fengine%2Fhls2-c%2F01%2F15323%2Fgqlhm9hbobwu_%2Ch%2Cn%2Cl%2C.urlset%2Findex-f1-v1-a1.m3u8%3Ft%3DLo_hAZk_EyHI5_dLs3ZUoZw1c0MuAxFI2yP1RpQ6-uc%26s%3D1762027963%26e%3D14400%26f%3D76616583%26node%3Dia36tgOsJ%2FbrWvSiHYab9Xb5naFXt8hAaWo1zPiAuGM%3D%26i%3D104.28%26sp%3D2500%26asn%3D13335%26q%3Dh%2Cn%2Cl%26rq%3D9aiQDIsNP9QQ9F2Pz2IqIMbDFUWO16qX8WvcWPet +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1339419,RESOLUTION=1728x720,FRAME-RATE=24.000,CODECS="avc1.640020,mp4a.40.2",VIDEO-RANGE=SDR +https://mediaflow.test.org/proxy/hls/manifest.m3u8?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fmegakino.zone%2Ffilms%2F5643-weapons-die-stunde-des-verschwindens.html&d=https%3A%2F%2Fcdn-d26zysohjhaqkqbo.edgeon-bandwidth.com%2Fengine%2Fhls2-c%2F01%2F15323%2Fgqlhm9hbobwu_%2Ch%2Cn%2Cl%2C.urlset%2Findex-f2-v1-a1.m3u8%3Ft%3DLo_hAZk_EyHI5_dLs3ZUoZw1c0MuAxFI2yP1RpQ6-uc%26s%3D1762027963%26e%3D14400%26f%3D76616583%26node%3Dia36tgOsJ%2FbrWvSiHYab9Xb5naFXt8hAaWo1zPiAuGM%3D%26i%3D104.28%26sp%3D2500%26asn%3D13335%26q%3Dh%2Cn%2Cl%26rq%3D9aiQDIsNP9QQ9F2Pz2IqIMbDFUWO16qX8WvcWPet +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=818334,RESOLUTION=1152x480,FRAME-RATE=24.000,CODECS="avc1.64001f,mp4a.40.2",VIDEO-RANGE=SDR +https://mediaflow.test.org/proxy/hls/manifest.m3u8?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fmegakino.zone%2Ffilms%2F5643-weapons-die-stunde-des-verschwindens.html&d=https%3A%2F%2Fcdn-d26zysohjhaqkqbo.edgeon-bandwidth.com%2Fengine%2Fhls2-c%2F01%2F15323%2Fgqlhm9hbobwu_%2Ch%2Cn%2Cl%2C.urlset%2Findex-f3-v1-a1.m3u8%3Ft%3DLo_hAZk_EyHI5_dLs3ZUoZw1c0MuAxFI2yP1RpQ6-uc%26s%3D1762027963%26e%3D14400%26f%3D76616583%26node%3Dia36tgOsJ%2FbrWvSiHYab9Xb5naFXt8hAaWo1zPiAuGM%3D%26i%3D104.28%26sp%3D2500%26asn%3D13335%26q%3Dh%2Cn%2Cl%26rq%3D9aiQDIsNP9QQ9F2Pz2IqIMbDFUWO16qX8WvcWPet +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=47743,RESOLUTION=1920x800,CODECS="avc1.640028",URI="https://mediaflow.test.org/proxy/hls/manifest.m3u8?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fmegakino.zone%2Ffilms%2F5643-weapons-die-stunde-des-verschwindens.html&d=https%3A%2F%2Fcdn-d26zysohjhaqkqbo.edgeon-bandwidth.com%2Fengine%2Fhls2-c%2F01%2F15323%2Fgqlhm9hbobwu_%2Ch%2Cn%2Cl%2C.urlset%2Fiframes-f1-v1-a1.m3u8%3Ft%3DLo_hAZk_EyHI5_dLs3ZUoZw1c0MuAxFI2yP1RpQ6-uc%26s%3D1762027963%26e%3D14400%26f%3D76616583%26node%3Dia36tgOsJ%2FbrWvSiHYab9Xb5naFXt8hAaWo1zPiAuGM%3D%26i%3D104.28%26sp%3D2500%26asn%3D13335%26q%3Dh%2Cn%2Cl%26rq%3D9aiQDIsNP9QQ9F2Pz2IqIMbDFUWO16qX8WvcWPet",VIDEO-RANGE=SDR +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=31901,RESOLUTION=1728x720,CODECS="avc1.640020",URI="https://mediaflow.test.org/proxy/hls/manifest.m3u8?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fmegakino.zone%2Ffilms%2F5643-weapons-die-stunde-des-verschwindens.html&d=https%3A%2F%2Fcdn-d26zysohjhaqkqbo.edgeon-bandwidth.com%2Fengine%2Fhls2-c%2F01%2F15323%2Fgqlhm9hbobwu_%2Ch%2Cn%2Cl%2C.urlset%2Fiframes-f2-v1-a1.m3u8%3Ft%3DLo_hAZk_EyHI5_dLs3ZUoZw1c0MuAxFI2yP1RpQ6-uc%26s%3D1762027963%26e%3D14400%26f%3D76616583%26node%3Dia36tgOsJ%2FbrWvSiHYab9Xb5naFXt8hAaWo1zPiAuGM%3D%26i%3D104.28%26sp%3D2500%26asn%3D13335%26q%3Dh%2Cn%2Cl%26rq%3D9aiQDIsNP9QQ9F2Pz2IqIMbDFUWO16qX8WvcWPet",VIDEO-RANGE=SDR +#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=20190,RESOLUTION=1152x480,CODECS="avc1.64001f",URI="https://mediaflow.test.org/proxy/hls/manifest.m3u8?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fmegakino.zone%2Ffilms%2F5643-weapons-die-stunde-des-verschwindens.html&d=https%3A%2F%2Fcdn-d26zysohjhaqkqbo.edgeon-bandwidth.com%2Fengine%2Fhls2-c%2F01%2F15323%2Fgqlhm9hbobwu_%2Ch%2Cn%2Cl%2C.urlset%2Fiframes-f3-v1-a1.m3u8%3Ft%3DLo_hAZk_EyHI5_dLs3ZUoZw1c0MuAxFI2yP1RpQ6-uc%26s%3D1762027963%26e%3D14400%26f%3D76616583%26node%3Dia36tgOsJ%2FbrWvSiHYab9Xb5naFXt8hAaWo1zPiAuGM%3D%26i%3D104.28%26sp%3D2500%26asn%3D13335%26q%3Dh%2Cn%2Cl%26rq%3D9aiQDIsNP9QQ9F2Pz2IqIMbDFUWO16qX8WvcWPet",VIDEO-RANGE=SDR diff --git a/src/extractor/__fixtures__/Voe/https:mediaflow.test.orgextractorvideohostVoeandapi_passwordtestanddhttpspercent3Apercent2Fpercent2Fmikaylaarealike.compercent2Fepercent2Fgqlhm9hbobwuandh_refererhttpspercent3Apercent2Fpercent2Fmikaylaarealike.compercent2Fepercent2Fgqlhm9hbobwu b/src/extractor/__fixtures__/Voe/https:mediaflow.test.orgextractorvideohostVoeandapi_passwordtestanddhttpspercent3Apercent2Fpercent2Fmikaylaarealike.compercent2Fepercent2Fgqlhm9hbobwuandh_refererhttpspercent3Apercent2Fpercent2Fmikaylaarealike.compercent2Fepercent2Fgqlhm9hbobwu new file mode 100644 index 0000000..d9c6b82 --- /dev/null +++ b/src/extractor/__fixtures__/Voe/https:mediaflow.test.orgextractorvideohostVoeandapi_passwordtestanddhttpspercent3Apercent2Fpercent2Fmikaylaarealike.compercent2Fepercent2Fgqlhm9hbobwuandh_refererhttpspercent3Apercent2Fpercent2Fmikaylaarealike.compercent2Fepercent2Fgqlhm9hbobwu @@ -0,0 +1 @@ +{"destination_url":"https://cdn-thhhlmzuxvpehc9r.orbitcache.com/engine/hls2-c/01/11526/qqfyi04w52mj_,l,.urlset/master.m3u8?t=g2zuS_OAHTtjrVe0bHp2xYcX7PpDxg8ebRIHTmF3H1c&s=1759861731&e=14400&f=57634053&node=UOx5276IumOH4BADikx4rlHC+ibdAWZ+mo07sx3WfaU=&i=104.28&sp=2500&asn=13335&q=l&rq=FxkyWdISXylA0UaLSPNcXWkDrksjs0pkAzy0GvIS","request_headers":{"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36","referer":"https://jilliandescribecompany.com/qqfyi04w52mj"},"mediaflow_proxy_url":"https://mediaflow.test.org/proxy/hls/manifest.m3u8","query_params":{"api_password":"test"}} diff --git a/src/extractor/__fixtures__/Voe/https:mikaylaarealike.comegqlhm9hbobwu b/src/extractor/__fixtures__/Voe/https:mikaylaarealike.comegqlhm9hbobwu new file mode 100644 index 0000000..162a189 --- /dev/null +++ b/src/extractor/__fixtures__/Voe/https:mikaylaarealike.comegqlhm9hbobwu @@ -0,0 +1,205 @@ + + + + + Watch Weapons.2025.German.DL.1080p.WEB.H264-ZeroTwo_encoded.mp4 - VOE | Content Delivery Network (CDN) & Video Cloud + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+
+
+
+
+
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + diff --git a/src/extractor/__fixtures__/Voe/https:mikaylaarealike.comgqlhm9hbobwu.error b/src/extractor/__fixtures__/Voe/https:mikaylaarealike.comgqlhm9hbobwu.error new file mode 100644 index 0000000..c31051e --- /dev/null +++ b/src/extractor/__fixtures__/Voe/https:mikaylaarealike.comgqlhm9hbobwu.error @@ -0,0 +1 @@ +Fetcher error: 404: Not Found \ No newline at end of file diff --git a/src/extractor/__snapshots__/Voe.test.ts.snap b/src/extractor/__snapshots__/Voe.test.ts.snap index c3da142..4855bee 100644 --- a/src/extractor/__snapshots__/Voe.test.ts.snap +++ b/src/extractor/__snapshots__/Voe.test.ts.snap @@ -1,5 +1,22 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +exports[`Voe embed only urls which otherwise lead to 404 1`] = ` +[ + { + "format": "hls", + "label": "VOE (via MediaFlow Proxy)", + "meta": { + "countryCodes": [], + "height": 800, + "title": "Weapons.2025.German.DL.1080p.WEB.H264-ZeroTwo_encoded.mp4", + }, + "sourceId": "voe_", + "ttl": 900000, + "url": "https://mediaflow.test.org/proxy/hls/manifest.m3u8?api_password=test&h_user-agent=Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Chrome%2F136.0.0.0+Safari%2F537.36&h_referer=https%3A%2F%2Fjilliandescribecompany.com%2Fqqfyi04w52mj&d=https%3A%2F%2Fcdn-thhhlmzuxvpehc9r.orbitcache.com%2Fengine%2Fhls2-c%2F01%2F11526%2Fqqfyi04w52mj_%2Cl%2C.urlset%2Fmaster.m3u8%3Ft%3Dg2zuS_OAHTtjrVe0bHp2xYcX7PpDxg8ebRIHTmF3H1c%26s%3D1759861731%26e%3D14400%26f%3D57634053%26node%3DUOx5276IumOH4BADikx4rlHC%2BibdAWZ%2Bmo07sx3WfaU%3D%26i%3D104.28%26sp%3D2500%26asn%3D13335%26q%3Dl%26rq%3DFxkyWdISXylA0UaLSPNcXWkDrksjs0pkAzy0GvIS", + }, +] +`; + exports[`Voe encoding error 1`] = `[]`; exports[`Voe jilliandescribecompany 1`] = ` @@ -28,6 +45,7 @@ exports[`Voe premium only without resolution 1`] = ` "meta": { "bytes": 363216240, "countryCodes": [], + "height": 800, "title": "268836--21ba9fb2-97f9-4566-b5eb-3d9cef7674cf--zrxn--2352322-voesx.mp4", }, "sourceId": "voe_", diff --git a/src/utils/FetcherMock.ts b/src/utils/FetcherMock.ts index 5ba00ec..004c3b0 100644 --- a/src/utils/FetcherMock.ts +++ b/src/utils/FetcherMock.ts @@ -5,6 +5,7 @@ import CachePolicy from 'http-cache-semantics'; import slugify from 'slugify'; import { RequestInit } from 'undici'; import winston from 'winston'; +import { NotFoundError } from '../error'; import { Context } from '../types'; import { envGet } from './env'; import { Fetcher, HttpCacheItem } from './Fetcher'; @@ -67,7 +68,12 @@ export class FetcherMock extends Fetcher { const isHead = init?.method === 'HEAD'; if (fs.existsSync(errorPath)) { - throw new Error(fs.readFileSync(errorPath).toString()); + const message = fs.readFileSync(errorPath).toString(); + if (message.includes('404: Not Found')) { + throw new NotFoundError(message); + } + + throw new Error(message); } else if (fs.existsSync(path)) { const data = fs.readFileSync(path).toString();