@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
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# Vyla SDK
|
|
2
|
+
|
|
3
|
+
VylaSDK is a JavaScript/TypeScript SDK for accessing Vyla's stream providers, subtitles, and download sources directly from Node.js. Instead of using the Vyla REST API, developers can run the scraping layer directly in their own environment with full control over requests, execution, and provider management.
|
|
4
|
+
|
|
5
|
+
### Installing
|
|
3
6
|
|
|
4
|
-
## Installing
|
|
5
7
|
To start, install VylaSDK through npm or pnpm:
|
|
8
|
+
|
|
6
9
|
```bash
|
|
7
10
|
# npm
|
|
8
11
|
npm install @vyla-entertainment/sdk
|
|
@@ -11,23 +14,22 @@ npm install @vyla-entertainment/sdk
|
|
|
11
14
|
pnpm install @vyla-entertainment/sdk
|
|
12
15
|
```
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
Please note we do not provide proxying, you have to do that on your end.
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
**View our full documentation at:** https://docs.vyla.cc/sdk
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
### [DMCA](https://docs.vyla.cc/misc/dmca)
|
|
19
22
|
|
|
20
23
|
If your source is listed here, I sincerely apologize. I understand that its inclusion may be costing you a lot of money, and that was never my intention. Please contact me as soon as possible, and I'll remove it ASAP.
|
|
21
24
|
|
|
22
25
|
This is a small hobby project that I built for learning and personal interest, not for profit. I genuinely want to respect the wishes of source owners and will address any removal requests as quickly as I can.
|
|
23
26
|
|
|
24
|
-
This project is developed and maintained by [Vyla Entertainment](https://github.com/vyla-entertainment)
|
|
27
|
+
This project is developed and maintained by [Vyla Entertainment](https://github.com/vyla-entertainment).
|
|
25
28
|
|
|
26
29
|
Original work and core authorship:
|
|
27
30
|
|
|
28
31
|
[@endoverdosing](https://github.com/EndOverdosing)
|
|
29
|
-
|
|
30
32
|
[@GavinGoGaming](https://github.com/GavinGoGaming)
|
|
31
33
|
|
|
32
|
-
Full credits and acknowledgements
|
|
33
|
-
https://docs.vyla.cc/misc/credit
|
|
34
|
+
**Full credits and acknowledgements:**
|
|
35
|
+
https://docs.vyla.cc/misc/credit
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Currently active sources reported on
|
|
1
|
+
// Currently active sources reported on 9:22:41 PM PST 7/30/2026
|
|
2
2
|
|
|
3
3
|
// None Anime Tested: 936075
|
|
4
4
|
// Anime Tested: 37854
|
|
@@ -60,6 +60,30 @@ export const SOURCES = [
|
|
|
60
60
|
skipProxy: true
|
|
61
61
|
},
|
|
62
62
|
|
|
63
|
+
{
|
|
64
|
+
key: 'anikoto-sub',
|
|
65
|
+
label: 'AniKoto (Sub)',
|
|
66
|
+
sourceFile: 'anikoto',
|
|
67
|
+
proxyParam: 'aksub',
|
|
68
|
+
timeout: 25000,
|
|
69
|
+
jitter: 500,
|
|
70
|
+
retries: 2,
|
|
71
|
+
multiUrl: true,
|
|
72
|
+
skipProxy: true
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
key: 'anikoto-dub',
|
|
77
|
+
label: 'AniKoto (Dub)',
|
|
78
|
+
sourceFile: 'anikoto',
|
|
79
|
+
proxyParam: 'akdub',
|
|
80
|
+
timeout: 25000,
|
|
81
|
+
jitter: 500,
|
|
82
|
+
retries: 2,
|
|
83
|
+
multiUrl: true,
|
|
84
|
+
skipProxy: true
|
|
85
|
+
},
|
|
86
|
+
|
|
63
87
|
{
|
|
64
88
|
key: 'anineko-sub',
|
|
65
89
|
sourceFile: 'anineko',
|
|
@@ -114,6 +138,18 @@ export const SOURCES = [
|
|
|
114
138
|
},
|
|
115
139
|
},
|
|
116
140
|
|
|
141
|
+
{
|
|
142
|
+
key: 'anizone',
|
|
143
|
+
label: 'AniZone',
|
|
144
|
+
sourceFile: 'anizone',
|
|
145
|
+
proxyParam: 'az',
|
|
146
|
+
timeout: 25000,
|
|
147
|
+
jitter: 500,
|
|
148
|
+
retries: 2,
|
|
149
|
+
multiUrl: true,
|
|
150
|
+
skipProxy: true
|
|
151
|
+
},
|
|
152
|
+
|
|
117
153
|
{
|
|
118
154
|
key: 'cinejoy',
|
|
119
155
|
label: 'Cinejoy',
|
|
@@ -126,6 +162,25 @@ export const SOURCES = [
|
|
|
126
162
|
skipProxy: true,
|
|
127
163
|
},
|
|
128
164
|
|
|
165
|
+
{
|
|
166
|
+
key: 'cinesu',
|
|
167
|
+
label: 'CineSu',
|
|
168
|
+
sourceFile: 'cinesu',
|
|
169
|
+
proxyParam: 'cs',
|
|
170
|
+
timeout: 20000,
|
|
171
|
+
jitter: 500,
|
|
172
|
+
retries: 2,
|
|
173
|
+
multiUrl: false,
|
|
174
|
+
skipProxy: true,
|
|
175
|
+
cdnHeaders: [{
|
|
176
|
+
pattern: /glendale-plumbing\.com/i,
|
|
177
|
+
headers: {
|
|
178
|
+
Referer: 'https://cine.su/',
|
|
179
|
+
Origin: 'https://cine.su',
|
|
180
|
+
},
|
|
181
|
+
}],
|
|
182
|
+
},
|
|
183
|
+
|
|
129
184
|
{
|
|
130
185
|
key: 'flaxmovies',
|
|
131
186
|
label: 'FlaxMovies',
|
|
@@ -135,7 +190,7 @@ export const SOURCES = [
|
|
|
135
190
|
jitter: 500,
|
|
136
191
|
retries: 2,
|
|
137
192
|
multiUrl: true,
|
|
138
|
-
|
|
193
|
+
skipProxy: true,
|
|
139
194
|
cdnHeaders: [{
|
|
140
195
|
pattern: /flix2watch\.pro/i,
|
|
141
196
|
headers: {
|
|
@@ -161,18 +216,6 @@ export const SOURCES = [
|
|
|
161
216
|
},
|
|
162
217
|
},
|
|
163
218
|
|
|
164
|
-
{
|
|
165
|
-
key: 'goated',
|
|
166
|
-
label: 'Goated',
|
|
167
|
-
sourceFile: 'goated',
|
|
168
|
-
proxyParam: 'gt',
|
|
169
|
-
timeout: 25000,
|
|
170
|
-
jitter: 500,
|
|
171
|
-
retries: 2,
|
|
172
|
-
multiUrl: true,
|
|
173
|
-
skipProxy: true,
|
|
174
|
-
},
|
|
175
|
-
|
|
176
219
|
{
|
|
177
220
|
key: 'fsonic',
|
|
178
221
|
label: 'Fsonic',
|
|
@@ -206,6 +249,18 @@ export const SOURCES = [
|
|
|
206
249
|
retries: 2,
|
|
207
250
|
},
|
|
208
251
|
|
|
252
|
+
{
|
|
253
|
+
key: 'kickassanime',
|
|
254
|
+
label: 'KickAssAnime',
|
|
255
|
+
sourceFile: 'kickassanime',
|
|
256
|
+
proxyParam: 'kaa',
|
|
257
|
+
timeout: 25000,
|
|
258
|
+
jitter: 500,
|
|
259
|
+
retries: 2,
|
|
260
|
+
multiUrl: true,
|
|
261
|
+
skipProxy: true
|
|
262
|
+
},
|
|
263
|
+
|
|
209
264
|
{
|
|
210
265
|
key: 'kisskh',
|
|
211
266
|
label: 'KissKH',
|
|
@@ -216,6 +271,17 @@ export const SOURCES = [
|
|
|
216
271
|
retries: 1,
|
|
217
272
|
},
|
|
218
273
|
|
|
274
|
+
{
|
|
275
|
+
key: 'lmscript',
|
|
276
|
+
label: 'LMScript',
|
|
277
|
+
sourceFile: 'lmscript',
|
|
278
|
+
proxyParam: 'lmsc',
|
|
279
|
+
timeout: 20000,
|
|
280
|
+
jitter: 500,
|
|
281
|
+
retries: 2,
|
|
282
|
+
multiUrl: true,
|
|
283
|
+
},
|
|
284
|
+
|
|
219
285
|
{
|
|
220
286
|
key: 'lookmovie',
|
|
221
287
|
label: 'LookMovie',
|
|
@@ -249,39 +315,6 @@ export const SOURCES = [
|
|
|
249
315
|
jitter: 500,
|
|
250
316
|
retries: 2,
|
|
251
317
|
multiUrl: true,
|
|
252
|
-
skipProxy: true
|
|
253
|
-
},
|
|
254
|
-
|
|
255
|
-
{
|
|
256
|
-
key: 'nebula',
|
|
257
|
-
label: 'Nebula',
|
|
258
|
-
sourceFile: 'nebula',
|
|
259
|
-
proxyParam: 'nb',
|
|
260
|
-
timeout: 25000,
|
|
261
|
-
jitter: 500,
|
|
262
|
-
retries: 1,
|
|
263
|
-
multiUrl: true,
|
|
264
|
-
skipProxy: true,
|
|
265
|
-
},
|
|
266
|
-
|
|
267
|
-
{
|
|
268
|
-
key: 'nekowatch-sub',
|
|
269
|
-
sourceFile: 'nekowatch',
|
|
270
|
-
label: 'NekoWatch (Sub)',
|
|
271
|
-
proxyParam: 'nwsub',
|
|
272
|
-
timeout: 30000,
|
|
273
|
-
jitter: 500,
|
|
274
|
-
retries: 2
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
{
|
|
278
|
-
key: 'nekowatch-dub',
|
|
279
|
-
sourceFile: 'nekowatch',
|
|
280
|
-
label: 'NekoWatch (Dub)',
|
|
281
|
-
proxyParam: 'nwdub',
|
|
282
|
-
timeout: 30000,
|
|
283
|
-
jitter: 500,
|
|
284
|
-
retries: 2
|
|
285
318
|
},
|
|
286
319
|
|
|
287
320
|
{
|
|
@@ -332,6 +365,31 @@ export const SOURCES = [
|
|
|
332
365
|
timeout: 20000,
|
|
333
366
|
jitter: 500,
|
|
334
367
|
retries: 2,
|
|
368
|
+
skipProxy: true
|
|
369
|
+
},
|
|
370
|
+
|
|
371
|
+
{
|
|
372
|
+
key: 'reanime-sub',
|
|
373
|
+
label: 'Reanime (Sub)',
|
|
374
|
+
sourceFile: 'reanime',
|
|
375
|
+
proxyParam: 'resub',
|
|
376
|
+
timeout: 25000,
|
|
377
|
+
jitter: 500,
|
|
378
|
+
retries: 2,
|
|
379
|
+
multiUrl: true,
|
|
380
|
+
skipProxy: true
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
{
|
|
384
|
+
key: 'reanime-dub',
|
|
385
|
+
label: 'Reanime (Dub)',
|
|
386
|
+
sourceFile: 'reanime',
|
|
387
|
+
proxyParam: 'redub',
|
|
388
|
+
timeout: 25000,
|
|
389
|
+
jitter: 500,
|
|
390
|
+
retries: 2,
|
|
391
|
+
multiUrl: true,
|
|
392
|
+
skipProxy: true
|
|
335
393
|
},
|
|
336
394
|
|
|
337
395
|
{
|
|
@@ -485,6 +543,17 @@ export const SOURCES = [
|
|
|
485
543
|
multiUrl: true
|
|
486
544
|
},
|
|
487
545
|
|
|
546
|
+
{
|
|
547
|
+
key: 'vidvault',
|
|
548
|
+
label: 'VidVault',
|
|
549
|
+
sourceFile: 'vidvault',
|
|
550
|
+
proxyParam: 'vv',
|
|
551
|
+
timeout: 25000,
|
|
552
|
+
jitter: 500,
|
|
553
|
+
retries: 2,
|
|
554
|
+
multiUrl: true,
|
|
555
|
+
},
|
|
556
|
+
|
|
488
557
|
{
|
|
489
558
|
key: 'vidzee',
|
|
490
559
|
label: 'VidZee',
|
|
@@ -494,7 +563,6 @@ export const SOURCES = [
|
|
|
494
563
|
sourcesTimeout: 10000,
|
|
495
564
|
jitter: 400,
|
|
496
565
|
retries: 3,
|
|
497
|
-
disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's
|
|
498
566
|
verifyHeaders: {
|
|
499
567
|
Accept: '/',
|
|
500
568
|
'Accept-Language': 'en-US,en;q=0.9',
|
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;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { fetchJson, fetchText, getTmdbInfo, tmdbToAnilist, USER_AGENT } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const ANIKOTO = "https://anikototv.to";
|
|
4
|
+
const MEGAPLAY = "https://megaplay.buzz";
|
|
5
|
+
const VIDWISH = "https://vidwish.live";
|
|
6
|
+
const SPOOF_REF = "https://hianimes.re/";
|
|
7
|
+
const HEADERS = { "User-Agent": USER_AGENT, Accept: "text/html,*/*" };
|
|
8
|
+
|
|
9
|
+
function extractEpisodes(html) {
|
|
10
|
+
const episodes = [];
|
|
11
|
+
const re = /<a\s[^>]*data-id="[^"]*"[^>]*>/g;
|
|
12
|
+
let m;
|
|
13
|
+
while ((m = re.exec(html)) !== null) {
|
|
14
|
+
const tag = m[0];
|
|
15
|
+
const getAttr = (a) => {
|
|
16
|
+
const x = tag.match(new RegExp(`data-${a}="([^"]*)"`));
|
|
17
|
+
return x ? x[1] : "";
|
|
18
|
+
};
|
|
19
|
+
const id = getAttr("id"), num = getAttr("num");
|
|
20
|
+
if (!id || !num) continue;
|
|
21
|
+
episodes.push({ id, num: parseInt(num), slug: getAttr("slug"), hasSub: getAttr("sub") === "1", hasDub: getAttr("dub") === "1", ids: getAttr("ids") });
|
|
22
|
+
}
|
|
23
|
+
return episodes;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function extractSearchCandidates(html) {
|
|
27
|
+
const results = [];
|
|
28
|
+
const re = /<a class="item" href="https:\/\/anikototv\.to\/watch\/([^"]+)"([\s\S]*?)<\/a>/g;
|
|
29
|
+
let m;
|
|
30
|
+
while ((m = re.exec(html)) !== null) {
|
|
31
|
+
const block = m[2];
|
|
32
|
+
const enM = block.match(/class="name d-title"[^>]*>([^<]*)</);
|
|
33
|
+
const jpM = block.match(/data-jp="([^"]*)"/);
|
|
34
|
+
results.push({ slug: m[1], titleEn: enM ? enM[1].trim() : "", titleJp: jpM ? jpM[1].trim() : "" });
|
|
35
|
+
}
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function searchAnikoto(keyword) {
|
|
40
|
+
const data = await fetchJson(`${ANIKOTO}/ajax/anime/search?keyword=${encodeURIComponent(keyword)}`, {
|
|
41
|
+
headers: { ...HEADERS, "X-Requested-With": "XMLHttpRequest", Referer: `${ANIKOTO}/` }
|
|
42
|
+
}).catch(() => null);
|
|
43
|
+
|
|
44
|
+
const results = extractSearchCandidates(data?.result?.html ?? "");
|
|
45
|
+
const html = await fetchText(`${ANIKOTO}/filter?keyword=${encodeURIComponent(keyword)}`, { headers: { ...HEADERS, Referer: `${ANIKOTO}/` } }).catch(() => "");
|
|
46
|
+
|
|
47
|
+
for (const m of html.matchAll(/<a class="name d-title" href="https:\/\/anikototv\.to\/watch\/([^"/]+)(?:\/ep-\d+)?" data-jp="([^"]*)">([\s\S]*?)<\/a>/g)) {
|
|
48
|
+
results.push({
|
|
49
|
+
slug: m[1],
|
|
50
|
+
titleEn: m[3].replace(/<[^>]*>/g, "").trim(),
|
|
51
|
+
titleJp: m[2].trim()
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const seen = new Set();
|
|
56
|
+
return results.filter((r) => {
|
|
57
|
+
if (seen.has(r.slug)) return false;
|
|
58
|
+
seen.add(r.slug);
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function findAnikotoShow(title) {
|
|
64
|
+
const candidates = await searchAnikoto(title);
|
|
65
|
+
if (!candidates.length) return null;
|
|
66
|
+
const chosenSlug = candidates[0].slug;
|
|
67
|
+
|
|
68
|
+
const pageHtml = await fetchText(`${ANIKOTO}/watch/${chosenSlug}`, { headers: { ...HEADERS, Referer: `${ANIKOTO}/` } });
|
|
69
|
+
const idM = pageHtml.match(/data-id="(\d+)"/);
|
|
70
|
+
if (!idM) return null;
|
|
71
|
+
return { slug: chosenSlug, showId: idM[1] };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function extractVidWish(realId, audio) {
|
|
75
|
+
try {
|
|
76
|
+
const page = await fetchText(`${VIDWISH}/stream/s-2/${realId}/${audio}`, { headers: { ...HEADERS, Referer: SPOOF_REF } });
|
|
77
|
+
const m = page.match(/data-id="([^"]*)"/);
|
|
78
|
+
if (!m?.[1]) return null;
|
|
79
|
+
const fileId = m[1];
|
|
80
|
+
const data = await fetchJson(`${VIDWISH}/stream/getSources?id=${fileId}&id=${fileId}`, { headers: { ...HEADERS, Referer: `${VIDWISH}/`, "X-Requested-With": "XMLHttpRequest" } });
|
|
81
|
+
return { fileId, data };
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function getStream({ id, s, e, audio, tmdbApiKey }) {
|
|
88
|
+
try {
|
|
89
|
+
const isTv = !!s;
|
|
90
|
+
const mediaType = isTv ? 'tv' : 'movie';
|
|
91
|
+
const epNum = e ? Number(e) : 1;
|
|
92
|
+
const seasonNum = s ? Number(s) : 1;
|
|
93
|
+
|
|
94
|
+
let anilistId = null;
|
|
95
|
+
let titles = [];
|
|
96
|
+
const tmdbInfo = await getTmdbInfo(tmdbApiKey, id, mediaType, seasonNum);
|
|
97
|
+
|
|
98
|
+
if (tmdbInfo) {
|
|
99
|
+
titles = tmdbInfo.titles || [];
|
|
100
|
+
anilistId = await tmdbToAnilist(id, mediaType, seasonNum, titles, tmdbInfo.year);
|
|
101
|
+
}
|
|
102
|
+
if (!anilistId) {
|
|
103
|
+
anilistId = Number(id);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const audioPref = audio === 'dub' ? 'dub' : 'sub';
|
|
107
|
+
let embedUrl = `${MEGAPLAY}/stream/ani/${anilistId}/${epNum}/${audioPref}`;
|
|
108
|
+
let megaHtml = await fetchText(embedUrl, { headers: { ...HEADERS, Referer: SPOOF_REF } }).catch(() => '');
|
|
109
|
+
|
|
110
|
+
const frameSrc = megaHtml.match(/<iframe\b[^>]*src="([^"]+)"/i)?.[1];
|
|
111
|
+
if (!megaHtml.match(/data-id="([^"]*)"/) && frameSrc) {
|
|
112
|
+
embedUrl = frameSrc.startsWith("http") ? frameSrc : `${MEGAPLAY}${frameSrc}`;
|
|
113
|
+
megaHtml = await fetchText(embedUrl, { headers: { ...HEADERS, Referer: SPOOF_REF } }).catch(() => '');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const attrMatch = (name) => {
|
|
117
|
+
const m = megaHtml.match(new RegExp(`data-${name}="([^"]*)"`));
|
|
118
|
+
return m ? m[1] : null;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const fileId = attrMatch("id");
|
|
122
|
+
const realId = attrMatch("realid");
|
|
123
|
+
|
|
124
|
+
const urls = [];
|
|
125
|
+
|
|
126
|
+
if (fileId) {
|
|
127
|
+
const megaSources = await fetchJson(`${MEGAPLAY}/stream/getSources?id=${fileId}&id=${fileId}`, {
|
|
128
|
+
headers: { ...HEADERS, Referer: `${MEGAPLAY}/`, "X-Requested-With": "XMLHttpRequest" }
|
|
129
|
+
}).catch(() => null);
|
|
130
|
+
|
|
131
|
+
if (megaSources?.sources?.file) {
|
|
132
|
+
urls.push({
|
|
133
|
+
url: megaSources.sources.file,
|
|
134
|
+
server: 'AniKoto (Megaplay)',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (realId) {
|
|
140
|
+
const vidwish = await extractVidWish(realId, audioPref);
|
|
141
|
+
if (vidwish?.data?.sources?.file) {
|
|
142
|
+
urls.push({
|
|
143
|
+
url: vidwish.data.sources.file,
|
|
144
|
+
server: 'AniKoto (VidWish)',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!urls.length && titles.length) {
|
|
150
|
+
const show = await findAnikotoShow(titles[0]);
|
|
151
|
+
if (show) {
|
|
152
|
+
const listData = await fetchJson(`${ANIKOTO}/ajax/episode/list/${show.showId}`, {
|
|
153
|
+
headers: { ...HEADERS, "X-Requested-With": "XMLHttpRequest", Referer: `${ANIKOTO}/watch/${show.slug}` }
|
|
154
|
+
}).catch(() => null);
|
|
155
|
+
|
|
156
|
+
const episodes = extractEpisodes(listData?.result ?? "");
|
|
157
|
+
const ep = episodes.find((item) => item.num === epNum);
|
|
158
|
+
if (ep && ep.ids) {
|
|
159
|
+
const serverData = await fetchJson(`${ANIKOTO}/ajax/server/list?servers=${encodeURIComponent(ep.ids)}`, {
|
|
160
|
+
headers: { ...HEADERS, "X-Requested-With": "XMLHttpRequest", Referer: `${ANIKOTO}/` }
|
|
161
|
+
}).catch(() => null);
|
|
162
|
+
|
|
163
|
+
const items = [];
|
|
164
|
+
const typeRe = /<div class="type" data-type="(sub|dub)">([\s\S]*?)<\/ul>\s*<\/div>/g;
|
|
165
|
+
let typeM;
|
|
166
|
+
while ((typeM = typeRe.exec(serverData?.result ?? "")) !== null) {
|
|
167
|
+
if (typeM[1] !== audioPref) continue;
|
|
168
|
+
for (const li of typeM[2].matchAll(/<li\s+([^>]*data-link-id[^>]*)>([\s\S]*?)<\/li>/g)) {
|
|
169
|
+
const linkId = li[1].match(/data-link-id="([^"]+)"/)?.[1];
|
|
170
|
+
const name = li[2].replace(/<[^>]+>/g, "").trim();
|
|
171
|
+
if (linkId) items.push({ linkId, name });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const item of items) {
|
|
176
|
+
const resolved = await fetchJson(`${ANIKOTO}/ajax/server?get=${encodeURIComponent(item.linkId)}`, {
|
|
177
|
+
headers: { ...HEADERS, "X-Requested-With": "XMLHttpRequest", Referer: `${ANIKOTO}/` }
|
|
178
|
+
}).catch(() => null);
|
|
179
|
+
if (resolved?.result?.url) {
|
|
180
|
+
urls.push({
|
|
181
|
+
url: resolved.result.url,
|
|
182
|
+
server: `AniKoto (${item.name})`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return urls.length ? { allUrls: urls } : null;
|
|
191
|
+
} catch (err) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function getSources() {
|
|
197
|
+
return ['AniKoto'];
|
|
198
|
+
}
|