mirror of
https://github.com/movie-web/providers.git
synced 2025-09-13 12:43:25 +00:00
vidsrcto draft
This commit is contained in:
@@ -18,6 +18,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: ['@typescript-eslint', 'import', 'prettier'],
|
plugins: ['@typescript-eslint', 'import', 'prettier'],
|
||||||
rules: {
|
rules: {
|
||||||
|
'no-bitwise': 'off',
|
||||||
'no-underscore-dangle': 'off',
|
'no-underscore-dangle': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
'no-console': 'off',
|
'no-console': 'off',
|
||||||
|
@@ -45,6 +45,7 @@ export async function getMovieMediaDetails(id: string): Promise<MovieMedia> {
|
|||||||
title: movie.title,
|
title: movie.title,
|
||||||
releaseYear: Number(movie.release_date.split('-')[0]),
|
releaseYear: Number(movie.release_date.split('-')[0]),
|
||||||
tmdbId: id,
|
tmdbId: id,
|
||||||
|
imdbId: movie.imdb_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,5 +92,6 @@ export async function getShowMediaDetails(id: string, seasonNumber: string, epis
|
|||||||
number: season.season_number,
|
number: season.season_number,
|
||||||
tmdbId: season.id,
|
tmdbId: season.id,
|
||||||
},
|
},
|
||||||
|
imdbId: series.imdb_id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -14,9 +14,12 @@ import { remotestreamScraper } from '@/providers/sources/remotestream';
|
|||||||
import { showboxScraper } from '@/providers/sources/showbox/index';
|
import { showboxScraper } from '@/providers/sources/showbox/index';
|
||||||
import { zoechipScraper } from '@/providers/sources/zoechip';
|
import { zoechipScraper } from '@/providers/sources/zoechip';
|
||||||
|
|
||||||
|
import { fileMoonScraper } from './embeds/filemoon';
|
||||||
import { smashyStreamDScraper } from './embeds/smashystream/dued';
|
import { smashyStreamDScraper } from './embeds/smashystream/dued';
|
||||||
import { smashyStreamFScraper } from './embeds/smashystream/video1';
|
import { smashyStreamFScraper } from './embeds/smashystream/video1';
|
||||||
|
import { vidplayScraper } from './embeds/vidplay';
|
||||||
import { smashyStreamScraper } from './sources/smashystream';
|
import { smashyStreamScraper } from './sources/smashystream';
|
||||||
|
import { vidSrcToScraper } from './sources/vidsrcto';
|
||||||
|
|
||||||
export function gatherAllSources(): Array<Sourcerer> {
|
export function gatherAllSources(): Array<Sourcerer> {
|
||||||
// all sources are gathered here
|
// all sources are gathered here
|
||||||
@@ -29,6 +32,7 @@ export function gatherAllSources(): Array<Sourcerer> {
|
|||||||
zoechipScraper,
|
zoechipScraper,
|
||||||
lookmovieScraper,
|
lookmovieScraper,
|
||||||
smashyStreamScraper,
|
smashyStreamScraper,
|
||||||
|
vidSrcToScraper,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,5 +48,7 @@ export function gatherAllEmbeds(): Array<Embed> {
|
|||||||
mixdropScraper,
|
mixdropScraper,
|
||||||
smashyStreamFScraper,
|
smashyStreamFScraper,
|
||||||
smashyStreamDScraper,
|
smashyStreamDScraper,
|
||||||
|
fileMoonScraper,
|
||||||
|
vidplayScraper,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
34
src/providers/embeds/filemoon.ts
Normal file
34
src/providers/embeds/filemoon.ts
Normal file
@@ -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<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: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
53
src/providers/embeds/vidplay/common.ts
Normal file
53
src/providers/embeds/vidplay/common.ts
Normal file
@@ -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<string[]> => {
|
||||||
|
const res = await ctx.fetcher<string>(
|
||||||
|
'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<string>('/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]}`;
|
||||||
|
};
|
32
src/providers/embeds/vidplay/index.ts
Normal file
32
src/providers/embeds/vidplay/index.ts
Normal file
@@ -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<VidplaySourceResponse>(`${fileUrl}&autostart=true`, {
|
||||||
|
headers: {
|
||||||
|
referer: ctx.url,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const source = fileUrlRes.result.sources[0].file;
|
||||||
|
|
||||||
|
return {
|
||||||
|
stream: [
|
||||||
|
{
|
||||||
|
id: 'primary',
|
||||||
|
type: 'hls',
|
||||||
|
playlist: source,
|
||||||
|
flags: [],
|
||||||
|
captions: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
11
src/providers/embeds/vidplay/types.ts
Normal file
11
src/providers/embeds/vidplay/types.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
export type VidplaySourceResponse = {
|
||||||
|
result: {
|
||||||
|
sources: {
|
||||||
|
file: string;
|
||||||
|
tracks: {
|
||||||
|
file: string;
|
||||||
|
kind: string;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
51
src/providers/sources/vidsrcto/common.ts
Normal file
51
src/providers/sources/vidsrcto/common.ts
Normal file
@@ -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));
|
||||||
|
};
|
58
src/providers/sources/vidsrcto/index.ts
Normal file
58
src/providers/sources/vidsrcto/index.ts
Normal file
@@ -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<SourcererOutput> => {
|
||||||
|
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<string>(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<SourcesResult>(`/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<SourceResult>(`/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,
|
||||||
|
});
|
15
src/providers/sources/vidsrcto/types.ts
Normal file
15
src/providers/sources/vidsrcto/types.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export type VidSrcToResponse<T> = {
|
||||||
|
status: number;
|
||||||
|
result: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SourcesResult = VidSrcToResponse<
|
||||||
|
{
|
||||||
|
id: string;
|
||||||
|
title: 'Filemoon' | 'Vidplay';
|
||||||
|
}[]
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type SourceResult = VidSrcToResponse<{
|
||||||
|
url: string;
|
||||||
|
}>;
|
Reference in New Issue
Block a user