diff --git a/src/types.ts b/src/types.ts index 84597a9..c8870f9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,6 +23,7 @@ export enum CountryCode { export enum BlockedReason { cloudflare_challenge = 'cloudflare_challenge', flaresolverr_failed = 'flaresolverr_failed', + media_flow_proxy_auth = 'media_flow_proxy_auth', unknown = 'unknown', } diff --git a/src/utils/Fetcher.test.ts b/src/utils/Fetcher.test.ts index c8535c2..721e046 100644 --- a/src/utils/Fetcher.test.ts +++ b/src/utils/Fetcher.test.ts @@ -4,6 +4,7 @@ import fetchMock from 'fetch-mock'; import { Fetcher } from './Fetcher'; import { BlockedError, HttpError, NotFoundError, QueueIsFullError, TimeoutError, TooManyRequestsError, TooManyTimeoutsError } from '../error'; import { createTestContext } from '../test'; +import { BlockedReason } from '../types'; fetchMock.mockGlobal(); const fetcher = new Fetcher(winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] })); @@ -88,7 +89,7 @@ describe('fetch', () => { fail(); } catch (error) { expect(error).toBeInstanceOf(BlockedError); - expect(error).toMatchObject({ reason: 'cloudflare_challenge' }); + expect(error).toMatchObject({ reason: BlockedReason.cloudflare_challenge }); } }); @@ -137,7 +138,7 @@ describe('fetch', () => { fail(); } catch (error) { expect(error).toBeInstanceOf(BlockedError); - expect(error).toMatchObject({ reason: 'flaresolverr_failed' }); + expect(error).toMatchObject({ reason: BlockedReason.flaresolverr_failed }); } delete process.env['FLARESOLVERR_ENDPOINT']; @@ -151,7 +152,19 @@ describe('fetch', () => { fail(); } catch (error) { expect(error).toBeInstanceOf(BlockedError); - expect(error).toMatchObject({ reason: 'unknown' }); + expect(error).toMatchObject({ reason: BlockedReason.unknown }); + } + }); + + test('converts MediaFlow Proxy forbidden to custom BlockedError', async () => { + fetchMock.get('https://media-flow-proxy-forbidden-url.test/', { status: 403 }); + + try { + await fetcher.text(createTestContext({ mediaFlowProxyUrl: 'https://media-flow-proxy-forbidden-url.test/' }), new URL('https://media-flow-proxy-forbidden-url.test/')); + fail(); + } catch (error) { + expect(error).toBeInstanceOf(BlockedError); + expect(error).toMatchObject({ reason: BlockedReason.media_flow_proxy_auth }); } }); diff --git a/src/utils/Fetcher.ts b/src/utils/Fetcher.ts index eb65646..57edf53 100644 --- a/src/utils/Fetcher.ts +++ b/src/utils/Fetcher.ts @@ -174,6 +174,10 @@ export class Fetcher { } if (httpCacheItem.status === 403) { + if (ctx.config.mediaFlowProxyUrl && url.href.startsWith(ctx.config.mediaFlowProxyUrl)) { + throw new BlockedError(BlockedReason.media_flow_proxy_auth, httpCacheItem.policy.responseHeaders()); + } + throw new BlockedError(BlockedReason.unknown, httpCacheItem.policy.responseHeaders()); } diff --git a/src/utils/StreamResolver.test.ts b/src/utils/StreamResolver.test.ts index e7b7438..b84ef00 100644 --- a/src/utils/StreamResolver.test.ts +++ b/src/utils/StreamResolver.test.ts @@ -101,6 +101,17 @@ describe('resolve', () => { countryCode: CountryCode.de, }, }, + { + url: new URL('https://example.com'), + format: Format.unknown, + isExternal: true, + error: new BlockedError(BlockedReason.media_flow_proxy_auth, {}), + label: 'hoster.com', + sourceId: '', + meta: { + countryCode: CountryCode.de, + }, + }, { url: new URL('https://example.com'), format: Format.unknown, diff --git a/src/utils/StreamResolver.ts b/src/utils/StreamResolver.ts index 45b053c..504bdb5 100644 --- a/src/utils/StreamResolver.ts +++ b/src/utils/StreamResolver.ts @@ -1,7 +1,7 @@ import { ContentType, Stream } from 'stremio-addon-sdk'; import winston from 'winston'; import bytes from 'bytes'; -import { Context, Format, UrlResult } from '../types'; +import { BlockedReason, Context, Format, UrlResult } from '../types'; import { Source } from '../source'; import { BlockedError, @@ -167,8 +167,10 @@ export class StreamResolver { private logErrorAndReturnNiceString(ctx: Context, source: string, error: unknown): string { if (error instanceof BlockedError) { - if (error.reason === 'cloudflare_challenge') { + if (error.reason === BlockedReason.cloudflare_challenge) { this.logger.warn(`${source}: Request was blocked via Cloudflare challenge.`, ctx); + } else if (error.reason === BlockedReason.media_flow_proxy_auth) { + return '⚠️ MediaFlow Proxy authentication failed. Please set the correct password.'; } else { this.logger.warn(`${source}: Request was blocked, headers: ${JSON.stringify(error.headers)}.`, ctx); } diff --git a/src/utils/__snapshots__/StreamResolver.test.ts.snap b/src/utils/__snapshots__/StreamResolver.test.ts.snap index 028ad2b..4cd5b79 100644 --- a/src/utils/__snapshots__/StreamResolver.test.ts.snap +++ b/src/utils/__snapshots__/StreamResolver.test.ts.snap @@ -19,6 +19,15 @@ exports[`resolve adds error info 1`] = ` "externalUrl": "http://localhost/", "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com +⚠️ MediaFlow Proxy authentication failed. Please set the correct password.", + }, + { + "behaviorHints": { + "notWebReady": true, + }, + "externalUrl": "http://localhost/", + "name": "WebStreamr 🇩🇪", + "title": "🔗 hoster.com ⚠️ Request was blocked.", }, { @@ -98,6 +107,15 @@ exports[`resolve adds error info 2`] = ` "externalUrl": "https://example.com/", "name": "WebStreamr 🇩🇪 ⚠️ external", "title": "🔗 hoster.com +⚠️ MediaFlow Proxy authentication failed. Please set the correct password.", + }, + { + "behaviorHints": { + "notWebReady": true, + }, + "externalUrl": "https://example.com/", + "name": "WebStreamr 🇩🇪 ⚠️ external", + "title": "🔗 hoster.com ⚠️ Request was blocked.", }, {