From e2172508e4b954f06f72f3fa765728b14885abbe Mon Sep 17 00:00:00 2001 From: TPN Date: Fri, 10 Jan 2025 15:27:09 +0000 Subject: [PATCH] Add mp4hydra --- src/providers/all.ts | 5 +++ src/providers/embeds/mp4hydra.ts | 41 +++++++++++++++++ src/providers/sources/mp4hydra.ts | 75 +++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/providers/embeds/mp4hydra.ts create mode 100644 src/providers/sources/mp4hydra.ts diff --git a/src/providers/all.ts b/src/providers/all.ts index e81cea5..50b5fe9 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -25,6 +25,7 @@ import { goMoviesScraper } from '@/providers/sources/gomovies/index'; import { insertunitScraper } from '@/providers/sources/insertunit'; import { kissAsianScraper } from '@/providers/sources/kissasian/index'; import { lookmovieScraper } from '@/providers/sources/lookmovie'; +import { mp4hydraScraper } from '@/providers/sources/mp4hydra'; import { nsbxScraper } from '@/providers/sources/nsbx'; import { redStarScraper } from '@/providers/sources/redstar'; import { remotestreamScraper } from '@/providers/sources/remotestream'; @@ -48,6 +49,7 @@ import { closeLoadScraper } from './embeds/closeload'; import { fileMoonScraper } from './embeds/filemoon'; import { fileMoonMp4Scraper } from './embeds/filemoon/mp4'; import { hydraxScraper } from './embeds/hydrax'; +import { mp4hydraServer1Scraper, mp4hydraServer2Scraper } from './embeds/mp4hydra'; import { alphaScraper, deltaScraper } from './embeds/nsbx'; import { playm4uNMScraper } from './embeds/playm4u/nm'; import { ridooScraper } from './embeds/ridoo'; @@ -108,6 +110,7 @@ export function gatherAllSources(): Array { bombtheirishScraper, vidsrcsuScraper, TASFScraper, + mp4hydraScraper, ]; } @@ -157,5 +160,7 @@ export function gatherAllEmbeds(): Array { astraScraper, orionScraper, streamwishScraper, + mp4hydraServer1Scraper, + mp4hydraServer2Scraper, ]; } diff --git a/src/providers/embeds/mp4hydra.ts b/src/providers/embeds/mp4hydra.ts new file mode 100644 index 0000000..39ed6d5 --- /dev/null +++ b/src/providers/embeds/mp4hydra.ts @@ -0,0 +1,41 @@ +import { flags } from '@/entrypoint/utils/targets'; +import { makeEmbed } from '@/providers/base'; + +const providers = [ + { + id: 'mp4hydra-1', + name: 'Server 1', + rank: 36, + }, + { + id: 'mp4hydra-2', + name: 'Server 2', + rank: 35, + }, +]; + +function embed(provider: { id: string; name: string; rank: number; disabled?: boolean }) { + return makeEmbed({ + id: provider.id, + name: provider.name, + disabled: provider.disabled, + rank: provider.rank, + async scrape(ctx) { + return { + stream: [ + { + id: 'primary', + type: 'file', + qualities: { + unknown: { url: ctx.url, type: 'mp4' }, + }, + flags: [flags.CORS_ALLOWED], + captions: [], + }, + ], + }; + }, + }); +} + +export const [mp4hydraServer1Scraper, mp4hydraServer2Scraper] = providers.map(embed); diff --git a/src/providers/sources/mp4hydra.ts b/src/providers/sources/mp4hydra.ts new file mode 100644 index 0000000..cf38684 --- /dev/null +++ b/src/providers/sources/mp4hydra.ts @@ -0,0 +1,75 @@ +import { load } from 'cheerio'; + +import { flags } from '@/entrypoint/utils/targets'; +import { SourcererEmbed, SourcererOutput, makeSourcerer } from '@/providers/base'; +import { compareMedia } from '@/utils/compare'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; + +const baseUrl = 'https://mp4hydra.org/'; + +async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise { + const searchPage = await ctx.proxiedFetcher('/search', { + baseUrl, + query: { + q: ctx.media.title, + }, + }); + + ctx.progress(40); + + const $search = load(searchPage); + const searchResults: { title: string; year?: number | undefined; url: string }[] = []; + + $search('.search-details').each((_, element) => { + const [, title, year] = + $search(element) + .find('a') + .first() + .text() + .trim() + .match(/^(.*?)\s*(?:\(?\s*(\d{4})(?:\s*-\s*\d{0,4})?\s*\)?)?\s*$/) || []; + const url = $search(element).find('a').attr('href')?.split('/')[4]; + + if (!title || !url) return; + + searchResults.push({ title, year: year ? parseInt(year, 10) : undefined, url }); + }); + + const s = searchResults.find((x) => x && compareMedia(ctx.media, x.title, x.year))?.url; + if (!s) throw new NotFoundError('No watchable item found'); + + ctx.progress(60); + + const data: { playlist: { src: string }[]; servers: { [key: string]: string; auto: string } } = + await ctx.proxiedFetcher('/info2?v=8', { + method: 'POST', + body: new URLSearchParams({ z: JSON.stringify([{ s, t: 'movie' }]) }), + baseUrl, + }); + if (!data.playlist[0].src || !data.servers) throw new NotFoundError('No watchable item found'); + + ctx.progress(80); + + const embeds: SourcererEmbed[] = []; + // rank the server as suggested by the api + [ + data.servers[data.servers.auto], + ...Object.values(data.servers).filter((x) => x !== data.servers[data.servers.auto] && x !== data.servers.auto), + ].forEach((server, _) => embeds.push({ embedId: `mp4hydra-${_ + 1}`, url: server + data.playlist[0].src })); + + ctx.progress(90); + + return { + embeds, + }; +} + +export const mp4hydraScraper = makeSourcerer({ + id: 'mp4hydra', + name: 'Mp4Hydra', + rank: 35, + flags: [flags.CORS_ALLOWED], + scrapeMovie: comboScraper, + scrapeShow: comboScraper, +});