@vyla-entertainment/sdk 1.6.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -10
- package/package.json +1 -1
- package/src/config.js +116 -48
- package/src/sdk.d.ts +1 -1
- package/src/sources/1embed.js +34 -11
- package/src/sources/anihq.js +14 -2
- package/src/sources/anikoto.js +198 -0
- package/src/sources/anineko.js +122 -15
- package/src/sources/anizone.js +109 -0
- package/src/sources/cinesu.js +94 -0
- package/src/sources/flaxmovies.js +21 -9
- package/src/sources/fsonic.js +2 -2
- package/src/sources/fsonline.js +2 -2
- package/src/sources/kickassanime.js +107 -0
- package/src/sources/lmscript.js +43 -0
- package/src/sources/meowtv.js +1 -8
- package/src/sources/reanime.js +324 -0
- package/src/sources/videasy.js +6 -12
- package/src/sources/vidvault.js +114 -0
- package/src/sources/vidzee.js +210 -101
- package/src/sources/vixsrc.js +28 -14
- package/src/subtitles.js +0 -18
- package/src/utils/helpers.js +56 -20
- package/src/sources/goated.js +0 -262
- package/src/sources/nebula.js +0 -62
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { fetchJson, getTmdbInfo, tmdbToAnilist, USER_AGENT } from '../utils/helpers.js';
|
|
3
|
+
|
|
4
|
+
const BASE = "https://reanime.to";
|
|
5
|
+
const FLIX = "https://flixcloud.cc";
|
|
6
|
+
const HEADERS = { "User-Agent": USER_AGENT, Accept: "application/json, */*" };
|
|
7
|
+
|
|
8
|
+
async function sha256hex(s) {
|
|
9
|
+
const buf = await crypto.subtle.digest("SHA-256", typeof s === "string" ? new TextEncoder().encode(s) : s);
|
|
10
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function b64toU8(b64) {
|
|
14
|
+
const bin = Buffer.from(b64, 'base64');
|
|
15
|
+
return new Uint8Array(bin);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function deriveFields(seed) {
|
|
19
|
+
let e = seed;
|
|
20
|
+
for (let i = 0; i < 3; i++) e = await sha256hex(e + i);
|
|
21
|
+
let l = e;
|
|
22
|
+
for (let i = 0; i < 3; i++) l = await sha256hex(l + i);
|
|
23
|
+
return {
|
|
24
|
+
keyField: "kf_" + e.substring(8, 16),
|
|
25
|
+
ivField: "ivf_" + e.substring(16, 24),
|
|
26
|
+
containerName: "cd_" + e.substring(24, 32),
|
|
27
|
+
arrayName: "ad_" + e.substring(32, 40),
|
|
28
|
+
objectName: "od_" + e.substring(40, 48),
|
|
29
|
+
tokenField: e.substring(48, 64) + "_" + e.substring(56, 64),
|
|
30
|
+
keyFrag2Field: l.substring(0, 16) + "_" + l.substring(16, 24)
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function extractSsrObj(html) {
|
|
35
|
+
const m = html.match(/\{type:"data",data:(\{)/);
|
|
36
|
+
if (!m) return null;
|
|
37
|
+
let depth = 0;
|
|
38
|
+
const start = html.indexOf("{", m.index + m[0].length - 1);
|
|
39
|
+
for (let i = start; i < html.length; i++) {
|
|
40
|
+
if (html[i] === "{") depth++;
|
|
41
|
+
else if (html[i] === "}") {
|
|
42
|
+
if (--depth === 0) return html.slice(start, i + 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseJsLiteral(src) {
|
|
49
|
+
let i = 0;
|
|
50
|
+
function ws() { while (i < src.length && /\s/.test(src[i])) i++; }
|
|
51
|
+
function parseValue() {
|
|
52
|
+
ws();
|
|
53
|
+
if (src[i] === "{") return parseObject();
|
|
54
|
+
if (src[i] === "[") return parseArray();
|
|
55
|
+
if (src[i] === '"') return parseDStr();
|
|
56
|
+
if (src[i] === "'") return parseSStr();
|
|
57
|
+
if (src.startsWith("true", i)) { i += 4; return true; }
|
|
58
|
+
if (src.startsWith("false", i)) { i += 5; return false; }
|
|
59
|
+
if (src.startsWith("null", i)) { i += 4; return null; }
|
|
60
|
+
if (src.startsWith("undefined", i)) { i += 9; return null; }
|
|
61
|
+
if (src.startsWith("!0", i)) { i += 2; return true; }
|
|
62
|
+
if (src.startsWith("!1", i)) { i += 2; return false; }
|
|
63
|
+
const m = src.slice(i).match(/^-?[\d.]+([eE][+-]?\d+)?/);
|
|
64
|
+
if (m) { i += m[0].length; return parseFloat(m[0]); }
|
|
65
|
+
throw new Error(`Parse error`);
|
|
66
|
+
}
|
|
67
|
+
function parseDStr() {
|
|
68
|
+
let r = ""; i++;
|
|
69
|
+
while (i < src.length && src[i] !== '"') {
|
|
70
|
+
if (src[i] === "\\") { i++; r += src[i++]; }
|
|
71
|
+
else r += src[i++];
|
|
72
|
+
}
|
|
73
|
+
i++; return r;
|
|
74
|
+
}
|
|
75
|
+
function parseSStr() {
|
|
76
|
+
let r = ""; i++;
|
|
77
|
+
while (i < src.length && src[i] !== "'") {
|
|
78
|
+
if (src[i] === "\\") { i++; r += src[i++]; }
|
|
79
|
+
else r += src[i++];
|
|
80
|
+
}
|
|
81
|
+
i++; return r;
|
|
82
|
+
}
|
|
83
|
+
function parseKey() {
|
|
84
|
+
ws();
|
|
85
|
+
if (src[i] === '"') return parseDStr();
|
|
86
|
+
if (src[i] === "'") return parseSStr();
|
|
87
|
+
const m = src.slice(i).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/);
|
|
88
|
+
if (m) { i += m[0].length; return m[0]; }
|
|
89
|
+
throw new Error(`Bad key`);
|
|
90
|
+
}
|
|
91
|
+
function parseObject() {
|
|
92
|
+
const obj = {}; i++; ws();
|
|
93
|
+
while (i < src.length && src[i] !== "}") {
|
|
94
|
+
if (src[i] === ",") { i++; ws(); continue; }
|
|
95
|
+
const k = parseKey(); ws(); i++;
|
|
96
|
+
obj[k] = parseValue(); ws();
|
|
97
|
+
}
|
|
98
|
+
i++; return obj;
|
|
99
|
+
}
|
|
100
|
+
function parseArray() {
|
|
101
|
+
const arr = []; i++; ws();
|
|
102
|
+
while (i < src.length && src[i] !== "]") {
|
|
103
|
+
if (src[i] === ",") { i++; ws(); continue; }
|
|
104
|
+
arr.push(parseValue()); ws();
|
|
105
|
+
}
|
|
106
|
+
i++; return arr;
|
|
107
|
+
}
|
|
108
|
+
return parseValue();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function parseWasmDecrypt(wasmBytes) {
|
|
112
|
+
const b = wasmBytes;
|
|
113
|
+
let pos = 8;
|
|
114
|
+
while (pos < b.length) {
|
|
115
|
+
const secId = b[pos++];
|
|
116
|
+
let sz = 0, sh = 0, by;
|
|
117
|
+
do { by = b[pos++]; sz |= (by & 127) << sh; sh += 7; } while (by & 128);
|
|
118
|
+
if (secId === 10) {
|
|
119
|
+
pos++;
|
|
120
|
+
let sbs = 0, sh2 = 0, by2;
|
|
121
|
+
do { by2 = b[pos++]; sbs |= (by2 & 127) << sh2; sh2 += 7; } while (by2 & 128);
|
|
122
|
+
pos += sbs; break;
|
|
123
|
+
}
|
|
124
|
+
pos += sz;
|
|
125
|
+
}
|
|
126
|
+
let rbs = 0, sh3 = 0, by3;
|
|
127
|
+
do { by3 = b[pos++]; rbs |= (by3 & 127) << sh3; sh3 += 7; } while (by3 & 128);
|
|
128
|
+
const r = b.slice(pos, pos + rbs);
|
|
129
|
+
function leb(arr, i) {
|
|
130
|
+
let v = 0, s = 0, b2;
|
|
131
|
+
do { b2 = arr[i++]; v |= (b2 & 127) << s; s += 7; } while (b2 & 128);
|
|
132
|
+
return [v, i];
|
|
133
|
+
}
|
|
134
|
+
const XOR_END = [32, 2, 32, 5, 106, 45, 0, 0, 115, 33, 6];
|
|
135
|
+
let txStart = -1;
|
|
136
|
+
outer: for (let i = 0; i < r.length - XOR_END.length; i++) {
|
|
137
|
+
for (let j = 0; j < XOR_END.length; j++) if (r[i + j] !== XOR_END[j]) continue outer;
|
|
138
|
+
txStart = i + XOR_END.length; break;
|
|
139
|
+
}
|
|
140
|
+
if (txStart < 0) return null;
|
|
141
|
+
let txEnd = -1, step = 36;
|
|
142
|
+
for (let i = txStart; i < r.length - 4; i++) {
|
|
143
|
+
if (r[i] === 32 && r[i + 1] === 5 && r[i + 2] === 65) {
|
|
144
|
+
const [val, ni] = leb(r, i + 3);
|
|
145
|
+
if (r[ni] === 108) { txEnd = i; step = val; break; }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (txEnd < 0) return null;
|
|
149
|
+
const code = r.slice(txStart, txEnd);
|
|
150
|
+
function transform(inputByte) {
|
|
151
|
+
let local6 = inputByte & 255;
|
|
152
|
+
const stk = []; let i = 0;
|
|
153
|
+
while (i < code.length) {
|
|
154
|
+
const op = code[i++];
|
|
155
|
+
if (op === 32) { const [idx, ni] = leb(code, i); i = ni; stk.push(idx === 6 ? local6 : 0); }
|
|
156
|
+
else if (op === 33) { const [idx, ni] = leb(code, i); i = ni; const v = stk.pop(); if (idx === 6) local6 = v & 255; }
|
|
157
|
+
else if (op === 65) { const [v, ni] = leb(code, i); i = ni; stk.push(v); }
|
|
158
|
+
else if (op === 106) { const b2 = stk.pop(), a = stk.pop(); stk.push(a + b2 & 255); }
|
|
159
|
+
else if (op === 107) { const b2 = stk.pop(), a = stk.pop(); stk.push(a - b2 + 256 & 255); }
|
|
160
|
+
else if (op === 113) { const b2 = stk.pop(), a = stk.pop(); stk.push(a & b2 & 255); }
|
|
161
|
+
else if (op === 114) { const b2 = stk.pop(), a = stk.pop(); stk.push((a | b2) & 255); }
|
|
162
|
+
else if (op === 115) { const b2 = stk.pop(), a = stk.pop(); stk.push((a ^ b2) & 255); }
|
|
163
|
+
else if (op === 116) { const b2 = stk.pop(), a = stk.pop(); stk.push(a << (b2 & 7) & 255); }
|
|
164
|
+
else if (op === 118) { const b2 = stk.pop(), a = stk.pop(); stk.push(a >>> (b2 & 7) & 255); }
|
|
165
|
+
}
|
|
166
|
+
return local6;
|
|
167
|
+
}
|
|
168
|
+
return { step, transform };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function runDecrypt(wasmBytes, frag1, kf2, T, seedInt) {
|
|
172
|
+
const parsed = parseWasmDecrypt(wasmBytes);
|
|
173
|
+
if (!parsed) return null;
|
|
174
|
+
const { step, transform } = parsed;
|
|
175
|
+
const out = new Uint8Array(frag1.length);
|
|
176
|
+
for (let i = 0; i < frag1.length; i++) {
|
|
177
|
+
const c = (frag1[i] ^ kf2[i] ^ T[i]) & 255;
|
|
178
|
+
out[i] = transform(c) ^ i * step + seedInt & 255;
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function decryptEmbed(html) {
|
|
184
|
+
const raw = extractSsrObj(html);
|
|
185
|
+
if (!raw) return null;
|
|
186
|
+
const data = parseJsLiteral(raw);
|
|
187
|
+
const seed = data.obfuscation_seed;
|
|
188
|
+
if (!seed) return null;
|
|
189
|
+
|
|
190
|
+
const fields = await deriveFields(seed);
|
|
191
|
+
const ocd = data.obfuscated_crypto_data;
|
|
192
|
+
if (!ocd) return null;
|
|
193
|
+
|
|
194
|
+
const container = ocd[fields.containerName];
|
|
195
|
+
if (!container) return null;
|
|
196
|
+
|
|
197
|
+
const arr = container[fields.arrayName];
|
|
198
|
+
if (!arr || !arr[0]) return null;
|
|
199
|
+
|
|
200
|
+
const obj = arr[0][fields.objectName];
|
|
201
|
+
if (!obj) return null;
|
|
202
|
+
|
|
203
|
+
const frag1 = b64toU8(obj[fields.keyField]);
|
|
204
|
+
const iv = b64toU8(obj[fields.ivField]);
|
|
205
|
+
const kf2raw = data[fields.keyFrag2Field];
|
|
206
|
+
if (!kf2raw) return null;
|
|
207
|
+
|
|
208
|
+
const kf2 = b64toU8(kf2raw);
|
|
209
|
+
const token = data[fields.tokenField];
|
|
210
|
+
if (!token) return null;
|
|
211
|
+
|
|
212
|
+
const tokData = await fetchJson(`${FLIX}/api/m3u8/${token}`, { headers: { ...HEADERS, Referer: `${BASE}/` } }).catch(() => null);
|
|
213
|
+
if (!tokData) return null;
|
|
214
|
+
|
|
215
|
+
const vidKey = (await sha256hex(token + "vid")).substring(0, 10);
|
|
216
|
+
const keyKey = (await sha256hex(token + "key")).substring(0, 10);
|
|
217
|
+
const v_bytes = b64toU8(tokData[vidKey] || "");
|
|
218
|
+
const T_bytes = b64toU8(tokData[keyKey] || "");
|
|
219
|
+
|
|
220
|
+
if (!v_bytes.length || !T_bytes.length) return null;
|
|
221
|
+
|
|
222
|
+
const seedInt = parseInt(seed.substring(0, 8), 16);
|
|
223
|
+
const wPayload = b64toU8(data.w_payload ?? "");
|
|
224
|
+
if (!wPayload.length) return null;
|
|
225
|
+
|
|
226
|
+
const wasmOut = runDecrypt(wPayload, frag1, kf2, T_bytes, seedInt);
|
|
227
|
+
if (!wasmOut) return null;
|
|
228
|
+
|
|
229
|
+
const keyMat = await crypto.subtle.importKey("raw", wasmOut, { name: "PBKDF2" }, false, ["deriveBits"]);
|
|
230
|
+
const derived = new Uint8Array(await crypto.subtle.deriveBits(
|
|
231
|
+
{ name: "PBKDF2", salt: new TextEncoder().encode(seed), iterations: 1000, hash: "SHA-256" },
|
|
232
|
+
keyMat,
|
|
233
|
+
256
|
|
234
|
+
));
|
|
235
|
+
|
|
236
|
+
for (let i = 0; i < 32; i++) derived[i] ^= seed.charCodeAt(i % seed.length);
|
|
237
|
+
|
|
238
|
+
const aesKeyBytes = new Uint8Array(await crypto.subtle.digest("SHA-256", derived));
|
|
239
|
+
const aesKey = await crypto.subtle.importKey("raw", aesKeyBytes, { name: "AES-CBC" }, false, ["decrypt"]);
|
|
240
|
+
|
|
241
|
+
const plain = await crypto.subtle.decrypt({ name: "AES-CBC", iv }, aesKey, v_bytes).catch(() => null);
|
|
242
|
+
if (!plain) return null;
|
|
243
|
+
|
|
244
|
+
const url = new TextDecoder().decode(plain).trim().replace(/\0+$/, "");
|
|
245
|
+
if (!url.startsWith("http")) return null;
|
|
246
|
+
|
|
247
|
+
return { url };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function searchReanime(query) {
|
|
251
|
+
const data = await fetchJson(`${BASE}/api/v1/search?${new URLSearchParams({ q: query, limit: 10 })}`, { headers: HEADERS }).catch(() => null);
|
|
252
|
+
return Array.isArray(data?.results) ? data.results : [];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export async function getStream({ id, s, e, audio, tmdbApiKey }) {
|
|
256
|
+
try {
|
|
257
|
+
const isTv = !!s;
|
|
258
|
+
const mediaType = isTv ? 'tv' : 'movie';
|
|
259
|
+
const epNum = e ? Number(e) : 1;
|
|
260
|
+
const seasonNum = s ? Number(s) : 1;
|
|
261
|
+
|
|
262
|
+
let anilistId = null;
|
|
263
|
+
let titles = [];
|
|
264
|
+
const tmdbInfo = await getTmdbInfo(tmdbApiKey, id, mediaType, seasonNum);
|
|
265
|
+
|
|
266
|
+
if (tmdbInfo) {
|
|
267
|
+
titles = tmdbInfo.titles || [];
|
|
268
|
+
anilistId = await tmdbToAnilist(id, mediaType, seasonNum, titles, tmdbInfo.year);
|
|
269
|
+
}
|
|
270
|
+
if (!anilistId) {
|
|
271
|
+
anilistId = Number(id);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let animeSlug = null;
|
|
275
|
+
for (const title of titles) {
|
|
276
|
+
const results = await searchReanime(title);
|
|
277
|
+
if (results.length) {
|
|
278
|
+
animeSlug = results[0].anime_id;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!animeSlug) return null;
|
|
284
|
+
|
|
285
|
+
const audioPref = audio === 'dub' ? 'dub' : 'sub';
|
|
286
|
+
const watchRes = await fetchJson(`${BASE}/api/watch/${animeSlug}/${epNum}`, { headers: HEADERS }).catch(() => null);
|
|
287
|
+
const links = [...(watchRes?.episode_links || [])];
|
|
288
|
+
|
|
289
|
+
const flixRes = await fetchJson(`${BASE}/api/flix/${anilistId}/${epNum}`, { headers: HEADERS }).catch(() => null);
|
|
290
|
+
if (flixRes?.success && Array.isArray(flixRes.servers)) {
|
|
291
|
+
const seen = new Set(links.map((s) => s["$id"]));
|
|
292
|
+
for (const srv of flixRes.servers) {
|
|
293
|
+
if (!seen.has(srv["$id"])) links.push(srv);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const audioTypes = audioPref === "sub" ? ["sub", "s-sub"] : ["dub", "s-dub"];
|
|
298
|
+
const servers = links.filter((srv) => audioTypes.includes(srv.dataType));
|
|
299
|
+
if (!servers.length) return null;
|
|
300
|
+
|
|
301
|
+
const urls = [];
|
|
302
|
+
for (const srv of servers.slice(0, 3)) {
|
|
303
|
+
if (!srv.dataLink) continue;
|
|
304
|
+
const res = await fetch(srv.dataLink, { headers: { ...HEADERS, Referer: `${BASE}/` } }).catch(() => null);
|
|
305
|
+
if (!res || !res.ok) continue;
|
|
306
|
+
const html = await res.text();
|
|
307
|
+
const stream = await decryptEmbed(html);
|
|
308
|
+
if (stream?.url) {
|
|
309
|
+
urls.push({
|
|
310
|
+
url: stream.url,
|
|
311
|
+
server: `Reanime - ${srv.serverName || 'Server'}`,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return urls.length ? { allUrls: urls } : null;
|
|
317
|
+
} catch (err) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export async function getSources() {
|
|
323
|
+
return ['Reanime'];
|
|
324
|
+
}
|
package/src/sources/videasy.js
CHANGED
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
import { getTmdbInfo, fetchJson, fetchText, USER_AGENT } from '../utils/helpers.js';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const SPEEDRACE_BASE = "https://api.speedracelight.com";
|
|
4
4
|
const HEADERS = { "Accept": "*/*", "Origin": "https://player.videasy.to", "Referer": "https://player.videasy.to/", "User-Agent": USER_AGENT };
|
|
5
5
|
const SERVERS = [
|
|
6
|
-
{ id: 'jett', name: 'Jett' },
|
|
7
6
|
{ id: 'cdn', name: 'Yoru' },
|
|
8
|
-
{ id: 'tejo', name: 'Tejo' },
|
|
9
|
-
{ id: 'neon2', name: 'Neon' },
|
|
10
|
-
{ id: 'ym', name: 'Sage' },
|
|
11
|
-
{ id: 'downloader2', name: 'Cypher' },
|
|
12
7
|
{ id: 'm4uhd', name: 'Breach' },
|
|
8
|
+
{ id: 'vsrc', name: 'Neon' },
|
|
13
9
|
{ id: 'hdmovie', name: 'Vyse' },
|
|
14
10
|
{ id: 'meine', name: 'Killjoy' },
|
|
15
11
|
{ id: 'lamovie', name: 'Omen' },
|
|
16
12
|
{ id: 'superflix', name: 'Raze' }
|
|
17
13
|
];
|
|
18
14
|
|
|
19
|
-
export async function getStream({ id, s, e, server }) {
|
|
15
|
+
export async function getStream({ id, s, e, server, tmdbApiKey }) {
|
|
20
16
|
try {
|
|
21
17
|
const isTv = s != null && e != null;
|
|
22
|
-
const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie');
|
|
18
|
+
const info = await getTmdbInfo(tmdbApiKey, id, isTv ? 'tv' : 'movie');
|
|
23
19
|
if (!info?.titles?.length) return null;
|
|
24
20
|
|
|
25
|
-
const seedData = await fetchJson(`${
|
|
21
|
+
const seedData = await fetchJson(`${SPEEDRACE_BASE}/seed?mediaId=${id}`, { headers: HEADERS, signal: AbortSignal.timeout(5000) });
|
|
26
22
|
const seed = seedData?.seed;
|
|
27
23
|
if (!seed) return null;
|
|
28
24
|
|
|
@@ -36,9 +32,7 @@ export async function getStream({ id, s, e, server }) {
|
|
|
36
32
|
}
|
|
37
33
|
|
|
38
34
|
const settled = await Promise.allSettled(targets.map(async srv => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let url = `${WINGS_BASE}/${srv.id}/sources-with-title?title=${encTitle}&mediaType=${isTv ? 'tv' : 'movie'}&year=${info.year || ''}&tmdbId=${id}&imdbId=${info.imdbId || "tt0000000"}&enc=2&seed=${seed}`;
|
|
35
|
+
let url = `${SPEEDRACE_BASE}/${srv.id}/sources-with-title?title=${encTitle}&mediaType=${isTv ? 'tv' : 'movie'}&year=${info.year || ''}&tmdbId=${id}&imdbId=${info.imdbId || "tt0000000"}&enc=2&seed=${seed}`;
|
|
42
36
|
if (isTv) url += `&episodeId=${e}&seasonId=${s}`;
|
|
43
37
|
|
|
44
38
|
const encText = await fetchText(url, { headers: HEADERS, signal: AbortSignal.timeout(10000) });
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { USER_AGENT } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "https://vidvault.ru";
|
|
4
|
+
const HEADERS = {
|
|
5
|
+
"Referer": `${BASE_URL}/`,
|
|
6
|
+
"Origin": BASE_URL,
|
|
7
|
+
"User-Agent": USER_AGENT,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
async function getToken() {
|
|
11
|
+
const res = await fetch(`${BASE_URL}/api/get-token`, {
|
|
12
|
+
method: "GET",
|
|
13
|
+
headers: HEADERS
|
|
14
|
+
});
|
|
15
|
+
const data = await res.json();
|
|
16
|
+
return data.t;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getCaptionType(url) {
|
|
20
|
+
return url.toLowerCase().includes(".vtt") ? "vtt" : "srt";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function getStream(args) {
|
|
24
|
+
const { id, s, e } = args;
|
|
25
|
+
const type = (s != null && e != null) ? "tv" : "movie";
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const token = await getToken();
|
|
29
|
+
|
|
30
|
+
const payload = { type, tmdbId: String(id) };
|
|
31
|
+
if (type === "tv") {
|
|
32
|
+
payload.season = String(s);
|
|
33
|
+
payload.episode = String(e);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const res = await fetch(`${BASE_URL}/api/download-proxy`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
...HEADERS,
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"x-request-token": token
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify(payload)
|
|
44
|
+
});
|
|
45
|
+
const data = await res.json();
|
|
46
|
+
|
|
47
|
+
const allUrls = [];
|
|
48
|
+
const subtitles = [];
|
|
49
|
+
|
|
50
|
+
const mp4 = data?.mp4Data;
|
|
51
|
+
if (mp4) {
|
|
52
|
+
const country = mp4.country || "";
|
|
53
|
+
const lanName = mp4.lanName || "";
|
|
54
|
+
const detailPath = mp4.detailPath || "";
|
|
55
|
+
|
|
56
|
+
const downloads = mp4.downloadInfo?.data?.downloads || [];
|
|
57
|
+
for (const d of downloads) {
|
|
58
|
+
if (!d?.url) continue;
|
|
59
|
+
allUrls.push({
|
|
60
|
+
quality: `${d.resolution}p ${detailPath} ${country} ${lanName}`.trim(),
|
|
61
|
+
url: d.url,
|
|
62
|
+
type: "mp4",
|
|
63
|
+
headers: HEADERS
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const srtCaptions = mp4.downloadInfo?.data?.captions || [];
|
|
68
|
+
for (const c of srtCaptions) {
|
|
69
|
+
if (!c?.url) continue;
|
|
70
|
+
subtitles.push({
|
|
71
|
+
label: c.lanName || c.lan || "Unknown",
|
|
72
|
+
type: getCaptionType(c.url),
|
|
73
|
+
language: c.lan || "",
|
|
74
|
+
url: c.url
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const mkvFiles = data?.mkvData?.files || [];
|
|
80
|
+
for (const f of mkvFiles) {
|
|
81
|
+
if (!f?.url) continue;
|
|
82
|
+
allUrls.push({
|
|
83
|
+
quality: `MKV ${f.size || ""}`.trim(),
|
|
84
|
+
url: f.url,
|
|
85
|
+
type: "mp4",
|
|
86
|
+
headers: HEADERS
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const mkvV2 = data?.mkvV2Data;
|
|
91
|
+
if (mkvV2?.url) {
|
|
92
|
+
allUrls.push({
|
|
93
|
+
quality: `${mkvV2.quality || ""} ${mkvV2.language || ""} ${mkvV2.country || ""} (${mkvV2.size || ""})`.trim(),
|
|
94
|
+
url: mkvV2.url,
|
|
95
|
+
type: "mp4",
|
|
96
|
+
headers: HEADERS
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const mkvV3 = data?.mkvV3Data;
|
|
101
|
+
if (mkvV3?.url) {
|
|
102
|
+
allUrls.push({
|
|
103
|
+
quality: `${mkvV3.quality || ""} ${mkvV3.language || ""} ${mkvV3.country || ""} (${mkvV3.size || ""})`.trim(),
|
|
104
|
+
url: mkvV3.url,
|
|
105
|
+
type: "mp4",
|
|
106
|
+
headers: HEADERS
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return allUrls.length ? { allUrls, subtitles } : null;
|
|
111
|
+
} catch (err) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|