diff --git a/src/.env copy b/src/.env copy new file mode 100644 index 0000000..daeee0b --- /dev/null +++ b/src/.env copy @@ -0,0 +1 @@ +MOVIE_WEB_TMDB_API_KEY="045e3dc79db2f0cbaa9ef67397afca39" \ No newline at end of file diff --git a/src/providers/all.ts b/src/providers/all.ts index de22b4c..b99777d 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -16,11 +16,14 @@ import { remotestreamScraper } from '@/providers/sources/remotestream'; import { showboxScraper } from '@/providers/sources/showbox/index'; import { vidsrcScraper } from '@/providers/sources/vidsrc/index'; import { zoechipScraper } from '@/providers/sources/zoechip'; +import { doodScraper } from '@/providers/embeds/dood'; import { fileMoonScraper } from './embeds/filemoon'; import { smashyStreamDScraper } from './embeds/smashystream/dued'; import { smashyStreamFScraper } from './embeds/smashystream/video1'; import { vidplayScraper } from './embeds/vidplay'; +import { wootlyScraper } from './embeds/wootly'; +import { goojaraScraper } from './sources/goojara'; import { smashyStreamScraper } from './sources/smashystream'; import { vidSrcToScraper } from './sources/vidsrcto'; @@ -37,6 +40,7 @@ export function gatherAllSources(): Array { lookmovieScraper, smashyStreamScraper, vidSrcToScraper, + goojaraScraper, ]; } @@ -56,5 +60,7 @@ export function gatherAllEmbeds(): Array { smashyStreamDScraper, fileMoonScraper, vidplayScraper, + wootlyScraper, + doodScraper ]; } diff --git a/src/providers/embeds/dood.ts b/src/providers/embeds/dood.ts new file mode 100644 index 0000000..82ab60e --- /dev/null +++ b/src/providers/embeds/dood.ts @@ -0,0 +1,75 @@ +import { load } from 'cheerio'; + +import { flags } from '@/entrypoint/utils/targets'; +import { makeEmbed } from '@/providers/base'; + +export const doodScraper = makeEmbed({ + id: 'dood', + name: 'dood', + rank: 173, + async scrape(ctx) { + function makeTheFunny() { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + + for (let i = 0; i < 10; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + + return result; + } + + const baseUrl = 'https://do0od.com'; + + const id = ctx.url.split('/d/')[1] || ctx.url.split('/e/')[1]; + console.log(id); + + const doodData = await ctx.proxiedFetcher(`/e/${id}`, { + method: 'GET', + baseUrl, + }); + + // console.log(doodData); + + const dataForLater = doodData.split(`a+"?token=`)[1].split(`"`)[0]; + const path = doodData.split(`$.get('/pass_md5`)[1].split(`'`)[0]; + + const doodPage = await ctx.proxiedFetcher(`/pass_md5/${path}`, { + headers: { + referer: `${baseUrl}/e/${id}`, + }, + method: 'GET', + baseUrl, + }); + + console.log(`${baseUrl}/pass_md5/${path}`); + + console.log(doodPage); + + // const doodPage = await ctx.proxiedFetcher(`/download/${path}`, { method: 'GET', baseUrl }); + // console.log(doodPage); + const downloadURL = `${doodPage}${makeTheFunny()}?token=${dataForLater}${Date.now()}`; + + if (downloadURL) { + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: downloadURL, + flags: [flags.CORS_ALLOWED], + captions: [], + preferredHeaders: { + referer: 'https://do0od.com/', + 'content-type': 'video/mp4', + range: 'bytes=0-', + }, + }, + ], + }; + } + + throw new Error('wootly source not found'); + }, +}); diff --git a/src/providers/embeds/wootly.ts b/src/providers/embeds/wootly.ts new file mode 100644 index 0000000..8b5199d --- /dev/null +++ b/src/providers/embeds/wootly.ts @@ -0,0 +1,77 @@ +import { load } from 'cheerio'; + +import { flags } from '@/entrypoint/utils/targets'; +import { makeEmbed } from '@/providers/base'; + +export const wootlyScraper = makeEmbed({ + id: 'wootly', + name: 'wootly', + rank: 172, + async scrape(ctx) { + const baseUrl = 'https://www.wootly.ch'; + + const wootlyData = await ctx.proxiedFetcher.full(ctx.url, { + method: 'GET', + readHeaders: ['Set-Cookie'], + }); + + const wootssesCookie = wootlyData.headers.get('Set-Cookie')?.split(';')[0].split('wootsses=')[1]; + + let $ = load(wootlyData.body); // load the html data + const iframeSrc = $('iframe').attr('src') ?? ''; + + const woozCookieRequest = await ctx.proxiedFetcher.full(iframeSrc, { + method: 'GET', + readHeaders: ['Set-Cookie'], + headers: { + cookie: `wootsses=${wootssesCookie};`, + }, + }); + + const woozCookie = woozCookieRequest.headers.get('Set-Cookie')?.split(';')[0].split('wooz=')[1]; + + const iframeData = await ctx.proxiedFetcher(iframeSrc, { + method: 'POST', + body: new URLSearchParams({ qdf: '1' }), + headers: { + cookie: `wooz=${woozCookie}`, + Referer: iframeSrc, + }, + }); + + $ = load(iframeData); + + const scriptText = $('script').html() ?? ''; + + // Regular expressions to match the variables + + const tk = scriptText.split('tk=')[1].split(';')[0].replaceAll('"', '').replaceAll(' ', ''); + const vd = scriptText.split('vd=')[1].split(',')[0].replaceAll('"', '').replaceAll(' ', ''); + const cv = scriptText.split('cv=')[1].split(',')[0].replaceAll('"', '').replaceAll(' ', ''); + + const url = await ctx.proxiedFetcher(`/grabd`, { + baseUrl, + query: { t: tk, id: vd }, + method: 'GET', + headers: { + cookie: `wooz=${woozCookie}; wootsses=${wootssesCookie};`, + }, + }); + + if (url) { + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: url, + flags: [flags.CORS_ALLOWED, flags.IP_LOCKED], + captions: [], + }, + ], + }; + } + + throw new Error('wootly source not found'); + }, +}); diff --git a/src/providers/sources/goojara/getEmbeds.ts b/src/providers/sources/goojara/getEmbeds.ts new file mode 100644 index 0000000..d0a96a4 --- /dev/null +++ b/src/providers/sources/goojara/getEmbeds.ts @@ -0,0 +1,52 @@ +import { load } from 'cheerio'; + +import { ScrapeContext } from '@/utils/context'; + +import { EmbedsResult } from './type'; + +export async function getEmbeds(ctx: ScrapeContext, id: string): Promise { + const data = await ctx.fetcher.full(`/${id}`, { + baseUrl: 'https://ww1.goojara.to', + headers: { + accept: '*/*', + 'content-type': 'application/x-www-form-urlencoded', + Referer: 'https://www.goojara.to/', + 'Referrer-Policy': 'strict-origin-when-cross-origin', + }, + readHeaders: ['Set-Cookie'], + method: 'GET', + }); + + const aGoozCookie = data.headers.get('Set-Cookie')?.split(';')[0].split('aGooz=')[1]; + const $ = load(data.body); + const RandomCookieName = data.body.split(`_3chk('`)[1].split(`'`)[0]; + const RandomCookieValue = data.body.split(`_3chk('`)[1].split(`'`)[2]; + + const embedRedirectURLs = $('a') + .map((index, element) => $(element).attr('href')) + .get() + .filter((href) => href && href.includes('https://ww1.goojara.to/go.php')); + + const embeds = await Promise.all( + embedRedirectURLs.map((url) => + ctx.fetcher + .full(url, { + headers: { + cookie: `aGooz=${aGoozCookie}; ${RandomCookieName}=${RandomCookieValue};`, + Referer: 'https://ww1.goojara.to/eJwD5z', + }, + method: 'GET', + }) + .then((result) => { + if (result) { + const embedId = ['wootly', 'upstream', 'mixdrop', 'dood'].find((a) => result.finalUrl.includes(a)); + return embedId ? { embedId, url: result.finalUrl } : null; + } + return null; + }) + .catch(() => null), + ), + ).then((results) => results.filter((result) => result !== null) as EmbedsResult); + + return embeds; +} diff --git a/src/providers/sources/goojara/index.ts b/src/providers/sources/goojara/index.ts new file mode 100644 index 0000000..ce2b096 --- /dev/null +++ b/src/providers/sources/goojara/index.ts @@ -0,0 +1,30 @@ +import { SourcererOutput, makeSourcerer } from '@/providers/base'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; + +import { scrapeIds, searchAndFindMedia } from './util'; + +async function universalScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise { + const goojaraData = await searchAndFindMedia(ctx, ctx.media); + if (!goojaraData) throw new NotFoundError('Media not found'); + + ctx.progress(30); + const embeds = await scrapeIds(ctx, ctx.media, goojaraData); + if (embeds?.length === 0) throw new NotFoundError('No embeds found'); + + + ctx.progress(60); + + return { + embeds + } +} + +export const goojaraScraper = makeSourcerer({ + id: 'goojara', + name: 'goojara', + rank: 69, + flags: [], + scrapeShow: universalScraper, + scrapeMovie: universalScraper, +}); diff --git a/src/providers/sources/goojara/type.ts b/src/providers/sources/goojara/type.ts new file mode 100644 index 0000000..7227ec8 --- /dev/null +++ b/src/providers/sources/goojara/type.ts @@ -0,0 +1,35 @@ +// ! Types +interface BaseConfig { + /** The website's slug. Formatted as `1839578-person-of-interest-2011` */ + slug: string; + /** Type of request */ + type: 'show' | 'movie'; + /** Hash */ + hash: string; + /** Hash expiry */ + expires: number; +} +interface TvConfig extends BaseConfig { + /** Type of request */ + type: 'show'; + /** The episode ID for a TV show. Given in search and URL */ + episodeId: string; +} +interface MovieConfig extends BaseConfig { + /** Type of request */ + type: 'movie'; + /** Movie's id */ + slug: string; +} +export type Config = MovieConfig | TvConfig; + +export type EmbedsResult = { embedId: string; url: string; }[] + +export interface Result { + title: string; + slug: any; + year: string; + type: string; + id_movie?: string; + id_show?: string; +} diff --git a/src/providers/sources/goojara/util.ts b/src/providers/sources/goojara/util.ts new file mode 100644 index 0000000..f2b452b --- /dev/null +++ b/src/providers/sources/goojara/util.ts @@ -0,0 +1,111 @@ +import { load } from 'cheerio'; + +import { MovieMedia, ShowMedia } from '@/entrypoint/utils/media'; +import { compareMedia } from '@/utils/compare'; +import { ScrapeContext } from '@/utils/context'; +import { NotFoundError } from '@/utils/errors'; + +import { getEmbeds } from './getEmbeds'; +import { Result, EmbedsResult } from './type'; + +const headersData = { + accept: '*/*', + 'accept-language': 'en-US,en;q=0.9', + 'content-type': 'application/x-www-form-urlencoded', + 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + cookie: `aGooz=t9pmkdtef1b3lg3pmo1u2re816; bd9aa48e=0d7b89e8c79844e9df07a2; _b414=2151C6B12E2A88379AFF2C0DD65AC8298DEC2BF4; 9d287aaa=8f32ad589e1c4288fe152f`, + Referer: 'https://www.goojara.to/', + 'Referrer-Policy': 'strict-origin-when-cross-origin', +}; + +export async function searchAndFindMedia( + ctx: ScrapeContext, + media: MovieMedia | ShowMedia, +): Promise { + const data = await ctx.fetcher(`/xhrr.php`, { + baseUrl: 'https://www.goojara.to', + headers: headersData, + method: 'POST', + body: `q=${media.title}`, + }) + + const $ = load(data); + + const results: Result[] = []; + + $('.mfeed > li').each((index, element) => { + const title = $(element).find('strong').text(); + const yearMatch = $(element) + .text() + .match(/\((\d{4})\)/); + const typeDiv = $(element).find('div').attr('class'); + const type = typeDiv === 'it' ? 'show' : typeDiv === 'im' ? 'movie' : ''; + const year = yearMatch ? yearMatch[1] : ''; + const slug = $(element).find('a').attr('href')?.split('/')[3]; + + if (media.type === type) { + results.push({ title, year, slug, type }); + } + }); + + const result = results.find((res: Result) => compareMedia(media, res.title, Number(res.year))); + + return result; +} + +export async function scrapeIds(ctx: ScrapeContext, media: MovieMedia | ShowMedia, result: Result): Promise { + // Find the relevant id + let id = null; + if (media.type === 'movie') { + id = result.slug; + } else if (media.type === 'show') { + const data = await ctx.fetcher(`/${result.slug}`, { + baseUrl: 'https://www.goojara.to', + headers: headersData, + method: 'GET', + }); + + const $1 = load(data); + + const dataId = $1('#seon').data('id'); + + const data2 = await ctx.fetcher(`/xhrc.php`, { + baseUrl: 'https://ww1.goojara.to', + headers: headersData, + method: 'POST', + body: `s=${media.season.number}&t=${dataId}`, + }); + + let episodeId = ''; + + const $2 = load(data2); + + $2('.seho').each((index, element) => { + // Extracting the episode number as a string + const episodeNumber = $2(element).find('.seep .sea').text().trim(); + + // Comparing with the desired episode number as a string + if (parseInt(episodeNumber, 10) === media.episode.number) { + const href = $2(element).find('.snfo h1 a').attr('href'); + const idMatch = href?.match(/\/([a-zA-Z0-9]+)$/); + if (idMatch && idMatch[1]) { + episodeId = idMatch[1]; + return false; // Break out of the loop once the episode is found + } + } + }); + + id = episodeId; + } + + // Check ID + if (id === null) throw new NotFoundError('Not found'); + + const embeds = await getEmbeds(ctx, id); + return embeds; +}