fix vidplay, add captions to filemoon

This commit is contained in:
Jorrin 2023-12-27 20:35:08 +01:00
parent 8b7e840814
commit f39aaca3e3
7 changed files with 160 additions and 72 deletions

View File

@ -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<string>(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: [],
},
],
};
},
});

View File

@ -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<string>(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<SubtitleResult>(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,
},
],
};
},
});

View File

@ -0,0 +1,5 @@
export type SubtitleResult = {
file: string;
label: string;
kind: string;
}[];

View File

@ -1,10 +1,34 @@
import { createCipheriv } from 'crypto';
import { Buffer } from 'node:buffer';
import { EmbedScrapeContext } from '@/utils/context'; import { EmbedScrapeContext } from '@/utils/context';
export const vidplayBase = 'https://vidplay.site'; 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<string[]> => { export const getDecryptionKeys = async (ctx: EmbedScrapeContext): Promise<string[]> => {
const res = await ctx.fetcher<string>( const res = await ctx.fetcher<string>(
'https://raw.githubusercontent.com/Claudemirovsky/worstsource-keys/keys/keys.json', 'https://raw.githubusercontent.com/Claudemirovsky/worstsource-keys/keys/keys.json',
@ -12,23 +36,19 @@ export const getDecryptionKeys = async (ctx: EmbedScrapeContext): Promise<string
return JSON.parse(res); return JSON.parse(res);
}; };
// TODO: Fix this so its cross platform compatible
export const getEncodedId = async (ctx: EmbedScrapeContext) => { export const getEncodedId = async (ctx: EmbedScrapeContext) => {
const id = ctx.url.split('/e/')[1].split('?')[0]; const url = new URL(ctx.url);
const keys = await getDecryptionKeys(ctx); const id = url.pathname.replace('/e/', '');
const c1 = createCipheriv('rc4', Buffer.from(keys[0]), ''); const keyList = await getDecryptionKeys(ctx);
const c2 = createCipheriv('rc4', Buffer.from(keys[1]), '');
let input = Buffer.from(id); const decodedId = keyPermutation(keyList[0], id);
input = Buffer.concat([c1.update(input), c1.final()]); const encodedResult = keyPermutation(keyList[1], decodedId);
input = Buffer.concat([c2.update(input), c2.final()]); const base64 = btoa(encodedResult);
return base64.replace('/', '_');
return input.toString('base64').replace('/', '_');
}; };
export const getFuTokenKey = async (ctx: EmbedScrapeContext) => { export const getFuTokenKey = async (ctx: EmbedScrapeContext) => {
const id = await getEncodedId(ctx); const id = await getEncodedId(ctx);
console.log(`ENCODED ID: ${id}`);
const fuTokenRes = await ctx.proxiedFetcher<string>('/futoken', { const fuTokenRes = await ctx.proxiedFetcher<string>('/futoken', {
baseUrl: vidplayBase, baseUrl: vidplayBase,
headers: { headers: {
@ -36,18 +56,15 @@ export const getFuTokenKey = async (ctx: EmbedScrapeContext) => {
}, },
}); });
const fuKey = fuTokenRes.match(/var\s+k\s*=\s*'([^']+)'/)?.[1]; const fuKey = fuTokenRes.match(/var\s+k\s*=\s*'([^']+)'/)?.[1];
console.log(`FU KEY: ${fuKey}`);
if (!fuKey) throw new Error('No fuKey found'); if (!fuKey) throw new Error('No fuKey found');
const tokens = []; const tokens = [];
for (let i = 0; i < id.length; i += 1) { for (let i = 0; i < id.length; i += 1) {
tokens.push(fuKey.charCodeAt(i % fuKey.length) + id.charCodeAt(i)); tokens.push(fuKey.charCodeAt(i % fuKey.length) + id.charCodeAt(i));
} }
console.log(`${fuKey},${tokens.join(',')}`);
return `${fuKey},${tokens.join(',')}`; return `${fuKey},${tokens.join(',')}`;
}; };
export const getFileUrl = async (ctx: EmbedScrapeContext) => { export const getFileUrl = async (ctx: EmbedScrapeContext) => {
console.log(ctx.url);
const fuToken = await getFuTokenKey(ctx); const fuToken = await getFuTokenKey(ctx);
return `${vidplayBase}/mediainfo/${fuToken}?${ctx.url.split('?')[1]}`; return `${vidplayBase}/mediainfo/${fuToken}${new URL(ctx.url).search}&autostart=true`;
}; };

View File

@ -1,7 +1,8 @@
import { makeEmbed } from '@/providers/base'; import { makeEmbed } from '@/providers/base';
import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '@/providers/captions';
import { getFileUrl } from './common'; import { getFileUrl } from './common';
import { VidplaySourceResponse } from './types'; import { SubtitleResult, VidplaySourceResponse } from './types';
export const vidplayScraper = makeEmbed({ export const vidplayScraper = makeEmbed({
id: 'vidplay', id: 'vidplay',
@ -9,14 +10,34 @@ export const vidplayScraper = makeEmbed({
rank: 499, rank: 499,
scrape: async (ctx) => { scrape: async (ctx) => {
const fileUrl = await getFileUrl(ctx); const fileUrl = await getFileUrl(ctx);
console.log(fileUrl); const fileUrlRes = await ctx.proxiedFetcher<VidplaySourceResponse>(fileUrl, {
const fileUrlRes = await ctx.proxiedFetcher<VidplaySourceResponse>(`${fileUrl}&autostart=true`, {
headers: { headers: {
referer: ctx.url, referer: ctx.url,
}, },
}); });
if (typeof fileUrlRes.result === 'number') throw new Error('File not found');
const source = fileUrlRes.result.sources[0].file; 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<SubtitleResult>(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 { return {
stream: [ stream: [
{ {
@ -24,7 +45,7 @@ export const vidplayScraper = makeEmbed({
type: 'hls', type: 'hls',
playlist: source, playlist: source,
flags: [], flags: [],
captions: [], captions,
}, },
], ],
}; };

View File

@ -1,11 +1,19 @@
export type VidplaySourceResponse = { export type VidplaySourceResponse = {
result: { result:
sources: { | {
file: string; sources: {
tracks: { file: string;
file: string; tracks: {
kind: string; file: string;
}[]; kind: string;
}[]; }[];
}; }[];
}
| number;
}; };
export type SubtitleResult = {
file: string;
label: string;
kind: string;
}[];

View File

@ -25,24 +25,39 @@ const universalScraper = async (ctx: ShowScrapeContext | MovieScrapeContext): Pr
if (sources.status !== 200) throw new Error('No sources found'); if (sources.status !== 200) throw new Error('No sources found');
const embeds: SourcererEmbed[] = []; const embeds: SourcererEmbed[] = [];
const embedUrls = [];
for (const source of sources.result) { for (const source of sources.result) {
const sourceRes = await ctx.fetcher<SourceResult>(`/ajax/embed/source/${source.id}`, { const sourceRes = await ctx.fetcher<SourceResult>(`/ajax/embed/source/${source.id}`, {
baseUrl: vidSrcToBase, baseUrl: vidSrcToBase,
}); });
const decryptedUrl = decryptSourceUrl(sourceRes.result.url); const decryptedUrl = decryptSourceUrl(sourceRes.result.url);
if (source.title === 'Filemoon') { embedUrls.push(decryptedUrl);
embeds.push({ }
embedId: 'filemoon',
url: 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') { if (source.title === 'Vidplay') {
const embedUrl = embedUrls.find((v) => v.includes('vidplay'));
if (!embedUrl) continue;
embeds.push({ embeds.push({
embedId: 'vidplay', 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 { return {
embeds, embeds,
}; };