From cfb8da1366ea82ed9fbbfe35ad96ce3d15d8c9e8 Mon Sep 17 00:00:00 2001 From: vlOd <66838724+vlOd2@users.noreply.github.com> Date: Wed, 10 Dec 2025 03:01:09 +0200 Subject: [PATCH] Refactoring to try to align to codebase - also rip filemoon --- package-lock.json | 14 +- src/providers/all.ts | 4 +- src/providers/custom/fsonline/filemoon.ts | 122 ------------------ .../{custom => sources}/dopebox/index.ts | 6 +- .../{custom => sources}/dopebox/search.ts | 0 .../{custom => sources}/dopebox/upcloud.ts | 6 +- .../{custom => sources}/dopebox/utils.ts | 0 .../fsonline/doodstream.ts | 8 +- .../{custom => sources}/fsonline/index.ts | 35 +++-- .../{custom => sources}/fsonline/utils.ts | 20 ++- 10 files changed, 48 insertions(+), 167 deletions(-) delete mode 100644 src/providers/custom/fsonline/filemoon.ts rename src/providers/{custom => sources}/dopebox/index.ts (97%) rename src/providers/{custom => sources}/dopebox/search.ts (100%) rename src/providers/{custom => sources}/dopebox/upcloud.ts (97%) rename src/providers/{custom => sources}/dopebox/utils.ts (100%) rename src/providers/{custom => sources}/fsonline/doodstream.ts (93%) rename src/providers/{custom => sources}/fsonline/index.ts (82%) rename src/providers/{custom => sources}/fsonline/utils.ts (67%) diff --git a/package-lock.json b/package-lock.json index 4812f10..dbe4381 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1680,7 +1680,6 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -2358,7 +2357,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3472,8 +3470,7 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/diff-sequences": { "version": "29.6.3", @@ -3892,7 +3889,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3979,7 +3975,6 @@ "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4078,7 +4073,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6902,7 +6896,6 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -7244,7 +7237,6 @@ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -8112,7 +8104,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8340,7 +8331,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8477,7 +8467,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -8589,7 +8578,6 @@ "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "1.6.1", "@vitest/runner": "1.6.1", diff --git a/src/providers/all.ts b/src/providers/all.ts index bfeb361..b1799db 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -1,6 +1,4 @@ import { Embed, Sourcerer } from '@/providers/base'; -import { dopeboxEmbeds, dopeboxScraper } from '@/providers/custom/dopebox/index'; -import { fsOnlineEmbeds, fsOnlineScraper } from '@/providers/custom/fsonline/index'; import { doodScraper } from '@/providers/embeds/dood'; import { filemoonScraper } from '@/providers/embeds/filemoon'; import { mixdropScraper } from '@/providers/embeds/mixdrop'; @@ -8,8 +6,10 @@ import { serverMirrorEmbed } from '@/providers/embeds/server-mirrors'; import { turbovidScraper } from '@/providers/embeds/turbovid'; import { upcloudScraper } from '@/providers/embeds/upcloud'; import { autoembedScraper } from '@/providers/sources/autoembed'; +import { dopeboxEmbeds, dopeboxScraper } from '@/providers/sources/dopebox/index'; import { ee3Scraper } from '@/providers/sources/ee3'; import { fsharetvScraper } from '@/providers/sources/fsharetv'; +import { fsOnlineEmbeds, fsOnlineScraper } from '@/providers/sources/fsonline/index'; import { insertunitScraper } from '@/providers/sources/insertunit'; import { mp4hydraScraper } from '@/providers/sources/mp4hydra'; import { nepuScraper } from '@/providers/sources/nepu'; diff --git a/src/providers/custom/fsonline/filemoon.ts b/src/providers/custom/fsonline/filemoon.ts deleted file mode 100644 index 5a042f5..0000000 --- a/src/providers/custom/fsonline/filemoon.ts +++ /dev/null @@ -1,122 +0,0 @@ -import * as cheerio from 'cheerio'; -import type { CheerioAPI } from 'cheerio'; - -import { FetcherResponse } from '@/fetchers/types'; -import { EmbedScrapeContext, ScrapeContext } from '@/utils/context'; - -import { ORIGIN_HOST, fetchIFrame, throwOnResponse } from './utils'; -import { EmbedOutput } from '../../base'; - -const LOG_PREFIX = `[Filemoon]`; -const UNPACK_PARAMS_PATERN = /eval\(.+?}\(('.+'),(\d+),(\d+),('.+')\.split\('(.)'\).+/; - -function unpack(payload: string, radix: number, id: number, map: string[]) { - while (id--) { - if (map[id]) { - payload = payload.replace(new RegExp(`\\b${id.toString(radix)}\\b`, 'g'), map[id]); - } - } - return payload; -} - -function deobfuscatePlayerCfg(data: string): string | undefined { - const match = data.match(UNPACK_PARAMS_PATERN); - if (!match) { - return undefined; - } - const obfPayload: string = match[1]; - const radix: number = Number.parseInt(match[2]); - const id: number = Number.parseInt(match[3]); - const obfMap: string = match[4]; - const mapChar: string = match[5]; - return unpack(obfPayload, radix, id, obfMap.split(mapChar)); -} - -async function getStream(ctx: ScrapeContext, url: string): Promise { - console.log(LOG_PREFIX, 'Fetching iframe'); - - let $: CheerioAPI; - let vpReferer: string; - try { - const response: FetcherResponse | undefined = await fetchIFrame(ctx, url); - if (!response) { - return undefined; - } - $ = cheerio.load(await response.body); - vpReferer = response.finalUrl; - } catch (error) { - console.error(LOG_PREFIX, 'Failed to fetch iframe', error); - return undefined; - } - - const videoPlayerURL: string | undefined = $('#iframe-holder').find('iframe').first().attr('src'); - if (!videoPlayerURL) { - console.error(LOG_PREFIX, 'Could not find video player URL'); - return undefined; - } - console.log(LOG_PREFIX, 'Video player URL', videoPlayerURL); - - try { - const response: FetcherResponse = await ctx.proxiedFetcher.full(videoPlayerURL, { - headers: { - Referer: vpReferer, - Origin: ORIGIN_HOST, - }, - }); - throwOnResponse(response); - $ = cheerio.load(await response.body); - } catch (error) { - console.error(LOG_PREFIX, 'Failed to fetch video player', error); - return undefined; - } - - let streamURL: string | undefined; - $('script').each((_, script) => { - if (streamURL) { - return; - } - const cfgScript = deobfuscatePlayerCfg($(script).text()); - if (!cfgScript) { - return undefined; - } - const url = cfgScript.match('file:"(https?://.+?)"')?.[1]; - if (!url) { - return; - } - streamURL = url; - }); - console.log(LOG_PREFIX, 'Stream URL', streamURL); - - return streamURL; -} - -export async function scrapeFilemoonEmbed(ctx: EmbedScrapeContext): Promise { - console.log(LOG_PREFIX, 'Scraping stream URL', ctx.url); - let streamURL: string | undefined; - try { - streamURL = await getStream(ctx, ctx.url); - } catch (error) { - console.warn(LOG_PREFIX, 'Failed to get stream', error); - throw error; - } - if (!streamURL) { - return { - stream: [], - }; - } - return { - stream: [ - { - type: 'hls', - id: 'primary', - flags: ['cors-allowed'], - captions: [], - playlist: streamURL, - headers: { - Referer: ORIGIN_HOST, - Origin: ORIGIN_HOST, - }, - }, - ], - }; -} diff --git a/src/providers/custom/dopebox/index.ts b/src/providers/sources/dopebox/index.ts similarity index 97% rename from src/providers/custom/dopebox/index.ts rename to src/providers/sources/dopebox/index.ts index 5840ca5..6721e99 100644 --- a/src/providers/custom/dopebox/index.ts +++ b/src/providers/sources/dopebox/index.ts @@ -78,7 +78,7 @@ async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promis export const dopeboxScraper = makeSourcerer({ id: 'dopebox', name: 'Dopebox', - rank: 600, + rank: 210, flags: ['cors-allowed'], scrapeMovie: comboScraper, scrapeShow: comboScraper, @@ -88,8 +88,8 @@ export const dopeboxEmbeds = [ makeEmbed({ id: 'dopebox-upcloud', name: 'UpCloud', - rank: 6001, - flags: [], + rank: 2101, + flags: ['cors-allowed'], scrape: scrapeUpCloudEmbed, }), ]; diff --git a/src/providers/custom/dopebox/search.ts b/src/providers/sources/dopebox/search.ts similarity index 100% rename from src/providers/custom/dopebox/search.ts rename to src/providers/sources/dopebox/search.ts diff --git a/src/providers/custom/dopebox/upcloud.ts b/src/providers/sources/dopebox/upcloud.ts similarity index 97% rename from src/providers/custom/dopebox/upcloud.ts rename to src/providers/sources/dopebox/upcloud.ts index 66439cb..895ba7d 100644 --- a/src/providers/custom/dopebox/upcloud.ts +++ b/src/providers/sources/dopebox/upcloud.ts @@ -123,19 +123,19 @@ export async function scrapeUpCloudEmbed(ctx: EmbedScrapeContext): Promise { - console.log(LOG_PREFIX, 'Fetching iframe'); + // console.log(LOG_PREFIX, 'Fetching iframe'); let $: CheerioAPI; let streamHost: string; @@ -66,7 +66,7 @@ async function getStream(ctx: ScrapeContext, url: string): Promise<[string, stri console.error(LOG_PREFIX, "Couldn't find stream info", streamReq, tokenParams); return undefined; } - console.log(LOG_PREFIX, 'Stream info', streamReq, tokenParams); + // console.log(LOG_PREFIX, 'Stream info', streamReq, tokenParams); let streamURL: string; try { @@ -83,13 +83,13 @@ async function getStream(ctx: ScrapeContext, url: string): Promise<[string, stri console.error(LOG_PREFIX, 'Failed to request stream URL', error); return undefined; } - console.log(LOG_PREFIX, 'Stream URL', streamURL); + // console.log(LOG_PREFIX, 'Stream URL', streamURL); return [streamURL, streamHost]; } export async function scrapeDoodstreamEmbed(ctx: EmbedScrapeContext): Promise { - console.log(LOG_PREFIX, 'Scraping stream URL', ctx.url); + // console.log(LOG_PREFIX, 'Scraping stream URL', ctx.url); let streamURL: string | undefined; let streamHost: string | undefined; try { diff --git a/src/providers/custom/fsonline/index.ts b/src/providers/sources/fsonline/index.ts similarity index 82% rename from src/providers/custom/fsonline/index.ts rename to src/providers/sources/fsonline/index.ts index 9b977df..0e8df62 100644 --- a/src/providers/custom/fsonline/index.ts +++ b/src/providers/sources/fsonline/index.ts @@ -6,13 +6,12 @@ import { SourcererEmbed, SourcererOutput, makeEmbed, makeSourcerer } from '@/pro import { MovieScrapeContext, ScrapeContext, ShowScrapeContext } from '@/utils/context'; import { scrapeDoodstreamEmbed } from './doodstream'; -import { scrapeFilemoonEmbed } from './filemoon'; -import { EMBED_URL, ORIGIN_HOST, getMoviePageURL, throwOnResponse } from './utils'; +import { EMBED_URL, ORIGIN_HOST, fetchENTMDBName, getMoviePageURL, throwOnResponse } from './utils'; export const LOG_PREFIX = '[FSOnline]'; async function getMovieID(ctx: ScrapeContext, url: string): Promise { - console.log(LOG_PREFIX, 'Scraping movie ID from', url); + // console.log(LOG_PREFIX, 'Scraping movie ID from', url); let $: CheerioAPI; try { @@ -34,13 +33,13 @@ async function getMovieID(ctx: ScrapeContext, url: string): Promise> { - console.log(LOG_PREFIX, 'Scraping movie sources for', id); + // console.log(LOG_PREFIX, 'Scraping movie sources for', id); const sources: Map = new Map(); let $: CheerioAPI; @@ -69,7 +68,7 @@ async function getMovieSources(ctx: ScrapeContext, id: string, refererHeader: st console.warn(LOG_PREFIX, 'Skipping invalid source', name); return; } - console.log(LOG_PREFIX, 'Found movie source for', id, name, url); + // console.log(LOG_PREFIX, 'Found movie source for', id, name, url); sources.set(name, url); }); @@ -88,13 +87,13 @@ function addEmbedFromSources(name: string, sources: Map, embeds: } async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise { - // always use the english title + const movieName = await fetchENTMDBName(Number(ctx.media.tmdbId), ctx.media.type); const moviePageURL = getMoviePageURL( - ctx.media.type === 'movie' ? `${ctx.media.title} ${ctx.media.releaseYear}` : ctx.media.title, + ctx.media.type === 'movie' ? `${movieName} ${ctx.media.releaseYear}` : movieName, ctx.media.type === 'show' ? ctx.media.season.number : undefined, ctx.media.type === 'show' ? ctx.media.episode.number : undefined, ); - console.log(LOG_PREFIX, 'Movie page URL', moviePageURL); + // console.log(LOG_PREFIX, 'Movie page URL', moviePageURL); const movieID = await getMovieID(ctx, moviePageURL); if (!movieID) { @@ -121,7 +120,7 @@ async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promis export const fsOnlineScraper = makeSourcerer({ id: 'fsonline', name: 'FSOnline', - rank: 500, + rank: 200, flags: ['cors-allowed'], scrapeMovie: comboScraper, scrapeShow: comboScraper, @@ -131,15 +130,15 @@ export const fsOnlineEmbeds = [ makeEmbed({ id: 'fsonline-doodstream', name: 'Doodstream', - rank: 5001, + rank: 2001, scrape: scrapeDoodstreamEmbed, flags: ['cors-allowed'], }), - makeEmbed({ - id: 'fsonline-filemoon', - name: 'Filemoon', - rank: 5002, - scrape: scrapeFilemoonEmbed, - flags: ['cors-allowed'], - }), + // makeEmbed({ + // id: 'fsonline-filemoon', + // name: 'Filemoon', + // rank: 2002, + // scrape: scrapeFilemoonEmbed, + // flags: ['cors-allowed'], + // }), ]; diff --git a/src/providers/custom/fsonline/utils.ts b/src/providers/sources/fsonline/utils.ts similarity index 67% rename from src/providers/custom/fsonline/utils.ts rename to src/providers/sources/fsonline/utils.ts index d32404d..959cc6b 100644 --- a/src/providers/custom/fsonline/utils.ts +++ b/src/providers/sources/fsonline/utils.ts @@ -5,6 +5,7 @@ export const ORIGIN_HOST = 'https://www3.fsonline.app'; export const MOVIE_PAGE_URL = 'https://www3.fsonline.app/film/'; export const SHOW_PAGE_URL = 'https://www3.fsonline.app/episoade/{{MOVIE}}-sezonul-{{SEASON}}-episodul-{{EPISODE}}/'; export const EMBED_URL = 'https://www3.fsonline.app/wp-admin/admin-ajax.php'; +const TMDB_API_KEY = 'a500049f3e06109fe3e8289b06cf5685'; export function throwOnResponse(response: FetcherResponse) { if (response.statusCode >= 400) { @@ -29,11 +30,26 @@ export function getMoviePageURL(name: string, season?: number, episode?: number) return `${MOVIE_PAGE_URL}${name}/`; } +export async function fetchENTMDBName(tmdbId: number, mediaType: 'movie' | 'show'): Promise { + const endpoint = + mediaType === 'movie' + ? `https://api.themoviedb.org/3/movie/${tmdbId}?api_key=${TMDB_API_KEY}&language=en-US` + : `https://api.themoviedb.org/3/tv/${tmdbId}?api_key=${TMDB_API_KEY}&language=en-US`; + + const response = await fetch(endpoint); + if (!response.ok) { + throw new Error(`Error fetching TMDB data: ${response.statusText}`); + } + + const tmdbData = await response.json(); + return mediaType === 'movie' ? tmdbData.title : tmdbData.name; +} + export async function fetchIFrame(ctx: ScrapeContext, url: string): Promise { const response: FetcherResponse = await ctx.proxiedFetcher.full(url, { headers: { - Referer: `${ORIGIN_HOST}/`, - // Origin: ORIGIN_HOST, + Referer: ORIGIN_HOST, + Origin: ORIGIN_HOST, 'sec-fetch-dest': 'iframe', 'sec-fetch-mode': 'navigate', 'sec-fetch-site': 'cross-site',