From 854b211f2c30abc149a8f2d33fd77f727ce565d1 Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Mon, 4 Aug 2025 09:52:48 +0000 Subject: [PATCH] feat: do not show errors by default --- src/types.ts | 2 +- src/utils/StreamResolver.test.ts | 8 +- src/utils/StreamResolver.ts | 26 ++-- .../__snapshots__/StreamResolver.test.ts.snap | 127 +++--------------- src/utils/__snapshots__/manifest.test.ts.snap | 30 ++++- src/utils/config.ts | 2 + src/utils/manifest.test.ts | 6 +- src/utils/manifest.ts | 7 + 8 files changed, 75 insertions(+), 133 deletions(-) diff --git a/src/types.ts b/src/types.ts index 097742e..63ed775 100644 --- a/src/types.ts +++ b/src/types.ts @@ -15,7 +15,7 @@ export type CustomManifest = Manifest & { }; }; -export type Config = Partial & Record<`disableExtractor_${string}`, string>>; +export type Config = Partial & Record<`disableExtractor_${string}`, string>>; export enum CountryCode { multi = 'multi', diff --git a/src/utils/StreamResolver.test.ts b/src/utils/StreamResolver.test.ts index c1fd4fc..6bcc0c3 100644 --- a/src/utils/StreamResolver.test.ts +++ b/src/utils/StreamResolver.test.ts @@ -32,9 +32,11 @@ describe('resolve', () => { const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher))); const streams = await streamResolver.resolve(ctx, [meineCloud], 'movie', new ImdbId('tt123456789', undefined, undefined)); - expect(streams).toMatchSnapshot(); + const streamsWithShowErrors = await streamResolver.resolve({ ...ctx, config: { ...ctx.config, showErrors: 'on' } }, [meineCloud], 'movie', new ImdbId('tt123456789', undefined, undefined)); + expect(streamsWithShowErrors).toMatchSnapshot(); + fetcherSpy.mockRestore(); }); @@ -241,8 +243,8 @@ describe('resolve', () => { const streams = await streamResolver.resolve(ctx, [new MockHandler()], 'movie', new ImdbId('tt11655566', undefined, undefined)); expect(streams).toMatchSnapshot(); - const streamsWithExternalUrls = await streamResolver.resolve({ ...ctx, config: { ...ctx.config, includeExternalUrls: 'on' } }, [new MockHandler()], 'movie', new ImdbId('tt11655566', undefined, undefined)); - expect(streamsWithExternalUrls).toMatchSnapshot(); + const streamsWithShowErrors = await streamResolver.resolve({ ...ctx, config: { ...ctx.config, showErrors: 'on' } }, [new MockHandler()], 'movie', new ImdbId('tt11655566', undefined, undefined)); + expect(streamsWithShowErrors).toMatchSnapshot(); }); test('ignores not found errors', async () => { diff --git a/src/utils/StreamResolver.ts b/src/utils/StreamResolver.ts index 463877b..4712f33 100644 --- a/src/utils/StreamResolver.ts +++ b/src/utils/StreamResolver.ts @@ -5,7 +5,7 @@ import { logErrorAndReturnNiceString, NotFoundError } from '../error'; import { ExtractorRegistry } from '../extractor'; import { Source } from '../source'; import { Context, Format, UrlResult } from '../types'; -import { showExternalUrls } from './config'; +import { showErrors, showExternalUrls } from './config'; import { envGetAppName } from './env'; import { Id } from './id'; import { flagFromCountryCode } from './language'; @@ -62,11 +62,13 @@ export class StreamResolver { handlerErrorOccurred = true; - streams.push({ - name: envGetAppName(), - title: [`🔗 ${source.label}`, logErrorAndReturnNiceString(ctx, this.logger, source.id, error)].join('\n'), - externalUrl: source.baseUrl, - }); + if (showErrors(ctx.config)) { + streams.push({ + name: envGetAppName(), + title: [`🔗 ${source.label}`, logErrorAndReturnNiceString(ctx, this.logger, source.id, error)].join('\n'), + externalUrl: source.baseUrl, + }); + } } }); await Promise.all(handlerPromises); @@ -96,9 +98,9 @@ export class StreamResolver { this.logger.info(`Return ${urlResults.length} streams`, ctx); streams.push( - ...urlResults.filter(urlResult => !urlResult.isExternal || showExternalUrls(ctx.config) || urlResult.error) + ...urlResults.filter(urlResult => !urlResult.error || showErrors(ctx.config)) .map(urlResult => ({ - ...this.buildUrl(ctx, urlResult), + ...this.buildUrl(urlResult), name: this.buildName(ctx, urlResult), title: this.buildTitle(ctx, urlResult), behaviorHints: { @@ -133,16 +135,12 @@ export class StreamResolver { return Math.min(...urlResults.map(urlResult => urlResult.ttl as number)); }; - private buildUrl(ctx: Context, urlResult: UrlResult): { externalUrl: string } | { url: string } { + private buildUrl(urlResult: UrlResult): { externalUrl: string } | { url: string } { if (!urlResult.isExternal) { return { url: urlResult.url.href }; } - if (showExternalUrls(ctx.config)) { - return { externalUrl: urlResult.url.href }; - } - - return { externalUrl: ctx.hostUrl.href }; + return { externalUrl: urlResult.url.href }; }; private buildName(ctx: Context, urlResult: UrlResult): string { diff --git a/src/utils/__snapshots__/StreamResolver.test.ts.snap b/src/utils/__snapshots__/StreamResolver.test.ts.snap index ecac763..d3608d9 100644 --- a/src/utils/__snapshots__/StreamResolver.test.ts.snap +++ b/src/utils/__snapshots__/StreamResolver.test.ts.snap @@ -21,105 +21,6 @@ exports[`resolve adds error info 1`] = ` "title": "🔗 working2", "url": "https://working1.com/", }, - { - "behaviorHints": { - "notWebReady": true, - }, - "externalUrl": "http://localhost/", - "name": "WebStreamr 🇩🇪", - "title": "🔗 hoster.com -⚠️ Request was blocked. Reason: cloudflare_challenge", - }, - { - "behaviorHints": { - "notWebReady": true, - }, - "externalUrl": "http://localhost/", - "name": "WebStreamr 🇩🇪", - "title": "🔗 hoster.com -⚠️ Request was blocked. Reason: cloudflare_censor", - }, - { - "behaviorHints": { - "notWebReady": true, - }, - "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. Reason: unknown", - }, - { - "behaviorHints": { - "notWebReady": true, - }, - "externalUrl": "http://localhost/", - "name": "WebStreamr 🇩🇪", - "title": "🔗 hoster.com -🚦 Request was rate-limited. Please try again later or consider self-hosting.", - }, - { - "behaviorHints": { - "notWebReady": true, - }, - "externalUrl": "http://localhost/", - "name": "WebStreamr 🇩🇪", - "title": "🔗 hoster.com -🚦 Too many recent timeouts. Please try again later.", - }, - { - "behaviorHints": { - "notWebReady": true, - }, - "externalUrl": "http://localhost/", - "name": "WebStreamr 🇩🇪", - "title": "🔗 hoster.com -❌ Request failed. Request-id: test.", - }, - { - "behaviorHints": { - "notWebReady": true, - }, - "externalUrl": "http://localhost/", - "name": "WebStreamr 🇩🇪", - "title": "🔗 hoster.com -🐢 Request timed out.", - }, - { - "behaviorHints": { - "notWebReady": true, - }, - "externalUrl": "http://localhost/", - "name": "WebStreamr 🇩🇪", - "title": "🔗 hoster.com -⏳ Request queue is full. Please try again later or consider self-hosting.", - }, - { - "behaviorHints": { - "notWebReady": true, - }, - "externalUrl": "http://localhost/", - "name": "WebStreamr 🇩🇪", - "title": "🔗 hoster.com -❌ Remote server has issues. We can't fix this, please try later again.", - }, - { - "behaviorHints": { - "notWebReady": true, - }, - "externalUrl": "http://localhost/", - "name": "WebStreamr 🇩🇪", - "title": "🔗 hoster.com -❌ Request failed with status 418 (I'm a tea pot). Request-id: test.", - }, ], } `; @@ -150,7 +51,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com ⚠️ Request was blocked. Reason: cloudflare_challenge", }, @@ -159,7 +60,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com ⚠️ Request was blocked. Reason: cloudflare_censor", }, @@ -168,7 +69,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com ⚠️ MediaFlow Proxy authentication failed. Please set the correct password.", }, @@ -177,7 +78,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com ⚠️ Request was blocked. Reason: unknown", }, @@ -186,7 +87,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com 🚦 Request was rate-limited. Please try again later or consider self-hosting.", }, @@ -195,7 +96,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com 🚦 Too many recent timeouts. Please try again later.", }, @@ -204,7 +105,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example2.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com ❌ Request failed. Request-id: test.", }, @@ -213,7 +114,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example2.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com 🐢 Request timed out.", }, @@ -222,7 +123,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example3.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com ⏳ Request queue is full. Please try again later or consider self-hosting.", }, @@ -231,7 +132,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example4.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com ❌ Remote server has issues. We can't fix this, please try later again.", }, @@ -240,7 +141,7 @@ exports[`resolve adds error info 2`] = ` "notWebReady": true, }, "externalUrl": "https://example5.com/", - "name": "WebStreamr 🇩🇪 ⚠️ external", + "name": "WebStreamr 🇩🇪", "title": "🔗 hoster.com ❌ Request failed with status 418 (I'm a tea pot). Request-id: test.", }, @@ -460,6 +361,12 @@ exports[`resolve returns sorted results 2`] = ` `; exports[`resolve returns source errors as stream 1`] = ` +{ + "streams": [], +} +`; + +exports[`resolve returns source errors as stream 2`] = ` { "streams": [ { diff --git a/src/utils/__snapshots__/manifest.test.ts.snap b/src/utils/__snapshots__/manifest.test.ts.snap index 57e102e..4e9ea8e 100644 --- a/src/utils/__snapshots__/manifest.test.ts.snap +++ b/src/utils/__snapshots__/manifest.test.ts.snap @@ -2,6 +2,11 @@ exports[`buildManifest disable extractors 1`] = ` [ + { + "key": "showErrors", + "title": "Show errors", + "type": "checkbox", + }, { "key": "includeExternalUrls", "title": "Include external URLs in results", @@ -33,8 +38,14 @@ exports[`buildManifest disable extractors 1`] = ` ] `; -exports[`buildManifest has checked includeExternalUrls 1`] = ` +exports[`buildManifest has checked showErrors and includeExternalUrls 1`] = ` [ + { + "default": "checked", + "key": "showErrors", + "title": "Show errors", + "type": "checkbox", + }, { "default": "checked", "key": "includeExternalUrls", @@ -74,6 +85,11 @@ exports[`buildManifest has checked source with appropriate config 1`] = ` "title": "Latin American Spanish 🇲🇽 (VerHdLink)", "type": "checkbox", }, + { + "key": "showErrors", + "title": "Show errors", + "type": "checkbox", + }, { "default": "checked", "key": "includeExternalUrls", @@ -122,6 +138,11 @@ exports[`buildManifest has unchecked source without a config 1`] = ` "title": "Latin American Spanish 🇲🇽 (VerHdLink)", "type": "checkbox", }, + { + "key": "showErrors", + "title": "Show errors", + "type": "checkbox", + }, { "key": "includeExternalUrls", "title": "Include external URLs in results", @@ -142,8 +163,13 @@ exports[`buildManifest has unchecked source without a config 1`] = ` ] `; -exports[`buildManifest includeExternalUrls is unchecked by default 1`] = ` +exports[`buildManifest showErrors and includeExternalUrls are unchecked by default 1`] = ` [ + { + "key": "showErrors", + "title": "Show errors", + "type": "checkbox", + }, { "key": "includeExternalUrls", "title": "Include external URLs in results", diff --git a/src/utils/config.ts b/src/utils/config.ts index 3c0c194..fd503db 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -5,6 +5,8 @@ export const getDefaultConfig = (): Config => { return { multi: 'on', en: 'on' }; }; +export const showErrors = (config: Config): boolean => 'showErrors' in config; + export const showExternalUrls = (config: Config): boolean => 'includeExternalUrls' in config; export const hasMultiEnabled = (config: Config): boolean => 'multi' in config; diff --git a/src/utils/manifest.test.ts b/src/utils/manifest.test.ts index f8be976..41fe2bf 100644 --- a/src/utils/manifest.test.ts +++ b/src/utils/manifest.test.ts @@ -35,14 +35,14 @@ describe('buildManifest', () => { expect(manifest.config).toMatchSnapshot(); }); - test('includeExternalUrls is unchecked by default', () => { + test('showErrors and includeExternalUrls are unchecked by default', () => { const manifest = buildManifest([], [], {}); expect(manifest.config).toMatchSnapshot(); }); - test('has checked includeExternalUrls', () => { - const manifest = buildManifest([], [], { includeExternalUrls: 'on' }); + test('has checked showErrors and includeExternalUrls', () => { + const manifest = buildManifest([], [], { showErrors: 'on', includeExternalUrls: 'on' }); expect(manifest.config).toMatchSnapshot(); }); diff --git a/src/utils/manifest.ts b/src/utils/manifest.ts index 5ae4a3b..df7f803 100644 --- a/src/utils/manifest.ts +++ b/src/utils/manifest.ts @@ -57,6 +57,13 @@ export const buildManifest = (sources: Source[], extractors: Extractor[], config }); } + manifest.config.push({ + key: 'showErrors', + type: 'checkbox', + title: 'Show errors', + ...('showErrors' in config && { default: 'checked' }), + }); + manifest.config.push({ key: 'includeExternalUrls', type: 'checkbox',