From 5b389ada9e78d51d53b9a5cc5dcc24c27c826deb Mon Sep 17 00:00:00 2001 From: WebStreamr <210764791+webstreamr@users.noreply.github.com> Date: Sat, 17 May 2025 23:48:34 +0200 Subject: [PATCH] feat: configure languages instead of handlers just feels more natural and helps maintainence. handlers should be an internal thing. --- src/controller/StreamController.ts | 5 ++-- src/extractor/ExtractorRegistry.test.ts | 2 +- src/handler/CineHDPlus.test.ts | 2 +- src/handler/Eurostreaming.test.ts | 2 +- src/handler/Frembed.test.ts | 2 +- src/handler/FrenchCloud.test.ts | 2 +- src/handler/KinoKiste.test.ts | 2 +- src/handler/MeineCloud.test.ts | 2 +- src/handler/MostraGuarda.test.ts | 2 +- src/handler/VerHdLink.test.ts | 2 +- src/handler/VerHdLink.ts | 4 +-- src/types.ts | 1 + src/utils/Fetcher.test.ts | 2 +- src/utils/StreamResolver.test.ts | 2 +- src/utils/manifest.test.ts | 34 +++++++++++++++++++------ src/utils/manifest.ts | 20 ++++++++++----- 16 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/controller/StreamController.ts b/src/controller/StreamController.ts index 05126eb..5844254 100644 --- a/src/controller/StreamController.ts +++ b/src/controller/StreamController.ts @@ -29,13 +29,14 @@ export class StreamController { const ctx: Context = { id: res.getHeader('X-Request-ID') as string, ip: req.ip as string, + config, }; this.logger.info(`Search stream for type "${type}" and id "${id}"`, ctx); - const selectedHandlers = this.handlers.filter(handler => handler.id in config); + const handlers = this.handlers.filter(handler => handler.languages.filter(language => language in ctx.config).length); - const streams = await this.streamResolver.resolve(ctx, selectedHandlers, type, id); + const streams = await this.streamResolver.resolve(ctx, handlers, type, id); res.setHeader('Content-Type', 'application/json'); res.send(JSON.stringify({ streams })); diff --git a/src/extractor/ExtractorRegistry.test.ts b/src/extractor/ExtractorRegistry.test.ts index a0f5a4f..cc8d4c1 100644 --- a/src/extractor/ExtractorRegistry.test.ts +++ b/src/extractor/ExtractorRegistry.test.ts @@ -10,7 +10,7 @@ const fetcher = new Fetcher(); const extractorRegistry = new ExtractorRegistry(logger, fetcher); describe('ExtractorRegistry', () => { - const ctx: Context = { id: 'id', ip: '127.0.0.1' }; + const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } }; test('returns undefined when no extractor can be found', async () => { const urlResult = await extractorRegistry.handle(ctx, new URL('https://some-url.test'), 'en'); diff --git a/src/handler/CineHDPlus.test.ts b/src/handler/CineHDPlus.test.ts index 64a3d48..ff9d85b 100644 --- a/src/handler/CineHDPlus.test.ts +++ b/src/handler/CineHDPlus.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new CineHDPlus(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { id: 'id', ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { es: 'on', mx: 'on' } }; describe('CineHDPlus', () => { test('does not handle non imdb series', async () => { diff --git a/src/handler/Eurostreaming.test.ts b/src/handler/Eurostreaming.test.ts index a30433b..149197a 100644 --- a/src/handler/Eurostreaming.test.ts +++ b/src/handler/Eurostreaming.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new Eurostreaming(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { id: 'id', ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { it: 'on' } }; describe('Eurostreaming', () => { test('does not handle non imdb series', async () => { diff --git a/src/handler/Frembed.test.ts b/src/handler/Frembed.test.ts index e031b36..147dca1 100644 --- a/src/handler/Frembed.test.ts +++ b/src/handler/Frembed.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new Frembed(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { id: 'id', ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { fr: 'on' } }; describe('Frembed', () => { test('does not handle non imdb series', async () => { diff --git a/src/handler/FrenchCloud.test.ts b/src/handler/FrenchCloud.test.ts index b029071..0647840 100644 --- a/src/handler/FrenchCloud.test.ts +++ b/src/handler/FrenchCloud.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new FrenchCloud(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { id: 'id', ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { fr: 'on' } }; describe('FrenchCloud', () => { test('does not handle non imdb movies', async () => { diff --git a/src/handler/KinoKiste.test.ts b/src/handler/KinoKiste.test.ts index 754d023..138d0f2 100644 --- a/src/handler/KinoKiste.test.ts +++ b/src/handler/KinoKiste.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new KinoKiste(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { id: 'id', ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } }; describe('KinoKiste', () => { test('does not handle non imdb series', async () => { diff --git a/src/handler/MeineCloud.test.ts b/src/handler/MeineCloud.test.ts index 3fad108..ff5b96a 100644 --- a/src/handler/MeineCloud.test.ts +++ b/src/handler/MeineCloud.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new MeineCloud(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { id: 'id', ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } }; describe('MeineCloud', () => { test('does not handle non imdb movies', async () => { diff --git a/src/handler/MostraGuarda.test.ts b/src/handler/MostraGuarda.test.ts index fccfc46..fdc47f7 100644 --- a/src/handler/MostraGuarda.test.ts +++ b/src/handler/MostraGuarda.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new MostraGuarda(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { id: 'id', ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { it: 'on' } }; describe('MostraGuarda', () => { test('does not handle non imdb movies', async () => { diff --git a/src/handler/VerHdLink.test.ts b/src/handler/VerHdLink.test.ts index 31145f2..adb25dc 100644 --- a/src/handler/VerHdLink.test.ts +++ b/src/handler/VerHdLink.test.ts @@ -9,7 +9,7 @@ const logger = winston.createLogger({ transports: [new winston.transports.Consol // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); const handler = new VerHdLink(fetcher, new ExtractorRegistry(logger, fetcher)); -const ctx: Context = { id: 'id', ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { es: 'on', mx: 'on' } }; describe('VerHdLink', () => { test('does not handle non imdb movies', async () => { diff --git a/src/handler/VerHdLink.ts b/src/handler/VerHdLink.ts index 26297c4..3e27e8a 100644 --- a/src/handler/VerHdLink.ts +++ b/src/handler/VerHdLink.ts @@ -34,9 +34,9 @@ export class VerHdLink implements Handler { $('._player-mirrors') .map((_i, el) => { let countryCode = undefined; - if ($(el).hasClass('latino')) { + if ($(el).hasClass('latino') && 'mx' in ctx.config) { countryCode = 'mx'; - } else if ($(el).hasClass('castellano')) { + } else if ($(el).hasClass('castellano') && 'es' in ctx.config) { countryCode = 'es'; } else { return []; diff --git a/src/types.ts b/src/types.ts index 106f932..1df7549 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ import { Manifest, ManifestConfig } from 'stremio-addon-sdk'; export interface Context { id: string; ip: string; + config: Config; } export type ManifestWithConfig = Manifest & { config: ManifestConfig[] }; diff --git a/src/utils/Fetcher.test.ts b/src/utils/Fetcher.test.ts index 00dda1d..a67c01e 100644 --- a/src/utils/Fetcher.test.ts +++ b/src/utils/Fetcher.test.ts @@ -9,7 +9,7 @@ const axiosMock = new AxiosMockAdapter(axios); const fetcher = new Fetcher(axios, winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] })); describe('fetch', () => { - const ctx: Context = { id: 'id', ip: '127.0.0.1' }; + const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } }; test('text passes successful response through setting headers', async () => { axiosMock.onGet().reply(200, 'some text'); diff --git a/src/utils/StreamResolver.test.ts b/src/utils/StreamResolver.test.ts index 35fac12..ac9df84 100644 --- a/src/utils/StreamResolver.test.ts +++ b/src/utils/StreamResolver.test.ts @@ -8,7 +8,7 @@ jest.mock('../utils/Fetcher'); const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] }); const streamResolver = new StreamResolver(logger); -const ctx: Context = { id: 'id', ip: '127.0.0.1' }; +const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on', it: 'on' } }; // @ts-expect-error No constructor args needed const fetcher = new Fetcher(); diff --git a/src/utils/manifest.test.ts b/src/utils/manifest.test.ts index 5b69b9d..b1c6b78 100644 --- a/src/utils/manifest.test.ts +++ b/src/utils/manifest.test.ts @@ -1,6 +1,6 @@ import winston from 'winston'; import { buildManifest } from './manifest'; -import { KinoKiste } from '../handler'; +import { KinoKiste, MeineCloud, VerHdLink } from '../handler'; import { Fetcher } from './Fetcher'; import { ExtractorRegistry } from '../extractor'; @@ -16,17 +16,35 @@ describe('buildManifest', () => { }); test('has unchecked handler without a config', () => { - const manifest = buildManifest([new KinoKiste(fetcher, new ExtractorRegistry(logger, fetcher))], {}); + const extractorRegistry = new ExtractorRegistry(logger, fetcher); + const handlers = [ + new VerHdLink(fetcher, extractorRegistry), + new KinoKiste(fetcher, extractorRegistry), + new MeineCloud(fetcher, extractorRegistry), + ]; - expect(manifest.config).toHaveLength(1); - expect(manifest.config[0]?.default).toBeUndefined(); + const manifest = buildManifest(handlers, {}); + + expect(manifest.config).toStrictEqual([ + { key: 'de', type: 'checkbox', title: '🇩🇪 (KinoKiste, MeineCloud)' }, + { key: 'es', type: 'checkbox', title: '🇪🇸 (VerHdLink)' }, + { key: 'mx', type: 'checkbox', title: '🇲🇽 (VerHdLink)' }, + ]); }); test('has checked handler with appropriate config', () => { - const kinokiste = new KinoKiste(fetcher, new ExtractorRegistry(logger, fetcher)); - const manifest = buildManifest([kinokiste], { [kinokiste.id]: 'on' }); + const extractorRegistry = new ExtractorRegistry(logger, fetcher); + const handlers = [ + new VerHdLink(fetcher, extractorRegistry), + new KinoKiste(fetcher, extractorRegistry), + new MeineCloud(fetcher, extractorRegistry), + ]; + const manifest = buildManifest(handlers, { de: 'on' }); - expect(manifest.config).toHaveLength(1); - expect(manifest.config[0]?.default).toBe('checked'); + expect(manifest.config).toStrictEqual([ + { key: 'de', type: 'checkbox', title: '🇩🇪 (KinoKiste, MeineCloud)', default: 'checked' }, + { key: 'es', type: 'checkbox', title: '🇪🇸 (VerHdLink)' }, + { key: 'mx', type: 'checkbox', title: '🇲🇽 (VerHdLink)' }, + ]); }); }); diff --git a/src/utils/manifest.ts b/src/utils/manifest.ts index 7d3dde7..48ee449 100644 --- a/src/utils/manifest.ts +++ b/src/utils/manifest.ts @@ -25,14 +25,22 @@ export const buildManifest = (handlers: Handler[], config: Config): ManifestWith config: [], }; + const languageHandlers: Record = {}; handlers.forEach((handler) => { - manifest.config.push({ - key: handler.id, - type: 'checkbox', - title: `${handler.languages.map(language => flag(language)).join(' ')} | ${handler.label} (${handler.contentTypes.map(contentType => contentType.replace('movie', 'movies')).join(', ')})`, - ...(handler.id in config && { default: 'checked' }), - }); + handler.languages.forEach(language => languageHandlers[language] = [...(languageHandlers[language] ?? []), handler]); }); + const sortedLanguageHandlers = Object.entries(languageHandlers) + .sort(([languageA], [languageB]) => languageA.localeCompare(languageB)); + + for (const [language, handlers] of sortedLanguageHandlers) { + manifest.config.push({ + key: language, + type: 'checkbox', + title: `${flag(language)} (${handlers.map(handler => handler.label).join(', ')})`, + ...(language in config && { default: 'checked' }), + }); + } + return manifest; };