diff --git a/src/providers/all.ts b/src/providers/all.ts index de22b4c..13e9039 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -17,10 +17,13 @@ import { showboxScraper } from '@/providers/sources/showbox/index'; import { vidsrcScraper } from '@/providers/sources/vidsrc/index'; import { zoechipScraper } from '@/providers/sources/zoechip'; +import { closeLoadScraper } from './embeds/closeload'; import { fileMoonScraper } from './embeds/filemoon'; +import { ridooScraper } from './embeds/ridoo'; import { smashyStreamDScraper } from './embeds/smashystream/dued'; import { smashyStreamFScraper } from './embeds/smashystream/video1'; import { vidplayScraper } from './embeds/vidplay'; +import { ridooMoviesScraper } from './sources/ridomovies'; import { smashyStreamScraper } from './sources/smashystream'; import { vidSrcToScraper } from './sources/vidsrcto'; @@ -36,6 +39,7 @@ export function gatherAllSources(): Array { vidsrcScraper, lookmovieScraper, smashyStreamScraper, + ridooMoviesScraper, vidSrcToScraper, ]; } @@ -54,6 +58,8 @@ export function gatherAllEmbeds(): Array { streambucketScraper, smashyStreamFScraper, smashyStreamDScraper, + ridooScraper, + closeLoadScraper, fileMoonScraper, vidplayScraper, ]; diff --git a/src/providers/embeds/closeload.ts b/src/providers/embeds/closeload.ts new file mode 100644 index 0000000..0235a49 --- /dev/null +++ b/src/providers/embeds/closeload.ts @@ -0,0 +1,71 @@ +import { load } from 'cheerio'; +import { unpack } from 'unpacker'; + +import { flags } from '@/entrypoint/utils/targets'; +import { NotFoundError } from '@/utils/errors'; + +import { makeEmbed } from '../base'; +import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '../captions'; + +const referer = 'https://ridomovies.tv/'; + +export const closeLoadScraper = makeEmbed({ + id: 'closeload', + name: 'CloseLoad', + rank: 106, + async scrape(ctx) { + const baseUrl = new URL(ctx.url).origin; + + const iframeRes = await ctx.proxiedFetcher(ctx.url, { + headers: { referer }, + }); + const iframeRes$ = load(iframeRes); + const captions: Caption[] = iframeRes$('track') + .map((_, el) => { + const track = iframeRes$(el); + const url = `${baseUrl}${track.attr('src')}`; + const label = track.attr('label') ?? ''; + const language = labelToLanguageCode(label); + const captionType = getCaptionTypeFromUrl(url); + + if (!language || !captionType) return null; + return { + id: url, + language, + hasCorsRestrictions: true, + type: captionType, + url, + }; + }) + .get() + .filter((x) => x !== null); + + const evalCode = iframeRes$('script') + .filter((_, el) => { + const script = iframeRes$(el); + return (script.attr('type') === 'text/javascript' && script.html()?.includes('eval')) ?? false; + }) + .html(); + if (!evalCode) throw new Error("Couldn't find eval code"); + const decoded = unpack(evalCode); + const regexPattern = /var\s+(\w+)\s*=\s*"([^"]+)";/g; + const base64EncodedUrl = regexPattern.exec(decoded)?.[2]; + if (!base64EncodedUrl) throw new NotFoundError('Unable to find source url'); + const url = atob(base64EncodedUrl); + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: url, + captions, + flags: [flags.IP_LOCKED], + headers: { + Referer: 'https://closeload.top/', + Origin: 'https://closeload.top', + }, + }, + ], + }; + }, +}); diff --git a/src/providers/embeds/ridoo.ts b/src/providers/embeds/ridoo.ts new file mode 100644 index 0000000..d7f5df5 --- /dev/null +++ b/src/providers/embeds/ridoo.ts @@ -0,0 +1,34 @@ +import { flags } from '@/entrypoint/utils/targets'; +import { NotFoundError } from '@/utils/errors'; + +import { makeEmbed } from '../base'; + +const referer = 'https://ridomovies.tv/'; + +export const ridooScraper = makeEmbed({ + id: 'ridoo', + name: 'Ridoo', + rank: 105, + async scrape(ctx) { + const res = await ctx.proxiedFetcher(ctx.url, { + headers: { + referer, + }, + }); + const regexPattern = /file:"([^"]+)"/g; + const url = regexPattern.exec(res)?.[1]; + if (!url) throw new NotFoundError('Unable to find source url'); + + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: url, + captions: [], + flags: [flags.CORS_ALLOWED], + }, + ], + }; + }, +}); diff --git a/src/providers/sources/ridomovies/index.ts b/src/providers/sources/ridomovies/index.ts new file mode 100644 index 0000000..f028875 --- /dev/null +++ b/src/providers/sources/ridomovies/index.ts @@ -0,0 +1,75 @@ +import { load } from 'cheerio'; + +import { flags } from '@/entrypoint/utils/targets'; +import { SourcererEmbed, makeSourcerer } from '@/providers/base'; +import { closeLoadScraper } from '@/providers/embeds/closeload'; +import { ridooScraper } from '@/providers/embeds/ridoo'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; + +import { IframeSourceResult, SearchResult } from './types'; + +const ridoMoviesBase = `https://ridomovies.tv`; +const ridoMoviesApiBase = `${ridoMoviesBase}/core/api`; + +const universalScraper = async (ctx: MovieScrapeContext | ShowScrapeContext) => { + const searchResult = await ctx.proxiedFetcher('/search', { + baseUrl: ridoMoviesApiBase, + query: { + q: ctx.media.title, + }, + }); + const show = searchResult.data.items[0]; + if (!show) throw new NotFoundError('No watchable item found'); + + let iframeSourceUrl = `/${show.fullSlug}/videos`; + + if (ctx.media.type === 'show') { + const showPageResult = await ctx.proxiedFetcher(`/${show.fullSlug}`, { + baseUrl: ridoMoviesBase, + }); + const fullEpisodeSlug = `${show.fullSlug}/season-${ctx.media.season.number}/episode-${ctx.media.episode.number}`; + const regexPattern = new RegExp( + `\\\\"id\\\\":\\\\"(\\d+)\\\\"(?=.*?\\\\\\"fullSlug\\\\\\":\\\\\\"${fullEpisodeSlug}\\\\\\")`, + 'g', + ); + const matches = [...showPageResult.matchAll(regexPattern)]; + const episodeIds = matches.map((match) => match[1]); + if (episodeIds.length === 0) throw new NotFoundError('No watchable item found'); + const episodeId = episodeIds.at(-1); + iframeSourceUrl = `/episodes/${episodeId}/videos`; + } + + const iframeSource = await ctx.proxiedFetcher(iframeSourceUrl, { + baseUrl: ridoMoviesApiBase, + }); + const iframeSource$ = load(iframeSource.data[0].url); + const iframeUrl = iframeSource$('iframe').attr('data-src'); + if (!iframeUrl) throw new NotFoundError('No watchable item found'); + + const embeds: SourcererEmbed[] = []; + if (iframeUrl.includes('closeload')) { + embeds.push({ + embedId: closeLoadScraper.id, + url: iframeUrl, + }); + } + if (iframeUrl.includes('ridoo')) { + embeds.push({ + embedId: ridooScraper.id, + url: iframeUrl, + }); + } + return { + embeds, + }; +}; + +export const ridooMoviesScraper = makeSourcerer({ + id: 'ridomovies', + name: 'RidoMovies', + rank: 105, + flags: [flags.CORS_ALLOWED], + scrapeMovie: universalScraper, + scrapeShow: universalScraper, +}); diff --git a/src/providers/sources/ridomovies/types.ts b/src/providers/sources/ridomovies/types.ts new file mode 100644 index 0000000..a030738 --- /dev/null +++ b/src/providers/sources/ridomovies/types.ts @@ -0,0 +1,78 @@ +export interface Content { + id: string; + type: string; + slug: string; + title: string; + metaTitle: any; + metaDescription: any; + usersOnly: boolean; + userLevel: number; + vipOnly: boolean; + copyrighted: boolean; + status: string; + publishedAt: string; + createdAt: string; + updatedAt: string; + fullSlug: string; +} + +export interface Contentable { + id: string; + contentId: string; + revisionId: any; + originalTitle: string; + overview: string; + releaseDate: string; + releaseYear: string; + videoNote: any; + posterNote: any; + userRating: number; + imdbRating: number; + imdbVotes: number; + imdbId: string; + duration: number; + countryCode: string; + posterPath: string; + backdropPath: string; + apiPosterPath: string; + apiBackdropPath: string; + trailerUrl: string; + mpaaRating: string; + tmdbId: number; + manual: number; + directorId: number; + createdAt: string; + updatedAt: string; + content: Content; +} + +export interface SearchResultItem { + id: string; + type: string; + slug: string; + title: string; + metaTitle: any; + metaDescription: any; + usersOnly: boolean; + userLevel: number; + vipOnly: boolean; + copyrighted: boolean; + status: string; + publishedAt: string; + createdAt: string; + updatedAt: string; + fullSlug: string; + contentable: Contentable; +} + +export type SearchResult = { + data: { + items: SearchResultItem[]; + }; +}; + +export type IframeSourceResult = { + data: { + url: string; + }[]; +};