mirror of
https://github.com/p-stream/providers.git
synced 2026-04-21 00:12:09 +00:00
Archive old providers
This commit is contained in:
parent
21cb58dd33
commit
252283b09d
128 changed files with 2093 additions and 2186 deletions
|
|
@ -1,41 +1,16 @@
|
|||
import { Embed, Sourcerer } from '@/providers/base';
|
||||
import { doodScraper } from '@/providers/embeds/dood';
|
||||
import { droploadScraper } from '@/providers/embeds/dropload';
|
||||
import { febboxHlsScraper } from '@/providers/embeds/febbox/hls';
|
||||
import { febboxMp4Scraper } from '@/providers/embeds/febbox/mp4';
|
||||
import { filelionsScraper } from '@/providers/embeds/filelions';
|
||||
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 { streamwishScraper } from '@/providers/embeds/streamwish';
|
||||
import { turbovidScraper } from '@/providers/embeds/turbovid';
|
||||
import { upcloudScraper } from '@/providers/embeds/upcloud';
|
||||
import { upstreamScraper } from '@/providers/embeds/upstream';
|
||||
import { vidsrcembedScraper } from '@/providers/embeds/vidsrc';
|
||||
import { vTubeScraper } from '@/providers/embeds/vtube';
|
||||
import { astraScraper, novaScraper, orionScraper } from '@/providers/embeds/whvx';
|
||||
import { autoembedScraper } from '@/providers/sources/autoembed';
|
||||
import { bombtheirishScraper } from '@/providers/sources/bombtheirish';
|
||||
import { catflixScraper } from '@/providers/sources/catflix';
|
||||
import { ee3Scraper } from '@/providers/sources/ee3';
|
||||
import { flixhqScraper } from '@/providers/sources/flixhq/index';
|
||||
import { fsharetvScraper } from '@/providers/sources/fsharetv';
|
||||
import { goMoviesScraper } from '@/providers/sources/gomovies/index';
|
||||
import { insertunitScraper } from '@/providers/sources/insertunit';
|
||||
import { kissAsianScraper } from '@/providers/sources/kissasian/index';
|
||||
import { lookmovieScraper } from '@/providers/sources/lookmovie';
|
||||
import { mp4hydraScraper } from '@/providers/sources/mp4hydra';
|
||||
import { nsbxScraper } from '@/providers/sources/nsbx';
|
||||
import { redStarScraper } from '@/providers/sources/redstar';
|
||||
import { remotestreamScraper } from '@/providers/sources/remotestream';
|
||||
import { showboxScraper } from '@/providers/sources/showbox/index';
|
||||
import { TASFScraper } from '@/providers/sources/theyallsayflix';
|
||||
import { tugaflixScraper } from '@/providers/sources/tugaflix';
|
||||
import { vidsrcScraper } from '@/providers/sources/vidsrc/index';
|
||||
import { vidsrcsuScraper } from '@/providers/sources/vidsrcsu';
|
||||
import { whvxScraper } from '@/providers/sources/whvx';
|
||||
import { zoechipScraper } from '@/providers/sources/zoechip';
|
||||
|
||||
import {
|
||||
autoembedBengaliScraper,
|
||||
|
|
@ -44,21 +19,12 @@ import {
|
|||
autoembedTamilScraper,
|
||||
autoembedTeluguScraper,
|
||||
} from './embeds/autoembed';
|
||||
import { bflixScraper } from './embeds/bflix';
|
||||
import { closeLoadScraper } from './embeds/closeload';
|
||||
import { fileMoonScraper } from './embeds/filemoon';
|
||||
import { fileMoonMp4Scraper } from './embeds/filemoon/mp4';
|
||||
import { hydraxScraper } from './embeds/hydrax';
|
||||
import { mp4hydraServer1Scraper, mp4hydraServer2Scraper } from './embeds/mp4hydra';
|
||||
import { alphaScraper, deltaScraper } from './embeds/nsbx';
|
||||
import { playm4uNMScraper } from './embeds/playm4u/nm';
|
||||
import { ridooScraper } from './embeds/ridoo';
|
||||
import { smashyStreamOScraper } from './embeds/smashystream/opstream';
|
||||
import { smashyStreamFScraper } from './embeds/smashystream/video1';
|
||||
import { streamtapeScraper } from './embeds/streamtape';
|
||||
import { streamvidScraper } from './embeds/streamvid';
|
||||
import { vidCloudScraper } from './embeds/vidcloud';
|
||||
import { vidplayScraper } from './embeds/vidplay';
|
||||
import {
|
||||
VidsrcsuServer10Scraper,
|
||||
VidsrcsuServer11Scraper,
|
||||
|
|
@ -75,33 +41,22 @@ import {
|
|||
VidsrcsuServer9Scraper,
|
||||
} from './embeds/vidsrcsu';
|
||||
import { viperScraper } from './embeds/viper';
|
||||
import { voeScraper } from './embeds/voe';
|
||||
import { warezcdnembedHlsScraper } from './embeds/warezcdn/hls';
|
||||
import { warezcdnembedMp4Scraper } from './embeds/warezcdn/mp4';
|
||||
import { warezPlayerScraper } from './embeds/warezcdn/warezplayer';
|
||||
import { webtor1080Scraper, webtor480Scraper, webtor4kScraper, webtor720Scraper } from './embeds/webtor';
|
||||
import { wootlyScraper } from './embeds/wootly';
|
||||
import { coitusScraper } from './sources/coitus';
|
||||
import { embedsuScraper } from './sources/embedsu';
|
||||
import { FedAPIScraper } from './sources/fedapi';
|
||||
import { FedAPIDBScraper } from './sources/fedapidb';
|
||||
import { goojaraScraper } from './sources/goojara';
|
||||
import { hdRezkaScraper } from './sources/hdrezka';
|
||||
import { iosmirrorScraper } from './sources/iosmirror';
|
||||
import { iosmirrorPVScraper } from './sources/iosmirrorpv';
|
||||
import { m4uScraper } from './sources/m4ufree';
|
||||
import { nepuScraper } from './sources/nepu';
|
||||
import { nitesScraper } from './sources/nites';
|
||||
import { primewireScraper } from './sources/primewire';
|
||||
import { ridooMoviesScraper } from './sources/ridomovies';
|
||||
import { roverScraper } from './sources/rover';
|
||||
import { slidemoviesScraper } from './sources/slidemovies';
|
||||
import { smashyStreamScraper } from './sources/smashystream';
|
||||
import { soaperTvScraper } from './sources/soapertv';
|
||||
import { uiraliveScraper } from './sources/uiralive';
|
||||
import { vidapiClickScraper } from './sources/vidapiclick';
|
||||
import { vidlinkScraper } from './sources/vidlink';
|
||||
import { vidSrcToScraper } from './sources/vidsrcto';
|
||||
import { warezcdnScraper } from './sources/warezcdn';
|
||||
import { webtorScraper } from './sources/webtor';
|
||||
|
||||
|
|
@ -109,40 +64,19 @@ export function gatherAllSources(): Array<Sourcerer> {
|
|||
// all sources are gathered here
|
||||
return [
|
||||
catflixScraper,
|
||||
flixhqScraper,
|
||||
remotestreamScraper,
|
||||
kissAsianScraper,
|
||||
showboxScraper,
|
||||
goMoviesScraper,
|
||||
zoechipScraper,
|
||||
vidsrcScraper,
|
||||
lookmovieScraper,
|
||||
nsbxScraper,
|
||||
smashyStreamScraper,
|
||||
ridooMoviesScraper,
|
||||
vidSrcToScraper,
|
||||
nepuScraper,
|
||||
goojaraScraper,
|
||||
hdRezkaScraper,
|
||||
m4uScraper,
|
||||
primewireScraper,
|
||||
warezcdnScraper,
|
||||
insertunitScraper,
|
||||
nitesScraper,
|
||||
soaperTvScraper,
|
||||
autoembedScraper,
|
||||
tugaflixScraper,
|
||||
ee3Scraper,
|
||||
whvxScraper,
|
||||
fsharetvScraper,
|
||||
redStarScraper,
|
||||
bombtheirishScraper,
|
||||
vidsrcsuScraper,
|
||||
TASFScraper,
|
||||
mp4hydraScraper,
|
||||
webtorScraper,
|
||||
embedsuScraper,
|
||||
vidlinkScraper,
|
||||
FedAPIScraper,
|
||||
FedAPIDBScraper,
|
||||
slidemoviesScraper,
|
||||
|
|
@ -151,7 +85,6 @@ export function gatherAllSources(): Array<Sourcerer> {
|
|||
uiraliveScraper,
|
||||
vidapiClickScraper,
|
||||
coitusScraper,
|
||||
roverScraper,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -160,47 +93,21 @@ export function gatherAllEmbeds(): Array<Embed> {
|
|||
return [
|
||||
upcloudScraper,
|
||||
vidCloudScraper,
|
||||
mp4uploadScraper,
|
||||
streamsbScraper,
|
||||
upstreamScraper,
|
||||
febboxMp4Scraper,
|
||||
febboxHlsScraper,
|
||||
mixdropScraper,
|
||||
vidsrcembedScraper,
|
||||
streambucketScraper,
|
||||
smashyStreamFScraper,
|
||||
smashyStreamOScraper,
|
||||
ridooScraper,
|
||||
closeLoadScraper,
|
||||
fileMoonScraper,
|
||||
fileMoonMp4Scraper,
|
||||
deltaScraper,
|
||||
alphaScraper,
|
||||
vidplayScraper,
|
||||
wootlyScraper,
|
||||
doodScraper,
|
||||
streamvidScraper,
|
||||
voeScraper,
|
||||
streamtapeScraper,
|
||||
droploadScraper,
|
||||
filelionsScraper,
|
||||
vTubeScraper,
|
||||
warezcdnembedHlsScraper,
|
||||
warezcdnembedMp4Scraper,
|
||||
warezPlayerScraper,
|
||||
bflixScraper,
|
||||
playm4uNMScraper,
|
||||
hydraxScraper,
|
||||
autoembedEnglishScraper,
|
||||
autoembedHindiScraper,
|
||||
autoembedBengaliScraper,
|
||||
autoembedTamilScraper,
|
||||
autoembedTeluguScraper,
|
||||
turbovidScraper,
|
||||
novaScraper,
|
||||
astraScraper,
|
||||
orionScraper,
|
||||
streamwishScraper,
|
||||
mp4hydraServer1Scraper,
|
||||
mp4hydraServer2Scraper,
|
||||
VidsrcsuServer1Scraper,
|
||||
|
|
|
|||
52
src/providers/archive/embeds/dropload.ts
Normal file
52
src/providers/archive/embeds/dropload.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// import { unpack } from 'unpacker';
|
||||
|
||||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
|
||||
// import { makeEmbed } from '../base';
|
||||
|
||||
// const evalCodeRegex = /eval\((.*)\)/g;
|
||||
// const fileRegex = /file:"(.*?)"/g;
|
||||
// const tracksRegex = /\{file:"([^"]+)",kind:"thumbnails"\}/g;
|
||||
|
||||
// export const droploadScraper = makeEmbed({
|
||||
// id: 'dropload',
|
||||
// name: 'Dropload',
|
||||
// rank: 120,
|
||||
// scrape: async (ctx) => {
|
||||
// const mainPageRes = await ctx.proxiedFetcher.full<string>(ctx.url, {
|
||||
// headers: {
|
||||
// referer: ctx.url,
|
||||
// },
|
||||
// });
|
||||
// const mainPageUrl = new URL(mainPageRes.finalUrl);
|
||||
// const mainPage = mainPageRes.body;
|
||||
|
||||
// const evalCode = mainPage.match(evalCodeRegex);
|
||||
// if (!evalCode) throw new Error('Failed to find eval code');
|
||||
// const unpacked = unpack(evalCode[1]);
|
||||
|
||||
// const file = fileRegex.exec(unpacked);
|
||||
// const thumbnailTrack = tracksRegex.exec(unpacked);
|
||||
// if (!file?.[1]) throw new Error('Failed to find file');
|
||||
|
||||
// return {
|
||||
// stream: [
|
||||
// {
|
||||
// id: 'primary',
|
||||
// type: 'hls',
|
||||
// playlist: file[1],
|
||||
// flags: [flags.IP_LOCKED, flags.CORS_ALLOWED],
|
||||
// captions: [],
|
||||
// ...(thumbnailTrack
|
||||
// ? {
|
||||
// thumbnailTrack: {
|
||||
// type: 'vtt',
|
||||
// url: mainPageUrl.origin + thumbnailTrack[1],
|
||||
// },
|
||||
// }
|
||||
// : {}),
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
24
src/providers/archive/embeds/febbox/common.ts
Normal file
24
src/providers/archive/embeds/febbox/common.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// import { MediaTypes } from '@/entrypoint/utils/media';
|
||||
|
||||
// export const febBoxBase = `https://www.febbox.com`;
|
||||
|
||||
// export interface FebboxFileList {
|
||||
// file_name: string;
|
||||
// ext: string;
|
||||
// fid: number;
|
||||
// oss_fid: number;
|
||||
// is_dir: 0 | 1;
|
||||
// }
|
||||
|
||||
// export function parseInputUrl(url: string) {
|
||||
// const [type, id, seasonId, episodeId] = url.slice(1).split('/');
|
||||
// const season = seasonId ? parseInt(seasonId, 10) : undefined;
|
||||
// const episode = episodeId ? parseInt(episodeId, 10) : undefined;
|
||||
|
||||
// return {
|
||||
// type: type as MediaTypes,
|
||||
// id,
|
||||
// season,
|
||||
// episode,
|
||||
// };
|
||||
// }
|
||||
69
src/providers/archive/embeds/febbox/fileList.ts
Normal file
69
src/providers/archive/embeds/febbox/fileList.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// import { MediaTypes } from '@/entrypoint/utils/media';
|
||||
// import { FebboxFileList, febBoxBase } from '@/providers/embeds/febbox/common';
|
||||
// import { EmbedScrapeContext } from '@/utils/context';
|
||||
|
||||
// export async function getFileList(
|
||||
// ctx: EmbedScrapeContext,
|
||||
// shareKey: string,
|
||||
// parentId?: number,
|
||||
// ): Promise<FebboxFileList[]> {
|
||||
// const query: Record<string, string> = {
|
||||
// share_key: shareKey,
|
||||
// pwd: '',
|
||||
// };
|
||||
// if (parentId) {
|
||||
// query.parent_id = parentId.toString();
|
||||
// query.page = '1';
|
||||
// }
|
||||
|
||||
// const streams = await ctx.proxiedFetcher<{
|
||||
// data?: {
|
||||
// file_list?: FebboxFileList[];
|
||||
// };
|
||||
// }>('/file/file_share_list', {
|
||||
// headers: {
|
||||
// 'accept-language': 'en', // without this header, the request is marked as a webscraper
|
||||
// },
|
||||
// baseUrl: febBoxBase,
|
||||
// query,
|
||||
// });
|
||||
|
||||
// return streams.data?.file_list ?? [];
|
||||
// }
|
||||
|
||||
// function isValidStream(file: FebboxFileList): boolean {
|
||||
// return file.ext === 'mp4' || file.ext === 'mkv';
|
||||
// }
|
||||
|
||||
// export async function getStreams(
|
||||
// ctx: EmbedScrapeContext,
|
||||
// shareKey: string,
|
||||
// type: MediaTypes,
|
||||
// season?: number,
|
||||
// episode?: number,
|
||||
// ): Promise<FebboxFileList[]> {
|
||||
// const streams = await getFileList(ctx, shareKey);
|
||||
|
||||
// if (type === 'show') {
|
||||
// const seasonFolder = streams.find((v) => {
|
||||
// if (!v.is_dir) return false;
|
||||
// return v.file_name.toLowerCase() === `season ${season}`;
|
||||
// });
|
||||
// if (!seasonFolder) return [];
|
||||
|
||||
// const episodes = await getFileList(ctx, shareKey, seasonFolder.fid);
|
||||
// const s = season?.toString() ?? '0';
|
||||
// const e = episode?.toString() ?? '0';
|
||||
// const episodeRegex = new RegExp(`[Ss]0*${s}[Ee]0*${e}`);
|
||||
// return episodes
|
||||
// .filter((file) => {
|
||||
// if (file.is_dir) return false;
|
||||
// const match = file.file_name.match(episodeRegex);
|
||||
// if (!match) return false;
|
||||
// return true;
|
||||
// })
|
||||
// .filter(isValidStream);
|
||||
// }
|
||||
|
||||
// return streams.filter((v) => !v.is_dir).filter(isValidStream);
|
||||
// }
|
||||
50
src/providers/archive/embeds/febbox/hls.ts
Normal file
50
src/providers/archive/embeds/febbox/hls.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// import { MediaTypes } from '@/entrypoint/utils/media';
|
||||
// import { makeEmbed } from '@/providers/base';
|
||||
// import { parseInputUrl } from '@/providers/embeds/febbox/common';
|
||||
// import { getStreams } from '@/providers/embeds/febbox/fileList';
|
||||
// import { getSubtitles } from '@/providers/embeds/febbox/subtitles';
|
||||
// import { showboxBase } from '@/providers/sources/showbox/common';
|
||||
|
||||
// // structure: https://www.febbox.com/share/<random_key>
|
||||
// export function extractShareKey(url: string): string {
|
||||
// const parsedUrl = new URL(url);
|
||||
// const shareKey = parsedUrl.pathname.split('/')[2];
|
||||
// return shareKey;
|
||||
// }
|
||||
// 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<{
|
||||
// data?: { link?: string };
|
||||
// }>('/index/share_link', {
|
||||
// baseUrl: showboxBase,
|
||||
// query: {
|
||||
// id,
|
||||
// type: type === 'movie' ? '1' : '2',
|
||||
// },
|
||||
// });
|
||||
// if (!sharelinkResult?.data?.link) throw new Error('No embed url found');
|
||||
// ctx.progress(30);
|
||||
// const shareKey = extractShareKey(sharelinkResult.data.link);
|
||||
// const fileList = await getStreams(ctx, shareKey, type, season, episode);
|
||||
// const firstStream = fileList[0];
|
||||
// if (!firstStream) throw new Error('No playable mp4 stream found');
|
||||
// ctx.progress(70);
|
||||
|
||||
// return {
|
||||
// stream: [
|
||||
// {
|
||||
// id: 'primary',
|
||||
// type: 'hls',
|
||||
// flags: [],
|
||||
// captions: await getSubtitles(ctx, id, firstStream.fid, type as MediaTypes, season, episode),
|
||||
// playlist: `https://www.febbox.com/hls/main/${firstStream.oss_fid}.m3u8`,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
53
src/providers/archive/embeds/febbox/mp4.ts
Normal file
53
src/providers/archive/embeds/febbox/mp4.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { makeEmbed } from '@/providers/base';
|
||||
// import { parseInputUrl } from '@/providers/embeds/febbox/common';
|
||||
// import { getStreamQualities } from '@/providers/embeds/febbox/qualities';
|
||||
// import { getSubtitles } from '@/providers/embeds/febbox/subtitles';
|
||||
|
||||
// export const febboxMp4Scraper = makeEmbed({
|
||||
// id: 'febbox-mp4',
|
||||
// name: 'Febbox (MP4)',
|
||||
// rank: 190,
|
||||
// async scrape(ctx) {
|
||||
// const { type, id, season, episode } = parseInputUrl(ctx.url);
|
||||
// let apiQuery: object | null = null;
|
||||
|
||||
// if (type === 'movie') {
|
||||
// apiQuery = {
|
||||
// uid: '',
|
||||
// module: 'Movie_downloadurl_v3',
|
||||
// mid: id,
|
||||
// oss: '1',
|
||||
// group: '',
|
||||
// };
|
||||
// } else if (type === 'show') {
|
||||
// apiQuery = {
|
||||
// uid: '',
|
||||
// module: 'TV_downloadurl_v3',
|
||||
// tid: id,
|
||||
// season,
|
||||
// episode,
|
||||
// oss: '1',
|
||||
// group: '',
|
||||
// };
|
||||
// }
|
||||
|
||||
// if (!apiQuery) throw Error('Incorrect type');
|
||||
|
||||
// const { qualities, fid } = await getStreamQualities(ctx, apiQuery);
|
||||
// if (fid === undefined) throw new Error('No streamable file found');
|
||||
// ctx.progress(70);
|
||||
|
||||
// return {
|
||||
// stream: [
|
||||
// {
|
||||
// id: 'primary',
|
||||
// captions: await getSubtitles(ctx, id, fid, type, episode, season),
|
||||
// qualities,
|
||||
// type: 'file',
|
||||
// flags: [flags.CORS_ALLOWED],
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
44
src/providers/archive/embeds/febbox/qualities.ts
Normal file
44
src/providers/archive/embeds/febbox/qualities.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// import { sendRequest } from '@/providers/sources/showbox/sendRequest';
|
||||
// import { StreamFile } from '@/providers/streams';
|
||||
// import { ScrapeContext } from '@/utils/context';
|
||||
|
||||
// const allowedQualities = ['360', '480', '720', '1080', '4k'];
|
||||
|
||||
// interface FebboxQuality {
|
||||
// path: string;
|
||||
// real_quality: string;
|
||||
// fid?: number;
|
||||
// }
|
||||
|
||||
// function mapToQuality(quality: FebboxQuality): FebboxQuality | null {
|
||||
// const q = quality.real_quality.replace('p', '').toLowerCase();
|
||||
// if (!allowedQualities.includes(q)) return null;
|
||||
// return {
|
||||
// real_quality: q,
|
||||
// path: quality.path,
|
||||
// fid: quality.fid,
|
||||
// };
|
||||
// }
|
||||
|
||||
// export async function getStreamQualities(ctx: ScrapeContext, apiQuery: object) {
|
||||
// const mediaRes: { list: FebboxQuality[] } = (await sendRequest(ctx, apiQuery)).data;
|
||||
|
||||
// const qualityMap = mediaRes.list.map((v) => mapToQuality(v)).filter((v): v is FebboxQuality => !!v);
|
||||
|
||||
// const qualities: Record<string, StreamFile> = {};
|
||||
|
||||
// allowedQualities.forEach((quality) => {
|
||||
// const foundQuality = qualityMap.find((q) => q.real_quality === quality && q.path);
|
||||
// if (foundQuality) {
|
||||
// qualities[quality] = {
|
||||
// type: 'mp4',
|
||||
// url: foundQuality.path,
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
|
||||
// return {
|
||||
// qualities,
|
||||
// fid: mediaRes.list[0]?.fid,
|
||||
// };
|
||||
// }
|
||||
75
src/providers/archive/embeds/febbox/subtitles.ts
Normal file
75
src/providers/archive/embeds/febbox/subtitles.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// import {
|
||||
// Caption,
|
||||
// getCaptionTypeFromUrl,
|
||||
// isValidLanguageCode,
|
||||
// removeDuplicatedLanguages as removeDuplicateLanguages,
|
||||
// } from '@/providers/captions';
|
||||
// import { captionsDomains } from '@/providers/sources/showbox/common';
|
||||
// import { sendRequest } from '@/providers/sources/showbox/sendRequest';
|
||||
// import { ScrapeContext } from '@/utils/context';
|
||||
|
||||
// interface CaptionApiResponse {
|
||||
// data: {
|
||||
// list: {
|
||||
// subtitles: {
|
||||
// order: number;
|
||||
// lang: string;
|
||||
// file_path: string;
|
||||
// }[];
|
||||
// }[];
|
||||
// };
|
||||
// }
|
||||
|
||||
// export async function getSubtitles(
|
||||
// ctx: ScrapeContext,
|
||||
// id: string,
|
||||
// fid: number | undefined,
|
||||
// type: 'show' | 'movie',
|
||||
// episodeId?: number,
|
||||
// seasonId?: number,
|
||||
// ): Promise<Caption[]> {
|
||||
// const module = type === 'movie' ? 'Movie_srt_list_v2' : 'TV_srt_list_v2';
|
||||
// const subtitleApiQuery = {
|
||||
// fid,
|
||||
// uid: '',
|
||||
// module,
|
||||
// mid: type === 'movie' ? id : undefined,
|
||||
// tid: type !== 'movie' ? id : undefined,
|
||||
// episode: episodeId?.toString(),
|
||||
// season: seasonId?.toString(),
|
||||
// };
|
||||
|
||||
// const subResult = (await sendRequest(ctx, subtitleApiQuery)) as CaptionApiResponse;
|
||||
// const subtitleList = subResult.data.list;
|
||||
// let output: Caption[] = [];
|
||||
|
||||
// subtitleList.forEach((sub) => {
|
||||
// const subtitle = sub.subtitles.sort((a, b) => b.order - a.order)[0];
|
||||
// if (!subtitle) return;
|
||||
|
||||
// const subtitleFilePath = subtitle.file_path
|
||||
// .replace(captionsDomains[0], captionsDomains[1])
|
||||
// .replace(/\s/g, '+')
|
||||
// .replace(/[()]/g, (c) => {
|
||||
// return `%${c.charCodeAt(0).toString(16)}`;
|
||||
// });
|
||||
|
||||
// const subtitleType = getCaptionTypeFromUrl(subtitleFilePath);
|
||||
// if (!subtitleType) return;
|
||||
|
||||
// const validCode = isValidLanguageCode(subtitle.lang);
|
||||
// if (!validCode) return;
|
||||
|
||||
// output.push({
|
||||
// id: subtitleFilePath,
|
||||
// language: subtitle.lang,
|
||||
// hasCorsRestrictions: true,
|
||||
// type: subtitleType,
|
||||
// url: subtitleFilePath,
|
||||
// });
|
||||
// });
|
||||
|
||||
// output = removeDuplicateLanguages(output);
|
||||
|
||||
// return output;
|
||||
// }
|
||||
62
src/providers/archive/embeds/filemoon/index.ts
Normal file
62
src/providers/archive/embeds/filemoon/index.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// import { load } from 'cheerio';
|
||||
// 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: 300,
|
||||
// scrape: async (ctx) => {
|
||||
// const embedRes = await ctx.proxiedFetcher<string>(ctx.url, {
|
||||
// headers: {
|
||||
// referer: ctx.url,
|
||||
// },
|
||||
// });
|
||||
// const embedHtml = load(embedRes);
|
||||
// const evalCode = embedHtml('script').text().match(evalCodeRegex);
|
||||
// if (!evalCode) throw new Error('Failed to find eval code');
|
||||
// const unpacked = unpack(evalCode[0]);
|
||||
// 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.proxiedFetcher<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.IP_LOCKED],
|
||||
// captions,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
38
src/providers/archive/embeds/filemoon/mp4.ts
Normal file
38
src/providers/archive/embeds/filemoon/mp4.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// import { makeEmbed } from '../../base';
|
||||
|
||||
// import { fileMoonScraper } from './index';
|
||||
|
||||
// export const fileMoonMp4Scraper = makeEmbed({
|
||||
// id: 'filemoon-mp4',
|
||||
// name: 'Filemoon MP4',
|
||||
// rank: 400,
|
||||
// scrape: async (ctx) => {
|
||||
// const result = await fileMoonScraper.scrape(ctx);
|
||||
|
||||
// if (!result.stream) throw new NotFoundError('Failed to find result');
|
||||
|
||||
// if (result.stream[0].type !== 'hls') throw new NotFoundError('Failed to find hls stream');
|
||||
|
||||
// const url = result.stream[0].playlist.replace(/\/hls2\//, '/download/').replace(/\.m3u8/, '.mp4');
|
||||
|
||||
// return {
|
||||
// stream: [
|
||||
// {
|
||||
// id: 'primary',
|
||||
// type: 'file',
|
||||
// qualities: {
|
||||
// unknown: {
|
||||
// type: 'mp4',
|
||||
// url,
|
||||
// },
|
||||
// },
|
||||
// flags: [flags.IP_LOCKED],
|
||||
// captions: result.stream[0].captions,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
57
src/providers/archive/embeds/vidplay/common.ts
Normal file
57
src/providers/archive/embeds/vidplay/common.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// import { makeFullUrl } from '@/fetchers/common';
|
||||
// import { decodeData } from '@/providers/sources/vidsrcto/common';
|
||||
// import { EmbedScrapeContext } from '@/utils/context';
|
||||
|
||||
// export const vidplayBase = 'https://vidplay.online';
|
||||
// export const referer = `${vidplayBase}/`;
|
||||
|
||||
// // This file is based on https://github.com/Ciarands/vidsrc-to-resolver/blob/dffa45e726a4b944cb9af0c9e7630476c93c0213/vidsrc.py#L16
|
||||
// // Full credits to @Ciarands!
|
||||
|
||||
// export const getDecryptionKeys = async (ctx: EmbedScrapeContext): Promise<string[]> => {
|
||||
// const res = await ctx.proxiedFetcher<string>('https://github.com/Ciarands/vidsrc-keys/blob/main/keys.json');
|
||||
// const regex = /"rawLines":\s*\[([\s\S]*?)\]/;
|
||||
// const rawLines = res.match(regex)?.[1];
|
||||
// if (!rawLines) throw new Error('No keys found');
|
||||
// const keys = JSON.parse(`${rawLines.substring(1).replace(/\\"/g, '"')}]`);
|
||||
// return keys;
|
||||
// };
|
||||
|
||||
// export const getEncodedId = async (ctx: EmbedScrapeContext) => {
|
||||
// const url = new URL(ctx.url);
|
||||
// const id = url.pathname.replace('/e/', '');
|
||||
// const keyList = await getDecryptionKeys(ctx);
|
||||
|
||||
// const decodedId = decodeData(keyList[0], id);
|
||||
// const encodedResult = decodeData(keyList[1], decodedId);
|
||||
// const b64encoded = btoa(encodedResult);
|
||||
// return b64encoded.replace('/', '_');
|
||||
// };
|
||||
|
||||
// export const getFuTokenKey = async (ctx: EmbedScrapeContext) => {
|
||||
// const id = await getEncodedId(ctx);
|
||||
// const fuTokenRes = await ctx.proxiedFetcher<string>('/futoken', {
|
||||
// baseUrl: vidplayBase,
|
||||
// headers: {
|
||||
// referer: ctx.url,
|
||||
// },
|
||||
// });
|
||||
// const fuKey = fuTokenRes.match(/var\s+k\s*=\s*'([^']+)'/)?.[1];
|
||||
// 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));
|
||||
// }
|
||||
// return `${fuKey},${tokens.join(',')}`;
|
||||
// };
|
||||
|
||||
// export const getFileUrl = async (ctx: EmbedScrapeContext) => {
|
||||
// const fuToken = await getFuTokenKey(ctx);
|
||||
// return makeFullUrl(`/mediainfo/${fuToken}`, {
|
||||
// baseUrl: vidplayBase,
|
||||
// query: {
|
||||
// ...Object.fromEntries(new URL(ctx.url).searchParams.entries()),
|
||||
// autostart: 'true',
|
||||
// },
|
||||
// });
|
||||
// };
|
||||
68
src/providers/archive/embeds/vidplay/index.ts
Normal file
68
src/providers/archive/embeds/vidplay/index.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { makeEmbed } from '@/providers/base';
|
||||
// import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '@/providers/captions';
|
||||
|
||||
// import { getFileUrl } from './common';
|
||||
// import { SubtitleResult, ThumbnailTrack, VidplaySourceResponse } from './types';
|
||||
|
||||
// export const vidplayScraper = makeEmbed({
|
||||
// id: 'vidplay',
|
||||
// name: 'VidPlay',
|
||||
// rank: 401,
|
||||
// scrape: async (ctx) => {
|
||||
// const fileUrl = await getFileUrl(ctx);
|
||||
// const fileUrlRes = await ctx.proxiedFetcher<VidplaySourceResponse>(fileUrl, {
|
||||
// headers: {
|
||||
// referer: ctx.url,
|
||||
// },
|
||||
// });
|
||||
// if (typeof fileUrlRes.result === 'number') throw new Error('File not found');
|
||||
// const source = fileUrlRes.result.sources[0].file;
|
||||
// const thumbnailSource = fileUrlRes.result.tracks.find((track) => track.kind === 'thumbnails');
|
||||
|
||||
// let thumbnailTrack: ThumbnailTrack | undefined;
|
||||
// if (thumbnailSource) {
|
||||
// thumbnailTrack = {
|
||||
// type: 'vtt',
|
||||
// url: thumbnailSource.file,
|
||||
// };
|
||||
// }
|
||||
|
||||
// const url = new URL(ctx.url);
|
||||
// const subtitlesLink = url.searchParams.get('sub.info');
|
||||
// const captions: Caption[] = [];
|
||||
// if (subtitlesLink) {
|
||||
// const captionsResult = await ctx.proxiedFetcher<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: source,
|
||||
// flags: [flags.PROXY_BLOCKED],
|
||||
// headers: {
|
||||
// Referer: url.origin,
|
||||
// Origin: url.origin,
|
||||
// },
|
||||
// captions,
|
||||
// thumbnailTrack,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
69
src/providers/archive/embeds/vidsrc.ts
Normal file
69
src/providers/archive/embeds/vidsrc.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// import { makeEmbed } from '@/providers/base';
|
||||
// import { vidsrcRCPBase } from '@/providers/sources/vidsrc/common';
|
||||
|
||||
// const hlsURLRegex = /file:"(.*?)"/;
|
||||
// const setPassRegex = /var pass_path = "(.*set_pass\.php.*)";/;
|
||||
|
||||
// function formatHlsB64(data: string): string {
|
||||
// const encodedB64 = data.replace(/\/@#@\/[^=/]+==/g, '');
|
||||
// if (encodedB64.match(/\/@#@\/[^=/]+==/)) {
|
||||
// return formatHlsB64(encodedB64);
|
||||
// }
|
||||
// return encodedB64;
|
||||
// }
|
||||
|
||||
// export const vidsrcembedScraper = makeEmbed({
|
||||
// id: 'vidsrcembed', // VidSrc is both a source and an embed host
|
||||
// name: 'VidSrc',
|
||||
// rank: 197,
|
||||
// async scrape(ctx) {
|
||||
// const html = await ctx.proxiedFetcher<string>(ctx.url, {
|
||||
// headers: {
|
||||
// referer: ctx.url,
|
||||
// },
|
||||
// });
|
||||
|
||||
// // When this eventually breaks see the player js @ pjs_main.js
|
||||
// // If you know what youre doing and are slightly confused about how to reverse this feel free to reach out to ciaran_ds on discord with any queries
|
||||
// let hlsMatch = html.match(hlsURLRegex)?.[1]?.slice(2);
|
||||
// if (!hlsMatch) throw new Error('Unable to find HLS playlist');
|
||||
// hlsMatch = formatHlsB64(hlsMatch);
|
||||
// const finalUrl = atob(hlsMatch);
|
||||
// if (!finalUrl.includes('.m3u8')) throw new Error('Unable to find HLS playlist');
|
||||
|
||||
// let setPassLink = html.match(setPassRegex)?.[1];
|
||||
|
||||
// // isn't neeeded, the stream works without it anyway
|
||||
// // shouldn't fail if the setpass link is not found
|
||||
// if (setPassLink) {
|
||||
// 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: [
|
||||
// {
|
||||
// id: 'primary',
|
||||
// type: 'hls',
|
||||
// playlist: finalUrl,
|
||||
// headers: {
|
||||
// Referer: vidsrcRCPBase,
|
||||
// Origin: vidsrcRCPBase,
|
||||
// },
|
||||
// flags: [],
|
||||
// captions: [],
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
51
src/providers/archive/embeds/vtube.ts
Normal file
51
src/providers/archive/embeds/vtube.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// import { load } from 'cheerio';
|
||||
// import { unpack } from 'unpacker';
|
||||
|
||||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
|
||||
// import { makeEmbed } from '../base';
|
||||
|
||||
// const evalCodeRegex = /eval\((.*)\)/g;
|
||||
// const fileRegex = /file:"(.*?)"/g;
|
||||
// const tracksRegex = /\{file:"([^"]+)",kind:"thumbnails"\}/g;
|
||||
|
||||
// export const vTubeScraper = makeEmbed({
|
||||
// id: 'vtube',
|
||||
// name: 'vTube',
|
||||
// rank: 145,
|
||||
// scrape: async (ctx) => {
|
||||
// const mainPageRes = await ctx.proxiedFetcher.full<string>(ctx.url, {
|
||||
// headers: {
|
||||
// referer: ctx.url,
|
||||
// },
|
||||
// });
|
||||
// const mainPage = mainPageRes.body;
|
||||
// const html = load(mainPage);
|
||||
// const evalCode = html('script').text().match(evalCodeRegex);
|
||||
// if (!evalCode) throw new Error('Failed to find eval code');
|
||||
// const unpacked = unpack(evalCode?.toString());
|
||||
// const file = fileRegex.exec(unpacked);
|
||||
// const thumbnailTrack = tracksRegex.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: [],
|
||||
// ...(thumbnailTrack
|
||||
// ? {
|
||||
// thumbnailTrack: {
|
||||
// type: 'vtt',
|
||||
// url: new URL(mainPageRes.finalUrl).origin + thumbnailTrack[1],
|
||||
// },
|
||||
// }
|
||||
// : {}),
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
70
src/providers/archive/embeds/whvx.ts
Normal file
70
src/providers/archive/embeds/whvx.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// import { EmbedOutput, makeEmbed } from '@/providers/base';
|
||||
// import { baseUrl } from '@/providers/sources/whvx';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// const providers = [
|
||||
// {
|
||||
// id: 'nova',
|
||||
// rank: 720,
|
||||
// },
|
||||
// {
|
||||
// id: 'astra',
|
||||
// rank: 710,
|
||||
// },
|
||||
// {
|
||||
// id: 'orion',
|
||||
// rank: 700,
|
||||
// },
|
||||
// ];
|
||||
|
||||
// export const headers = {
|
||||
// Origin: 'https://www.vidbinge.com',
|
||||
// Referer: 'https://www.vidbinge.com',
|
||||
// };
|
||||
|
||||
// function embed(provider: { id: string; rank: number; disabled?: boolean }) {
|
||||
// return makeEmbed({
|
||||
// id: provider.id,
|
||||
// name: provider.id.charAt(0).toUpperCase() + provider.id.slice(1),
|
||||
// rank: provider.rank,
|
||||
// disabled: provider.disabled,
|
||||
// async scrape(ctx) {
|
||||
// let progress = 50;
|
||||
// const interval = setInterval(() => {
|
||||
// if (progress < 100) {
|
||||
// progress += 1;
|
||||
// ctx.progress(progress);
|
||||
// }
|
||||
// }, 100);
|
||||
|
||||
// try {
|
||||
// const search = await ctx.fetcher.full(
|
||||
// `${baseUrl}/search?query=${encodeURIComponent(ctx.url)}&provider=${provider.id}`,
|
||||
// { headers },
|
||||
// );
|
||||
|
||||
// if (search.statusCode === 429) {
|
||||
// throw new Error('Rate limited');
|
||||
// } else if (search.statusCode !== 200) {
|
||||
// throw new NotFoundError('Failed to search');
|
||||
// }
|
||||
|
||||
// const result = await ctx.fetcher(
|
||||
// `${baseUrl}/source?resourceId=${encodeURIComponent(search.body.url)}&provider=${provider.id}`,
|
||||
// { headers },
|
||||
// );
|
||||
|
||||
// clearInterval(interval);
|
||||
// ctx.progress(100);
|
||||
|
||||
// return result as EmbedOutput;
|
||||
// } catch (error) {
|
||||
// clearInterval(interval);
|
||||
// ctx.progress(100);
|
||||
// throw new NotFoundError('Failed to search');
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
// export const [novaScraper, astraScraper, orionScraper] = providers.map(embed);
|
||||
BIN
src/providers/archive/sources/.DS_Store
vendored
Normal file
BIN
src/providers/archive/sources/.DS_Store
vendored
Normal file
Binary file not shown.
66
src/providers/archive/sources/flixhq/index.ts
Normal file
66
src/providers/archive/sources/flixhq/index.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { SourcererEmbed, makeSourcerer } from '@/providers/base';
|
||||
// import { upcloudScraper } from '@/providers/embeds/upcloud';
|
||||
// import { vidCloudScraper } from '@/providers/embeds/vidcloud';
|
||||
// import { getFlixhqMovieSources, getFlixhqShowSources, getFlixhqSourceDetails } from '@/providers/sources/flixhq/scrape';
|
||||
// import { getFlixhqId } from '@/providers/sources/flixhq/search';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// export const flixhqScraper = makeSourcerer({
|
||||
// id: 'flixhq',
|
||||
// name: 'FlixHQ',
|
||||
// rank: 229,
|
||||
// flags: [flags.CORS_ALLOWED],
|
||||
// disabled: true,
|
||||
// async scrapeMovie(ctx) {
|
||||
// const id = await getFlixhqId(ctx, ctx.media);
|
||||
// if (!id) throw new NotFoundError('no search results match');
|
||||
|
||||
// const sources = await getFlixhqMovieSources(ctx, ctx.media, id);
|
||||
|
||||
// const embeds: SourcererEmbed[] = [];
|
||||
|
||||
// for (const source of sources) {
|
||||
// if (source.embed.toLowerCase() === 'upcloud') {
|
||||
// embeds.push({
|
||||
// embedId: upcloudScraper.id,
|
||||
// url: await getFlixhqSourceDetails(ctx, source.episodeId),
|
||||
// });
|
||||
// } else if (source.embed.toLowerCase() === 'vidcloud') {
|
||||
// embeds.push({
|
||||
// embedId: vidCloudScraper.id,
|
||||
// url: await getFlixhqSourceDetails(ctx, source.episodeId),
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// return {
|
||||
// embeds,
|
||||
// };
|
||||
// },
|
||||
// async scrapeShow(ctx) {
|
||||
// const id = await getFlixhqId(ctx, ctx.media);
|
||||
// if (!id) throw new NotFoundError('no search results match');
|
||||
|
||||
// const sources = await getFlixhqShowSources(ctx, ctx.media, id);
|
||||
|
||||
// const embeds: SourcererEmbed[] = [];
|
||||
// for (const source of sources) {
|
||||
// if (source.embed.toLowerCase() === 'server upcloud') {
|
||||
// embeds.push({
|
||||
// embedId: upcloudScraper.id,
|
||||
// url: await getFlixhqSourceDetails(ctx, source.episodeId),
|
||||
// });
|
||||
// } else if (source.embed.toLowerCase() === 'server vidcloud') {
|
||||
// embeds.push({
|
||||
// embedId: vidCloudScraper.id,
|
||||
// url: await getFlixhqSourceDetails(ctx, source.episodeId),
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// return {
|
||||
// embeds,
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
92
src/providers/archive/sources/flixhq/scrape.ts
Normal file
92
src/providers/archive/sources/flixhq/scrape.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
// import { load } from 'cheerio';
|
||||
|
||||
// import { MovieMedia, ShowMedia } from '@/entrypoint/utils/media';
|
||||
// import { flixHqBase } from '@/providers/sources/flixhq/common';
|
||||
// import { ScrapeContext } from '@/utils/context';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// export async function getFlixhqSourceDetails(ctx: ScrapeContext, sourceId: string): Promise<string> {
|
||||
// const jsonData = await ctx.proxiedFetcher<Record<string, any>>(`/ajax/sources/${sourceId}`, {
|
||||
// baseUrl: flixHqBase,
|
||||
// });
|
||||
|
||||
// return jsonData.link;
|
||||
// }
|
||||
|
||||
// export async function getFlixhqMovieSources(ctx: ScrapeContext, media: MovieMedia, id: string) {
|
||||
// const episodeParts = id.split('-');
|
||||
// const episodeId = episodeParts[episodeParts.length - 1];
|
||||
|
||||
// const data = await ctx.proxiedFetcher<string>(`/ajax/movie/episodes/${episodeId}`, {
|
||||
// baseUrl: flixHqBase,
|
||||
// });
|
||||
|
||||
// const doc = load(data);
|
||||
// const sourceLinks = doc('.nav-item > a')
|
||||
// .toArray()
|
||||
// .map((el) => {
|
||||
// const query = doc(el);
|
||||
// const embedTitle = query.attr('title');
|
||||
// const linkId = query.attr('data-linkid');
|
||||
// if (!embedTitle || !linkId) throw new Error('invalid sources');
|
||||
// return {
|
||||
// embed: embedTitle,
|
||||
// episodeId: linkId,
|
||||
// };
|
||||
// });
|
||||
|
||||
// return sourceLinks;
|
||||
// }
|
||||
|
||||
// export async function getFlixhqShowSources(ctx: ScrapeContext, media: ShowMedia, id: string) {
|
||||
// const episodeParts = id.split('-');
|
||||
// const episodeId = episodeParts[episodeParts.length - 1];
|
||||
|
||||
// const seasonsListData = await ctx.proxiedFetcher<string>(`/ajax/season/list/${episodeId}`, {
|
||||
// baseUrl: flixHqBase,
|
||||
// });
|
||||
|
||||
// const seasonsDoc = load(seasonsListData);
|
||||
// const season = seasonsDoc('.dropdown-item')
|
||||
// .toArray()
|
||||
// .find((el) => seasonsDoc(el).text() === `Season ${media.season.number}`)?.attribs['data-id'];
|
||||
|
||||
// if (!season) throw new NotFoundError('season not found');
|
||||
|
||||
// const seasonData = await ctx.proxiedFetcher<string>(`/ajax/season/episodes/${season}`, {
|
||||
// baseUrl: flixHqBase,
|
||||
// });
|
||||
// const seasonDoc = load(seasonData);
|
||||
// const episode = seasonDoc('.nav-item > a')
|
||||
// .toArray()
|
||||
// .map((el) => {
|
||||
// return {
|
||||
// id: seasonDoc(el).attr('data-id'),
|
||||
// title: seasonDoc(el).attr('title'),
|
||||
// };
|
||||
// })
|
||||
// .find((e) => e.title?.startsWith(`Eps ${media.episode.number}`))?.id;
|
||||
|
||||
// if (!episode) throw new NotFoundError('episode not found');
|
||||
|
||||
// const data = await ctx.proxiedFetcher<string>(`/ajax/episode/servers/${episode}`, {
|
||||
// baseUrl: flixHqBase,
|
||||
// });
|
||||
|
||||
// const doc = load(data);
|
||||
|
||||
// const sourceLinks = doc('.nav-item > a')
|
||||
// .toArray()
|
||||
// .map((el) => {
|
||||
// const query = doc(el);
|
||||
// const embedTitle = query.attr('title');
|
||||
// const linkId = query.attr('data-id');
|
||||
// if (!embedTitle || !linkId) throw new Error('invalid sources');
|
||||
// return {
|
||||
// embed: embedTitle,
|
||||
// episodeId: linkId,
|
||||
// };
|
||||
// });
|
||||
|
||||
// return sourceLinks;
|
||||
// }
|
||||
44
src/providers/archive/sources/flixhq/search.ts
Normal file
44
src/providers/archive/sources/flixhq/search.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
// import { load } from 'cheerio';
|
||||
|
||||
// import { MovieMedia, ShowMedia } from '@/entrypoint/utils/media';
|
||||
// import { flixHqBase } from '@/providers/sources/flixhq/common';
|
||||
// import { compareMedia, compareTitle } from '@/utils/compare';
|
||||
// import { ScrapeContext } from '@/utils/context';
|
||||
|
||||
// export async function getFlixhqId(ctx: ScrapeContext, media: MovieMedia | ShowMedia): Promise<string | null> {
|
||||
// const searchResults = await ctx.proxiedFetcher<string>(`/search/${media.title.replaceAll(/[^a-z0-9A-Z]/g, '-')}`, {
|
||||
// baseUrl: flixHqBase,
|
||||
// });
|
||||
|
||||
// const doc = load(searchResults);
|
||||
// const items = doc('.film_list-wrap > div.flw-item')
|
||||
// .toArray()
|
||||
// .map((el) => {
|
||||
// const query = doc(el);
|
||||
// const id = query.find('div.film-poster > a').attr('href')?.slice(1);
|
||||
// const title = query.find('div.film-detail > h2 > a').attr('title');
|
||||
// const year = query.find('div.film-detail > div.fd-infor > span:nth-child(1)').text();
|
||||
// const seasons = year.includes('SS') ? year.split('SS')[1] : '0';
|
||||
|
||||
// if (!id || !title || !year) return null;
|
||||
// return {
|
||||
// id,
|
||||
// title,
|
||||
// year: parseInt(year, 10),
|
||||
// seasons: parseInt(seasons, 10),
|
||||
// };
|
||||
// });
|
||||
|
||||
// const matchingItem = items.find((v) => {
|
||||
// if (!v) return false;
|
||||
|
||||
// if (media.type === 'movie') {
|
||||
// return compareMedia(media, v.title, v.year);
|
||||
// }
|
||||
|
||||
// return compareTitle(media.title, v.title) && media.season.number < v.seasons + 1;
|
||||
// });
|
||||
|
||||
// if (!matchingItem) return null;
|
||||
// return matchingItem.id;
|
||||
// }
|
||||
225
src/providers/archive/sources/gomovies/index.ts
Normal file
225
src/providers/archive/sources/gomovies/index.ts
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
// import { load } from 'cheerio';
|
||||
|
||||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { makeSourcerer } from '@/providers/base';
|
||||
// import { doodScraper } from '@/providers/embeds/dood';
|
||||
// import { mixdropScraper } from '@/providers/embeds/mixdrop';
|
||||
// import { upcloudScraper } from '@/providers/embeds/upcloud';
|
||||
// import { upstreamScraper } from '@/providers/embeds/upstream';
|
||||
// import { vidCloudScraper } from '@/providers/embeds/vidcloud';
|
||||
// import { voeScraper } from '@/providers/embeds/voe';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// import { getSource } from './source';
|
||||
|
||||
// export const gomoviesBase = `https://gomovies.sx`;
|
||||
|
||||
// export const goMoviesScraper = makeSourcerer({
|
||||
// id: 'gomovies',
|
||||
// name: 'GOmovies',
|
||||
// rank: 50,
|
||||
// disabled: true,
|
||||
// flags: [flags.CORS_ALLOWED],
|
||||
// async scrapeShow(ctx) {
|
||||
// const search = await ctx.proxiedFetcher(`/search/${ctx.media.title.replaceAll(/[^a-z0-9A-Z]/g, '-')}`, {
|
||||
// method: 'GET',
|
||||
// headers: {
|
||||
// 'X-Requested-With': 'XMLHttpRequest',
|
||||
// },
|
||||
// baseUrl: gomoviesBase,
|
||||
// });
|
||||
|
||||
// const searchPage = load(search);
|
||||
// const mediaElements = searchPage('div.film-detail');
|
||||
|
||||
// const mediaData = mediaElements.toArray().map((movieEl) => {
|
||||
// const name = searchPage(movieEl).find('h2.film-name a')?.text();
|
||||
// const year = searchPage(movieEl).find('span.fdi-item:first')?.text();
|
||||
// const path = searchPage(movieEl).find('h2.film-name a').attr('href');
|
||||
// return { name, year, path };
|
||||
// });
|
||||
|
||||
// const targetMedia = mediaData.find((m) => m.name === ctx.media.title);
|
||||
// if (!targetMedia?.path) throw new NotFoundError('Media not found');
|
||||
|
||||
// // Example movie path: /movie/watch-{slug}-{id}
|
||||
// // Example series path: /tv/watch-{slug}-{id}
|
||||
// let mediaId = targetMedia.path.split('-').pop()?.replace('/', '');
|
||||
|
||||
// const seasons = await ctx.proxiedFetcher<string>(`/ajax/v2/tv/seasons/${mediaId}`, {
|
||||
// headers: {
|
||||
// 'X-Requested-With': 'XMLHttpRequest',
|
||||
// },
|
||||
// baseUrl: gomoviesBase,
|
||||
// });
|
||||
|
||||
// const seasonsEl = load(seasons)('.ss-item');
|
||||
|
||||
// const seasonsData = seasonsEl.toArray().map((season) => ({
|
||||
// number: load(season).text().replace('Season ', ''),
|
||||
// dataId: season.attribs['data-id'],
|
||||
// }));
|
||||
|
||||
// const seasonNumber = ctx.media.season.number;
|
||||
// const targetSeason = seasonsData.find((season) => +season.number === seasonNumber);
|
||||
|
||||
// if (!targetSeason) throw new NotFoundError('Season not found');
|
||||
|
||||
// const episodes = await ctx.proxiedFetcher<string>(`/ajax/v2/season/episodes/${targetSeason.dataId}`, {
|
||||
// headers: {
|
||||
// 'X-Requested-With': 'XMLHttpRequest',
|
||||
// },
|
||||
// baseUrl: gomoviesBase,
|
||||
// });
|
||||
|
||||
// const episodesPage = load(episodes);
|
||||
// const episodesEl = episodesPage('.eps-item');
|
||||
|
||||
// const episodesData = episodesEl.toArray().map((ep) => ({
|
||||
// dataId: ep.attribs['data-id'],
|
||||
// number: episodesPage(ep).find('strong').text().replace('Eps', '').replace(':', '').trim(),
|
||||
// }));
|
||||
|
||||
// const episodeNumber = ctx.media.episode.number;
|
||||
// const targetEpisode = episodesData.find((ep) => (ep.number ? +ep.number === episodeNumber : false));
|
||||
// if (!targetEpisode?.dataId) throw new NotFoundError('Episode not found');
|
||||
|
||||
// mediaId = targetEpisode.dataId;
|
||||
|
||||
// const sources = await ctx.proxiedFetcher<string>(`ajax/v2/episode/servers/${mediaId}`, {
|
||||
// baseUrl: gomoviesBase,
|
||||
// headers: {
|
||||
// 'X-Requested-With': 'XMLHttpRequest',
|
||||
// },
|
||||
// });
|
||||
|
||||
// const upcloudSource = await getSource(ctx, sources, 'upcloud');
|
||||
// const vidcloudSource = await getSource(ctx, sources, 'vidcloud');
|
||||
// const voeSource = await getSource(ctx, sources, 'voe');
|
||||
// const doodSource = await getSource(ctx, sources, 'doodstream');
|
||||
// const upstreamSource = await getSource(ctx, sources, 'upstream');
|
||||
// const mixdropSource = await getSource(ctx, sources, 'mixdrop');
|
||||
|
||||
// const embeds = [
|
||||
// {
|
||||
// embedId: upcloudScraper.id,
|
||||
// url: upcloudSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: vidCloudScraper.id,
|
||||
// url: vidcloudSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: voeScraper.id,
|
||||
// url: voeSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: doodScraper.id,
|
||||
// url: doodSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: upstreamScraper.id,
|
||||
// url: upstreamSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: mixdropScraper.id,
|
||||
// url: mixdropSource?.link,
|
||||
// },
|
||||
// ];
|
||||
|
||||
// const filteredEmbeds = embeds
|
||||
// .filter((embed) => embed.url)
|
||||
// .map((embed) => ({
|
||||
// embedId: embed.embedId,
|
||||
// url: embed.url as string,
|
||||
// }));
|
||||
|
||||
// if (filteredEmbeds.length === 0) throw new Error('No valid embeds found.');
|
||||
|
||||
// return {
|
||||
// embeds: filteredEmbeds,
|
||||
// };
|
||||
// },
|
||||
// async scrapeMovie(ctx) {
|
||||
// const search = await ctx.proxiedFetcher(`/search/${ctx.media.title.replaceAll(/[^a-z0-9A-Z]/g, '-')}`, {
|
||||
// method: 'GET',
|
||||
// headers: {
|
||||
// 'X-Requested-With': 'XMLHttpRequest',
|
||||
// },
|
||||
// baseUrl: gomoviesBase,
|
||||
// });
|
||||
|
||||
// const searchPage = load(search);
|
||||
// const mediaElements = searchPage('div.film-detail');
|
||||
|
||||
// const mediaData = mediaElements.toArray().map((movieEl) => {
|
||||
// const name = searchPage(movieEl).find('h2.film-name a')?.text();
|
||||
// const year = searchPage(movieEl).find('span.fdi-item:first')?.text();
|
||||
// const path = searchPage(movieEl).find('h2.film-name a').attr('href');
|
||||
// return { name, year, path };
|
||||
// });
|
||||
|
||||
// const targetMedia = mediaData.find(
|
||||
// (m) => m.name === ctx.media.title && m.year === ctx.media.releaseYear.toString(),
|
||||
// );
|
||||
// if (!targetMedia?.path) throw new NotFoundError('Media not found');
|
||||
|
||||
// // Example movie path: /movie/watch-{slug}-{id}
|
||||
// // Example series path: /tv/watch-{slug}-{id}
|
||||
// const mediaId = targetMedia.path.split('-').pop()?.replace('/', '');
|
||||
|
||||
// const sources = await ctx.proxiedFetcher<string>(`ajax/movie/episodes/${mediaId}`, {
|
||||
// headers: {
|
||||
// 'X-Requested-With': 'XMLHttpRequest',
|
||||
// },
|
||||
// baseUrl: gomoviesBase,
|
||||
// });
|
||||
|
||||
// const upcloudSource = await getSource(ctx, sources, 'upcloud');
|
||||
// const vidcloudSource = await getSource(ctx, sources, 'vidcloud');
|
||||
// const voeSource = await getSource(ctx, sources, 'voe');
|
||||
// const doodSource = await getSource(ctx, sources, 'doodstream');
|
||||
// const upstreamSource = await getSource(ctx, sources, 'upstream');
|
||||
// const mixdropSource = await getSource(ctx, sources, 'mixdrop');
|
||||
|
||||
// const embeds = [
|
||||
// {
|
||||
// embedId: upcloudScraper.id,
|
||||
// url: upcloudSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: vidCloudScraper.id,
|
||||
// url: vidcloudSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: voeScraper.id,
|
||||
// url: voeSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: doodScraper.id,
|
||||
// url: doodSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: upstreamScraper.id,
|
||||
// url: upstreamSource?.link,
|
||||
// },
|
||||
// {
|
||||
// embedId: mixdropScraper.id,
|
||||
// url: mixdropSource?.link,
|
||||
// },
|
||||
// ];
|
||||
|
||||
// const filteredEmbeds = embeds
|
||||
// .filter((embed) => embed.url)
|
||||
// .map((embed) => ({
|
||||
// embedId: embed.embedId,
|
||||
// url: embed.url as string,
|
||||
// }));
|
||||
|
||||
// if (filteredEmbeds.length === 0) throw new Error('No valid embeds found.');
|
||||
|
||||
// return {
|
||||
// embeds: filteredEmbeds,
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
30
src/providers/archive/sources/gomovies/source.ts
Normal file
30
src/providers/archive/sources/gomovies/source.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// import { load } from 'cheerio';
|
||||
|
||||
// import { ScrapeContext } from '@/utils/context';
|
||||
|
||||
// import { gomoviesBase } from '.';
|
||||
|
||||
// export async function getSource(ctx: ScrapeContext, sources: any, title: string) {
|
||||
// const source = load(sources)(`a[title*=${title} i]`);
|
||||
|
||||
// const sourceDataId = source?.attr('data-id') ?? source?.attr('data-linkid');
|
||||
|
||||
// if (!sourceDataId) return undefined;
|
||||
|
||||
// const sourceData = await ctx.proxiedFetcher<{
|
||||
// type: 'iframe' | string;
|
||||
// link: string;
|
||||
// sources: [];
|
||||
// title: string;
|
||||
// tracks: [];
|
||||
// }>(`/ajax/sources/${sourceDataId}`, {
|
||||
// headers: {
|
||||
// 'X-Requested-With': 'XMLHttpRequest',
|
||||
// },
|
||||
// baseUrl: gomoviesBase,
|
||||
// });
|
||||
|
||||
// if (!sourceData.link || sourceData.type !== 'iframe') return undefined;
|
||||
|
||||
// return sourceData;
|
||||
// }
|
||||
15
src/providers/archive/sources/kissasian/common.ts
Normal file
15
src/providers/archive/sources/kissasian/common.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// import { mp4uploadScraper } from '@/providers/embeds/mp4upload';
|
||||
// import { streamsbScraper } from '@/providers/embeds/streamsb';
|
||||
|
||||
// export const kissasianBase = 'https://kissasian.sh';
|
||||
|
||||
// export const embedProviders = [
|
||||
// {
|
||||
// type: mp4uploadScraper.id,
|
||||
// id: 'mp',
|
||||
// },
|
||||
// {
|
||||
// type: streamsbScraper.id,
|
||||
// id: 'sb',
|
||||
// },
|
||||
// ];
|
||||
55
src/providers/archive/sources/kissasian/getEmbeds.ts
Normal file
55
src/providers/archive/sources/kissasian/getEmbeds.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// import { load } from 'cheerio';
|
||||
|
||||
// import type { ScrapeContext } from '@/utils/context';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// import { embedProviders, kissasianBase } from './common';
|
||||
|
||||
// export async function getEmbeds(
|
||||
// ctx: ScrapeContext,
|
||||
// targetEpisode: {
|
||||
// number: string;
|
||||
// url?: string;
|
||||
// },
|
||||
// ) {
|
||||
// let embeds = await Promise.all(
|
||||
// embedProviders.map(async (provider) => {
|
||||
// if (!targetEpisode.url) throw new NotFoundError('Episode not found');
|
||||
// const watch = await ctx.proxiedFetcher<string>(`${targetEpisode.url}&s=${provider.id}`, {
|
||||
// baseUrl: kissasianBase,
|
||||
// headers: {
|
||||
// accept:
|
||||
// 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
|
||||
// 'accept-language': 'en-US,en;q=0.9',
|
||||
// 'cache-control': 'no-cache',
|
||||
// pragma: 'no-cache',
|
||||
// 'sec-ch-ua': '"Not)A;Brand";v="24", "Chromium";v="116"',
|
||||
// 'sec-ch-ua-mobile': '?0',
|
||||
// 'sec-ch-ua-platform': '"macOS"',
|
||||
// 'sec-fetch-dest': 'document',
|
||||
// 'sec-fetch-mode': 'navigate',
|
||||
// 'sec-fetch-site': 'cross-site',
|
||||
// 'sec-fetch-user': '?1',
|
||||
// 'upgrade-insecure-requests': '1',
|
||||
// cookie:
|
||||
// '__rd=; ASP.NET_SessionId=jwnl2kmlw5h4mfdaxvpk30q0; k_token=OKbJDFNx3rUtaw7iAA6UxMKSJb79lgZ2X2rVC9aupJhycYQKVSLaW1y2B4K%2f%2fo3i6BuzhXgfkJGmKlKH6LpNlKPPpZUk31n9DapfMdJgjlLExgrPS3jpSKwGnNUI%2bOpNpZu9%2fFnkLZRxvVKCa8APMxrck1tYkKXWqfyJJh8%2b7hQTI1wfAOU%2fLEouHhtQGL%2fReTzElw2LQ0XSL1pjs%2fkWW3rM3of2je7Oo13I%2f7olLFuiJUVWyNbn%2fYKSgNrm%2bQ3p',
|
||||
// },
|
||||
// });
|
||||
|
||||
// const watchPage = load(watch);
|
||||
|
||||
// const embedUrl = watchPage('#my_video_1').attr('src');
|
||||
|
||||
// if (!embedUrl) throw new Error('Embed not found');
|
||||
|
||||
// return {
|
||||
// embedId: provider.id,
|
||||
// url: embedUrl,
|
||||
// };
|
||||
// }),
|
||||
// );
|
||||
|
||||
// embeds = embeds.filter((e) => !!e.url);
|
||||
|
||||
// return embeds;
|
||||
// }
|
||||
76
src/providers/archive/sources/kissasian/index.ts
Normal file
76
src/providers/archive/sources/kissasian/index.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// import { load } from 'cheerio';
|
||||
|
||||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { makeSourcerer } from '@/providers/base';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// import { kissasianBase } from './common';
|
||||
// import { getEmbeds } from './getEmbeds';
|
||||
// import { getEpisodes } from './getEpisodes';
|
||||
// import { search } from './search';
|
||||
|
||||
// export const kissAsianScraper = makeSourcerer({
|
||||
// id: 'kissasian',
|
||||
// name: 'KissAsian',
|
||||
// rank: 40,
|
||||
// flags: [flags.CORS_ALLOWED],
|
||||
// disabled: true,
|
||||
|
||||
// async scrapeShow(ctx) {
|
||||
// const seasonNumber = ctx.media.season.number;
|
||||
// const episodeNumber = ctx.media.episode.number;
|
||||
|
||||
// const dramas = await search(ctx, ctx.media.title, seasonNumber);
|
||||
|
||||
// const targetDrama = dramas.find((d) => d.name?.toLowerCase() === ctx.media.title.toLowerCase()) ?? dramas[0];
|
||||
// if (!targetDrama) throw new NotFoundError('Drama not found');
|
||||
|
||||
// ctx.progress(30);
|
||||
|
||||
// const drama = await ctx.proxiedFetcher<string>(targetDrama.url, {
|
||||
// baseUrl: kissasianBase,
|
||||
// });
|
||||
|
||||
// const dramaPage = load(drama);
|
||||
|
||||
// const episodes = await getEpisodes(dramaPage);
|
||||
|
||||
// const targetEpisode = episodes.find((e) => e.number === `${episodeNumber}`);
|
||||
// if (!targetEpisode?.url) throw new NotFoundError('Episode not found');
|
||||
|
||||
// ctx.progress(70);
|
||||
|
||||
// const embeds = await getEmbeds(ctx, targetEpisode);
|
||||
|
||||
// return {
|
||||
// embeds,
|
||||
// };
|
||||
// },
|
||||
// async scrapeMovie(ctx) {
|
||||
// const dramas = await search(ctx, ctx.media.title, undefined);
|
||||
|
||||
// const targetDrama = dramas.find((d) => d.name?.toLowerCase() === ctx.media.title.toLowerCase()) ?? dramas[0];
|
||||
// if (!targetDrama) throw new NotFoundError('Drama not found');
|
||||
|
||||
// ctx.progress(30);
|
||||
|
||||
// const drama = await ctx.proxiedFetcher<string>(targetDrama.url, {
|
||||
// baseUrl: kissasianBase,
|
||||
// });
|
||||
|
||||
// const dramaPage = load(drama);
|
||||
|
||||
// const episodes = getEpisodes(dramaPage);
|
||||
|
||||
// const targetEpisode = episodes[0];
|
||||
// if (!targetEpisode?.url) throw new NotFoundError('Episode not found');
|
||||
|
||||
// ctx.progress(70);
|
||||
|
||||
// const embeds = await getEmbeds(ctx, targetEpisode);
|
||||
|
||||
// return {
|
||||
// embeds,
|
||||
// };
|
||||
// },
|
||||
// });
|
||||
27
src/providers/archive/sources/kissasian/search.ts
Normal file
27
src/providers/archive/sources/kissasian/search.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// import { load } from 'cheerio';
|
||||
// import FormData from 'form-data';
|
||||
|
||||
// import { ScrapeContext } from '@/utils/context';
|
||||
|
||||
// import { kissasianBase } from './common';
|
||||
|
||||
// export async function search(ctx: ScrapeContext, title: string, seasonNumber?: number) {
|
||||
// const searchForm = new FormData();
|
||||
// searchForm.append('keyword', `${title} ${seasonNumber ?? ''}`.trim());
|
||||
// searchForm.append('type', 'Drama');
|
||||
|
||||
// const searchResults = await ctx.proxiedFetcher<string>('/Search/SearchSuggest', {
|
||||
// baseUrl: kissasianBase,
|
||||
// method: 'POST',
|
||||
// body: searchForm,
|
||||
// });
|
||||
|
||||
// const searchPage = load(searchResults);
|
||||
|
||||
// return Array.from(searchPage('a')).map((drama) => {
|
||||
// return {
|
||||
// name: searchPage(drama).text(),
|
||||
// url: drama.attribs.href,
|
||||
// };
|
||||
// });
|
||||
// }
|
||||
49
src/providers/archive/sources/showbox/index.ts
Normal file
49
src/providers/archive/sources/showbox/index.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||
// import { febboxMp4Scraper } from '@/providers/embeds/febbox/mp4';
|
||||
// import { compareTitle } from '@/utils/compare';
|
||||
// import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// import { sendRequest } from './sendRequest';
|
||||
|
||||
// async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
|
||||
// const searchQuery = {
|
||||
// module: 'Search4',
|
||||
// page: '1',
|
||||
// type: 'all',
|
||||
// keyword: ctx.media.title,
|
||||
// pagelimit: '20',
|
||||
// };
|
||||
|
||||
// const searchRes = (await sendRequest(ctx, searchQuery, true)).data.list;
|
||||
// ctx.progress(50);
|
||||
|
||||
// const showboxEntry = searchRes.find(
|
||||
// (res: any) => compareTitle(res.title, ctx.media.title) && res.year === Number(ctx.media.releaseYear),
|
||||
// );
|
||||
// if (!showboxEntry) throw new NotFoundError('No entry found');
|
||||
|
||||
// const id = showboxEntry.id;
|
||||
// const season = ctx.media.type === 'show' ? ctx.media.season.number : '';
|
||||
// const episode = ctx.media.type === 'show' ? ctx.media.episode.number : '';
|
||||
|
||||
// return {
|
||||
// embeds: [
|
||||
// {
|
||||
// embedId: febboxMp4Scraper.id,
|
||||
// url: `/${ctx.media.type}/${id}/${season}/${episode}`,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// }
|
||||
|
||||
// export const showboxScraper = makeSourcerer({
|
||||
// id: 'showbox',
|
||||
// name: 'Showbox',
|
||||
// rank: 250,
|
||||
// disabled: true,
|
||||
// flags: [flags.CORS_ALLOWED, flags.CF_BLOCKED],
|
||||
// scrapeShow: comboScraper,
|
||||
// scrapeMovie: comboScraper,
|
||||
// });
|
||||
38
src/providers/archive/sources/smashystream/index.ts
Normal file
38
src/providers/archive/sources/smashystream/index.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||
// import { smashyStreamOScraper } from '@/providers/embeds/smashystream/opstream';
|
||||
// import { smashyStreamFScraper } from '@/providers/embeds/smashystream/video1';
|
||||
// import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
|
||||
// const universalScraper = async (ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> => {
|
||||
// // theres no point in fetching the player page
|
||||
// // because it too just calls the api with the tmdb id
|
||||
// // thats the only way to find out if the embed has any streams
|
||||
// const query =
|
||||
// ctx.media.type === 'movie'
|
||||
// ? `?tmdb=${ctx.media.tmdbId}`
|
||||
// : `?tmdb=${ctx.media.tmdbId}&season=${ctx.media.season.number}&episode=${ctx.media.episode.number}`;
|
||||
|
||||
// return {
|
||||
// embeds: [
|
||||
// {
|
||||
// embedId: smashyStreamFScraper.id,
|
||||
// url: `https://embed.smashystream.com/videofeee.php${query}`,
|
||||
// },
|
||||
// {
|
||||
// embedId: smashyStreamOScraper.id,
|
||||
// url: `https://embed.smashystream.com/shortmoviec.php${query}`,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// };
|
||||
|
||||
// export const smashyStreamScraper = makeSourcerer({
|
||||
// id: 'smashystream',
|
||||
// name: 'SmashyStream',
|
||||
// rank: 20,
|
||||
// disabled: true,
|
||||
// flags: [flags.CORS_ALLOWED],
|
||||
// scrapeMovie: universalScraper,
|
||||
// scrapeShow: universalScraper,
|
||||
// });
|
||||
65
src/providers/archive/sources/vidlink.ts
Normal file
65
src/providers/archive/sources/vidlink.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// /* eslint-disable no-console */
|
||||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { Caption, labelToLanguageCode } from '@/providers/captions';
|
||||
// import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// import { SourcererOutput, makeSourcerer } from '../base';
|
||||
|
||||
// async function comboScraper(ctx: MovieScrapeContext | ShowScrapeContext): Promise<SourcererOutput> {
|
||||
// const embedUrl =
|
||||
// ctx.media.type === 'movie'
|
||||
// ? `https://vidlink.pro/movie/${ctx.media.tmdbId}`
|
||||
// : `https://vidlink.pro/tv/${ctx.media.tmdbId}/${ctx.media.season.number}/${ctx.media.episode.number}`;
|
||||
|
||||
// const apiResponse = await ctx.proxiedFetcher<{
|
||||
// results: Array<{
|
||||
// video_urls: string[];
|
||||
// subtitles: Array<{ label: string; file: string }>;
|
||||
// }>;
|
||||
// }>('https://psvl.api.pstream.org/api/video-url', {
|
||||
// query: { embedUrl },
|
||||
// });
|
||||
|
||||
// if (!apiResponse?.results?.length) throw new NotFoundError('No results found');
|
||||
// const primaryResult = apiResponse.results[0];
|
||||
|
||||
// if (!primaryResult.video_urls?.length) throw new NotFoundError('No video URLs found');
|
||||
|
||||
// const captions: Caption[] = [];
|
||||
// for (const sub of primaryResult.subtitles) {
|
||||
// const language = labelToLanguageCode(sub.label.split('-')[0].trim());
|
||||
// if (!language) continue;
|
||||
|
||||
// captions.push({
|
||||
// id: sub.file,
|
||||
// url: sub.file,
|
||||
// type: 'vtt',
|
||||
// hasCorsRestrictions: false,
|
||||
// language,
|
||||
// });
|
||||
// }
|
||||
|
||||
// return {
|
||||
// embeds: [],
|
||||
// stream: [
|
||||
// {
|
||||
// id: 'primary',
|
||||
// playlist: primaryResult.video_urls[0],
|
||||
// type: 'hls',
|
||||
// flags: [flags.CORS_ALLOWED],
|
||||
// captions,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// }
|
||||
|
||||
// export const vidlinkScraper = makeSourcerer({
|
||||
// id: 'vidlink',
|
||||
// name: 'PSVL',
|
||||
// rank: 113,
|
||||
// disabled: true,
|
||||
// flags: [flags.CORS_ALLOWED],
|
||||
// scrapeMovie: comboScraper,
|
||||
// scrapeShow: comboScraper,
|
||||
// });
|
||||
13
src/providers/archive/sources/vidsrc/index.ts
Normal file
13
src/providers/archive/sources/vidsrc/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// 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: 116,
|
||||
// disabled: true,
|
||||
// flags: [],
|
||||
// scrapeMovie,
|
||||
// scrapeShow,
|
||||
// });
|
||||
8
src/providers/archive/sources/vidsrc/scrape-movie.ts
Normal file
8
src/providers/archive/sources/vidsrc/scrape-movie.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// import { getVidSrcMovieSources } from '@/providers/sources/vidsrc/scrape';
|
||||
// import { MovieScrapeContext } from '@/utils/context';
|
||||
|
||||
// export async function scrapeMovie(ctx: MovieScrapeContext) {
|
||||
// return {
|
||||
// embeds: await getVidSrcMovieSources(ctx),
|
||||
// };
|
||||
// }
|
||||
8
src/providers/archive/sources/vidsrc/scrape-show.ts
Normal file
8
src/providers/archive/sources/vidsrc/scrape-show.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// import { getVidSrcShowSources } from '@/providers/sources/vidsrc/scrape';
|
||||
// import { ShowScrapeContext } from '@/utils/context';
|
||||
|
||||
// export async function scrapeShow(ctx: ShowScrapeContext) {
|
||||
// return {
|
||||
// embeds: await getVidSrcShowSources(ctx),
|
||||
// };
|
||||
// }
|
||||
133
src/providers/archive/sources/vidsrc/scrape.ts
Normal file
133
src/providers/archive/sources/vidsrc/scrape.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
// import { load } from 'cheerio';
|
||||
|
||||
// import { SourcererEmbed } from '@/providers/base';
|
||||
// import { streambucketScraper } from '@/providers/embeds/streambucket';
|
||||
// import { vidsrcembedScraper } from '@/providers/embeds/vidsrc';
|
||||
// import { vidsrcBase, vidsrcRCPBase } from '@/providers/sources/vidsrc/common';
|
||||
// import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
|
||||
// function decodeSrc(encoded: string, seed: string) {
|
||||
// let decoded = '';
|
||||
// const seedLength = 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;
|
||||
// }
|
||||
|
||||
// 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
|
||||
// // 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: SourcererEmbed[] = [];
|
||||
|
||||
// let html = await ctx.proxiedFetcher<string>(startingURL, {
|
||||
// baseUrl: vidsrcBase,
|
||||
// });
|
||||
|
||||
// let $ = load(html);
|
||||
|
||||
// const sourceHashes = $('.server[data-hash]')
|
||||
// .toArray()
|
||||
// .map((el) => $(el).attr('data-hash'))
|
||||
// .filter((hash) => hash !== undefined);
|
||||
|
||||
// for (const hash of sourceHashes) {
|
||||
// html = await ctx.proxiedFetcher<string>(`/rcp/${hash}`, {
|
||||
// baseUrl: vidsrcRCPBase,
|
||||
// headers: {
|
||||
// referer: vidsrcBase,
|
||||
// },
|
||||
// });
|
||||
|
||||
// $ = 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}`;
|
||||
// }
|
||||
|
||||
// const { finalUrl } = await ctx.proxiedFetcher.full(redirectURL, {
|
||||
// method: 'HEAD',
|
||||
// headers: {
|
||||
// referer: vidsrcBase,
|
||||
// },
|
||||
// });
|
||||
|
||||
// const embed: SourcererEmbed = {
|
||||
// embedId: '',
|
||||
// url: finalUrl,
|
||||
// };
|
||||
|
||||
// const parsedUrl = new URL(finalUrl);
|
||||
|
||||
// switch (parsedUrl.host) {
|
||||
// case 'vidsrc.stream':
|
||||
// embed.embedId = vidsrcembedScraper.id;
|
||||
// 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 ${finalUrl}`);
|
||||
// }
|
||||
|
||||
// // 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: MovieScrapeContext) {
|
||||
// return getVidSrcEmbeds(ctx, `/embed/${ctx.media.tmdbId}`);
|
||||
// }
|
||||
|
||||
// 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
|
||||
// // all seasons. To get the real embed URL, have to
|
||||
// // parse this from the response
|
||||
// const html = await ctx.proxiedFetcher<string>(`/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);
|
||||
// }
|
||||
71
src/providers/archive/sources/zoechip/common.ts
Normal file
71
src/providers/archive/sources/zoechip/common.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// import { mixdropScraper } from '@/providers/embeds/mixdrop';
|
||||
// import { upcloudScraper } from '@/providers/embeds/upcloud';
|
||||
// import { upstreamScraper } from '@/providers/embeds/upstream';
|
||||
// import { vidCloudScraper } from '@/providers/embeds/vidcloud';
|
||||
// import { getZoeChipSourceURL, getZoeChipSources } from '@/providers/sources/zoechip/scrape';
|
||||
// import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
|
||||
// export const zoeBase = 'https://zoechip.cc';
|
||||
|
||||
// export type ZoeChipSourceDetails = {
|
||||
// type: string; // Only seen "iframe" so far
|
||||
// link: string;
|
||||
// sources: string[]; // Never seen this populated, assuming it's a string array
|
||||
// tracks: string[]; // Never seen this populated, assuming it's a string array
|
||||
// title: string;
|
||||
// };
|
||||
|
||||
// export async function formatSource(
|
||||
// ctx: MovieScrapeContext | ShowScrapeContext,
|
||||
// source: { embed: string; episodeId: string },
|
||||
// ) {
|
||||
// const link = await getZoeChipSourceURL(ctx, source.episodeId);
|
||||
// if (link) {
|
||||
// const embed = {
|
||||
// embedId: '',
|
||||
// url: link,
|
||||
// };
|
||||
|
||||
// const parsedUrl = new URL(link);
|
||||
|
||||
// switch (parsedUrl.host) {
|
||||
// case 'rabbitstream.net':
|
||||
// embed.embedId = upcloudScraper.id;
|
||||
// break;
|
||||
// case 'upstream.to':
|
||||
// embed.embedId = upstreamScraper.id;
|
||||
// break;
|
||||
// case 'mixdrop.co':
|
||||
// embed.embedId = mixdropScraper.id;
|
||||
// break;
|
||||
// default:
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// return embed;
|
||||
// }
|
||||
// }
|
||||
|
||||
// export async function createZoeChipStreamData(ctx: MovieScrapeContext | ShowScrapeContext, id: string) {
|
||||
// const sources = await getZoeChipSources(ctx, id);
|
||||
// const embeds: {
|
||||
// embedId: string;
|
||||
// url: string;
|
||||
// }[] = [];
|
||||
|
||||
// for (const source of sources) {
|
||||
// const formatted = await formatSource(ctx, source);
|
||||
// if (formatted) {
|
||||
// // Zoechip does not return titles for their sources, so we can not check if a source is upcloud or vidcloud because the domain is the same.
|
||||
// const upCloudAlreadyExists = embeds.find((e) => e.embedId === upcloudScraper.id);
|
||||
// if (formatted.embedId === upcloudScraper.id && upCloudAlreadyExists) {
|
||||
// formatted.embedId = vidCloudScraper.id;
|
||||
// }
|
||||
// embeds.push(formatted);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return {
|
||||
// embeds,
|
||||
// };
|
||||
// }
|
||||
14
src/providers/archive/sources/zoechip/index.ts
Normal file
14
src/providers/archive/sources/zoechip/index.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// import { flags } from '@/entrypoint/utils/targets';
|
||||
// import { makeSourcerer } from '@/providers/base';
|
||||
// import { scrapeMovie } from '@/providers/sources/zoechip/scrape-movie';
|
||||
// import { scrapeShow } from '@/providers/sources/zoechip/scrape-show';
|
||||
|
||||
// export const zoechipScraper = makeSourcerer({
|
||||
// id: 'zoechip',
|
||||
// name: 'ZoeChip',
|
||||
// rank: 240,
|
||||
// flags: [flags.CORS_ALLOWED],
|
||||
// disabled: true,
|
||||
// scrapeMovie,
|
||||
// scrapeShow,
|
||||
// });
|
||||
13
src/providers/archive/sources/zoechip/scrape-movie.ts
Normal file
13
src/providers/archive/sources/zoechip/scrape-movie.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// import { createZoeChipStreamData } from '@/providers/sources/zoechip/common';
|
||||
// import { getZoeChipMovieID } from '@/providers/sources/zoechip/search';
|
||||
// import { MovieScrapeContext } from '@/utils/context';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// export async function scrapeMovie(ctx: MovieScrapeContext) {
|
||||
// const movieID = await getZoeChipMovieID(ctx, ctx.media);
|
||||
// if (!movieID) {
|
||||
// throw new NotFoundError('no search results match');
|
||||
// }
|
||||
|
||||
// return createZoeChipStreamData(ctx, movieID);
|
||||
// }
|
||||
24
src/providers/archive/sources/zoechip/scrape-show.ts
Normal file
24
src/providers/archive/sources/zoechip/scrape-show.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// import { createZoeChipStreamData } from '@/providers/sources/zoechip/common';
|
||||
// import { getZoeChipEpisodeID, getZoeChipSeasonID } from '@/providers/sources/zoechip/scrape';
|
||||
// import { getZoeChipShowID } from '@/providers/sources/zoechip/search';
|
||||
// import { ShowScrapeContext } from '@/utils/context';
|
||||
// import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
// export async function scrapeShow(ctx: ShowScrapeContext) {
|
||||
// const showID = await getZoeChipShowID(ctx, ctx.media);
|
||||
// if (!showID) {
|
||||
// throw new NotFoundError('no search results match');
|
||||
// }
|
||||
|
||||
// const seasonID = await getZoeChipSeasonID(ctx, ctx.media, showID);
|
||||
// if (!seasonID) {
|
||||
// throw new NotFoundError('no season found');
|
||||
// }
|
||||
|
||||
// const episodeID = await getZoeChipEpisodeID(ctx, ctx.media, seasonID);
|
||||
// if (!episodeID) {
|
||||
// throw new NotFoundError('no episode found');
|
||||
// }
|
||||
|
||||
// return createZoeChipStreamData(ctx, episodeID);
|
||||
// }
|
||||
126
src/providers/archive/sources/zoechip/scrape.ts
Normal file
126
src/providers/archive/sources/zoechip/scrape.ts
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
// import { load } from 'cheerio';
|
||||
|
||||
// import { ShowMedia } from '@/entrypoint/utils/media';
|
||||
// import { ZoeChipSourceDetails, zoeBase } from '@/providers/sources/zoechip/common';
|
||||
// import { MovieScrapeContext, ScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||
|
||||
// export async function getZoeChipSources(ctx: MovieScrapeContext | ShowScrapeContext, id: string) {
|
||||
// // Movies use /ajax/episode/list/ID
|
||||
// // Shows use /ajax/episode/servers/ID
|
||||
// const endpoint = ctx.media.type === 'movie' ? 'list' : 'servers';
|
||||
// const html = await ctx.proxiedFetcher<string>(`/ajax/episode/${endpoint}/${id}`, {
|
||||
// baseUrl: zoeBase,
|
||||
// });
|
||||
// const $ = load(html);
|
||||
|
||||
// return $('.nav-item a')
|
||||
// .toArray()
|
||||
// .map((el) => {
|
||||
// // Movies use data-linkid
|
||||
// // Shows use data-id
|
||||
// const idAttribute = ctx.media.type === 'movie' ? 'data-linkid' : 'data-id';
|
||||
// const element = $(el);
|
||||
// const embedTitle = element.attr('title');
|
||||
// const linkId = element.attr(idAttribute);
|
||||
|
||||
// if (!embedTitle || !linkId) {
|
||||
// throw new Error('invalid sources');
|
||||
// }
|
||||
|
||||
// return {
|
||||
// embed: embedTitle,
|
||||
// episodeId: linkId,
|
||||
// };
|
||||
// });
|
||||
// }
|
||||
|
||||
// export async function getZoeChipSourceURL(ctx: ScrapeContext, sourceID: string): Promise<string | null> {
|
||||
// const details = await ctx.proxiedFetcher<ZoeChipSourceDetails>(`/ajax/sources/${sourceID}`, {
|
||||
// baseUrl: zoeBase,
|
||||
// });
|
||||
|
||||
// // TODO - Support non-iframe sources
|
||||
// if (details.type !== 'iframe') {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// // TODO - Extract the other data from the source
|
||||
|
||||
// return details.link;
|
||||
// }
|
||||
|
||||
// export async function getZoeChipSeasonID(ctx: ScrapeContext, media: ShowMedia, showID: string): Promise<string | null> {
|
||||
// const html = await ctx.proxiedFetcher<string>(`/ajax/season/list/${showID}`, {
|
||||
// baseUrl: zoeBase,
|
||||
// });
|
||||
|
||||
// const $ = load(html);
|
||||
|
||||
// const seasons = $('.dropdown-menu a')
|
||||
// .toArray()
|
||||
// .map((el) => {
|
||||
// const element = $(el);
|
||||
// const seasonID = element.attr('data-id');
|
||||
// const seasonNumber = element.html()?.split(' ')[1];
|
||||
|
||||
// if (!seasonID || !seasonNumber || Number.isNaN(Number(seasonNumber))) {
|
||||
// throw new Error('invalid season');
|
||||
// }
|
||||
|
||||
// return {
|
||||
// id: seasonID,
|
||||
// season: Number(seasonNumber),
|
||||
// };
|
||||
// });
|
||||
|
||||
// const foundSeason = seasons.find((season) => season.season === media.season.number);
|
||||
|
||||
// if (!foundSeason) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// return foundSeason.id;
|
||||
// }
|
||||
|
||||
// export async function getZoeChipEpisodeID(
|
||||
// ctx: ScrapeContext,
|
||||
// media: ShowMedia,
|
||||
// seasonID: string,
|
||||
// ): Promise<string | null> {
|
||||
// const episodeNumberRegex = /Eps (\d*):/;
|
||||
// const html = await ctx.proxiedFetcher<string>(`/ajax/season/episodes/${seasonID}`, {
|
||||
// baseUrl: zoeBase,
|
||||
// });
|
||||
|
||||
// const $ = load(html);
|
||||
|
||||
// const episodes = $('.eps-item')
|
||||
// .toArray()
|
||||
// .map((el) => {
|
||||
// const element = $(el);
|
||||
// const episodeID = element.attr('data-id');
|
||||
// const title = element.attr('title');
|
||||
|
||||
// if (!episodeID || !title) {
|
||||
// throw new Error('invalid episode');
|
||||
// }
|
||||
|
||||
// const regexResult = title.match(episodeNumberRegex);
|
||||
// if (!regexResult || Number.isNaN(Number(regexResult[1]))) {
|
||||
// throw new Error('invalid episode');
|
||||
// }
|
||||
|
||||
// return {
|
||||
// id: episodeID,
|
||||
// episode: Number(regexResult[1]),
|
||||
// };
|
||||
// });
|
||||
|
||||
// const foundEpisode = episodes.find((episode) => episode.episode === media.episode.number);
|
||||
|
||||
// if (!foundEpisode) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// return foundEpisode.id;
|
||||
// }
|
||||
111
src/providers/archive/sources/zoechip/search.ts
Normal file
111
src/providers/archive/sources/zoechip/search.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// import { load } from 'cheerio';
|
||||
|
||||
// import { MovieMedia, ShowMedia } from '@/entrypoint/utils/media';
|
||||
// import { zoeBase } from '@/providers/sources/zoechip/common';
|
||||
// import { compareMedia } from '@/utils/compare';
|
||||
// import { ScrapeContext } from '@/utils/context';
|
||||
|
||||
// export async function getZoeChipSearchResults(ctx: ScrapeContext, media: MovieMedia | ShowMedia) {
|
||||
// const titleCleaned = media.title.toLocaleLowerCase().replace(/ /g, '-');
|
||||
|
||||
// const html = await ctx.proxiedFetcher<string>(`/search/${titleCleaned}`, {
|
||||
// baseUrl: zoeBase,
|
||||
// });
|
||||
|
||||
// const $ = load(html);
|
||||
// return $('.film_list-wrap .flw-item .film-detail')
|
||||
// .toArray()
|
||||
// .map((element) => {
|
||||
// const movie = $(element);
|
||||
// const anchor = movie.find('.film-name a');
|
||||
// const info = movie.find('.fd-infor');
|
||||
|
||||
// const title = anchor.attr('title');
|
||||
// const href = anchor.attr('href');
|
||||
// const type = info.find('.fdi-type').html();
|
||||
// let year = info.find('.fdi-item').html();
|
||||
// const id = href?.split('-').pop();
|
||||
|
||||
// if (!title) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// if (!href) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// if (!type) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// // TV shows on ZoeChip do not have a year in their search results
|
||||
// // Allow TV shows to pass this failure
|
||||
// if (!year || Number.isNaN(Number(year))) {
|
||||
// if (type === 'TV') {
|
||||
// year = '0';
|
||||
// } else {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!id) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// return {
|
||||
// title,
|
||||
// year: Number(year),
|
||||
// id,
|
||||
// type,
|
||||
// href,
|
||||
// };
|
||||
// });
|
||||
// }
|
||||
|
||||
// export async function getZoeChipMovieID(ctx: ScrapeContext, media: MovieMedia): Promise<string | null> {
|
||||
// const searchResults = await getZoeChipSearchResults(ctx, media);
|
||||
|
||||
// const matchingItem = searchResults.find((v) => v && v.type === 'Movie' && compareMedia(media, v.title, v.year));
|
||||
|
||||
// if (!matchingItem) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// return matchingItem.id;
|
||||
// }
|
||||
|
||||
// export async function getZoeChipShowID(ctx: ScrapeContext, media: ShowMedia): Promise<string | null> {
|
||||
// // ZoeChip TV shows don't have a year on their search results
|
||||
// // This makes it hard to filter between shows with the same name
|
||||
// // To find the year, we must query each shows details page
|
||||
// // This is slower, but more reliable
|
||||
|
||||
// const releasedRegex = /<\/strong><\/span> (\d.*)-\d.*-\d.*/;
|
||||
// const searchResults = await getZoeChipSearchResults(ctx, media);
|
||||
|
||||
// const filtered = searchResults.filter((v) => v && v.type === 'TV' && compareMedia(media, v.title));
|
||||
|
||||
// for (const result of filtered) {
|
||||
// // This gets filtered above but the linter Gods don't think so
|
||||
// if (!result) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// const html = await ctx.proxiedFetcher<string>(result.href, {
|
||||
// baseUrl: zoeBase,
|
||||
// });
|
||||
|
||||
// // The HTML is not structured in a way that makes using Cheerio clean
|
||||
// // There are no unique IDs or classes to query, resulting in long ugly queries
|
||||
// // Regex is faster and cleaner in this case
|
||||
// const regexResult = html.match(releasedRegex);
|
||||
// if (regexResult) {
|
||||
// const year = Number(regexResult[1]);
|
||||
// if (!Number.isNaN(year) && compareMedia(media, result.title, year)) {
|
||||
// return result.id;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return null;
|
||||
// }
|
||||
BIN
src/providers/embeds/.DS_Store
vendored
Normal file
BIN
src/providers/embeds/.DS_Store
vendored
Normal file
Binary file not shown.
|
|
@ -1,52 +0,0 @@
|
|||
import { unpack } from 'unpacker';
|
||||
|
||||
import { flags } from '@/entrypoint/utils/targets';
|
||||
|
||||
import { makeEmbed } from '../base';
|
||||
|
||||
const evalCodeRegex = /eval\((.*)\)/g;
|
||||
const fileRegex = /file:"(.*?)"/g;
|
||||
const tracksRegex = /\{file:"([^"]+)",kind:"thumbnails"\}/g;
|
||||
|
||||
export const droploadScraper = makeEmbed({
|
||||
id: 'dropload',
|
||||
name: 'Dropload',
|
||||
rank: 120,
|
||||
scrape: async (ctx) => {
|
||||
const mainPageRes = await ctx.proxiedFetcher.full<string>(ctx.url, {
|
||||
headers: {
|
||||
referer: ctx.url,
|
||||
},
|
||||
});
|
||||
const mainPageUrl = new URL(mainPageRes.finalUrl);
|
||||
const mainPage = mainPageRes.body;
|
||||
|
||||
const evalCode = mainPage.match(evalCodeRegex);
|
||||
if (!evalCode) throw new Error('Failed to find eval code');
|
||||
const unpacked = unpack(evalCode[1]);
|
||||
|
||||
const file = fileRegex.exec(unpacked);
|
||||
const thumbnailTrack = tracksRegex.exec(unpacked);
|
||||
if (!file?.[1]) throw new Error('Failed to find file');
|
||||
|
||||
return {
|
||||
stream: [
|
||||
{
|
||||
id: 'primary',
|
||||
type: 'hls',
|
||||
playlist: file[1],
|
||||
flags: [flags.IP_LOCKED, flags.CORS_ALLOWED],
|
||||
captions: [],
|
||||
...(thumbnailTrack
|
||||
? {
|
||||
thumbnailTrack: {
|
||||
type: 'vtt',
|
||||
url: mainPageUrl.origin + thumbnailTrack[1],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
import { MediaTypes } from '@/entrypoint/utils/media';
|
||||
|
||||
export const febBoxBase = `https://www.febbox.com`;
|
||||
|
||||
export interface FebboxFileList {
|
||||
file_name: string;
|
||||
ext: string;
|
||||
fid: number;
|
||||
oss_fid: number;
|
||||
is_dir: 0 | 1;
|
||||
}
|
||||
|
||||
export function parseInputUrl(url: string) {
|
||||
const [type, id, seasonId, episodeId] = url.slice(1).split('/');
|
||||
const season = seasonId ? parseInt(seasonId, 10) : undefined;
|
||||
const episode = episodeId ? parseInt(episodeId, 10) : undefined;
|
||||
|
||||
return {
|
||||
type: type as MediaTypes,
|
||||
id,
|
||||
season,
|
||||
episode,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import { MediaTypes } from '@/entrypoint/utils/media';
|
||||
import { FebboxFileList, febBoxBase } from '@/providers/embeds/febbox/common';
|
||||
import { EmbedScrapeContext } from '@/utils/context';
|
||||
|
||||
export async function getFileList(
|
||||
ctx: EmbedScrapeContext,
|
||||
shareKey: string,
|
||||
parentId?: number,
|
||||
): Promise<FebboxFileList[]> {
|
||||
const query: Record<string, string> = {
|
||||
share_key: shareKey,
|
||||
pwd: '',
|
||||
};
|
||||
if (parentId) {
|
||||
query.parent_id = parentId.toString();
|
||||
query.page = '1';
|
||||
}
|
||||
|
||||
const streams = await ctx.proxiedFetcher<{
|
||||
data?: {
|
||||
file_list?: FebboxFileList[];
|
||||
};
|
||||
}>('/file/file_share_list', {
|
||||
headers: {
|
||||
'accept-language': 'en', // without this header, the request is marked as a webscraper
|
||||
},
|
||||
baseUrl: febBoxBase,
|
||||
query,
|
||||
});
|
||||
|
||||
return streams.data?.file_list ?? [];
|
||||
}
|
||||
|
||||
function isValidStream(file: FebboxFileList): boolean {
|
||||
return file.ext === 'mp4' || file.ext === 'mkv';
|
||||
}
|
||||
|
||||
export async function getStreams(
|
||||
ctx: EmbedScrapeContext,
|
||||
shareKey: string,
|
||||
type: MediaTypes,
|
||||
season?: number,
|
||||
episode?: number,
|
||||
): Promise<FebboxFileList[]> {
|
||||
const streams = await getFileList(ctx, shareKey);
|
||||
|
||||
if (type === 'show') {
|
||||
const seasonFolder = streams.find((v) => {
|
||||
if (!v.is_dir) return false;
|
||||
return v.file_name.toLowerCase() === `season ${season}`;
|
||||
});
|
||||
if (!seasonFolder) return [];
|
||||
|
||||
const episodes = await getFileList(ctx, shareKey, seasonFolder.fid);
|
||||
const s = season?.toString() ?? '0';
|
||||
const e = episode?.toString() ?? '0';
|
||||
const episodeRegex = new RegExp(`[Ss]0*${s}[Ee]0*${e}`);
|
||||
return episodes
|
||||
.filter((file) => {
|
||||
if (file.is_dir) return false;
|
||||
const match = file.file_name.match(episodeRegex);
|
||||
if (!match) return false;
|
||||
return true;
|
||||
})
|
||||
.filter(isValidStream);
|
||||
}
|
||||
|
||||
return streams.filter((v) => !v.is_dir).filter(isValidStream);
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import { MediaTypes } from '@/entrypoint/utils/media';
|
||||
import { makeEmbed } from '@/providers/base';
|
||||
import { parseInputUrl } from '@/providers/embeds/febbox/common';
|
||||
import { getStreams } from '@/providers/embeds/febbox/fileList';
|
||||
import { getSubtitles } from '@/providers/embeds/febbox/subtitles';
|
||||
import { showboxBase } from '@/providers/sources/showbox/common';
|
||||
|
||||
// structure: https://www.febbox.com/share/<random_key>
|
||||
export function extractShareKey(url: string): string {
|
||||
const parsedUrl = new URL(url);
|
||||
const shareKey = parsedUrl.pathname.split('/')[2];
|
||||
return shareKey;
|
||||
}
|
||||
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<{
|
||||
data?: { link?: string };
|
||||
}>('/index/share_link', {
|
||||
baseUrl: showboxBase,
|
||||
query: {
|
||||
id,
|
||||
type: type === 'movie' ? '1' : '2',
|
||||
},
|
||||
});
|
||||
if (!sharelinkResult?.data?.link) throw new Error('No embed url found');
|
||||
ctx.progress(30);
|
||||
const shareKey = extractShareKey(sharelinkResult.data.link);
|
||||
const fileList = await getStreams(ctx, shareKey, type, season, episode);
|
||||
const firstStream = fileList[0];
|
||||
if (!firstStream) throw new Error('No playable mp4 stream found');
|
||||
ctx.progress(70);
|
||||
|
||||
return {
|
||||
stream: [
|
||||
{
|
||||
id: 'primary',
|
||||
type: 'hls',
|
||||
flags: [],
|
||||
captions: await getSubtitles(ctx, id, firstStream.fid, type as MediaTypes, season, episode),
|
||||
playlist: `https://www.febbox.com/hls/main/${firstStream.oss_fid}.m3u8`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
import { flags } from '@/entrypoint/utils/targets';
|
||||
import { makeEmbed } from '@/providers/base';
|
||||
import { parseInputUrl } from '@/providers/embeds/febbox/common';
|
||||
import { getStreamQualities } from '@/providers/embeds/febbox/qualities';
|
||||
import { getSubtitles } from '@/providers/embeds/febbox/subtitles';
|
||||
|
||||
export const febboxMp4Scraper = makeEmbed({
|
||||
id: 'febbox-mp4',
|
||||
name: 'Febbox (MP4)',
|
||||
rank: 190,
|
||||
async scrape(ctx) {
|
||||
const { type, id, season, episode } = parseInputUrl(ctx.url);
|
||||
let apiQuery: object | null = null;
|
||||
|
||||
if (type === 'movie') {
|
||||
apiQuery = {
|
||||
uid: '',
|
||||
module: 'Movie_downloadurl_v3',
|
||||
mid: id,
|
||||
oss: '1',
|
||||
group: '',
|
||||
};
|
||||
} else if (type === 'show') {
|
||||
apiQuery = {
|
||||
uid: '',
|
||||
module: 'TV_downloadurl_v3',
|
||||
tid: id,
|
||||
season,
|
||||
episode,
|
||||
oss: '1',
|
||||
group: '',
|
||||
};
|
||||
}
|
||||
|
||||
if (!apiQuery) throw Error('Incorrect type');
|
||||
|
||||
const { qualities, fid } = await getStreamQualities(ctx, apiQuery);
|
||||
if (fid === undefined) throw new Error('No streamable file found');
|
||||
ctx.progress(70);
|
||||
|
||||
return {
|
||||
stream: [
|
||||
{
|
||||
id: 'primary',
|
||||
captions: await getSubtitles(ctx, id, fid, type, episode, season),
|
||||
qualities,
|
||||
type: 'file',
|
||||
flags: [flags.CORS_ALLOWED],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import { sendRequest } from '@/providers/sources/showbox/sendRequest';
|
||||
import { StreamFile } from '@/providers/streams';
|
||||
import { ScrapeContext } from '@/utils/context';
|
||||
|
||||
const allowedQualities = ['360', '480', '720', '1080', '4k'];
|
||||
|
||||
interface FebboxQuality {
|
||||
path: string;
|
||||
real_quality: string;
|
||||
fid?: number;
|
||||
}
|
||||
|
||||
function mapToQuality(quality: FebboxQuality): FebboxQuality | null {
|
||||
const q = quality.real_quality.replace('p', '').toLowerCase();
|
||||
if (!allowedQualities.includes(q)) return null;
|
||||
return {
|
||||
real_quality: q,
|
||||
path: quality.path,
|
||||
fid: quality.fid,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getStreamQualities(ctx: ScrapeContext, apiQuery: object) {
|
||||
const mediaRes: { list: FebboxQuality[] } = (await sendRequest(ctx, apiQuery)).data;
|
||||
|
||||
const qualityMap = mediaRes.list.map((v) => mapToQuality(v)).filter((v): v is FebboxQuality => !!v);
|
||||
|
||||
const qualities: Record<string, StreamFile> = {};
|
||||
|
||||
allowedQualities.forEach((quality) => {
|
||||
const foundQuality = qualityMap.find((q) => q.real_quality === quality && q.path);
|
||||
if (foundQuality) {
|
||||
qualities[quality] = {
|
||||
type: 'mp4',
|
||||
url: foundQuality.path,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
qualities,
|
||||
fid: mediaRes.list[0]?.fid,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
import {
|
||||
Caption,
|
||||
getCaptionTypeFromUrl,
|
||||
isValidLanguageCode,
|
||||
removeDuplicatedLanguages as removeDuplicateLanguages,
|
||||
} from '@/providers/captions';
|
||||
import { captionsDomains } from '@/providers/sources/showbox/common';
|
||||
import { sendRequest } from '@/providers/sources/showbox/sendRequest';
|
||||
import { ScrapeContext } from '@/utils/context';
|
||||
|
||||
interface CaptionApiResponse {
|
||||
data: {
|
||||
list: {
|
||||
subtitles: {
|
||||
order: number;
|
||||
lang: string;
|
||||
file_path: string;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
export async function getSubtitles(
|
||||
ctx: ScrapeContext,
|
||||
id: string,
|
||||
fid: number | undefined,
|
||||
type: 'show' | 'movie',
|
||||
episodeId?: number,
|
||||
seasonId?: number,
|
||||
): Promise<Caption[]> {
|
||||
const module = type === 'movie' ? 'Movie_srt_list_v2' : 'TV_srt_list_v2';
|
||||
const subtitleApiQuery = {
|
||||
fid,
|
||||
uid: '',
|
||||
module,
|
||||
mid: type === 'movie' ? id : undefined,
|
||||
tid: type !== 'movie' ? id : undefined,
|
||||
episode: episodeId?.toString(),
|
||||
season: seasonId?.toString(),
|
||||
};
|
||||
|
||||
const subResult = (await sendRequest(ctx, subtitleApiQuery)) as CaptionApiResponse;
|
||||
const subtitleList = subResult.data.list;
|
||||
let output: Caption[] = [];
|
||||
|
||||
subtitleList.forEach((sub) => {
|
||||
const subtitle = sub.subtitles.sort((a, b) => b.order - a.order)[0];
|
||||
if (!subtitle) return;
|
||||
|
||||
const subtitleFilePath = subtitle.file_path
|
||||
.replace(captionsDomains[0], captionsDomains[1])
|
||||
.replace(/\s/g, '+')
|
||||
.replace(/[()]/g, (c) => {
|
||||
return `%${c.charCodeAt(0).toString(16)}`;
|
||||
});
|
||||
|
||||
const subtitleType = getCaptionTypeFromUrl(subtitleFilePath);
|
||||
if (!subtitleType) return;
|
||||
|
||||
const validCode = isValidLanguageCode(subtitle.lang);
|
||||
if (!validCode) return;
|
||||
|
||||
output.push({
|
||||
id: subtitleFilePath,
|
||||
language: subtitle.lang,
|
||||
hasCorsRestrictions: true,
|
||||
type: subtitleType,
|
||||
url: subtitleFilePath,
|
||||
});
|
||||
});
|
||||
|
||||
output = removeDuplicateLanguages(output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
import { load } from 'cheerio';
|
||||
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: 300,
|
||||
scrape: async (ctx) => {
|
||||
const embedRes = await ctx.proxiedFetcher<string>(ctx.url, {
|
||||
headers: {
|
||||
referer: ctx.url,
|
||||
},
|
||||
});
|
||||
const embedHtml = load(embedRes);
|
||||
const evalCode = embedHtml('script').text().match(evalCodeRegex);
|
||||
if (!evalCode) throw new Error('Failed to find eval code');
|
||||
const unpacked = unpack(evalCode[0]);
|
||||
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.proxiedFetcher<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.IP_LOCKED],
|
||||
captions,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import { flags } from '@/entrypoint/utils/targets';
|
||||
import { NotFoundError } from '@/utils/errors';
|
||||
|
||||
import { makeEmbed } from '../../base';
|
||||
|
||||
import { fileMoonScraper } from './index';
|
||||
|
||||
export const fileMoonMp4Scraper = makeEmbed({
|
||||
id: 'filemoon-mp4',
|
||||
name: 'Filemoon MP4',
|
||||
rank: 400,
|
||||
scrape: async (ctx) => {
|
||||
const result = await fileMoonScraper.scrape(ctx);
|
||||
|
||||
if (!result.stream) throw new NotFoundError('Failed to find result');
|
||||
|
||||
if (result.stream[0].type !== 'hls') throw new NotFoundError('Failed to find hls stream');
|
||||
|
||||
const url = result.stream[0].playlist.replace(/\/hls2\//, '/download/').replace(/\.m3u8/, '.mp4');
|
||||
|
||||
return {
|
||||
stream: [
|
||||
{
|
||||
id: 'primary',
|
||||
type: 'file',
|
||||
qualities: {
|
||||
unknown: {
|
||||
type: 'mp4',
|
||||
url,
|
||||
},
|
||||
},
|
||||
flags: [flags.IP_LOCKED],
|
||||
captions: result.stream[0].captions,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
import { makeFullUrl } from '@/fetchers/common';
|
||||
import { decodeData } from '@/providers/sources/vidsrcto/common';
|
||||
import { EmbedScrapeContext } from '@/utils/context';
|
||||
|
||||
export const vidplayBase = 'https://vidplay.online';
|
||||
export const referer = `${vidplayBase}/`;
|
||||
|
||||
// This file is based on https://github.com/Ciarands/vidsrc-to-resolver/blob/dffa45e726a4b944cb9af0c9e7630476c93c0213/vidsrc.py#L16
|
||||
// Full credits to @Ciarands!
|
||||
|
||||
export const getDecryptionKeys = async (ctx: EmbedScrapeContext): Promise<string[]> => {
|
||||
const res = await ctx.proxiedFetcher<string>('https://github.com/Ciarands/vidsrc-keys/blob/main/keys.json');
|
||||
const regex = /"rawLines":\s*\[([\s\S]*?)\]/;
|
||||
const rawLines = res.match(regex)?.[1];
|
||||
if (!rawLines) throw new Error('No keys found');
|
||||
const keys = JSON.parse(`${rawLines.substring(1).replace(/\\"/g, '"')}]`);
|
||||
return keys;
|
||||
};
|
||||
|
||||
export const getEncodedId = async (ctx: EmbedScrapeContext) => {
|
||||
const url = new URL(ctx.url);
|
||||
const id = url.pathname.replace('/e/', '');
|
||||
const keyList = await getDecryptionKeys(ctx);
|
||||
|
||||
const decodedId = decodeData(keyList[0], id);
|
||||
const encodedResult = decodeData(keyList[1], decodedId);
|
||||
const b64encoded = btoa(encodedResult);
|
||||
return b64encoded.replace('/', '_');
|
||||
};
|
||||
|
||||
export const getFuTokenKey = async (ctx: EmbedScrapeContext) => {
|
||||
const id = await getEncodedId(ctx);
|
||||
const fuTokenRes = await ctx.proxiedFetcher<string>('/futoken', {
|
||||
baseUrl: vidplayBase,
|
||||
headers: {
|
||||
referer: ctx.url,
|
||||
},
|
||||
});
|
||||
const fuKey = fuTokenRes.match(/var\s+k\s*=\s*'([^']+)'/)?.[1];
|
||||
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));
|
||||
}
|
||||
return `${fuKey},${tokens.join(',')}`;
|
||||
};
|
||||
|
||||
export const getFileUrl = async (ctx: EmbedScrapeContext) => {
|
||||
const fuToken = await getFuTokenKey(ctx);
|
||||
return makeFullUrl(`/mediainfo/${fuToken}`, {
|
||||
baseUrl: vidplayBase,
|
||||
query: {
|
||||
...Object.fromEntries(new URL(ctx.url).searchParams.entries()),
|
||||
autostart: 'true',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import { flags } from '@/entrypoint/utils/targets';
|
||||
import { makeEmbed } from '@/providers/base';
|
||||
import { Caption, getCaptionTypeFromUrl, labelToLanguageCode } from '@/providers/captions';
|
||||
|
||||
import { getFileUrl } from './common';
|
||||
import { SubtitleResult, ThumbnailTrack, VidplaySourceResponse } from './types';
|
||||
|
||||
export const vidplayScraper = makeEmbed({
|
||||
id: 'vidplay',
|
||||
name: 'VidPlay',
|
||||
rank: 401,
|
||||
scrape: async (ctx) => {
|
||||
const fileUrl = await getFileUrl(ctx);
|
||||
const fileUrlRes = await ctx.proxiedFetcher<VidplaySourceResponse>(fileUrl, {
|
||||
headers: {
|
||||
referer: ctx.url,
|
||||
},
|
||||
});
|
||||
if (typeof fileUrlRes.result === 'number') throw new Error('File not found');
|
||||
const source = fileUrlRes.result.sources[0].file;
|
||||
const thumbnailSource = fileUrlRes.result.tracks.find((track) => track.kind === 'thumbnails');
|
||||
|
||||
let thumbnailTrack: ThumbnailTrack | undefined;
|
||||
if (thumbnailSource) {
|
||||
thumbnailTrack = {
|
||||
type: 'vtt',
|
||||
url: thumbnailSource.file,
|
||||
};
|
||||
}
|
||||
|
||||
const url = new URL(ctx.url);
|
||||
const subtitlesLink = url.searchParams.get('sub.info');
|
||||
const captions: Caption[] = [];
|
||||
if (subtitlesLink) {
|
||||
const captionsResult = await ctx.proxiedFetcher<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: source,
|
||||
flags: [flags.PROXY_BLOCKED],
|
||||
headers: {
|
||||
Referer: url.origin,
|
||||
Origin: url.origin,
|
||||
},
|
||||
captions,
|
||||
thumbnailTrack,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue