Files
sdk/src/sources/movenerds.js

125 lines
3.7 KiB
JavaScript

import { getUA } from "../utils/helpers.js";
const API_BASE = "https://api.movenerds.space/api";
function getHeaders() {
return {
"User-Agent": getUA(),
Accept: "application/json",
Referer: "https://movenerds.space/",
Origin: "https://movenerds.space",
};
}
function resolveApiKey(args) {
return (
args?.tmdbApiKey ||
args?.sdk?.tmdbApiKey ||
process.env.TMDB_API_KEY ||
null
);
}
async function getMeta(id, isTv, apiKey) {
if (!apiKey) return null;
const type = isTv ? "tv" : "movie";
try {
const res = await fetch(
`https://api.themoviedb.org/3/${type}/${id}?api_key=${apiKey}`,
{ signal: AbortSignal.timeout(5000) }
);
if (!res.ok) return null;
const data = await res.json();
const title = isTv ? data.name : data.title;
const date = isTv ? data.first_air_date : data.release_date;
const year = date ? parseInt(date.slice(0, 4), 10) : null;
return { title, year };
} catch {
return null;
}
}
function mapSource(source) {
if (!source || !source.url) return null;
if (source.type !== "hls" && source.type !== "mp4") return null;
if (!/^https?:\/\//.test(source.url)) return null;
return {
url: source.url,
headers: {
"User-Agent": source.headers?.["User-Agent"] || getUA(),
Referer: source.headers?.Referer || "https://movenerds.space/",
Origin: source.headers?.Origin || "https://movenerds.space",
},
server: source.name || source.providerName || "MoverNerds",
quality: source.quality || "1080p",
type: source.type,
skipProxy: true,
};
}
export async function getStream(args) {
const { id, s = null, e = null } = args;
const isTv = s != null && e != null;
const apiKey = resolveApiKey(args);
const meta = await getMeta(id, isTv, apiKey);
if (!meta?.title) {
if (process.env.DEBUG_CAUSE) {
console.error(
`[movenerds] Could not resolve title for ${isTv ? "tv" : "movie"} ${id} ` +
`(tmdbApiKey ${apiKey ? "present" : "MISSING"})`
);
}
return null;
}
const params = new URLSearchParams({
tmdbId: String(id),
type: isTv ? "tv" : "movie",
title: meta.title,
});
if (meta.year) params.set("year", String(meta.year));
if (isTv) {
params.set("season", String(s));
params.set("episode", String(e));
params.set("language", "en");
}
const url = `${API_BASE}/stream?${params.toString()}`;
try {
const res = await fetch(url, {
headers: getHeaders(),
signal: AbortSignal.timeout(20000),
});
if (!res.ok) {
if (process.env.DEBUG_CAUSE) {
console.error(`[movenerds] HTTP ${res.status} for ${url}`);
}
return null;
}
const data = await res.json();
const primary = Array.isArray(data?.primarySources) ? data.primarySources : [];
if (!primary.length) {
if (process.env.DEBUG_CAUSE) {
console.error(`[movenerds] No primarySources in response for ${url}`);
}
return null;
}
const allUrls = primary.map(mapSource).filter(Boolean);
if (!allUrls.length) return null;
return { allUrls };
} catch (err) {
if (process.env.DEBUG_CAUSE) {
console.error(`[movenerds] fetch error for ${url}:`, err.message);
if (err.cause) console.error(" cause:", err.cause.code, err.cause.message);
}
return null;
}
}