@vyla-entertainment/sdk 1.17.0 → 1.19.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 +5 -5
- package/package.json +1 -1
- package/src/config.js +187 -118
- package/src/sources/cinecat.js +19 -0
- package/src/sources/cinesrc.js +398 -0
- package/src/sources/cinezo.js +55 -0
- package/src/sources/cno.js +95 -0
- package/src/sources/flaxmovies.js +91 -103
- package/src/sources/hexa.js +54 -0
- package/src/sources/hyper.js +36 -0
- package/src/sources/kickassanime.js +125 -0
- package/src/sources/lmscript.js +53 -25
- package/src/sources/movy.js +161 -233
- package/src/sources/net79.js +56 -0
- package/src/sources/rivo.js +54 -0
- package/src/sources/thugtv.js +56 -55
- package/src/sources/vidora.js +62 -0
- package/src/sources/yapgrid.js +63 -0
- package/src/sources/yastream.js +30 -0
- package/src/sources/anihq.js +0 -124
- package/src/sources/anineko.js +0 -190
- package/src/sources/movienight.js +0 -117
- package/src/sources/vidgod.js +0 -147
- package/src/sources/vidnest.js +0 -79
package/src/sources/thugtv.js
CHANGED
|
@@ -1,62 +1,63 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fetchJson } from '../utils/helpers.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
export async function getStream({ id, s, e }) {
|
|
4
|
+
const isTv = s != null && e != null;
|
|
5
|
+
const path = isTv ? `/stream/tv/${id}/${s}/${e}` : `/stream/movie/${id}`;
|
|
6
|
+
const mainUrl = `https://api.thugtv.online/api${path}`;
|
|
7
|
+
|
|
8
|
+
const headers = {
|
|
9
|
+
'Accept': 'application/json',
|
|
10
|
+
'Referer': 'https://thugtv.online/',
|
|
11
|
+
'Origin': 'https://thugtv.online',
|
|
12
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
|
|
11
13
|
};
|
|
12
|
-
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
if (!
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
const res = await fetchJson(mainUrl, { headers }).catch(() => null);
|
|
16
|
+
if (!res || !Array.isArray(res.sources) || !res.sources.length) return null;
|
|
17
|
+
|
|
18
|
+
const allUrls = [];
|
|
19
|
+
|
|
20
|
+
for (const src of res.sources) {
|
|
21
|
+
if (src.type === 'embed') continue;
|
|
22
|
+
|
|
23
|
+
if (src.url && src.url.startsWith('http')) {
|
|
24
|
+
allUrls.push({
|
|
25
|
+
url: src.url,
|
|
26
|
+
server: `ThugTV - ${src.name || src.label || 'Direct'} (${src.quality || 'Auto'})`,
|
|
27
|
+
headers: {
|
|
28
|
+
Referer: 'https://thugtv.online/',
|
|
29
|
+
Origin: 'https://thugtv.online'
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (src.resolvePath) {
|
|
36
|
+
const cleanPath = src.resolvePath.startsWith('/') ? src.resolvePath : `/${src.resolvePath}`;
|
|
37
|
+
const resolveUrl = `https://thugtv.online/api${cleanPath}`;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const resolved = await fetchJson(resolveUrl, { headers });
|
|
41
|
+
const directUrl = resolved?.source?.url;
|
|
42
|
+
if (directUrl && directUrl.startsWith('http')) {
|
|
43
|
+
allUrls.push({
|
|
44
|
+
url: directUrl,
|
|
45
|
+
server: `ThugTV - ${resolved.source.name || resolved.source.label || src.name || 'Direct'}`,
|
|
46
|
+
headers: {
|
|
47
|
+
Referer: 'https://thugtv.online/',
|
|
48
|
+
Origin: 'https://thugtv.online'
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
} catch { }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!allUrls.length) return null;
|
|
18
57
|
|
|
19
58
|
return {
|
|
20
|
-
url:
|
|
21
|
-
headers:
|
|
22
|
-
|
|
23
|
-
Referer: "https://thugtv.online/",
|
|
24
|
-
Origin: "https://thugtv.online",
|
|
25
|
-
},
|
|
26
|
-
server: source.name || source.label || "ThugTV",
|
|
27
|
-
quality: source.quality || "1080p",
|
|
28
|
-
type: source.type,
|
|
29
|
-
skipProxy: true,
|
|
59
|
+
url: allUrls[0].url,
|
|
60
|
+
headers: allUrls[0].headers,
|
|
61
|
+
allUrls
|
|
30
62
|
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function getStream(args) {
|
|
34
|
-
const { id, s = null, e = null } = args;
|
|
35
|
-
const isTv = s != null && e != null;
|
|
36
|
-
|
|
37
|
-
const url = isTv
|
|
38
|
-
? `${API_BASE}/stream/tv/${id}/${s}/${e}`
|
|
39
|
-
: `${API_BASE}/stream/movie/${id}`;
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const res = await fetch(url, {
|
|
43
|
-
headers: getHeaders(),
|
|
44
|
-
signal: AbortSignal.timeout(15000),
|
|
45
|
-
});
|
|
46
|
-
if (!res.ok) return null;
|
|
47
|
-
|
|
48
|
-
const data = await res.json();
|
|
49
|
-
const sources = Array.isArray(data?.sources) ? data.sources : [];
|
|
50
|
-
if (!sources.length) return null;
|
|
51
|
-
|
|
52
|
-
const allUrls = sources
|
|
53
|
-
.map(mapSource)
|
|
54
|
-
.filter(Boolean);
|
|
55
|
-
|
|
56
|
-
if (!allUrls.length) return null;
|
|
57
|
-
|
|
58
|
-
return { allUrls };
|
|
59
|
-
} catch {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
63
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { fetchText } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
export async function getStream({ id, s, e }) {
|
|
4
|
+
const isTv = s != null && e != null;
|
|
5
|
+
const url = isTv
|
|
6
|
+
? `https://vidora-3l2w.onrender.com/api/sources?type=tv&id=${id}&season=${s}&episode=${e}`
|
|
7
|
+
: `https://vidora-3l2w.onrender.com/api/sources?type=movie&id=${id}`;
|
|
8
|
+
|
|
9
|
+
const headers = {
|
|
10
|
+
'Accept': 'text/event-stream',
|
|
11
|
+
'Referer': 'https://vidora-3l2w.onrender.com/',
|
|
12
|
+
'Origin': 'https://vidora-3l2w.onrender.com',
|
|
13
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const raw = await fetchText(url, { headers }).catch(() => null);
|
|
17
|
+
if (!raw) return null;
|
|
18
|
+
|
|
19
|
+
const lines = raw.split('\n');
|
|
20
|
+
let doneSources = null;
|
|
21
|
+
const streamItems = [];
|
|
22
|
+
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
const trimmed = line.trim();
|
|
25
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) continue;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(trimmed);
|
|
29
|
+
if (parsed.event === 'source' && parsed.source?.url) {
|
|
30
|
+
streamItems.push(parsed.source);
|
|
31
|
+
} else if (parsed.event === 'done' && Array.isArray(parsed.sources)) {
|
|
32
|
+
doneSources = parsed.sources;
|
|
33
|
+
}
|
|
34
|
+
} catch { }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const targetList = (doneSources && doneSources.length > 0) ? doneSources : streamItems;
|
|
38
|
+
if (!targetList.length) return null;
|
|
39
|
+
|
|
40
|
+
const allUrls = [];
|
|
41
|
+
|
|
42
|
+
for (const src of targetList) {
|
|
43
|
+
if (!src.url || !src.url.startsWith('http')) continue;
|
|
44
|
+
|
|
45
|
+
allUrls.push({
|
|
46
|
+
url: src.url,
|
|
47
|
+
server: `Vidora - ${src.label || src.server || src.providerGroup || 'Server'} (${src.quality || 'Auto'})`,
|
|
48
|
+
headers: src.headers || {
|
|
49
|
+
Referer: 'https://vidora-3l2w.onrender.com/',
|
|
50
|
+
Origin: 'https://vidora-3l2w.onrender.com'
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!allUrls.length) return null;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
url: allUrls[0].url,
|
|
59
|
+
headers: allUrls[0].headers,
|
|
60
|
+
allUrls
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { fetchJson } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
export async function getStream({ id, s, e }) {
|
|
4
|
+
const isTv = s != null && e != null;
|
|
5
|
+
const targetEmbedUrl = isTv
|
|
6
|
+
? `https://embedwave.cc/embed/tv/${id}/${s}/${e}`
|
|
7
|
+
: `https://embedwave.cc/embed/movie/${id}`;
|
|
8
|
+
|
|
9
|
+
const headers = {
|
|
10
|
+
'Accept': 'application/json',
|
|
11
|
+
'Content-Type': 'application/json',
|
|
12
|
+
'Origin': 'https://yapgrid.com',
|
|
13
|
+
'Referer': isTv ? `https://yapgrid.com/embed/tv/${id}/${s}/${e}` : `https://yapgrid.com/embed/movie/${id}`,
|
|
14
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const payload = {
|
|
18
|
+
url: targetEmbedUrl,
|
|
19
|
+
source: 'embedwave',
|
|
20
|
+
strictSource: true,
|
|
21
|
+
waitMs: 5000,
|
|
22
|
+
nativeHls: false,
|
|
23
|
+
cacheOnly: true
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const res = await fetchJson('https://yapgrid.com/player/api/extract', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers,
|
|
29
|
+
body: JSON.stringify(payload)
|
|
30
|
+
}).catch(() => null);
|
|
31
|
+
|
|
32
|
+
if (!res?.success || !Array.isArray(res.found) || !res.found.length) return null;
|
|
33
|
+
|
|
34
|
+
const allUrls = [];
|
|
35
|
+
|
|
36
|
+
for (const item of res.found) {
|
|
37
|
+
if (!item.url) continue;
|
|
38
|
+
|
|
39
|
+
let streamUrl = item.url;
|
|
40
|
+
if (streamUrl.startsWith('/')) {
|
|
41
|
+
streamUrl = `https://yapgrid.com${streamUrl}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
allUrls.push({
|
|
45
|
+
url: streamUrl,
|
|
46
|
+
server: `YapGrid - ${item.nxshaProvider || item.provider || 'Server'}`,
|
|
47
|
+
type: item.type === 'master' ? 'hls' : (streamUrl.includes('.m3u8') ? 'hls' : 'mp4'),
|
|
48
|
+
headers: {
|
|
49
|
+
Referer: 'https://yapgrid.com/',
|
|
50
|
+
Origin: 'https://yapgrid.com',
|
|
51
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!allUrls.length) return null;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
url: allUrls[0].url,
|
|
60
|
+
headers: allUrls[0].headers,
|
|
61
|
+
allUrls
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { fetchJson } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
export async function getStream({ id, s, e }) {
|
|
4
|
+
const isTv = s != null && e != null;
|
|
5
|
+
const path = isTv
|
|
6
|
+
? `/stream/series/tmdb:${id}:${s}:${e}.json`
|
|
7
|
+
: `/stream/movie/tmdb:${id}.json`;
|
|
8
|
+
|
|
9
|
+
const url = `https://yastream.tamthai.de${path}`;
|
|
10
|
+
const res = await fetchJson(url);
|
|
11
|
+
if (!res || !Array.isArray(res.streams) || !res.streams.length) return null;
|
|
12
|
+
|
|
13
|
+
const allUrls = res.streams.map(item => {
|
|
14
|
+
if (!item.url) return null;
|
|
15
|
+
return {
|
|
16
|
+
url: item.url,
|
|
17
|
+
server: `YaStream - ${item.name?.replace(/\n/g, ' ') || 'Server'}`,
|
|
18
|
+
headers: item.behaviorHints?.proxyHeaders?.request || undefined,
|
|
19
|
+
skipProxy: item.behaviorHints?.notWebReady === false
|
|
20
|
+
};
|
|
21
|
+
}).filter(Boolean);
|
|
22
|
+
|
|
23
|
+
if (!allUrls.length) return null;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
url: allUrls[0].url,
|
|
27
|
+
headers: allUrls[0].headers,
|
|
28
|
+
allUrls
|
|
29
|
+
};
|
|
30
|
+
}
|
package/src/sources/anihq.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
|
|
2
|
-
|
|
3
|
-
async function extractVoe(voeUrl) {
|
|
4
|
-
try {
|
|
5
|
-
let res = await fetch(voeUrl, {
|
|
6
|
-
headers: {
|
|
7
|
-
'User-Agent': USER_AGENT,
|
|
8
|
-
'Referer': 'https://anihq.cc/'
|
|
9
|
-
}
|
|
10
|
-
});
|
|
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
|
-
|
|
25
|
-
const mirrorUrl = res.url;
|
|
26
|
-
|
|
27
|
-
const scriptMatch = html.match(/<script type="application\/json">\["(.*?)"\]<\/script>/);
|
|
28
|
-
if (!scriptMatch) return null;
|
|
29
|
-
|
|
30
|
-
let str = scriptMatch[1];
|
|
31
|
-
|
|
32
|
-
str = str.replace(/[a-zA-Z]/g, (c) => {
|
|
33
|
-
return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const junk = ['@$', '^^', '~@', '%?', '*~', '!!', '#&'];
|
|
37
|
-
junk.forEach(j => { str = str.split(j).join(''); });
|
|
38
|
-
|
|
39
|
-
str = Buffer.from(str, 'base64').toString('utf-8');
|
|
40
|
-
|
|
41
|
-
let shifted = '';
|
|
42
|
-
for (let i = 0; i < str.length; i++) {
|
|
43
|
-
shifted += String.fromCharCode(str.charCodeAt(i) - 3);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
let reversed = shifted.split('').reverse().join('');
|
|
47
|
-
|
|
48
|
-
let finalJsonStr = Buffer.from(reversed, 'base64').toString('utf-8');
|
|
49
|
-
|
|
50
|
-
const finalData = JSON.parse(finalJsonStr);
|
|
51
|
-
|
|
52
|
-
if (finalData.file || finalData.source) {
|
|
53
|
-
return {
|
|
54
|
-
url: finalData.file || finalData.source,
|
|
55
|
-
mirror: mirrorUrl
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
} catch (e) {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function cleanSlug(title) {
|
|
66
|
-
return title
|
|
67
|
-
.toLowerCase()
|
|
68
|
-
.replace(/[^a-z0-9\s]/g, '')
|
|
69
|
-
.replace(/\s+/g, '-')
|
|
70
|
-
.replace(/^-+|-+$/g, '');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export async function getStream({ id, s, e, audio }) {
|
|
74
|
-
if (!s) return null;
|
|
75
|
-
|
|
76
|
-
const info = await getTmdbInfo(id, 'tv', s);
|
|
77
|
-
if (!info.isAnime) return null;
|
|
78
|
-
|
|
79
|
-
const seriesTitle = info.titles.find(t =>
|
|
80
|
-
t.toLowerCase() !== 'east blue' &&
|
|
81
|
-
t.length > 3
|
|
82
|
-
) || info.titles[0];
|
|
83
|
-
|
|
84
|
-
const cleanTitle = cleanSlug(seriesTitle);
|
|
85
|
-
const typeSuffix = audio === 'dub' ? 'english-dubbed' : 'english-subbed';
|
|
86
|
-
const url = `https://anihq.cc/watch/${cleanTitle}-episode-${e}-${typeSuffix}/`;
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
let pageRes = await fetch(url, { headers: { 'User-Agent': USER_AGENT } });
|
|
90
|
-
let html = await pageRes.text();
|
|
91
|
-
|
|
92
|
-
if (!pageRes.ok) {
|
|
93
|
-
const searchUrl = `https://anihq.cc/search?keyword=${encodeURIComponent(seriesTitle)}`;
|
|
94
|
-
const searchRes = await fetch(searchUrl, { headers: { 'User-Agent': USER_AGENT } });
|
|
95
|
-
const searchHtml = await searchRes.text();
|
|
96
|
-
const searchRegex = new RegExp(`href="(https://anihq.cc/watch/[^"]+-episode-${e}-${typeSuffix}/)"`, 'i');
|
|
97
|
-
const match = searchHtml.match(searchRegex);
|
|
98
|
-
if (match) {
|
|
99
|
-
const retryRes = await fetch(match[1], { headers: { 'User-Agent': USER_AGENT } });
|
|
100
|
-
html = await retryRes.text();
|
|
101
|
-
} else return null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const iframeMatch = html.match(/iframe[^>]+src=["'](https:\/\/(?:[a-z0-9.]+|ellenpoliticalfollow\.com)\/e\/[a-zA-Z0-9]+)["']/i);
|
|
105
|
-
if (!iframeMatch) return null;
|
|
106
|
-
|
|
107
|
-
const result = await extractVoe(iframeMatch[1]);
|
|
108
|
-
|
|
109
|
-
if (result && result.url) {
|
|
110
|
-
const mirrorOrigin = new URL(result.mirror).origin;
|
|
111
|
-
return {
|
|
112
|
-
url: result.url,
|
|
113
|
-
headers: {
|
|
114
|
-
'Referer': mirrorOrigin + '/',
|
|
115
|
-
'User-Agent': USER_AGENT,
|
|
116
|
-
'Origin': mirrorOrigin
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
} catch (err) {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
return null;
|
|
124
|
-
}
|
package/src/sources/anineko.js
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import { getTmdbInfo, USER_AGENT, fetchText } from '../utils/helpers.js';
|
|
2
|
-
|
|
3
|
-
const BASE_URL = "https://anineko.to";
|
|
4
|
-
|
|
5
|
-
function cleanTitle(t) { return t ? t.toLowerCase().replace(/[^a-z0-9]/g, '') : ''; }
|
|
6
|
-
|
|
7
|
-
function decodeEntities(str) {
|
|
8
|
-
return str.replace(/&(nbsp|amp|quot|lt|gt);/g, (m, e) => ({ "nbsp": " ", "amp": "&", "quot": "\"", "lt": "<", "gt": ">" })[e]).replace(/&#(\d+);/gi, (m, n) => String.fromCharCode(parseInt(n, 10)));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async function search(query) {
|
|
12
|
-
try {
|
|
13
|
-
const html = await fetchText(`${BASE_URL}/browser?keyword=${encodeURIComponent(query)}`, {
|
|
14
|
-
headers: { 'User-Agent': USER_AGENT },
|
|
15
|
-
signal: AbortSignal.timeout(6000)
|
|
16
|
-
});
|
|
17
|
-
const results = [];
|
|
18
|
-
let idx = 0;
|
|
19
|
-
while ((idx = html.indexOf('nv-anime-thumb', idx)) !== -1) {
|
|
20
|
-
const startHref = html.lastIndexOf('<a', idx);
|
|
21
|
-
if (startHref === -1) { idx += 14; continue; }
|
|
22
|
-
const hrefMatch = html.slice(startHref, html.indexOf('>', startHref)).match(/href=["']([^"']+)["']/i);
|
|
23
|
-
if (!hrefMatch) { idx += 14; continue; }
|
|
24
|
-
const slug = hrefMatch[1].split('/').pop();
|
|
25
|
-
const h3Start = html.indexOf('nv-anime-title', idx);
|
|
26
|
-
if (h3Start !== -1) {
|
|
27
|
-
const contentStart = html.indexOf('>', h3Start) + 1;
|
|
28
|
-
const contentEnd = html.indexOf('<', contentStart);
|
|
29
|
-
if (contentEnd !== -1) results.push({ slug, text: html.slice(contentStart, contentEnd).trim() });
|
|
30
|
-
}
|
|
31
|
-
idx += 14;
|
|
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
|
-
}
|
|
49
|
-
return results;
|
|
50
|
-
} catch { return []; }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function extractHls(embedUrl) {
|
|
54
|
-
try {
|
|
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);
|
|
67
|
-
if (m) return decodeEntities(m[1]);
|
|
68
|
-
} catch { }
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export async function getStream({ id, s, e, audio, sdk }) {
|
|
73
|
-
const tmdbApiKey = sdk?.tmdbApiKey || null;
|
|
74
|
-
if (!tmdbApiKey) return null;
|
|
75
|
-
try {
|
|
76
|
-
const info = await getTmdbInfo(tmdbApiKey, id, s ? 'tv' : 'movie', s);
|
|
77
|
-
if (!info || !info.isAnime) return null;
|
|
78
|
-
let seriesSlug = null;
|
|
79
|
-
for (const title of info.titles) {
|
|
80
|
-
const potentialSlug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
81
|
-
if (!potentialSlug) continue;
|
|
82
|
-
try {
|
|
83
|
-
const checkHtml = await fetchText(`${BASE_URL}/watch/${potentialSlug}/ep-${e || 1}`, {
|
|
84
|
-
headers: { 'User-Agent': USER_AGENT, Referer: `${BASE_URL}/watch/${potentialSlug}` },
|
|
85
|
-
signal: AbortSignal.timeout(4000)
|
|
86
|
-
});
|
|
87
|
-
if (checkHtml && checkHtml.includes('nv-watch-page')) {
|
|
88
|
-
seriesSlug = potentialSlug;
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
} catch {}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (!seriesSlug) {
|
|
95
|
-
for (const title of info.titles) {
|
|
96
|
-
const results = await search(title);
|
|
97
|
-
const targetClean = cleanTitle(title);
|
|
98
|
-
|
|
99
|
-
const exactSlugMatch = results.find(r => {
|
|
100
|
-
const slugClean = cleanTitle(r.slug);
|
|
101
|
-
return slugClean === targetClean || r.slug === targetClean;
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (exactSlugMatch) {
|
|
105
|
-
seriesSlug = exactSlugMatch.slug;
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const sortedResults = [...results].sort((a, b) => a.slug.length - b.slug.length);
|
|
110
|
-
|
|
111
|
-
for (const r of sortedResults) {
|
|
112
|
-
const resultClean = cleanTitle(r.text);
|
|
113
|
-
const isExactMatch = resultClean === targetClean;
|
|
114
|
-
const isCloseMatch = r.text.toLowerCase().includes(title.toLowerCase());
|
|
115
|
-
if (isExactMatch || isCloseMatch) {
|
|
116
|
-
seriesSlug = r.slug;
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (seriesSlug) break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (!seriesSlug && info.titles.length) {
|
|
124
|
-
const fSearch = await search(info.titles[0]);
|
|
125
|
-
const sortedResults = [...fSearch].sort((a, b) => a.slug.length - b.slug.length);
|
|
126
|
-
if (sortedResults.length) seriesSlug = sortedResults[0].slug;
|
|
127
|
-
}
|
|
128
|
-
if (!seriesSlug) return null;
|
|
129
|
-
const watchHtml = await fetchText(`${BASE_URL}/watch/${seriesSlug}/ep-${e || 1}`, {
|
|
130
|
-
headers: {
|
|
131
|
-
'User-Agent': USER_AGENT,
|
|
132
|
-
Referer: `${BASE_URL}/watch/${seriesSlug}`
|
|
133
|
-
},
|
|
134
|
-
signal: AbortSignal.timeout(6000)
|
|
135
|
-
});
|
|
136
|
-
const byAudio = { sub: [], dub: [] };
|
|
137
|
-
|
|
138
|
-
const iframeMatch = watchHtml.match(/<iframe[^>]*src=["']([^"']+)["'][^>]*>/i);
|
|
139
|
-
if (iframeMatch) {
|
|
140
|
-
const iframeSrc = decodeEntities(iframeMatch[1]);
|
|
141
|
-
const hls = await extractHls(iframeSrc);
|
|
142
|
-
if (hls) {
|
|
143
|
-
const audioType = audio === 'dub' ? 'dub' : 'sub';
|
|
144
|
-
byAudio[audioType].push(iframeSrc);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (byAudio.sub.length === 0 && byAudio.dub.length === 0) {
|
|
149
|
-
const tabMatches = [...watchHtml.matchAll(/<button[^>]*class=["'][^"']*nv-server-tab[^"']*["'][^>]*data-id=["']([^"']+)["'][^>]*>/gi)];
|
|
150
|
-
const tabMap = {};
|
|
151
|
-
for (const match of tabMatches) {
|
|
152
|
-
const tabId = match[1].toLowerCase();
|
|
153
|
-
const tabClassMatch = match[0].match(/class=["'][^"']*(tab_\d+)[^"']/i);
|
|
154
|
-
if (tabClassMatch) {
|
|
155
|
-
const tabClass = tabClassMatch[1];
|
|
156
|
-
tabMap[tabClass] = tabId.includes('dub') ? 'dub' : 'sub';
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const btnRegex = /<button\s+[^>]*class=["'][^"']*server-video[^"']*["'][^>]*>/gi;
|
|
161
|
-
const btnMatches = [...watchHtml.matchAll(btnRegex)];
|
|
162
|
-
for (const match of btnMatches) {
|
|
163
|
-
const btnHtml = match[0];
|
|
164
|
-
const videoMatch = btnHtml.match(/data-video=["']([^"']+)["']/i);
|
|
165
|
-
const tabMatch = btnHtml.match(/data-tab=["']([^"']+)["']/i);
|
|
166
|
-
if (videoMatch && tabMatch) {
|
|
167
|
-
const videoUrl = decodeEntities(videoMatch[1]);
|
|
168
|
-
const tabClass = tabMatch[1];
|
|
169
|
-
const audioType = tabMap[tabClass] || 'sub';
|
|
170
|
-
byAudio[audioType].push(videoUrl);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]);
|
|
175
|
-
const allUrls = [];
|
|
176
|
-
for (const aud of audiosToTry) {
|
|
177
|
-
for (const embed of byAudio[aud]) {
|
|
178
|
-
const hls = await extractHls(embed);
|
|
179
|
-
if (hls) allUrls.push({ url: hls, type: "hls", audio: aud, server: "AniNeko", skipProxy: false });
|
|
180
|
-
}
|
|
181
|
-
if (allUrls.length) break;
|
|
182
|
-
}
|
|
183
|
-
return allUrls.length ? { allUrls } : null;
|
|
184
|
-
} catch { return null; }
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export async function getSources(args) {
|
|
188
|
-
const stream = await getStream(args);
|
|
189
|
-
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : [];
|
|
190
|
-
}
|