Merge pull request #45 from movie-web/febbox-hls
Split superstream into showbox and febbox
This commit is contained in:
commit
c00ed69801
|
@ -1,5 +1,6 @@
|
||||||
import { Embed, Sourcerer } from '@/providers/base';
|
import { Embed, Sourcerer } from '@/providers/base';
|
||||||
import { febBoxScraper } from '@/providers/embeds/febBox';
|
import { febboxHlsScraper } from '@/providers/embeds/febbox/hls';
|
||||||
|
import { febboxMp4Scraper } from '@/providers/embeds/febbox/mp4';
|
||||||
import { mixdropScraper } from '@/providers/embeds/mixdrop';
|
import { mixdropScraper } from '@/providers/embeds/mixdrop';
|
||||||
import { mp4uploadScraper } from '@/providers/embeds/mp4upload';
|
import { mp4uploadScraper } from '@/providers/embeds/mp4upload';
|
||||||
import { streamsbScraper } from '@/providers/embeds/streamsb';
|
import { streamsbScraper } from '@/providers/embeds/streamsb';
|
||||||
|
@ -10,12 +11,11 @@ import { goMoviesScraper } from '@/providers/sources/gomovies/index';
|
||||||
import { kissAsianScraper } from '@/providers/sources/kissasian/index';
|
import { kissAsianScraper } from '@/providers/sources/kissasian/index';
|
||||||
import { lookmovieScraper } from '@/providers/sources/lookmovie';
|
import { lookmovieScraper } from '@/providers/sources/lookmovie';
|
||||||
import { remotestreamScraper } from '@/providers/sources/remotestream';
|
import { remotestreamScraper } from '@/providers/sources/remotestream';
|
||||||
import { superStreamScraper } from '@/providers/sources/superstream/index';
|
import { showboxScraper } from '@/providers/sources/showbox/index';
|
||||||
import { zoechipScraper } from '@/providers/sources/zoechip';
|
import { zoechipScraper } from '@/providers/sources/zoechip';
|
||||||
|
|
||||||
import { smashyStreamDScraper } from './embeds/smashystream/dued';
|
import { smashyStreamDScraper } from './embeds/smashystream/dued';
|
||||||
import { smashyStreamFScraper } from './embeds/smashystream/video1';
|
import { smashyStreamFScraper } from './embeds/smashystream/video1';
|
||||||
import { showBoxScraper } from './sources/showbox';
|
|
||||||
import { smashyStreamScraper } from './sources/smashystream';
|
import { smashyStreamScraper } from './sources/smashystream';
|
||||||
|
|
||||||
export function gatherAllSources(): Array<Sourcerer> {
|
export function gatherAllSources(): Array<Sourcerer> {
|
||||||
|
@ -24,11 +24,10 @@ export function gatherAllSources(): Array<Sourcerer> {
|
||||||
flixhqScraper,
|
flixhqScraper,
|
||||||
remotestreamScraper,
|
remotestreamScraper,
|
||||||
kissAsianScraper,
|
kissAsianScraper,
|
||||||
superStreamScraper,
|
showboxScraper,
|
||||||
goMoviesScraper,
|
goMoviesScraper,
|
||||||
zoechipScraper,
|
zoechipScraper,
|
||||||
lookmovieScraper,
|
lookmovieScraper,
|
||||||
showBoxScraper,
|
|
||||||
smashyStreamScraper,
|
smashyStreamScraper,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -40,7 +39,8 @@ export function gatherAllEmbeds(): Array<Embed> {
|
||||||
mp4uploadScraper,
|
mp4uploadScraper,
|
||||||
streamsbScraper,
|
streamsbScraper,
|
||||||
upstreamScraper,
|
upstreamScraper,
|
||||||
febBoxScraper,
|
febboxMp4Scraper,
|
||||||
|
febboxHlsScraper,
|
||||||
mixdropScraper,
|
mixdropScraper,
|
||||||
smashyStreamFScraper,
|
smashyStreamFScraper,
|
||||||
smashyStreamDScraper,
|
smashyStreamDScraper,
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { flags } from '@/main/targets';
|
|
||||||
import { makeEmbed } from '@/providers/base';
|
|
||||||
import { StreamFile } from '@/providers/streams';
|
|
||||||
import { NotFoundError } from '@/utils/errors';
|
|
||||||
|
|
||||||
const febBoxBase = `https://www.febbox.com`;
|
|
||||||
|
|
||||||
const allowedQualities = ['360', '480', '720', '1080'];
|
|
||||||
|
|
||||||
export const febBoxScraper = makeEmbed({
|
|
||||||
id: 'febbox',
|
|
||||||
name: 'FebBox',
|
|
||||||
rank: 160,
|
|
||||||
async scrape(ctx) {
|
|
||||||
const shareKey = ctx.url.split('/')[4];
|
|
||||||
const streams = await ctx.proxiedFetcher<{
|
|
||||||
data?: {
|
|
||||||
file_list?: {
|
|
||||||
fid?: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
}>('/file/file_share_list', {
|
|
||||||
headers: {
|
|
||||||
'accept-language': 'en', // without this header, the request is marked as a webscraper
|
|
||||||
},
|
|
||||||
baseUrl: febBoxBase,
|
|
||||||
query: {
|
|
||||||
share_key: shareKey,
|
|
||||||
pwd: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const fid = streams?.data?.file_list?.[0]?.fid;
|
|
||||||
if (!fid) throw new NotFoundError('no result found');
|
|
||||||
|
|
||||||
const formParams = new URLSearchParams();
|
|
||||||
formParams.append('fid', fid);
|
|
||||||
formParams.append('share_key', shareKey);
|
|
||||||
|
|
||||||
const player = await ctx.proxiedFetcher<string>('/file/player', {
|
|
||||||
baseUrl: febBoxBase,
|
|
||||||
body: formParams,
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'accept-language': 'en', // without this header, the request is marked as a webscraper
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const sourcesMatch = player?.match(/var sources = (\[[^\]]+\]);/);
|
|
||||||
const qualities = sourcesMatch ? JSON.parse(sourcesMatch[0].replace('var sources = ', '').replace(';', '')) : null;
|
|
||||||
|
|
||||||
const embedQualities: Record<string, StreamFile> = {};
|
|
||||||
|
|
||||||
qualities.forEach((quality: { file: string; label: string }) => {
|
|
||||||
const normalizedLabel = quality.label.toLowerCase().replace('p', '');
|
|
||||||
if (allowedQualities.includes(normalizedLabel)) {
|
|
||||||
if (!quality.file) return;
|
|
||||||
embedQualities[normalizedLabel] = {
|
|
||||||
type: 'mp4',
|
|
||||||
url: quality.file,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
stream: {
|
|
||||||
type: 'file',
|
|
||||||
captions: [],
|
|
||||||
flags: [flags.NO_CORS],
|
|
||||||
qualities: embedQualities,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { MediaTypes } from '@/main/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,
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { MediaTypes } from '@/main/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);
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { MediaTypes } from '@/main/media';
|
||||||
|
import { flags } from '@/main/targets';
|
||||||
|
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,
|
||||||
|
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: {
|
||||||
|
type: 'hls',
|
||||||
|
flags: [flags.NO_CORS],
|
||||||
|
captions: await getSubtitles(ctx, id, firstStream.fid, type as MediaTypes, season, episode),
|
||||||
|
playlist: `https://www.febbox.com/hls/main/${firstStream.oss_fid}.m3u8`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { flags } from '@/main/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: {
|
||||||
|
captions: await getSubtitles(ctx, id, fid, type, episode, season),
|
||||||
|
qualities,
|
||||||
|
type: 'file',
|
||||||
|
flags: [flags.NO_CORS],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,13 +1,11 @@
|
||||||
|
import { sendRequest } from '@/providers/sources/showbox/sendRequest';
|
||||||
import { StreamFile } from '@/providers/streams';
|
import { StreamFile } from '@/providers/streams';
|
||||||
import { ScrapeContext } from '@/utils/context';
|
import { ScrapeContext } from '@/utils/context';
|
||||||
|
|
||||||
import { sendRequest } from './sendRequest';
|
const allowedQualities = ['360', '480', '720', '1080', '4k'];
|
||||||
|
|
||||||
const allowedQualities = ['360', '480', '720', '1080'];
|
|
||||||
|
|
||||||
export async function getStreamQualities(ctx: ScrapeContext, apiQuery: object) {
|
export async function getStreamQualities(ctx: ScrapeContext, apiQuery: object) {
|
||||||
const mediaRes: { list: { path: string; quality: string; fid?: number }[] } = (await sendRequest(ctx, apiQuery)).data;
|
const mediaRes: { list: { path: string; quality: string; fid?: number }[] } = (await sendRequest(ctx, apiQuery)).data;
|
||||||
ctx.progress(66);
|
|
||||||
|
|
||||||
const qualityMap = mediaRes.list
|
const qualityMap = mediaRes.list
|
||||||
.filter((file) => allowedQualities.includes(file.quality.replace('p', '')))
|
.filter((file) => allowedQualities.includes(file.quality.replace('p', '')))
|
|
@ -1,9 +1,8 @@
|
||||||
import { Caption, getCaptionTypeFromUrl, isValidLanguageCode } from '@/providers/captions';
|
import { Caption, getCaptionTypeFromUrl, isValidLanguageCode } from '@/providers/captions';
|
||||||
import { sendRequest } from '@/providers/sources/superstream/sendRequest';
|
import { captionsDomains } from '@/providers/sources/showbox/common';
|
||||||
|
import { sendRequest } from '@/providers/sources/showbox/sendRequest';
|
||||||
import { ScrapeContext } from '@/utils/context';
|
import { ScrapeContext } from '@/utils/context';
|
||||||
|
|
||||||
import { captionsDomains } from './common';
|
|
||||||
|
|
||||||
interface CaptionApiResponse {
|
interface CaptionApiResponse {
|
||||||
data: {
|
data: {
|
||||||
list: {
|
list: {
|
|
@ -12,3 +12,5 @@ export const apiUrls = [
|
||||||
export const appKey = atob('bW92aWVib3g=');
|
export const appKey = atob('bW92aWVib3g=');
|
||||||
export const appId = atob('Y29tLnRkby5zaG93Ym94');
|
export const appId = atob('Y29tLnRkby5zaG93Ym94');
|
||||||
export const captionsDomains = [atob('bWJwaW1hZ2VzLmNodWF4aW4uY29t'), atob('aW1hZ2VzLnNoZWd1Lm5ldA==')];
|
export const captionsDomains = [atob('bWJwaW1hZ2VzLmNodWF4aW4uY29t'), atob('aW1hZ2VzLnNoZWd1Lm5ldA==')];
|
||||||
|
|
||||||
|
export const showboxBase = 'https://www.showbox.media';
|
|
@ -1,64 +1,53 @@
|
||||||
import { load } from 'cheerio';
|
|
||||||
|
|
||||||
import { flags } from '@/main/targets';
|
import { flags } from '@/main/targets';
|
||||||
import { makeSourcerer } from '@/providers/base';
|
import { SourcererOutput, makeSourcerer } from '@/providers/base';
|
||||||
import { febBoxScraper } from '@/providers/embeds/febBox';
|
import { febboxHlsScraper } from '@/providers/embeds/febbox/hls';
|
||||||
import { compareMedia } from '@/utils/compare';
|
import { febboxMp4Scraper } from '@/providers/embeds/febbox/mp4';
|
||||||
|
import { compareTitle } from '@/utils/compare';
|
||||||
|
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
|
||||||
import { NotFoundError } from '@/utils/errors';
|
import { NotFoundError } from '@/utils/errors';
|
||||||
|
|
||||||
const showboxBase = `https://www.showbox.media`;
|
import { sendRequest } from './sendRequest';
|
||||||
|
|
||||||
export const showBoxScraper = makeSourcerer({
|
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
|
||||||
id: 'show_box',
|
const searchQuery = {
|
||||||
name: 'ShowBox',
|
module: 'Search4',
|
||||||
rank: 20,
|
page: '1',
|
||||||
disabled: true,
|
type: 'all',
|
||||||
flags: [flags.NO_CORS],
|
|
||||||
async scrapeMovie(ctx) {
|
|
||||||
const search = await ctx.proxiedFetcher<string>('/search', {
|
|
||||||
baseUrl: showboxBase,
|
|
||||||
query: {
|
|
||||||
keyword: ctx.media.title,
|
keyword: ctx.media.title,
|
||||||
},
|
pagelimit: '20',
|
||||||
});
|
|
||||||
|
|
||||||
const searchPage = load(search);
|
|
||||||
const result = searchPage('.film-name > a')
|
|
||||||
.toArray()
|
|
||||||
.map((el) => {
|
|
||||||
const titleContainer = el.parent?.parent;
|
|
||||||
if (!titleContainer) return;
|
|
||||||
const year = searchPage(titleContainer).find('.fdi-item').first().text();
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: el.attribs.title,
|
|
||||||
path: el.attribs.href,
|
|
||||||
year: !year.includes('SS') ? parseInt(year, 10) : undefined,
|
|
||||||
};
|
};
|
||||||
})
|
|
||||||
.find((v) => v && compareMedia(ctx.media, v.title, v.year ? v.year : undefined));
|
|
||||||
|
|
||||||
if (!result?.path) throw new NotFoundError('no result found');
|
const searchRes = (await sendRequest(ctx, searchQuery, true)).data.list;
|
||||||
|
ctx.progress(50);
|
||||||
|
|
||||||
const febboxResult = await ctx.proxiedFetcher<{
|
const showboxEntry = searchRes.find(
|
||||||
data?: { link?: string };
|
(res: any) => compareTitle(res.title, ctx.media.title) && res.year === Number(ctx.media.releaseYear),
|
||||||
}>('/index/share_link', {
|
);
|
||||||
baseUrl: showboxBase,
|
if (!showboxEntry) throw new NotFoundError('No entry found');
|
||||||
query: {
|
|
||||||
id: result.path.split('/')[3],
|
|
||||||
type: '1',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!febboxResult?.data?.link) throw new NotFoundError('no result 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 {
|
return {
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
embedId: febBoxScraper.id,
|
embedId: febboxHlsScraper.id,
|
||||||
url: febboxResult.data.link,
|
url: `/${ctx.media.type}/${id}/${season}/${episode}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
embedId: febboxMp4Scraper.id,
|
||||||
|
url: `/${ctx.media.type}/${id}/${season}/${episode}`,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
|
export const showboxScraper = makeSourcerer({
|
||||||
|
id: 'showbox',
|
||||||
|
name: 'Showbox',
|
||||||
|
rank: 300,
|
||||||
|
flags: [flags.NO_CORS],
|
||||||
|
scrapeShow: comboScraper,
|
||||||
|
scrapeMovie: comboScraper,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
import { flags } from '@/main/targets';
|
|
||||||
import { makeSourcerer } from '@/providers/base';
|
|
||||||
import { getSubtitles } from '@/providers/sources/superstream/subtitles';
|
|
||||||
import { compareTitle } from '@/utils/compare';
|
|
||||||
import { NotFoundError } from '@/utils/errors';
|
|
||||||
|
|
||||||
import { getStreamQualities } from './getStreamQualities';
|
|
||||||
import { sendRequest } from './sendRequest';
|
|
||||||
|
|
||||||
export const superStreamScraper = makeSourcerer({
|
|
||||||
id: 'superstream',
|
|
||||||
name: 'Superstream',
|
|
||||||
rank: 300,
|
|
||||||
flags: [flags.NO_CORS],
|
|
||||||
async scrapeShow(ctx) {
|
|
||||||
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(33);
|
|
||||||
|
|
||||||
const superstreamEntry = searchRes.find(
|
|
||||||
(res: any) => compareTitle(res.title, ctx.media.title) && res.year === Number(ctx.media.releaseYear),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!superstreamEntry) throw new NotFoundError('No entry found');
|
|
||||||
const superstreamId = superstreamEntry.id;
|
|
||||||
|
|
||||||
// Fetch requested episode
|
|
||||||
const apiQuery = {
|
|
||||||
uid: '',
|
|
||||||
module: 'TV_downloadurl_v3',
|
|
||||||
tid: superstreamId,
|
|
||||||
season: ctx.media.season.number,
|
|
||||||
episode: ctx.media.episode.number,
|
|
||||||
oss: '1',
|
|
||||||
group: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const { qualities, fid } = await getStreamQualities(ctx, apiQuery);
|
|
||||||
if (fid === undefined) throw new NotFoundError('No streamable file found');
|
|
||||||
|
|
||||||
return {
|
|
||||||
embeds: [],
|
|
||||||
stream: {
|
|
||||||
captions: await getSubtitles(
|
|
||||||
ctx,
|
|
||||||
superstreamId,
|
|
||||||
fid,
|
|
||||||
'show',
|
|
||||||
ctx.media.episode.number,
|
|
||||||
ctx.media.season.number,
|
|
||||||
),
|
|
||||||
qualities,
|
|
||||||
type: 'file',
|
|
||||||
flags: [flags.NO_CORS],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async scrapeMovie(ctx) {
|
|
||||||
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(33);
|
|
||||||
|
|
||||||
const superstreamEntry = searchRes.find(
|
|
||||||
(res: any) => compareTitle(res.title, ctx.media.title) && res.year === Number(ctx.media.releaseYear),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!superstreamEntry) throw new NotFoundError('No entry found');
|
|
||||||
const superstreamId = superstreamEntry.id;
|
|
||||||
|
|
||||||
// Fetch requested episode
|
|
||||||
const apiQuery = {
|
|
||||||
uid: '',
|
|
||||||
module: 'Movie_downloadurl_v3',
|
|
||||||
mid: superstreamId,
|
|
||||||
oss: '1',
|
|
||||||
group: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
const { qualities, fid } = await getStreamQualities(ctx, apiQuery);
|
|
||||||
if (fid === undefined) throw new NotFoundError('No streamable file found');
|
|
||||||
|
|
||||||
return {
|
|
||||||
embeds: [],
|
|
||||||
stream: {
|
|
||||||
captions: await getSubtitles(ctx, superstreamId, fid, 'movie'),
|
|
||||||
qualities,
|
|
||||||
type: 'file',
|
|
||||||
flags: [flags.NO_CORS],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -7,7 +7,7 @@ export type StreamFile = {
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Qualities = 'unknown' | '360' | '480' | '720' | '1080';
|
export type Qualities = 'unknown' | '360' | '480' | '720' | '1080' | '4k';
|
||||||
|
|
||||||
export type FileBasedStream = {
|
export type FileBasedStream = {
|
||||||
type: 'file';
|
type: 'file';
|
||||||
|
|
Loading…
Reference in New Issue