add debrid source (not enabled yet)

This commit is contained in:
Pas 2025-11-19 12:32:15 -07:00
parent 35199c5b8e
commit cafa41add6
3 changed files with 209 additions and 0 deletions

View file

@ -98,6 +98,7 @@ import vidnestScraper from './sources/vidnest';
import { warezcdnScraper } from './sources/warezcdn';
import { wecimaScraper } from './sources/wecima';
import { zunimeScraper } from './sources/zunime';
import { debridScraper } from './sources/debrid';
export function gatherAllSources(): Array<Sourcerer> {
// all sources are gathered here
@ -142,6 +143,7 @@ export function gatherAllSources(): Array<Sourcerer> {
pelisplushdScraper,
primewireScraper,
movies4fScraper,
debridScraper,
];
}

View file

@ -0,0 +1,186 @@
import { flags } from '@/entrypoint/utils/targets';
import { SourcererOutput, makeSourcerer } from '@/providers/base';
import { MovieScrapeContext, ShowScrapeContext } from '@/utils/context';
import { NotFoundError } from '@/utils/errors';
import { debridProviders, torrentioResponse } from './types';
const OVERRIDE_TOKEN = '';
const OVERRIDE_SERVICE = ''; // torbox or realdebrid (or real-debrid)
const getDebridToken = (): string | null => {
try {
if (OVERRIDE_TOKEN) return OVERRIDE_TOKEN;
} catch {
// Ignore
}
try {
if (typeof window === 'undefined') return null;
const prefData = window.localStorage.getItem('__MW::preferences');
if (!prefData) return null;
const parsedAuth = JSON.parse(prefData);
return parsedAuth?.state?.debridToken || null;
} catch (e) {
console.error('Error getting debrid token:', e);
return null;
}
};
// torbox or realdebrid (coverted to `real-debrid` which is the type for torrentio)
const getDebridService = (): debridProviders => {
try {
if (OVERRIDE_SERVICE) return OVERRIDE_SERVICE as debridProviders;
} catch {
// Ignore
}
try {
if (typeof window === 'undefined') return 'real-debrid';
const prefData = window.localStorage.getItem('__MW::preferences');
if (!prefData) return 'real-debrid';
const parsedPrefs = JSON.parse(prefData);
const saved = parsedPrefs?.state?.debridService;
if (saved === 'realdebrid' || !saved) return 'real-debrid';
return saved as debridProviders;
} catch (e) {
console.error('Error getting debrid service (defaulting to real-debrid):', e);
return 'real-debrid';
}
};
type DebridParsedStream = {
resolution?: string;
year?: number;
source?: string;
bitDepth?: string;
codec?: string;
audio?: string;
container?: string;
seasons?: number[];
season?: number;
episodes?: number[];
episode?: number;
complete?: boolean;
unrated?: boolean;
remastered?: boolean;
languages?: string[];
dubbed?: boolean;
title: string;
url: string;
};
function normalizeQuality(resolution?: string): '4k' | 1080 | 720 | 480 | 360 | 'unknown' {
if (!resolution) return 'unknown';
const res = resolution.toLowerCase();
if (res === '4k' || res === '2160p') return '4k';
if (res === '1080p') return 1080;
if (res === '720p') return 720;
if (res === '480p') return 480;
if (res === '360p') return 360;
return 'unknown';
}
// Helper to score streams for compatibility (higher is better)
function scoreStream(stream: DebridParsedStream): number {
let score = 0;
// Prefer mp4 container
if (stream.container === 'mp4') score += 10;
// Prefer aac audio
if (stream.audio === 'aac') score += 5;
// Prefer h265 codec
if (stream.codec === 'h265') score += 2;
// Penalize mkv container
if (stream.container === 'mkv') score -= 2;
// Prefer complete
if (stream.complete) score += 1;
return score;
}
async function comboScraper(ctx: ShowScrapeContext | MovieScrapeContext): Promise<SourcererOutput> {
const apiKey = getDebridToken();
if (!apiKey) {
throw new NotFoundError('Debrid API token is required');
}
const debridProvider: debridProviders = getDebridService();
let torrentioUrl = `https://torrentio.strem.fun/${debridProvider}=${apiKey}/stream/`;
if (ctx.media.type === 'show') {
torrentioUrl += `series/${ctx.media.imdbId}:${ctx.media.season.number}:${ctx.media.episode.number}.json`;
} else {
torrentioUrl += `movie/${ctx.media.imdbId}.json`;
}
const torrentioStreams: torrentioResponse = (await ctx.proxiedFetcher(torrentioUrl)).streams;
const response: DebridParsedStream[] = await ctx.proxiedFetcher('https://torrent-parse.pstream.mov/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(torrentioStreams),
});
// Group by quality, pick the most compatible stream for each
const qualities: Partial<Record<'4k' | 1080 | 720 | 480 | 360 | 'unknown', { type: 'mp4'; url: string }>> = {};
// Group streams by normalized quality
const byQuality: Record<string, DebridParsedStream[]> = {};
for (const stream of response) {
const quality = normalizeQuality(stream.resolution);
if (!byQuality[quality]) byQuality[quality] = [];
byQuality[quality].push(stream);
}
// For each quality, pick the best compatible stream (prefer mp4+aac, fallback to mkv)
for (const [quality, streams] of Object.entries(byQuality)) {
// Prefer mp4 + aac
const mp4Aac = streams.find((s) => s.container === 'mp4' && s.audio === 'aac');
if (mp4Aac) {
qualities[quality as keyof typeof qualities] = {
type: 'mp4',
url: mp4Aac.url,
};
continue;
}
// Fallback: any mp4
const mp4 = streams.find((s) => s.container === 'mp4');
if (mp4) {
qualities[quality as keyof typeof qualities] = {
type: 'mp4',
url: mp4.url,
};
continue;
}
streams.sort((a, b) => scoreStream(b) - scoreStream(a));
const best = streams[0];
if (best) {
qualities[quality as keyof typeof qualities] = {
type: 'mp4', // has to be set as mp4 because of types..... But mkvs *can* work in a browser depending on codec, usually it cant be hevc and has to have AAC audio
url: best.url,
};
}
}
return {
embeds: [],
stream: [
{
id: 'primary',
type: 'file',
qualities,
captions: [],
flags: [],
},
],
};
}
export const debridScraper = makeSourcerer({
id: 'debrid',
name: 'Debrid',
rank: 999,
disabled: true,
flags: [flags.CORS_ALLOWED],
scrapeMovie: comboScraper,
scrapeShow: comboScraper,
});

View file

@ -0,0 +1,21 @@
// these need to be the names that correspond with Torrentio's
export type debridProviders = 'torbox' | 'real-debrid';
export interface stremioStream {
name: string;
title: string;
url: string;
behaviorHints?: {
bingeGroup: string;
filename: string;
videoSize: number;
videoHash: string;
};
}
export interface torrentioResponse {
streams: stremioStream[];
cacheMaxAge: number;
staleRevalidate: number;
staleError: number;
}