From 8b7e840814a5322eb90470ae49432036f30fb566 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Wed, 27 Dec 2023 02:03:53 +0100 Subject: [PATCH] vidsrcto draft --- .eslintrc.js | 1 + src/dev-cli/tmdb.ts | 2 + src/providers/all.ts | 6 +++ src/providers/embeds/filemoon.ts | 34 ++++++++++++++ src/providers/embeds/vidplay/common.ts | 53 ++++++++++++++++++++++ src/providers/embeds/vidplay/index.ts | 32 +++++++++++++ src/providers/embeds/vidplay/types.ts | 11 +++++ src/providers/sources/vidsrcto/common.ts | 51 +++++++++++++++++++++ src/providers/sources/vidsrcto/index.ts | 58 ++++++++++++++++++++++++ src/providers/sources/vidsrcto/types.ts | 15 ++++++ 10 files changed, 263 insertions(+) create mode 100644 src/providers/embeds/filemoon.ts create mode 100644 src/providers/embeds/vidplay/common.ts create mode 100644 src/providers/embeds/vidplay/index.ts create mode 100644 src/providers/embeds/vidplay/types.ts create mode 100644 src/providers/sources/vidsrcto/common.ts create mode 100644 src/providers/sources/vidsrcto/index.ts create mode 100644 src/providers/sources/vidsrcto/types.ts diff --git a/.eslintrc.js b/.eslintrc.js index 0e7322b..2bb81f9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,6 +18,7 @@ module.exports = { }, plugins: ['@typescript-eslint', 'import', 'prettier'], rules: { + 'no-bitwise': 'off', 'no-underscore-dangle': 'off', '@typescript-eslint/no-explicit-any': 'off', 'no-console': 'off', diff --git a/src/dev-cli/tmdb.ts b/src/dev-cli/tmdb.ts index 7490336..90e87f8 100644 --- a/src/dev-cli/tmdb.ts +++ b/src/dev-cli/tmdb.ts @@ -45,6 +45,7 @@ export async function getMovieMediaDetails(id: string): Promise { title: movie.title, releaseYear: Number(movie.release_date.split('-')[0]), tmdbId: id, + imdbId: movie.imdb_id, }; } @@ -91,5 +92,6 @@ export async function getShowMediaDetails(id: string, seasonNumber: string, epis number: season.season_number, tmdbId: season.id, }, + imdbId: series.imdb_id, }; } diff --git a/src/providers/all.ts b/src/providers/all.ts index d1e7885..07458b9 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -14,9 +14,12 @@ import { remotestreamScraper } from '@/providers/sources/remotestream'; import { showboxScraper } from '@/providers/sources/showbox/index'; import { zoechipScraper } from '@/providers/sources/zoechip'; +import { fileMoonScraper } from './embeds/filemoon'; import { smashyStreamDScraper } from './embeds/smashystream/dued'; import { smashyStreamFScraper } from './embeds/smashystream/video1'; +import { vidplayScraper } from './embeds/vidplay'; import { smashyStreamScraper } from './sources/smashystream'; +import { vidSrcToScraper } from './sources/vidsrcto'; export function gatherAllSources(): Array { // all sources are gathered here @@ -29,6 +32,7 @@ export function gatherAllSources(): Array { zoechipScraper, lookmovieScraper, smashyStreamScraper, + vidSrcToScraper, ]; } @@ -44,5 +48,7 @@ export function gatherAllEmbeds(): Array { mixdropScraper, smashyStreamFScraper, smashyStreamDScraper, + fileMoonScraper, + vidplayScraper, ]; } diff --git a/src/providers/embeds/filemoon.ts b/src/providers/embeds/filemoon.ts new file mode 100644 index 0000000..9b092a2 --- /dev/null +++ b/src/providers/embeds/filemoon.ts @@ -0,0 +1,34 @@ +import { unpack } from 'unpacker'; + +import { flags } from '@/entrypoint/utils/targets'; + +import { makeEmbed } from '../base'; + +const evalCodeRegex = /eval\((.*)\)/g; +const fileRegex = /file:"(.*?)"/g; + +export const fileMoonScraper = makeEmbed({ + id: 'filemoon', + name: 'FileMoon', + rank: 501, + scrape: async (ctx) => { + const embedRes = await ctx.fetcher(ctx.url); + const evalCode = evalCodeRegex.exec(embedRes); + if (!evalCode) throw new Error('Failed to find eval code'); + const unpacked = unpack(evalCode[1]); + const file = fileRegex.exec(unpacked); + if (!file?.[1]) throw new Error('Failed to find file'); + + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: file[1], + flags: [flags.CORS_ALLOWED], + captions: [], + }, + ], + }; + }, +}); diff --git a/src/providers/embeds/vidplay/common.ts b/src/providers/embeds/vidplay/common.ts new file mode 100644 index 0000000..9fbc8d2 --- /dev/null +++ b/src/providers/embeds/vidplay/common.ts @@ -0,0 +1,53 @@ +import { createCipheriv } from 'crypto'; +import { Buffer } from 'node:buffer'; + +import { EmbedScrapeContext } from '@/utils/context'; + +export const vidplayBase = 'https://vidplay.site'; + +export const getDecryptionKeys = async (ctx: EmbedScrapeContext): Promise => { + const res = await ctx.fetcher( + 'https://raw.githubusercontent.com/Claudemirovsky/worstsource-keys/keys/keys.json', + ); + return JSON.parse(res); +}; + +// TODO: Fix this so its cross platform compatible +export const getEncodedId = async (ctx: EmbedScrapeContext) => { + const id = ctx.url.split('/e/')[1].split('?')[0]; + const keys = await getDecryptionKeys(ctx); + const c1 = createCipheriv('rc4', Buffer.from(keys[0]), ''); + const c2 = createCipheriv('rc4', Buffer.from(keys[1]), ''); + + let input = Buffer.from(id); + input = Buffer.concat([c1.update(input), c1.final()]); + input = Buffer.concat([c2.update(input), c2.final()]); + + return input.toString('base64').replace('/', '_'); +}; + +export const getFuTokenKey = async (ctx: EmbedScrapeContext) => { + const id = await getEncodedId(ctx); + console.log(`ENCODED ID: ${id}`); + const fuTokenRes = await ctx.proxiedFetcher('/futoken', { + baseUrl: vidplayBase, + headers: { + referer: ctx.url, + }, + }); + const fuKey = fuTokenRes.match(/var\s+k\s*=\s*'([^']+)'/)?.[1]; + console.log(`FU KEY: ${fuKey}`); + if (!fuKey) throw new Error('No fuKey found'); + const tokens = []; + for (let i = 0; i < id.length; i += 1) { + tokens.push(fuKey.charCodeAt(i % fuKey.length) + id.charCodeAt(i)); + } + console.log(`${fuKey},${tokens.join(',')}`); + return `${fuKey},${tokens.join(',')}`; +}; + +export const getFileUrl = async (ctx: EmbedScrapeContext) => { + console.log(ctx.url); + const fuToken = await getFuTokenKey(ctx); + return `${vidplayBase}/mediainfo/${fuToken}?${ctx.url.split('?')[1]}`; +}; diff --git a/src/providers/embeds/vidplay/index.ts b/src/providers/embeds/vidplay/index.ts new file mode 100644 index 0000000..93243e5 --- /dev/null +++ b/src/providers/embeds/vidplay/index.ts @@ -0,0 +1,32 @@ +import { makeEmbed } from '@/providers/base'; + +import { getFileUrl } from './common'; +import { VidplaySourceResponse } from './types'; + +export const vidplayScraper = makeEmbed({ + id: 'vidplay', + name: 'VidPlay', + rank: 499, + scrape: async (ctx) => { + const fileUrl = await getFileUrl(ctx); + console.log(fileUrl); + const fileUrlRes = await ctx.proxiedFetcher(`${fileUrl}&autostart=true`, { + headers: { + referer: ctx.url, + }, + }); + const source = fileUrlRes.result.sources[0].file; + + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: source, + flags: [], + captions: [], + }, + ], + }; + }, +}); diff --git a/src/providers/embeds/vidplay/types.ts b/src/providers/embeds/vidplay/types.ts new file mode 100644 index 0000000..14c33a8 --- /dev/null +++ b/src/providers/embeds/vidplay/types.ts @@ -0,0 +1,11 @@ +export type VidplaySourceResponse = { + result: { + sources: { + file: string; + tracks: { + file: string; + kind: string; + }[]; + }[]; + }; +}; diff --git a/src/providers/sources/vidsrcto/common.ts b/src/providers/sources/vidsrcto/common.ts new file mode 100644 index 0000000..e31b58d --- /dev/null +++ b/src/providers/sources/vidsrcto/common.ts @@ -0,0 +1,51 @@ +const DECRYPTION_KEY = '8z5Ag5wgagfsOuhz'; + +export const decodeBase64UrlSafe = (str: string) => { + const standardizedInput = str.replace(/_/g, '/').replace(/-/g, '+'); + + const binaryData = Buffer.from(standardizedInput, 'base64').toString('binary'); + + const bytes = new Uint8Array(binaryData.length); + for (let i = 0; i < bytes.length; i += 1) { + bytes[i] = binaryData.charCodeAt(i); + } + + return bytes; +}; + +export const decode = (str: Uint8Array) => { + const keyBytes = new TextEncoder().encode(DECRYPTION_KEY); + + let j = 0; + const s = new Uint8Array(256); + for (let i = 0; i < 256; i += 1) { + s[i] = i; + } + + for (let i = 0, k = 0; i < 256; i += 1) { + j = (j + s[i] + keyBytes[k % keyBytes.length]) & 0xff; + [s[i], s[j]] = [s[j], s[i]]; + k += 1; + } + + const decoded = new Uint8Array(str.length); + let i = 0; + let k = 0; + for (let index = 0; index < str.length; index += 1) { + i = (i + 1) & 0xff; + k = (k + s[i]) & 0xff; + [s[i], s[k]] = [s[k], s[i]]; + const t = (s[i] + s[k]) & 0xff; + decoded[index] = str[index] ^ s[t]; + } + + return decoded; +}; + +export const decryptSourceUrl = (sourceUrl: string) => { + const encoded = decodeBase64UrlSafe(sourceUrl); + const decoded = decode(encoded); + const decodedText = new TextDecoder().decode(decoded); + + return decodeURIComponent(decodeURIComponent(decodedText)); +}; diff --git a/src/providers/sources/vidsrcto/index.ts b/src/providers/sources/vidsrcto/index.ts new file mode 100644 index 0000000..eec22d4 --- /dev/null +++ b/src/providers/sources/vidsrcto/index.ts @@ -0,0 +1,58 @@ +import { load } from 'cheerio'; + +import { SourcererEmbed, SourcererOutput, makeSourcerer } from '@/providers/base'; +import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; + +import { decryptSourceUrl } from './common'; +import { SourceResult, SourcesResult } from './types'; + +const vidSrcToBase = 'https://vidsrc.to'; + +const universalScraper = async (ctx: ShowScrapeContext | MovieScrapeContext): Promise => { + const imdbId = ctx.media.imdbId; + const url = + ctx.media.type === 'movie' + ? `${vidSrcToBase}/embed/movie/${imdbId}` + : `${vidSrcToBase}}/embed/tv/${imdbId}/${ctx.media.season.number}/${ctx.media.episode.number}`; + + const mainPage = await ctx.fetcher(url); + const mainPage$ = load(mainPage); + const dataId = mainPage$('a[data-id]').attr('data-id'); + if (!dataId) throw new Error('No data-id found'); + const sources = await ctx.fetcher(`/ajax/embed/episode/${dataId}/sources`, { + baseUrl: vidSrcToBase, + }); + if (sources.status !== 200) throw new Error('No sources found'); + + const embeds: SourcererEmbed[] = []; + for (const source of sources.result) { + const sourceRes = await ctx.fetcher(`/ajax/embed/source/${source.id}`, { + baseUrl: vidSrcToBase, + }); + const decryptedUrl = decryptSourceUrl(sourceRes.result.url); + if (source.title === 'Filemoon') { + embeds.push({ + embedId: 'filemoon', + url: decryptedUrl, + }); + } + if (source.title === 'Vidplay') { + embeds.push({ + embedId: 'vidplay', + url: decryptedUrl, + }); + } + } + return { + embeds, + }; +}; + +export const vidSrcToScraper = makeSourcerer({ + id: 'vidsrcto', + name: 'VidSrcTo', + scrapeMovie: universalScraper, + scrapeShow: universalScraper, + flags: [], + rank: 500, +}); diff --git a/src/providers/sources/vidsrcto/types.ts b/src/providers/sources/vidsrcto/types.ts new file mode 100644 index 0000000..0694b15 --- /dev/null +++ b/src/providers/sources/vidsrcto/types.ts @@ -0,0 +1,15 @@ +export type VidSrcToResponse = { + status: number; + result: T; +}; + +export type SourcesResult = VidSrcToResponse< + { + id: string; + title: 'Filemoon' | 'Vidplay'; + }[] +>; + +export type SourceResult = VidSrcToResponse<{ + url: string; +}>;