From f39aaca3e32fd11a3fca7f56711675f331592473 Mon Sep 17 00:00:00 2001 From: Jorrin Date: Wed, 27 Dec 2023 20:35:08 +0100 Subject: [PATCH] fix vidplay, add captions to filemoon --- src/providers/embeds/filemoon.ts | 34 --------------- src/providers/embeds/filemoon/index.ts | 56 +++++++++++++++++++++++++ src/providers/embeds/filemoon/types.ts | 5 +++ src/providers/embeds/vidplay/common.ts | 53 +++++++++++++++-------- src/providers/embeds/vidplay/index.ts | 29 +++++++++++-- src/providers/embeds/vidplay/types.ts | 26 ++++++++---- src/providers/sources/vidsrcto/index.ts | 29 +++++++++---- 7 files changed, 160 insertions(+), 72 deletions(-) delete mode 100644 src/providers/embeds/filemoon.ts create mode 100644 src/providers/embeds/filemoon/index.ts create mode 100644 src/providers/embeds/filemoon/types.ts diff --git a/src/providers/embeds/filemoon.ts b/src/providers/embeds/filemoon.ts deleted file mode 100644 index 9b092a2..0000000 --- a/src/providers/embeds/filemoon.ts +++ /dev/null @@ -1,34 +0,0 @@ -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/filemoon/index.ts b/src/providers/embeds/filemoon/index.ts new file mode 100644 index 0000000..584be7e --- /dev/null +++ b/src/providers/embeds/filemoon/index.ts @@ -0,0 +1,56 @@ +import { unpack } from 'unpacker'; + +import { flags } from '@/entrypoint/utils/targets'; + +import { SubtitleResult } from './types'; +import { makeEmbed } from '../../base'; +import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '../../captions'; + +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'); + + const url = new URL(ctx.url); + const subtitlesLink = url.searchParams.get('sub.info'); + const captions: Caption[] = []; + if (subtitlesLink) { + const captionsResult = await ctx.fetcher(subtitlesLink); + + for (const caption of captionsResult) { + const language = labelToLanguageCode(caption.label); + const captionType = getCaptionTypeFromUrl(caption.file); + if (!language || !captionType) continue; + captions.push({ + id: caption.file, + url: caption.file, + type: captionType, + language, + hasCorsRestrictions: false, + }); + } + } + + return { + stream: [ + { + id: 'primary', + type: 'hls', + playlist: file[1], + flags: [flags.CORS_ALLOWED], + captions, + }, + ], + }; + }, +}); diff --git a/src/providers/embeds/filemoon/types.ts b/src/providers/embeds/filemoon/types.ts new file mode 100644 index 0000000..caa27af --- /dev/null +++ b/src/providers/embeds/filemoon/types.ts @@ -0,0 +1,5 @@ +export type SubtitleResult = { + file: string; + label: string; + kind: string; +}[]; diff --git a/src/providers/embeds/vidplay/common.ts b/src/providers/embeds/vidplay/common.ts index 9fbc8d2..cf2eb0f 100644 --- a/src/providers/embeds/vidplay/common.ts +++ b/src/providers/embeds/vidplay/common.ts @@ -1,10 +1,34 @@ -import { createCipheriv } from 'crypto'; -import { Buffer } from 'node:buffer'; - import { EmbedScrapeContext } from '@/utils/context'; export const vidplayBase = 'https://vidplay.site'; +export function keyPermutation(key: string, data: any) { + const state = Array.from(Array(256).keys()); + let index1 = 0; + for (let i = 0; i < 256; i += 1) { + index1 = (index1 + state[i] + key.charCodeAt(i % key.length)) % 256; + const temp = state[i]; + state[i] = state[index1]; + state[index1] = temp; + } + index1 = 0; + let index2 = 0; + let finalKey = ''; + for (let char = 0; char < data.length; char += 1) { + index1 = (index1 + 1) % 256; + index2 = (index2 + state[index1]) % 256; + const temp = state[index1]; + state[index1] = state[index2]; + state[index2] = temp; + if (typeof data[char] === 'string') { + finalKey += String.fromCharCode(data[char].charCodeAt(0) ^ state[(state[index1] + state[index2]) % 256]); + } else if (typeof data[char] === 'number') { + finalKey += String.fromCharCode(data[char] ^ state[(state[index1] + state[index2]) % 256]); + } + } + return finalKey; +} + export const getDecryptionKeys = async (ctx: EmbedScrapeContext): Promise => { const res = await ctx.fetcher( 'https://raw.githubusercontent.com/Claudemirovsky/worstsource-keys/keys/keys.json', @@ -12,23 +36,19 @@ export const getDecryptionKeys = async (ctx: EmbedScrapeContext): Promise { - 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]), ''); + const url = new URL(ctx.url); + const id = url.pathname.replace('/e/', ''); + const keyList = await getDecryptionKeys(ctx); - 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('/', '_'); + const decodedId = keyPermutation(keyList[0], id); + const encodedResult = keyPermutation(keyList[1], decodedId); + const base64 = btoa(encodedResult); + return 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: { @@ -36,18 +56,15 @@ export const getFuTokenKey = async (ctx: EmbedScrapeContext) => { }, }); 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]}`; + return `${vidplayBase}/mediainfo/${fuToken}${new URL(ctx.url).search}&autostart=true`; }; diff --git a/src/providers/embeds/vidplay/index.ts b/src/providers/embeds/vidplay/index.ts index 93243e5..425d583 100644 --- a/src/providers/embeds/vidplay/index.ts +++ b/src/providers/embeds/vidplay/index.ts @@ -1,7 +1,8 @@ import { makeEmbed } from '@/providers/base'; +import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '@/providers/captions'; import { getFileUrl } from './common'; -import { VidplaySourceResponse } from './types'; +import { SubtitleResult, VidplaySourceResponse } from './types'; export const vidplayScraper = makeEmbed({ id: 'vidplay', @@ -9,14 +10,34 @@ export const vidplayScraper = makeEmbed({ rank: 499, scrape: async (ctx) => { const fileUrl = await getFileUrl(ctx); - console.log(fileUrl); - const fileUrlRes = await ctx.proxiedFetcher(`${fileUrl}&autostart=true`, { + const fileUrlRes = await ctx.proxiedFetcher(fileUrl, { headers: { referer: ctx.url, }, }); + if (typeof fileUrlRes.result === 'number') throw new Error('File not found'); const source = fileUrlRes.result.sources[0].file; + const url = new URL(ctx.url); + const subtitlesLink = url.searchParams.get('sub.info'); + const captions: Caption[] = []; + if (subtitlesLink) { + const captionsResult = await ctx.fetcher(subtitlesLink); + + for (const caption of captionsResult) { + const language = labelToLanguageCode(caption.label); + const captionType = getCaptionTypeFromUrl(caption.file); + if (!language || !captionType) continue; + captions.push({ + id: caption.file, + url: caption.file, + type: captionType, + language, + hasCorsRestrictions: false, + }); + } + } + return { stream: [ { @@ -24,7 +45,7 @@ export const vidplayScraper = makeEmbed({ type: 'hls', playlist: source, flags: [], - captions: [], + captions, }, ], }; diff --git a/src/providers/embeds/vidplay/types.ts b/src/providers/embeds/vidplay/types.ts index 14c33a8..29cde1d 100644 --- a/src/providers/embeds/vidplay/types.ts +++ b/src/providers/embeds/vidplay/types.ts @@ -1,11 +1,19 @@ export type VidplaySourceResponse = { - result: { - sources: { - file: string; - tracks: { - file: string; - kind: string; - }[]; - }[]; - }; + result: + | { + sources: { + file: string; + tracks: { + file: string; + kind: string; + }[]; + }[]; + } + | number; }; + +export type SubtitleResult = { + file: string; + label: string; + kind: string; +}[]; diff --git a/src/providers/sources/vidsrcto/index.ts b/src/providers/sources/vidsrcto/index.ts index eec22d4..8b43e47 100644 --- a/src/providers/sources/vidsrcto/index.ts +++ b/src/providers/sources/vidsrcto/index.ts @@ -25,24 +25,39 @@ const universalScraper = async (ctx: ShowScrapeContext | MovieScrapeContext): Pr if (sources.status !== 200) throw new Error('No sources found'); const embeds: SourcererEmbed[] = []; + const embedUrls = []; 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, - }); - } + embedUrls.push(decryptedUrl); + } + + // Originally Filemoon does not have subtitles. But we can use the ones from Vidplay. + const subtitleUrl = new URL(embedUrls.find((v) => v.includes('sub.info')) ?? '').searchParams.get('sub.info'); + for (const source of sources.result) { if (source.title === 'Vidplay') { + const embedUrl = embedUrls.find((v) => v.includes('vidplay')); + if (!embedUrl) continue; embeds.push({ embedId: 'vidplay', - url: decryptedUrl, + url: embedUrl, + }); + } + + if (source.title === 'Filemoon') { + const embedUrl = embedUrls.find((v) => v.includes('filemoon')); + if (!embedUrl) continue; + const fullUrl = new URL(embedUrl); + if (subtitleUrl) fullUrl.searchParams.set('sub.info', subtitleUrl); + embeds.push({ + embedId: 'filemoon', + url: fullUrl.toString(), }); } } + return { embeds, };