From 849347afbe830b6a9d693c9ef6181f47dc4ecef7 Mon Sep 17 00:00:00 2001 From: Jonathan Barrow Date: Sat, 30 Sep 2023 17:44:32 -0400 Subject: [PATCH 01/15] add vidsrc source+embed and StreamBucket embed --- .eslintrc.js | 2 + README.md | 1 + src/dev-cli.ts | 9 +- src/fetchers/common.ts | 1 + src/fetchers/fetch.ts | 1 + src/fetchers/standardFetch.ts | 4 + src/fetchers/types.ts | 6 +- src/main/builder.ts | 3 + src/main/individualRunner.ts | 2 + src/providers/all.ts | 23 ++- src/providers/base.ts | 1 + src/providers/embeds/streambucket.ts | 97 ++++++++++++ src/providers/embeds/vidsrc.ts | 50 +++++++ src/providers/sources/vidsrc/common.ts | 13 ++ src/providers/sources/vidsrc/index.ts | 13 ++ src/providers/sources/vidsrc/scrape-movie.ts | 8 + src/providers/sources/vidsrc/scrape-show.ts | 8 + src/providers/sources/vidsrc/scrape.ts | 147 +++++++++++++++++++ src/utils/context.ts | 1 + 19 files changed, 385 insertions(+), 5 deletions(-) create mode 100644 src/providers/embeds/streambucket.ts create mode 100644 src/providers/embeds/vidsrc.ts create mode 100644 src/providers/sources/vidsrc/common.ts create mode 100644 src/providers/sources/vidsrc/index.ts create mode 100644 src/providers/sources/vidsrc/scrape-movie.ts create mode 100644 src/providers/sources/vidsrc/scrape-show.ts create mode 100644 src/providers/sources/vidsrc/scrape.ts diff --git a/.eslintrc.js b/.eslintrc.js index 7939452..d62f0d6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,6 +18,8 @@ module.exports = { }, plugins: ['@typescript-eslint', 'import', 'prettier'], rules: { + 'no-plusplus': 'off', + 'no-bitwise': 'off', 'no-underscore-dangle': 'off', '@typescript-eslint/no-explicit-any': 'off', 'no-console': 'off', diff --git a/README.md b/README.md index d30d021..0042142 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ The following CLI Mode arguments are available | `--season` | `-s` | Season number. Only used if type is `show` | `0` | | `--episode` | `-e` | Episode number. Only used if type is `show` | `0` | | `--url` | `-u` | URL to a video embed. Only used if source is an embed | | +| `--headers` | `-h` | Optional headers to send while scraping | | | `--help` | `-h` | Shows help for the command arguments | | Example testing the FlixHQ source on the movie "Spirited Away" diff --git a/src/dev-cli.ts b/src/dev-cli.ts index 4989354..4f72256 100644 --- a/src/dev-cli.ts +++ b/src/dev-cli.ts @@ -37,6 +37,7 @@ type CommandLineArguments = { season: string; episode: string; url: string; + headers?: Record; }; const TMDB_API_KEY = process.env.MOVIE_WEB_TMDB_API_KEY ?? ''; @@ -179,6 +180,7 @@ async function runScraper(providers: ProviderControls, source: MetaOutput, optio const result = await providers.runEmbedScraper({ url: options.url, id: source.id, + headers: options.headers, }); spinnies.succeed('scrape', { text: 'Done!' }); console.log(result); @@ -273,6 +275,10 @@ async function processOptions(options: CommandLineArguments) { } } + if (typeof options.headers === 'string') { + options.headers = JSON.parse(options.headers); + } + let fetcher; if (options.fetcher === 'native') { @@ -403,7 +409,8 @@ async function runCommandLine() { .option('-t, --type ', "Media type. Either 'movie' or 'show'. Only used if source is a provider", 'movie') .option('-s, --season ', "Season number. Only used if type is 'show'", '0') .option('-e, --episode ', "Episode number. Only used if type is 'show'", '0') - .option('-u, --url ', 'URL to a video embed. Only used if source is an embed', ''); + .option('-u, --url ', 'URL to a video embed. Only used if source is an embed', '') + .option('-h, --headers ', 'Optional headers to pass to scrapers. JSON encoded'); program.parse(); diff --git a/src/fetchers/common.ts b/src/fetchers/common.ts index e31b6d1..dff6978 100644 --- a/src/fetchers/common.ts +++ b/src/fetchers/common.ts @@ -34,6 +34,7 @@ export function makeFullFetcher(fetcher: Fetcher): UseableFetcher { query: ops?.query ?? {}, baseUrl: ops?.baseUrl ?? '', body: ops?.body, + returnRaw: ops?.returnRaw, }); }; } diff --git a/src/fetchers/fetch.ts b/src/fetchers/fetch.ts index 1d419f0..8311fb2 100644 --- a/src/fetchers/fetch.ts +++ b/src/fetchers/fetch.ts @@ -17,6 +17,7 @@ export type FetchReply = { text(): Promise; json(): Promise; headers: FetchHeaders; + url: string; }; export type FetchLike = (url: string, ops?: FetchOps | undefined) => Promise; diff --git a/src/fetchers/standardFetch.ts b/src/fetchers/standardFetch.ts index dd84893..bdf14d1 100644 --- a/src/fetchers/standardFetch.ts +++ b/src/fetchers/standardFetch.ts @@ -17,6 +17,10 @@ export function makeStandardFetcher(f: FetchLike): Fetcher { body: seralizedBody.body, }); + if (ops.returnRaw) { + return res; + } + const isJson = res.headers.get('content-type')?.includes('application/json'); if (isJson) return res.json(); return res.text(); diff --git a/src/fetchers/types.ts b/src/fetchers/types.ts index 2d14748..1ad3092 100644 --- a/src/fetchers/types.ts +++ b/src/fetchers/types.ts @@ -4,8 +4,9 @@ export type FetcherOptions = { baseUrl?: string; headers?: Record; query?: Record; - method?: 'GET' | 'POST'; + method?: 'HEAD' | 'GET' | 'POST'; body?: Record | string | FormData | URLSearchParams; + returnRaw?: boolean; }; export type DefaultedFetcherOptions = { @@ -13,7 +14,8 @@ export type DefaultedFetcherOptions = { body?: Record | string | FormData; headers: Record; query: Record; - method: 'GET' | 'POST'; + method: 'HEAD' | 'GET' | 'POST'; + returnRaw?: boolean; }; export type Fetcher = { diff --git a/src/main/builder.ts b/src/main/builder.ts index 0322dbd..a02f298 100644 --- a/src/main/builder.ts +++ b/src/main/builder.ts @@ -57,6 +57,9 @@ export interface EmbedRunnerOptions { // id of the embed scraper you want to scrape from id: string; + + // optional headers for the embed scraper to use + headers?: Record; } export interface ProviderControls { diff --git a/src/main/individualRunner.ts b/src/main/individualRunner.ts index 957edd0..bb64587 100644 --- a/src/main/individualRunner.ts +++ b/src/main/individualRunner.ts @@ -65,6 +65,7 @@ export type IndividualEmbedRunnerOptions = { url: string; id: string; events?: IndividualScraperEvents; + headers?: Record; }; export async function scrapeIndividualEmbed( @@ -78,6 +79,7 @@ export async function scrapeIndividualEmbed( fetcher: ops.fetcher, proxiedFetcher: ops.proxiedFetcher, url: ops.url, + headers: ops.headers, progress(val) { ops.events?.update?.({ id: embedScraper.id, diff --git a/src/providers/all.ts b/src/providers/all.ts index ef26094..00c139f 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -1,22 +1,41 @@ import { Embed, Sourcerer } from '@/providers/base'; import { mixdropScraper } from '@/providers/embeds/mixdrop'; import { mp4uploadScraper } from '@/providers/embeds/mp4upload'; +import { streambucketScraper } from '@/providers/embeds/streambucket'; import { streamsbScraper } from '@/providers/embeds/streamsb'; import { upcloudScraper } from '@/providers/embeds/upcloud'; import { upstreamScraper } from '@/providers/embeds/upstream'; +import { vidsrcembedScraper } from '@/providers/embeds/vidsrc'; import { flixhqScraper } from '@/providers/sources/flixhq/index'; import { goMoviesScraper } from '@/providers/sources/gomovies/index'; import { kissAsianScraper } from '@/providers/sources/kissasian/index'; import { remotestreamScraper } from '@/providers/sources/remotestream'; import { superStreamScraper } from '@/providers/sources/superstream/index'; +import { vidsrcScraper } from '@/providers/sources/vidsrc'; import { zoechipScraper } from '@/providers/sources/zoechip'; export function gatherAllSources(): Array { // all sources are gathered here - return [flixhqScraper, remotestreamScraper, kissAsianScraper, superStreamScraper, goMoviesScraper, zoechipScraper]; + return [ + flixhqScraper, + remotestreamScraper, + kissAsianScraper, + superStreamScraper, + goMoviesScraper, + zoechipScraper, + vidsrcScraper, + ]; } export function gatherAllEmbeds(): Array { // all embeds are gathered here - return [upcloudScraper, mp4uploadScraper, streamsbScraper, upstreamScraper, mixdropScraper]; + return [ + upcloudScraper, + mp4uploadScraper, + streamsbScraper, + upstreamScraper, + mixdropScraper, + vidsrcembedScraper, + streambucketScraper, + ]; } diff --git a/src/providers/base.ts b/src/providers/base.ts index 902a5a6..f7bc982 100644 --- a/src/providers/base.ts +++ b/src/providers/base.ts @@ -7,6 +7,7 @@ export type SourcererOutput = { embeds: { embedId: string; url: string; + headers?: Record; }[]; stream?: Stream; }; diff --git a/src/providers/embeds/streambucket.ts b/src/providers/embeds/streambucket.ts new file mode 100644 index 0000000..a3d2689 --- /dev/null +++ b/src/providers/embeds/streambucket.ts @@ -0,0 +1,97 @@ +import { flags } from '@/main/targets'; +import { makeEmbed } from '@/providers/base'; + +// StreamBucket makes use of https://github.com/nicxlau/hunter-php-javascript-obfuscator + +const hunterRegex = /eval\(function\(h,u,n,t,e,r\).*?\("(.*?)",\d*?,"(.*?)",(\d*?),(\d*?),\d*?\)\)/; +const linkRegex = /file:"(.*?)"/; + +// This is a much more simple and optimized version of the "h,u,n,t,e,r" +// obfuscation algorithm. It's just basic chunked+mask encoding. +// I have seen this same encoding used on some sites under the name +// "p,l,a,y,e,r" as well +function decodeHunter(encoded: string, mask: string, charCodeOffset: number, delimiterOffset: number) { + // The encoded string is made up of 'n' number of chunks. + // Each chunk is separated by a delimiter inside the mask. + // This offset is also used as the exponentiation base in + // the charCode calculations + const delimiter = mask[delimiterOffset]; + + // Split the 'encoded' string into chunks using the delimiter, + // and filter out any empty chunks. + const chunks = encoded.split(delimiter).filter((chunk) => chunk); + + // Decode each chunk and concatenate the results to form the final 'decoded' string. + const decoded = chunks + .map((chunk) => { + // Chunks are in reverse order. 'reduceRight' removes the + // need to 'reverse' the array first + const charCode = chunk.split('').reduceRight((c, value, index) => { + // Calculate the character code for each character in the chunk. + // This involves finding the index of 'value' in the 'mask' and + // multiplying it by (delimiterOffset^position). + return c + mask.indexOf(value) * delimiterOffset ** (chunk.length - 1 - index); + }, 0); + + // The actual character code is offset by the given amount + return String.fromCharCode(charCode - charCodeOffset); + }) + .join(''); + + return decoded; +} + +export const streambucketScraper = makeEmbed({ + id: 'streambucket', + name: 'StreamBucket', + rank: 196, + // TODO - Disabled until ctx.fetcher and ctx.proxiedFetcher don't trigger bot detection + disabled: true, + async scrape(ctx) { + // Using the context fetchers make the site return just the string "No bots please!"? + // TODO - Fix this. Native fetch does not trigger this. No idea why right now + const response = await fetch(ctx.url); + const html = await response.text(); + + // This is different than the above mentioned bot detection + if (html.includes('captcha-checkbox')) { + // TODO - This doesn't use recaptcha, just really basic "image match". Maybe could automate? + throw new Error('StreamBucket got captchaed'); + } + + let regexResult = html.match(hunterRegex); + + if (!regexResult) { + throw new Error('Failed to find StreamBucket hunter JavaScript'); + } + + const encoded = regexResult[1]; + const mask = regexResult[2]; + const charCodeOffset = Number(regexResult[3]); + const delimiterOffset = Number(regexResult[4]); + + if (Number.isNaN(charCodeOffset)) { + throw new Error('StreamBucket hunter JavaScript charCodeOffset is not a valid number'); + } + + if (Number.isNaN(delimiterOffset)) { + throw new Error('StreamBucket hunter JavaScript delimiterOffset is not a valid number'); + } + + const decoded = decodeHunter(encoded, mask, charCodeOffset, delimiterOffset); + + regexResult = decoded.match(linkRegex); + + if (!regexResult) { + throw new Error('Failed to find StreamBucket HLS link'); + } + + return { + stream: { + type: 'hls', + playlist: regexResult[1], + flags: [flags.NO_CORS], + }, + }; + }, +}); diff --git a/src/providers/embeds/vidsrc.ts b/src/providers/embeds/vidsrc.ts new file mode 100644 index 0000000..3e025b6 --- /dev/null +++ b/src/providers/embeds/vidsrc.ts @@ -0,0 +1,50 @@ +import { flags } from '@/main/targets'; +import { makeEmbed } from '@/providers/base'; + +const hlsURLRegex = /var hls_url = "(.*?)";/; +const setPassRegex = /var path = "(.*set_pass\.php.*)";/; + +export const vidsrcembedScraper = makeEmbed({ + id: 'vidsrcembed', // VidSrc is both a source and an embed host + name: 'VidSrc', + rank: 197, + async scrape(ctx) { + if (!ctx.headers || (!ctx.headers.referer && !ctx.headers.Referer)) { + throw new Error('VidSrc embeds require the referer header to be set'); + } + const html = await ctx.proxiedFetcher(ctx.url, { + headers: ctx.headers, + }); + + let regexResult = html.match(setPassRegex); + if (!regexResult) { + throw new Error('Unable to find VidSrc set_pass.php link'); + } + + let setPassLink = regexResult[1]; + + if (setPassLink.startsWith('//')) { + setPassLink = `https:${setPassLink}`; + } + + regexResult = html.match(hlsURLRegex); + if (!regexResult) { + throw new Error('Unable to find VidSrc HLS stream'); + } + + // Must call set_pass.php BEFORE using the stream + await fetch(setPassLink, { + headers: { + referer: ctx.url, + }, + }); + + return { + stream: { + type: 'hls', + playlist: regexResult[1], + flags: [flags.NO_CORS], + }, + }; + }, +}); diff --git a/src/providers/sources/vidsrc/common.ts b/src/providers/sources/vidsrc/common.ts new file mode 100644 index 0000000..6f8d773 --- /dev/null +++ b/src/providers/sources/vidsrc/common.ts @@ -0,0 +1,13 @@ +import { MovieMedia, ShowMedia } from '@/main/media'; +import { ScrapeContext } from '@/utils/context'; + +export const vidsrcBase = 'https://vidsrc.me'; +export const vidsrcRCPBase = 'https://rcp.vidsrc.me'; + +export type MovieContext = ScrapeContext & { + media: MovieMedia; +}; + +export type ShowContext = ScrapeContext & { + media: ShowMedia; +}; diff --git a/src/providers/sources/vidsrc/index.ts b/src/providers/sources/vidsrc/index.ts new file mode 100644 index 0000000..f7ad792 --- /dev/null +++ b/src/providers/sources/vidsrc/index.ts @@ -0,0 +1,13 @@ +import { flags } from '@/main/targets'; +import { makeSourcerer } from '@/providers/base'; +import { scrapeMovie } from '@/providers/sources/vidsrc/scrape-movie'; +import { scrapeShow } from '@/providers/sources/vidsrc/scrape-show'; + +export const vidsrcScraper = makeSourcerer({ + id: 'vidsrc', + name: 'VidSrc', + rank: 120, + flags: [flags.NO_CORS], + scrapeMovie, + scrapeShow, +}); diff --git a/src/providers/sources/vidsrc/scrape-movie.ts b/src/providers/sources/vidsrc/scrape-movie.ts new file mode 100644 index 0000000..7e0a796 --- /dev/null +++ b/src/providers/sources/vidsrc/scrape-movie.ts @@ -0,0 +1,8 @@ +import { MovieContext } from '@/providers/sources/vidsrc/common'; +import { getVidSrcMovieSources } from '@/providers/sources/vidsrc/scrape'; + +export async function scrapeMovie(ctx: MovieContext) { + return { + embeds: await getVidSrcMovieSources(ctx), + }; +} diff --git a/src/providers/sources/vidsrc/scrape-show.ts b/src/providers/sources/vidsrc/scrape-show.ts new file mode 100644 index 0000000..7214332 --- /dev/null +++ b/src/providers/sources/vidsrc/scrape-show.ts @@ -0,0 +1,8 @@ +import { ShowContext } from '@/providers/sources/vidsrc/common'; +import { getVidSrcShowSources } from '@/providers/sources/vidsrc/scrape'; + +export async function scrapeShow(ctx: ShowContext) { + return { + embeds: await getVidSrcShowSources(ctx), + }; +} diff --git a/src/providers/sources/vidsrc/scrape.ts b/src/providers/sources/vidsrc/scrape.ts new file mode 100644 index 0000000..a8aa4fc --- /dev/null +++ b/src/providers/sources/vidsrc/scrape.ts @@ -0,0 +1,147 @@ +import { load } from 'cheerio'; + +import { FetchReply } from '@/fetchers/fetch'; +import { streambucketScraper } from '@/providers/embeds/streambucket'; +import { vidsrcembedScraper } from '@/providers/embeds/vidsrc'; +import { MovieContext, ShowContext, vidsrcBase, vidsrcRCPBase } from '@/providers/sources/vidsrc/common'; + +function decodeSrc(encoded: string, seed: string) { + const encodedBuffer = Buffer.from(encoded, 'hex'); + let decoded = ''; + + for (let i = 0; i < encodedBuffer.length; i++) { + decoded += String.fromCharCode(encodedBuffer[i] ^ seed.charCodeAt(i % seed.length)); + } + + return decoded; +} + +async function getVidSrcEmbeds(ctx: MovieContext | ShowContext, startingURL: string) { + // VidSrc works by using hashes and a redirect system. + // The hashes are stored in the html, and VidSrc will + // make requests to their servers with the hash. This + // will trigger a 302 response with a Location header + // sending the user to the correct embed. To get the + // real embed links, we must do the same. Slow, but + // required + + const embeds: { + embedId: string; + url: string; + headers?: Record; + }[] = []; + + let html = await ctx.proxiedFetcher(startingURL, { + baseUrl: vidsrcBase, + }); + + let $ = load(html); + + const sourceHashes = $('.source[data-hash]') + .toArray() + .map((el) => $(el).attr('data-hash')) + .filter((hash) => hash !== undefined); + + for (const hash of sourceHashes) { + html = await ctx.proxiedFetcher(`/rcp/${hash}`, { + baseUrl: vidsrcRCPBase, + headers: { + referer: `${vidsrcBase}${startingURL}`, + }, + }); + + $ = load(html); + const encoded = $('#hidden').attr('data-h'); + const seed = $('body').attr('data-i'); + + if (!encoded || !seed) { + throw new Error('Failed to find encoded iframe src'); + } + + let redirectURL = decodeSrc(encoded, seed); + if (redirectURL.startsWith('//')) { + redirectURL = `https:${redirectURL}`; + } + + // Return the raw fetch response here. + // When a Location header is sent, fetch + // will silently follow it. The "url" inside + // the Response is the final requested URL, + // which is the real embeds URL + const { url: embedURL } = await ctx.proxiedFetcher(redirectURL, { + returnRaw: true, + method: 'HEAD', // We don't care about the actual response body here + headers: { + referer: `${vidsrcRCPBase}/rcp/${hash}`, + }, + }); + + const embed: { + embedId: string; + url: string; + headers?: Record; + } = { + embedId: '', + url: embedURL, + }; + + const parsedUrl = new URL(embedURL); + + switch (parsedUrl.host) { + case 'vidsrc.stream': + embed.embedId = vidsrcembedScraper.id; + embed.headers = { + referer: `${vidsrcRCPBase}/rcp/${hash}`, + }; + break; + case 'streambucket.net': + embed.embedId = streambucketScraper.id; + break; + case '2embed.cc': + case 'www.2embed.cc': + // Just ignore this. This embed just sources from other embeds we can scrape as a 'source' + break; + case 'player-cdn.com': + // Just ignore this. This embed streams video over a custom WebSocket connection + break; + default: + throw new Error(`Failed to find VidSrc embed source for ${embedURL}`); + } + + // Since some embeds are ignored on purpose, check if a valid one was found + if (embed.embedId !== '') { + embeds.push(embed); + } + } + + return embeds; +} + +export async function getVidSrcMovieSources(ctx: MovieContext) { + return getVidSrcEmbeds(ctx, `/embed/${ctx.media.tmdbId}`); +} + +export async function getVidSrcShowSources(ctx: ShowContext) { + // VidSrc will always default to season 1 episode 1 + // no matter what embed URL is used. It sends back + // a list of ALL the shows episodes, in order, for + // all seasons. To get the real embed URL, have to + // parse this from the response + const html = await ctx.proxiedFetcher(`/embed/${ctx.media.tmdbId}`, { + baseUrl: vidsrcBase, + }); + + const $ = load(html); + + const episodeElement = $(`.ep[data-s="${ctx.media.season.number}"][data-e="${ctx.media.episode.number}"]`).first(); + if (episodeElement.length === 0) { + throw new Error('failed to find episode element'); + } + + const startingURL = episodeElement.attr('data-iframe'); + if (!startingURL) { + throw new Error('failed to find episode starting URL'); + } + + return getVidSrcEmbeds(ctx, startingURL); +} diff --git a/src/utils/context.ts b/src/utils/context.ts index 5a9664a..83c645d 100644 --- a/src/utils/context.ts +++ b/src/utils/context.ts @@ -8,6 +8,7 @@ export type ScrapeContext = { export type EmbedInput = { url: string; + headers?: Record; }; export type EmbedScrapeContext = EmbedInput & ScrapeContext; From 64050df3501c18ca302765e61bb8f0c95d9e2d0e Mon Sep 17 00:00:00 2001 From: Jorrin Date: Mon, 25 Dec 2023 22:51:55 +0100 Subject: [PATCH 02/15] fix vidsrc --- src/fetchers/common.ts | 2 +- src/fetchers/types.ts | 2 +- src/providers/embeds/streambucket.ts | 1 + src/providers/embeds/vidsrc.ts | 37 ++++++-------------- src/providers/sources/vidsrc/common.ts | 11 ------ src/providers/sources/vidsrc/scrape-movie.ts | 4 +-- src/providers/sources/vidsrc/scrape-show.ts | 4 +-- src/providers/sources/vidsrc/scrape.ts | 24 +++++-------- 8 files changed, 27 insertions(+), 58 deletions(-) diff --git a/src/fetchers/common.ts b/src/fetchers/common.ts index dff6978..a2b77aa 100644 --- a/src/fetchers/common.ts +++ b/src/fetchers/common.ts @@ -34,7 +34,7 @@ export function makeFullFetcher(fetcher: Fetcher): UseableFetcher { query: ops?.query ?? {}, baseUrl: ops?.baseUrl ?? '', body: ops?.body, - returnRaw: ops?.returnRaw, + returnRaw: ops?.returnRaw ?? false, }); }; } diff --git a/src/fetchers/types.ts b/src/fetchers/types.ts index 1ad3092..8e581a6 100644 --- a/src/fetchers/types.ts +++ b/src/fetchers/types.ts @@ -15,7 +15,7 @@ export type DefaultedFetcherOptions = { headers: Record; query: Record; method: 'HEAD' | 'GET' | 'POST'; - returnRaw?: boolean; + returnRaw: boolean; }; export type Fetcher = { diff --git a/src/providers/embeds/streambucket.ts b/src/providers/embeds/streambucket.ts index a3d2689..f137ceb 100644 --- a/src/providers/embeds/streambucket.ts +++ b/src/providers/embeds/streambucket.ts @@ -91,6 +91,7 @@ export const streambucketScraper = makeEmbed({ type: 'hls', playlist: regexResult[1], flags: [flags.NO_CORS], + captions: [], }, }; }, diff --git a/src/providers/embeds/vidsrc.ts b/src/providers/embeds/vidsrc.ts index 3e025b6..da3f2aa 100644 --- a/src/providers/embeds/vidsrc.ts +++ b/src/providers/embeds/vidsrc.ts @@ -1,8 +1,6 @@ -import { flags } from '@/main/targets'; import { makeEmbed } from '@/providers/base'; -const hlsURLRegex = /var hls_url = "(.*?)";/; -const setPassRegex = /var path = "(.*set_pass\.php.*)";/; +const hlsURLRegex = /file:"(.*?)"/; export const vidsrcembedScraper = makeEmbed({ id: 'vidsrcembed', // VidSrc is both a source and an embed host @@ -16,34 +14,21 @@ export const vidsrcembedScraper = makeEmbed({ headers: ctx.headers, }); - let regexResult = html.match(setPassRegex); - if (!regexResult) { - throw new Error('Unable to find VidSrc set_pass.php link'); - } + const match = html + .match(hlsURLRegex)?.[1] + ?.replace(/(\/\/\S+?=)/g, '') + .replace('#2', ''); + if (!match) throw new Error('Unable to find HLS playlist'); + const finalUrl = atob(match); - let setPassLink = regexResult[1]; - - if (setPassLink.startsWith('//')) { - setPassLink = `https:${setPassLink}`; - } - - regexResult = html.match(hlsURLRegex); - if (!regexResult) { - throw new Error('Unable to find VidSrc HLS stream'); - } - - // Must call set_pass.php BEFORE using the stream - await fetch(setPassLink, { - headers: { - referer: ctx.url, - }, - }); + if (!finalUrl.includes('.m3u8')) throw new Error('Unable to find HLS playlist'); return { stream: { type: 'hls', - playlist: regexResult[1], - flags: [flags.NO_CORS], + playlist: finalUrl, + flags: [], + captions: [], }, }; }, diff --git a/src/providers/sources/vidsrc/common.ts b/src/providers/sources/vidsrc/common.ts index 6f8d773..4ccc93c 100644 --- a/src/providers/sources/vidsrc/common.ts +++ b/src/providers/sources/vidsrc/common.ts @@ -1,13 +1,2 @@ -import { MovieMedia, ShowMedia } from '@/main/media'; -import { ScrapeContext } from '@/utils/context'; - export const vidsrcBase = 'https://vidsrc.me'; export const vidsrcRCPBase = 'https://rcp.vidsrc.me'; - -export type MovieContext = ScrapeContext & { - media: MovieMedia; -}; - -export type ShowContext = ScrapeContext & { - media: ShowMedia; -}; diff --git a/src/providers/sources/vidsrc/scrape-movie.ts b/src/providers/sources/vidsrc/scrape-movie.ts index 7e0a796..585eb31 100644 --- a/src/providers/sources/vidsrc/scrape-movie.ts +++ b/src/providers/sources/vidsrc/scrape-movie.ts @@ -1,7 +1,7 @@ -import { MovieContext } from '@/providers/sources/vidsrc/common'; import { getVidSrcMovieSources } from '@/providers/sources/vidsrc/scrape'; +import { MovieScrapeContext } from '@/utils/context'; -export async function scrapeMovie(ctx: MovieContext) { +export async function scrapeMovie(ctx: MovieScrapeContext) { return { embeds: await getVidSrcMovieSources(ctx), }; diff --git a/src/providers/sources/vidsrc/scrape-show.ts b/src/providers/sources/vidsrc/scrape-show.ts index 7214332..ff5d2a4 100644 --- a/src/providers/sources/vidsrc/scrape-show.ts +++ b/src/providers/sources/vidsrc/scrape-show.ts @@ -1,7 +1,7 @@ -import { ShowContext } from '@/providers/sources/vidsrc/common'; import { getVidSrcShowSources } from '@/providers/sources/vidsrc/scrape'; +import { ShowScrapeContext } from '@/utils/context'; -export async function scrapeShow(ctx: ShowContext) { +export async function scrapeShow(ctx: ShowScrapeContext) { return { embeds: await getVidSrcShowSources(ctx), }; diff --git a/src/providers/sources/vidsrc/scrape.ts b/src/providers/sources/vidsrc/scrape.ts index a8aa4fc..6ea5256 100644 --- a/src/providers/sources/vidsrc/scrape.ts +++ b/src/providers/sources/vidsrc/scrape.ts @@ -1,9 +1,11 @@ import { load } from 'cheerio'; import { FetchReply } from '@/fetchers/fetch'; +import { SourcererEmbed } from '@/providers/base'; import { streambucketScraper } from '@/providers/embeds/streambucket'; import { vidsrcembedScraper } from '@/providers/embeds/vidsrc'; -import { MovieContext, ShowContext, vidsrcBase, vidsrcRCPBase } from '@/providers/sources/vidsrc/common'; +import { vidsrcBase, vidsrcRCPBase } from '@/providers/sources/vidsrc/common'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; function decodeSrc(encoded: string, seed: string) { const encodedBuffer = Buffer.from(encoded, 'hex'); @@ -16,7 +18,7 @@ function decodeSrc(encoded: string, seed: string) { return decoded; } -async function getVidSrcEmbeds(ctx: MovieContext | ShowContext, startingURL: string) { +async function getVidSrcEmbeds(ctx: MovieScrapeContext | ShowScrapeContext, startingURL: string) { // VidSrc works by using hashes and a redirect system. // The hashes are stored in the html, and VidSrc will // make requests to their servers with the hash. This @@ -25,11 +27,7 @@ async function getVidSrcEmbeds(ctx: MovieContext | ShowContext, startingURL: str // real embed links, we must do the same. Slow, but // required - const embeds: { - embedId: string; - url: string; - headers?: Record; - }[] = []; + const embeds: SourcererEmbed[] = []; let html = await ctx.proxiedFetcher(startingURL, { baseUrl: vidsrcBase, @@ -37,7 +35,7 @@ async function getVidSrcEmbeds(ctx: MovieContext | ShowContext, startingURL: str let $ = load(html); - const sourceHashes = $('.source[data-hash]') + const sourceHashes = $('.server[data-hash]') .toArray() .map((el) => $(el).attr('data-hash')) .filter((hash) => hash !== undefined); @@ -76,11 +74,7 @@ async function getVidSrcEmbeds(ctx: MovieContext | ShowContext, startingURL: str }, }); - const embed: { - embedId: string; - url: string; - headers?: Record; - } = { + const embed: SourcererEmbed = { embedId: '', url: embedURL, }; @@ -117,11 +111,11 @@ async function getVidSrcEmbeds(ctx: MovieContext | ShowContext, startingURL: str return embeds; } -export async function getVidSrcMovieSources(ctx: MovieContext) { +export async function getVidSrcMovieSources(ctx: MovieScrapeContext) { return getVidSrcEmbeds(ctx, `/embed/${ctx.media.tmdbId}`); } -export async function getVidSrcShowSources(ctx: ShowContext) { +export async function getVidSrcShowSources(ctx: ShowScrapeContext) { // VidSrc will always default to season 1 episode 1 // no matter what embed URL is used. It sends back // a list of ALL the shows episodes, in order, for From 65bbf28442d040474b7c77cf7c3dc00fa383c0a3 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Tue, 26 Dec 2023 23:16:26 +0100 Subject: [PATCH 03/15] merge with dev --- README.md | 1 - src/fetchers/common.ts | 1 - src/fetchers/standardFetch.ts | 4 ---- src/fetchers/types.ts | 2 -- src/providers/embeds/streambucket.ts | 17 ++++++++++------- src/providers/embeds/vidsrc.ts | 23 +++++++++++++---------- src/providers/sources/vidsrc/index.ts | 4 ++-- src/providers/sources/vidsrc/scrape.ts | 24 +++++++----------------- src/utils/context.ts | 10 ++++++++-- 9 files changed, 40 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 2b40e6e..8b7633c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ The following CLI Mode arguments are available | `--season` | `-s` | Season number. Only used if type is `show` | `0` | | `--episode` | `-e` | Episode number. Only used if type is `show` | `0` | | `--url` | `-u` | URL to a video embed. Only used if source is an embed | | -| `--headers` | `-h` | Optional headers to send while scraping | | | `--help` | `-h` | Shows help for the command arguments | | Example testing the FlixHQ source on the movie "Spirited Away" diff --git a/src/fetchers/common.ts b/src/fetchers/common.ts index 90e87ce..71956ba 100644 --- a/src/fetchers/common.ts +++ b/src/fetchers/common.ts @@ -35,7 +35,6 @@ export function makeFetcher(fetcher: Fetcher): UseableFetcher { baseUrl: ops?.baseUrl ?? '', readHeaders: ops?.readHeaders ?? [], body: ops?.body, - returnRaw: ops?.returnRaw ?? false, }); }; const output: UseableFetcher = async (url, ops) => (await newFetcher(url, ops)).body; diff --git a/src/fetchers/standardFetch.ts b/src/fetchers/standardFetch.ts index 4eba0d0..9fb6afa 100644 --- a/src/fetchers/standardFetch.ts +++ b/src/fetchers/standardFetch.ts @@ -29,10 +29,6 @@ export function makeStandardFetcher(f: FetchLike): Fetcher { body: seralizedBody.body, }); - if (ops.returnRaw) { - return res; - } - let body: any; const isJson = res.headers.get('content-type')?.includes('application/json'); if (isJson) body = await res.json(); diff --git a/src/fetchers/types.ts b/src/fetchers/types.ts index 09640aa..8904173 100644 --- a/src/fetchers/types.ts +++ b/src/fetchers/types.ts @@ -7,7 +7,6 @@ export type FetcherOptions = { method?: 'HEAD' | 'GET' | 'POST'; readHeaders?: string[]; body?: Record | string | FormData | URLSearchParams; - returnRaw?: boolean; }; // Version of the options that always has the defaults set @@ -18,7 +17,6 @@ export type DefaultedFetcherOptions = { headers: Record; query: Record; method: 'HEAD' | 'GET' | 'POST'; - returnRaw: boolean; readHeaders: string[]; }; diff --git a/src/providers/embeds/streambucket.ts b/src/providers/embeds/streambucket.ts index f137ceb..f22ad42 100644 --- a/src/providers/embeds/streambucket.ts +++ b/src/providers/embeds/streambucket.ts @@ -1,4 +1,4 @@ -import { flags } from '@/main/targets'; +import { flags } from '@/index'; import { makeEmbed } from '@/providers/base'; // StreamBucket makes use of https://github.com/nicxlau/hunter-php-javascript-obfuscator @@ -87,12 +87,15 @@ export const streambucketScraper = makeEmbed({ } return { - stream: { - type: 'hls', - playlist: regexResult[1], - flags: [flags.NO_CORS], - captions: [], - }, + stream: [ + { + id: 'primary', + type: 'hls', + playlist: regexResult[1], + flags: [flags.CORS_ALLOWED], + captions: [], + }, + ], }; }, }); diff --git a/src/providers/embeds/vidsrc.ts b/src/providers/embeds/vidsrc.ts index da3f2aa..efd3a18 100644 --- a/src/providers/embeds/vidsrc.ts +++ b/src/providers/embeds/vidsrc.ts @@ -1,3 +1,4 @@ +import { flags } from '@/../lib'; import { makeEmbed } from '@/providers/base'; const hlsURLRegex = /file:"(.*?)"/; @@ -7,11 +8,10 @@ export const vidsrcembedScraper = makeEmbed({ name: 'VidSrc', rank: 197, async scrape(ctx) { - if (!ctx.headers || (!ctx.headers.referer && !ctx.headers.Referer)) { - throw new Error('VidSrc embeds require the referer header to be set'); - } const html = await ctx.proxiedFetcher(ctx.url, { - headers: ctx.headers, + headers: { + referer: ctx.url, + }, }); const match = html @@ -24,12 +24,15 @@ export const vidsrcembedScraper = makeEmbed({ if (!finalUrl.includes('.m3u8')) throw new Error('Unable to find HLS playlist'); return { - stream: { - type: 'hls', - playlist: finalUrl, - flags: [], - captions: [], - }, + stream: [ + { + id: 'primary', + type: 'hls', + playlist: finalUrl, + flags: [flags.CORS_ALLOWED], + captions: [], + }, + ], }; }, }); diff --git a/src/providers/sources/vidsrc/index.ts b/src/providers/sources/vidsrc/index.ts index f7ad792..6331a05 100644 --- a/src/providers/sources/vidsrc/index.ts +++ b/src/providers/sources/vidsrc/index.ts @@ -1,4 +1,4 @@ -import { flags } from '@/main/targets'; +import { flags } from '@/entrypoint/utils/targets'; import { makeSourcerer } from '@/providers/base'; import { scrapeMovie } from '@/providers/sources/vidsrc/scrape-movie'; import { scrapeShow } from '@/providers/sources/vidsrc/scrape-show'; @@ -7,7 +7,7 @@ export const vidsrcScraper = makeSourcerer({ id: 'vidsrc', name: 'VidSrc', rank: 120, - flags: [flags.NO_CORS], + flags: [flags.CORS_ALLOWED], scrapeMovie, scrapeShow, }); diff --git a/src/providers/sources/vidsrc/scrape.ts b/src/providers/sources/vidsrc/scrape.ts index 6ea5256..65a709c 100644 --- a/src/providers/sources/vidsrc/scrape.ts +++ b/src/providers/sources/vidsrc/scrape.ts @@ -1,6 +1,5 @@ import { load } from 'cheerio'; -import { FetchReply } from '@/fetchers/fetch'; import { SourcererEmbed } from '@/providers/base'; import { streambucketScraper } from '@/providers/embeds/streambucket'; import { vidsrcembedScraper } from '@/providers/embeds/vidsrc'; @@ -44,7 +43,7 @@ async function getVidSrcEmbeds(ctx: MovieScrapeContext | ShowScrapeContext, star html = await ctx.proxiedFetcher(`/rcp/${hash}`, { baseUrl: vidsrcRCPBase, headers: { - referer: `${vidsrcBase}${startingURL}`, + referer: vidsrcBase, }, }); @@ -61,32 +60,23 @@ async function getVidSrcEmbeds(ctx: MovieScrapeContext | ShowScrapeContext, star redirectURL = `https:${redirectURL}`; } - // Return the raw fetch response here. - // When a Location header is sent, fetch - // will silently follow it. The "url" inside - // the Response is the final requested URL, - // which is the real embeds URL - const { url: embedURL } = await ctx.proxiedFetcher(redirectURL, { - returnRaw: true, - method: 'HEAD', // We don't care about the actual response body here + const { finalUrl } = await ctx.proxiedFetcher.full(redirectURL, { + method: 'HEAD', headers: { - referer: `${vidsrcRCPBase}/rcp/${hash}`, + referer: vidsrcBase, }, }); const embed: SourcererEmbed = { embedId: '', - url: embedURL, + url: finalUrl, }; - const parsedUrl = new URL(embedURL); + const parsedUrl = new URL(finalUrl); switch (parsedUrl.host) { case 'vidsrc.stream': embed.embedId = vidsrcembedScraper.id; - embed.headers = { - referer: `${vidsrcRCPBase}/rcp/${hash}`, - }; break; case 'streambucket.net': embed.embedId = streambucketScraper.id; @@ -99,7 +89,7 @@ async function getVidSrcEmbeds(ctx: MovieScrapeContext | ShowScrapeContext, star // Just ignore this. This embed streams video over a custom WebSocket connection break; default: - throw new Error(`Failed to find VidSrc embed source for ${embedURL}`); + throw new Error(`Failed to find VidSrc embed source for ${finalUrl}`); } // Since some embeds are ignored on purpose, check if a valid one was found diff --git a/src/utils/context.ts b/src/utils/context.ts index cd11f21..f7c005e 100644 --- a/src/utils/context.ts +++ b/src/utils/context.ts @@ -2,8 +2,14 @@ import { MovieMedia, ShowMedia } from '@/entrypoint/utils/media'; import { UseableFetcher } from '@/fetchers/types'; export type ScrapeContext = { - proxiedFetcher: (...params: Parameters>) => ReturnType>; - fetcher: (...params: Parameters>) => ReturnType>; + proxiedFetcher: { + (...params: Parameters>): ReturnType>; + full(...params: Parameters['full']>): ReturnType['full']>; + }; + fetcher: { + (...params: Parameters>): ReturnType>; + full(...params: Parameters['full']>): ReturnType['full']>; + }; progress(val: number): void; }; From 2117b417f4b225885cec796de55e5b97934eca6d Mon Sep 17 00:00:00 2001 From: Jorrin Date: Tue, 26 Dec 2023 23:23:20 +0100 Subject: [PATCH 04/15] fix ci --- src/providers/embeds/streambucket.ts | 2 +- src/providers/embeds/vidsrc.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/providers/embeds/streambucket.ts b/src/providers/embeds/streambucket.ts index f22ad42..9e21a93 100644 --- a/src/providers/embeds/streambucket.ts +++ b/src/providers/embeds/streambucket.ts @@ -1,4 +1,4 @@ -import { flags } from '@/index'; +import { flags } from '@/entrypoint/utils/targets'; import { makeEmbed } from '@/providers/base'; // StreamBucket makes use of https://github.com/nicxlau/hunter-php-javascript-obfuscator diff --git a/src/providers/embeds/vidsrc.ts b/src/providers/embeds/vidsrc.ts index efd3a18..d9eafb3 100644 --- a/src/providers/embeds/vidsrc.ts +++ b/src/providers/embeds/vidsrc.ts @@ -1,4 +1,4 @@ -import { flags } from '@/../lib'; +import { flags } from '@/entrypoint/utils/targets'; import { makeEmbed } from '@/providers/base'; const hlsURLRegex = /file:"(.*?)"/; From 06acec4675fca367a8496a79632850caada4beab Mon Sep 17 00:00:00 2001 From: Jorrin Date: Wed, 27 Dec 2023 00:21:13 +0100 Subject: [PATCH 05/15] remove references of embed headers --- src/entrypoint/controls.ts | 3 --- src/fetchers/types.ts | 2 +- src/providers/base.ts | 1 - src/runners/individualRunner.ts | 2 -- src/utils/context.ts | 1 - 5 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/entrypoint/controls.ts b/src/entrypoint/controls.ts index 2ac7898..5ff400b 100644 --- a/src/entrypoint/controls.ts +++ b/src/entrypoint/controls.ts @@ -52,9 +52,6 @@ export interface EmbedRunnerOptions { // id of the embed scraper you want to scrape from id: string; - - // optional headers for the embed scraper to use - headers?: Record; } export interface ProviderControls { diff --git a/src/fetchers/types.ts b/src/fetchers/types.ts index 8904173..d542298 100644 --- a/src/fetchers/types.ts +++ b/src/fetchers/types.ts @@ -16,8 +16,8 @@ export type DefaultedFetcherOptions = { body?: Record | string | FormData; headers: Record; query: Record; - method: 'HEAD' | 'GET' | 'POST'; readHeaders: string[]; + method: 'HEAD' | 'GET' | 'POST'; }; export type FetcherResponse = { diff --git a/src/providers/base.ts b/src/providers/base.ts index 1625b50..0d43895 100644 --- a/src/providers/base.ts +++ b/src/providers/base.ts @@ -7,7 +7,6 @@ export type MediaScraperTypes = 'show' | 'movie'; export type SourcererEmbed = { embedId: string; url: string; - headers?: Record; }; export type SourcererOutput = { diff --git a/src/runners/individualRunner.ts b/src/runners/individualRunner.ts index 43730c7..2befd84 100644 --- a/src/runners/individualRunner.ts +++ b/src/runners/individualRunner.ts @@ -70,7 +70,6 @@ export type IndividualEmbedRunnerOptions = { url: string; id: string; events?: IndividualScraperEvents; - headers?: Record; }; export async function scrapeIndividualEmbed( @@ -84,7 +83,6 @@ export async function scrapeIndividualEmbed( fetcher: ops.fetcher, proxiedFetcher: ops.proxiedFetcher, url: ops.url, - headers: ops.headers, progress(val) { ops.events?.update?.({ id: embedScraper.id, diff --git a/src/utils/context.ts b/src/utils/context.ts index f7c005e..cf7acc7 100644 --- a/src/utils/context.ts +++ b/src/utils/context.ts @@ -15,7 +15,6 @@ export type ScrapeContext = { export type EmbedInput = { url: string; - headers?: Record; }; export type EmbedScrapeContext = EmbedInput & ScrapeContext; From c9a611d6b765478ec81dadf956a452fb4aed7ca4 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 27 Dec 2023 22:27:37 +0100 Subject: [PATCH 06/15] Add console logs to browser dev cli --- src/dev-cli/scraper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dev-cli/scraper.ts b/src/dev-cli/scraper.ts index 882d321..39f75f6 100644 --- a/src/dev-cli/scraper.ts +++ b/src/dev-cli/scraper.ts @@ -41,6 +41,7 @@ async function runBrowserScraping( args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); + page.on('console', (message) => console.log(`${message.type().slice(0, 3).toUpperCase()} ${message.text()}`)); await page.goto(server.resolvedUrls.local[0]); await page.waitForFunction('!!window.scrape', { timeout: 5000 }); From fb3f2378087f8e414f4458c8f10f1eac8f1d52f8 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 27 Dec 2023 22:33:13 +0100 Subject: [PATCH 07/15] Better types for fetchers --- src/fetchers/types.ts | 10 +++++----- src/utils/context.ts | 10 ++-------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/fetchers/types.ts b/src/fetchers/types.ts index d542298..f5dbe06 100644 --- a/src/fetchers/types.ts +++ b/src/fetchers/types.ts @@ -28,12 +28,12 @@ export type FetcherResponse = { }; // This is the version that will be inputted by library users -export type Fetcher = { - (url: string, ops: DefaultedFetcherOptions): Promise>; +export type Fetcher = { + (url: string, ops: DefaultedFetcherOptions): Promise>; }; // This is the version that scrapers will be interacting with -export type UseableFetcher = { - (url: string, ops?: FetcherOptions): Promise; - full: (url: string, ops?: FetcherOptions) => Promise>; +export type UseableFetcher = { + (url: string, ops?: FetcherOptions): Promise; + full: (url: string, ops?: FetcherOptions) => Promise>; }; diff --git a/src/utils/context.ts b/src/utils/context.ts index cf7acc7..6f16bca 100644 --- a/src/utils/context.ts +++ b/src/utils/context.ts @@ -2,14 +2,8 @@ import { MovieMedia, ShowMedia } from '@/entrypoint/utils/media'; import { UseableFetcher } from '@/fetchers/types'; export type ScrapeContext = { - proxiedFetcher: { - (...params: Parameters>): ReturnType>; - full(...params: Parameters['full']>): ReturnType['full']>; - }; - fetcher: { - (...params: Parameters>): ReturnType>; - full(...params: Parameters['full']>): ReturnType['full']>; - }; + proxiedFetcher: UseableFetcher; + fetcher: UseableFetcher; progress(val: number): void; }; From 98230470f14ed6fa4fcb3797e484cfb6607a93f6 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 2 Jan 2024 20:12:28 +0100 Subject: [PATCH 08/15] remove febbox HLS, it doesnt work --- src/entrypoint/providers.ts | 4 ++-- src/providers/embeds/febbox/hls.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/entrypoint/providers.ts b/src/entrypoint/providers.ts index b306417..e456eb0 100644 --- a/src/entrypoint/providers.ts +++ b/src/entrypoint/providers.ts @@ -2,9 +2,9 @@ import { gatherAllEmbeds, gatherAllSources } from '@/providers/all'; import { Embed, Sourcerer } from '@/providers/base'; export function getBuiltinSources(): Sourcerer[] { - return gatherAllSources(); + return gatherAllSources().filter((v) => !v.disabled); } export function getBuiltinEmbeds(): Embed[] { - return gatherAllEmbeds(); + return gatherAllEmbeds().filter((v) => !v.disabled); } diff --git a/src/providers/embeds/febbox/hls.ts b/src/providers/embeds/febbox/hls.ts index 2a3a03f..792c112 100644 --- a/src/providers/embeds/febbox/hls.ts +++ b/src/providers/embeds/febbox/hls.ts @@ -15,6 +15,7 @@ export const febboxHlsScraper = makeEmbed({ id: 'febbox-hls', name: 'Febbox (HLS)', rank: 160, + disabled: true, async scrape(ctx) { const { type, id, season, episode } = parseInputUrl(ctx.url); const sharelinkResult = await ctx.proxiedFetcher<{ From 10eb0cc8f6e319366b56ebe63930ecfb24040def Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 2 Jan 2024 20:21:55 +0100 Subject: [PATCH 09/15] Update codeowner file --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d0f0ca6..0ccc218 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ -* @movie-web/core +* @movie-web/project-leads .github @binaryoverload From 7f4e412b9d28493abee46d737a040df505a172c3 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 2 Jan 2024 20:22:06 +0100 Subject: [PATCH 10/15] Remove binary --- .github/CODEOWNERS | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0ccc218..7458772 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1 @@ * @movie-web/project-leads - -.github @binaryoverload From 7dfeeb270092672d6a1ad99acb6e1e8a37c0058b Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 2 Jan 2024 20:33:43 +0100 Subject: [PATCH 11/15] Fix CLI not working for ip locked sources + disable lookmovie due to bug --- src/dev-cli/validate.ts | 1 + src/providers/sources/lookmovie/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/dev-cli/validate.ts b/src/dev-cli/validate.ts index b600454..dd1f638 100644 --- a/src/dev-cli/validate.ts +++ b/src/dev-cli/validate.ts @@ -81,6 +81,7 @@ export async function processOptions(sources: Array, options: const providerOptions: ProviderMakerOptions = { fetcher, target: targets.ANY, + consistentIpForRequests: true, }; return { diff --git a/src/providers/sources/lookmovie/index.ts b/src/providers/sources/lookmovie/index.ts index 8611373..73226dc 100644 --- a/src/providers/sources/lookmovie/index.ts +++ b/src/providers/sources/lookmovie/index.ts @@ -33,6 +33,7 @@ export const lookmovieScraper = makeSourcerer({ id: 'lookmovie', name: 'LookMovie', rank: 1, + disabled: true, flags: [flags.IP_LOCKED], scrapeShow: universalScraper, scrapeMovie: universalScraper, From cdb59c604693bb05f997f27806560fdd9d3c93e7 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 2 Jan 2024 22:29:14 +0100 Subject: [PATCH 12/15] Fix sujtasdfASDFG --- src/providers/sources/showbox/index.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/providers/sources/showbox/index.ts b/src/providers/sources/showbox/index.ts index 267a6ef..d6c4887 100644 --- a/src/providers/sources/showbox/index.ts +++ b/src/providers/sources/showbox/index.ts @@ -1,6 +1,5 @@ import { flags } from '@/entrypoint/utils/targets'; import { SourcererOutput, makeSourcerer } from '@/providers/base'; -import { febboxHlsScraper } from '@/providers/embeds/febbox/hls'; import { febboxMp4Scraper } from '@/providers/embeds/febbox/mp4'; import { compareTitle } from '@/utils/compare'; import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; @@ -31,10 +30,6 @@ async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promis return { embeds: [ - { - embedId: febboxHlsScraper.id, - url: `/${ctx.media.type}/${id}/${season}/${episode}`, - }, { embedId: febboxMp4Scraper.id, url: `/${ctx.media.type}/${id}/${season}/${episode}`, From 0d36d51dd4a173525f68fc0257304ad6e659d0bc Mon Sep 17 00:00:00 2001 From: mrjvs Date: Tue, 2 Jan 2024 22:31:08 +0100 Subject: [PATCH 13/15] asdfua --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 890aa50..f7d1e18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@movie-web/providers", - "version": "2.0.2", + "version": "2.0.3", "description": "Package that contains all the providers of movie-web", "main": "./lib/index.umd.js", "types": "./lib/index.d.ts", From 7b5c53e6bd051e09aad9d1666fb631d9527eb45a Mon Sep 17 00:00:00 2001 From: Jorrin Date: Thu, 4 Jan 2024 22:35:02 +0100 Subject: [PATCH 14/15] browser compatible decodeSrc function --- src/providers/sources/vidsrc/scrape.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/providers/sources/vidsrc/scrape.ts b/src/providers/sources/vidsrc/scrape.ts index 65a709c..81dceff 100644 --- a/src/providers/sources/vidsrc/scrape.ts +++ b/src/providers/sources/vidsrc/scrape.ts @@ -7,11 +7,13 @@ import { vidsrcBase, vidsrcRCPBase } from '@/providers/sources/vidsrc/common'; import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; function decodeSrc(encoded: string, seed: string) { - const encodedBuffer = Buffer.from(encoded, 'hex'); let decoded = ''; + const seedLength = seed.length; - for (let i = 0; i < encodedBuffer.length; i++) { - decoded += String.fromCharCode(encodedBuffer[i] ^ seed.charCodeAt(i % seed.length)); + for (let i = 0; i < encoded.length; i += 2) { + const byte = parseInt(encoded.substr(i, 2), 16); + const seedChar = seed.charCodeAt((i / 2) % seedLength); + decoded += String.fromCharCode(byte ^ seedChar); } return decoded; From fe1f8d364b3e162e73e41cdaaf451ee326f72e97 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Fri, 5 Jan 2024 14:51:54 +0100 Subject: [PATCH 15/15] reintroduce the password endpoint --- src/providers/embeds/vidsrc.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/providers/embeds/vidsrc.ts b/src/providers/embeds/vidsrc.ts index d9eafb3..cd47e21 100644 --- a/src/providers/embeds/vidsrc.ts +++ b/src/providers/embeds/vidsrc.ts @@ -2,6 +2,7 @@ import { flags } from '@/entrypoint/utils/targets'; import { makeEmbed } from '@/providers/base'; const hlsURLRegex = /file:"(.*?)"/; +const setPassRegex = /var pass_path = "(.*set_pass\.php.*)";/; export const vidsrcembedScraper = makeEmbed({ id: 'vidsrcembed', // VidSrc is both a source and an embed host @@ -23,6 +24,22 @@ export const vidsrcembedScraper = makeEmbed({ if (!finalUrl.includes('.m3u8')) throw new Error('Unable to find HLS playlist'); + let setPassLink = html.match(setPassRegex)?.[1]; + if (!setPassLink) throw new Error('Unable to find set_pass.php link'); + + if (setPassLink.startsWith('//')) { + setPassLink = `https:${setPassLink}`; + } + + // VidSrc uses a password endpoint to temporarily whitelist the user's IP. This is called in an interval by the player. + // It currently has no effect on the player itself, the content plays fine without it. + // In the future we might have to introduce hooks for the frontend to call this endpoint. + await ctx.proxiedFetcher(setPassLink, { + headers: { + referer: ctx.url, + }, + }); + return { stream: [ {