@vyla-entertainment/sdk 1.5.0 → 1.8.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/.env.example +1 -0
- package/README.md +2 -32
- package/package.json +1 -1
- package/src/config.js +45 -46
- package/src/downloads/downloads.js +8 -4
- package/src/sdk.d.ts +1 -1
- package/src/sources/1embed.js +34 -11
- package/src/sources/anihq.js +14 -2
- package/src/sources/anineko.js +122 -15
- package/src/sources/cinejoy.js +210 -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/lmscript.js +43 -0
- package/src/sources/videasy.js +6 -12
- package/src/sources/vidvault.js +114 -0
- package/src/sources/vidzee.js +73 -87
- package/src/sources/vixsrc.js +28 -14
- package/src/utils/helpers.js +56 -20
- package/src/sources/goated.js +0 -262
- package/src/sources/nebula.js +0 -62
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
TMDB_API_KEY=your_tmdb_api_key_here
|
package/README.md
CHANGED
|
@@ -11,37 +11,7 @@ npm install @vyla-entertainment/sdk
|
|
|
11
11
|
pnpm install @vyla-entertainment/sdk
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
Start by creating a VylaSDK element with your TMDB api key.
|
|
16
|
-
```js
|
|
17
|
-
import VylaSDK from "@vyla-entertainment/sdk";
|
|
18
|
-
|
|
19
|
-
const sdk = new VylaSDK({
|
|
20
|
-
tmdbApiKey: ...
|
|
21
|
-
});
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
You can now use this to access Vyla's content.
|
|
25
|
-
```js
|
|
26
|
-
// Get sources
|
|
27
|
-
// Args: excludeDisabled
|
|
28
|
-
sdk.getSources(false); -> {key, timeout, label, ...}[]
|
|
29
|
-
|
|
30
|
-
// Get subtitles
|
|
31
|
-
// Args: tmdb id, season?, episode?
|
|
32
|
-
await sdk.getSubtitles('0000000', null, null); -> {label, file, type, source}[]
|
|
33
|
-
|
|
34
|
-
// Probe & test a source
|
|
35
|
-
// Args: source key
|
|
36
|
-
await sdk.probeSource('nebula'); -> {ok, ms}
|
|
37
|
-
|
|
38
|
-
// Probe & test all sources
|
|
39
|
-
await sdk.probeAllSources(); -> {'key': {ok, ms}, ...}
|
|
40
|
-
|
|
41
|
-
// Get HLS streams
|
|
42
|
-
// Args: source key, tmdb id, season?, episode?, clientIP?
|
|
43
|
-
await sdk.getStream('nebula', '0000000', null, null, null) -> {allUrls: {url, quality, label?, server?, type?, skipProxy?, skipVerify?, headers? ...}[]}
|
|
44
|
-
```
|
|
14
|
+
View our full documentation at https://docs.vyla.cc/sdk
|
|
45
15
|
|
|
46
16
|
# [DMCA](https://docs.vyla.cc/misc/dmca)
|
|
47
17
|
|
|
@@ -58,4 +28,4 @@ Original work and core authorship:
|
|
|
58
28
|
[@GavinGoGaming](https://github.com/GavinGoGaming)
|
|
59
29
|
|
|
60
30
|
Full credits and acknowledgements:
|
|
61
|
-
https://docs.vyla.cc/misc/credit
|
|
31
|
+
https://docs.vyla.cc/misc/credit
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// Currently active sources reported on 9:22:41 PM PST 7/30/2026
|
|
2
|
+
|
|
3
|
+
// None Anime Tested: 936075
|
|
4
|
+
// Anime Tested: 37854
|
|
5
|
+
|
|
6
|
+
// Fshare/Fsonic Tested: 155 ( they don't have Michael )
|
|
7
|
+
|
|
1
8
|
export const SOURCES = [
|
|
2
9
|
{
|
|
3
10
|
key: '1embed',
|
|
@@ -107,6 +114,18 @@ export const SOURCES = [
|
|
|
107
114
|
},
|
|
108
115
|
},
|
|
109
116
|
|
|
117
|
+
{
|
|
118
|
+
key: 'cinejoy',
|
|
119
|
+
label: 'Cinejoy',
|
|
120
|
+
sourceFile: 'cinejoy',
|
|
121
|
+
proxyParam: 'cj',
|
|
122
|
+
timeout: 30000,
|
|
123
|
+
jitter: 500,
|
|
124
|
+
retries: 1,
|
|
125
|
+
multiUrl: true,
|
|
126
|
+
skipProxy: true,
|
|
127
|
+
},
|
|
128
|
+
|
|
110
129
|
{
|
|
111
130
|
key: 'flaxmovies',
|
|
112
131
|
label: 'FlaxMovies',
|
|
@@ -116,7 +135,7 @@ export const SOURCES = [
|
|
|
116
135
|
jitter: 500,
|
|
117
136
|
retries: 2,
|
|
118
137
|
multiUrl: true,
|
|
119
|
-
|
|
138
|
+
skipProxy: true,
|
|
120
139
|
cdnHeaders: [{
|
|
121
140
|
pattern: /flix2watch\.pro/i,
|
|
122
141
|
headers: {
|
|
@@ -142,18 +161,6 @@ export const SOURCES = [
|
|
|
142
161
|
},
|
|
143
162
|
},
|
|
144
163
|
|
|
145
|
-
{
|
|
146
|
-
key: 'goated',
|
|
147
|
-
label: 'Goated',
|
|
148
|
-
sourceFile: 'goated',
|
|
149
|
-
proxyParam: 'gt',
|
|
150
|
-
timeout: 25000,
|
|
151
|
-
jitter: 500,
|
|
152
|
-
retries: 2,
|
|
153
|
-
multiUrl: true,
|
|
154
|
-
skipProxy: true,
|
|
155
|
-
},
|
|
156
|
-
|
|
157
164
|
{
|
|
158
165
|
key: 'fsonic',
|
|
159
166
|
label: 'Fsonic',
|
|
@@ -197,6 +204,17 @@ export const SOURCES = [
|
|
|
197
204
|
retries: 1,
|
|
198
205
|
},
|
|
199
206
|
|
|
207
|
+
{
|
|
208
|
+
key: 'lmscript',
|
|
209
|
+
label: 'LMScript',
|
|
210
|
+
sourceFile: 'lmscript',
|
|
211
|
+
proxyParam: 'lmsc',
|
|
212
|
+
timeout: 20000,
|
|
213
|
+
jitter: 500,
|
|
214
|
+
retries: 2,
|
|
215
|
+
multiUrl: true,
|
|
216
|
+
},
|
|
217
|
+
|
|
200
218
|
{
|
|
201
219
|
key: 'lookmovie',
|
|
202
220
|
label: 'LookMovie',
|
|
@@ -233,38 +251,6 @@ export const SOURCES = [
|
|
|
233
251
|
skipProxy: true
|
|
234
252
|
},
|
|
235
253
|
|
|
236
|
-
{
|
|
237
|
-
key: 'nebula',
|
|
238
|
-
label: 'Nebula',
|
|
239
|
-
sourceFile: 'nebula',
|
|
240
|
-
proxyParam: 'nb',
|
|
241
|
-
timeout: 25000,
|
|
242
|
-
jitter: 500,
|
|
243
|
-
retries: 1,
|
|
244
|
-
multiUrl: true,
|
|
245
|
-
skipProxy: true,
|
|
246
|
-
},
|
|
247
|
-
|
|
248
|
-
{
|
|
249
|
-
key: 'nekowatch-sub',
|
|
250
|
-
sourceFile: 'nekowatch',
|
|
251
|
-
label: 'NekoWatch (Sub)',
|
|
252
|
-
proxyParam: 'nwsub',
|
|
253
|
-
timeout: 30000,
|
|
254
|
-
jitter: 500,
|
|
255
|
-
retries: 2
|
|
256
|
-
},
|
|
257
|
-
|
|
258
|
-
{
|
|
259
|
-
key: 'nekowatch-dub',
|
|
260
|
-
sourceFile: 'nekowatch',
|
|
261
|
-
label: 'NekoWatch (Dub)',
|
|
262
|
-
proxyParam: 'nwdub',
|
|
263
|
-
timeout: 30000,
|
|
264
|
-
jitter: 500,
|
|
265
|
-
retries: 2
|
|
266
|
-
},
|
|
267
|
-
|
|
268
254
|
{
|
|
269
255
|
key: 'opstream',
|
|
270
256
|
label: 'OpStream',
|
|
@@ -313,6 +299,7 @@ export const SOURCES = [
|
|
|
313
299
|
timeout: 20000,
|
|
314
300
|
jitter: 500,
|
|
315
301
|
retries: 2,
|
|
302
|
+
skipProxy: true
|
|
316
303
|
},
|
|
317
304
|
|
|
318
305
|
{
|
|
@@ -466,6 +453,17 @@ export const SOURCES = [
|
|
|
466
453
|
multiUrl: true
|
|
467
454
|
},
|
|
468
455
|
|
|
456
|
+
{
|
|
457
|
+
key: 'vidvault',
|
|
458
|
+
label: 'VidVault',
|
|
459
|
+
sourceFile: 'vidvault',
|
|
460
|
+
proxyParam: 'vv',
|
|
461
|
+
timeout: 25000,
|
|
462
|
+
jitter: 500,
|
|
463
|
+
retries: 2,
|
|
464
|
+
multiUrl: true,
|
|
465
|
+
},
|
|
466
|
+
|
|
469
467
|
{
|
|
470
468
|
key: 'vidzee',
|
|
471
469
|
label: 'VidZee',
|
|
@@ -475,13 +473,13 @@ export const SOURCES = [
|
|
|
475
473
|
sourcesTimeout: 10000,
|
|
476
474
|
jitter: 400,
|
|
477
475
|
retries: 3,
|
|
478
|
-
disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's
|
|
479
476
|
verifyHeaders: {
|
|
480
477
|
Accept: '/',
|
|
481
478
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
482
479
|
Referer: 'https://player.vidzee.wtf',
|
|
483
480
|
Origin: 'https://player.vidzee.wtf',
|
|
484
481
|
},
|
|
482
|
+
disabled: true // Disabled temporarily due to decryption issues
|
|
485
483
|
},
|
|
486
484
|
|
|
487
485
|
{
|
|
@@ -512,6 +510,7 @@ export const SOURCES = [
|
|
|
512
510
|
retries: 2,
|
|
513
511
|
skipProxy: true,
|
|
514
512
|
},
|
|
513
|
+
|
|
515
514
|
];
|
|
516
515
|
|
|
517
516
|
export const HEALTH_PROBE_ID = '155';
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { getDownloads, getDownloadsTv } from "./sources/trendimovies.js";
|
|
2
2
|
|
|
3
3
|
export async function fetchDownloads(id, s = null, e = null) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
try {
|
|
5
|
+
const res = s != null && e != null
|
|
6
|
+
? await getDownloadsTv(id, s, e)
|
|
7
|
+
: await getDownloads(id);
|
|
8
|
+
return res || [];
|
|
9
|
+
} catch {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
8
12
|
}
|
package/src/sdk.d.ts
CHANGED
|
@@ -95,7 +95,7 @@ export default class VylaSDK {
|
|
|
95
95
|
s?: number | null,
|
|
96
96
|
e?: number | null,
|
|
97
97
|
clientIP?: string | null
|
|
98
|
-
): Promise<{allUrls: StreamResult[]}>;
|
|
98
|
+
): Promise<StreamResult | { allUrls: StreamResult[] }>;
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
101
|
* Get subtitles for a movie or TV episode.
|
package/src/sources/1embed.js
CHANGED
|
@@ -3,13 +3,11 @@ import { USER_AGENT } from '../utils/helpers.js';
|
|
|
3
3
|
const BASE_URL = "https://1embed.cc";
|
|
4
4
|
|
|
5
5
|
const PROVIDERS = [
|
|
6
|
-
{ id: "
|
|
7
|
-
{ id: "
|
|
8
|
-
{ id: "
|
|
9
|
-
{ id: "
|
|
10
|
-
{ id: "
|
|
11
|
-
{ id: "czo", name: "1Embed - Cinezo" },
|
|
12
|
-
{ id: "fas", name: "1Embed - VidFast" }
|
|
6
|
+
{ id: "BUKE", name: "1Embed - BUKE" },
|
|
7
|
+
{ id: "YOTA", name: "1Embed - YOTA" },
|
|
8
|
+
{ id: "NEPT", name: "1Embed - NEPT" },
|
|
9
|
+
{ id: "AZURE", name: "1Embed - AZURE" },
|
|
10
|
+
{ id: "YORU", name: "1Embed - YORU" }
|
|
13
11
|
];
|
|
14
12
|
|
|
15
13
|
let cachedToken = null;
|
|
@@ -28,6 +26,30 @@ async function getToken() {
|
|
|
28
26
|
} catch { return null; }
|
|
29
27
|
}
|
|
30
28
|
|
|
29
|
+
function decodeBsUrl(url) {
|
|
30
|
+
if (!url) return url;
|
|
31
|
+
let target = url;
|
|
32
|
+
if (url.includes('d=')) {
|
|
33
|
+
try {
|
|
34
|
+
const parts = url.split(/[?&]/);
|
|
35
|
+
for (const part of parts) {
|
|
36
|
+
if (part.startsWith('d=')) {
|
|
37
|
+
target = decodeURIComponent(part.substring(2));
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch { }
|
|
42
|
+
}
|
|
43
|
+
if (target.startsWith('bs_')) {
|
|
44
|
+
try {
|
|
45
|
+
const decoded = Buffer.from(target.slice(3), 'base64').toString('utf-8');
|
|
46
|
+
const reversed = decoded.split('').reverse().join('');
|
|
47
|
+
if (reversed.startsWith('http')) return reversed;
|
|
48
|
+
} catch { }
|
|
49
|
+
}
|
|
50
|
+
return url;
|
|
51
|
+
}
|
|
52
|
+
|
|
31
53
|
async function fetchProviderStream(provider, type, id, s, e, token) {
|
|
32
54
|
try {
|
|
33
55
|
const referer = type === "movie" ? `${BASE_URL}/embed/movie/${id}` : `${BASE_URL}/embed/tv/${id}/${s}/${e}`;
|
|
@@ -51,11 +73,12 @@ async function fetchProviderStream(provider, type, id, s, e, token) {
|
|
|
51
73
|
const results = [];
|
|
52
74
|
for (const src of rawSources) {
|
|
53
75
|
if (typeof src !== 'object' || !src) continue;
|
|
54
|
-
let
|
|
55
|
-
if (typeof
|
|
76
|
+
let rawUrl = src.url || src.file || src.link;
|
|
77
|
+
if (typeof rawUrl !== 'string') continue;
|
|
78
|
+
let streamUrl = decodeBsUrl(rawUrl);
|
|
56
79
|
if (streamUrl.startsWith('/')) streamUrl = `${BASE_URL}${streamUrl}`;
|
|
57
80
|
const isWorker = streamUrl.includes('omena-puu') || streamUrl.includes('nocach') || streamUrl.includes('?p=');
|
|
58
|
-
const isHls = src.type === 'hls' || streamUrl.includes('m3u8') || isWorker;
|
|
81
|
+
const isHls = src.type === 'hls' || src.type === 'm3u8' || streamUrl.includes('m3u8') || isWorker;
|
|
59
82
|
if (isHls && !streamUrl.includes('m3u8')) streamUrl += streamUrl.includes('?') ? '&format=.m3u8' : '?format=.m3u8';
|
|
60
83
|
const srcHeaders = (src.headers && typeof src.headers === 'object') ? src.headers : {};
|
|
61
84
|
results.push({
|
|
@@ -80,7 +103,7 @@ export async function getStream(args) {
|
|
|
80
103
|
let targets = PROVIDERS;
|
|
81
104
|
if (server && server !== 'all') {
|
|
82
105
|
const cleanName = server.replace('1Embed - ', '').toLowerCase();
|
|
83
|
-
targets = PROVIDERS.filter(p => p.id === cleanName || p.name.toLowerCase().includes(cleanName));
|
|
106
|
+
targets = PROVIDERS.filter(p => p.id.toLowerCase() === cleanName || p.name.toLowerCase().includes(cleanName));
|
|
84
107
|
if (!targets.length) targets = PROVIDERS;
|
|
85
108
|
}
|
|
86
109
|
const settled = await Promise.allSettled(targets.map(p => fetchProviderStream(p, type, id, s, e, token)));
|
package/src/sources/anihq.js
CHANGED
|
@@ -2,15 +2,27 @@ import { getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
|
|
|
2
2
|
|
|
3
3
|
async function extractVoe(voeUrl) {
|
|
4
4
|
try {
|
|
5
|
-
|
|
5
|
+
let res = await fetch(voeUrl, {
|
|
6
6
|
headers: {
|
|
7
7
|
'User-Agent': USER_AGENT,
|
|
8
8
|
'Referer': 'https://anihq.cc/'
|
|
9
9
|
}
|
|
10
10
|
});
|
|
11
11
|
|
|
12
|
+
let html = await res.text();
|
|
13
|
+
|
|
14
|
+
const redirectMatch = html.match(/window\.location\.href\s*=\s*['"](https?:\/\/[^'"]+)['"]/);
|
|
15
|
+
if (redirectMatch) {
|
|
16
|
+
res = await fetch(redirectMatch[1], {
|
|
17
|
+
headers: {
|
|
18
|
+
'User-Agent': USER_AGENT,
|
|
19
|
+
'Referer': 'https://anihq.cc/'
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
html = await res.text();
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
const mirrorUrl = res.url;
|
|
13
|
-
const html = await res.text();
|
|
14
26
|
|
|
15
27
|
const scriptMatch = html.match(/<script type="application\/json">\["(.*?)"\]<\/script>/);
|
|
16
28
|
if (!scriptMatch) return null;
|
package/src/sources/anineko.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getTmdbInfo } from '../utils/helpers.js';
|
|
1
|
+
import { getTmdbInfo, USER_AGENT, fetchText } from '../utils/helpers.js';
|
|
2
2
|
|
|
3
3
|
const BASE_URL = "https://anineko.to";
|
|
4
4
|
|
|
@@ -10,7 +10,10 @@ function decodeEntities(str) {
|
|
|
10
10
|
|
|
11
11
|
async function search(query) {
|
|
12
12
|
try {
|
|
13
|
-
const html = await fetchText(`${BASE_URL}/browser?keyword=${encodeURIComponent(query)}`, {
|
|
13
|
+
const html = await fetchText(`${BASE_URL}/browser?keyword=${encodeURIComponent(query)}`, {
|
|
14
|
+
headers: { 'User-Agent': USER_AGENT },
|
|
15
|
+
signal: AbortSignal.timeout(6000)
|
|
16
|
+
});
|
|
14
17
|
const results = [];
|
|
15
18
|
let idx = 0;
|
|
16
19
|
while ((idx = html.indexOf('nv-anime-thumb', idx)) !== -1) {
|
|
@@ -27,40 +30,144 @@ async function search(query) {
|
|
|
27
30
|
}
|
|
28
31
|
idx += 14;
|
|
29
32
|
}
|
|
33
|
+
if (results.length === 0) {
|
|
34
|
+
const linkMatches = [...html.matchAll(/href=["']\/watch\/([^"']+)["']/gi)];
|
|
35
|
+
for (const match of linkMatches) {
|
|
36
|
+
if (!results.find(r => r.slug === match[1])) {
|
|
37
|
+
const linkIdx = html.indexOf(match[0]);
|
|
38
|
+
const titleStart = html.lastIndexOf('>', linkIdx);
|
|
39
|
+
const titleEnd = html.indexOf('<', linkIdx);
|
|
40
|
+
if (titleStart !== -1 && titleEnd !== -1 && titleEnd > linkIdx) {
|
|
41
|
+
const title = html.slice(titleStart + 1, Math.min(titleEnd, linkIdx + 200)).trim();
|
|
42
|
+
if (title.length > 2 && title.length < 100) {
|
|
43
|
+
results.push({ slug: match[1], text: title });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
30
49
|
return results;
|
|
31
50
|
} catch { return []; }
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
async function extractHls(embedUrl) {
|
|
35
54
|
try {
|
|
36
|
-
const html = await fetchText(embedUrl, {
|
|
37
|
-
|
|
55
|
+
const html = await fetchText(embedUrl, {
|
|
56
|
+
headers: {
|
|
57
|
+
'User-Agent': USER_AGENT,
|
|
58
|
+
Referer: `${BASE_URL}/`
|
|
59
|
+
},
|
|
60
|
+
signal: AbortSignal.timeout(6000)
|
|
61
|
+
});
|
|
62
|
+
const m = html.match(/const\s+src\s*=\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) ||
|
|
63
|
+
html.match(/file\s*:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) ||
|
|
64
|
+
html.match(/["'](https?:\/\/[^"']+\/master\.m3u8[^"']*)["']/i) ||
|
|
65
|
+
html.match(/["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) ||
|
|
66
|
+
html.match(/src:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i);
|
|
38
67
|
if (m) return decodeEntities(m[1]);
|
|
39
68
|
} catch { }
|
|
40
69
|
return null;
|
|
41
70
|
}
|
|
42
71
|
|
|
43
|
-
export async function getStream({ id, s, e, audio }) {
|
|
72
|
+
export async function getStream({ id, s, e, audio, tmdbApiKey }) {
|
|
44
73
|
try {
|
|
45
|
-
const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
|
|
74
|
+
const info = await getTmdbInfo(tmdbApiKey, id, s ? 'tv' : 'movie', s);
|
|
46
75
|
if (!info || !info.isAnime) return null;
|
|
47
76
|
let seriesSlug = null;
|
|
48
77
|
for (const title of info.titles) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
78
|
+
const potentialSlug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
79
|
+
if (!potentialSlug) continue;
|
|
80
|
+
try {
|
|
81
|
+
const checkHtml = await fetchText(`${BASE_URL}/watch/${potentialSlug}/ep-${e || 1}`, {
|
|
82
|
+
headers: { 'User-Agent': USER_AGENT, Referer: `${BASE_URL}/watch/${potentialSlug}` },
|
|
83
|
+
signal: AbortSignal.timeout(4000)
|
|
84
|
+
});
|
|
85
|
+
if (checkHtml && checkHtml.includes('nv-watch-page')) {
|
|
86
|
+
seriesSlug = potentialSlug;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
} catch {}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!seriesSlug) {
|
|
93
|
+
for (const title of info.titles) {
|
|
94
|
+
const results = await search(title);
|
|
95
|
+
const targetClean = cleanTitle(title);
|
|
96
|
+
|
|
97
|
+
const exactSlugMatch = results.find(r => {
|
|
98
|
+
const slugClean = cleanTitle(r.slug);
|
|
99
|
+
return slugClean === targetClean || r.slug === targetClean;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (exactSlugMatch) {
|
|
103
|
+
seriesSlug = exactSlugMatch.slug;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const sortedResults = [...results].sort((a, b) => a.slug.length - b.slug.length);
|
|
108
|
+
|
|
109
|
+
for (const r of sortedResults) {
|
|
110
|
+
const resultClean = cleanTitle(r.text);
|
|
111
|
+
const isExactMatch = resultClean === targetClean;
|
|
112
|
+
const isCloseMatch = r.text.toLowerCase().includes(title.toLowerCase());
|
|
113
|
+
if (isExactMatch || isCloseMatch) {
|
|
114
|
+
seriesSlug = r.slug;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (seriesSlug) break;
|
|
119
|
+
}
|
|
53
120
|
}
|
|
54
121
|
if (!seriesSlug && info.titles.length) {
|
|
55
122
|
const fSearch = await search(info.titles[0]);
|
|
56
|
-
|
|
123
|
+
const sortedResults = [...fSearch].sort((a, b) => a.slug.length - b.slug.length);
|
|
124
|
+
if (sortedResults.length) seriesSlug = sortedResults[0].slug;
|
|
57
125
|
}
|
|
58
126
|
if (!seriesSlug) return null;
|
|
59
|
-
const watchHtml = await fetchText(`${BASE_URL}/watch/${seriesSlug}/ep-${e || 1}`, {
|
|
127
|
+
const watchHtml = await fetchText(`${BASE_URL}/watch/${seriesSlug}/ep-${e || 1}`, {
|
|
128
|
+
headers: {
|
|
129
|
+
'User-Agent': USER_AGENT,
|
|
130
|
+
Referer: `${BASE_URL}/watch/${seriesSlug}`
|
|
131
|
+
},
|
|
132
|
+
signal: AbortSignal.timeout(6000)
|
|
133
|
+
});
|
|
60
134
|
const byAudio = { sub: [], dub: [] };
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
135
|
+
|
|
136
|
+
const iframeMatch = watchHtml.match(/<iframe[^>]*src=["']([^"']+)["'][^>]*>/i);
|
|
137
|
+
if (iframeMatch) {
|
|
138
|
+
const iframeSrc = decodeEntities(iframeMatch[1]);
|
|
139
|
+
const hls = await extractHls(iframeSrc);
|
|
140
|
+
if (hls) {
|
|
141
|
+
const audioType = audio === 'dub' ? 'dub' : 'sub';
|
|
142
|
+
byAudio[audioType].push(iframeSrc);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (byAudio.sub.length === 0 && byAudio.dub.length === 0) {
|
|
147
|
+
const tabMatches = [...watchHtml.matchAll(/<button[^>]*class=["'][^"']*nv-server-tab[^"']*["'][^>]*data-id=["']([^"']+)["'][^>]*>/gi)];
|
|
148
|
+
const tabMap = {};
|
|
149
|
+
for (const match of tabMatches) {
|
|
150
|
+
const tabId = match[1].toLowerCase();
|
|
151
|
+
const tabClassMatch = match[0].match(/class=["'][^"']*(tab_\d+)[^"']/i);
|
|
152
|
+
if (tabClassMatch) {
|
|
153
|
+
const tabClass = tabClassMatch[1];
|
|
154
|
+
tabMap[tabClass] = tabId.includes('dub') ? 'dub' : 'sub';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const btnRegex = /<button\s+[^>]*class=["'][^"']*server-video[^"']*["'][^>]*>/gi;
|
|
159
|
+
const btnMatches = [...watchHtml.matchAll(btnRegex)];
|
|
160
|
+
for (const match of btnMatches) {
|
|
161
|
+
const btnHtml = match[0];
|
|
162
|
+
const videoMatch = btnHtml.match(/data-video=["']([^"']+)["']/i);
|
|
163
|
+
const tabMatch = btnHtml.match(/data-tab=["']([^"']+)["']/i);
|
|
164
|
+
if (videoMatch && tabMatch) {
|
|
165
|
+
const videoUrl = decodeEntities(videoMatch[1]);
|
|
166
|
+
const tabClass = tabMatch[1];
|
|
167
|
+
const audioType = tabMap[tabClass] || 'sub';
|
|
168
|
+
byAudio[audioType].push(videoUrl);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
64
171
|
}
|
|
65
172
|
const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]);
|
|
66
173
|
const allUrls = [];
|