From e82ae630da3ef41584fdc80e0bbeda379fc1e538 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Sun, 16 Mar 2025 17:49:40 -0700 Subject: [PATCH] Update FED API --- src/providers/all.ts | 6 +- src/providers/embeds/fedapi.ts | 213 ++++++++++++++++++++++++++++++ src/providers/sources/fedapi.ts | 160 ++++------------------ src/providers/sources/fedapidb.ts | 158 ---------------------- src/utils/valid.ts | 4 +- 5 files changed, 246 insertions(+), 295 deletions(-) create mode 100644 src/providers/embeds/fedapi.ts delete mode 100644 src/providers/sources/fedapidb.ts diff --git a/src/providers/all.ts b/src/providers/all.ts index 2f7f8a0..cd08b08 100644 --- a/src/providers/all.ts +++ b/src/providers/all.ts @@ -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 { webtorScraper, embedsuScraper, FedAPIScraper, - FedAPIDBScraper, slidemoviesScraper, iosmirrorScraper, iosmirrorPVScraper, @@ -130,5 +129,8 @@ export function gatherAllEmbeds(): Array { webtor720Scraper, webtor480Scraper, viperScraper, + FedAPIPrivateScraper, + FedAPISharedScraper, + FedDBScraper, ]; } diff --git a/src/providers/embeds/fedapi.ts b/src/providers/embeds/fedapi.ts new file mode 100644 index 0000000..737b544 --- /dev/null +++ b/src/providers/embeds/fedapi.ts @@ -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 = { + 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; + subtitles: Record; + 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 { + // 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 = {}; + 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(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, [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); diff --git a/src/providers/sources/fedapi.ts b/src/providers/sources/fedapi.ts index 4b1f95c..5334056 100644 --- a/src/providers/sources/fedapi.ts +++ b/src/providers/sources/fedapi.ts @@ -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 = { - 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; - subtitles: Record; - error?: string; - name?: string; - size?: string; -} - async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise { - 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(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, [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, diff --git a/src/providers/sources/fedapidb.ts b/src/providers/sources/fedapidb.ts deleted file mode 100644 index f7a721e..0000000 --- a/src/providers/sources/fedapidb.ts +++ /dev/null @@ -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 = { - 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; - subtitles: Record; - error?: string; - name?: string; - size?: string; -} - -async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise { - 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(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, [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, -}); diff --git a/src/utils/valid.ts b/src/utils/valid.ts index be029a7..64a8201 100644 --- a/src/utils/valid.ts +++ b/src/utils/valid.ts @@ -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 {