Split superstream into showbox and febbox. add febbox HLS scraper

This commit is contained in:
mrjvs 2023-12-23 18:57:28 +01:00
parent 0477b876ec
commit 4be2da76ba
13 changed files with 189 additions and 249 deletions

View File

@ -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,

View File

@ -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,
},
};
},
});

View File

@ -0,0 +1,8 @@
export const febBoxBase = `https://www.febbox.com`;
export interface FebboxFileList {
file_name: string;
ext: string;
fid: number;
oss_fid: number;
}

View File

@ -0,0 +1,53 @@
import { flags } from '@/main/targets';
import { makeEmbed } from '@/providers/base';
import { FebboxFileList, febBoxBase } from '@/providers/embeds/febbox/common';
import { EmbedScrapeContext } from '@/utils/context';
// 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 async function getFileList(ctx: EmbedScrapeContext, shareKey: string): Promise<FebboxFileList[]> {
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: {
share_key: shareKey,
pwd: '',
},
});
return streams.data?.file_list ?? [];
}
export const febboxHlsScraper = makeEmbed({
id: 'febbox-hls',
name: 'Febbox (HLS)',
rank: 160,
async scrape(ctx) {
const shareKey = extractShareKey(ctx.url);
const fileList = await getFileList(ctx, shareKey);
const firstMp4 = fileList.find((v) => v.ext === 'mp4');
// TODO support TV, file list is gotten differently
// TODO support subtitles with getSubtitles
if (!firstMp4) throw new Error('No playable mp4 stream found');
return {
stream: {
type: 'hls',
flags: [flags.NO_CORS],
captions: [],
playlist: `https://www.febbox.com/hls/main/${firstMp4.oss_fid}.m3u8`,
},
};
},
});

View File

@ -0,0 +1,51 @@
import { MediaTypes } from '@/main/media';
import { flags } from '@/main/targets';
import { makeEmbed } from '@/providers/base';
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, seasonId, episodeId] = ctx.url.slice(1).split('/');
const season = seasonId ? parseInt(seasonId, 10) : undefined;
const episode = episodeId ? parseInt(episodeId, 10) : undefined;
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');
return {
stream: {
captions: await getSubtitles(ctx, id, fid, type as MediaTypes, episode, season),
qualities,
type: 'file',
flags: [flags.NO_CORS],
},
};
},
});

View File

@ -1,8 +1,7 @@
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']; const allowedQualities = ['360', '480', '720', '1080'];
export async function getStreamQualities(ctx: ScrapeContext, apiQuery: object) { export async function getStreamQualities(ctx: ScrapeContext, apiQuery: object) {

View File

@ -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: {

View File

@ -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';

View File

@ -1,64 +1,72 @@
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 { showboxBase } from '@/providers/sources/showbox/common';
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',
keyword: ctx.media.title,
pagelimit: '20',
};
const searchRes = (await sendRequest(ctx, searchQuery, true)).data.list;
ctx.progress(33);
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 sharelinkResult = await ctx.proxiedFetcher<{
data?: { link?: string };
}>('/index/share_link', {
baseUrl: showboxBase,
query: {
id,
type: ctx.media.type === 'movie' ? '1' : '2',
},
});
if (!sharelinkResult?.data?.link) throw new NotFoundError('No embed url found');
ctx.progress(80);
const season = ctx.media.type === 'show' ? ctx.media.season.number : '';
const episode = ctx.media.type === 'show' ? ctx.media.episode.number : '';
const embeds = [
{
embedId: febboxMp4Scraper.id,
url: `/${ctx.media.type}/${id}/${season}/${episode}`,
},
];
if (sharelinkResult?.data?.link) {
embeds.push({
embedId: febboxHlsScraper.id,
url: sharelinkResult.data.link,
});
}
return {
embeds,
};
}
export const showboxScraper = makeSourcerer({
id: 'showbox',
name: 'Showbox',
rank: 300,
flags: [flags.NO_CORS], flags: [flags.NO_CORS],
async scrapeMovie(ctx) { scrapeShow: comboScraper,
const search = await ctx.proxiedFetcher<string>('/search', { scrapeMovie: comboScraper,
baseUrl: showboxBase,
query: {
keyword: ctx.media.title,
},
});
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 febboxResult = await ctx.proxiedFetcher<{
data?: { link?: string };
}>('/index/share_link', {
baseUrl: showboxBase,
query: {
id: result.path.split('/')[3],
type: '1',
},
});
if (!febboxResult?.data?.link) throw new NotFoundError('no result found');
return {
embeds: [
{
embedId: febBoxScraper.id,
url: febboxResult.data.link,
},
],
};
},
}); });

View File

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