add show support to febbox and add captions

This commit is contained in:
mrjvs
2023-12-23 19:55:28 +01:00
parent 4be2da76ba
commit af6ede4a39
7 changed files with 124 additions and 64 deletions

View File

@@ -1,3 +1,5 @@
import { MediaTypes } from '@/main/media';
export const febBoxBase = `https://www.febbox.com`; export const febBoxBase = `https://www.febbox.com`;
export interface FebboxFileList { export interface FebboxFileList {
@@ -5,4 +7,18 @@ export interface FebboxFileList {
ext: string; ext: string;
fid: number; fid: number;
oss_fid: number; oss_fid: number;
is_dir: 0 | 1;
}
export function parseInput(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,
};
} }

View File

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

View File

@@ -1,7 +1,10 @@
import { MediaTypes } from '@/main/media';
import { flags } from '@/main/targets'; import { flags } from '@/main/targets';
import { makeEmbed } from '@/providers/base'; import { makeEmbed } from '@/providers/base';
import { FebboxFileList, febBoxBase } from '@/providers/embeds/febbox/common'; import { parseInput } from '@/providers/embeds/febbox/common';
import { EmbedScrapeContext } from '@/utils/context'; 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> // structure: https://www.febbox.com/share/<random_key>
export function extractShareKey(url: string): string { export function extractShareKey(url: string): string {
@@ -9,44 +12,35 @@ export function extractShareKey(url: string): string {
const shareKey = parsedUrl.pathname.split('/')[2]; const shareKey = parsedUrl.pathname.split('/')[2];
return shareKey; 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({ export const febboxHlsScraper = makeEmbed({
id: 'febbox-hls', id: 'febbox-hls',
name: 'Febbox (HLS)', name: 'Febbox (HLS)',
rank: 160, rank: 160,
async scrape(ctx) { async scrape(ctx) {
const shareKey = extractShareKey(ctx.url); const { type, id, season, episode } = parseInput(ctx.url);
const fileList = await getFileList(ctx, shareKey); const sharelinkResult = await ctx.proxiedFetcher<{
const firstMp4 = fileList.find((v) => v.ext === 'mp4'); data?: { link?: string };
// TODO support TV, file list is gotten differently }>('/index/share_link', {
// TODO support subtitles with getSubtitles baseUrl: showboxBase,
if (!firstMp4) throw new Error('No playable mp4 stream found'); 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 { return {
stream: { stream: {
type: 'hls', type: 'hls',
flags: [flags.NO_CORS], flags: [flags.NO_CORS],
captions: [], captions: await getSubtitles(ctx, id, firstStream.fid, type as MediaTypes, season, episode),
playlist: `https://www.febbox.com/hls/main/${firstMp4.oss_fid}.m3u8`, playlist: `https://www.febbox.com/hls/main/${firstStream.oss_fid}.m3u8`,
}, },
}; };
}, },

View File

@@ -38,6 +38,7 @@ export const febboxMp4Scraper = makeEmbed({
const { qualities, fid } = await getStreamQualities(ctx, apiQuery); const { qualities, fid } = await getStreamQualities(ctx, apiQuery);
if (fid === undefined) throw new Error('No streamable file found'); if (fid === undefined) throw new Error('No streamable file found');
ctx.progress(70);
return { return {
stream: { stream: {

View File

@@ -2,11 +2,10 @@ 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';
const allowedQualities = ['360', '480', '720', '1080']; const allowedQualities = ['360', '480', '720', '1080', '4k'];
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', '')))

View File

@@ -2,7 +2,6 @@ import { flags } from '@/main/targets';
import { SourcererOutput, makeSourcerer } from '@/providers/base'; import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { febboxHlsScraper } from '@/providers/embeds/febbox/hls'; import { febboxHlsScraper } from '@/providers/embeds/febbox/hls';
import { febboxMp4Scraper } from '@/providers/embeds/febbox/mp4'; import { febboxMp4Scraper } from '@/providers/embeds/febbox/mp4';
import { showboxBase } from '@/providers/sources/showbox/common';
import { compareTitle } from '@/utils/compare'; import { compareTitle } from '@/utils/compare';
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context'; import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors'; import { NotFoundError } from '@/utils/errors';
@@ -19,46 +18,28 @@ async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promis
}; };
const searchRes = (await sendRequest(ctx, searchQuery, true)).data.list; const searchRes = (await sendRequest(ctx, searchQuery, true)).data.list;
ctx.progress(33); ctx.progress(50);
const showboxEntry = searchRes.find( const showboxEntry = searchRes.find(
(res: any) => compareTitle(res.title, ctx.media.title) && res.year === Number(ctx.media.releaseYear), (res: any) => compareTitle(res.title, ctx.media.title) && res.year === Number(ctx.media.releaseYear),
); );
if (!showboxEntry) throw new NotFoundError('No entry found'); if (!showboxEntry) throw new NotFoundError('No entry found');
const id = showboxEntry.id; 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 season = ctx.media.type === 'show' ? ctx.media.season.number : '';
const episode = ctx.media.type === 'show' ? ctx.media.episode.number : ''; const episode = ctx.media.type === 'show' ? ctx.media.episode.number : '';
const embeds = [ return {
embeds: [
{
embedId: febboxHlsScraper.id,
url: `/${ctx.media.type}/${id}/${season}/${episode}`,
},
{ {
embedId: febboxMp4Scraper.id, embedId: febboxMp4Scraper.id,
url: `/${ctx.media.type}/${id}/${season}/${episode}`, url: `/${ctx.media.type}/${id}/${season}/${episode}`,
}, },
]; ],
if (sharelinkResult?.data?.link) {
embeds.push({
embedId: febboxHlsScraper.id,
url: sharelinkResult.data.link,
});
}
return {
embeds,
}; };
} }

View File

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