@vyla-entertainment/sdk 1.8.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 -8
- package/package.json +1 -1
- package/src/config.js +91 -2
- package/src/sources/anikoto.js +198 -0
- package/src/sources/anizone.js +109 -0
- package/src/sources/cinesu.js +94 -0
- package/src/sources/kickassanime.js +107 -0
- package/src/sources/meowtv.js +1 -8
- package/src/sources/reanime.js +324 -0
- package/src/sources/vidzee.js +212 -89
- package/src/subtitles.js +0 -18
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,21 +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
|
|
20
|
+
|
|
21
|
+
### [DMCA](https://docs.vyla.cc/misc/dmca)
|
|
17
22
|
|
|
18
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.
|
|
19
24
|
|
|
20
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.
|
|
21
26
|
|
|
22
|
-
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).
|
|
23
28
|
|
|
24
29
|
Original work and core authorship:
|
|
25
30
|
|
|
26
31
|
[@endoverdosing](https://github.com/EndOverdosing)
|
|
27
|
-
|
|
28
32
|
[@GavinGoGaming](https://github.com/GavinGoGaming)
|
|
29
33
|
|
|
30
|
-
Full credits and acknowledgements
|
|
34
|
+
**Full credits and acknowledgements:**
|
|
31
35
|
https://docs.vyla.cc/misc/credit
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -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',
|
|
@@ -194,6 +249,18 @@ export const SOURCES = [
|
|
|
194
249
|
retries: 2,
|
|
195
250
|
},
|
|
196
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
|
+
|
|
197
264
|
{
|
|
198
265
|
key: 'kisskh',
|
|
199
266
|
label: 'KissKH',
|
|
@@ -248,7 +315,6 @@ export const SOURCES = [
|
|
|
248
315
|
jitter: 500,
|
|
249
316
|
retries: 2,
|
|
250
317
|
multiUrl: true,
|
|
251
|
-
skipProxy: true
|
|
252
318
|
},
|
|
253
319
|
|
|
254
320
|
{
|
|
@@ -302,6 +368,30 @@ export const SOURCES = [
|
|
|
302
368
|
skipProxy: true
|
|
303
369
|
},
|
|
304
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
|
|
393
|
+
},
|
|
394
|
+
|
|
305
395
|
{
|
|
306
396
|
key: 'vidapi',
|
|
307
397
|
label: 'VidAPI',
|
|
@@ -479,7 +569,6 @@ export const SOURCES = [
|
|
|
479
569
|
Referer: 'https://player.vidzee.wtf',
|
|
480
570
|
Origin: 'https://player.vidzee.wtf',
|
|
481
571
|
},
|
|
482
|
-
disabled: true // Disabled temporarily due to decryption issues
|
|
483
572
|
},
|
|
484
573
|
|
|
485
574
|
{
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { fetchText, getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE = "https://anizone.to";
|
|
4
|
+
const HEADERS = { "User-Agent": USER_AGENT, Referer: `${BASE}/` };
|
|
5
|
+
|
|
6
|
+
function decodeEntities(encodedString) {
|
|
7
|
+
var translate_re = /&(nbsp|amp|quot|lt|gt);/g;
|
|
8
|
+
var translate = { "nbsp": " ", "amp": "&", "quot": "\"", "lt": "<", "gt": ">" };
|
|
9
|
+
return (encodedString || "").replace(translate_re, function (match, entity) {
|
|
10
|
+
return translate[entity];
|
|
11
|
+
}).replace(/&#(\d+);/g, function (match, dec) {
|
|
12
|
+
return String.fromCharCode(dec);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function processJsonArg(raw) {
|
|
17
|
+
const PH = "\x01U\x01";
|
|
18
|
+
let s = raw.replace(/\\\\u([0-9a-fA-F]{4})/g, `${PH}$1`);
|
|
19
|
+
s = s.replace(/\\u([0-9a-fA-F]{4})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
|
|
20
|
+
s = s.replace(/\x01U\x01([0-9a-fA-F]{4})/g, "\\u$1");
|
|
21
|
+
try { return JSON.parse(s); } catch { return {}; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function pickTitle(titles) {
|
|
25
|
+
return titles["1"] || titles["5"] || titles["8"] || Object.values(titles)[0] || "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractSlug(ctx) {
|
|
29
|
+
const m = ctx.match(/href="(?:https:\/\/anizone\.to)?\/anime\/([a-z0-9-]+)"/);
|
|
30
|
+
return m ? m[1] : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function extractJsonArg(xdata, key) {
|
|
34
|
+
const re = new RegExp(`${key}:\\s*JSON\\.parse\\('((?:[^'\\\\]|\\\\.)*)'\\)`);
|
|
35
|
+
const m = xdata.match(re);
|
|
36
|
+
return m ? m[1] : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function search(query) {
|
|
40
|
+
const html = await fetchText(`${BASE}/anime?search=${encodeURIComponent(query)}`, { headers: HEADERS }).catch(() => "");
|
|
41
|
+
const results = [];
|
|
42
|
+
const xdataRe = /x-data="(\{[^"]*anmTitles[^"]*\})"/g;
|
|
43
|
+
let m;
|
|
44
|
+
while ((m = xdataRe.exec(html)) !== null) {
|
|
45
|
+
const ctxStart = Math.max(0, m.index - 300);
|
|
46
|
+
const ctxEnd = Math.min(html.length, m.index + m[0].length + 800);
|
|
47
|
+
const ctx = html.slice(ctxStart, ctxEnd);
|
|
48
|
+
const slug = extractSlug(ctx);
|
|
49
|
+
if (!slug) continue;
|
|
50
|
+
const xdata = decodeEntities(m[1]);
|
|
51
|
+
const raw = extractJsonArg(xdata, "anmTitles");
|
|
52
|
+
if (!raw) continue;
|
|
53
|
+
const titles = processJsonArg(raw);
|
|
54
|
+
const title = pickTitle(titles);
|
|
55
|
+
if (title) results.push({ slug, text: title });
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function scrapeWatch(slug, episodeNum) {
|
|
61
|
+
const html = await fetchText(`${BASE}/anime/${slug}/${episodeNum}`, { headers: HEADERS }).catch(() => "");
|
|
62
|
+
if (!html) return null;
|
|
63
|
+
|
|
64
|
+
const hlsMatch = html.match(/<media-player[^>]+src="([^"]+\.m3u8[^"]*)"/i);
|
|
65
|
+
const hls = hlsMatch ? decodeEntities(hlsMatch[1]) : null;
|
|
66
|
+
|
|
67
|
+
return hls;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function getStream({ id, s, e, tmdbApiKey }) {
|
|
71
|
+
try {
|
|
72
|
+
const isTv = !!s;
|
|
73
|
+
const mediaType = isTv ? 'tv' : 'movie';
|
|
74
|
+
const epNum = e ? Number(e) : 1;
|
|
75
|
+
const seasonNum = s ? Number(s) : 1;
|
|
76
|
+
|
|
77
|
+
const tmdbInfo = await getTmdbInfo(tmdbApiKey, id, mediaType, seasonNum);
|
|
78
|
+
const titles = tmdbInfo?.titles || [];
|
|
79
|
+
|
|
80
|
+
let slug = null;
|
|
81
|
+
for (const title of titles) {
|
|
82
|
+
const results = await search(title);
|
|
83
|
+
if (results.length) {
|
|
84
|
+
slug = results[0].slug;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!slug) return null;
|
|
90
|
+
|
|
91
|
+
const hls = await scrapeWatch(slug, epNum);
|
|
92
|
+
if (!hls) return null;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
allUrls: [
|
|
96
|
+
{
|
|
97
|
+
url: hls,
|
|
98
|
+
server: 'AniZone',
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
};
|
|
102
|
+
} catch (err) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function getSources() {
|
|
108
|
+
return ['AniZone'];
|
|
109
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { USER_AGENT } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const lS = [17, 91, 203, 44, 8, 177, 62, 239, 119, 3, 154, 81, 28, 210, 101, 7];
|
|
4
|
+
|
|
5
|
+
function lA(e) {
|
|
6
|
+
let t = e >>> 0;
|
|
7
|
+
t ^= t >>> 16;
|
|
8
|
+
t = Math.imul(t, 0x7feb352d) >>> 0;
|
|
9
|
+
t ^= t >>> 15;
|
|
10
|
+
t = Math.imul(t, 0x846ca68b) >>> 0;
|
|
11
|
+
return (t ^ (t >>> 16)) >>> 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function lL(keyStr, targetLen) {
|
|
15
|
+
const encoder = new TextEncoder();
|
|
16
|
+
const i = encoder.encode(keyStr || "dev");
|
|
17
|
+
const rLen = Math.max(32, Math.min(128, targetLen + 17));
|
|
18
|
+
const r = new Uint8Array(rLen);
|
|
19
|
+
let s = 0x811c9dc5;
|
|
20
|
+
for (let e = 0; e < rLen; e += 1) {
|
|
21
|
+
const a = i[e % i.length] ?? e;
|
|
22
|
+
s ^= a;
|
|
23
|
+
s = lA((s + lS[e % lS.length] + ((0x9e3779b1 * e) >>> 0)) >>> 0);
|
|
24
|
+
r[e] = s & 255;
|
|
25
|
+
}
|
|
26
|
+
return r;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function base64UrlEncode(bytes) {
|
|
30
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
31
|
+
let i = "";
|
|
32
|
+
for (let r = 0; r < bytes.length; r += 3) {
|
|
33
|
+
const s = bytes[r];
|
|
34
|
+
const a = bytes[r + 1];
|
|
35
|
+
const n = bytes[r + 2];
|
|
36
|
+
i += chars[s >>> 2];
|
|
37
|
+
i += chars[((3 & s) << 4) | ((a ?? 0) >>> 4)];
|
|
38
|
+
if (a === undefined) break;
|
|
39
|
+
i += chars[((15 & a) << 2) | ((n ?? 0) >>> 6)];
|
|
40
|
+
if (n === undefined) break;
|
|
41
|
+
i += chars[63 & n];
|
|
42
|
+
}
|
|
43
|
+
return i;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function generateCineSuUrl(type, tmdbId, season = 1, episode = 1) {
|
|
47
|
+
const kind = type === "show" || type === "tv" ? "show" : "movie";
|
|
48
|
+
const s = kind === "show" ? Math.max(1, Math.floor(season)) : 0;
|
|
49
|
+
const ep = kind === "show" ? Math.max(1, Math.floor(episode)) : 0;
|
|
50
|
+
const char = kind[0];
|
|
51
|
+
|
|
52
|
+
const payloadStr = `4860ac8bfddb:${char}:${Math.floor(Number(tmdbId))}:${s}:${ep}`;
|
|
53
|
+
const encoder = new TextEncoder();
|
|
54
|
+
const d = encoder.encode(payloadStr);
|
|
55
|
+
|
|
56
|
+
const key = "224eff10e662e9635c9f671cf46351dcd69af42b1edd56f5e5fa21751f44b9c8";
|
|
57
|
+
const u = lL(key, d.length);
|
|
58
|
+
|
|
59
|
+
const h = new Uint8Array(d.length + 2);
|
|
60
|
+
h[0] = 255 & d.length;
|
|
61
|
+
h[1] = (d.length >>> 8) & 255;
|
|
62
|
+
|
|
63
|
+
let c = (0x9e3779b9 ^ d.length) >>> 0;
|
|
64
|
+
for (let e = 0; e < d.length; e += 1) {
|
|
65
|
+
c = lA((c + u[e % u.length] + lS[e % lS.length] + e) >>> 0);
|
|
66
|
+
h[e + 2] = d[e] ^ (255 & c) ^ u[(7 * e + 3) % u.length];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const token = base64UrlEncode(h);
|
|
70
|
+
return `https://glendale-plumbing.com/c/v1/${token}/master.m3u8`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function getStream({ id, s, e }) {
|
|
74
|
+
try {
|
|
75
|
+
const isTv = !!s;
|
|
76
|
+
const mediaType = isTv ? 'tv' : 'movie';
|
|
77
|
+
const m3u8Url = generateCineSuUrl(mediaType, id, s || 1, e || 1);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
allUrls: [
|
|
81
|
+
{
|
|
82
|
+
url: m3u8Url,
|
|
83
|
+
server: 'CineSu',
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
};
|
|
87
|
+
} catch (err) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function getSources() {
|
|
93
|
+
return ['CineSu'];
|
|
94
|
+
}
|