refactor: season instead of series ;)

This commit is contained in:
WebStreamr 2025-06-09 18:52:08 +00:00
parent b5a4de9c9e
commit 7ad61df839
No known key found for this signature in database
11 changed files with 45 additions and 45 deletions

View file

@ -42,13 +42,13 @@ export class CineHDPlus implements Handler {
const title = $('meta[property="og:title"]').attr('content') as string;
return Promise.all(
$(`[data-num="${imdbId.series}x${imdbId.episode}"]`)
$(`[data-num="${imdbId.season}x${imdbId.episode}"]`)
.siblings('.mirrors')
.children('[data-link]')
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/cinehdplus/))
.map(url => this.extractorRegistry.handle({ ...ctx, referer: seriesPageUrl }, url, { countryCode, title: `${title.trim()} ${imdbId.series}x${imdbId.episode}` })),
.map(url => this.extractorRegistry.handle({ ...ctx, referer: seriesPageUrl }, url, { countryCode, title: `${title.trim()} ${imdbId.season}x${imdbId.episode}` })),
);
};

View file

@ -34,8 +34,8 @@ export class Eurostreaming implements Handler {
const $ = cheerio.load(html);
const mainDataLinkElements = $(`[data-num="${imdbId.series}x${imdbId.episode}"][data-link!="#"]`);
const mirrorDataLinkElements = $(`[data-num="${imdbId.series}x${imdbId.episode}"]`)
const mainDataLinkElements = $(`[data-num="${imdbId.season}x${imdbId.episode}"][data-link!="#"]`);
const mirrorDataLinkElements = $(`[data-num="${imdbId.season}x${imdbId.episode}"]`)
.siblings('.mirrors')
.children('[data-link!="#"]');
@ -46,7 +46,7 @@ export class Eurostreaming implements Handler {
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/eurostreaming/))
.map(url => this.extractorRegistry.handle({ ...ctx, referer: seriesPageUrl }, url, { countryCode: 'it', title: `${title.trim()} ${imdbId.series}x${imdbId.episode}` })),
.map(url => this.extractorRegistry.handle({ ...ctx, referer: seriesPageUrl }, url, { countryCode: 'it', title: `${title.trim()} ${imdbId.season}x${imdbId.episode}` })),
);
};

View file

@ -24,7 +24,7 @@ export class Frembed implements Handler {
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`);
const apiUrl = new URL(`https://frembed.space/api/series?id=${tmdbId.id}&sa=${tmdbId.season}&epi=${tmdbId.episode}&idType=tmdb`);
const json = JSON.parse(await this.fetcher.text(ctx, apiUrl));
@ -40,7 +40,7 @@ export class Frembed implements Handler {
}
return Promise.all(
urls.map(url => this.extractorRegistry.handle({ ...ctx, referer: apiUrl }, url, { countryCode: 'fr', title: `${json['title']} ${tmdbId.series}x${tmdbId.episode}` })),
urls.map(url => this.extractorRegistry.handle({ ...ctx, referer: apiUrl }, url, { countryCode: 'fr', title: `${json['title']} ${tmdbId.season}x${tmdbId.episode}` })),
);
};
}

View file

@ -34,19 +34,19 @@ export class Soaper implements Handler {
return [];
}
if (tmdbId.series) {
if (tmdbId.season) {
const html = await this.fetcher.text(ctx, pageUrl);
const $ = cheerio.load(html);
const episodePageHref = $(`.alert:has(h4:contains("Season${tmdbId.series}")) a:contains("${tmdbId.episode}.")`).attr('href');
const episodePageHref = $(`.alert:has(h4:contains("Season${tmdbId.season}")) a:contains("${tmdbId.episode}.")`).attr('href');
if (!episodePageHref) {
return [];
}
const episodeUrl = new URL(episodePageHref, this.baseUrl);
return [await this.extractorRegistry.handle({ ...ctx, referer: episodeUrl }, episodeUrl, { countryCode: 'en', title: `${keyword} ${tmdbId.series}x${tmdbId.episode}` })];
return [await this.extractorRegistry.handle({ ...ctx, referer: episodeUrl }, episodeUrl, { countryCode: 'en', title: `${keyword} ${tmdbId.season}x${tmdbId.episode}` })];
}
return [await this.extractorRegistry.handle({ ...ctx, referer: pageUrl }, pageUrl, { countryCode: 'en', title: `${keyword} (${year})` })];
@ -64,7 +64,7 @@ export class Soaper implements Handler {
};
private readonly getKeywordYearAndHrefPrefix = async (ctx: Context, tmdbId: TmdbId): Promise<[string, number, string]> => {
if (tmdbId.series) {
if (tmdbId.season) {
const tmdbDetails = await getTmdbTvDetails(ctx, this.fetcher, tmdbId);
return [tmdbDetails.name, (new Date(tmdbDetails.first_air_date)).getFullYear(), '/tv_'];

View file

@ -37,13 +37,13 @@ export class StreamKiste implements Handler {
const title = $('meta[property="og:title"]').attr('content') as string;
return Promise.all(
$(`[data-num="${imdbId.series}x${imdbId.episode}"]`)
$(`[data-num="${imdbId.season}x${imdbId.episode}"]`)
.siblings('.mirrors')
.children('[data-link]')
.map((_i, el) => new URL(($(el).attr('data-link') as string).replace(/^(https:)?\/\//, 'https://')))
.toArray()
.filter(url => !url.host.match(/streamkiste/))
.map(url => this.extractorRegistry.handle({ ...ctx, referer: seriesPageUrl }, url, { countryCode: 'de', title: `${title.trim()} ${imdbId.series}x${imdbId.episode}` })),
.map(url => this.extractorRegistry.handle({ ...ctx, referer: seriesPageUrl }, url, { countryCode: 'de', title: `${title.trim()} ${imdbId.season}x${imdbId.episode}` })),
);
};

View file

@ -2,26 +2,26 @@ import { ImdbId } from './ImdbId';
describe('can be created from string', () => {
test('splits id properly', () => {
const { id, series, episode } = ImdbId.fromString('tt2085059:2:4');
const { id, season, episode } = ImdbId.fromString('tt2085059:2:4');
expect(id).toBe('tt2085059');
expect(series).toBe(2);
expect(season).toBe(2);
expect(episode).toBe(4);
});
test('handles weird 0 prefixes in series and episode', () => {
const { id, series, episode } = ImdbId.fromString('tt2085059:02:04');
const { id, season, episode } = ImdbId.fromString('tt2085059:02:04');
expect(id).toBe('tt2085059');
expect(series).toBe(2);
expect(season).toBe(2);
expect(episode).toBe(4);
});
test('supports movie with missing series and episode', () => {
const { id, series, episode } = ImdbId.fromString('tt2085059');
const { id, season, episode } = ImdbId.fromString('tt2085059');
expect(id).toBe('tt2085059');
expect(series).toBeUndefined();
expect(season).toBeUndefined();
expect(episode).toBeUndefined();
});

View file

@ -1,11 +1,11 @@
export class ImdbId {
public readonly id: string;
public readonly series: number | undefined;
public readonly season: number | undefined;
public readonly episode: number | undefined;
constructor(id: string, series: number | undefined, episode: number | undefined) {
constructor(id: string, season: number | undefined, episode: number | undefined) {
this.id = id;
this.series = series;
this.season = season;
this.episode = episode;
}

View file

@ -2,26 +2,26 @@ import { TmdbId } from './TmdbId';
describe('can be created from string', () => {
test('splits id properly', () => {
const { id, series, episode } = TmdbId.fromString('2085059:2:4');
const { id, season, episode } = TmdbId.fromString('2085059:2:4');
expect(id).toBe(2085059);
expect(series).toBe(2);
expect(season).toBe(2);
expect(episode).toBe(4);
});
test('handles weird 0 prefixes in series and episode', () => {
const { id, series, episode } = TmdbId.fromString('2085059:02:04');
const { id, season, episode } = TmdbId.fromString('2085059:02:04');
expect(id).toBe(2085059);
expect(series).toBe(2);
expect(season).toBe(2);
expect(episode).toBe(4);
});
test('supports movie with missing series and episode', () => {
const { id, series, episode } = TmdbId.fromString('2085059');
const { id, season, episode } = TmdbId.fromString('2085059');
expect(id).toBe(2085059);
expect(series).toBeUndefined();
expect(season).toBeUndefined();
expect(episode).toBeUndefined();
});

View file

@ -1,11 +1,11 @@
export class TmdbId {
public readonly id: number;
public readonly series: number | undefined;
public readonly season: number | undefined;
public readonly episode: number | undefined;
constructor(id: number, series: number | undefined, episode: number | undefined) {
constructor(id: number, season: number | undefined, episode: number | undefined) {
this.id = id;
this.series = series;
this.season = season;
this.episode = episode;
}

View file

@ -13,14 +13,14 @@ describe('getTmdbIdFromImdbId', () => {
const tmdbId1 = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt2085059', 2, 4));
expect(tmdbId1).toBeInstanceOf(TmdbId);
expect(tmdbId1).toHaveProperty('id', 42009);
expect(tmdbId1).toHaveProperty('series', 2);
expect(tmdbId1).toHaveProperty('season', 2);
expect(tmdbId1).toHaveProperty('episode', 4);
// from cache
const tmdbId2 = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt2085059', 2, 4));
expect(tmdbId2).toBeInstanceOf(TmdbId);
expect(tmdbId2).toHaveProperty('id', 42009);
expect(tmdbId2).toHaveProperty('series', 2);
expect(tmdbId2).toHaveProperty('season', 2);
expect(tmdbId2).toHaveProperty('episode', 4);
});
@ -28,14 +28,14 @@ describe('getTmdbIdFromImdbId', () => {
const tmdbId1 = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt29141112', undefined, undefined));
expect(tmdbId1).toBeInstanceOf(TmdbId);
expect(tmdbId1).toHaveProperty('id', 931944);
expect(tmdbId1).toHaveProperty('series', undefined);
expect(tmdbId1).toHaveProperty('season', undefined);
expect(tmdbId1).toHaveProperty('episode', undefined);
// from cache
const tmdbId2 = await getTmdbIdFromImdbId(ctx, fetcher, new ImdbId('tt29141112', undefined, undefined));
expect(tmdbId2).toBeInstanceOf(TmdbId);
expect(tmdbId2).toHaveProperty('id', 931944);
expect(tmdbId2).toHaveProperty('series', undefined);
expect(tmdbId2).toHaveProperty('season', undefined);
expect(tmdbId2).toHaveProperty('episode', undefined);
});
@ -49,14 +49,14 @@ describe('getImdbIdFromTmdbId', () => {
const imdbId1 = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(42009, 2, 4));
expect(imdbId1).toBeInstanceOf(ImdbId);
expect(imdbId1).toHaveProperty('id', 'tt2085059');
expect(imdbId1).toHaveProperty('series', 2);
expect(imdbId1).toHaveProperty('season', 2);
expect(imdbId1).toHaveProperty('episode', 4);
// from cache
const imdbId2 = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(42009, 2, 4));
expect(imdbId2).toBeInstanceOf(ImdbId);
expect(imdbId2).toHaveProperty('id', 'tt2085059');
expect(imdbId2).toHaveProperty('series', 2);
expect(imdbId2).toHaveProperty('season', 2);
expect(imdbId2).toHaveProperty('episode', 4);
});
@ -64,14 +64,14 @@ describe('getImdbIdFromTmdbId', () => {
const imdbId1 = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(931944, undefined, undefined));
expect(imdbId1).toBeInstanceOf(ImdbId);
expect(imdbId1).toHaveProperty('id', 'tt29141112');
expect(imdbId1).toHaveProperty('series', undefined);
expect(imdbId1).toHaveProperty('season', undefined);
expect(imdbId1).toHaveProperty('episode', undefined);
// from cache
const imdbId2 = await getImdbIdFromTmdbId(ctx, fetcher, new TmdbId(931944, undefined, undefined));
expect(imdbId2).toBeInstanceOf(ImdbId);
expect(imdbId2).toHaveProperty('id', 'tt29141112');
expect(imdbId2).toHaveProperty('series', undefined);
expect(imdbId2).toHaveProperty('season', undefined);
expect(imdbId2).toHaveProperty('episode', undefined);
});

View file

@ -35,33 +35,33 @@ export const tmdbFetch = async (ctx: Context, fetcher: Fetcher, path: string): P
const imdbTmdbMap = new Map<string, number>();
export const getTmdbIdFromImdbId = async (ctx: Context, fetcher: Fetcher, imdbId: ImdbId): Promise<TmdbId> => {
if (imdbTmdbMap.has(imdbId.id)) {
return new TmdbId(imdbTmdbMap.get(imdbId.id) as number, imdbId.series, imdbId.episode);
return new TmdbId(imdbTmdbMap.get(imdbId.id) as number, imdbId.season, imdbId.episode);
}
const response = await tmdbFetch(ctx, fetcher, `/find/${imdbId.id}?external_source=imdb_id`) as FindResponsePartial;
const id = (imdbId.series ? response.tv_results[0] : response.movie_results[0])?.id;
const id = (imdbId.season ? response.tv_results[0] : response.movie_results[0])?.id;
if (!id) {
throw new Error(`Could not get TMDB ID of IMDb ID "${imdbId.id}"`);
}
imdbTmdbMap.set(imdbId.id, id);
return new TmdbId(id, imdbId.series, imdbId.episode);
return new TmdbId(id, imdbId.season, imdbId.episode);
};
const tmdbImdbMap = new Map<number, string>();
export const getImdbIdFromTmdbId = async (ctx: Context, fetcher: Fetcher, tmdbId: TmdbId): Promise<ImdbId> => {
if (tmdbImdbMap.has(tmdbId.id)) {
return new ImdbId(tmdbImdbMap.get(tmdbId.id) as string, tmdbId.series, tmdbId.episode);
return new ImdbId(tmdbImdbMap.get(tmdbId.id) as string, tmdbId.season, tmdbId.episode);
}
const type = tmdbId.series ? 'tv' : 'movie';
const type = tmdbId.season ? 'tv' : 'movie';
const response = await tmdbFetch(ctx, fetcher, `/${type}/${tmdbId.id}/external_ids`) as ExternalIdsResponsePartial;
tmdbImdbMap.set(tmdbId.id, response.imdb_id);
return new ImdbId(response.imdb_id, tmdbId.series, tmdbId.episode);
return new ImdbId(response.imdb_id, tmdbId.season, tmdbId.episode);
};
export const getTmdbMovieDetails = async (ctx: Context, fetcher: Fetcher, tmdbId: TmdbId): Promise<MovieDetailsResponsePartial> => {