diff --git a/src/providers/all.ts b/src/providers/all.ts index 0f3ce9e..9a17e46 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -26,6 +26,7 @@ import { smashyStreamFScraper } from './embeds/smashystream/video1'; import { vidplayScraper } from './embeds/vidplay'; import { wootlyScraper } from './embeds/wootly'; import { goojaraScraper } from './sources/goojara'; +import { nepuScraper } from './sources/nepu'; import { ridooMoviesScraper } from './sources/ridomovies'; import { smashyStreamScraper } from './sources/smashystream'; import { vidSrcToScraper } from './sources/vidsrcto'; @@ -44,6 +45,7 @@ export function gatherAllSources(): Array { smashyStreamScraper, ridooMoviesScraper, vidSrcToScraper, + nepuScraper, goojaraScraper, ]; } diff --git a/src/providers/sources/nepu/index.ts b/src/providers/sources/nepu/index.ts new file mode 100644 index 0000000..576c789 --- /dev/null +++ b/src/providers/sources/nepu/index.ts @@ -0,0 +1,83 @@ +import { load } from 'cheerio'; + +import { SourcererOutput, makeSourcerer } from '@/providers/base'; +import { compareTitle } from '@/utils/compare'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; + +import { SearchResults } from './types'; + +const nepuBase = 'https://nepu.to'; +const nepuReferer = `${nepuBase}/`; + +const universalScraper = async (ctx: MovieScrapeContext | ShowScrapeContext) => { + const searchResultRequest = await ctx.proxiedFetcher('/ajax/posts', { + baseUrl: nepuBase, + query: { + q: ctx.media.title, + }, + }); + + // json isn't parsed by proxiedFetcher due to content-type being text/html. + const searchResult = JSON.parse(searchResultRequest) as SearchResults; + + const show = searchResult.data.find((item) => { + if (!item) return false; + if (ctx.media.type === 'movie' && item.type !== 'Movie') return false; + if (ctx.media.type === 'show' && item.type !== 'Serie') return false; + + return compareTitle(ctx.media.title, item.name); + }); + + if (!show) throw new NotFoundError('No watchable item found'); + + let videoUrl = show.url; + + if (ctx.media.type === 'show') { + videoUrl = `${show.url}/season/${ctx.media.season.number}/episode/${ctx.media.episode.number}`; + } + + const videoPage = await ctx.proxiedFetcher(videoUrl, { + baseUrl: nepuBase, + }); + const videoPage$ = load(videoPage); + const embedId = videoPage$('a[data-embed]').attr('data-embed'); + + if (!embedId) throw new NotFoundError('No embed found.'); + + const playerPage = await ctx.proxiedFetcher('/ajax/embed', { + method: 'POST', + baseUrl: nepuBase, + body: new URLSearchParams({ id: embedId }), + }); + + const streamUrl = playerPage.match(/"file":"(http[^"]+)"/); + + if (!streamUrl) throw new NotFoundError('No stream found.'); + + return { + embeds: [], + stream: [ + { + id: 'primary', + captions: [], + playlist: streamUrl[1], + type: 'hls', + flags: [], + headers: { + Origin: nepuBase, + Referer: nepuReferer, + }, + }, + ], + } as SourcererOutput; +}; + +export const nepuScraper = makeSourcerer({ + id: 'nepu', + name: 'Nepu', + rank: 111, + flags: [], + scrapeMovie: universalScraper, + scrapeShow: universalScraper, +}); diff --git a/src/providers/sources/nepu/types.ts b/src/providers/sources/nepu/types.ts new file mode 100644 index 0000000..200995a --- /dev/null +++ b/src/providers/sources/nepu/types.ts @@ -0,0 +1,8 @@ +export type SearchResults = { + data: { + id: number; + name: string; + url: string; + type: 'Movie' | 'Serie'; + }[]; +};