@vyla-entertainment/sdk 1.0.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/LICENSE +407 -0
- package/README.md +44 -0
- package/package.json +31 -0
- package/src/config.js +519 -0
- package/src/downloads/downloads.js +8 -0
- package/src/downloads/sources/trendimovies.js +63 -0
- package/src/health.js +38 -0
- package/src/sdk.js +65 -0
- package/src/sources/123anime.js +57 -0
- package/src/sources/1embed.js +92 -0
- package/src/sources/anihq.js +112 -0
- package/src/sources/anineko.js +81 -0
- package/src/sources/anipm.js +70 -0
- package/src/sources/flaxmovies.js +14 -0
- package/src/sources/fsharetv.js +44 -0
- package/src/sources/fsonic.js +47 -0
- package/src/sources/fsonline.js +46 -0
- package/src/sources/goated.js +262 -0
- package/src/sources/hexa.js +82 -0
- package/src/sources/kisskh.js +60 -0
- package/src/sources/lookmovie.js +78 -0
- package/src/sources/mapple.js +115 -0
- package/src/sources/meowtv.js +111 -0
- package/src/sources/nebula.js +62 -0
- package/src/sources/nekowatch.js +57 -0
- package/src/sources/opstream.js +123 -0
- package/src/sources/peachify.js +78 -0
- package/src/sources/pengu.js +45 -0
- package/src/sources/purstream.js +104 -0
- package/src/sources/vidapi.js +115 -0
- package/src/sources/vidcore.js +43 -0
- package/src/sources/videasy.js +98 -0
- package/src/sources/vidfast.js +53 -0
- package/src/sources/vidlink.js +28 -0
- package/src/sources/vidnest.js +77 -0
- package/src/sources/vidrift.js +95 -0
- package/src/sources/vidrock.js +145 -0
- package/src/sources/vidsync.js +38 -0
- package/src/sources/vidup.js +79 -0
- package/src/sources/vidzee.js +120 -0
- package/src/sources/vixsrc.js +98 -0
- package/src/sources/xpass.js +40 -0
- package/src/subtitles.js +127 -0
- package/src/utils/helpers.js +194 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getTmdbInfo, fetchJson, USER_AGENT } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
async function resolveSlug(id, s, e, info) {
|
|
4
|
+
const titles = info?.titles || [];
|
|
5
|
+
const keywords = [];
|
|
6
|
+
for (const title of titles) {
|
|
7
|
+
const t = title.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
8
|
+
if (!keywords.includes(t)) keywords.push(t);
|
|
9
|
+
const words = title.split(/[\s:]+/).filter(w => w.length > 2);
|
|
10
|
+
if (words.length >= 2) {
|
|
11
|
+
const short = words.slice(0, 3).join(' ').toLowerCase();
|
|
12
|
+
if (!keywords.includes(short)) keywords.push(short);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const slugCandidates = [];
|
|
16
|
+
for (const keyword of keywords) {
|
|
17
|
+
try {
|
|
18
|
+
const data = await fetchJson(`https://123animehub.cc/ajax/film/search?keyword=${encodeURIComponent(keyword)}&_=${Date.now()}`, { headers: { 'X-Requested-With': 'XMLHttpRequest', 'Referer': 'https://123animehub.cc', 'Accept': 'application/json', 'User-Agent': USER_AGENT }, signal: AbortSignal.timeout(8000) });
|
|
19
|
+
let html = data.html || '', idx = 0;
|
|
20
|
+
while ((idx = html.indexOf('href="/anime/', idx)) !== -1) {
|
|
21
|
+
const start = idx + 13;
|
|
22
|
+
const end = html.indexOf('"', start);
|
|
23
|
+
const slug = html.slice(start, end);
|
|
24
|
+
if (!slugCandidates.includes(slug)) slugCandidates.push(slug);
|
|
25
|
+
idx = end;
|
|
26
|
+
}
|
|
27
|
+
} catch { }
|
|
28
|
+
if (slugCandidates.length) break;
|
|
29
|
+
}
|
|
30
|
+
for (const slug of slugCandidates) {
|
|
31
|
+
try {
|
|
32
|
+
const data = await fetchJson(`https://123animehub.cc/ajax/episode/info?epr=${encodeURIComponent(s ? `${slug}/${s}/${e}` : `${slug}/1`)}&ts=1&_=${Date.now()}`, { headers: { 'Referer': `https://123animehub.cc/anime/${slug}`, 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/javascript, */*; q=0.01', 'User-Agent': USER_AGENT }, signal: AbortSignal.timeout(10000) });
|
|
33
|
+
if (data?.target) return data.target;
|
|
34
|
+
} catch { }
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function getStream({ id, s, e }) {
|
|
40
|
+
if (!s) return null;
|
|
41
|
+
const info = await getTmdbInfo(id, 'tv', s);
|
|
42
|
+
if (!info.isAnime) return null;
|
|
43
|
+
const target = await resolveSlug(id, s, e, info);
|
|
44
|
+
if (!target) return null;
|
|
45
|
+
const embedMatch = target.match(/\/embed-[^/]+\/([A-Za-z0-9+/=]+)$/);
|
|
46
|
+
if (!embedMatch) return null;
|
|
47
|
+
const origin = new URL(target).origin;
|
|
48
|
+
try {
|
|
49
|
+
const embedRes = await fetch(target, { headers: { 'User-Agent': USER_AGENT }, signal: AbortSignal.timeout(10000) });
|
|
50
|
+
if (!embedRes.ok) return null;
|
|
51
|
+
const setCookie = embedRes.headers.get('set-cookie');
|
|
52
|
+
const data = await fetchJson(`${origin}/hs/getSources?id=${embedMatch[1]}`, { headers: { 'Referer': target, 'Accept': '*/*', 'User-Agent': USER_AGENT, ...(setCookie ? { 'Cookie': setCookie.split(';')[0].trim() } : {}) }, signal: AbortSignal.timeout(10000) });
|
|
53
|
+
const src = typeof data.sources === 'string' && data.sources.length ? data.sources : (Array.isArray(data.sources) && data.sources.length ? (data.sources[0].file || data.sources[0].src || data.sources[0].url) : null);
|
|
54
|
+
if (src) return { url: src, headers: { 'Referer': 'https://play2.echovideo.ru/', 'Origin': 'https://play2.echovideo.ru' } };
|
|
55
|
+
} catch { }
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { USER_AGENT } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "https://1embed.cc";
|
|
4
|
+
|
|
5
|
+
const PROVIDERS = [
|
|
6
|
+
{ id: "sdev", name: "1Embed - SDev" },
|
|
7
|
+
{ id: "xpa", name: "1Embed - XPass" },
|
|
8
|
+
{ id: "vnes", name: "1Embed - VidNest" },
|
|
9
|
+
{ id: "pro", name: "1Embed - Pro" },
|
|
10
|
+
{ id: "pur", name: "1Embed - Purstream" },
|
|
11
|
+
{ id: "czo", name: "1Embed - Cinezo" },
|
|
12
|
+
{ id: "fas", name: "1Embed - VidFast" }
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
let cachedToken = null;
|
|
16
|
+
let tokenExpiresAt = 0;
|
|
17
|
+
|
|
18
|
+
async function getToken() {
|
|
19
|
+
const now = Math.floor(Date.now() / 1000);
|
|
20
|
+
if (cachedToken && tokenExpiresAt > now + 10) return cachedToken;
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(`${BASE_URL}/api/token`, { headers: { "Referer": `${BASE_URL}/`, "User-Agent": USER_AGENT, "Accept": "application/json" }, signal: AbortSignal.timeout(10000) });
|
|
23
|
+
if (!res.ok) return null;
|
|
24
|
+
const data = await res.json();
|
|
25
|
+
cachedToken = data.token;
|
|
26
|
+
tokenExpiresAt = data.expiresAt;
|
|
27
|
+
return cachedToken;
|
|
28
|
+
} catch { return null; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function fetchProviderStream(provider, type, id, s, e, token) {
|
|
32
|
+
try {
|
|
33
|
+
const referer = type === "movie" ? `${BASE_URL}/embed/movie/${id}` : `${BASE_URL}/embed/tv/${id}/${s}/${e}`;
|
|
34
|
+
const payload = { type, id: String(id), provider: provider.id };
|
|
35
|
+
if (type === "tv") { payload.season = Number(s); payload.episode = Number(e); }
|
|
36
|
+
const res = await fetch(`${BASE_URL}/api/sources`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "User-Agent": USER_AGENT, "Referer": referer, "Origin": BASE_URL, "Accept": "application/json", "Content-Type": "application/json", "x-bcine-key": token, "X-Requested-With": "XMLHttpRequest" },
|
|
39
|
+
body: JSON.stringify(payload),
|
|
40
|
+
signal: AbortSignal.timeout(15000)
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok) return null;
|
|
43
|
+
const data = await res.json();
|
|
44
|
+
|
|
45
|
+
let rawSources = [];
|
|
46
|
+
if (Array.isArray(data.servers)) rawSources.push(...data.servers);
|
|
47
|
+
if (data.providerResults?.[provider.id]?.sources) rawSources.push(...data.providerResults[provider.id].sources);
|
|
48
|
+
if (data.providerResults?.[provider.id] && Array.isArray(data.providerResults[provider.id])) rawSources.push(...data.providerResults[provider.id]);
|
|
49
|
+
|
|
50
|
+
if (!rawSources.length) return null;
|
|
51
|
+
const results = [];
|
|
52
|
+
for (const src of rawSources) {
|
|
53
|
+
if (typeof src !== 'object' || !src) continue;
|
|
54
|
+
let streamUrl = src.url || src.file || src.link;
|
|
55
|
+
if (typeof streamUrl !== 'string') continue;
|
|
56
|
+
if (streamUrl.startsWith('/')) streamUrl = `${BASE_URL}${streamUrl}`;
|
|
57
|
+
const isWorker = streamUrl.includes('omena-puu') || streamUrl.includes('nocach') || streamUrl.includes('?p=');
|
|
58
|
+
const isHls = src.type === 'hls' || streamUrl.includes('m3u8') || isWorker;
|
|
59
|
+
if (isHls && !streamUrl.includes('m3u8')) streamUrl += streamUrl.includes('?') ? '&format=.m3u8' : '?format=.m3u8';
|
|
60
|
+
const srcHeaders = (src.headers && typeof src.headers === 'object') ? src.headers : {};
|
|
61
|
+
results.push({
|
|
62
|
+
url: streamUrl,
|
|
63
|
+
server: provider.name,
|
|
64
|
+
quality: src.quality || src.label || src.title || "Auto",
|
|
65
|
+
type: isHls ? "hls" : "mp4",
|
|
66
|
+
headers: { "User-Agent": USER_AGENT, "Referer": referer, "Origin": BASE_URL, ...srcHeaders },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function getStream(args) {
|
|
76
|
+
const { id, s, e, server } = args;
|
|
77
|
+
const type = s != null && e != null ? "tv" : "movie";
|
|
78
|
+
const token = await getToken();
|
|
79
|
+
if (!token) return null;
|
|
80
|
+
let targets = PROVIDERS;
|
|
81
|
+
if (server && server !== 'all') {
|
|
82
|
+
const cleanName = server.replace('1Embed - ', '').toLowerCase();
|
|
83
|
+
targets = PROVIDERS.filter(p => p.id === cleanName || p.name.toLowerCase().includes(cleanName));
|
|
84
|
+
if (!targets.length) targets = PROVIDERS;
|
|
85
|
+
}
|
|
86
|
+
const settled = await Promise.allSettled(targets.map(p => fetchProviderStream(p, type, id, s, e, token)));
|
|
87
|
+
const allUrls = [];
|
|
88
|
+
for (const r of settled) if (r.status === 'fulfilled' && r.value) allUrls.push(...r.value);
|
|
89
|
+
return allUrls.length ? { allUrls } : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function getSources() { return PROVIDERS.map(p => p.name); }
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { getTmdbInfo, USER_AGENT } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
async function extractVoe(voeUrl) {
|
|
4
|
+
try {
|
|
5
|
+
const res = await fetch(voeUrl, {
|
|
6
|
+
headers: {
|
|
7
|
+
'User-Agent': USER_AGENT,
|
|
8
|
+
'Referer': 'https://anihq.cc/'
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const mirrorUrl = res.url;
|
|
13
|
+
const html = await res.text();
|
|
14
|
+
|
|
15
|
+
const scriptMatch = html.match(/<script type="application\/json">\["(.*?)"\]<\/script>/);
|
|
16
|
+
if (!scriptMatch) return null;
|
|
17
|
+
|
|
18
|
+
let str = scriptMatch[1];
|
|
19
|
+
|
|
20
|
+
str = str.replace(/[a-zA-Z]/g, (c) => {
|
|
21
|
+
return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const junk = ['@$', '^^', '~@', '%?', '*~', '!!', '#&'];
|
|
25
|
+
junk.forEach(j => { str = str.split(j).join(''); });
|
|
26
|
+
|
|
27
|
+
str = Buffer.from(str, 'base64').toString('utf-8');
|
|
28
|
+
|
|
29
|
+
let shifted = '';
|
|
30
|
+
for (let i = 0; i < str.length; i++) {
|
|
31
|
+
shifted += String.fromCharCode(str.charCodeAt(i) - 3);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let reversed = shifted.split('').reverse().join('');
|
|
35
|
+
|
|
36
|
+
let finalJsonStr = Buffer.from(reversed, 'base64').toString('utf-8');
|
|
37
|
+
|
|
38
|
+
const finalData = JSON.parse(finalJsonStr);
|
|
39
|
+
|
|
40
|
+
if (finalData.file || finalData.source) {
|
|
41
|
+
return {
|
|
42
|
+
url: finalData.file || finalData.source,
|
|
43
|
+
mirror: mirrorUrl
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function cleanSlug(title) {
|
|
54
|
+
return title
|
|
55
|
+
.toLowerCase()
|
|
56
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
57
|
+
.replace(/\s+/g, '-')
|
|
58
|
+
.replace(/^-+|-+$/g, '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function getStream({ id, s, e, audio }) {
|
|
62
|
+
if (!s) return null;
|
|
63
|
+
|
|
64
|
+
const info = await getTmdbInfo(id, 'tv', s);
|
|
65
|
+
if (!info.isAnime) return null;
|
|
66
|
+
|
|
67
|
+
const seriesTitle = info.titles.find(t =>
|
|
68
|
+
t.toLowerCase() !== 'east blue' &&
|
|
69
|
+
t.length > 3
|
|
70
|
+
) || info.titles[0];
|
|
71
|
+
|
|
72
|
+
const cleanTitle = cleanSlug(seriesTitle);
|
|
73
|
+
const typeSuffix = audio === 'dub' ? 'english-dubbed' : 'english-subbed';
|
|
74
|
+
const url = `https://anihq.cc/watch/${cleanTitle}-episode-${e}-${typeSuffix}/`;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
let pageRes = await fetch(url, { headers: { 'User-Agent': USER_AGENT } });
|
|
78
|
+
let html = await pageRes.text();
|
|
79
|
+
|
|
80
|
+
if (!pageRes.ok) {
|
|
81
|
+
const searchUrl = `https://anihq.cc/search?keyword=${encodeURIComponent(seriesTitle)}`;
|
|
82
|
+
const searchRes = await fetch(searchUrl, { headers: { 'User-Agent': USER_AGENT } });
|
|
83
|
+
const searchHtml = await searchRes.text();
|
|
84
|
+
const searchRegex = new RegExp(`href="(https://anihq.cc/watch/[^"]+-episode-${e}-${typeSuffix}/)"`, 'i');
|
|
85
|
+
const match = searchHtml.match(searchRegex);
|
|
86
|
+
if (match) {
|
|
87
|
+
const retryRes = await fetch(match[1], { headers: { 'User-Agent': USER_AGENT } });
|
|
88
|
+
html = await retryRes.text();
|
|
89
|
+
} else return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const iframeMatch = html.match(/iframe[^>]+src=["'](https:\/\/(?:[a-z0-9.]+|ellenpoliticalfollow\.com)\/e\/[a-zA-Z0-9]+)["']/i);
|
|
93
|
+
if (!iframeMatch) return null;
|
|
94
|
+
|
|
95
|
+
const result = await extractVoe(iframeMatch[1]);
|
|
96
|
+
|
|
97
|
+
if (result && result.url) {
|
|
98
|
+
const mirrorOrigin = new URL(result.mirror).origin;
|
|
99
|
+
return {
|
|
100
|
+
url: result.url,
|
|
101
|
+
headers: {
|
|
102
|
+
'Referer': mirrorOrigin + '/',
|
|
103
|
+
'User-Agent': USER_AGENT,
|
|
104
|
+
'Origin': mirrorOrigin
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { getTmdbInfo } 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)}`, { signal: AbortSignal.timeout(6000) });
|
|
14
|
+
const results = [];
|
|
15
|
+
let idx = 0;
|
|
16
|
+
while ((idx = html.indexOf('nv-anime-thumb', idx)) !== -1) {
|
|
17
|
+
const startHref = html.lastIndexOf('<a', idx);
|
|
18
|
+
if (startHref === -1) { idx += 14; continue; }
|
|
19
|
+
const hrefMatch = html.slice(startHref, html.indexOf('>', startHref)).match(/href=["']([^"']+)["']/i);
|
|
20
|
+
if (!hrefMatch) { idx += 14; continue; }
|
|
21
|
+
const slug = hrefMatch[1].split('/').pop();
|
|
22
|
+
const h3Start = html.indexOf('nv-anime-title', idx);
|
|
23
|
+
if (h3Start !== -1) {
|
|
24
|
+
const contentStart = html.indexOf('>', h3Start) + 1;
|
|
25
|
+
const contentEnd = html.indexOf('<', contentStart);
|
|
26
|
+
if (contentEnd !== -1) results.push({ slug, text: html.slice(contentStart, contentEnd).trim() });
|
|
27
|
+
}
|
|
28
|
+
idx += 14;
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
} catch { return []; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function extractHls(embedUrl) {
|
|
35
|
+
try {
|
|
36
|
+
const html = await fetchText(embedUrl, { headers: { Referer: `${BASE_URL}/` }, signal: AbortSignal.timeout(6000) });
|
|
37
|
+
const m = html.match(/const\s+src\s*=\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) || html.match(/file\s*:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i) || html.match(/["'](https?:\/\/[^"']+\/master\.m3u8[^"']*)["']/i) || html.match(/["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/i);
|
|
38
|
+
if (m) return decodeEntities(m[1]);
|
|
39
|
+
} catch { }
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getStream({ id, s, e, audio }) {
|
|
44
|
+
try {
|
|
45
|
+
const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
|
|
46
|
+
if (!info || !info.isAnime) return null;
|
|
47
|
+
let seriesSlug = null;
|
|
48
|
+
for (const title of info.titles) {
|
|
49
|
+
const results = await search(title);
|
|
50
|
+
const targetClean = cleanTitle(title);
|
|
51
|
+
for (const r of results) if (cleanTitle(r.text) === targetClean || r.text.toLowerCase().includes(title.toLowerCase())) { seriesSlug = r.slug; break; }
|
|
52
|
+
if (seriesSlug) break;
|
|
53
|
+
}
|
|
54
|
+
if (!seriesSlug && info.titles.length) {
|
|
55
|
+
const fSearch = await search(info.titles[0]);
|
|
56
|
+
if (fSearch.length) seriesSlug = fSearch[0].slug;
|
|
57
|
+
}
|
|
58
|
+
if (!seriesSlug) return null;
|
|
59
|
+
const watchHtml = await fetchText(`${BASE_URL}/watch/${seriesSlug}/ep-${e || 1}`, { headers: { Referer: `${BASE_URL}/watch/${seriesSlug}` }, signal: AbortSignal.timeout(6000) });
|
|
60
|
+
const byAudio = { sub: [], dub: [] };
|
|
61
|
+
for (const panel of watchHtml.matchAll(/<div\b[^>]*class=["'][^"']*nv-server-grid[^"']*["'][^>]*data-id=["']([^"']+)["'][^>]*>([\s\S]*?)(?=<div\b[^>]*class=["'][^"']*nv-server-grid|$)/gi)) {
|
|
62
|
+
const panelAudio = panel[1].toLowerCase().includes("dub") ? "dub" : "sub";
|
|
63
|
+
for (const btn of panel[2].matchAll(/data-video=["']([^"']+)["']/gi)) byAudio[panelAudio].push(decodeEntities(btn[1]));
|
|
64
|
+
}
|
|
65
|
+
const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]);
|
|
66
|
+
const allUrls = [];
|
|
67
|
+
for (const aud of audiosToTry) {
|
|
68
|
+
for (const embed of byAudio[aud]) {
|
|
69
|
+
const hls = await extractHls(embed);
|
|
70
|
+
if (hls) allUrls.push({ url: hls, type: "hls", audio: aud, server: "AniNeko", skipProxy: false });
|
|
71
|
+
}
|
|
72
|
+
if (allUrls.length) break;
|
|
73
|
+
}
|
|
74
|
+
return allUrls.length ? { allUrls } : null;
|
|
75
|
+
} catch { return null; }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function getSources(args) {
|
|
79
|
+
const stream = await getStream(args);
|
|
80
|
+
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : [];
|
|
81
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getTmdbInfo, fetchJson } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "https://ani.pm";
|
|
4
|
+
|
|
5
|
+
function cleanTitle(t) { return t ? t.toLowerCase().replace(/[^a-z0-9]/g, '') : ''; }
|
|
6
|
+
|
|
7
|
+
async function searchCatalog(query) {
|
|
8
|
+
try {
|
|
9
|
+
const data = await fetchJson(`${BASE_URL}/api/anime/catalog?q=${encodeURIComponent(query)}&page=1`, { signal: AbortSignal.timeout(6000) });
|
|
10
|
+
return Array.isArray(data?.items) ? data.items : [];
|
|
11
|
+
} catch { return []; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function resolveEntry(titles, year) {
|
|
15
|
+
const orderedTitles = titles.length > 1 ? [...titles].reverse() : titles;
|
|
16
|
+
let best = null, bestScore = -1;
|
|
17
|
+
for (const title of orderedTitles) {
|
|
18
|
+
const items = await searchCatalog(title);
|
|
19
|
+
const targetClean = cleanTitle(title);
|
|
20
|
+
for (const item of items) {
|
|
21
|
+
const itemTitles = [item.title, item.native].filter(Boolean).map(cleanTitle);
|
|
22
|
+
let score = 0;
|
|
23
|
+
if (itemTitles.some(t => t === targetClean)) score += 5;
|
|
24
|
+
else if (itemTitles.some(t => t.includes(targetClean) || targetClean.includes(t))) score += 3;
|
|
25
|
+
else continue;
|
|
26
|
+
if (item.type === 'TV') score += 4;
|
|
27
|
+
else if (item.type === 'Movie' || item.type === 'Special' || item.type === 'ONA') score -= 2;
|
|
28
|
+
if (year && item.year) {
|
|
29
|
+
const diff = Math.abs(item.year - year);
|
|
30
|
+
if (diff === 0) score += 3;
|
|
31
|
+
else if (diff === 1) score += 1;
|
|
32
|
+
else if (diff > 2) score -= 3;
|
|
33
|
+
}
|
|
34
|
+
if (score > bestScore) { bestScore = score; best = item; }
|
|
35
|
+
}
|
|
36
|
+
if (bestScore >= 9) break;
|
|
37
|
+
}
|
|
38
|
+
return best;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function getStream({ id, s, e, audio }) {
|
|
42
|
+
try {
|
|
43
|
+
const info = await getTmdbInfo(id, s ? 'tv' : 'movie', s);
|
|
44
|
+
if (!info || !info.isAnime) return null;
|
|
45
|
+
const entry = await resolveEntry(info.titles, info.year);
|
|
46
|
+
if (!entry) return null;
|
|
47
|
+
const episodeNum = e || 1;
|
|
48
|
+
const params = new URLSearchParams({ title: entry.title || '', ep: String(episodeNum) });
|
|
49
|
+
if (entry.year) params.set('year', entry.year);
|
|
50
|
+
if (entry.anilistId) params.set('anilistId', entry.anilistId);
|
|
51
|
+
if (entry.malId) params.set('malId', entry.malId);
|
|
52
|
+
const servers = await fetchJson(`${BASE_URL}/api/anime/src/servers?${params}`, { signal: AbortSignal.timeout(10000) });
|
|
53
|
+
if (!servers) return null;
|
|
54
|
+
const audiosToTry = audio === "all" ? ["sub", "dub"] : (audio === "dub" ? ["dub", "sub"] : ["sub", "dub"]);
|
|
55
|
+
for (const aud of audiosToTry) {
|
|
56
|
+
const list = servers[aud];
|
|
57
|
+
if (!Array.isArray(list) || !list.length) continue;
|
|
58
|
+
const allUrls = list.filter(srv => (srv.kind === "hls" || srv.kind === "file") && srv.url)
|
|
59
|
+
.sort((a, b) => (b.priority || 0) - (a.priority || 0))
|
|
60
|
+
.map(srv => ({ url: srv.url.startsWith('/') ? `${BASE_URL}${srv.url}` : srv.url, type: srv.kind === "hls" ? "hls" : "mp4", audio: aud, server: srv.provider ? `AniPM-${srv.provider}` : "AniPM", headers: { Referer: `${BASE_URL}/`, Origin: BASE_URL }, skipProxy: false }));
|
|
61
|
+
if (allUrls.length) return { allUrls };
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
} catch { return null; }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function getSources(args) {
|
|
68
|
+
const stream = await getStream(args);
|
|
69
|
+
return stream?.allUrls ? [...new Set(stream.allUrls.map(u => u.server))] : [];
|
|
70
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { fetchJson } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const API_BASE = 'https://itjiocunahckqxcnzpoy.supabase.co/functions/v1';
|
|
4
|
+
const APIKEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Iml0amlvY3VuYWhja3F4Y256cG95Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzczMzM1MjQsImV4cCI6MjA5MjkwOTUyNH0.9x9ykdHAzrv_GSvnawPeQaOxQeh3sZg0QAh4u9VOF4M';
|
|
5
|
+
const HEADERS = { 'apikey': APIKEY, 'authorization': `Bearer ${APIKEY}`, 'content-type': 'application/json', 'origin': 'https://flaxmovies.xyz', 'referer': 'https://flaxmovies.xyz/' };
|
|
6
|
+
|
|
7
|
+
export async function getStream({ id, s, e }) {
|
|
8
|
+
try {
|
|
9
|
+
const isMovie = !s;
|
|
10
|
+
const data = await fetchJson(isMovie ? `${API_BASE}/get-movie` : `${API_BASE}/get-tv`, { method: 'POST', headers: HEADERS, body: JSON.stringify(isMovie ? { id: String(id) } : { id: String(id), season: Number(s), episode: Number(e) }), signal: AbortSignal.timeout(15000) });
|
|
11
|
+
if (!data?.signed_url) return null;
|
|
12
|
+
return { allUrls: [{ url: data.signed_url, headers: { 'Referer': 'https://flaxmovies.xyz/', 'Origin': 'https://flaxmovies.xyz' } }] };
|
|
13
|
+
} catch { return null; }
|
|
14
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { fetchText, fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = 'https://fsharetv.cc';
|
|
4
|
+
const HEADERS = { 'User-Agent': USER_AGENT, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9', 'Referer': `${BASE_URL}/` };
|
|
5
|
+
const API_HEADERS = { ...HEADERS, 'Accept': 'application/json, */*; q=0.01', 'X-Requested-With': 'XMLHttpRequest' };
|
|
6
|
+
|
|
7
|
+
export async function getStream({ id, s }) {
|
|
8
|
+
if (s != null) return null;
|
|
9
|
+
try {
|
|
10
|
+
const info = await getTmdbInfo(id, 'movie');
|
|
11
|
+
if (!info?.imdbId) return null;
|
|
12
|
+
const html = await fetchText(`${BASE_URL}/movie/${info.imdbId}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
|
|
13
|
+
let watchPath = null;
|
|
14
|
+
const match = html.match(/href="(\/w\/[^"]+)"/);
|
|
15
|
+
if (match) watchPath = match[1];
|
|
16
|
+
if (!watchPath) return null;
|
|
17
|
+
const watchHtml = await fetchText(`${BASE_URL}${watchPath}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
|
|
18
|
+
let sourceId = null;
|
|
19
|
+
const sidMatch = watchHtml.match(/(?:source_id|file_id|setSource)[\s=:\('"]+([^'"\)]+)/i);
|
|
20
|
+
if (sidMatch) sourceId = sidMatch[1];
|
|
21
|
+
if (!sourceId) return null;
|
|
22
|
+
const json = await fetchJson(`${BASE_URL}/api/file/${sourceId}/source?trailer=Png81APqcxU&type=watch`, { headers: { ...API_HEADERS, 'Referer': `${BASE_URL}${watchPath}` }, signal: AbortSignal.timeout(8000) });
|
|
23
|
+
if (json.status !== 'ok') return null;
|
|
24
|
+
const groups = [];
|
|
25
|
+
if (Array.isArray(json.data?.file?.sources)) groups.push(json.data.file.sources);
|
|
26
|
+
if (Array.isArray(json.data?.file?.alternatives)) json.data.file.alternatives.forEach(g => Array.isArray(g) && groups.push(g));
|
|
27
|
+
const out = [], seen = new Set();
|
|
28
|
+
for (const group of groups) {
|
|
29
|
+
for (const item of group.filter(st => st?.src).sort((a, b) => parseInt(b.quality) - parseInt(a.quality))) {
|
|
30
|
+
const url = item.src.startsWith('http') ? item.src : `${BASE_URL}${item.src}`;
|
|
31
|
+
if (seen.has(url)) continue;
|
|
32
|
+
seen.add(url);
|
|
33
|
+
out.push({ url, quality: parseInt(item.quality) || 0, label: item.label || null });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
out.sort((a, b) => b.quality - a.quality);
|
|
37
|
+
return out.length ? { allUrls: out.map(u => ({ url: u.url, headers: HEADERS, skipProxy: false })) } : null;
|
|
38
|
+
} catch { return null; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function getSources(args) {
|
|
42
|
+
const stream = await getStream(args);
|
|
43
|
+
return stream?.allUrls ? stream.allUrls.map(u => u.url) : [];
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { fetchText, fetchJson, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = 'https://www.fsonic.net';
|
|
4
|
+
const FSHARE_BASE = 'https://fsharetv.co';
|
|
5
|
+
const HEADERS = { 'User-Agent': USER_AGENT, 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.9' };
|
|
6
|
+
|
|
7
|
+
export async function getStream({ id, s }) {
|
|
8
|
+
if (s != null) return null;
|
|
9
|
+
try {
|
|
10
|
+
const info = await getTmdbInfo(id, 'movie');
|
|
11
|
+
if (!info?.titles?.length) return null;
|
|
12
|
+
let watchSlug = null;
|
|
13
|
+
for (const title of info.titles) {
|
|
14
|
+
try {
|
|
15
|
+
const html = await fetchText(`${BASE_URL}/movie/search/${encodeURIComponent(title)}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
|
|
16
|
+
let idx = 0, found = null;
|
|
17
|
+
const yearStr = String(info.year);
|
|
18
|
+
while ((idx = html.indexOf('href="/watch/', idx)) !== -1) {
|
|
19
|
+
const start = idx + 6;
|
|
20
|
+
const end = html.indexOf('"', start);
|
|
21
|
+
const link = html.slice(start, end);
|
|
22
|
+
if (!found) found = link;
|
|
23
|
+
if (link.includes(yearStr)) { found = link; break; }
|
|
24
|
+
idx = end;
|
|
25
|
+
}
|
|
26
|
+
if (found) { watchSlug = found; break; }
|
|
27
|
+
} catch { }
|
|
28
|
+
}
|
|
29
|
+
if (!watchSlug) return null;
|
|
30
|
+
const wHtml = await fetchText(`${BASE_URL}${watchSlug}`, { headers: HEADERS, signal: AbortSignal.timeout(8000) });
|
|
31
|
+
let token = null, trailer = null;
|
|
32
|
+
const match = wHtml.match(/init\('([^']+)',\s*(?:'[^']*',\s*)?'([^']+)'/);
|
|
33
|
+
if (match) { token = match[1]; trailer = match[2]; }
|
|
34
|
+
if (!token) return null;
|
|
35
|
+
const json = await fetchJson(`${BASE_URL}/api/source/${token}?trailer=${trailer}&type=watch`, { headers: { ...HEADERS, 'Accept': 'application/json, text/plain, */*', 'Referer': `${BASE_URL}${watchSlug}` }, signal: AbortSignal.timeout(8000) });
|
|
36
|
+
if (json.status !== 'ok') return null;
|
|
37
|
+
const allGroups = [];
|
|
38
|
+
if (json.data?.file?.sources?.length) allGroups.push(json.data.file.sources);
|
|
39
|
+
if (json.data?.file?.alternatives) for (const g of json.data.file.alternatives) if (g?.length) allGroups.push(g);
|
|
40
|
+
const urls = new Set();
|
|
41
|
+
for (const group of allGroups) {
|
|
42
|
+
const best = [...group].filter(st => st?.src).sort((a, b) => parseInt(b.quality) - parseInt(a.quality))[0];
|
|
43
|
+
if (best) urls.add(best.src.startsWith('http') ? best.src : `${FSHARE_BASE}${best.src}`);
|
|
44
|
+
}
|
|
45
|
+
return urls.size ? { allUrls: [...urls].map(url => ({ url, headers: { ...HEADERS, 'Referer': `${FSHARE_BASE}/` }, skipProxy: false })) } : null;
|
|
46
|
+
} catch { return null; }
|
|
47
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { fetchText, USER_AGENT, getTmdbInfo } from '../utils/helpers.js';
|
|
2
|
+
|
|
3
|
+
const ORIGIN = 'https://www3.fsonline.app';
|
|
4
|
+
const AJAX_URL = `${ORIGIN}/wp-admin/admin-ajax.php`;
|
|
5
|
+
const HEADERS = { 'User-Agent': USER_AGENT, 'Origin': ORIGIN, 'Referer': `${ORIGIN}/` };
|
|
6
|
+
|
|
7
|
+
export async function getStream({ id, s, e }) {
|
|
8
|
+
try {
|
|
9
|
+
const isTv = s != null && e != null;
|
|
10
|
+
const info = await getTmdbInfo(id, isTv ? 'tv' : 'movie');
|
|
11
|
+
if (!info?.titles?.length) return null;
|
|
12
|
+
const searchHtml = await fetchText(`${ORIGIN}/?s=${encodeURIComponent(info.titles[0] + (info.year ? ' ' + info.year : ''))}`, { headers: HEADERS });
|
|
13
|
+
const typeFolder = isTv ? 'seriale' : 'film';
|
|
14
|
+
const linkMatch = searchHtml.match(new RegExp(`href=["'](https?://www3\\.fsonline\\.app/${typeFolder}/([^"'/]+)/)["']`, 'i'));
|
|
15
|
+
if (!linkMatch) return null;
|
|
16
|
+
const targetPageUrl = isTv ? `${ORIGIN}/episoade/${linkMatch[2].replace(/-\d{4}$/, '')}-sezonul-${s}-episodul-${e}/` : linkMatch[1];
|
|
17
|
+
const pageHtml = await fetchText(targetPageUrl, { headers: HEADERS });
|
|
18
|
+
const movieId = (pageHtml.match(/movie-id=['"]([^'"]+)['"]/) || pageHtml.match(/movie-id=([^ >]+)/))?.[1];
|
|
19
|
+
if (!movieId) return null;
|
|
20
|
+
const ajaxHtml = await fetchText(AJAX_URL, { method: 'POST', headers: { ...HEADERS, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': targetPageUrl }, body: `action=lazy_player&movieID=${movieId}` });
|
|
21
|
+
const results = [];
|
|
22
|
+
let idx = 0;
|
|
23
|
+
while ((idx = ajaxHtml.indexOf('data-vs="', idx)) !== -1) {
|
|
24
|
+
const embedStart = idx + 9;
|
|
25
|
+
const embedEnd = ajaxHtml.indexOf('"', embedStart);
|
|
26
|
+
const embedUrl = ajaxHtml.slice(embedStart, embedEnd);
|
|
27
|
+
const spanStart = ajaxHtml.indexOf('<span>', embedEnd);
|
|
28
|
+
const spanEnd = ajaxHtml.indexOf('</span>', spanStart);
|
|
29
|
+
const serverLabel = ajaxHtml.slice(spanStart + 6, spanEnd).trim().toLowerCase();
|
|
30
|
+
if (serverLabel.includes('filesun')) {
|
|
31
|
+
try {
|
|
32
|
+
const rHtml = await fetchText(embedUrl, { headers: { 'Referer': ORIGIN, 'User-Agent': USER_AGENT } });
|
|
33
|
+
const m3u8Match = rHtml.match(/file:\s*["'](https?:\/\/[^"']+\.m3u8[^"']*)["']/) || rHtml.match(/["']?file["']?:\s*["'](https?:\/\/[^"']+)["']/);
|
|
34
|
+
if (m3u8Match) results.push({ url: m3u8Match[1].replace(/\\\//g, '/'), server: 'FSOnline - FileSuN', headers: { 'Referer': embedUrl, 'Origin': 'https://player.fsonline.app', 'User-Agent': USER_AGENT } });
|
|
35
|
+
} catch { }
|
|
36
|
+
}
|
|
37
|
+
idx = spanEnd;
|
|
38
|
+
}
|
|
39
|
+
return results.length ? { allUrls: results } : null;
|
|
40
|
+
} catch { return null; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getSources(args) {
|
|
44
|
+
const res = await getStream(args);
|
|
45
|
+
return res?.allUrls ? [...new Set(res.allUrls.map(u => u.server))] : [];
|
|
46
|
+
}
|