Update FED API

This commit is contained in:
Pas 2025-03-16 17:49:40 -07:00
parent 7afa76ad3d
commit e82ae630da
5 changed files with 246 additions and 295 deletions

View file

@ -20,6 +20,7 @@ import {
autoembedTeluguScraper,
} from './embeds/autoembed';
import { closeLoadScraper } from './embeds/closeload';
import { FedAPIPrivateScraper, FedAPISharedScraper, FedDBScraper } from './embeds/fedapi';
import { mp4hydraServer1Scraper, mp4hydraServer2Scraper } from './embeds/mp4hydra';
import { ridooScraper } from './embeds/ridoo';
import { streamtapeScraper } from './embeds/streamtape';
@ -48,7 +49,6 @@ import { webtor1080Scraper, webtor480Scraper, webtor4kScraper, webtor720Scraper
import { coitusScraper } from './sources/coitus';
import { embedsuScraper } from './sources/embedsu';
import { FedAPIScraper } from './sources/fedapi';
import { FedAPIDBScraper } from './sources/fedapidb';
import { hdRezkaScraper } from './sources/hdrezka';
import { iosmirrorScraper } from './sources/iosmirror';
import { iosmirrorPVScraper } from './sources/iosmirrorpv';
@ -79,7 +79,6 @@ export function gatherAllSources(): Array<Sourcerer> {
webtorScraper,
embedsuScraper,
FedAPIScraper,
FedAPIDBScraper,
slidemoviesScraper,
iosmirrorScraper,
iosmirrorPVScraper,
@ -130,5 +129,8 @@ export function gatherAllEmbeds(): Array<Embed> {
webtor720Scraper,
webtor480Scraper,
viperScraper,
FedAPIPrivateScraper,
FedAPISharedScraper,
FedDBScraper,
];
}

View file

@ -0,0 +1,213 @@
/* eslint-disable no-console */
import { flags } from '@/entrypoint/utils/targets';
import { EmbedOutput, makeEmbed } from '@/providers/base';
import { NotFoundError } from '@/utils/errors';
import { Caption } from '../captions';
// Thanks Nemo for this API!
const BASE_URL = 'https://fed-api.pstream.org';
const CACHE_URL = 'https://fed-api.pstream.org/cache';
const getShareConsent = (): string | null => {
try {
return typeof window !== 'undefined' ? window.localStorage.getItem('share-token') : null;
} catch (e) {
console.warn('Unable to access localStorage:', e);
return null;
}
};
// Language mapping for subtitles
const languageMap: Record<string, string> = {
English: 'en',
Spanish: 'es',
French: 'fr',
German: 'de',
Italian: 'it',
Portuguese: 'pt',
Arabic: 'ar',
Russian: 'ru',
Japanese: 'ja',
Korean: 'ko',
Chinese: 'zh',
Hindi: 'hi',
Turkish: 'tr',
Dutch: 'nl',
Polish: 'pl',
Swedish: 'sv',
Indonesian: 'id',
Thai: 'th',
Vietnamese: 'vi',
};
interface StreamData {
streams: Record<string, string>;
subtitles: Record<string, any>;
error?: string;
name?: string;
size?: string;
}
const providers = [
{
id: 'fedapi-private',
rank: 303,
name: 'FED API (Private)',
useToken: true,
useCacheUrl: false,
},
{
id: 'fedapi-shared',
rank: 302,
name: 'FED API (Shared)',
useToken: false,
useCacheUrl: false,
},
{
id: 'feddb',
rank: 301,
name: 'FED DB',
useToken: false,
useCacheUrl: true,
},
];
function embed(provider: { id: string; rank: number; name: string; useToken: boolean; useCacheUrl: boolean }) {
return makeEmbed({
id: provider.id,
name: provider.name,
rank: provider.rank,
async scrape(ctx): Promise<EmbedOutput> {
// Parse the query parameters from the URL
const query = JSON.parse(ctx.url);
// Build the API URL based on the provider configuration and media type
let apiUrl: string;
if (provider.useCacheUrl) {
// Cache URL format
apiUrl =
query.type === 'movie'
? `${CACHE_URL}/${query.imdbId}`
: `${CACHE_URL}/${query.imdbId}/${query.season}/${query.episode}`;
} else {
// Standard API URL format
apiUrl =
query.type === 'movie'
? `${BASE_URL}/movie/${query.imdbId}`
: `${BASE_URL}/tv/${query.tmdbId}/${query.season}/${query.episode}`;
}
// Prepare request headers
const headers: Record<string, string> = {};
if (provider.useToken && query.token) {
headers['ui-token'] = query.token;
}
// Add share-token header if it's set to "true" in localStorage
const shareToken = getShareConsent();
if (shareToken === 'true') {
headers['share-token'] = 'true';
}
// Fetch data from the API
const data = await ctx.fetcher<StreamData>(apiUrl, {
headers: Object.keys(headers).length > 0 ? headers : undefined,
});
if (data?.error === 'No results found in MovieBox search') {
throw new NotFoundError('No stream found');
}
if (!data) throw new NotFoundError('No response from API');
ctx.progress(50);
// Process streams data
const streams = Object.entries(data.streams).reduce((acc: Record<string, string>, [quality, url]) => {
let qualityKey: number;
if (quality === '4K') {
qualityKey = 2160;
} else if (quality === 'ORG') {
return acc;
} else {
qualityKey = parseInt(quality.replace('P', ''), 10);
}
if (Number.isNaN(qualityKey) || acc[qualityKey]) return acc;
acc[qualityKey] = url;
return acc;
}, {});
// Process captions data
const captions: Caption[] = [];
if (data.subtitles) {
for (const [langKey, subtitleData] of Object.entries(data.subtitles)) {
// Extract language name from key
const languageKeyPart = langKey.split('_')[0];
const languageName = languageKeyPart.charAt(0).toUpperCase() + languageKeyPart.slice(1);
const languageCode = languageMap[languageName]?.toLowerCase() ?? 'unknown';
// Check if the subtitle data is in the new format (has subtitle_link)
if (subtitleData.subtitle_link) {
const url = subtitleData.subtitle_link;
const isVtt = url.toLowerCase().endsWith('.vtt');
captions.push({
type: isVtt ? 'vtt' : 'srt',
id: url,
url,
language: languageCode,
hasCorsRestrictions: false,
});
}
}
}
ctx.progress(90);
return {
stream: [
{
id: 'primary',
captions,
qualities: {
...(streams[2160] && {
'4k': {
type: 'mp4',
url: streams[2160],
},
}),
...(streams[1080] && {
1080: {
type: 'mp4',
url: streams[1080],
},
}),
...(streams[720] && {
720: {
type: 'mp4',
url: streams[720],
},
}),
...(streams[480] && {
480: {
type: 'mp4',
url: streams[480],
},
}),
...(streams[360] && {
360: {
type: 'mp4',
url: streams[360],
},
}),
},
type: 'file',
flags: [flags.CORS_ALLOWED],
},
],
};
},
});
}
export const [FedAPIPrivateScraper, FedAPISharedScraper, FedDBScraper] = providers.map(embed);

View file

@ -1,36 +1,6 @@
/* eslint-disable no-console */
import { flags } from '@/entrypoint/utils/targets';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
import { Caption } from '../captions';
// Thanks Nemo for this API!
const BASE_URL = 'https://fed-api.pstream.org';
// this is so fucking useless
const languageMap: Record<string, string> = {
English: 'en',
Spanish: 'es',
French: 'fr',
German: 'de',
Italian: 'it',
Portuguese: 'pt',
Arabic: 'ar',
Russian: 'ru',
Japanese: 'ja',
Korean: 'ko',
Chinese: 'zh',
Hindi: 'hi',
Turkish: 'tr',
Dutch: 'nl',
Polish: 'pl',
Swedish: 'sv',
Indonesian: 'id',
Thai: 'th',
Vietnamese: 'vi',
};
const getUserToken = (): string | null => {
try {
@ -41,118 +11,43 @@ const getUserToken = (): string | null => {
}
};
interface StreamData {
streams: Record<string, string>;
subtitles: Record<string, any>;
error?: string;
name?: string;
size?: string;
}
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
const apiUrl =
ctx.media.type === 'movie'
? `${BASE_URL}/movie/${ctx.media.imdbId}`
: `${BASE_URL}/tv/${ctx.media.tmdbId}/${ctx.media.season.number}/${ctx.media.episode.number}`;
const query = {
type: ctx.media.type,
imdbId: ctx.media.imdbId,
tmdbId: ctx.media.tmdbId,
...(ctx.media.type === 'show' && {
season: ctx.media.season.number,
episode: ctx.media.episode.number,
}),
};
const userToken = getUserToken();
const embeds = [];
if (userToken) {
console.log('Custom token found:');
embeds.push({
embedId: 'fedapi-private',
url: `${JSON.stringify({ ...query, token: userToken })}`,
});
}
const data = await ctx.fetcher<StreamData>(apiUrl, {
headers: {
...(userToken && { 'ui-token': userToken }),
},
});
if (data?.error === 'No results found in MovieBox search') {
throw new NotFoundError('No stream found');
}
if (!data) throw new NotFoundError('No response from API');
ctx.progress(50);
const streams = Object.entries(data.streams).reduce((acc: Record<string, string>, [quality, url]) => {
let qualityKey: number;
if (quality === '4K') {
qualityKey = 2160;
} else if (quality === 'ORG') {
return acc;
} else {
qualityKey = parseInt(quality.replace('P', ''), 10);
}
if (Number.isNaN(qualityKey) || acc[qualityKey]) return acc;
acc[qualityKey] = url;
return acc;
}, {});
const captions: Caption[] = [];
if (data.subtitles) {
for (const [langKey, subtitleData] of Object.entries(data.subtitles)) {
// Extract language name from key
const languageKeyPart = langKey.split('_')[0];
const languageName = languageKeyPart.charAt(0).toUpperCase() + languageKeyPart.slice(1);
const languageCode = languageMap[languageName]?.toLowerCase() ?? 'unknown';
// Check if the subtitle data is in the new format (has subtitle_link)
if (subtitleData.subtitle_link) {
const url = subtitleData.subtitle_link;
const isVtt = url.toLowerCase().endsWith('.vtt');
captions.push({
type: isVtt ? 'vtt' : 'srt',
id: url,
url,
language: languageCode,
hasCorsRestrictions: false,
});
}
}
if (!userToken) {
embeds.push({
embedId: 'fedapi-shared',
url: `${JSON.stringify(query)}`,
});
}
ctx.progress(90);
if (!userToken) {
embeds.push({
embedId: 'feddb',
url: `${JSON.stringify(query)}`,
});
}
return {
embeds: [],
stream: [
{
id: 'primary',
captions,
qualities: {
...(streams[2160] && {
'4k': {
type: 'mp4',
url: streams[2160],
},
}),
...(streams[1080] && {
1080: {
type: 'mp4',
url: streams[1080],
},
}),
...(streams[720] && {
720: {
type: 'mp4',
url: streams[720],
},
}),
...(streams[480] && {
480: {
type: 'mp4',
url: streams[480],
},
}),
...(streams[360] && {
360: {
type: 'mp4',
url: streams[360],
},
}),
},
type: 'file',
flags: [flags.CORS_ALLOWED],
},
],
embeds,
};
}
@ -160,7 +55,6 @@ export const FedAPIScraper = makeSourcerer({
id: 'fedapi',
name: 'FED API (4K)',
rank: 260,
disabled: !getUserToken(),
flags: [flags.CORS_ALLOWED],
scrapeMovie: comboScraper,
scrapeShow: comboScraper,

View file

@ -1,158 +0,0 @@
/* eslint-disable no-console */
import { flags } from '@/entrypoint/utils/targets';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
import { Caption } from '../captions';
// Thanks Nemo for this API!
const BASE_URL = 'https://fed-api.pstream.org/cache';
// this is so fucking useless
const languageMap: Record<string, string> = {
English: 'en',
Spanish: 'es',
French: 'fr',
German: 'de',
Italian: 'it',
Portuguese: 'pt',
Arabic: 'ar',
Russian: 'ru',
Japanese: 'ja',
Korean: 'ko',
Chinese: 'zh',
Hindi: 'hi',
Turkish: 'tr',
Dutch: 'nl',
Polish: 'pl',
Swedish: 'sv',
Indonesian: 'id',
Thai: 'th',
Vietnamese: 'vi',
};
const getUserToken = (): string | null => {
try {
return typeof window !== 'undefined' ? window.localStorage.getItem('febbox_ui_token') : null;
} catch (e) {
console.warn('Unable to access localStorage:', e);
return null;
}
};
interface StreamData {
streams: Record<string, string>;
subtitles: Record<string, any>;
error?: string;
name?: string;
size?: string;
}
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
const apiUrl =
ctx.media.type === 'movie'
? `${BASE_URL}/${ctx.media.imdbId}`
: `${BASE_URL}/${ctx.media.imdbId}/${ctx.media.season.number}/${ctx.media.episode.number}`;
const data = await ctx.fetcher<StreamData>(apiUrl);
if (data?.error) {
throw new NotFoundError('No stream found');
}
if (!data) throw new NotFoundError('No response from API');
ctx.progress(50);
const streams = Object.entries(data.streams).reduce((acc: Record<string, string>, [quality, url]) => {
let qualityKey: number;
if (quality === '4K') {
qualityKey = 2160;
} else if (quality === 'ORG') {
return acc;
} else {
qualityKey = parseInt(quality.replace('P', ''), 10);
}
if (Number.isNaN(qualityKey) || acc[qualityKey]) return acc;
acc[qualityKey] = url;
return acc;
}, {});
const captions: Caption[] = [];
if (data.subtitles) {
for (const [langKey, subtitleData] of Object.entries(data.subtitles)) {
// Extract language name from key
const languageKeyPart = langKey.split('_')[0];
const languageName = languageKeyPart.charAt(0).toUpperCase() + languageKeyPart.slice(1);
const languageCode = languageMap[languageName]?.toLowerCase() ?? 'unknown';
// Check if the subtitle data is in the new format (has subtitle_link)
if (subtitleData.subtitle_link) {
const url = subtitleData.subtitle_link;
const isVtt = url.toLowerCase().endsWith('.vtt');
captions.push({
type: isVtt ? 'vtt' : 'srt',
id: url,
url,
language: languageCode,
hasCorsRestrictions: false,
});
}
}
}
ctx.progress(90);
return {
embeds: [],
stream: [
{
id: 'primary',
captions,
qualities: {
...(streams[2160] && {
'4k': {
type: 'mp4',
url: streams[2160],
},
}),
...(streams[1080] && {
1080: {
type: 'mp4',
url: streams[1080],
},
}),
...(streams[720] && {
720: {
type: 'mp4',
url: streams[720],
},
}),
...(streams[480] && {
480: {
type: 'mp4',
url: streams[480],
},
}),
...(streams[360] && {
360: {
type: 'mp4',
url: streams[360],
},
}),
},
type: 'file',
flags: [flags.CORS_ALLOWED],
},
],
};
}
export const FedAPIDBScraper = makeSourcerer({
id: 'fedapidb',
name: 'FED DB (Beta)',
rank: 259,
disabled: !!getUserToken(),
flags: [flags.CORS_ALLOWED],
scrapeMovie: comboScraper,
scrapeShow: comboScraper,
});

View file

@ -1,8 +1,8 @@
// import { alphaScraper, deltaScraper } from '@/providers/embeds/nsbx';
// import { astraScraper, novaScraper, orionScraper } from '@/providers/embeds/whvx';
import { warezcdnembedMp4Scraper } from '@/providers/embeds/warezcdn/mp4';
import { embedsuScraper } from '@/providers/sources/embedsu';
import { FedAPIScraper } from '@/providers/sources/fedapi';
import { FedAPIDBScraper } from '@/providers/sources/fedapidb';
import { uiraliveScraper } from '@/providers/sources/uiralive';
import { Stream } from '@/providers/streams';
import { IndividualEmbedRunnerOptions } from '@/runners/individualRunner';
@ -17,7 +17,7 @@ const SKIP_VALIDATION_CHECK_IDS = [
// orionScraper.id,
uiraliveScraper.id,
FedAPIScraper.id,
FedAPIDBScraper.id,
embedsuScraper.id,
];
export function isValidStream(stream: Stream | undefined): boolean {