@vyla-entertainment/sdk 1.9.0 → 1.10.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/.github/workflows/publish.yml +38 -38
- package/.gitlab-ci.yml +16 -0
- package/README.md +9 -13
- package/package.json +33 -32
- package/src/config.js +86 -118
- package/src/health.js +2 -2
- package/src/sdk.d.ts +122 -122
- package/src/sdk.js +7 -4
- package/src/sources/anineko.js +3 -1
- package/src/sources/cinejoy.js +194 -209
- package/src/sources/flaxmovies.js +113 -21
- package/src/sources/frame.js +88 -0
- package/src/sources/fsonic.js +3 -1
- package/src/sources/fsonline.js +3 -1
- package/src/sources/lmscript.js +42 -42
- package/src/sources/lookmovie.js +3 -1
- package/src/sources/luna.js +125 -0
- package/src/sources/mapple.js +40 -4
- package/src/sources/megasource.js +47 -0
- package/src/sources/meowtv.js +0 -1
- package/src/sources/nova.js +46 -0
- package/src/sources/peestream.js +122 -0
- package/src/sources/pengu.js +8 -3
- package/src/sources/rivestream.js +179 -0
- package/src/sources/videasy.js +3 -1
- package/src/sources/vidgod.js +147 -0
- package/src/sources/vidnest.js +3 -1
- package/src/sources/vidvault.js +113 -113
- package/src/sources/vidzee.js +154 -199
- package/src/subtitles.js +18 -0
- package/src/utils/helpers.js +2 -2
- package/src/sources/1embed.js +0 -115
- package/src/sources/anikoto.js +0 -198
- package/src/sources/anizone.js +0 -109
- package/src/sources/cinesu.js +0 -94
- package/src/sources/kickassanime.js +0 -107
- package/src/sources/nekowatch.js +0 -57
- package/src/sources/opstream.js +0 -123
- package/src/sources/peachify.js +0 -78
- package/src/sources/reanime.js +0 -324
package/src/sources/anikoto.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
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
|
-
}
|
package/src/sources/anizone.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
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
|
-
}
|
package/src/sources/cinesu.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { fetchJson, getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
|
|
2
|
-
|
|
3
|
-
const BASE = "https://kaa.lt";
|
|
4
|
-
const HLS_BASE = "https://hls.krussdomi.com/manifest";
|
|
5
|
-
const HEADERS = { "User-Agent": USER_AGENT, Accept: "application/json" };
|
|
6
|
-
|
|
7
|
-
async function kaaSearch(query) {
|
|
8
|
-
const res = await fetchJson(`${BASE}/api/fsearch`, {
|
|
9
|
-
method: "POST",
|
|
10
|
-
headers: { ...HEADERS, "Content-Type": "application/json" },
|
|
11
|
-
body: JSON.stringify({ page: 1, query }),
|
|
12
|
-
}).catch(() => null);
|
|
13
|
-
return Array.isArray(res?.result) ? res.result : [];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async function kaaShowInfo(showSlug) {
|
|
17
|
-
return fetchJson(`${BASE}/api/show/${showSlug}`, { headers: HEADERS }).catch(() => null);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function kaaEpisodePage(showSlug, ep) {
|
|
21
|
-
return fetchJson(`${BASE}/api/show/${showSlug}/episodes?ep=${ep}&lang=ja-JP`, { headers: HEADERS }).catch(() => null);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function kaaAllEpisodes(showSlug) {
|
|
25
|
-
const first = await kaaEpisodePage(showSlug, 1);
|
|
26
|
-
if (!first) return [];
|
|
27
|
-
const pages = Array.isArray(first.pages) ? first.pages : [];
|
|
28
|
-
const all = Array.isArray(first.result) ? [...first.result] : [];
|
|
29
|
-
|
|
30
|
-
if (pages.length > 1) {
|
|
31
|
-
const rest = await Promise.all(
|
|
32
|
-
pages.slice(1).map(async (pg) => {
|
|
33
|
-
const startEp = pg.eps?.[0];
|
|
34
|
-
if (!startEp) return [];
|
|
35
|
-
const d = await kaaEpisodePage(showSlug, startEp);
|
|
36
|
-
return Array.isArray(d?.result) ? d.result : [];
|
|
37
|
-
})
|
|
38
|
-
);
|
|
39
|
-
for (const batch of rest) all.push(...batch);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return all;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function kaaEpisodeServers(showSlug, fullEpSlug) {
|
|
46
|
-
return fetchJson(`${BASE}/api/show/${showSlug}/episode/${fullEpSlug}`, { headers: HEADERS }).catch(() => null);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function getStream({ id, s, e, tmdbApiKey }) {
|
|
50
|
-
try {
|
|
51
|
-
const isTv = !!s;
|
|
52
|
-
const mediaType = isTv ? 'tv' : 'movie';
|
|
53
|
-
const epNum = e ? Number(e) : 1;
|
|
54
|
-
const seasonNum = s ? Number(s) : 1;
|
|
55
|
-
|
|
56
|
-
const tmdbInfo = await getTmdbInfo(tmdbApiKey, id, mediaType, seasonNum);
|
|
57
|
-
const titles = tmdbInfo?.titles || [];
|
|
58
|
-
|
|
59
|
-
let showSlug = null;
|
|
60
|
-
for (const title of titles) {
|
|
61
|
-
const results = await kaaSearch(title);
|
|
62
|
-
if (results.length) {
|
|
63
|
-
showSlug = results[0].slug;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!showSlug) return null;
|
|
69
|
-
|
|
70
|
-
const showInfo = await kaaShowInfo(showSlug);
|
|
71
|
-
let fullEpSlug = null;
|
|
72
|
-
|
|
73
|
-
if (showInfo?.type === "movie") {
|
|
74
|
-
const m = (showInfo.watch_uri || "").match(/\/(ep-(\d+)-([a-f0-9]+))$/i);
|
|
75
|
-
if (m) fullEpSlug = m[1];
|
|
76
|
-
} else {
|
|
77
|
-
const episodes = await kaaAllEpisodes(showSlug);
|
|
78
|
-
const epItem = episodes.find((item) => item.episode_number === epNum);
|
|
79
|
-
if (epItem) fullEpSlug = `ep-${epItem.episode_number}-${epItem.slug}`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!fullEpSlug) return null;
|
|
83
|
-
|
|
84
|
-
const episodeData = await kaaEpisodeServers(showSlug, fullEpSlug);
|
|
85
|
-
const servers = Array.isArray(episodeData?.servers) ? episodeData.servers : [];
|
|
86
|
-
|
|
87
|
-
const urls = [];
|
|
88
|
-
for (const srv of servers) {
|
|
89
|
-
if (!srv.src) continue;
|
|
90
|
-
const m = srv.src.match(/[?&]id=([^&]+)/);
|
|
91
|
-
if (m) {
|
|
92
|
-
urls.push({
|
|
93
|
-
url: `${HLS_BASE}/${m[1]}/master.m3u8`,
|
|
94
|
-
server: `KickAssAnime (${srv.name || 'Server'})`,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return urls.length ? { allUrls: urls } : null;
|
|
100
|
-
} catch (err) {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export async function getSources() {
|
|
106
|
-
return ['KickAssAnime'];
|
|
107
|
-
}
|
package/src/sources/nekowatch.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { fetchJson, fetchText, getTmdbInfo, tmdbToAnilist } from '../utils/helpers.js';
|
|
2
|
-
|
|
3
|
-
const BASE_URL = "https://nekowatch.xyz";
|
|
4
|
-
const ANIME_JS_URL = `${BASE_URL}/js/anime.js?v=v263_fast_player_start`;
|
|
5
|
-
const FALLBACK_ORDER = ['kiwi', 'ally', 'bee', 'arc', 'anikoto', 'jet', 'bonk', 'moo', 'hop', 'pulsar', 'pahe'];
|
|
6
|
-
|
|
7
|
-
let providerOrderCache = null, providerOrderTs = 0;
|
|
8
|
-
|
|
9
|
-
async function getProviderOrder() {
|
|
10
|
-
const now = Date.now();
|
|
11
|
-
if (providerOrderCache && now - providerOrderTs < 1800000) return providerOrderCache;
|
|
12
|
-
try {
|
|
13
|
-
const js = await fetchText(ANIME_JS_URL, { signal: AbortSignal.timeout(6000) });
|
|
14
|
-
const match = js.match(/PROVIDER_ORDER\s*=\s*\[([\s\S]*?)\]/);
|
|
15
|
-
if (match) {
|
|
16
|
-
const order = [...match[1].matchAll(/['"]([a-z0-9_]+)['"]/gi)].map(m => m[1]);
|
|
17
|
-
if (order.length) { providerOrderCache = order; providerOrderTs = now; return order; }
|
|
18
|
-
}
|
|
19
|
-
} catch { }
|
|
20
|
-
return providerOrderCache || FALLBACK_ORDER;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function getStream({ id, s, e, audio }) {
|
|
24
|
-
try {
|
|
25
|
-
const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
|
|
26
|
-
if (!info || !info.isAnime) return null;
|
|
27
|
-
const anilistId = await tmdbToAnilist(id, s ? 'tv' : 'movie', s, info.titles, info.year);
|
|
28
|
-
if (!anilistId) return null;
|
|
29
|
-
const ep = e || 1;
|
|
30
|
-
const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]);
|
|
31
|
-
const providerOrder = await getProviderOrder();
|
|
32
|
-
for (const aud of audiosToTry) {
|
|
33
|
-
const results = await Promise.allSettled(providerOrder.map(p => fetchJson(`${BASE_URL}/api/anime/watch/${p}/${anilistId}/${aud}/${p}-${ep}`, { signal: AbortSignal.timeout(8000) })));
|
|
34
|
-
const allUrls = [], seenActual = new Set();
|
|
35
|
-
for (const r of results) {
|
|
36
|
-
if (r.status !== 'fulfilled' || !r.value) continue;
|
|
37
|
-
const data = r.value;
|
|
38
|
-
let streams = [], actual = data.attempted?.actualProvider || data.provider;
|
|
39
|
-
if (Array.isArray(data.streams)) streams = data.streams;
|
|
40
|
-
else {
|
|
41
|
-
for (const key of Object.keys(data)) if (data[key] && Array.isArray(data[key].streams)) { streams.push(...data[key].streams); if (!actual) actual = data[key].provider || key; }
|
|
42
|
-
}
|
|
43
|
-
if (!streams.length) continue;
|
|
44
|
-
if (actual) { if (seenActual.has(actual)) continue; seenActual.add(actual); }
|
|
45
|
-
const valid = streams.filter(st => st.type === "hls" && st.url).sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
46
|
-
for (const st of valid) allUrls.push({ url: st.url, type: "hls", audio: aud, server: st.server ? `NekoWatch-${st.server}` : "NekoWatch", headers: st.referer ? { Referer: st.referer } : undefined, skipProxy: /\.workers\.dev(\/|$)/i.test(st.url) });
|
|
47
|
-
}
|
|
48
|
-
if (allUrls.length) return { allUrls };
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
} catch { return null; }
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function getSources(args) {
|
|
55
|
-
const stream = await getStream(args);
|
|
56
|
-
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : [];
|
|
57
|
-
}
|
package/src/sources/opstream.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
|
|
2
|
-
|
|
3
|
-
const BASE_URL = 'https://opstream.fun';
|
|
4
|
-
const API_BASE = `${BASE_URL}/api/resolve`;
|
|
5
|
-
|
|
6
|
-
const API_HEADERS = {
|
|
7
|
-
'User-Agent': USER_AGENT,
|
|
8
|
-
'Accept': 'application/x-ndjson; charset=utf-8',
|
|
9
|
-
'Referer': `${BASE_URL}/`,
|
|
10
|
-
'Origin': BASE_URL,
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const STREAM_HEADERS = {
|
|
14
|
-
'User-Agent': USER_AGENT,
|
|
15
|
-
'Referer': `${BASE_URL}/`,
|
|
16
|
-
'Origin': BASE_URL,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export async function getStream({ id, s, e }) {
|
|
20
|
-
try {
|
|
21
|
-
const isTv = s != null && e != null;
|
|
22
|
-
const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie');
|
|
23
|
-
|
|
24
|
-
if (!info?.titles?.length || !info.imdbId) return null;
|
|
25
|
-
|
|
26
|
-
const params = new URLSearchParams({
|
|
27
|
-
type: isTv ? 'tv' : 'movie',
|
|
28
|
-
tmdbId: id,
|
|
29
|
-
title: info.titles[0],
|
|
30
|
-
year: info.year || '',
|
|
31
|
-
imdbId: info.imdbId,
|
|
32
|
-
progress: '1'
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (isTv) {
|
|
36
|
-
params.append('season', s);
|
|
37
|
-
params.append('episode', e);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const res = await fetch(`${API_BASE}?${params}`, {
|
|
41
|
-
headers: API_HEADERS,
|
|
42
|
-
signal: AbortSignal.timeout(15000)
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
if (!res.ok) return null;
|
|
46
|
-
|
|
47
|
-
const text = await res.text();
|
|
48
|
-
const lines = text.split('\n');
|
|
49
|
-
|
|
50
|
-
let streamData = null;
|
|
51
|
-
for (const line of lines) {
|
|
52
|
-
const trimmed = line.trim();
|
|
53
|
-
if (!trimmed) continue;
|
|
54
|
-
try {
|
|
55
|
-
const json = JSON.parse(trimmed);
|
|
56
|
-
if (json.t === 'done' && json.data) {
|
|
57
|
-
streamData = json.data;
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
} catch { continue; }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!streamData) return null;
|
|
64
|
-
|
|
65
|
-
const subtitles = (streamData.captions || []).map(c => ({
|
|
66
|
-
url: c.url.startsWith('http') ? c.url : `${BASE_URL}${c.url}`,
|
|
67
|
-
lang: c.label || 'Unknown'
|
|
68
|
-
}));
|
|
69
|
-
|
|
70
|
-
const allUrls = [];
|
|
71
|
-
|
|
72
|
-
if (streamData.qualities && streamData.qualities.length > 0) {
|
|
73
|
-
for (const q of streamData.qualities) {
|
|
74
|
-
let finalUrl = q.url;
|
|
75
|
-
if (!finalUrl.startsWith('http')) {
|
|
76
|
-
finalUrl = `${BASE_URL}${finalUrl}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const isDash = finalUrl.includes('.mpd');
|
|
80
|
-
const isHls = finalUrl.includes('.m3u8');
|
|
81
|
-
|
|
82
|
-
allUrls.push({
|
|
83
|
-
url: finalUrl,
|
|
84
|
-
server: `OpStream - ${q.label}`,
|
|
85
|
-
type: isDash ? 'dash' : (isHls ? 'hls' : 'mp4'),
|
|
86
|
-
headers: STREAM_HEADERS,
|
|
87
|
-
subtitles,
|
|
88
|
-
skipProxy: true
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
} else if (streamData.url) {
|
|
92
|
-
let finalUrl = streamData.url;
|
|
93
|
-
if (finalUrl.includes('?u=')) {
|
|
94
|
-
try {
|
|
95
|
-
const uParam = new URL(finalUrl, BASE_URL).searchParams.get('u');
|
|
96
|
-
if (uParam) finalUrl = Buffer.from(uParam, 'base64').toString('utf8');
|
|
97
|
-
} catch {}
|
|
98
|
-
} else if (!finalUrl.startsWith('http')) {
|
|
99
|
-
finalUrl = `${BASE_URL}${finalUrl}`;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const isDash = streamData.kind === 'dash' || finalUrl.includes('.mpd');
|
|
103
|
-
const isHls = streamData.kind === 'hls' || finalUrl.includes('.m3u8');
|
|
104
|
-
|
|
105
|
-
allUrls.push({
|
|
106
|
-
url: finalUrl,
|
|
107
|
-
server: 'OpStream',
|
|
108
|
-
type: isDash ? 'dash' : (isHls ? 'hls' : 'mp4'),
|
|
109
|
-
headers: STREAM_HEADERS,
|
|
110
|
-
subtitles,
|
|
111
|
-
skipProxy: true
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (allUrls.length === 0) return null;
|
|
116
|
-
|
|
117
|
-
return { allUrls };
|
|
118
|
-
} catch { return null; }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export async function getSources() {
|
|
122
|
-
return ['OpStream'];
|
|
123
|
-
}
|