diff --git a/src/providers/all.ts b/src/providers/all.ts index 854cef4..ebaaebf 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -16,6 +16,7 @@ import { vTubeScraper } from '@/providers/embeds/vtube'; import { astraScraper, novaScraper } from '@/providers/embeds/whvx'; import { autoembedScraper } from '@/providers/sources/autoembed'; import { catflixScraper } from '@/providers/sources/catflix'; +import { ee3Scraper } from '@/providers/sources/ee3'; import { flixhqScraper } from '@/providers/sources/flixhq/index'; import { goMoviesScraper } from '@/providers/sources/gomovies/index'; import { insertunitScraper } from '@/providers/sources/insertunit'; @@ -93,6 +94,7 @@ export function gatherAllSources(): Array { soaperTvScraper, autoembedScraper, tugaflixScraper, + ee3Scraper, whvxScraper, ]; } diff --git a/src/providers/sources/ee3/common.ts b/src/providers/sources/ee3/common.ts new file mode 100644 index 0000000..f1201cc --- /dev/null +++ b/src/providers/sources/ee3/common.ts @@ -0,0 +1,7 @@ +export const useAltEndpoint: boolean = false; + +export const baseUrl = useAltEndpoint ? 'https://rips.cc' : 'https://ee3.me'; + +export const username = '_sf_'; + +export const password = 'defonotscraping'; diff --git a/src/providers/sources/ee3/index.ts b/src/providers/sources/ee3/index.ts new file mode 100644 index 0000000..4424386 --- /dev/null +++ b/src/providers/sources/ee3/index.ts @@ -0,0 +1,97 @@ +import { flags } from '@/entrypoint/utils/targets'; +import { SourcererOutput, makeSourcerer } from '@/providers/base'; +import { Caption } from '@/providers/captions'; +import { compareMedia } from '@/utils/compare'; +import { MovieScrapeContext } from '@/utils/context'; +import { makeCookieHeader } from '@/utils/cookie'; +import { NotFoundError } from '@/utils/errors'; + +import { baseUrl, password, username } from './common'; +import { itemDetails, renewResponse } from './types'; +import { login, parseSearch } from './utils'; + +// this source only has movies +async function comboScraper(ctx: MovieScrapeContext): Promise { + const pass = await login(username, password, ctx); + if (!pass) throw new Error('Login failed'); + + const search = parseSearch( + await ctx.proxiedFetcher('/get', { + baseUrl, + method: 'POST', + body: new URLSearchParams({ query: ctx.media.title, action: 'search' }), + headers: { + cookie: makeCookieHeader({ PHPSESSID: pass }), + }, + }), + ); + + const id = search.find((v) => v && compareMedia(ctx.media, v.title, v.year))?.id; + if (!id) throw new NotFoundError('No watchable item found'); + + const details: itemDetails = JSON.parse( + await ctx.proxiedFetcher('/get', { + baseUrl, + method: 'POST', + body: new URLSearchParams({ id, action: 'get_movie_info' }), + headers: { + cookie: makeCookieHeader({ PHPSESSID: pass }), + }, + }), + ); + if (!details.message.video) throw new Error('Failed to get the stream'); + + const keyParams: renewResponse = JSON.parse( + await ctx.proxiedFetcher('/renew', { + baseUrl, + method: 'POST', + headers: { + cookie: makeCookieHeader({ PHPSESSID: pass }), + }, + }), + ); + if (!keyParams.k) throw new Error('Failed to get the key'); + + const server = details.message.server === '1' ? 'https://vid.ee3.me/vid/' : 'https://vault.rips.cc/video/'; + const k = keyParams.k; + const url = `${server}${details.message.video}?${new URLSearchParams({ k })}`; + const captions: Caption[] = []; + + // this how they actually deal with subtitles + if (details.message.subs?.toLowerCase() === 'yes' && details.message.imdbID) { + captions.push({ + id: `https://rips.cc/subs/${details.message.imdbID}.vtt`, + url: `https://rips.cc/subs/${details.message.imdbID}.vtt`, + type: 'vtt', + hasCorsRestrictions: false, + language: 'en', + }); + } + + return { + embeds: [], + stream: [ + { + id: 'primary', + type: 'file', + flags: [flags.CORS_ALLOWED], + captions, + qualities: { + // should be unknown, but all the videos are 720p + 720: { + type: 'mp4', + url, + }, + }, + }, + ], + }; +} + +export const ee3Scraper = makeSourcerer({ + id: 'ee3', + name: 'EE3', + rank: 111, + flags: [flags.CORS_ALLOWED], + scrapeMovie: comboScraper, +}); diff --git a/src/providers/sources/ee3/types.ts b/src/providers/sources/ee3/types.ts new file mode 100644 index 0000000..6728c9c --- /dev/null +++ b/src/providers/sources/ee3/types.ts @@ -0,0 +1,31 @@ +export interface itemDetails { + status: number; + message: { + id: string; + imdbID: string; + title: string; + video: string; + server: string; + year: string; + image: string; + glow: string; + rating: string; + watch_count: string; + datetime?: string | null; + requested_by?: string | null; + subs?: string | null; + time?: string | null; + duration?: string | null; + }; +} + +export interface renewResponse { + k: string; + msg?: string | null; + status: number | string | null; +} + +export interface loginResponse { + status: number; + message: string; +} diff --git a/src/providers/sources/ee3/utils.ts b/src/providers/sources/ee3/utils.ts new file mode 100644 index 0000000..a797977 --- /dev/null +++ b/src/providers/sources/ee3/utils.ts @@ -0,0 +1,46 @@ +import { load } from 'cheerio'; + +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; +import { parseSetCookie } from '@/utils/cookie'; + +import { baseUrl } from './common'; +import { loginResponse } from './types'; + +export async function login( + user: string, + pass: string, + ctx: ShowScrapeContext | MovieScrapeContext, +): Promise { + const req = await ctx.proxiedFetcher.full('/login', { + baseUrl, + method: 'POST', + body: new URLSearchParams({ user, pass, action: 'login' }), + readHeaders: ['Set-Cookie'], + }); + const res: loginResponse = JSON.parse(req.body); + + const cookie = parseSetCookie( + // It retruns a cookie even when the login failed + // I have the backup cookie here just in case + res.status === 1 ? (req.headers.get('Set-Cookie') ?? '') : 'PHPSESSID=mk2p73c77qc28o5i5120843ruu;', + ); + + return cookie.PHPSESSID.value; +} + +export function parseSearch(body: string): { title: string; year: number; id: string }[] { + const result: { title: string; year: number; id: string }[] = []; + + const $ = load(body); + $('div').each((_, element) => { + const title = $(element).find('.title').text().trim(); + const year = parseInt($(element).find('.details span').first().text().trim(), 10); + const id = $(element).find('.control-buttons').attr('data-id'); + + if (title && year && id) { + result.push({ title, year, id }); + } + }); + + return result; +} diff --git a/src/providers/sources/nepu/index.ts b/src/providers/sources/nepu/index.ts index f3be5a1..8f3a094 100644 --- a/src/providers/sources/nepu/index.ts +++ b/src/providers/sources/nepu/index.ts @@ -1,6 +1,5 @@ import { load } from 'cheerio'; -import { flags } from '@/entrypoint/utils/targets'; import { SourcererOutput, makeSourcerer } from '@/providers/base'; import { compareTitle } from '@/utils/compare'; import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';