feat(source): add HomeCine
This commit is contained in:
parent
533470d080
commit
5759a311aa
14 changed files with 5274 additions and 1 deletions
|
|
@ -5,7 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|||
import winston from 'winston';
|
||||
import { ConfigureController, ManifestController, StreamController } from './controller';
|
||||
import { createExtractors, ExtractorRegistry } from './extractor';
|
||||
import { CineHDPlus, Cuevana, Eurostreaming, Frembed, FrenchCloud, KinoGer, MegaKino, MeineCloud, MostraGuarda, Movix, PrimeWire, Soaper, Source, StreamKiste, VerHdLink, VidSrc, VixSrc } from './source';
|
||||
import { CineHDPlus, Cuevana, Eurostreaming, Frembed, FrenchCloud, HomeCine, KinoGer, MegaKino, MeineCloud, MostraGuarda, Movix, PrimeWire, Soaper, Source, StreamKiste, VerHdLink, VidSrc, VixSrc } from './source';
|
||||
import { contextFromRequest, envGet, envIsProd, Fetcher, StreamResolver } from './utils';
|
||||
|
||||
const logger = winston.createLogger({
|
||||
|
|
@ -39,6 +39,7 @@ const sources: Source[] = [
|
|||
// ES / MX
|
||||
new CineHDPlus(fetcher),
|
||||
new Cuevana(fetcher),
|
||||
new HomeCine(fetcher),
|
||||
new VerHdLink(fetcher),
|
||||
// DE
|
||||
new KinoGer(fetcher),
|
||||
|
|
|
|||
43
src/source/HomeCine.test.ts
Normal file
43
src/source/HomeCine.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { createTestContext } from '../test';
|
||||
import { FetcherMock, TmdbId } from '../utils';
|
||||
import { HomeCine } from './HomeCine';
|
||||
|
||||
const ctx = createTestContext({ es: 'on', mx: 'on' });
|
||||
|
||||
describe('HomeCine', () => {
|
||||
let handler: HomeCine;
|
||||
|
||||
beforeEach(() => {
|
||||
handler = new HomeCine(new FetcherMock(`${__dirname}/__fixtures__/HomeCine`));
|
||||
});
|
||||
|
||||
test('handles non-uploaded movie gracefully', async () => {
|
||||
const streams = await handler.handle(ctx, 'series', new TmdbId(3176, undefined, undefined));
|
||||
expect(streams).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('handles non-existent episodes gracefully', async () => {
|
||||
const streams = await handler.handle(ctx, 'series', new TmdbId(1402, 1, 99));
|
||||
expect(streams).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('handle walking dead s1e1', async () => {
|
||||
const streams = await handler.handle(ctx, 'series', new TmdbId(1402, 1, 1));
|
||||
expect(streams).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('handle el camino', async () => {
|
||||
const streams = await handler.handle(ctx, 'movie', new TmdbId(559969, undefined, undefined));
|
||||
expect(streams).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('does not return mx content for es', async () => {
|
||||
const streams = await handler.handle(createTestContext({ es: 'on' }), 'movie', new TmdbId(559969, undefined, undefined));
|
||||
expect(streams).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('does not return es content for mx', async () => {
|
||||
const streams = await handler.handle(createTestContext({ mx: 'on' }), 'movie', new TmdbId(559969, undefined, undefined));
|
||||
expect(streams).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
91
src/source/HomeCine.ts
Normal file
91
src/source/HomeCine.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import * as cheerio from 'cheerio';
|
||||
import { ContentType } from 'stremio-addon-sdk';
|
||||
import { Context, CountryCode } from '../types';
|
||||
import { Fetcher, getTmdbId, getTmdbNameAndYear, Id, TmdbId } from '../utils';
|
||||
import { Source, SourceResult } from './types';
|
||||
|
||||
export class HomeCine implements Source {
|
||||
public readonly id = 'homecine';
|
||||
|
||||
public readonly label = 'HomeCine';
|
||||
|
||||
public readonly contentTypes: ContentType[] = ['movie', 'series'];
|
||||
|
||||
public readonly countryCodes: CountryCode[] = [CountryCode.es, CountryCode.mx];
|
||||
|
||||
private readonly baseUrl = 'https://www3.homecine.to';
|
||||
|
||||
private readonly fetcher: Fetcher;
|
||||
|
||||
public constructor(fetcher: Fetcher) {
|
||||
this.fetcher = fetcher;
|
||||
}
|
||||
|
||||
public async handle(ctx: Context, _type: string, id: Id): Promise<SourceResult[]> {
|
||||
const tmdbId = await getTmdbId(ctx, this.fetcher, id);
|
||||
|
||||
const [name, year] = await getTmdbNameAndYear(ctx, this.fetcher, tmdbId, 'es');
|
||||
|
||||
const pageUrl = await this.fetchPageUrl(ctx, name, year, tmdbId);
|
||||
if (!pageUrl) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const pageHtml = await this.fetcher.text(ctx, pageUrl);
|
||||
|
||||
let linksHtml = pageHtml;
|
||||
if (tmdbId.season) {
|
||||
const episodeUrl = await this.fetchEpisodeUrl(pageHtml, tmdbId);
|
||||
if (!episodeUrl) {
|
||||
return [];
|
||||
}
|
||||
|
||||
linksHtml = await this.fetcher.text(ctx, episodeUrl);
|
||||
}
|
||||
|
||||
const title = tmdbId.season ? `${name} ${tmdbId.season}x${tmdbId.episode}` : `${name} (${year})`;
|
||||
|
||||
const $ = cheerio.load(linksHtml);
|
||||
|
||||
return $('.les-content a')
|
||||
.map((_i, el) => {
|
||||
let countryCode: CountryCode;
|
||||
if ($(el).text().toLowerCase().includes('latino') && CountryCode.mx in ctx.config) {
|
||||
countryCode = CountryCode.mx;
|
||||
} else if ($(el).text().toLowerCase().includes('castellano') && CountryCode.es in ctx.config) {
|
||||
countryCode = CountryCode.es;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
return { countryCode, title, url: new URL($('iframe', $(el).attr('href')).attr('src') as string) };
|
||||
}).toArray();
|
||||
};
|
||||
|
||||
private readonly fetchPageUrl = async (ctx: Context, keyword: string, year: number, tmdbId: TmdbId): Promise<URL | undefined> => {
|
||||
const searchUrl = new URL(`/?s=${encodeURIComponent(keyword)}`, this.baseUrl);
|
||||
|
||||
const html = await this.fetcher.text(ctx, searchUrl);
|
||||
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const urls = $(`a[oldtitle="${keyword} (${year})"], a[oldtitle="${keyword}"]`)
|
||||
.filter((_i, el) => $(el).siblings('#hidden_tip').find(`a[href$="release-year/${year}"]`).length !== 0)
|
||||
.map((_i, el) => new URL($(el).attr('href') as string))
|
||||
.toArray()
|
||||
.filter(url => tmdbId.season ? url.href.includes('/series/') : !url.href.includes('/series/'));
|
||||
|
||||
return urls[0];
|
||||
};
|
||||
|
||||
private readonly fetchEpisodeUrl = async (pageHtml: string, tmdbId: TmdbId): Promise<URL | undefined> => {
|
||||
const $ = cheerio.load(pageHtml);
|
||||
|
||||
const urls = $('#seasons a')
|
||||
.map((_i, el) => new URL($(el).attr('href') as string))
|
||||
.toArray()
|
||||
.filter(url => url.href.endsWith(`-temporada-${tmdbId.season}-capitulo-${tmdbId.episode}`));
|
||||
|
||||
return urls[0];
|
||||
};
|
||||
}
|
||||
1
src/source/__fixtures__/HomeCine/https:api.themoviedb.org3movie3176languagees
generated
Normal file
1
src/source/__fixtures__/HomeCine/https:api.themoviedb.org3movie3176languagees
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"adult":false,"backdrop_path":"/amBvmIshdsSkOtvVIgxl7YSQ9Dg.jpg","belongs_to_collection":{"id":16302,"name":"Battle Royale - Colección","poster_path":"/e9x8MueDBGtEAALdZZIZQ96i8C6.jpg","backdrop_path":"/gKpdj5aKtxrAoAbJJ3EtBZHcsyP.jpg"},"budget":4500000,"genres":[{"id":18,"name":"Drama"},{"id":53,"name":"Suspense"},{"id":28,"name":"Acción"}],"homepage":"","id":3176,"imdb_id":"tt0266308","origin_country":["JP"],"original_language":"ja","original_title":"バトル・ロワイアル","overview":"En el amanecer de un nuevo milenio, el país está al borde del colapso. Millones de personas vagan sin empleo. La violencia en la escuela está descontrolada y adolescentes rebeldes protagonizan boicots masivos. El gobierno contrataca con “Battle Royale”. Cada año, una clase es escogida al azar para que se enfrente, en una isla abandonada, a un cruel juego de supervivencia.","popularity":4.1635,"poster_path":"/e9UQGoX2ZFNdrjLwEejMSMDSxmR.jpg","production_companies":[{"id":5822,"logo_path":"/qyTbRgCyU9NLKvKaiQVbadtr7RY.png","name":"Toei Company","origin_country":"JP"},{"id":157807,"logo_path":"/tyrJOOVAUIxQEJzjP1PhP2H5zFi.png","name":"WOWOW","origin_country":"JP"},{"id":11538,"logo_path":null,"name":"AM Associates","origin_country":"JP"},{"id":11539,"logo_path":null,"name":"Kobi","origin_country":"JP"},{"id":3032,"logo_path":"/gaf3ez34cuvG9snphWGreB1WerA.png","name":"Nippan Group Holdings","origin_country":"JP"},{"id":11540,"logo_path":null,"name":"MF Pictures","origin_country":"JP"},{"id":3656,"logo_path":"/mY6L0bHRp8fNWfICPqLjxp3khvq.png","name":"GAGA Communications","origin_country":"JP"}],"production_countries":[{"iso_3166_1":"JP","name":"Japan"}],"release_date":"2000-12-16","revenue":30600000,"runtime":114,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"},{"english_name":"Japanese","iso_639_1":"ja","name":"日本語"}],"status":"Released","tagline":"¿Puedes matar a tu mejor amigo?","title":"Battle Royale","video":false,"vote_average":7.284,"vote_count":3539}
|
||||
1
src/source/__fixtures__/HomeCine/https:api.themoviedb.org3movie559969languagees
generated
Normal file
1
src/source/__fixtures__/HomeCine/https:api.themoviedb.org3movie559969languagees
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"adult":false,"backdrop_path":"/uLXK1LQM28XovWHPao3ViTeggXA.jpg","belongs_to_collection":null,"budget":0,"genres":[{"id":80,"name":"Crimen"},{"id":18,"name":"Drama"},{"id":53,"name":"Suspense"}],"homepage":"https://www.netflix.com/es/title/81078819","id":559969,"imdb_id":"tt9243946","origin_country":["US"],"original_language":"en","original_title":"El Camino: A Breaking Bad Movie","overview":"Tiempo después de los eventos sucedidos tras el último episodio de la serie \"Breaking Bad\", el fugitivo Jesse Pinkman (Aaron Paul) huye de sus perseguidores, de la ley y de su pasado.","popularity":6.0483,"poster_path":"/hoWADuvXs3Ua4AXBAiZYnppTupO.jpg","production_companies":[{"id":11073,"logo_path":"/aCbASRcI1MI7DXjPbSW9Fcv9uGR.png","name":"Sony Pictures Television","origin_country":"US"},{"id":2605,"logo_path":null,"name":"Gran Via Productions","origin_country":"US"},{"id":33742,"logo_path":"/2jdh2sEa0R6y6uT0F7g0IgA2WO8.png","name":"High Bridge Productions","origin_country":"US"}],"production_countries":[{"iso_3166_1":"US","name":"United States of America"}],"release_date":"2019-10-11","revenue":0,"runtime":123,"spoken_languages":[{"english_name":"English","iso_639_1":"en","name":"English"}],"status":"Released","tagline":"","title":"El Camino: Una película de Breaking Bad","video":false,"vote_average":6.965,"vote_count":5113}
|
||||
1
src/source/__fixtures__/HomeCine/https:api.themoviedb.org3tv1402languagees
generated
Normal file
1
src/source/__fixtures__/HomeCine/https:api.themoviedb.org3tv1402languagees
generated
Normal file
File diff suppressed because one or more lines are too long
938
src/source/__fixtures__/HomeCine/https:www3.homecine.toel-camino-una-pelicula-de-breaking-bad
generated
Normal file
938
src/source/__fixtures__/HomeCine/https:www3.homecine.toel-camino-una-pelicula-de-breaking-bad
generated
Normal file
File diff suppressed because one or more lines are too long
932
src/source/__fixtures__/HomeCine/https:www3.homecine.toepisodethe-walking-dead-temporada-1-capitulo-1
generated
Normal file
932
src/source/__fixtures__/HomeCine/https:www3.homecine.toepisodethe-walking-dead-temporada-1-capitulo-1
generated
Normal file
File diff suppressed because one or more lines are too long
369
src/source/__fixtures__/HomeCine/https:www3.homecine.tosBattlepercent20Royale
generated
Normal file
369
src/source/__fixtures__/HomeCine/https:www3.homecine.tosBattlepercent20Royale
generated
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
757
src/source/__fixtures__/HomeCine/https:www3.homecine.tosThepercent20Walkingpercent20Dead
generated
Normal file
757
src/source/__fixtures__/HomeCine/https:www3.homecine.tosThepercent20Walkingpercent20Dead
generated
Normal file
File diff suppressed because one or more lines are too long
1639
src/source/__fixtures__/HomeCine/https:www3.homecine.toseriesserie-the-walking-dead-cualquier-idioma
generated
Normal file
1639
src/source/__fixtures__/HomeCine/https:www3.homecine.toseriesserie-the-walking-dead-cualquier-idioma
generated
Normal file
File diff suppressed because one or more lines are too long
51
src/source/__snapshots__/HomeCine.test.ts.snap
Normal file
51
src/source/__snapshots__/HomeCine.test.ts.snap
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
|
||||
|
||||
exports[`HomeCine does not return es content for mx 1`] = `
|
||||
[
|
||||
{
|
||||
"countryCode": "mx",
|
||||
"title": "El Camino: Una película de Breaking Bad (2019)",
|
||||
"url": "https://fastream.to/embed-kc5dvcy34x8p.html",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`HomeCine does not return mx content for es 1`] = `
|
||||
[
|
||||
{
|
||||
"countryCode": "es",
|
||||
"title": "El Camino: Una película de Breaking Bad (2019)",
|
||||
"url": "https://fastream.to/embed-c9tmfzsbfj0z.html",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`HomeCine handle el camino 1`] = `
|
||||
[
|
||||
{
|
||||
"countryCode": "mx",
|
||||
"title": "El Camino: Una película de Breaking Bad (2019)",
|
||||
"url": "https://fastream.to/embed-kc5dvcy34x8p.html",
|
||||
},
|
||||
{
|
||||
"countryCode": "es",
|
||||
"title": "El Camino: Una película de Breaking Bad (2019)",
|
||||
"url": "https://fastream.to/embed-c9tmfzsbfj0z.html",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`HomeCine handle walking dead s1e1 1`] = `
|
||||
[
|
||||
{
|
||||
"countryCode": "mx",
|
||||
"title": "The Walking Dead 1x1",
|
||||
"url": "https://fastream.to/embed-nkztf1memuca.html",
|
||||
},
|
||||
{
|
||||
"countryCode": "es",
|
||||
"title": "The Walking Dead 1x1",
|
||||
"url": "https://fastream.to/embed-5bv96pdifz0b.html",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
|
@ -3,6 +3,7 @@ export * from './Cuevana';
|
|||
export * from './Eurostreaming';
|
||||
export * from './Frembed';
|
||||
export * from './FrenchCloud';
|
||||
export * from './HomeCine';
|
||||
export * from './KinoGer';
|
||||
export * from './MegaKino';
|
||||
export * from './MeineCloud';
|
||||
|
|
|
|||
Loading…
Reference in a new issue