diff --git a/src/controller/StreamController.ts b/src/controller/StreamController.ts index e3ff813..b509747 100644 --- a/src/controller/StreamController.ts +++ b/src/controller/StreamController.ts @@ -2,7 +2,7 @@ import { Request, Response, Router } from 'express'; import winston from 'winston'; import { Handler } from '../handler'; import { Config, Context } from '../types'; -import { envIsProd, StreamResolver } from '../utils'; +import { envIsProd, ImdbId, StreamResolver } from '../utils'; import { ContentType } from 'stremio-addon-sdk'; export class StreamController { @@ -37,7 +37,7 @@ export class StreamController { const handlers = this.handlers.filter(handler => handler.countryCodes.filter(countryCode => countryCode in ctx.config).length); - const { streams, ttl } = await this.streamResolver.resolve(ctx, handlers, type, id); + const { streams, ttl } = await this.streamResolver.resolve(ctx, handlers, type, ImdbId.fromString(id)); if (ttl && envIsProd()) { res.setHeader('Cache-Control', `max-age=${ttl / 1000}, public`); diff --git a/src/handler/CineHDPlus.test.ts b/src/handler/CineHDPlus.test.ts index ccd1596..d4e5917 100644 --- a/src/handler/CineHDPlus.test.ts +++ b/src/handler/CineHDPlus.test.ts @@ -1,7 +1,7 @@ import winston from 'winston'; import { CineHDPlus } from './CineHDPlus'; import { ExtractorRegistry } from '../extractor'; -import { Fetcher } from '../utils'; +import { Fetcher, ImdbId } from '../utils'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -12,31 +12,26 @@ const handler = new CineHDPlus(fetcher, new ExtractorRegistry(logger, fetcher)); 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 () => { - const streams = await handler.handle(ctx, 'series', 'kitsu:123'); - expect(streams).toHaveLength(0); - }); - test('handles non-existent series gracefully', async () => { - const streams = await handler.handle(ctx, 'series', 'tt12345678:1:1'); + const streams = await handler.handle(ctx, 'series', new ImdbId('tt12345678', 1, 1)); expect(streams).toHaveLength(0); }); test('handle imdb black mirror s2e3 (mx)', async () => { - const streams = (await handler.handle(ctx, 'series', 'tt2085059:2:3')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new ImdbId('tt2085059', 2, 3))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); test('handle imdb babylon 5 s2e3 (es)', async () => { - const streams = (await handler.handle(ctx, 'series', 'tt0105946:2:3')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new ImdbId('tt0105946', 2, 3))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); test('does not return mx results for es and vice-versa', async () => { - const streamsEs = (await handler.handle({ ...ctx, ...{ config: { es: 'on' } } }, 'series', 'tt2085059:2:3')).filter(stream => stream !== undefined); + const streamsEs = (await handler.handle({ ...ctx, ...{ config: { es: 'on' } } }, 'series', new ImdbId('tt2085059', 2, 3))).filter(stream => stream !== undefined); expect(streamsEs).toHaveLength(0); - const streamsMx = (await handler.handle({ ...ctx, ...{ config: { mx: 'on' } } }, 'series', 'tt0105946:2:3')).filter(stream => stream !== undefined); + const streamsMx = (await handler.handle({ ...ctx, ...{ config: { mx: 'on' } } }, 'series', new ImdbId('tt0105946', 2, 3))).filter(stream => stream !== undefined); expect(streamsMx).toHaveLength(0); }); }); diff --git a/src/handler/CineHDPlus.ts b/src/handler/CineHDPlus.ts index 504b9d7..a5ec430 100644 --- a/src/handler/CineHDPlus.ts +++ b/src/handler/CineHDPlus.ts @@ -1,7 +1,7 @@ import { ContentType } from 'stremio-addon-sdk'; import * as cheerio from 'cheerio'; import { Handler } from './types'; -import { Fetcher, ImdbId } from '../utils'; +import { Fetcher, getImdbId, Id, ImdbId } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context, CountryCode } from '../types'; @@ -22,12 +22,8 @@ export class CineHDPlus implements Handler { this.extractorRegistry = extractorRegistry; } - readonly handle = async (ctx: Context, _type: string, id: string) => { - if (!id.startsWith('tt')) { - return []; - } - - const imdbId = ImdbId.fromString(id); + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const imdbId = await getImdbId(ctx, this.fetcher, id); const seriesPageUrl = await this.fetchSeriesPageUrl(ctx, imdbId); if (!seriesPageUrl) { diff --git a/src/handler/Eurostreaming.test.ts b/src/handler/Eurostreaming.test.ts index da8a983..d42de6e 100644 --- a/src/handler/Eurostreaming.test.ts +++ b/src/handler/Eurostreaming.test.ts @@ -1,7 +1,7 @@ import winston from 'winston'; import { Eurostreaming } from './Eurostreaming'; import { ExtractorRegistry } from '../extractor'; -import { Fetcher } from '../utils'; +import { Fetcher, ImdbId } from '../utils'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -12,18 +12,13 @@ const handler = new Eurostreaming(fetcher, new ExtractorRegistry(logger, fetcher const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { it: 'on' } }; describe('Eurostreaming', () => { - test('does not handle non imdb series', async () => { - const streams = await handler.handle(ctx, 'series', 'kitsu:123'); - expect(streams).toHaveLength(0); - }); - test('handles non-existent series gracefully', async () => { - const streams = await handler.handle(ctx, 'series', 'tt12345678:1:1'); + const streams = await handler.handle(ctx, 'series', new ImdbId('tt12345678', 1, 1)); expect(streams).toHaveLength(0); }); test('handle imdb black mirror s2e4', async () => { - const streams = (await handler.handle(ctx, 'series', 'tt2085059:2:4')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new ImdbId('tt2085059', 2, 4))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); }); diff --git a/src/handler/Eurostreaming.ts b/src/handler/Eurostreaming.ts index 97ce591..0fbfcca 100644 --- a/src/handler/Eurostreaming.ts +++ b/src/handler/Eurostreaming.ts @@ -1,7 +1,7 @@ import { ContentType } from 'stremio-addon-sdk'; import * as cheerio from 'cheerio'; import { Handler } from './types'; -import { ImdbId, Fetcher } from '../utils'; +import { ImdbId, Fetcher, Id, getImdbId } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context, CountryCode } from '../types'; @@ -22,12 +22,8 @@ export class Eurostreaming implements Handler { this.extractorRegistry = extractorRegistry; } - readonly handle = async (ctx: Context, _type: string, id: string) => { - if (!id.startsWith('tt')) { - return []; - } - - const imdbId = ImdbId.fromString(id); + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const imdbId = await getImdbId(ctx, this.fetcher, id); const seriesPageUrl = await this.fetchSeriesPageUrl(ctx, imdbId); if (!seriesPageUrl) { diff --git a/src/handler/Frembed.test.ts b/src/handler/Frembed.test.ts index 47913a2..22055fa 100644 --- a/src/handler/Frembed.test.ts +++ b/src/handler/Frembed.test.ts @@ -1,7 +1,7 @@ import winston from 'winston'; import { Frembed } from './Frembed'; import { ExtractorRegistry } from '../extractor'; -import { Fetcher } from '../utils'; +import { Fetcher, ImdbId, TmdbId } from '../utils'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -12,18 +12,13 @@ const handler = new Frembed(fetcher, new ExtractorRegistry(logger, fetcher)); const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { fr: 'on' } }; describe('Frembed', () => { - test('does not handle non imdb and tmdb series', async () => { - const streams = await handler.handle(ctx, 'series', 'kitsu:123'); - expect(streams).toHaveLength(0); - }); - test('handle imdb black mirror s4e2', async () => { - const streams = (await handler.handle(ctx, 'series', 'tt2085059:4:2')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new ImdbId('tt2085059', 4, 2))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); test('handle tmdb black mirror s4e2', async () => { - const streams = (await handler.handle(ctx, 'series', '42009:4:2')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new TmdbId(42009, 4, 2))).filter(stream => stream !== undefined); const streamsWithoutTtl = streams.map((stream) => { delete stream.ttl; diff --git a/src/handler/Frembed.ts b/src/handler/Frembed.ts index 9efceac..e659b1f 100644 --- a/src/handler/Frembed.ts +++ b/src/handler/Frembed.ts @@ -1,6 +1,6 @@ import { ContentType } from 'stremio-addon-sdk'; import { Handler } from './types'; -import { Fetcher, getTmdbIdFromImdbId, ImdbId, TmdbId } from '../utils'; +import { Fetcher, getTmdbId, Id } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context, CountryCode } from '../types'; @@ -21,15 +21,8 @@ export class Frembed implements Handler { this.extractorRegistry = extractorRegistry; } - readonly handle = async (ctx: Context, _type: string, id: string) => { - let tmdbId: TmdbId; - if (id.startsWith('tt')) { - tmdbId = await getTmdbIdFromImdbId(ctx, this.fetcher, ImdbId.fromString(id)); - } else if (/^\d+:/.test(id)) { - tmdbId = TmdbId.fromString(id); - } else { - return []; - } + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const tmdbId = await getTmdbId(ctx, this.fetcher, id); const apiUrl = new URL(`https://frembed.space/api/series?id=${tmdbId.id}&sa=${tmdbId.series}&epi=${tmdbId.episode}&idType=tmdb`); diff --git a/src/handler/FrenchCloud.test.ts b/src/handler/FrenchCloud.test.ts index ba77395..ee3462f 100644 --- a/src/handler/FrenchCloud.test.ts +++ b/src/handler/FrenchCloud.test.ts @@ -1,6 +1,6 @@ import winston from 'winston'; import { FrenchCloud } from './FrenchCloud'; -import { Fetcher } from '../utils'; +import { Fetcher, ImdbId } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -12,18 +12,13 @@ const handler = new FrenchCloud(fetcher, new ExtractorRegistry(logger, fetcher)) const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { fr: 'on' } }; describe('FrenchCloud', () => { - test('does not handle non imdb movies', async () => { - const streams = await handler.handle(ctx, 'movie', 'kitsu:123'); - expect(streams).toHaveLength(0); - }); - test('handles non-existent movies gracefully', async () => { - const streams = await handler.handle(ctx, 'movie', 'tt12345678'); + const streams = await handler.handle(ctx, 'movie', new ImdbId('tt12345678', undefined, undefined)); expect(streams).toHaveLength(0); }); test('handle imdb the devil\'s bath', async () => { - const streams = (await handler.handle(ctx, 'movie', 'tt29141112')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'movie', new ImdbId('tt29141112', undefined, undefined))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); }); diff --git a/src/handler/FrenchCloud.ts b/src/handler/FrenchCloud.ts index 20a54e0..304113f 100644 --- a/src/handler/FrenchCloud.ts +++ b/src/handler/FrenchCloud.ts @@ -1,7 +1,7 @@ import { ContentType } from 'stremio-addon-sdk'; import * as cheerio from 'cheerio'; import { Handler } from './types'; -import { Fetcher, ImdbId } from '../utils'; +import { Fetcher, getImdbId, Id } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context, CountryCode } from '../types'; @@ -22,12 +22,10 @@ export class FrenchCloud implements Handler { this.extractorRegistry = extractorRegistry; } - readonly handle = async (ctx: Context, _type: string, id: string) => { - if (!id.startsWith('tt')) { - return []; - } + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const imdbId = await getImdbId(ctx, this.fetcher, id); - const pageUrl = new URL(`https://frenchcloud.cam/movie/${ImdbId.fromString(id).id}`); + const pageUrl = new URL(`https://frenchcloud.cam/movie/${imdbId.id}`); const html = await this.fetcher.text(ctx, pageUrl); const $ = cheerio.load(html); diff --git a/src/handler/KinoKiste.test.ts b/src/handler/KinoKiste.test.ts index 3ebecd0..290b012 100644 --- a/src/handler/KinoKiste.test.ts +++ b/src/handler/KinoKiste.test.ts @@ -1,7 +1,7 @@ import winston from 'winston'; import { KinoKiste } from './KinoKiste'; import { ExtractorRegistry } from '../extractor'; -import { Fetcher } from '../utils'; +import { Fetcher, ImdbId } from '../utils'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -12,18 +12,13 @@ const handler = new KinoKiste(fetcher, new ExtractorRegistry(logger, fetcher)); const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } }; describe('KinoKiste', () => { - test('does not handle non imdb series', async () => { - const streams = await handler.handle(ctx, 'series', 'kitsu:123'); - expect(streams).toHaveLength(0); - }); - test('handles non-existent series gracefully', async () => { - const streams = await handler.handle(ctx, 'series', 'tt12345678:1:1'); + const streams = await handler.handle(ctx, 'series', new ImdbId('tt12345678', 1, 1)); expect(streams).toHaveLength(0); }); test('handle imdb black mirror s2e4', async () => { - const streams = (await handler.handle(ctx, 'series', 'tt2085059:2:4')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new ImdbId('tt2085059', 2, 4))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); }); diff --git a/src/handler/KinoKiste.ts b/src/handler/KinoKiste.ts index a5e26d3..70ffa8a 100644 --- a/src/handler/KinoKiste.ts +++ b/src/handler/KinoKiste.ts @@ -1,7 +1,7 @@ import { ContentType } from 'stremio-addon-sdk'; import * as cheerio from 'cheerio'; import { Handler } from './types'; -import { ImdbId, Fetcher } from '../utils'; +import { ImdbId, Fetcher, getImdbId, Id } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context, CountryCode } from '../types'; @@ -22,12 +22,8 @@ export class KinoKiste implements Handler { this.extractorRegistry = extractorRegistry; } - readonly handle = async (ctx: Context, _type: string, id: string) => { - if (!id.startsWith('tt')) { - return []; - } - - const imdbId = ImdbId.fromString(id); + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const imdbId = await getImdbId(ctx, this.fetcher, id); const seriesPageUrl = await this.fetchSeriesPageUrl(ctx, imdbId); if (!seriesPageUrl) { diff --git a/src/handler/MeineCloud.test.ts b/src/handler/MeineCloud.test.ts index 8b86d30..6ff6dc8 100644 --- a/src/handler/MeineCloud.test.ts +++ b/src/handler/MeineCloud.test.ts @@ -1,6 +1,6 @@ import winston from 'winston'; import { MeineCloud } from './MeineCloud'; -import { Fetcher } from '../utils'; +import { Fetcher, ImdbId } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -12,18 +12,13 @@ const handler = new MeineCloud(fetcher, new ExtractorRegistry(logger, fetcher)); const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { de: 'on' } }; describe('MeineCloud', () => { - test('does not handle non imdb movies', async () => { - const streams = await handler.handle(ctx, 'movie', 'kitsu:123'); - expect(streams).toHaveLength(0); - }); - test('handles non-existent movies gracefully', async () => { - const streams = await handler.handle(ctx, 'movie', 'tt12345678'); + const streams = await handler.handle(ctx, 'movie', new ImdbId('tt12345678', undefined, undefined)); expect(streams).toHaveLength(0); }); test('handle imdb the devil\'s bath', async () => { - const streams = (await handler.handle(ctx, 'movie', 'tt29141112')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'movie', new ImdbId('tt29141112', undefined, undefined))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); }); diff --git a/src/handler/MeineCloud.ts b/src/handler/MeineCloud.ts index 0d68963..99aa2fe 100644 --- a/src/handler/MeineCloud.ts +++ b/src/handler/MeineCloud.ts @@ -1,7 +1,7 @@ import { ContentType } from 'stremio-addon-sdk'; import * as cheerio from 'cheerio'; import { Handler } from './types'; -import { Fetcher, ImdbId } from '../utils'; +import { Fetcher, getImdbId, Id } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context, CountryCode } from '../types'; @@ -22,12 +22,10 @@ export class MeineCloud implements Handler { this.extractorRegistry = extractorRegistry; } - readonly handle = async (ctx: Context, _type: string, id: string) => { - if (!id.startsWith('tt')) { - return []; - } + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const imdbId = await getImdbId(ctx, this.fetcher, id); - const pageUrl = new URL(`https://meinecloud.click/movie/${ImdbId.fromString(id).id}`); + const pageUrl = new URL(`https://meinecloud.click/movie/${imdbId.id}`); const html = await this.fetcher.text(ctx, pageUrl); const $ = cheerio.load(html); diff --git a/src/handler/MostraGuarda.test.ts b/src/handler/MostraGuarda.test.ts index 18cacae..9ed29d6 100644 --- a/src/handler/MostraGuarda.test.ts +++ b/src/handler/MostraGuarda.test.ts @@ -1,6 +1,6 @@ import winston from 'winston'; import { MostraGuarda } from './MostraGuarda'; -import { Fetcher } from '../utils'; +import { Fetcher, ImdbId } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -12,18 +12,13 @@ const handler = new MostraGuarda(fetcher, new ExtractorRegistry(logger, fetcher) const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { it: 'on' } }; describe('MostraGuarda', () => { - test('does not handle non imdb movies', async () => { - const streams = await handler.handle(ctx, 'movie', 'kitsu:123'); - expect(streams).toHaveLength(0); - }); - test('handles non-existent movies gracefully', async () => { - const streams = await handler.handle(ctx, 'movie', 'tt12345678'); + const streams = await handler.handle(ctx, 'movie', new ImdbId('tt12345678', undefined, undefined)); expect(streams).toHaveLength(0); }); test('handle imdb the devil\'s bath', async () => { - const streams = (await handler.handle(ctx, 'movie', 'tt29141112')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'movie', new ImdbId('tt29141112', undefined, undefined))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); }); diff --git a/src/handler/MostraGuarda.ts b/src/handler/MostraGuarda.ts index d930023..992cc09 100644 --- a/src/handler/MostraGuarda.ts +++ b/src/handler/MostraGuarda.ts @@ -1,7 +1,7 @@ import { ContentType } from 'stremio-addon-sdk'; import * as cheerio from 'cheerio'; import { Handler } from './types'; -import { Fetcher, ImdbId } from '../utils'; +import { Fetcher, getImdbId, Id } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context, CountryCode } from '../types'; @@ -22,12 +22,10 @@ export class MostraGuarda implements Handler { this.extractorRegistry = extractorRegistry; } - readonly handle = async (ctx: Context, _type: string, id: string) => { - if (!id.startsWith('tt')) { - return []; - } + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const imdbId = await getImdbId(ctx, this.fetcher, id); - const pageUrl = new URL(`https://mostraguarda.stream/movie/${ImdbId.fromString(id).id}`); + const pageUrl = new URL(`https://mostraguarda.stream/movie/${imdbId.id}`); const html = await this.fetcher.text(ctx, pageUrl); const $ = cheerio.load(html); diff --git a/src/handler/Soaper.test.ts b/src/handler/Soaper.test.ts index 3d56ebd..fb17011 100644 --- a/src/handler/Soaper.test.ts +++ b/src/handler/Soaper.test.ts @@ -1,6 +1,6 @@ import winston from 'winston'; import { Soaper } from './Soaper'; -import { Fetcher } from '../utils'; +import { Fetcher, ImdbId, TmdbId } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -12,33 +12,28 @@ const handler = new Soaper(fetcher, new ExtractorRegistry(logger, fetcher)); const ctx: Context = { id: 'id', ip: '127.0.0.1', config: { en: 'on' } }; describe('Soaper', () => { - test('does not handle non imdb and tmdb series', async () => { - const streams = await handler.handle(ctx, 'series', 'kitsu:123'); - expect(streams).toHaveLength(0); - }); - test('handles non-existent movies gracefully', async () => { - const streams = await handler.handle(ctx, 'movie', 'tt12345678'); + const streams = await handler.handle(ctx, 'movie', new ImdbId('tt12345678', undefined, undefined)); expect(streams).toHaveLength(0); }); test('handle non-uploaded movie gracefully', async () => { - const streams = (await handler.handle(ctx, 'series', 'tt29141112')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new ImdbId('tt29141112', undefined, undefined))).filter(stream => stream !== undefined); expect(streams).toHaveLength(0); }); test('handle non-existent episode gracefully', async () => { - const streams = (await handler.handle(ctx, 'series', 'tt2085059:99:99')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new ImdbId('tt2085059', 99, 99))).filter(stream => stream !== undefined); expect(streams).toHaveLength(0); }); test('handle imdb black mirror s4e2', async () => { - const streams = (await handler.handle(ctx, 'series', 'tt2085059:4:2')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new ImdbId('tt2085059', 4, 2))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); test('handle tmdb black mirror s4e2', async () => { - const streams = (await handler.handle(ctx, 'series', '42009:4:2')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new TmdbId(42009, 4, 2))).filter(stream => stream !== undefined); const streamsWithoutTtl = streams.map((stream) => { delete stream.ttl; @@ -49,7 +44,7 @@ describe('Soaper', () => { }); test('handle imdb full metal jacket', async () => { - const streams = (await handler.handle(ctx, 'series', 'tt0093058')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'series', new ImdbId('tt0093058', undefined, undefined))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); }); diff --git a/src/handler/Soaper.ts b/src/handler/Soaper.ts index 129a7c7..033947d 100644 --- a/src/handler/Soaper.ts +++ b/src/handler/Soaper.ts @@ -1,14 +1,7 @@ import { ContentType } from 'stremio-addon-sdk'; import * as cheerio from 'cheerio'; import { Handler } from './types'; -import { - Fetcher, - getTmdbIdFromImdbId, - getTmdbMovieDetails, - getTmdbTvDetails, - ImdbId, - TmdbId, -} from '../utils'; +import { Fetcher, getTmdbId, getTmdbMovieDetails, getTmdbTvDetails, Id, TmdbId } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context, CountryCode } from '../types'; @@ -31,15 +24,8 @@ export class Soaper implements Handler { this.extractorRegistry = extractorRegistry; } - readonly handle = async (ctx: Context, _type: string, id: string) => { - let tmdbId: TmdbId; - if (id.startsWith('tt')) { - tmdbId = await getTmdbIdFromImdbId(ctx, this.fetcher, ImdbId.fromString(id)); - } else if (/^\d+:/.test(id)) { - tmdbId = TmdbId.fromString(id); - } else { - return []; - } + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const tmdbId = await getTmdbId(ctx, this.fetcher, id); const [keyword, year, hrefPrefix] = await this.getKeywordYearAndHrefPrefix(ctx, tmdbId); diff --git a/src/handler/VerHdLink.test.ts b/src/handler/VerHdLink.test.ts index dc1a451..7629470 100644 --- a/src/handler/VerHdLink.test.ts +++ b/src/handler/VerHdLink.test.ts @@ -1,6 +1,6 @@ import winston from 'winston'; import { VerHdLink } from './VerHdLink'; -import { Fetcher } from '../utils'; +import { Fetcher, ImdbId } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context } from '../types'; jest.mock('../utils/Fetcher'); @@ -12,18 +12,13 @@ const handler = new VerHdLink(fetcher, new ExtractorRegistry(logger, fetcher)); 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 () => { - const streams = await handler.handle(ctx, 'movie', 'kitsu:123'); - expect(streams).toHaveLength(0); - }); - test('handles non-existent movies gracefully', async () => { - const streams = await handler.handle(ctx, 'movie', 'tt12345678'); + const streams = await handler.handle(ctx, 'movie', new ImdbId('tt12345678', undefined, undefined)); expect(streams).toHaveLength(0); }); test('handle titanic', async () => { - const streams = (await handler.handle(ctx, 'movie', 'tt0120338')).filter(stream => stream !== undefined); + const streams = (await handler.handle(ctx, 'movie', new ImdbId('tt0120338', undefined, undefined))).filter(stream => stream !== undefined); expect(streams).toMatchSnapshot(); }); }); diff --git a/src/handler/VerHdLink.ts b/src/handler/VerHdLink.ts index b4c886c..48d8883 100644 --- a/src/handler/VerHdLink.ts +++ b/src/handler/VerHdLink.ts @@ -1,7 +1,7 @@ import { ContentType } from 'stremio-addon-sdk'; import * as cheerio from 'cheerio'; import { Handler } from './types'; -import { Fetcher, ImdbId } from '../utils'; +import { Fetcher, getImdbId, Id } from '../utils'; import { ExtractorRegistry } from '../extractor'; import { Context, CountryCode } from '../types'; @@ -22,12 +22,10 @@ export class VerHdLink implements Handler { this.extractorRegistry = extractorRegistry; } - readonly handle = async (ctx: Context, _type: string, id: string) => { - if (!id.startsWith('tt')) { - return []; - } + readonly handle = async (ctx: Context, _type: string, id: Id) => { + const imdbId = await getImdbId(ctx, this.fetcher, id); - const pageUrl = new URL(`https://verhdlink.cam/movie/${ImdbId.fromString(id).id}`); + const pageUrl = new URL(`https://verhdlink.cam/movie/${imdbId.id}`); const html = await this.fetcher.text(ctx, pageUrl); const $ = cheerio.load(html); diff --git a/src/handler/types.ts b/src/handler/types.ts index 9208875..3b7007c 100644 --- a/src/handler/types.ts +++ b/src/handler/types.ts @@ -1,5 +1,6 @@ import { ContentType } from 'stremio-addon-sdk'; import { Context, CountryCode, UrlResult } from '../types'; +import { Id } from '../utils'; export interface Handler { readonly id: string; @@ -10,5 +11,5 @@ export interface Handler { readonly countryCodes: CountryCode[]; - readonly handle: (ctx: Context, type: ContentType, id: string) => Promise<(UrlResult | undefined)[]>; + readonly handle: (ctx: Context, type: ContentType, id: Id) => Promise<(UrlResult | undefined)[]>; } diff --git a/src/index.ts b/src/index.ts index 7cf57ec..ab2ea18 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ import { } from './handler'; import { ExtractorRegistry } from './extractor'; import { ConfigureController, ManifestController, StreamController } from './controller'; -import { envGet, envIsProd, Fetcher, getImdbIdFromTmdbId, StreamResolver, tmdbFetch } from './utils'; +import { envGet, envIsProd, Fetcher, StreamResolver, tmdbFetch, TmdbId } from './utils'; const logger = winston.createLogger({ transports: [ @@ -95,8 +95,7 @@ const cacheWarmup = async () => { } }); for (const id of movieIds) { - const imdbId = await getImdbIdFromTmdbId(ctx, fetcher, { id, series: undefined, episode: undefined }); - await streamResolver.resolve(ctx, handlers, 'movie', imdbId.id); + await streamResolver.resolve(ctx, handlers, 'movie', new TmdbId(id, undefined, undefined)); } logger.info(`warmed up cache with ${movieIds.length} movies`, ctx); @@ -110,8 +109,7 @@ const cacheWarmup = async () => { } }); for (const id of tvShowIds) { - const imdbId = await getImdbIdFromTmdbId(ctx, fetcher, { id, series: 1, episode: 1 }); - await streamResolver.resolve(ctx, handlers, 'series', `${imdbId.id}:1:1`); + await streamResolver.resolve(ctx, handlers, 'series', new TmdbId(id, 1, 1)); } logger.info(`warmed up cache with ${tvShowIds.length} tv shows`, ctx); diff --git a/src/utils/StreamResolver.test.ts b/src/utils/StreamResolver.test.ts index 6a0d81b..4d7d61c 100644 --- a/src/utils/StreamResolver.test.ts +++ b/src/utils/StreamResolver.test.ts @@ -6,6 +6,7 @@ import { Handler, MeineCloud, MostraGuarda } from '../handler'; import { Fetcher } from './Fetcher'; import { Context, CountryCode, TIMEOUT, UrlResult } from '../types'; import { BlockedError, HttpError, NotFoundError, QueueIsFullError } from '../error'; +import { ImdbId } from './id'; jest.mock('../utils/Fetcher'); const logger = winston.createLogger({ transports: [new winston.transports.Console({ level: 'nope' })] }); @@ -20,7 +21,7 @@ const mostraGuarda = new MostraGuarda(fetcher, extractorRegistry); describe('resolve', () => { test('returns info as stream if no handlers were configured', async () => { - const streams = await streamResolver.resolve(ctx, [], 'movie', 'tt123456789'); + const streams = await streamResolver.resolve(ctx, [], 'movie', new ImdbId('tt123456789', undefined, undefined)); expect(streams).toMatchSnapshot(); }); @@ -28,7 +29,7 @@ describe('resolve', () => { test('returns handler errors as stream', async () => { const fetcherSpy = jest.spyOn(fetcher, 'text').mockRejectedValue('ups, an error occurred.'); - const streams = await streamResolver.resolve(ctx, [meineCloud], 'movie', 'tt123456789'); + const streams = await streamResolver.resolve(ctx, [meineCloud], 'movie', new ImdbId('tt123456789', undefined, undefined)); expect(streams).toMatchSnapshot(); @@ -36,19 +37,19 @@ describe('resolve', () => { }); test('returns empty array if no handler found anything', async () => { - const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'movie', 'tt12345678'); + const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'movie', new ImdbId('tt12345678', undefined, undefined)); expect(streams).toMatchSnapshot(); }); test('returns empty array if no handler supported the type', async () => { - const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'series', 'tt12345678:1:1'); + const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'series', new ImdbId('tt12345678', 1, 1)); expect(streams).toMatchSnapshot(); }); test('returns sorted results', async () => { - const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'movie', 'tt29141112'); + const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'movie', new ImdbId('tt29141112', undefined, undefined)); expect(streams).toMatchSnapshot(); }); @@ -128,10 +129,10 @@ describe('resolve', () => { }; } - const streams = await streamResolver.resolve(ctx, [new MockHandler()], 'movie', 'tt11655566'); + const streams = await streamResolver.resolve(ctx, [new MockHandler()], 'movie', new ImdbId('tt11655566', undefined, undefined)); expect(streams).toMatchSnapshot(); - const streamsWithoutExternalUrls = await streamResolver.resolve({ ...ctx, config: { ...ctx.config, excludeExternalUrls: 'on' } }, [new MockHandler()], 'movie', 'tt11655566'); + const streamsWithoutExternalUrls = await streamResolver.resolve({ ...ctx, config: { ...ctx.config, excludeExternalUrls: 'on' } }, [new MockHandler()], 'movie', new ImdbId('tt11655566', undefined, undefined)); expect(streamsWithoutExternalUrls).toMatchSnapshot(); }); @@ -144,7 +145,7 @@ describe('resolve', () => { handle: jest.fn().mockRejectedValue(new NotFoundError()), }; - const streams = await streamResolver.resolve(ctx, [mockHandler], 'movie', 'tt12345678'); + const streams = await streamResolver.resolve(ctx, [mockHandler], 'movie', new ImdbId('tt12345678', undefined, undefined)); expect(streams).toMatchSnapshot(); }); diff --git a/src/utils/StreamResolver.ts b/src/utils/StreamResolver.ts index fa1e35d..dd92e66 100644 --- a/src/utils/StreamResolver.ts +++ b/src/utils/StreamResolver.ts @@ -6,6 +6,7 @@ import { Handler } from '../handler'; import { BlockedError, HttpError, NotFoundError, QueueIsFullError } from '../error'; import { flagFromCountryCode, languageFromCountryCode } from './language'; import { envGetAppName } from './env'; +import { Id } from './id'; interface ResolveResponse { streams: Stream[]; @@ -19,7 +20,7 @@ export class StreamResolver { this.logger = logger; } - readonly resolve = async (ctx: Context, handlers: Handler[], type: ContentType, id: string): Promise => { + readonly resolve = async (ctx: Context, handlers: Handler[], type: ContentType, id: Id): Promise => { if (handlers.length === 0) { return { streams: [ diff --git a/src/utils/id/index.ts b/src/utils/id/index.ts index c4e44b5..e466d1c 100644 --- a/src/utils/id/index.ts +++ b/src/utils/id/index.ts @@ -1,2 +1,26 @@ +import { ImdbId } from './ImdbId'; +import { TmdbId } from './TmdbId'; +import { getImdbIdFromTmdbId, getTmdbIdFromImdbId } from '../tmdb'; +import { Fetcher } from '../Fetcher'; +import { Context } from '../../types'; + export * from './ImdbId'; export * from './TmdbId'; + +export type Id = ImdbId | TmdbId; + +export const getImdbId = async (ctx: Context, fetcher: Fetcher, id: Id): Promise => { + if (id instanceof TmdbId) { + return await getImdbIdFromTmdbId(ctx, fetcher, id); + } + + return id; +}; + +export const getTmdbId = async (ctx: Context, fetcher: Fetcher, id: Id): Promise => { + if (id instanceof ImdbId) { + return await getTmdbIdFromImdbId(ctx, fetcher, id); + } + + return id; +};