added 3eyeddraven, cinemaarmy, movenerds, thugtv, vidy

This commit is contained in:
2026-10-11 16:04:29 -07:00
parent f3aa9fe7c7
commit 5e0a388056
10 changed files with 1230 additions and 12 deletions

View File

@@ -1,11 +1,23 @@
// Currently active sources reported on 6:18:22 PM PST 9/13/2026
// None Anime Tested: 936075
// Anime Tested: 37854
// Fshare/Fsonic Tested: 155 ( they don't have Michael )
export const SOURCES = [
{
key: '3eyedraven',
label: '3eyedraven',
sourceFile: '3eyedraven',
proxyParam: '3er',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: false,
skipProxy: true,
cdnHeaders: [{
pattern: /3eyedraven\.watch/i,
headers: {
Referer: 'https://3eyedraven.watch/',
Origin: 'https://3eyedraven.watch',
},
}],
},
{
key: '123anime',
label: '123Anime',
@@ -106,6 +118,25 @@ export const SOURCES = [
}],
},
{
key: 'cinemaarmy',
label: 'CinemaArmy',
sourceFile: 'cinemaarmy',
proxyParam: 'ca',
timeout: 30000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /cinema\.army/i,
headers: {
Referer: 'https://cinema.army/',
Origin: 'https://cinema.army',
},
}],
},
{
key: 'dulo',
label: 'Dulo',
@@ -191,6 +222,25 @@ export const SOURCES = [
},
},
{
key: 'movenerds',
label: 'MoverNerds',
sourceFile: 'movenerds',
proxyParam: 'mnr',
timeout: 30000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /movenerds\.space/i,
headers: {
Referer: 'https://movenerds.space/',
Origin: 'https://movenerds.space',
},
}],
},
{
key: 'movienight',
label: 'MovieNight',
@@ -293,6 +343,26 @@ export const SOURCES = [
},
}],
},
{
key: 'thugtv',
label: 'ThugTV',
sourceFile: 'thugtv',
proxyParam: 'tv',
timeout: 30000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /thugtv\.online|thuktv\.onrender\.com/i,
headers: {
Referer: 'https://thugtv.online/',
Origin: 'https://thugtv.online',
},
}],
},
{
key: 'vidapi',
label: 'VidAPI',
@@ -426,6 +496,25 @@ export const SOURCES = [
multiUrl: true,
},
{
key: 'vidy',
label: 'Vidy',
sourceFile: 'vidy',
proxyParam: 'vy',
timeout: 25000,
jitter: 500,
retries: 2,
multiUrl: true,
skipProxy: true,
cdnHeaders: [{
pattern: /wecollege\.net|vidy\.st/i,
headers: {
Referer: 'https://www.vidy.st/',
Origin: 'https://www.vidy.st',
},
}],
},
{
key: 'vidzee',
label: 'VidZee',

136
src/sources/3eyedraven.js Normal file
View File

@@ -0,0 +1,136 @@
import { getUA } from "../utils/helpers.js";
const BASE_URL = "https://3eyedraven.watch";
const API_BASE = "https://3eyedraven.watch/api";
function getHeaders() {
return {
"User-Agent": getUA(),
Accept: "application/json",
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
};
}
function resolveApiKey(args) {
return (
args?.tmdbApiKey ||
args?.sdk?.tmdbApiKey ||
process.env.TMDB_API_KEY ||
null
);
}
async function getTitle(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();
return isTv ? data.name : data.title;
} catch {
return null;
}
}
function slugify(text) {
return text
.toLowerCase()
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
}
function absolute(url) {
if (!url) return "";
if (/^https?:\/\//.test(url)) return url;
return `${BASE_URL}${url.startsWith("/") ? "" : "/"}${url}`;
}
export async function getStream(args) {
const { id, s = null, e = null } = args;
const isTv = s != null && e != null;
const apiKey = resolveApiKey(args);
const title = await getTitle(id, isTv, apiKey);
if (!title) {
if (process.env.DEBUG_CAUSE) {
console.error(
`[3eyedraven] Could not resolve title for ${isTv ? "tv" : "movie"} ${id} ` +
`(tmdbApiKey ${apiKey ? "present" : "MISSING"})`
);
}
return null;
}
const identifier = `${slugify(title)}-${id}`;
const url = isTv
? `${API_BASE}/stream/${identifier}?season=${s}&episode=${e}`
: `${API_BASE}/stream/${identifier}`;
try {
const res = await fetch(url, {
headers: getHeaders(),
signal: AbortSignal.timeout(15000),
});
if (!res.ok) {
if (process.env.DEBUG_CAUSE) {
console.error(`[3eyedraven] HTTP ${res.status} for ${url}`);
}
return null;
}
const data = await res.json();
const stream = data?.stream;
if (!stream?.url) {
if (process.env.DEBUG_CAUSE) {
console.error(`[3eyedraven] No stream.url in response for ${url}`);
}
return null;
}
const streamUrl = absolute(stream.url);
if (!/^https?:\/\//.test(streamUrl)) return null;
const subtitles = (stream.subtitles || [])
.map(sub => {
const subUrl = absolute(sub.url);
if (!subUrl) return null;
return {
label: sub.label || sub.language || "Subtitle",
file: subUrl,
type: "vtt",
source: "3eyedraven",
};
})
.filter(Boolean);
return {
url: streamUrl,
headers: {
"User-Agent": getUA(),
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
server: "3eyedraven",
quality: "1080p",
type: stream.format === "hls" ? "hls" : "mp4",
subtitles,
skipProxy: true,
};
} catch (err) {
if (process.env.DEBUG_CAUSE) {
console.error(`[3eyedraven] fetch error for ${url}:`, err.message);
if (err.cause) console.error(" cause:", err.cause.code, err.cause.message);
}
return null;
}
}

181
src/sources/cinemaarmy.js Normal file
View File

@@ -0,0 +1,181 @@
import crypto from "crypto";
import { Session, ClientIdentifier, initTLS, destroyTLS } from "node-tls-client";
const BASE_URL = "https://cinema.army";
const API_BASE = "https://cinema.army/api";
const XOR_KEY = "f16a6b92fcd6eed3e9476297f893f2505c32f79bc6efef9b6c95496aa5914548";
const HMAC_KEY = "a2cfc9eaa2a3690b5d4f6b5f958dbdeae52a097283d375bd253ee7c7062524f5";
const UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
const SERVERS = [
{ key: "cookiebakers", method: "POST", label: "CookieBakers" },
{ key: "tcloud", method: "GET", label: "TCloud" },
{ key: "ipcloud", method: "GET", label: "IPcloud" },
{ key: "dcloud", method: "GET", label: "Dcloud" },
];
let tlsInitialized = false;
let session = null;
async function ensureSession() {
if (!tlsInitialized) {
await initTLS();
tlsInitialized = true;
}
if (!session) {
session = new Session({
clientIdentifier: ClientIdentifier.chrome_131,
timeout: 15000,
insecureSkipVerify: false,
});
}
return session;
}
function getHeaders(token, id, isTv, s, e) {
const referer = isTv
? `${BASE_URL}/watch?id=${id}&type=tv&season=${s}&episode=${e}`
: `${BASE_URL}/watch?id=${id}&type=movie`;
return {
"User-Agent": UA,
"Accept": "application/json",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Cache-Control": "no-cache",
"Pragma": "no-cache",
"Referer": referer,
"Origin": BASE_URL,
"Sec-Ch-Ua": '"Chromium";v="131", "Not_A Brand";v="24"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"Priority": "u=1, i",
"X-Stream-Token": token,
};
}
function generateToken(id) {
try {
const minute = Math.floor(Date.now() / 1000 / 60);
return crypto
.createHmac("sha256", HMAC_KEY)
.update(`${id}:${minute}`)
.digest("hex");
} catch {
return "";
}
}
function decryptStreamUrl(encoded) {
if (!encoded) return "";
if (encoded.startsWith("http://") || encoded.startsWith("https://")) return encoded;
try {
const decoded = Buffer.from(encoded, "base64").toString("binary");
let out = "";
for (let i = 0; i < decoded.length; i++) {
out += String.fromCharCode(
decoded.charCodeAt(i) ^ XOR_KEY.charCodeAt(i % XOR_KEY.length)
);
}
return out;
} catch {
return encoded;
}
}
export async function getStream(args) {
const { id, s = null, e = null } = args;
const isTv = s != null && e != null;
const token = generateToken(id);
const headers = getHeaders(token, id, isTv, s, e);
const sess = await ensureSession();
const requests = SERVERS.map(server => {
const path = isTv
? `/${server.key}/tv/${id}/${s}/${e}`
: `/${server.key}/movie/${id}`;
return { url: `${API_BASE}${path}`, server };
});
const results = await Promise.allSettled(
requests.map(async ({ url, server }) => {
try {
const res = server.method === "POST"
? await sess.post(url, { headers })
: await sess.get(url, { headers });
if (res.status !== 200) {
if (process.env.DEBUG_CAUSE) {
console.error(`[CinemaArmy ${server.key}] HTTP ${res.status}`);
}
return null;
}
const text = await res.text();
let data;
try {
data = JSON.parse(text);
} catch {
return null;
}
const encrypted = data?.stream?.url;
if (!encrypted) return null;
const streamUrl = decryptStreamUrl(encrypted);
if (!streamUrl || !/^https?:\/\//.test(streamUrl)) return null;
return {
url: streamUrl,
headers: {
"User-Agent": UA,
Referer: `${BASE_URL}/`,
Origin: BASE_URL,
},
server: `CinemaArmy (${server.label})`,
quality: "1080p",
type: streamUrl.includes(".m3u8") ? "hls" : "mp4",
};
} catch (err) {
if (process.env.DEBUG_CAUSE) {
console.error(`[CinemaArmy ${server.key}] ${err.message}`);
if (err.cause) {
console.error(` cause.code: ${err.cause.code || "n/a"}`);
console.error(` cause.message: ${err.cause.message || err.cause}`);
}
}
return null;
}
})
);
const allUrls = results
.filter(r => r.status === "fulfilled" && r.value)
.map(r => r.value);
if (!allUrls.length) return null;
return { allUrls };
}
export async function cleanup() {
if (session) {
try {
await session.close();
} catch { }
session = null;
}
if (tlsInitialized) {
try {
await destroyTLS();
} catch { }
tlsInitialized = false;
}
}

125
src/sources/movenerds.js Normal file
View File

@@ -0,0 +1,125 @@
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;
}
}

View File

@@ -1,5 +1,3 @@
// im being salty
import { USER_AGENT, fetchJson } from '../utils/helpers.js';
const C_ARRAY = [

62
src/sources/thugtv.js Normal file
View File

@@ -0,0 +1,62 @@
import { getUA } from "../utils/helpers.js";
const API_BASE = "https://thuktv.onrender.com/api";
function getHeaders() {
return {
"User-Agent": getUA(),
Accept: "application/json",
Referer: "https://thugtv.online/",
Origin: "https://thugtv.online",
};
}
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": getUA(),
Referer: "https://thugtv.online/",
Origin: "https://thugtv.online",
},
server: source.name || source.label || "ThugTV",
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 url = isTv
? `${API_BASE}/stream/tv/${id}/${s}/${e}`
: `${API_BASE}/stream/movie/${id}`;
try {
const res = await fetch(url, {
headers: getHeaders(),
signal: AbortSignal.timeout(15000),
});
if (!res.ok) return null;
const data = await res.json();
const sources = Array.isArray(data?.sources) ? data.sources : [];
if (!sources.length) return null;
const allUrls = sources
.map(mapSource)
.filter(Boolean);
if (!allUrls.length) return null;
return { allUrls };
} catch {
return null;
}
}

187
src/sources/vidy.js Normal file
View File

@@ -0,0 +1,187 @@
import { getUA } from "../utils/helpers.js";
const API_URL = "https://api.wecollege.net";
const SERVERS = ["miami", "atlanta", "seattle", "denver", "dallas", "tampa"];
const MAGIC = [109, 118, 109, 49];
function imul(a, b) {
return Math.imul(a | 0, b | 0) >>> 0;
}
function pFn(e) {
e = e >>> 0;
e = (e ^ (e >>> 16)) >>> 0;
e = imul(e, 2246822507);
e = (e ^ (e >>> 13)) >>> 0;
e = imul(e, 3266489909);
e = (e ^ (e >>> 16)) >>> 0;
return e;
}
function gFn(e, t) {
e = e >>> 0;
t = t & 31;
if (t === 0) return e;
return ((e << t) | (e >>> (32 - t))) >>> 0;
}
function decryptVidy(ciphertext, seedStr, mediaId) {
let padded = ciphertext.replace(/-/g, "+").replace(/_/g, "/");
while (padded.length % 4) padded += "=";
const raw = new Uint8Array(Buffer.from(padded, "base64"));
const length = raw.length;
let tFnv = 2166136261;
for (let i = 0; i < seedStr.length; i++) {
tFnv = imul((tFnv ^ seedStr.charCodeAt(i)) >>> 0, 16777619);
}
const fnvRes = pFn(tFnv);
const innerArg = (((mediaId >>> 0) ^ 2654435769) >>> 0);
const inner = pFn(innerArg);
let n = pFn(((fnvRes ^ inner) >>> 0));
const S = {};
for (let e = 0; e < 8; e++) {
const t = n % 61;
n = gFn((n + 2654435769) >>> 0, 7 + (7 & e));
S[t] = ((n ^ pFn(n)) >>> 0);
n = pFn((n + t) >>> 0);
}
let acc = pFn(((2779096485 ^ n) >>> 0));
const keystream = new Uint8Array(length);
let sCnt = 0;
let idx = 0;
while (idx < length) {
const sVal = acc;
const o = sVal % 61;
const hasO = Object.prototype.hasOwnProperty.call(S, o);
const iVal = hasO ? -1 : 0;
const d = hasO ? S[o] : 0;
const nTerm = ((d ^ imul(2654435769, sCnt + 1)) >>> 0);
sCnt += 1;
const aTerm = sVal;
let lVal = (((aTerm ^ nTerm) | (aTerm & nTerm & iVal)) >>> 0);
const term1 = gFn((lVal + sVal) >>> 0, 31 & o);
const term2 = gFn(sVal, (31 & imul(o, 7)) >>> 0);
lVal = ((term1 ^ term2) >>> 0);
const newS = pFn((lVal + 2654435769) >>> 0);
S[o] = newS;
acc = newS;
const tVal = newS;
keystream[idx++] = tVal & 0xFF;
if (idx < length) keystream[idx++] = (tVal >>> 8) & 0xFF;
if (idx < length) keystream[idx++] = (tVal >>> 16) & 0xFF;
if (idx < length) keystream[idx++] = (tVal >>> 24) & 0xFF;
}
for (let i = 0; i < length; i++) {
raw[i] ^= keystream[i];
}
for (let i = 0; i < MAGIC.length; i++) {
if (raw[i] !== MAGIC[i]) {
throw new Error("Vidy decrypt failed: magic header mismatch");
}
}
return new TextDecoder().decode(raw.slice(MAGIC.length));
}
export async function getStream(args) {
const { id, s = null, e = null } = args;
const isTv = s != null && e != null;
const mediaId = Number(id);
const headers = {
"User-Agent": getUA(),
Referer: "https://www.vidy.st/",
Origin: "https://www.vidy.st",
};
try {
const seedUrl = new URL(`${API_URL}/seed`);
seedUrl.searchParams.set("mediaId", String(id));
const seedRes = await fetch(seedUrl.toString(), {
headers,
signal: AbortSignal.timeout(6000),
});
if (!seedRes.ok) return null;
const seedData = await seedRes.json();
const seed = seedData?.seed;
if (!seed) return null;
for (const server of SERVERS) {
try {
const srcUrl = new URL(`${API_URL}/${server}/sources`);
srcUrl.searchParams.set("mediaType", isTv ? "tv" : "movie");
srcUrl.searchParams.set("tmdbId", String(id));
srcUrl.searchParams.set("enc", "2");
srcUrl.searchParams.set("seed", seed);
if (isTv) {
srcUrl.searchParams.set("seasonId", String(s));
srcUrl.searchParams.set("episodeId", String(e));
}
const res = await fetch(srcUrl.toString(), {
headers,
signal: AbortSignal.timeout(6000),
});
if (!res.ok) continue;
const text = await res.text();
let data;
try {
data = JSON.parse(decryptVidy(text, seed, mediaId));
} catch {
continue;
}
const sources = data?.sources || [];
const playlist = data?.playlist || "";
if (!sources.length && !playlist) continue;
let streamUrl = playlist;
let quality = "1080p";
for (const src of sources) {
if (src?.quality === "1080p" && src?.url) {
streamUrl = src.url;
quality = "1080p";
break;
}
}
if (!streamUrl && sources.length) {
streamUrl = sources[0]?.url || "";
quality = sources[0]?.quality || "";
}
if (!streamUrl) continue;
return {
url: streamUrl,
headers: {
"User-Agent": getUA(),
Referer: "https://www.vidy.st/",
Origin: "https://www.vidy.st",
},
server: "Vidy",
quality,
type: streamUrl.includes(".m3u8") ? "hls" : "mp4",
};
} catch {
continue;
}
}
return null;
} catch {
return null;
}
}