Compare commits
366 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 400cf96447 | |||
| 059bc19173 | |||
| 74f821afd0 | |||
| 6ce219db8d | |||
| 0d335470ea | |||
| 0c67e8db83 | |||
| 6307f70497 | |||
| 1358cbbfff | |||
| cd001b9eab | |||
| 02147221e8 | |||
| aa6a0dd3f9 | |||
| 61d2d36b69 | |||
| 8c689ff476 | |||
| 5cf145e516 | |||
| 8ac56b2787 | |||
| 8c448c853e | |||
| 4062b4a7be | |||
| 9b18dd39d6 | |||
| eb3154f765 | |||
| 9be224e467 | |||
| ebfcd4d6dc | |||
| d7146f94da | |||
| 64ef556d75 | |||
| 597f279e49 | |||
| caffaabc88 | |||
| 4fbb2bc138 | |||
| 01b25ec53d | |||
| c4fccf85bd | |||
| aa56034bf7 | |||
| 6400b9c508 | |||
| 92fa874883 | |||
| fab874dbc5 | |||
| c2efefaf0e | |||
| 11d6986232 | |||
| 4bbde44022 | |||
| 7cced4311b | |||
| e324070a32 | |||
| 7b038968f3 | |||
| 837659590b | |||
| db7958ff83 | |||
| b9472ac7a2 | |||
| 6d987a6242 | |||
| 5a4ff66443 | |||
| 286041b093 | |||
| d1e170cfd4 | |||
| d55b0dbcfc | |||
| af610ae9c5 | |||
| 3c756e2890 | |||
| 5a83892bc1 | |||
| a651afa1ba | |||
| 951663b6d0 | |||
| 7b03037a59 | |||
| 388f6f4991 | |||
| b6d9e70dc7 | |||
| 3b720ebd25 | |||
| efa598937b | |||
| 6f0a381e77 | |||
| db52efdb5b | |||
| f7e4efd254 | |||
| a853ec6aee | |||
| c10e3b71b7 | |||
| 84a8284a50 | |||
| cbabd0643e | |||
| 7a2c05abb6 | |||
| 5a8c5cbb62 | |||
| 3d44fa0bb2 | |||
| 1098637c15 | |||
| 7d4e12986b | |||
| 904a711918 | |||
| 2c2d6b05a1 | |||
| 72cb66f38a | |||
| d009e3ed98 | |||
| c2b6118abc | |||
| 9546ddee03 | |||
| 03f3bc6ffc | |||
| 17b3606d4f | |||
| 5605f80b0f | |||
| 9643b4a963 | |||
| 58788a8cf2 | |||
| 4ad76c5519 | |||
| d56e8784f6 | |||
| c3dcbceefb | |||
| 29c1eeb15e | |||
| e5f71d3e0f | |||
| b3c073a4f6 | |||
| 81ef2f9398 | |||
| 1ced6f7e76 | |||
| 85e42df413 | |||
| 79990ff562 | |||
| ab217a7b5a | |||
| b1675936a6 | |||
| dd92aea52e | |||
| 284414a994 | |||
| ea29804e86 | |||
| 6f9b7112ec | |||
| cddda574cc | |||
| 98ae0a541b | |||
| 5e26b3b171 | |||
| 352b88b030 | |||
| 441dbbd9a2 | |||
| 611cbc8e1a | |||
| 2fc80e1206 | |||
| e4345774bd | |||
| 6acea93a3c | |||
| 58f398383e | |||
| e2bc89511c | |||
| 19ffb8254b | |||
| 484e9b20e4 | |||
| 2e823488d5 | |||
| f700830e2f | |||
| 14f0aea33d | |||
| 35986ceb2b | |||
| 8a7a8f3a6a | |||
| df457e7ade | |||
| f8a332fdb7 | |||
| aff574469c | |||
| 71e8747998 | |||
| b856529fce | |||
| d09299eea2 | |||
| cbdac19e16 | |||
| 78992bbd25 | |||
| a32db404cd | |||
| 207dfbcac9 | |||
| 0149e192d4 | |||
| 0c4b055562 | |||
| 6f51c9a900 | |||
| d4c72a54ee | |||
| cfac44b405 | |||
| e2413ee76c | |||
| c8092410a5 | |||
| c477fb7bb0 | |||
| 2a3817f04f | |||
| cb3aa79a4b | |||
| a9963bf9a1 | |||
| 68850c048e | |||
| b00be4f74d | |||
| a88ed329a5 | |||
| 5c34fb248b | |||
| 1c0d5e9b09 | |||
| a48b98ead1 | |||
| 6e8aabff02 | |||
| 8f7d0c3b10 | |||
| 6b97575731 | |||
| a7ccb640d4 | |||
| b6a8c394f6 | |||
| d160cb3e50 | |||
| ccbe5d5233 | |||
| 8318259db4 | |||
| 108227235c | |||
| 2ea1a3f27d | |||
| f94d9548a6 | |||
| 1f45c6d93c | |||
| 20636e80cc | |||
| 7c53568b2f | |||
| ee0fa333ce | |||
| b237d707e4 | |||
| 2e85203aed | |||
| b8517d88b5 | |||
| 5df41e8fc0 | |||
| 7362a5ddad | |||
| 083098d20e | |||
| bf095bc654 | |||
| 007c21a192 | |||
| 9e550f6517 | |||
| 5fbae69eac | |||
| 892c83e5b0 | |||
| 81af6b15df | |||
| e2d0051863 | |||
| e7c8fc8e68 | |||
| fd1bfed288 | |||
| d34df3a2f1 | |||
| e1eb629ce9 | |||
| 2250aca5d2 | |||
| 3c2b21f4d3 | |||
| 7de95ae00a | |||
| dc57820a54 | |||
| f3d337eb8d | |||
| 8dc4461655 | |||
| e8f11a3eb8 | |||
| 41e2330293 | |||
| 10d57783dd | |||
| 984881911e | |||
| 76c135dab9 | |||
| 92fec8ace8 | |||
| c2f7b20f36 | |||
| 5a0934e1bf | |||
| be3ad0e7e0 | |||
| 9c21adde0f | |||
| 4086596664 | |||
| 38abfb0145 | |||
| 0b31d1a086 | |||
| b6ebc52403 | |||
| 3e0ffb51d0 | |||
| b10b3435ef | |||
| 74df9103f7 | |||
| 494f820496 | |||
| ecc07a4c8c | |||
| 9f36faa961 | |||
| 8c0d6a9c3d | |||
| eb9ae7f6ed | |||
| d4ba0bca7f | |||
| fc26da62d1 | |||
| 534c66f496 | |||
| 1899c113e6 | |||
| 879a962872 | |||
| 34dc12f3c7 | |||
| b9dd2c370f | |||
| 85b2c8c73a | |||
| c46815ca85 | |||
| 554e323dde | |||
| 0d5b0b588a | |||
| 6661660310 | |||
| 7d4d3f90e9 | |||
| 7bba0e50f5 | |||
| 484bd2dbc2 | |||
| 7824ca4508 | |||
| fa3a84eb4a | |||
| 1b3c5ab3c6 | |||
| 85328a8b5c | |||
| 53cedeb8da | |||
| 44484f82c9 | |||
| 10d6691a07 | |||
| 243bf022b2 | |||
| aa11a6811d | |||
| 3c48260b8d | |||
| 1d011529f9 | |||
| d0bae7b61c | |||
| 2baadb69de | |||
| 25456bb152 | |||
| df16c09601 | |||
| 90277d5789 | |||
| a99e9a0a36 | |||
| 2049f57dc3 | |||
| 923467c19a | |||
| 866685c31d | |||
| fa1dd967eb | |||
| 58526a1c72 | |||
| 96e4a65b5a | |||
| e81bffe073 | |||
| edaf38b50e | |||
| be4616bfca | |||
| e0436ccea9 | |||
| 887e32bae7 | |||
| 1cd2eabe65 | |||
| fae1acab94 | |||
| d35b83e61d | |||
| 925c657e8e | |||
| ab785225d2 | |||
| e7e7b98445 | |||
| 613d297a79 | |||
| c287215dfb | |||
| 38252cef80 | |||
| 3d67182e5b | |||
| 00b1292625 | |||
| 6df3f100cc | |||
| 75536650ac | |||
| 4ecc55e778 | |||
| 09109d880c | |||
| 5378fd54bc | |||
| 4cd946aa4a | |||
| f08ad16a56 | |||
| 05fa125880 | |||
| 26d94d6d65 | |||
| 0456891ac1 | |||
| 915b9c93e8 | |||
| 8a06300b26 | |||
| 6ec67bcfb8 | |||
| cafc9dea38 | |||
| 1569479457 | |||
| 3d8d90c33e | |||
| 7296e211b7 | |||
| 6435ab818b | |||
| ef7d8b4485 | |||
| 0d2111dba1 | |||
| 51b4227631 | |||
| a2df765da5 | |||
| faf20a23f7 | |||
| 4f17b819f3 | |||
| a713e7ec09 | |||
| 100d6dabbb | |||
| cc1733742e | |||
| bd16389dc8 | |||
| a4269d7b8b | |||
| b49fcac090 | |||
| b19d0f23b7 | |||
| 44ca65cf6c | |||
| c259f310ed | |||
| fe5c007bb9 | |||
| c5e06aa55c | |||
| c686cfbab3 | |||
| a32c786156 | |||
| 0b00de663c | |||
| e003f58b44 | |||
| b9eb0166cc | |||
| 8d10fefb7e | |||
| 0c9db9005f | |||
| dc859c0dbf | |||
| e90e56a163 | |||
| fbbed00c89 | |||
| 1e56c7c785 | |||
| e2ef7a93ec | |||
| c76c6ccfb2 | |||
| b29f9b9336 | |||
| 9287773e75 | |||
| 2bd6ed54a1 | |||
| 007dedb72c | |||
| d4a687b5bc | |||
| 773bf76c33 | |||
| 5442db82c3 | |||
| 35bb4daaf2 | |||
| 92f3a20556 | |||
| 71f7d30321 | |||
| 8f84613441 | |||
| c943ee0e2a | |||
| 01f247a38f | |||
| 4549876366 | |||
| a3f5d391fb | |||
| e4362cfb95 | |||
| aebf2b3e06 | |||
| e21a030392 | |||
| db51e71e74 | |||
| ffce779935 | |||
| 0ac092ef30 | |||
| ac3a928325 | |||
| 3c1b8650a5 | |||
| db30644f7f | |||
| 0851988b8d | |||
| dc420790e6 | |||
| 5f64c89c7a | |||
| ffa786c01e | |||
| 548d822cd4 | |||
| cb1a96f2f7 | |||
| 1c652aa064 | |||
| a0adb99585 | |||
| dbd558ba53 | |||
| fadb2e796a | |||
| 186fd09c30 | |||
| 350ce531b1 | |||
| 11b0a3d928 | |||
| f5c5564ca6 | |||
| 1fa493a0b9 | |||
| a7694851e8 | |||
| 3e98c074da | |||
| 739a503c60 | |||
| 757916982b | |||
| 2ec41daa2a | |||
| b20283d1bc | |||
| a68dd1d709 | |||
| caee19ed05 | |||
| a75bbb782e | |||
| 5fdb173990 | |||
| 9fec9d8fff | |||
| 62135e381b | |||
| b8f7930684 | |||
| 419994f016 | |||
| f59a8b3a40 | |||
| e22faebe94 | |||
| cfaf9a7bab | |||
| 4c9db45dac | |||
| 3be93d7509 | |||
| 93a3a1a48d | |||
| 7b097156e0 | |||
| 836f22b348 | |||
| 8e6c64630a | |||
| cd54a8935f | |||
| 2508a024de |
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "111477",
|
||||||
|
"iconUrl": "https://media.tenor.com/tIOhF5a8McEAAAAe/heart-emoji-love-nonchalant.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Multi Language",
|
||||||
|
"streamType": "MKV",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://a.111477.xyz/",
|
||||||
|
"searchBaseUrl": "https://a.111477.xyz/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/111477/111477.js",
|
||||||
|
"type": "shows/movies",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"note": "USE AN EXTERNAL PLAYER (E.G., VLC/MPV)",
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ async function searchResults(query) {
|
|||||||
if (fullHref && imageSrc && cleanTitle) {
|
if (fullHref && imageSrc && cleanTitle) {
|
||||||
results.push({
|
results.push({
|
||||||
href: fullHref,
|
href: fullHref,
|
||||||
image: imageSrc,
|
image: "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(imageSrc),
|
||||||
title: cleanTitle
|
title: cleanTitle
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ async function searchResults(query) {
|
|||||||
`${searchBaseUrl}${encodedQuery}&page=3`
|
`${searchBaseUrl}${encodedQuery}&page=3`
|
||||||
];
|
];
|
||||||
|
|
||||||
const responses = await Promise.all(urls.map(url => fetchv2(url)));
|
const responses = await Promise.all(urls.map(url => fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url))));
|
||||||
|
|
||||||
const htmlTexts = await Promise.all(responses.map(response => response.text()));
|
const htmlTexts = await Promise.all(responses.map(response => response.text()));
|
||||||
|
|
||||||
@@ -60,9 +60,9 @@ async function searchResults(query) {
|
|||||||
href: "",
|
href: "",
|
||||||
image: "",
|
image: "",
|
||||||
title: "Search failed: " + error.message
|
title: "Search failed: " + error.message
|
||||||
}]);
|
}]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async function extractDetails(url) {
|
async function extractDetails(url) {
|
||||||
try {
|
try {
|
||||||
@@ -128,6 +128,11 @@ async function extractEpisodes(movieUrl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function extractStreamUrl(url) {
|
async function extractStreamUrl(url) {
|
||||||
|
const headers = {
|
||||||
|
"Referer": "https://1movies.bz/",
|
||||||
|
"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"
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const eidMatch = url.match(/eid=([^&]+)/);
|
const eidMatch = url.match(/eid=([^&]+)/);
|
||||||
if (eidMatch && eidMatch[1]) {
|
if (eidMatch && eidMatch[1]) {
|
||||||
@@ -138,31 +143,25 @@ async function extractStreamUrl(url) {
|
|||||||
url = url.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
|
url = url.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchUrl = `${url}`;
|
const response = await fetchv2(url);
|
||||||
const response = await fetchv2(fetchUrl);
|
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
const cleanedHtml = cleanJsonHtml(responseData.result);
|
const cleanedHtml = cleanJsonHtml(responseData.result);
|
||||||
|
|
||||||
const server1Regex = /<div class="server wnav-item"[^>]*data-lid="([^"]+)"[^>]*>\s*<span>Server 1<\/span>/;
|
const spanRegex = /<div class="server wnav-item"[^>]*data-lid="([^"]+)"[^>]*>/g;
|
||||||
const server1Match = server1Regex.exec(cleanedHtml);
|
const ids = [];
|
||||||
|
let match;
|
||||||
|
while ((match = spanRegex.exec(cleanedHtml)) !== null) ids.push(match[1]);
|
||||||
|
|
||||||
if (!server1Match) {
|
if (ids.length === 0) {
|
||||||
console.log("Server 1 not found");
|
console.log("No servers found");
|
||||||
return "error";
|
return "error";
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverId = server1Match[1];
|
const serverId = ids.length > 1 ? ids[1] : ids[0];
|
||||||
|
|
||||||
const tokenRequestData = [{ name: "Server1", data: serverId }];
|
const tokenData = await fetchv2(`https://enc-dec.app/api/enc-movies-flix?text=${encodeURIComponent(serverId)}`)
|
||||||
|
.then(res => res.json());
|
||||||
const tokenBatchResponse = await fetchv2(
|
const token = tokenData.result;
|
||||||
"https://ilovekai.simplepostrequest.workers.dev/ilovethighs",
|
|
||||||
{},
|
|
||||||
"POST",
|
|
||||||
JSON.stringify(tokenRequestData)
|
|
||||||
);
|
|
||||||
const tokenResults = await tokenBatchResponse.json();
|
|
||||||
const token = tokenResults[0]?.data;
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
console.log("Token not found");
|
console.log("Token not found");
|
||||||
@@ -178,45 +177,53 @@ async function extractStreamUrl(url) {
|
|||||||
return "error";
|
return "error";
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptRequestData = [{ name: "Server1", data: streamData.result }];
|
const decryptData = await fetchv2(
|
||||||
|
`https://enc-dec.app/api/dec-movies-flix?text=${streamData.result}`,
|
||||||
|
headers
|
||||||
|
).then(res => res.json());
|
||||||
|
|
||||||
const decryptBatchResponse = await fetchv2(
|
const decryptedUrl = decryptData.result?.url;
|
||||||
"https://ilovekai.simplepostrequest.workers.dev/iloveboobs",
|
|
||||||
{},
|
|
||||||
"POST",
|
|
||||||
JSON.stringify(decryptRequestData)
|
|
||||||
);
|
|
||||||
const decryptedResponse = await decryptBatchResponse.json();
|
|
||||||
const decryptedUrl = decryptedResponse[0]?.data.url;
|
|
||||||
|
|
||||||
const subListEncoded = decryptedUrl.split("sub.list=")[1]?.split("&")[0];
|
|
||||||
let subtitles = "N/A";
|
|
||||||
|
|
||||||
if (subListEncoded) {
|
|
||||||
try {
|
|
||||||
const subListUrl = decodeURIComponent(subListEncoded);
|
|
||||||
const subResponse = await fetchv2(subListUrl);
|
|
||||||
subtitles = await subResponse.json();
|
|
||||||
} catch {
|
|
||||||
subtitles = "N/A";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const englishSubUrl = Array.isArray(subtitles)
|
|
||||||
? subtitles.find(sub => sub.label === "English")?.file.replace(/\\\//g, "/")
|
|
||||||
: "N/A";
|
|
||||||
|
|
||||||
if (!decryptedUrl) {
|
if (!decryptedUrl) {
|
||||||
console.log("Decryption failed");
|
console.log("Decryption failed");
|
||||||
return "error";
|
return "error";
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = {
|
const subListEncoded = decryptedUrl.split("sub.list=")[1]?.split("&")[0];
|
||||||
"Referer": "https://1movies.bz/",
|
let subtitles = "N/A";
|
||||||
"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"
|
if (subListEncoded) {
|
||||||
};
|
try {
|
||||||
|
const subListUrl = decodeURIComponent(subListEncoded);
|
||||||
|
const subResponse = await fetchv2(subListUrl);
|
||||||
|
subtitles = await subResponse.json();
|
||||||
|
} catch {
|
||||||
|
subtitles = "N/A";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const mediaResponse = await fetchv2(decryptedUrl.replace("/e/", "/media/"), headers);
|
const englishSubUrl = Array.isArray(subtitles)
|
||||||
|
? subtitles.find(sub => sub.label === "English")?.file.replace(/\\\//g, "/")
|
||||||
|
: "N/A";
|
||||||
|
|
||||||
|
let finalUrl = decryptedUrl;
|
||||||
|
if (finalUrl.includes(".to/iframe")) {
|
||||||
|
try {
|
||||||
|
const iframeResponse = await fetchv2(finalUrl, headers);
|
||||||
|
const iframeHtml = await iframeResponse.text();
|
||||||
|
const iframeSrcMatch = iframeHtml.match(/<iframe[^>]+src="([^"]+)"/i);
|
||||||
|
if (iframeSrcMatch && iframeSrcMatch[1]) {
|
||||||
|
finalUrl = iframeSrcMatch[1];
|
||||||
|
} else {
|
||||||
|
console.log("No iframe src found in:", finalUrl);
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error fetching iframe:", e);
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaResponse = await fetchv2(finalUrl.replace("/e/", "/media/"), headers);
|
||||||
const mediaJson = await mediaResponse.json();
|
const mediaJson = await mediaResponse.json();
|
||||||
|
|
||||||
const result = mediaJson?.result;
|
const result = mediaJson?.result;
|
||||||
@@ -225,55 +232,73 @@ async function extractStreamUrl(url) {
|
|||||||
return "error";
|
return "error";
|
||||||
}
|
}
|
||||||
|
|
||||||
const postData = {
|
const finalJson = await fetchv2(
|
||||||
"text": result,
|
"https://enc-dec.app/api/dec-mega",
|
||||||
"Useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
|
{ "Content-Type": "application/json" },
|
||||||
};
|
"POST",
|
||||||
|
JSON.stringify({ text: result, agent: headers["User-Agent"] })
|
||||||
|
).then(res => res.json());
|
||||||
|
console.log("Final JSON: " + JSON.stringify(finalJson));
|
||||||
|
const m3u8Link = finalJson?.result?.sources?.[0]?.file;
|
||||||
|
|
||||||
const finalResponse = await fetchv2("https://ilovekai.simplepostrequest.workers.dev/ilovebush", {}, "POST", JSON.stringify(postData));
|
const streams = [];
|
||||||
const finalJson = await finalResponse.json();
|
if (m3u8Link) {
|
||||||
|
let pushedQualities = 0;
|
||||||
|
try {
|
||||||
|
const m3u8Response = await fetchv2("https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(m3u8Link));
|
||||||
|
const m3u8Text = await m3u8Response.text();
|
||||||
|
const lines = m3u8Text.split('\n');
|
||||||
|
|
||||||
const m3u8Link = finalJson?.result?.sources?.[0]?.file;
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||||
|
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
||||||
|
let quality = 'Unknown';
|
||||||
|
|
||||||
const m3u8Response = await fetchv2(m3u8Link);
|
if (resolutionMatch) {
|
||||||
const m3u8Text = await m3u8Response.text();
|
const [width, height] = resolutionMatch[1].split('x');
|
||||||
|
quality = `${height}p`;
|
||||||
|
}
|
||||||
|
|
||||||
const baseUrl = m3u8Link.substring(0, m3u8Link.lastIndexOf('/') + 1);
|
if (i + 1 < lines.length) {
|
||||||
|
let streamPath = lines[i + 1].trim();
|
||||||
const streams = [];
|
let absolutePath;
|
||||||
const lines = m3u8Text.split('\n');
|
if (streamPath.startsWith('/api/')) {
|
||||||
|
absolutePath = 'https://1anime.app' + streamPath;
|
||||||
for (let i = 0; i < lines.length; i++) {
|
} else if (streamPath.startsWith('http')) {
|
||||||
const line = lines[i].trim();
|
absolutePath = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(streamPath);
|
||||||
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
} else {
|
||||||
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
const baseUrl = m3u8Link.substring(0, m3u8Link.lastIndexOf('/') + 1);
|
||||||
let quality = 'Unknown';
|
absolutePath = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(baseUrl + streamPath);
|
||||||
|
}
|
||||||
if (resolutionMatch) {
|
streams.push({
|
||||||
const [width, height] = resolutionMatch[1].split('x');
|
title: quality,
|
||||||
quality = `${height}p`;
|
streamUrl: absolutePath
|
||||||
}
|
});
|
||||||
|
pushedQualities++;
|
||||||
if (i + 1 < lines.length) {
|
}
|
||||||
const streamPath = lines[i + 1].trim();
|
|
||||||
const streamUrl = baseUrl + streamPath;
|
|
||||||
|
|
||||||
streams.push({
|
|
||||||
title: quality,
|
|
||||||
streamUrl: streamUrl
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to extract qualities:", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
const returnValue = {
|
if (pushedQualities === 0) {
|
||||||
streams: streams,
|
streams.push({
|
||||||
subtitle: englishSubUrl !== "N/A" ? englishSubUrl : ""
|
title: "Source",
|
||||||
};
|
streamUrl: "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(m3u8Link)
|
||||||
console.log("RETURN: " + JSON.stringify(returnValue));
|
});
|
||||||
return JSON.stringify(returnValue);
|
}
|
||||||
|
}
|
||||||
|
const returnValue = {
|
||||||
|
streams: streams,
|
||||||
|
subtitles: englishSubUrl !== "N/A" ? englishSubUrl : ""
|
||||||
|
};
|
||||||
|
console.log("RETURN: " + JSON.stringify(returnValue));
|
||||||
|
return JSON.stringify(returnValue);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Fetch error:"+ error);
|
console.log("Fetch error:" + error);
|
||||||
return "https://error.org";
|
return "https://error.org";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "1Movies",
|
||||||
|
"iconUrl": "https://1movies.bz/assets/uploads/675b5c22f2829fc8e3a4030af7f4284acad017e5241280b3dc21.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.2.5",
|
||||||
|
"language": "English",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animekai.to/",
|
||||||
|
"searchBaseUrl": "https://1movies.bz/home",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/1movies/1movies.js",
|
||||||
|
"type": "shows/movies",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Anicore",
|
||||||
|
"iconUrl": "https://anicore.tv/_app/immutable/assets/favicon.Bq_xX7js.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "English",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://anicore.tv/",
|
||||||
|
"searchBaseUrl": "https://anicore.tv/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anicore/anicore.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
async function searchResults(keyword) {
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Host": "api.anicrush.to",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0",
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "en-US,en;q=0.5",
|
||||||
|
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||||
|
"x-site": "anicrush",
|
||||||
|
"Origin": "https://anicrush.to",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Referer": "https://anicrush.to/",
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "same-site",
|
||||||
|
"TE": "trailers"
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetchv2("https://api.anicrush.to/shared/v2/movie/list?keyword=" + encodeURIComponent(keyword) + "&limit=24&page=1", headers);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status && data.result && data.result.movies) {
|
||||||
|
for (const movie of data.result.movies) {
|
||||||
|
if (movie.has_sub) {
|
||||||
|
results.push({
|
||||||
|
title: (movie.name_english || movie.name) + " [SUB]",
|
||||||
|
image: getImage(movie.poster_path),
|
||||||
|
href: "/watch/" + movie.slug + "." + movie.id + "?type=SUB"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (movie.has_dub) {
|
||||||
|
results.push({
|
||||||
|
title: (movie.name_english || movie.name) + " [DUB]",
|
||||||
|
image: getImage(movie.poster_path),
|
||||||
|
href: "/watch/" + movie.slug + "." + movie.id + "?type=DUB"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(results);
|
||||||
|
} catch (err) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
title: "Error",
|
||||||
|
image: "Error",
|
||||||
|
href: "Error"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractDetails(url) {
|
||||||
|
const id = url.split('.').pop();
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Host": "api.anicrush.to",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0",
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "en-US,en;q=0.5",
|
||||||
|
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||||
|
"x-site": "anicrush",
|
||||||
|
"Origin": "https://anicrush.to",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Referer": "https://anicrush.to/",
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "same-site",
|
||||||
|
"TE": "trailers"
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetchv2(`https://api.anicrush.to/shared/v2/movie/getById/${id}`, headers);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status && data.result) {
|
||||||
|
const result = data.result;
|
||||||
|
const description = result.overview || "N/A";
|
||||||
|
const aliases = result.name_synonyms || "N/A";
|
||||||
|
const airdate = result.aired_from || "N/A";
|
||||||
|
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: description,
|
||||||
|
aliases: aliases,
|
||||||
|
airdate: airdate
|
||||||
|
}]);
|
||||||
|
} else {
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: "Error",
|
||||||
|
aliases: "Error",
|
||||||
|
airdate: "Error"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: "Error",
|
||||||
|
aliases: "Error",
|
||||||
|
airdate: "Error"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractEpisodes(url) {
|
||||||
|
const results = [];
|
||||||
|
const id = url.split('.').pop().split('?')[0];
|
||||||
|
const typeMatch = url.match(/[?&]type=([^&]+)/);
|
||||||
|
const type = typeMatch ? typeMatch[1] : "SUB";
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Host": "api.anicrush.to",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0",
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "en-US,en;q=0.5",
|
||||||
|
"x-site": "anicrush",
|
||||||
|
"Origin": "https://anicrush.to",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Referer": "https://anicrush.to/",
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "same-site",
|
||||||
|
"TE": "trailers"
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = `https://api.anicrush.to/shared/v2/episode/list?_movieId=${id}`;
|
||||||
|
|
||||||
|
const response = await fetchv2(url, headers);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status && data.result) {
|
||||||
|
const seasons = Object.keys(data.result);
|
||||||
|
for (const seasonKey of seasons) {
|
||||||
|
const season = data.result[seasonKey];
|
||||||
|
const isMovie = seasonKey === "001 - 001";
|
||||||
|
for (const ep of season) {
|
||||||
|
const href = isMovie ? `?_movieId=${id}&ep=1&type=${type}` : `?_movieId=${id}&ep=${ep.number}&type=${type}`;
|
||||||
|
results.push({
|
||||||
|
href: href,
|
||||||
|
number: ep.number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(results);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return JSON.stringify([{ id: "Error", href: "Error", number: "Error", title: "Error" }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractStreamUrl(ID, type = "DUB") {
|
||||||
|
const typeMatch = ID.match(/[?&]type=([^&]+)/);
|
||||||
|
if (typeMatch) {
|
||||||
|
type = typeMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Host": "api.anicrush.to",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:146.0) Gecko/20100101 Firefox/146.0",
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "en-US,en;q=0.5",
|
||||||
|
"x-site": "anicrush",
|
||||||
|
"Origin": "https://anicrush.to",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Referer": "https://anicrush.to/",
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "same-site",
|
||||||
|
"TE": "trailers"
|
||||||
|
};
|
||||||
|
|
||||||
|
const headersTwo = {
|
||||||
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:146.0) Gecko/20100101 Firefox/146.0",
|
||||||
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||||
|
"Referer": "https://hianime.to/"
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = `https://api.anicrush.to/shared/v2/episode/servers${ID}`;
|
||||||
|
|
||||||
|
const serversResp = await fetchv2(url, headers);
|
||||||
|
const serversJson = await serversResp.json();
|
||||||
|
console.log(JSON.stringify(serversJson));
|
||||||
|
|
||||||
|
const subServer = serversJson.result?.sub?.[0];
|
||||||
|
const dubServer = serversJson.result?.dub?.[0];
|
||||||
|
const rawServer = serversJson.result?.raw?.[0];
|
||||||
|
|
||||||
|
const subServerId = subServer?.server;
|
||||||
|
const dubServerId = dubServer?.server;
|
||||||
|
const rawServerId = rawServer?.server;
|
||||||
|
|
||||||
|
const processServer = async (serverId, title) => {
|
||||||
|
try {
|
||||||
|
const sourcesResp = await fetchv2(`https://api.anicrush.to/shared/v2/episode/sources${ID}&sv=${serverId}&sc=${title.toLowerCase()}`, headers);
|
||||||
|
const sourcesJson = await sourcesResp.json();
|
||||||
|
const iframeUrl = sourcesJson.result?.link;
|
||||||
|
|
||||||
|
if (!iframeUrl) return null;
|
||||||
|
|
||||||
|
const iframeResp = await fetchv2(iframeUrl, headersTwo);
|
||||||
|
const iframeHtml = await iframeResp.text();
|
||||||
|
|
||||||
|
const videoTagMatch = iframeHtml.match(/data-id="([^"]+)"/);
|
||||||
|
if (!videoTagMatch) return null;
|
||||||
|
const fileId = videoTagMatch[1];
|
||||||
|
|
||||||
|
const nonceMatch = iframeHtml.match(/\b[a-zA-Z0-9]{48}\b/) ||
|
||||||
|
iframeHtml.match(/\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b/);
|
||||||
|
if (!nonceMatch) return null;
|
||||||
|
|
||||||
|
const nonce = nonceMatch.length === 4 ?
|
||||||
|
nonceMatch[1] + nonceMatch[2] + nonceMatch[3] :
|
||||||
|
nonceMatch[0];
|
||||||
|
|
||||||
|
const urlParts = iframeUrl.split('/');
|
||||||
|
const protocol = iframeUrl.startsWith('https') ? 'https:' : 'http:';
|
||||||
|
const hostname = urlParts[2];
|
||||||
|
const defaultDomain = `${protocol}//${hostname}/`;
|
||||||
|
|
||||||
|
const getSourcesUrl = `${defaultDomain}embed-2/v3/e-1/getSources?id=${fileId}&_k=${nonce}`;
|
||||||
|
const getSourcesResp = await fetchv2(getSourcesUrl, headersTwo);
|
||||||
|
const getSourcesJson = await getSourcesResp.json();
|
||||||
|
console.log(JSON.stringify(getSourcesJson));
|
||||||
|
const videoUrl = getSourcesJson.sources?.[0]?.file || "";
|
||||||
|
if (!videoUrl) return null;
|
||||||
|
|
||||||
|
const streamHeaders = {
|
||||||
|
"Referer": defaultDomain,
|
||||||
|
"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36"
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: title,
|
||||||
|
streamUrl: videoUrl,
|
||||||
|
headers: streamHeaders,
|
||||||
|
sourcesData: getSourcesJson
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`${title} failed:`, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const serverPromises = [];
|
||||||
|
if (type === "SUB" && subServerId) serverPromises.push(processServer(subServerId, "SUB"));
|
||||||
|
else if (type === "DUB" && dubServerId) serverPromises.push(processServer(dubServerId, "DUB"));
|
||||||
|
else if (type === "RAW" && rawServerId) serverPromises.push(processServer(rawServerId, "RAW"));
|
||||||
|
|
||||||
|
const results = await Promise.all(serverPromises);
|
||||||
|
const streams = results.filter(r => r !== null);
|
||||||
|
|
||||||
|
if (streams.length === 0) {
|
||||||
|
return "https://error.org/";
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalStreams = streams.map(s => ({
|
||||||
|
title: s.title,
|
||||||
|
streamUrl: s.streamUrl,
|
||||||
|
headers: s.headers
|
||||||
|
}));
|
||||||
|
|
||||||
|
let subtitle = null;
|
||||||
|
if (streams.length > 0 && streams[0].sourcesData.tracks) {
|
||||||
|
const englishTrack = streams[0].sourcesData.tracks.find(t => t.kind === "captions" && t.label === "English");
|
||||||
|
subtitle = englishTrack ? englishTrack.file : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
streams: finalStreams,
|
||||||
|
subtitles: subtitle
|
||||||
|
};
|
||||||
|
console.log(JSON.stringify(result));
|
||||||
|
return JSON.stringify(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return "https://error.oraag/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getImage(path, type = "poster") {
|
||||||
|
const baseUrl = "https://static.gniyonna.com/media/poster";
|
||||||
|
const hashWithExt = path.split('/')[3];
|
||||||
|
const hash = hashWithExt.split('.')[0];
|
||||||
|
|
||||||
|
let reversedHash = '';
|
||||||
|
for (let i = hash.length - 1; i >= 0; i--) {
|
||||||
|
reversedHash += hash[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ext = hashWithExt.split('.').pop();
|
||||||
|
const size = type === "poster" ? "300x400" : "900x600";
|
||||||
|
const imageUrl = `${baseUrl}/${size}/100/${reversedHash}.${ext}`;
|
||||||
|
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AniCrush",
|
||||||
|
"iconUrl": "https://pbs.twimg.com/profile_images/1734825444543733760/dFCwrelN_400x400.jpg",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.1",
|
||||||
|
"language": "English (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://megacloud.blog/",
|
||||||
|
"searchBaseUrl": "https://megacloud.blog/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anicrush/anicrush.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,697 @@
|
|||||||
|
// AniDub module for Sora (AsyncJS)
|
||||||
|
// Author: emp0ry
|
||||||
|
// Version: 1.0.0
|
||||||
|
|
||||||
|
const BASE_URL = "https://anidub.org";
|
||||||
|
const SEARCH_URL = BASE_URL + "/index.php?do=search&subaction=search";
|
||||||
|
|
||||||
|
const PLAYLIST_API = "https://plapi.cdnvideohub.com/api/v1/player/sv/playlist";
|
||||||
|
const VIDEO_API = "https://plapi.cdnvideohub.com/api/v1/player/sv/video/";
|
||||||
|
|
||||||
|
const DEFAULT_PUB = "19";
|
||||||
|
const DEFAULT_AGGR = "mali";
|
||||||
|
const DEFAULT_SUBTITLE = "https://none.com";
|
||||||
|
|
||||||
|
function _ua() {
|
||||||
|
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _safeJsonParse(value, fallback) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch (_) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _htmlDecode(value) {
|
||||||
|
const s = String(value || "");
|
||||||
|
if (!s) return "";
|
||||||
|
|
||||||
|
const named = {
|
||||||
|
"&": "&",
|
||||||
|
"<": "<",
|
||||||
|
">": ">",
|
||||||
|
""": '"',
|
||||||
|
"'": "'",
|
||||||
|
"'": "'",
|
||||||
|
" ": " ",
|
||||||
|
"«": "«",
|
||||||
|
"»": "»"
|
||||||
|
};
|
||||||
|
|
||||||
|
const withNamed = s.replace(/&(amp|lt|gt|quot|#39|apos|nbsp|laquo|raquo);/g, m => named[m] || m);
|
||||||
|
|
||||||
|
const withDec = withNamed.replace(/&#(\d+);/g, (_, d) => {
|
||||||
|
const code = parseInt(d, 10);
|
||||||
|
return Number.isFinite(code) ? String.fromCharCode(code) : "";
|
||||||
|
});
|
||||||
|
|
||||||
|
return withDec.replace(/&#x([0-9a-f]+);/gi, (_, h) => {
|
||||||
|
const code = parseInt(h, 16);
|
||||||
|
return Number.isFinite(code) ? String.fromCharCode(code) : "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _stripTags(value) {
|
||||||
|
return _htmlDecode(String(value || "")
|
||||||
|
.replace(/<br\s*\/?>/gi, "\n")
|
||||||
|
.replace(/<\/p>/gi, "\n")
|
||||||
|
.replace(/<[^>]+>/g, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function _attr(tag, name) {
|
||||||
|
const block = String(tag || "");
|
||||||
|
if (!block) return "";
|
||||||
|
|
||||||
|
const quoted = new RegExp(name + "\\s*=\\s*(['\"])(.*?)\\1", "i").exec(block);
|
||||||
|
if (quoted && quoted[2]) return _htmlDecode(quoted[2]);
|
||||||
|
|
||||||
|
const plain = new RegExp(name + "\\s*=\\s*([^\\s>]+)", "i").exec(block);
|
||||||
|
return plain && plain[1] ? _htmlDecode(plain[1]) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _extractMeta(html, attrName, attrValue) {
|
||||||
|
const src = String(html || "");
|
||||||
|
|
||||||
|
const re = new RegExp(
|
||||||
|
"<meta[^>]*" + attrName + "=['\"]" + attrValue + "['\"][^>]*content=['\"]([^'\"]+)['\"][^>]*>",
|
||||||
|
"i"
|
||||||
|
);
|
||||||
|
|
||||||
|
const m = src.match(re);
|
||||||
|
return m && m[1] ? _htmlDecode(m[1]).trim() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _absUrl(url, base) {
|
||||||
|
const raw = String(url || "").trim();
|
||||||
|
if (!raw) return "";
|
||||||
|
if (raw.startsWith("http://") || raw.startsWith("https://")) return raw;
|
||||||
|
if (raw.startsWith("//")) return "https:" + raw;
|
||||||
|
|
||||||
|
const root = String(base || BASE_URL).replace(/\/+$/, "");
|
||||||
|
return root + "/" + raw.replace(/^\/+/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cleanUrl(url) {
|
||||||
|
const raw = String(url || "").trim();
|
||||||
|
if (!raw) return null;
|
||||||
|
if (raw.startsWith("//")) return "https:" + raw;
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _scoreTitle(title, keyword) {
|
||||||
|
const t = String(title || "").toLowerCase().trim();
|
||||||
|
const k = String(keyword || "").toLowerCase().trim();
|
||||||
|
|
||||||
|
if (!t || !k) return 99;
|
||||||
|
if (t === k) return 0;
|
||||||
|
if (t.startsWith(k)) return 1;
|
||||||
|
if (t.includes(k)) return 2;
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _voiceRank(name) {
|
||||||
|
const s = String(name || "").toLowerCase();
|
||||||
|
|
||||||
|
const order = [
|
||||||
|
"anidub online",
|
||||||
|
"anidub",
|
||||||
|
"aniliberty",
|
||||||
|
"anilibria",
|
||||||
|
"shiza",
|
||||||
|
"onwave",
|
||||||
|
"anistar",
|
||||||
|
"animevost",
|
||||||
|
"студийная банда",
|
||||||
|
"studio band",
|
||||||
|
"dream cast",
|
||||||
|
"dreamcast",
|
||||||
|
"jam club",
|
||||||
|
"jam",
|
||||||
|
"субтит",
|
||||||
|
"sub"
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i = 0; i < order.length; i++) {
|
||||||
|
if (s.includes(order[i])) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _packAnime(payload) {
|
||||||
|
return "anidub-release:" + encodeURIComponent(JSON.stringify(payload || {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _unpackAnime(href) {
|
||||||
|
const raw = String(href || "");
|
||||||
|
if (!raw.startsWith("anidub-release:")) return null;
|
||||||
|
|
||||||
|
return _safeJsonParse(decodeURIComponent(raw.slice("anidub-release:".length)), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _packEpisode(payload) {
|
||||||
|
return "anidub:" + encodeURIComponent(JSON.stringify(payload || {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _unpackEpisode(href) {
|
||||||
|
const raw = String(href || "");
|
||||||
|
if (!raw.startsWith("anidub:")) return null;
|
||||||
|
|
||||||
|
return _safeJsonParse(decodeURIComponent(raw.slice("anidub:".length)), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _searchHeaders() {
|
||||||
|
return {
|
||||||
|
"User-Agent": _ua(),
|
||||||
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||||
|
"Accept-Language": "ru-RU,ru;q=0.9,en;q=0.8",
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
|
"Origin": BASE_URL,
|
||||||
|
"Referer": BASE_URL + "/"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _htmlHeaders(referer) {
|
||||||
|
return {
|
||||||
|
"User-Agent": _ua(),
|
||||||
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||||
|
"Accept-Language": "ru-RU,ru;q=0.9,en;q=0.8",
|
||||||
|
"Referer": referer || BASE_URL + "/",
|
||||||
|
"Origin": BASE_URL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _jsonHeaders(referer) {
|
||||||
|
return {
|
||||||
|
"User-Agent": _ua(),
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "ru-RU,ru;q=0.9,en;q=0.8",
|
||||||
|
"Referer": referer || BASE_URL + "/"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _streamHeaders() {
|
||||||
|
return {
|
||||||
|
"User-Agent": _ua(),
|
||||||
|
"Referer": BASE_URL + "/"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _postSearch(query) {
|
||||||
|
const q = encodeURIComponent(String(query || ""));
|
||||||
|
const withParams = `${SEARCH_URL}&story=${q}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const r = await fetchv2(withParams, _searchHeaders(), "POST", "");
|
||||||
|
const txt = await r.text();
|
||||||
|
|
||||||
|
if (txt && txt.includes("all__item")) return txt;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
const body = "do=search&subaction=search&story=" + q;
|
||||||
|
const fallback = await fetchv2(BASE_URL + "/index.php?do=search", _searchHeaders(), "POST", body);
|
||||||
|
return fallback.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _parseSearchResults(html) {
|
||||||
|
const src = String(html || "");
|
||||||
|
const out = [];
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
const blocks = src.match(/<a\b[^>]*class=["'][^"']*all__item[^"']*["'][^>]*>[\s\S]*?<\/a>/gi) || [];
|
||||||
|
|
||||||
|
for (const block of blocks) {
|
||||||
|
const openTag = (block.match(/<a\b[^>]*>/i) || [""])[0];
|
||||||
|
const imgTag = (block.match(/<img\b[^>]*>/i) || [""])[0];
|
||||||
|
|
||||||
|
const titleMatch = block.match(
|
||||||
|
/<span\b[^>]*class=["'][^"']*all__item-title[^"']*["'][^>]*>([\s\S]*?)<\/span>/i
|
||||||
|
);
|
||||||
|
|
||||||
|
const href = _absUrl(_attr(openTag, "href"), BASE_URL);
|
||||||
|
const title = _stripTags((titleMatch && titleMatch[1]) || _attr(openTag, "title") || "Unknown title");
|
||||||
|
const image = _absUrl(_attr(imgTag, "src") || _attr(imgTag, "data-src"), BASE_URL);
|
||||||
|
|
||||||
|
if (!href || !title || seen.has(href)) continue;
|
||||||
|
seen.add(href);
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
title,
|
||||||
|
image,
|
||||||
|
href: _packAnime({
|
||||||
|
animeUrl: href,
|
||||||
|
title,
|
||||||
|
image
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _extractAnimeUrl(input) {
|
||||||
|
const packed = _unpackAnime(input);
|
||||||
|
if (packed?.animeUrl) return _absUrl(packed.animeUrl, BASE_URL);
|
||||||
|
|
||||||
|
const ep = _unpackEpisode(input);
|
||||||
|
if (ep?.animeUrl) return _absUrl(ep.animeUrl, BASE_URL);
|
||||||
|
|
||||||
|
return _absUrl(input, BASE_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _extractTitleId(html) {
|
||||||
|
const src = String(html || "");
|
||||||
|
|
||||||
|
const m1 = src.match(/data-title-id=["'](\d+)["']/i);
|
||||||
|
if (m1 && m1[1]) return m1[1];
|
||||||
|
|
||||||
|
const m2 = src.match(/[?&]titleId=(\d+)/i);
|
||||||
|
if (m2 && m2[1]) return m2[1];
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _extractPublisherId(html) {
|
||||||
|
const src = String(html || "");
|
||||||
|
|
||||||
|
const m = src.match(/data-publisher-id=["'](\d+)["']/i);
|
||||||
|
return m && m[1] ? m[1] : DEFAULT_PUB;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _extractAggregator(html) {
|
||||||
|
const src = String(html || "");
|
||||||
|
|
||||||
|
const m1 = src.match(/data-aggregator=["']([^"']+)["']/i);
|
||||||
|
if (m1 && m1[1]) return m1[1];
|
||||||
|
|
||||||
|
const m2 = src.match(/[?&]aggr=([^"'&<>\s]+)/i);
|
||||||
|
return m2 && m2[1] ? decodeURIComponent(m2[1]) : DEFAULT_AGGR;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _getAnimePage(animeUrl) {
|
||||||
|
const r = await fetchv2(animeUrl, _htmlHeaders(BASE_URL + "/"));
|
||||||
|
return r.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _getPlaylist(titleId, pub, aggr, referer) {
|
||||||
|
const url =
|
||||||
|
`${PLAYLIST_API}?pub=${encodeURIComponent(pub || DEFAULT_PUB)}` +
|
||||||
|
`&aggr=${encodeURIComponent(aggr || DEFAULT_AGGR)}` +
|
||||||
|
`&id=${encodeURIComponent(titleId)}`;
|
||||||
|
|
||||||
|
const r = await fetchv2(url, _jsonHeaders(referer || BASE_URL + "/"));
|
||||||
|
return r.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _getVideo(vkId, referer) {
|
||||||
|
const url = VIDEO_API + encodeURIComponent(String(vkId || ""));
|
||||||
|
const r = await fetchv2(url, _jsonHeaders(referer || BASE_URL + "/"));
|
||||||
|
return r.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _detailsDescription(html) {
|
||||||
|
const title =
|
||||||
|
_stripTags((html.match(/<h1\b[^>]*class=["'][^"']*movie__title[^"']*["'][^>]*>([\s\S]*?)<\/h1>/i) || [null, ""])[1]) ||
|
||||||
|
_extractMeta(html, "property", "og:title") ||
|
||||||
|
_extractMeta(html, "name", "title") ||
|
||||||
|
"AniDub";
|
||||||
|
|
||||||
|
const subtitle = _stripTags(
|
||||||
|
(html.match(/<span\b[^>]*class=["'][^"']*movie__subtitle[^"']*["'][^>]*>([\s\S]*?)<\/span>/i) || [null, ""])[1]
|
||||||
|
);
|
||||||
|
|
||||||
|
const desc =
|
||||||
|
_stripTags((html.match(/<div\b[^>]*class=["'][^"']*movie__description[^"']*["'][^>]*>([\s\S]*?)<\/div>/i) || [null, ""])[1]) ||
|
||||||
|
_extractMeta(html, "property", "og:description") ||
|
||||||
|
_extractMeta(html, "name", "description") ||
|
||||||
|
"No description available.";
|
||||||
|
|
||||||
|
return [title, subtitle, desc].filter(Boolean).join("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _detailsAliases(html, titleId, pub, aggr) {
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
const addField = (display, pattern) => {
|
||||||
|
const re = new RegExp(
|
||||||
|
"<div[^>]*class=[\"'][^\"']*movie__type[^\"']*[\"'][^>]*>\\s*<span>" +
|
||||||
|
pattern +
|
||||||
|
":<\\/span>\\s*([\\s\\S]*?)<\\/div>",
|
||||||
|
"i"
|
||||||
|
);
|
||||||
|
|
||||||
|
const m = html.match(re);
|
||||||
|
const val = m && m[1] ? _stripTags(m[1]) : "";
|
||||||
|
|
||||||
|
if (val) parts.push(`${display}: ${val}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
addField("Жанр", "Жанр");
|
||||||
|
addField("Тип", "Тип");
|
||||||
|
addField("Статус", "Статус");
|
||||||
|
addField("Год выхода", "Год выхода");
|
||||||
|
addField("Длительность", "Длительность \\(мин\\.\\)");
|
||||||
|
addField("Кол-во эпизодов", "Кол-во эпизодов");
|
||||||
|
addField("Другие названия", "Другие названия");
|
||||||
|
|
||||||
|
if (titleId) parts.push(`titleId: ${titleId}`);
|
||||||
|
if (pub) parts.push(`pub: ${pub}`);
|
||||||
|
if (aggr) parts.push(`aggr: ${aggr}`);
|
||||||
|
|
||||||
|
return parts.join(" | ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _airdate(html) {
|
||||||
|
const premiere = html.match(
|
||||||
|
/<div[^>]*class=["'][^"']*movie__type[^"']*["'][^>]*>\s*<span>Премьера:<\/span>\s*([\s\S]*?)<\/div>/i
|
||||||
|
);
|
||||||
|
|
||||||
|
const added = html.match(
|
||||||
|
/<div[^>]*class=["'][^"']*movie__added[^"']*["'][^>]*>[\s\S]*?<span>Добавлено<\/span>[\s\S]*?<span>([\s\S]*?)<\/span>/i
|
||||||
|
);
|
||||||
|
|
||||||
|
return _stripTags((premiere && premiere[1]) || (added && added[1]) || "Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _episodeKey(season, episode) {
|
||||||
|
return `${season || 1}:${episode || 0}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _episodeTitle(season, episode) {
|
||||||
|
if (season && season > 1) return `S${season}E${episode}`;
|
||||||
|
return `Episode ${episode}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _voiceName(item) {
|
||||||
|
const studio = String(item?.voiceStudio || "").trim();
|
||||||
|
const type = String(item?.voiceType || "").trim();
|
||||||
|
|
||||||
|
if (studio && type) return `${studio} · ${type}`;
|
||||||
|
if (studio) return studio;
|
||||||
|
if (type) return type;
|
||||||
|
|
||||||
|
return "Unknown voiceover";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sourceUrl(sources, key) {
|
||||||
|
return _cleanUrl(sources?.[key] || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function _buildQualityUrls(videoJson) {
|
||||||
|
const sources = videoJson?.sources || {};
|
||||||
|
|
||||||
|
const url2160 = _sourceUrl(sources, "mpeg4kUrl");
|
||||||
|
const url1440 = _sourceUrl(sources, "mpeg2kUrl") || _sourceUrl(sources, "mpegQhdUrl");
|
||||||
|
const url1080 = _sourceUrl(sources, "mpegFullHdUrl");
|
||||||
|
const url720 = _sourceUrl(sources, "mpegHighUrl");
|
||||||
|
const url480 = _sourceUrl(sources, "mpegMediumUrl");
|
||||||
|
const url360 = _sourceUrl(sources, "mpegLowUrl");
|
||||||
|
const url240 = _sourceUrl(sources, "mpegLowestUrl");
|
||||||
|
const url144 = _sourceUrl(sources, "mpegTinyUrl");
|
||||||
|
|
||||||
|
return {
|
||||||
|
url2160,
|
||||||
|
url1440,
|
||||||
|
url1080,
|
||||||
|
url720,
|
||||||
|
url480,
|
||||||
|
url360,
|
||||||
|
url240,
|
||||||
|
url144
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _pickBestMp4(qualityUrls) {
|
||||||
|
const ordered = [
|
||||||
|
{ quality: 2160, url: qualityUrls.url2160 },
|
||||||
|
{ quality: 1440, url: qualityUrls.url1440 },
|
||||||
|
{ quality: 1080, url: qualityUrls.url1080 },
|
||||||
|
{ quality: 720, url: qualityUrls.url720 },
|
||||||
|
{ quality: 480, url: qualityUrls.url480 },
|
||||||
|
{ quality: 360, url: qualityUrls.url360 },
|
||||||
|
{ quality: 240, url: qualityUrls.url240 },
|
||||||
|
{ quality: 144, url: qualityUrls.url144 }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const item of ordered) {
|
||||||
|
if (item.url) return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _hasAnyQuality(qualityUrls) {
|
||||||
|
return !!(
|
||||||
|
qualityUrls.url2160 ||
|
||||||
|
qualityUrls.url1440 ||
|
||||||
|
qualityUrls.url1080 ||
|
||||||
|
qualityUrls.url720 ||
|
||||||
|
qualityUrls.url480 ||
|
||||||
|
qualityUrls.url360 ||
|
||||||
|
qualityUrls.url240 ||
|
||||||
|
qualityUrls.url144
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _buildVoiceoverStream(voiceName, videoJson) {
|
||||||
|
const qualityUrls = _buildQualityUrls(videoJson);
|
||||||
|
|
||||||
|
if (!_hasAnyQuality(qualityUrls)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const best = _pickBestMp4(qualityUrls);
|
||||||
|
if (!best?.url) return null;
|
||||||
|
|
||||||
|
const headers = _streamHeaders();
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: voiceName || "Voiceover",
|
||||||
|
streamUrl: best.url,
|
||||||
|
|
||||||
|
// Main Sora quality-picker fields.
|
||||||
|
url1080: qualityUrls.url1080,
|
||||||
|
url720: qualityUrls.url720,
|
||||||
|
url480: qualityUrls.url480,
|
||||||
|
|
||||||
|
// Extra qualities. Harmless if Sora ignores them.
|
||||||
|
url2160: qualityUrls.url2160,
|
||||||
|
url1440: qualityUrls.url1440,
|
||||||
|
url360: qualityUrls.url360,
|
||||||
|
url240: qualityUrls.url240,
|
||||||
|
url144: qualityUrls.url144,
|
||||||
|
|
||||||
|
headers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Search
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
async function searchResults(keyword) {
|
||||||
|
try {
|
||||||
|
const query = String(keyword || "").trim();
|
||||||
|
if (!query) return JSON.stringify([]);
|
||||||
|
|
||||||
|
const html = await _postSearch(query);
|
||||||
|
const results = _parseSearchResults(html);
|
||||||
|
|
||||||
|
for (const item of results) {
|
||||||
|
item._score = _scoreTitle(item.title, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
results.sort((a, b) => a._score - b._score);
|
||||||
|
|
||||||
|
return JSON.stringify(results.map(({ _score, ...rest }) => rest));
|
||||||
|
} catch (_) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Details
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
async function extractDetails(href) {
|
||||||
|
try {
|
||||||
|
const animeUrl = _extractAnimeUrl(href);
|
||||||
|
if (!animeUrl) return JSON.stringify([]);
|
||||||
|
|
||||||
|
const html = await _getAnimePage(animeUrl);
|
||||||
|
|
||||||
|
const titleId = _extractTitleId(html);
|
||||||
|
const pub = _extractPublisherId(html);
|
||||||
|
const aggr = _extractAggregator(html);
|
||||||
|
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: _detailsDescription(html),
|
||||||
|
aliases: _detailsAliases(html, titleId, pub, aggr) || "AniDub",
|
||||||
|
airdate: _airdate(html)
|
||||||
|
}]);
|
||||||
|
} catch (_) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Episodes
|
||||||
|
// Unique episodes only.
|
||||||
|
// Store all voiceovers in href payload.
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
async function extractEpisodes(href) {
|
||||||
|
try {
|
||||||
|
const animeUrl = _extractAnimeUrl(href);
|
||||||
|
if (!animeUrl) return JSON.stringify([]);
|
||||||
|
|
||||||
|
const html = await _getAnimePage(animeUrl);
|
||||||
|
|
||||||
|
const titleId = _extractTitleId(html);
|
||||||
|
const pub = _extractPublisherId(html);
|
||||||
|
const aggr = _extractAggregator(html);
|
||||||
|
|
||||||
|
if (!titleId) return JSON.stringify([]);
|
||||||
|
|
||||||
|
const playlist = await _getPlaylist(titleId, pub, aggr, animeUrl);
|
||||||
|
const items = Array.isArray(playlist?.items) ? playlist.items : [];
|
||||||
|
|
||||||
|
const byEpisode = new Map();
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const vkId = String(item?.vkId || "").trim();
|
||||||
|
if (!vkId) continue;
|
||||||
|
|
||||||
|
const season = Number.isFinite(item?.season) ? item.season : 1;
|
||||||
|
const episode = Number.isFinite(item?.episode) ? item.episode : 0;
|
||||||
|
if (!episode) continue;
|
||||||
|
|
||||||
|
const key = _episodeKey(season, episode);
|
||||||
|
|
||||||
|
if (!byEpisode.has(key)) {
|
||||||
|
byEpisode.set(key, {
|
||||||
|
season,
|
||||||
|
episode,
|
||||||
|
options: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ep = byEpisode.get(key);
|
||||||
|
|
||||||
|
ep.options.push({
|
||||||
|
vkId,
|
||||||
|
cvhId: String(item?.cvhId || ""),
|
||||||
|
voiceStudio: String(item?.voiceStudio || "").trim(),
|
||||||
|
voiceType: String(item?.voiceType || "").trim(),
|
||||||
|
voiceName: _voiceName(item),
|
||||||
|
name: String(item?.name || "").trim(),
|
||||||
|
season,
|
||||||
|
episode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = Array.from(byEpisode.values())
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.season !== b.season) return a.season - b.season;
|
||||||
|
return a.episode - b.episode;
|
||||||
|
})
|
||||||
|
.map(ep => {
|
||||||
|
ep.options.sort((a, b) => _voiceRank(a.voiceName) - _voiceRank(b.voiceName));
|
||||||
|
|
||||||
|
return {
|
||||||
|
href: _packEpisode({
|
||||||
|
animeUrl,
|
||||||
|
titleId,
|
||||||
|
pub,
|
||||||
|
aggr,
|
||||||
|
season: ep.season,
|
||||||
|
episode: ep.episode,
|
||||||
|
options: ep.options
|
||||||
|
}),
|
||||||
|
number: ep.episode,
|
||||||
|
title: _episodeTitle(ep.season, ep.episode)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return JSON.stringify(out);
|
||||||
|
} catch (_) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
// Stream
|
||||||
|
// Correct voiceover + quality picker:
|
||||||
|
// - one stream per voiceover
|
||||||
|
// - each stream carries url1080/url720/url480/etc.
|
||||||
|
// - HLS skipped.
|
||||||
|
// ------------------------------------------------------------
|
||||||
|
async function extractStreamUrl(href) {
|
||||||
|
try {
|
||||||
|
const payload = _unpackEpisode(href);
|
||||||
|
const options = Array.isArray(payload?.options) ? payload.options : [];
|
||||||
|
|
||||||
|
if (!options.length) {
|
||||||
|
return JSON.stringify({
|
||||||
|
streams: [],
|
||||||
|
subtitle: DEFAULT_SUBTITLE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
options.sort((a, b) => _voiceRank(a.voiceName) - _voiceRank(b.voiceName));
|
||||||
|
|
||||||
|
const streams = [];
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
for (const opt of options) {
|
||||||
|
const vkId = String(opt?.vkId || "").trim();
|
||||||
|
if (!vkId || seen.has(vkId)) continue;
|
||||||
|
seen.add(vkId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const videoJson = await _getVideo(vkId, payload?.animeUrl || BASE_URL + "/");
|
||||||
|
|
||||||
|
const stream = _buildVoiceoverStream(
|
||||||
|
String(opt?.voiceName || "Voiceover").trim(),
|
||||||
|
videoJson
|
||||||
|
);
|
||||||
|
|
||||||
|
if (stream) streams.push(stream);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
streams,
|
||||||
|
subtitle: DEFAULT_SUBTITLE
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
return JSON.stringify({
|
||||||
|
streams: [],
|
||||||
|
subtitle: DEFAULT_SUBTITLE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _defaultExport() {
|
||||||
|
return {
|
||||||
|
searchResults,
|
||||||
|
extractDetails,
|
||||||
|
extractEpisodes,
|
||||||
|
extractStreamUrl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
globalThis.default = _defaultExport;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.default = _defaultExport;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
globalThis.module = globalThis.module || {};
|
||||||
|
globalThis.module.exports = { default: _defaultExport };
|
||||||
|
} catch (_) {}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AniDub",
|
||||||
|
"iconUrl": "https://anidub.org/templates/CinemaBlue/images/jutfun.png",
|
||||||
|
"author": {
|
||||||
|
"name": "emp0ry",
|
||||||
|
"icon": "https://avatars.githubusercontent.com/u/64217088"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Russian",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://anidub.org/",
|
||||||
|
"searchBaseUrl": "https://anidub.org/index.php?do=search&subaction=search&story=%s",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anidub/anidub.js",
|
||||||
|
"asyncJS": true,
|
||||||
|
"streamAsyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"type": "anime",
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -1,25 +1,54 @@
|
|||||||
// AniLiberty module for Sora (AsyncJS)
|
// AniLiberty module for Sora (AsyncJS)
|
||||||
// Author: emp0ry
|
// Author: emp0ry
|
||||||
// Version: 1.0.1
|
// Version: 1.0.2
|
||||||
|
|
||||||
const IMAGE_HOST = "https://anilibria.top";
|
const DEFAULT_IMAGE_HOST = "https://aniliberty.top";
|
||||||
|
|
||||||
function fullImg(path) {
|
function originFromApi(urlOrBase) {
|
||||||
|
if (!urlOrBase) return DEFAULT_IMAGE_HOST;
|
||||||
|
const raw = String(urlOrBase);
|
||||||
|
const m = raw.match(/^(https?:\/\/[^/]+)\/api\/v1\/?/i);
|
||||||
|
if (m && m[1]) return m[1];
|
||||||
|
const m2 = raw.match(/^(https?:\/\/[^/]+)/i);
|
||||||
|
return (m2 && m2[1]) ? m2[1] : DEFAULT_IMAGE_HOST;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fullImg(path, host) {
|
||||||
if (!path) return;
|
if (!path) return;
|
||||||
return path.startsWith("http") ? path : `${IMAGE_HOST}${path}`;
|
if (path.startsWith("http")) return path;
|
||||||
|
const base = host || DEFAULT_IMAGE_HOST;
|
||||||
|
return `${base}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickBestHls(ep) {
|
function pickBestHls(ep) {
|
||||||
return ep?.hls_1080 || ep?.hls_720 || ep?.hls_480 || null;
|
return ep?.hls_1080 || ep?.hls_720 || ep?.hls_480 || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _packEpisode(payload) {
|
||||||
|
return "aniliberty:" + encodeURIComponent(JSON.stringify(payload || {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _unpackEpisode(href) {
|
||||||
|
const s = String(href || "");
|
||||||
|
if (!s.startsWith("aniliberty:")) return null;
|
||||||
|
return _safeJsonParse(decodeURIComponent(s.slice("aniliberty:".length)), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _safeJsonParse(s, fallback) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(s);
|
||||||
|
} catch (_) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
// Detect working API domain
|
// Detect working API domain
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
async function checkApiStatus() {
|
async function checkApiStatus() {
|
||||||
const domains = [
|
const domains = [
|
||||||
"https://anilibria.top/api/v1/",
|
|
||||||
"https://aniliberty.top/api/v1/",
|
"https://aniliberty.top/api/v1/",
|
||||||
|
"https://anilibria.top/api/v1/",
|
||||||
"https://anilibria.wtf/api/v1/"
|
"https://anilibria.wtf/api/v1/"
|
||||||
];
|
];
|
||||||
for (const base of domains) {
|
for (const base of domains) {
|
||||||
@@ -29,7 +58,7 @@ async function checkApiStatus() {
|
|||||||
if (data?.is_alive || data?.result === "ok") return base;
|
if (data?.is_alive || data?.result === "ok") return base;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
return "https://anilibria.top/api/v1/";
|
return "https://aniliberty.top/api/v1/";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
@@ -38,6 +67,7 @@ async function checkApiStatus() {
|
|||||||
async function searchResults(keyword) {
|
async function searchResults(keyword) {
|
||||||
try {
|
try {
|
||||||
const base = await checkApiStatus();
|
const base = await checkApiStatus();
|
||||||
|
const origin = originFromApi(base);
|
||||||
const url = `${base}app/search/releases?query=${encodeURIComponent(keyword)}&include=id,name.main,poster.src`;
|
const url = `${base}app/search/releases?query=${encodeURIComponent(keyword)}&include=id,name.main,poster.src`;
|
||||||
|
|
||||||
const res = await fetchv2(url);
|
const res = await fetchv2(url);
|
||||||
@@ -45,7 +75,7 @@ async function searchResults(keyword) {
|
|||||||
|
|
||||||
const out = (Array.isArray(data) ? data : []).map(it => ({
|
const out = (Array.isArray(data) ? data : []).map(it => ({
|
||||||
title: it?.name?.main || "Unknown title",
|
title: it?.name?.main || "Unknown title",
|
||||||
image: fullImg(it?.poster?.src),
|
image: fullImg(it?.poster?.src, origin),
|
||||||
href:
|
href:
|
||||||
`${base}anime/releases/${it.id}?` +
|
`${base}anime/releases/${it.id}?` +
|
||||||
[
|
[
|
||||||
@@ -99,19 +129,24 @@ async function extractEpisodes(url) {
|
|||||||
const res = await fetchv2(url);
|
const res = await fetchv2(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
const seriesPoster = fullImg(data?.poster?.src);
|
const origin = originFromApi(url);
|
||||||
|
|
||||||
|
const seriesPoster = fullImg(data?.poster?.src, origin);
|
||||||
const eps = Array.isArray(data?.episodes) ? data.episodes : [];
|
const eps = Array.isArray(data?.episodes) ? data.episodes : [];
|
||||||
|
|
||||||
const out = eps.map((ep, idx) => {
|
const out = eps.map((ep, idx) => {
|
||||||
const href = pickBestHls(ep);
|
const url1080 = ep?.hls_1080 || null;
|
||||||
if (!href) return null;
|
const url720 = ep?.hls_720 || null;
|
||||||
|
const url480 = ep?.hls_480 || null;
|
||||||
|
const best = url1080 || url720 || url480;
|
||||||
|
if (!best) return null;
|
||||||
|
|
||||||
const num = Number.isFinite(ep?.ordinal)
|
const num = Number.isFinite(ep?.ordinal)
|
||||||
? ep.ordinal
|
? ep.ordinal
|
||||||
: (Number.isFinite(ep?.sort_order) ? ep.sort_order : (idx + 1));
|
: (Number.isFinite(ep?.sort_order) ? ep.sort_order : (idx + 1));
|
||||||
|
|
||||||
const title = ep?.name ? String(ep.name) : `Episode ${num}`;
|
const title = ep?.name ? String(ep.name) : `Episode ${num}`;
|
||||||
const image = fullImg(ep?.preview?.src) || seriesPoster;
|
const image = fullImg(ep?.preview?.src, origin) || seriesPoster;
|
||||||
|
|
||||||
// Build skip blocks only if numbers present
|
// Build skip blocks only if numbers present
|
||||||
const opening = (ep?.opening && Number.isFinite(ep.opening.start) && Number.isFinite(ep.opening.stop))
|
const opening = (ep?.opening && Number.isFinite(ep.opening.start) && Number.isFinite(ep.opening.stop))
|
||||||
@@ -123,7 +158,12 @@ async function extractEpisodes(url) {
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const entry = {
|
const entry = {
|
||||||
href,
|
href: _packEpisode({
|
||||||
|
url1080,
|
||||||
|
url720,
|
||||||
|
url480,
|
||||||
|
fallback: best
|
||||||
|
}),
|
||||||
number: num,
|
number: num,
|
||||||
title,
|
title,
|
||||||
image
|
image
|
||||||
@@ -148,7 +188,68 @@ async function extractEpisodes(url) {
|
|||||||
// ------------------------------------------------------------
|
// ------------------------------------------------------------
|
||||||
async function extractStreamUrl(url) {
|
async function extractStreamUrl(url) {
|
||||||
try {
|
try {
|
||||||
return url; // direct HLS
|
const payload = _unpackEpisode(url);
|
||||||
|
if (!payload) {
|
||||||
|
return url; // backward compatibility for old episode href format
|
||||||
|
}
|
||||||
|
|
||||||
|
const url1080 = (payload.url1080 || "").toString().trim() || null;
|
||||||
|
const url720 = (payload.url720 || "").toString().trim() || null;
|
||||||
|
const url480 = (payload.url480 || "").toString().trim() || null;
|
||||||
|
const fallback = (payload.fallback || "").toString().trim() || null;
|
||||||
|
|
||||||
|
const streams = [];
|
||||||
|
|
||||||
|
if (url1080) {
|
||||||
|
streams.push({
|
||||||
|
title: "1080p",
|
||||||
|
streamUrl: url1080,
|
||||||
|
url1080,
|
||||||
|
url720,
|
||||||
|
url480,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": "Mozilla/5.0",
|
||||||
|
"Referer": DEFAULT_IMAGE_HOST + "/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url720) {
|
||||||
|
streams.push({
|
||||||
|
title: "720p",
|
||||||
|
streamUrl: url720,
|
||||||
|
url1080,
|
||||||
|
url720,
|
||||||
|
url480,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": "Mozilla/5.0",
|
||||||
|
"Referer": DEFAULT_IMAGE_HOST + "/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url480) {
|
||||||
|
streams.push({
|
||||||
|
title: "480p",
|
||||||
|
streamUrl: url480,
|
||||||
|
url1080,
|
||||||
|
url720,
|
||||||
|
url480,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": "Mozilla/5.0",
|
||||||
|
"Referer": DEFAULT_IMAGE_HOST + "/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!streams.length) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
streams,
|
||||||
|
subtitle: "https://none.com"
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
{
|
{
|
||||||
"sourceName": "AniLiberty",
|
"sourceName": "AniLiberty",
|
||||||
"iconUrl": "https://anilibria.top/static/apple-touch-icon.png",
|
"iconUrl": "https://aniliberty.top/static/favicon-96x96.png",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "emp0ry",
|
"name": "emp0ry",
|
||||||
"icon": "https://avatars.githubusercontent.com/u/64217088"
|
"icon": "https://avatars.githubusercontent.com/u/64217088"
|
||||||
},
|
},
|
||||||
"version": "1.0.2",
|
"version": "1.0.4",
|
||||||
"language": "Russian",
|
"language": "Russian",
|
||||||
"streamType": "HLS",
|
"streamType": "HLS",
|
||||||
"quality": "1080p",
|
"quality": "1080p",
|
||||||
"baseUrl": "https://anilibria.top/api/v1/",
|
"baseUrl": "https://aniliberty.top/api/v1/",
|
||||||
"searchBaseUrl": "https://anilibria.top/api/v1/app/search/releases?query=%s",
|
"searchBaseUrl": "https://aniliberty.top/api/v1/app/search/releases?query=%s",
|
||||||
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/aniliberty/aniliberty.js",
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/aniliberty/aniliberty.js",
|
||||||
"asyncJS": true,
|
"asyncJS": true,
|
||||||
"streamAsyncJS": false,
|
"streamAsyncJS": false,
|
||||||
"softsub": false,
|
"softsub": false,
|
||||||
"type": "anime",
|
"type": "anime",
|
||||||
"downloadSupport": false
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsShirox": true
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AniLibria",
|
||||||
|
"iconUrl": "https://github.com/50n50/sources/blob/main/anilibria/icon.png?raw=true",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Russian",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "720p",
|
||||||
|
"baseUrl": "https://api.anilibria.tv/",
|
||||||
|
"searchBaseUrl": "https://api.anilibria.tv/v3/title/search?search=%s",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anilibria/anilibria.js",
|
||||||
|
"asyncJS": true,
|
||||||
|
"type": "anime",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
@@ -1,23 +1,76 @@
|
|||||||
async function searchResults(keyword) {
|
async function getDomainsList() {
|
||||||
const results = [];
|
try {
|
||||||
const headers = {
|
console.log("Fetching domains from anime-sama.pw...");
|
||||||
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
const response = await fetchv2("https://anime-sama.pw/");
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
const html = await response.text();
|
||||||
"referer": "https://anime-sama.org/"
|
console.log("Fetched HTML length:", html.length);
|
||||||
};
|
|
||||||
|
|
||||||
|
const domainRegex = /{ name: '([^']+)' }/g;
|
||||||
|
const domains = [];
|
||||||
|
let match;
|
||||||
|
while ((match = domainRegex.exec(html)) !== null) {
|
||||||
|
domains.push(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Parsed domains:", domains);
|
||||||
|
return domains.length > 0 ? domains : ["anime-sama.tv"];
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error in getDomainsList:", err);
|
||||||
|
return ["anime-sama.tv"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchResults(keyword) {
|
||||||
|
console.log("searchResults keyword:", keyword);
|
||||||
|
const domains = await getDomainsList();
|
||||||
const regex = /<a[^>]+href="([^"]+)"[\s\S]*?<img[^>]+src="([^"]+)"[\s\S]*?<h3[^>]*>(.*?)<\/h3>/gi;
|
const regex = /<a[^>]+href="([^"]+)"[\s\S]*?<img[^>]+src="([^"]+)"[\s\S]*?<h3[^>]*>(.*?)<\/h3>/gi;
|
||||||
|
|
||||||
|
const firstDomain = domains[0];
|
||||||
|
console.log("Trying first domain:", firstDomain);
|
||||||
|
const firstResult = await trySearch(firstDomain, keyword, regex);
|
||||||
|
if (firstResult && firstResult.length > 0) {
|
||||||
|
console.log("Results found in first domain:", firstDomain);
|
||||||
|
return JSON.stringify(firstResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("No results in first domain, trying others...");
|
||||||
|
const otherDomains = domains.slice(1);
|
||||||
|
const promises = otherDomains.map(domain => trySearch(domain, keyword, regex));
|
||||||
|
const results = await Promise.all(promises);
|
||||||
|
|
||||||
|
for (let i = 0; i < results.length; i++) {
|
||||||
|
const result = results[i];
|
||||||
|
if (result && result.length > 0) {
|
||||||
|
console.log("Results found in domain:", otherDomains[i]);
|
||||||
|
return JSON.stringify(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("No results found in any domain.");
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function trySearch(domain, keyword, regex) {
|
||||||
try {
|
try {
|
||||||
|
console.log("trySearch domain:", domain, "keyword:", keyword);
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"referer": `https://${domain}/`
|
||||||
|
};
|
||||||
|
|
||||||
const response = await fetchv2(
|
const response = await fetchv2(
|
||||||
"https://anime-sama.eu/template-php/defaut/fetch.php",
|
`https://${domain}/template-php/defaut/fetch.php`,
|
||||||
headers,
|
headers,
|
||||||
"POST",
|
"POST",
|
||||||
`query=${encodeURIComponent(keyword)}`
|
`query=${encodeURIComponent(keyword)}`
|
||||||
);
|
);
|
||||||
const html = await response.text();
|
const html = await response.text();
|
||||||
|
console.log("trySearch HTML length:", html.length);
|
||||||
|
|
||||||
|
const results = [];
|
||||||
let match;
|
let match;
|
||||||
|
regex.lastIndex = 0;
|
||||||
while ((match = regex.exec(html)) !== null) {
|
while ((match = regex.exec(html)) !== null) {
|
||||||
results.push({
|
results.push({
|
||||||
title: match[3].trim(),
|
title: match[3].trim(),
|
||||||
@@ -26,13 +79,11 @@ async function searchResults(keyword) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.stringify(results);
|
console.log("trySearch found results:", results.length);
|
||||||
|
return results;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return JSON.stringify([{
|
console.error("trySearch error for domain", domain, ":", err);
|
||||||
title: "Error",
|
return [];
|
||||||
image: "Error",
|
|
||||||
href: "Error"
|
|
||||||
}]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Anime3rb",
|
||||||
|
"iconUrl": "https://anime3rb.com/favicon/apple-touch-icon.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.2.0",
|
||||||
|
"language": "Arabic (SUB)",
|
||||||
|
"streamType": "MP4",
|
||||||
|
"quality": "1080p - 720p - 360p",
|
||||||
|
"baseUrl": "https://anime3rb.com/",
|
||||||
|
"searchBaseUrl": "https://anime3rb.com/search?q=%s",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anime3rb/anime3rb.js",
|
||||||
|
"streamAsyncJS": true,
|
||||||
|
"type": "anime",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Anime4Up",
|
||||||
|
"iconUrl": "https://ww.anime4up.rest/wp-content/uploads/2019/03/Anime4up-Icon-1.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.1",
|
||||||
|
"language": "Arabic",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"encrypted": true,
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://uqload.cx/",
|
||||||
|
"searchBaseUrl": "https://uqload.cx/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anime4up/anime4up.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeBalkan",
|
||||||
|
"iconUrl": "https://i.ibb.co/7tRnVMqY/favicon.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Croatian",
|
||||||
|
"streamType": "MP4",
|
||||||
|
"quality": "720p",
|
||||||
|
"baseUrl": "https://animebalkan.org//",
|
||||||
|
"searchBaseUrl": "https://animebalkan.org/?s=%s",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animebalkan/animebalkan.js",
|
||||||
|
"type": "anime",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeBum",
|
||||||
|
"iconUrl": "https://files.catbox.moe/i96cs1.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.animebum.net/",
|
||||||
|
"searchBaseUrl": "https://www.animebum.net/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animebum/animebum.js",
|
||||||
|
"type": "Anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeEpisodeSeries",
|
||||||
|
"iconUrl": "https://animeepisodeseries.com/wp-content/uploads/2019/10/cropped-anime-episode-1-192x192.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "English (DUB/SUB)",
|
||||||
|
"streamType": "mp4",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.4shared.com/",
|
||||||
|
"searchBaseUrl": "https://www.4shared.com/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animeepisodeseries/animeepisodeseries.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"note": "Use external player.",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeFHD",
|
||||||
|
"iconUrl": "https://animefhd.net/wp-content/uploads/2024/12/270.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.2",
|
||||||
|
"language": "Portuguese (SUB)",
|
||||||
|
"streamType": "MP4",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animefhd.net/",
|
||||||
|
"searchBaseUrl": "https://animefhd.net/?s=%s",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animefhd/animefhd.js",
|
||||||
|
"asyncJS": true,
|
||||||
|
"type": "anime",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeIndo",
|
||||||
|
"iconUrl": "https://animeindo.skin/favicon/favicon-32x32.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Indonesian",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animeindo.skin/home",
|
||||||
|
"searchBaseUrl": "https://animeindo.skin/home",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animeindo/animeindo.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
async function searchResults(query) {
|
||||||
|
const encodeQuery = keyword => encodeURIComponent(keyword);
|
||||||
|
const searchBaseUrl = "https://anikai.to/browser?keyword=";
|
||||||
|
const baseUrl = "https://anikai.to";
|
||||||
|
|
||||||
|
const posterHrefRegex = /href="[^"]*" class="poster"/g;
|
||||||
|
const titleRegex = /class="title"[^>]*title="[^"]*"/g;
|
||||||
|
const imageRegex = /data-src="[^"]*"/g;
|
||||||
|
const extractHrefRegex = /href="([^"]*)"/;
|
||||||
|
const extractImageRegex = /data-src="([^"]*)"/;
|
||||||
|
const extractTitleRegex = /title="([^"]*)"/;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const encodedQuery = encodeQuery(query);
|
||||||
|
const searchUrl = searchBaseUrl + encodedQuery;
|
||||||
|
const response = await fetchv2(searchUrl);
|
||||||
|
const htmlText = await response.text();
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
const posterMatches = htmlText.match(posterHrefRegex) || [];
|
||||||
|
const titleMatches = htmlText.match(titleRegex) || [];
|
||||||
|
const imageMatches = htmlText.match(imageRegex) || [];
|
||||||
|
|
||||||
|
const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);
|
||||||
|
|
||||||
|
for (let index = 0; index < minLength; index++) {
|
||||||
|
const hrefMatch = posterMatches[index].match(extractHrefRegex);
|
||||||
|
const fullHref = hrefMatch ?
|
||||||
|
(hrefMatch[1].startsWith("http") ? hrefMatch[1] : baseUrl + hrefMatch[1]) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
const imageMatch = imageMatches[index].match(extractImageRegex);
|
||||||
|
const imageSrc = imageMatch
|
||||||
|
? (imageMatch[1].startsWith("http") ? imageMatch[1] : baseUrl + imageMatch[1])
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const titleMatch = titleMatches[index].match(extractTitleRegex);
|
||||||
|
const cleanTitle = titleMatch ?
|
||||||
|
decodeHtmlEntities(titleMatch[1]) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
if (fullHref && imageSrc && cleanTitle) {
|
||||||
|
results.push({
|
||||||
|
href: fullHref,
|
||||||
|
image: imageSrc,
|
||||||
|
title: cleanTitle
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(results);
|
||||||
|
} catch (error) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
href: "",
|
||||||
|
image: "",
|
||||||
|
title: "Search failed: " + error.message
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractDetails(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetchv2(url);
|
||||||
|
const htmlText = await response.text();
|
||||||
|
|
||||||
|
const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
|
||||||
|
const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
|
||||||
|
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
|
||||||
|
aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not available",
|
||||||
|
airdate: "If stream doesn't load try later or disable VPN/DNS"
|
||||||
|
}]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching details:" + error);
|
||||||
|
return [{
|
||||||
|
description: "Error loading description",
|
||||||
|
aliases: "Aliases: Unknown",
|
||||||
|
airdate: "Aired: Unknown"
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractEpisodes(url) {
|
||||||
|
try {
|
||||||
|
const actualUrl = url.replace("Animekai:", "").trim();
|
||||||
|
const htmlText = await (await fetchv2(actualUrl)).text();
|
||||||
|
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
|
||||||
|
if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]);
|
||||||
|
|
||||||
|
const tokenResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(animeIdMatch)}`);
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
const token = tokenData.result;
|
||||||
|
|
||||||
|
const episodeListUrl = `https://anikai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
|
||||||
|
const episodeListData = await (await fetchv2(episodeListUrl)).json();
|
||||||
|
const cleanedHtml = cleanJsonHtml(episodeListData.result);
|
||||||
|
|
||||||
|
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
|
||||||
|
const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];
|
||||||
|
|
||||||
|
const episodes = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
|
||||||
|
number: parseInt(episodeNum, 10),
|
||||||
|
href: `https://anikai.to/ajax/links/list?token=${episodeToken}&_=ENCRYPT_ME`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return JSON.stringify(episodes);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching episodes:" + err);
|
||||||
|
return [{
|
||||||
|
number: 1,
|
||||||
|
href: "Error fetching episodes"
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractStreamUrl(url) {
|
||||||
|
const headers = {
|
||||||
|
"Referer": "https://anikai.to/",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
||||||
|
"Accept": "text/html, */*; q=0.01",
|
||||||
|
"Accept-Language": "en-US,en;q=0.5",
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "same-origin",
|
||||||
|
"Pragma": "no-cache",
|
||||||
|
"Cache-Control": "no-cache"
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tokenMatch = url.match(/token=([^&]+)/);
|
||||||
|
if (!tokenMatch?.[1]) throw new Error("No token found in URL");
|
||||||
|
const rawToken = tokenMatch[1];
|
||||||
|
|
||||||
|
const encTokenRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(rawToken)}`);
|
||||||
|
const encTokenData = await encTokenRes.json();
|
||||||
|
const encryptedToken = encTokenData.result;
|
||||||
|
|
||||||
|
const actualUrl = url.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
|
||||||
|
|
||||||
|
const response = await fetchv2(actualUrl);
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
let ajaxResultHtml = "";
|
||||||
|
try {
|
||||||
|
const parsedAjax = JSON.parse(text);
|
||||||
|
ajaxResultHtml = parsedAjax?.result || "";
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const cleanedHtml = cleanJsonHtml(text);
|
||||||
|
const cleanedAjaxResultHtml = cleanJsonHtml(ajaxResultHtml);
|
||||||
|
const serverHtmlSource = cleanedAjaxResultHtml || cleanedHtml;
|
||||||
|
|
||||||
|
const extractServerIds = (type) => {
|
||||||
|
const regex = new RegExp(`<div class="server-items lang-group" data-id="${type}"[^>]*>([\\s\\S]*?)<\\/div>`);
|
||||||
|
const content = regex.exec(serverHtmlSource)?.[1] ?? "";
|
||||||
|
const spanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>/g;
|
||||||
|
const ids = [];
|
||||||
|
let match;
|
||||||
|
while ((match = spanRegex.exec(content)) !== null) ids.push(match[1]);
|
||||||
|
console.log(`[extractStreamUrl] ${type} ids:`, ids);
|
||||||
|
return ids.length > 1 ? ids[1] : ids[0] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dubType = url.includes("dub") ? "dub" : "sub";
|
||||||
|
const types = dubType === "sub" ? ["sub", "softsub"] : ["dub"];
|
||||||
|
|
||||||
|
const servers = types
|
||||||
|
.map(type => ({ type, lid: extractServerIds(type) }))
|
||||||
|
.filter(s => s.lid);
|
||||||
|
|
||||||
|
const streams = [];
|
||||||
|
const subtitles = [];
|
||||||
|
|
||||||
|
await Promise.all(servers.map(async ({ type, lid }) => {
|
||||||
|
try {
|
||||||
|
const decLidRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(lid)}`);
|
||||||
|
const decLidData = await decLidRes.json();
|
||||||
|
const decodedLid = decLidData.result;
|
||||||
|
|
||||||
|
const viewRes = await fetchv2(`https://anikai.to/ajax/links/view?id=${lid}&_=${decodedLid}`);
|
||||||
|
const viewJson = await viewRes.json();
|
||||||
|
const encodedResult = viewJson.result;
|
||||||
|
|
||||||
|
const decRes = await fetchv2(
|
||||||
|
"https://enc-dec.app/api/dec-kai",
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
"POST",
|
||||||
|
JSON.stringify({ text: encodedResult })
|
||||||
|
);
|
||||||
|
const decJson = await decRes.json();
|
||||||
|
let iframeUrl = decJson.result?.url ?? null;
|
||||||
|
|
||||||
|
if (!iframeUrl) return;
|
||||||
|
|
||||||
|
if (iframeUrl.includes("anikai.to/iframe")) {
|
||||||
|
const iframePageRes = await fetchv2(iframeUrl, headers);
|
||||||
|
const iframePageText = await iframePageRes.text();
|
||||||
|
const iframeSrcMatch = iframePageText.match(/<iframe[^>]+src="([^"]+)"/i);
|
||||||
|
if (iframeSrcMatch && iframeSrcMatch[1]) {
|
||||||
|
iframeUrl = iframeSrcMatch[1];
|
||||||
|
console.log(`[extractStreamUrl] fallback iframeUrl for ${type}:`, iframeUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaUrl = iframeUrl.replace("/e/", "/media/").replace("/e2/", "/media/");
|
||||||
|
|
||||||
|
const mediaRes = await fetchv2(mediaUrl, headers);
|
||||||
|
const mediaJson = await mediaRes.json();
|
||||||
|
const encodedM3u8 = mediaJson?.result;
|
||||||
|
|
||||||
|
if (!encodedM3u8) return;
|
||||||
|
|
||||||
|
const finalRes = await fetchv2(
|
||||||
|
"https://enc-dec.app/api/dec-mega",
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
"POST",
|
||||||
|
JSON.stringify({ text: encodedM3u8, agent: headers["User-Agent"] })
|
||||||
|
);
|
||||||
|
const finalJson = await finalRes.json();
|
||||||
|
|
||||||
|
const sources = finalJson?.result?.sources ?? [];
|
||||||
|
const tracks = finalJson?.result?.tracks ?? [];
|
||||||
|
|
||||||
|
const file = sources[0]?.file ?? null;
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const titleMap = { sub: "Hardsub English", softsub: "Original audio", dub: "Dubbed English" };
|
||||||
|
let pushedQualities = 0;
|
||||||
|
const baseTitle = titleMap[type] || type;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const proxyReqUrl = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(file);
|
||||||
|
const m3u8Response = await fetchv2(proxyReqUrl);
|
||||||
|
const m3u8Text = await m3u8Response.text();
|
||||||
|
const lines = m3u8Text.split('\n');
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||||
|
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
||||||
|
const nameMatch = line.match(/NAME="([^"]+)"/i) || line.match(/BANDWIDTH=(\d+)/i);
|
||||||
|
|
||||||
|
let quality = 'Unknown';
|
||||||
|
if (resolutionMatch) {
|
||||||
|
const [width, height] = resolutionMatch[1].split('x');
|
||||||
|
quality = `${height}p`;
|
||||||
|
} else if (nameMatch && nameMatch[1]) {
|
||||||
|
quality = nameMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i + 1 < lines.length) {
|
||||||
|
let streamPath = lines[i + 1].trim();
|
||||||
|
let absolutePath;
|
||||||
|
if (streamPath.startsWith('/api/')) {
|
||||||
|
absolutePath = 'https://1anime.app' + streamPath;
|
||||||
|
} else if (streamPath.startsWith('http')) {
|
||||||
|
absolutePath = 'https://1anime.app/api/m3u8-proxy?url=' + encodeURIComponent(streamPath);
|
||||||
|
} else {
|
||||||
|
const baseUrl = file.substring(0, file.lastIndexOf('/') + 1);
|
||||||
|
absolutePath = 'https://1anime.app/api/m3u8-proxy?url=' + encodeURIComponent(baseUrl + streamPath);
|
||||||
|
}
|
||||||
|
streams.push({
|
||||||
|
title: `${quality} - ${baseTitle}`,
|
||||||
|
streamUrl: absolutePath
|
||||||
|
});
|
||||||
|
pushedQualities++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to extract qualities:", e);
|
||||||
|
}
|
||||||
|
if (pushedQualities === 0) {
|
||||||
|
streams.push({ title: baseTitle, streamUrl: "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(file) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const track of tracks) {
|
||||||
|
if (track.file && track.label) {
|
||||||
|
subtitles.push({ url: track.file, language: track.label });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`[extractStreamUrl] error for ${type}:`, e.toString());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return JSON.stringify({ streams, subtitles });
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Animekai fetch error:" + error);
|
||||||
|
return "https://error.org";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function cleanHtmlSymbols(string) {
|
||||||
|
if (!string) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
.replace(/’/g, "'")
|
||||||
|
.replace(/–/g, "-")
|
||||||
|
.replace(/&#[0-9]+;/g, "")
|
||||||
|
.replace(/\r?\n|\r/g, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanJsonHtml(jsonHtml) {
|
||||||
|
if (!jsonHtml) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return jsonHtml
|
||||||
|
.replace(/\\"/g, "\"")
|
||||||
|
.replace(/\\'/g, "'")
|
||||||
|
.replace(/\\\\/g, "\\")
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\t/g, "\t")
|
||||||
|
.replace(/\\r/g, "\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeHtmlEntities(text) {
|
||||||
|
if (!text) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/"/g, "\"")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/ /g, " ");
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeKai",
|
||||||
|
"iconUrl": "https://apktodo.io/uploads/2025/5/animekai-icon.jpg",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.4",
|
||||||
|
"language": "English",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animekai.to/",
|
||||||
|
"searchBaseUrl": "https://animekai.to/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animekai/animekai.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
async function searchResults(query) {
|
async function searchResults(query) {
|
||||||
const encodeQuery = keyword => encodeURIComponent(keyword);
|
const encodeQuery = keyword => encodeURIComponent(keyword);
|
||||||
const searchBaseUrl = "https://animekai.to/browser?keyword=";
|
const searchBaseUrl = "https://anikai.to/browser?keyword=";
|
||||||
const baseUrl = "https://animekai.to";
|
const baseUrl = "https://anikai.to";
|
||||||
|
|
||||||
const posterHrefRegex = /href="[^"]*" class="poster"/g;
|
const posterHrefRegex = /href="[^"]*" class="poster"/g;
|
||||||
const titleRegex = /class="title"[^>]*title="[^"]*"/g;
|
const titleRegex = /class="title"[^>]*title="[^"]*"/g;
|
||||||
@@ -13,7 +13,7 @@ async function searchResults(query) {
|
|||||||
try {
|
try {
|
||||||
const encodedQuery = encodeQuery(query);
|
const encodedQuery = encodeQuery(query);
|
||||||
const searchUrl = searchBaseUrl + encodedQuery;
|
const searchUrl = searchBaseUrl + encodedQuery;
|
||||||
const response = await fetchv2(searchUrl);
|
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(searchUrl));
|
||||||
const htmlText = await response.text();
|
const htmlText = await response.text();
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
@@ -40,7 +40,7 @@ async function searchResults(query) {
|
|||||||
if (fullHref && imageSrc && cleanTitle) {
|
if (fullHref && imageSrc && cleanTitle) {
|
||||||
results.push({
|
results.push({
|
||||||
href: fullHref,
|
href: fullHref,
|
||||||
image: imageSrc,
|
image: "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(imageSrc),
|
||||||
title: cleanTitle
|
title: cleanTitle
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ async function searchResults(query) {
|
|||||||
|
|
||||||
async function extractDetails(url) {
|
async function extractDetails(url) {
|
||||||
try {
|
try {
|
||||||
const response = await fetchv2(url);
|
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url));
|
||||||
const htmlText = await response.text();
|
const htmlText = await response.text();
|
||||||
console.log(htmlText);
|
console.log(htmlText);
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ async function extractDetails(url) {
|
|||||||
async function extractEpisodes(url) {
|
async function extractEpisodes(url) {
|
||||||
try {
|
try {
|
||||||
const actualUrl = url.replace("Animekai:", "").trim();
|
const actualUrl = url.replace("Animekai:", "").trim();
|
||||||
const htmlText = await (await fetchv2(actualUrl)).text();
|
const htmlText = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl))).text();
|
||||||
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
|
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
|
||||||
if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]);
|
if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]);
|
||||||
|
|
||||||
@@ -91,8 +91,8 @@ async function extractEpisodes(url) {
|
|||||||
const tokenData = await tokenResponse.json();
|
const tokenData = await tokenResponse.json();
|
||||||
const token = tokenData.result;
|
const token = tokenData.result;
|
||||||
|
|
||||||
const episodeListUrl = `https://animekai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
|
const episodeListUrl = `https://anikai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
|
||||||
const episodeListData = await (await fetchv2(episodeListUrl)).json();
|
const episodeListData = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(episodeListUrl))).json();
|
||||||
const cleanedHtml = cleanJsonHtml(episodeListData.result);
|
const cleanedHtml = cleanJsonHtml(episodeListData.result);
|
||||||
|
|
||||||
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
|
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
|
||||||
@@ -100,7 +100,7 @@ async function extractEpisodes(url) {
|
|||||||
|
|
||||||
const episodes = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
|
const episodes = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
|
||||||
number: parseInt(episodeNum, 10),
|
number: parseInt(episodeNum, 10),
|
||||||
href: `https://animekai.to/ajax/links/list?token=${episodeToken}&_=ENCRYPT_ME`
|
href: `https://anikai.to/ajax/links/list?token=${episodeToken}&_=ENCRYPT_ME`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return JSON.stringify(episodes);
|
return JSON.stringify(episodes);
|
||||||
@@ -125,7 +125,7 @@ async function extractStreamUrl(url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fetchUrl = `${url}`;
|
const fetchUrl = `${url}`;
|
||||||
const response = await fetchv2(fetchUrl);
|
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(fetchUrl));
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
const cleanedHtml = cleanJsonHtml(text);
|
const cleanedHtml = cleanJsonHtml(text);
|
||||||
const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/;
|
const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/;
|
||||||
@@ -164,7 +164,7 @@ async function extractStreamUrl(url) {
|
|||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
type: result.name,
|
type: result.name,
|
||||||
url: `https://animekai.to/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data}`
|
url: `https://anikai.to/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data}`
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -172,7 +172,7 @@ async function extractStreamUrl(url) {
|
|||||||
const streamResponses = await Promise.all(
|
const streamResponses = await Promise.all(
|
||||||
streamUrls.map(async ({ type, url }) => {
|
streamUrls.map(async ({ type, url }) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetchv2(url);
|
const res = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url));
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
return {
|
return {
|
||||||
type: type,
|
type: type,
|
||||||
@@ -223,30 +223,46 @@ async function extractStreamUrl(url) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const decryptedUrls = await processStreams(streamUrls);
|
const decryptedUrls = await processStreams(streamUrls);
|
||||||
const decryptedDub = decryptedUrls.Dub || decryptedUrls.Sub || decryptedUrls.Softsub;
|
const decryptedSoftsub = decryptedUrls.Softsub || decryptedUrls.Dub || decryptedUrls.Sub;
|
||||||
|
|
||||||
console.log(decryptedDub);
|
|
||||||
const headers = {
|
const headers = {
|
||||||
"Referer": "https://animekai.to/",
|
"Referer": "https://anikai.to/",
|
||||||
"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"
|
"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"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (decryptedDub) {
|
if (decryptedSoftsub) {
|
||||||
const response = await fetchv2(decryptedDub.replace("/e/", "/media/"), headers);
|
const mediaUrl = decryptedSoftsub.replace("/e/", "/media/");
|
||||||
|
const response = await fetchv2(mediaUrl, headers);
|
||||||
const responseJson = await response.json();
|
const responseJson = await response.json();
|
||||||
|
|
||||||
const result = responseJson?.result;
|
const result = responseJson?.result;
|
||||||
|
|
||||||
const postData = {
|
const postData = {
|
||||||
"text": result,
|
"text": result,
|
||||||
"Useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
|
"agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalResponse = await fetchv2("https://ilovekai.simplepostrequest.workers.dev/ilovebush", {}, "POST", JSON.stringify(postData));
|
const finalResponse = await fetchv2("https://enc-dec.app/api/dec-mega", { "Content-Type": "application/json" }, "POST", JSON.stringify(postData));
|
||||||
const finalJson = await finalResponse.json();
|
const finalJson = await finalResponse.json();
|
||||||
|
|
||||||
const m3u8Link = finalJson?.result?.sources?.[0]?.file;
|
const m3u8Link = finalJson?.result?.sources?.[0]?.file;
|
||||||
|
|
||||||
return m3u8Link;
|
const tracks = finalJson?.result?.tracks || [];
|
||||||
|
const arabicSub = tracks.find(track => track.label && (track.label.includes("Arabic") || track.label.includes("العربية")));
|
||||||
|
let subtitleUrl = null;
|
||||||
|
if (arabicSub) {
|
||||||
|
subtitleUrl = arabicSub.file;
|
||||||
|
} else {
|
||||||
|
const englishSub = tracks.find(track => track.label && track.label.includes("English"));
|
||||||
|
if (englishSub) {
|
||||||
|
subtitleUrl = englishSub.file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalResult = {
|
||||||
|
stream: m3u8Link,
|
||||||
|
subtitles: subtitleUrl ? "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url="+ encodeURIComponent(subtitleUrl) : null
|
||||||
|
};
|
||||||
|
return JSON.stringify(finalResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
return "error";
|
return "error";
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeKai Arabic",
|
||||||
|
"iconUrl": "https://apktodo.io/uploads/2025/5/animekai-icon.jpg",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.1",
|
||||||
|
"language": "Arabic",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animekai.to/",
|
||||||
|
"searchBaseUrl": "https://animekai.to/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animekai/arabic/animekai.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"note": "Make sure you're on the latest version of Sora.",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
async function searchResults(query) {
|
||||||
|
const encodeQuery = keyword => encodeURIComponent(keyword);
|
||||||
|
const searchBaseUrl = "https://anikai.to/browser?keyword=";
|
||||||
|
const baseUrl = "https://anikai.to";
|
||||||
|
|
||||||
|
const posterHrefRegex = /href="[^"]*" class="poster"/g;
|
||||||
|
const titleRegex = /class="title"[^>]*title="[^"]*"/g;
|
||||||
|
const imageRegex = /data-src="[^"]*"/g;
|
||||||
|
const extractHrefRegex = /href="([^"]*)"/;
|
||||||
|
const extractImageRegex = /data-src="([^"]*)"/;
|
||||||
|
const extractTitleRegex = /title="([^"]*)"/;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const encodedQuery = encodeQuery(query);
|
||||||
|
const searchUrl = searchBaseUrl + encodedQuery;
|
||||||
|
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(searchUrl));
|
||||||
|
const htmlText = await response.text();
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
const posterMatches = htmlText.match(posterHrefRegex) || [];
|
||||||
|
const titleMatches = htmlText.match(titleRegex) || [];
|
||||||
|
const imageMatches = htmlText.match(imageRegex) || [];
|
||||||
|
|
||||||
|
const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);
|
||||||
|
|
||||||
|
for (let index = 0; index < minLength; index++) {
|
||||||
|
const hrefMatch = posterMatches[index].match(extractHrefRegex);
|
||||||
|
const fullHref = hrefMatch ?
|
||||||
|
(hrefMatch[1].startsWith("http") ? hrefMatch[1] : baseUrl + hrefMatch[1]) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
const imageMatch = imageMatches[index].match(extractImageRegex);
|
||||||
|
const imageSrc = imageMatch ? imageMatch[1] : null;
|
||||||
|
|
||||||
|
const titleMatch = titleMatches[index].match(extractTitleRegex);
|
||||||
|
const cleanTitle = titleMatch ?
|
||||||
|
decodeHtmlEntities(titleMatch[1]) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
if (fullHref && imageSrc && cleanTitle) {
|
||||||
|
results.push({
|
||||||
|
href: fullHref,
|
||||||
|
image: "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(imageSrc),
|
||||||
|
title: cleanTitle
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(results);
|
||||||
|
} catch (error) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
href: "",
|
||||||
|
image: "",
|
||||||
|
title: "Search failed: " + error.message
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractDetails(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url));
|
||||||
|
const htmlText = await response.text();
|
||||||
|
console.log(htmlText);
|
||||||
|
|
||||||
|
const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
|
||||||
|
const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
|
||||||
|
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
|
||||||
|
aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not available",
|
||||||
|
airdate: "If stream doesn't load try later or disable VPN/DNS"
|
||||||
|
}]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching details:" + error);
|
||||||
|
return [{
|
||||||
|
description: "Error loading description",
|
||||||
|
aliases: "Aliases: Unknown",
|
||||||
|
airdate: "Aired: Unknown"
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractEpisodes(url) {
|
||||||
|
try {
|
||||||
|
const actualUrl = url.replace("Animekai:", "").trim();
|
||||||
|
const htmlText = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl))).text();
|
||||||
|
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
|
||||||
|
if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]);
|
||||||
|
|
||||||
|
const tokenResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(animeIdMatch)}`);
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
const token = tokenData.result;
|
||||||
|
|
||||||
|
const episodeListUrl = `https://anikai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
|
||||||
|
const episodeListData = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(episodeListUrl))).json();
|
||||||
|
const cleanedHtml = cleanJsonHtml(episodeListData.result);
|
||||||
|
|
||||||
|
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
|
||||||
|
const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];
|
||||||
|
|
||||||
|
const episodes = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
|
||||||
|
number: parseInt(episodeNum, 10),
|
||||||
|
href: `https://anikai.to/ajax/links/list?token=${episodeToken}&_=ENCRYPT_ME`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return JSON.stringify(episodes);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching episodes:" + err);
|
||||||
|
return [{
|
||||||
|
number: 1,
|
||||||
|
href: "Error fetching episodes"
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractStreamUrl(url) {
|
||||||
|
const headers = {
|
||||||
|
"Referer": "https://anikai.to/",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
||||||
|
"Accept": "text/html, */*; q=0.01",
|
||||||
|
"Accept-Language": "en-US,en;q=0.5",
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "same-origin",
|
||||||
|
"Pragma": "no-cache",
|
||||||
|
"Cache-Control": "no-cache"
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tokenMatch = url.match(/token=([^&]+)/);
|
||||||
|
if (!tokenMatch?.[1]) throw new Error("No token found in URL");
|
||||||
|
const rawToken = tokenMatch[1];
|
||||||
|
|
||||||
|
const encTokenRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(rawToken)}`);
|
||||||
|
const encTokenData = await encTokenRes.json();
|
||||||
|
const encryptedToken = encTokenData.result;
|
||||||
|
|
||||||
|
const actualUrl = url.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
|
||||||
|
|
||||||
|
const response = await fetchv2(actualUrl);
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
let ajaxResultHtml = "";
|
||||||
|
try {
|
||||||
|
const parsedAjax = JSON.parse(text);
|
||||||
|
ajaxResultHtml = parsedAjax?.result || "";
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const cleanedHtml = cleanJsonHtml(text);
|
||||||
|
const cleanedAjaxResultHtml = cleanJsonHtml(ajaxResultHtml);
|
||||||
|
const serverHtmlSource = cleanedAjaxResultHtml || cleanedHtml;
|
||||||
|
|
||||||
|
const extractServerIds = (type) => {
|
||||||
|
const regex = new RegExp(`<div class="server-items lang-group" data-id="${type}"[^>]*>([\\s\\S]*?)<\\/div>`);
|
||||||
|
const content = regex.exec(serverHtmlSource)?.[1] ?? "";
|
||||||
|
const spanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>/g;
|
||||||
|
const ids = [];
|
||||||
|
let match;
|
||||||
|
while ((match = spanRegex.exec(content)) !== null) ids.push(match[1]);
|
||||||
|
return ids.length > 1 ? ids[1] : ids[0] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const types = ["dub"];
|
||||||
|
|
||||||
|
const servers = types
|
||||||
|
.map(type => ({ type, lid: extractServerIds(type) }))
|
||||||
|
.filter(s => s.lid);
|
||||||
|
|
||||||
|
const streams = [];
|
||||||
|
const subtitles = [];
|
||||||
|
|
||||||
|
await Promise.all(servers.map(async ({ type, lid }) => {
|
||||||
|
try {
|
||||||
|
const decLidRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(lid)}`);
|
||||||
|
const decLidData = await decLidRes.json();
|
||||||
|
const decodedLid = decLidData.result;
|
||||||
|
|
||||||
|
const viewUrl = `https://anikai.to/ajax/links/view?id=${lid}&_=${decodedLid}`;
|
||||||
|
const viewRes = await fetchv2(viewUrl);
|
||||||
|
const viewJson = await viewRes.json();
|
||||||
|
const encodedResult = viewJson.result;
|
||||||
|
|
||||||
|
const decRes = await fetchv2(
|
||||||
|
"https://enc-dec.app/api/dec-kai",
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
"POST",
|
||||||
|
JSON.stringify({ text: encodedResult })
|
||||||
|
);
|
||||||
|
const decJson = await decRes.json();
|
||||||
|
let iframeUrl = decJson.result?.url ?? null;
|
||||||
|
|
||||||
|
if (!iframeUrl) return;
|
||||||
|
|
||||||
|
if (iframeUrl.includes("anikai.to/iframe")) {
|
||||||
|
const iframePageRes = await fetchv2(iframeUrl, headers);
|
||||||
|
const iframePageText = await iframePageRes.text();
|
||||||
|
const iframeSrcMatch = iframePageText.match(/<iframe[^>]+src="([^"]+)"/i);
|
||||||
|
if (iframeSrcMatch && iframeSrcMatch[1]) {
|
||||||
|
iframeUrl = iframeSrcMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaUrl = iframeUrl.replace("/e/", "/media/").replace("/e2/", "/media/");
|
||||||
|
|
||||||
|
const mediaRes = await fetchv2(mediaUrl, headers);
|
||||||
|
const mediaJson = await mediaRes.json();
|
||||||
|
const encodedM3u8 = mediaJson?.result;
|
||||||
|
|
||||||
|
if (!encodedM3u8) return;
|
||||||
|
|
||||||
|
const finalRes = await fetchv2(
|
||||||
|
"https://enc-dec.app/api/dec-mega",
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
"POST",
|
||||||
|
JSON.stringify({ text: encodedM3u8, agent: headers["User-Agent"] })
|
||||||
|
);
|
||||||
|
const finalJson = await finalRes.json();
|
||||||
|
|
||||||
|
const sources = finalJson?.result?.sources ?? [];
|
||||||
|
const tracks = finalJson?.result?.tracks ?? [];
|
||||||
|
|
||||||
|
const file = sources[0]?.file ?? null;
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const titleMap = { dub: "Dubbed English" };
|
||||||
|
let pushedQualities = 0;
|
||||||
|
const baseTitle = titleMap[type] || type;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const m3u8Response = await fetchv2("https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(file));
|
||||||
|
const m3u8Text = await m3u8Response.text();
|
||||||
|
const lines = m3u8Text.split('\n');
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||||
|
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
||||||
|
let quality = 'Unknown';
|
||||||
|
if (resolutionMatch) {
|
||||||
|
const [width, height] = resolutionMatch[1].split('x');
|
||||||
|
quality = `${height}p`;
|
||||||
|
}
|
||||||
|
if (i + 1 < lines.length) {
|
||||||
|
let streamPath = lines[i + 1].trim();
|
||||||
|
let absolutePath;
|
||||||
|
if (streamPath.startsWith('/api/')) {
|
||||||
|
absolutePath = 'https://1anime.app' + streamPath;
|
||||||
|
} else if (streamPath.startsWith('http')) {
|
||||||
|
absolutePath = 'https://1anime.app/api/m3u8-proxy?url=' + encodeURIComponent(streamPath);
|
||||||
|
} else {
|
||||||
|
const baseUrl = file.substring(0, file.lastIndexOf('/') + 1);
|
||||||
|
absolutePath = 'https://1anime.app/api/m3u8-proxy?url=' + encodeURIComponent(baseUrl + streamPath);
|
||||||
|
}
|
||||||
|
streams.push({
|
||||||
|
title: `${quality} - ${baseTitle}`,
|
||||||
|
streamUrl: absolutePath
|
||||||
|
});
|
||||||
|
pushedQualities++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to extract qualities:", e);
|
||||||
|
}
|
||||||
|
if (pushedQualities === 0) {
|
||||||
|
streams.push({ title: baseTitle, streamUrl: "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(file) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const track of tracks) {
|
||||||
|
if (track.file && track.label) {
|
||||||
|
subtitles.push({ url: track.file, language: track.label });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`[extractStreamUrl] error for ${type}:`, e.toString());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return JSON.stringify({ streams, subtitles });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Animekai fetch error:" + error);
|
||||||
|
return "https://error.org";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanHtmlSymbols(string) {
|
||||||
|
if (!string) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
.replace(/’/g, "'")
|
||||||
|
.replace(/–/g, "-")
|
||||||
|
.replace(/&#[0-9]+;/g, "")
|
||||||
|
.replace(/\r?\n|\r/g, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanJsonHtml(jsonHtml) {
|
||||||
|
if (!jsonHtml) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return jsonHtml
|
||||||
|
.replace(/\\"/g, "\"")
|
||||||
|
.replace(/\\'/g, "'")
|
||||||
|
.replace(/\\\\/g, "\\")
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\t/g, "\t")
|
||||||
|
.replace(/\\r/g, "\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeHtmlEntities(text) {
|
||||||
|
if (!text) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/"/g, "\"")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/ /g, " ");
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeKai (Dub)",
|
||||||
|
"iconUrl": "https://apktodo.io/uploads/2025/5/animekai-icon.jpg",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.1.7",
|
||||||
|
"language": "English",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animekai.to/",
|
||||||
|
"searchBaseUrl": "https://animekai.to/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animekai/dub/animekai.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"note": "Make sure you're on the latest version of Sora.",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
async function searchResults(query) {
|
||||||
|
const encodeQuery = keyword => encodeURIComponent(keyword);
|
||||||
|
const searchBaseUrl = "https://anikai.to/browser?keyword=";
|
||||||
|
const baseUrl = "https://anikai.to";
|
||||||
|
|
||||||
|
const posterHrefRegex = /href="[^"]*" class="poster"/g;
|
||||||
|
const titleRegex = /class="title"[^>]*title="[^"]*"/g;
|
||||||
|
const imageRegex = /data-src="[^"]*"/g;
|
||||||
|
const extractHrefRegex = /href="([^"]*)"/;
|
||||||
|
const extractImageRegex = /data-src="([^"]*)"/;
|
||||||
|
const extractTitleRegex = /title="([^"]*)"/;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const encodedQuery = encodeQuery(query);
|
||||||
|
const searchUrl = searchBaseUrl + encodedQuery;
|
||||||
|
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(searchUrl));
|
||||||
|
const htmlText = await response.text();
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
const posterMatches = htmlText.match(posterHrefRegex) || [];
|
||||||
|
const titleMatches = htmlText.match(titleRegex) || [];
|
||||||
|
const imageMatches = htmlText.match(imageRegex) || [];
|
||||||
|
|
||||||
|
const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);
|
||||||
|
|
||||||
|
for (let index = 0; index < minLength; index++) {
|
||||||
|
const hrefMatch = posterMatches[index].match(extractHrefRegex);
|
||||||
|
const fullHref = hrefMatch ?
|
||||||
|
(hrefMatch[1].startsWith("http") ? hrefMatch[1] : baseUrl + hrefMatch[1]) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
const imageMatch = imageMatches[index].match(extractImageRegex);
|
||||||
|
const imageSrc = imageMatch ? imageMatch[1] : null;
|
||||||
|
|
||||||
|
const titleMatch = titleMatches[index].match(extractTitleRegex);
|
||||||
|
const cleanTitle = titleMatch ?
|
||||||
|
decodeHtmlEntities(titleMatch[1]) :
|
||||||
|
null;
|
||||||
|
|
||||||
|
if (fullHref && imageSrc && cleanTitle) {
|
||||||
|
results.push({
|
||||||
|
href: fullHref,
|
||||||
|
image: "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(imageSrc),
|
||||||
|
title: cleanTitle
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(results);
|
||||||
|
} catch (error) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
href: "",
|
||||||
|
image: "",
|
||||||
|
title: "Search failed: " + error.message
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractDetails(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url));
|
||||||
|
const htmlText = await response.text();
|
||||||
|
console.log(htmlText);
|
||||||
|
|
||||||
|
const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
|
||||||
|
const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
|
||||||
|
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
|
||||||
|
aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not available",
|
||||||
|
airdate: "If stream doesn't load try later or disable VPN/DNS"
|
||||||
|
}]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching details:" + error);
|
||||||
|
return [{
|
||||||
|
description: "Error loading description",
|
||||||
|
aliases: "Aliases: Unknown",
|
||||||
|
airdate: "Aired: Unknown"
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractEpisodes(url) {
|
||||||
|
try {
|
||||||
|
const actualUrl = url.replace("Animekai:", "").trim();
|
||||||
|
const htmlText = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl))).text();
|
||||||
|
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
|
||||||
|
if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]);
|
||||||
|
|
||||||
|
const tokenResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(animeIdMatch)}`);
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
const token = tokenData.result;
|
||||||
|
|
||||||
|
const episodeListUrl = `https://anikai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
|
||||||
|
const episodeListData = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(episodeListUrl))).json();
|
||||||
|
const cleanedHtml = cleanJsonHtml(episodeListData.result);
|
||||||
|
|
||||||
|
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
|
||||||
|
const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];
|
||||||
|
|
||||||
|
const episodes = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
|
||||||
|
number: parseInt(episodeNum, 10),
|
||||||
|
href: `https://anikai.to/ajax/links/list?token=${episodeToken}&_=ENCRYPT_ME`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return JSON.stringify(episodes);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching episodes:" + err);
|
||||||
|
return [{
|
||||||
|
number: 1,
|
||||||
|
href: "Error fetching episodes"
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractStreamUrl(url) {
|
||||||
|
const headers = {
|
||||||
|
"Referer": "https://anikai.to/",
|
||||||
|
"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"
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tokenMatch = url.match(/token=([^&]+)/);
|
||||||
|
if (!tokenMatch?.[1]) throw new Error("No token found in URL");
|
||||||
|
const rawToken = tokenMatch[1];
|
||||||
|
|
||||||
|
const encTokenRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(rawToken)}`);
|
||||||
|
const encTokenData = await encTokenRes.json();
|
||||||
|
const encryptedToken = encTokenData.result;
|
||||||
|
|
||||||
|
const actualUrl = url.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
|
||||||
|
|
||||||
|
const response = await fetchv2(actualUrl);
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
let ajaxResultHtml = "";
|
||||||
|
try {
|
||||||
|
const parsedAjax = JSON.parse(text);
|
||||||
|
ajaxResultHtml = parsedAjax?.result || "";
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const cleanedHtml = cleanJsonHtml(text);
|
||||||
|
const cleanedAjaxResultHtml = cleanJsonHtml(ajaxResultHtml);
|
||||||
|
const serverHtmlSource = cleanedAjaxResultHtml || cleanedHtml;
|
||||||
|
|
||||||
|
const extractServerIds = (type) => {
|
||||||
|
const regex = new RegExp(`<div class="server-items lang-group" data-id="${type}"[^>]*>([\\s\\S]*?)<\\/div>`);
|
||||||
|
const content = regex.exec(serverHtmlSource)?.[1] ?? "";
|
||||||
|
const spanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>/g;
|
||||||
|
const ids = [];
|
||||||
|
let match;
|
||||||
|
while ((match = spanRegex.exec(content)) !== null) ids.push(match[1]);
|
||||||
|
return ids.length > 1 ? ids[1] : ids[0] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const types = ["sub"];
|
||||||
|
|
||||||
|
const servers = types
|
||||||
|
.map(type => ({ type, lid: extractServerIds(type) }))
|
||||||
|
.filter(s => s.lid);
|
||||||
|
|
||||||
|
const streams = [];
|
||||||
|
const subtitles = [];
|
||||||
|
|
||||||
|
await Promise.all(servers.map(async ({ type, lid }) => {
|
||||||
|
try {
|
||||||
|
const decLidRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(lid)}`);
|
||||||
|
const decLidData = await decLidRes.json();
|
||||||
|
const decodedLid = decLidData.result;
|
||||||
|
|
||||||
|
const viewUrl = `https://anikai.to/ajax/links/view?id=${lid}&_=${decodedLid}`;
|
||||||
|
const viewRes = await fetchv2(viewUrl);
|
||||||
|
const viewJson = await viewRes.json();
|
||||||
|
const encodedResult = viewJson.result;
|
||||||
|
|
||||||
|
const decRes = await fetchv2(
|
||||||
|
"https://enc-dec.app/api/dec-kai",
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
"POST",
|
||||||
|
JSON.stringify({ text: encodedResult })
|
||||||
|
);
|
||||||
|
const decJson = await decRes.json();
|
||||||
|
let iframeUrl = decJson.result?.url ?? null;
|
||||||
|
|
||||||
|
if (!iframeUrl) return;
|
||||||
|
|
||||||
|
if (iframeUrl.includes("anikai.to/iframe")) {
|
||||||
|
const iframePageRes = await fetchv2(iframeUrl, headers);
|
||||||
|
const iframePageText = await iframePageRes.text();
|
||||||
|
const iframeSrcMatch = iframePageText.match(/<iframe[^>]+src="([^"]+)"/i);
|
||||||
|
if (iframeSrcMatch && iframeSrcMatch[1]) {
|
||||||
|
iframeUrl = iframeSrcMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaUrl = iframeUrl.replace("/e/", "/media/").replace("/e2/", "/media/");
|
||||||
|
|
||||||
|
const mediaRes = await fetchv2(mediaUrl, headers);
|
||||||
|
const mediaJson = await mediaRes.json();
|
||||||
|
const encodedM3u8 = mediaJson?.result;
|
||||||
|
|
||||||
|
if (!encodedM3u8) return;
|
||||||
|
|
||||||
|
const finalRes = await fetchv2(
|
||||||
|
"https://enc-dec.app/api/dec-mega",
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
"POST",
|
||||||
|
JSON.stringify({ text: encodedM3u8, agent: headers["User-Agent"] })
|
||||||
|
);
|
||||||
|
const finalJson = await finalRes.json();
|
||||||
|
|
||||||
|
const sources = finalJson?.result?.sources ?? [];
|
||||||
|
const tracks = finalJson?.result?.tracks ?? [];
|
||||||
|
|
||||||
|
const file = sources[0]?.file ?? null;
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const titleMap = { sub: "Hardsub English" };
|
||||||
|
let pushedQualities = 0;
|
||||||
|
const baseTitle = titleMap[type] || type;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const m3u8Response = await fetchv2("https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(file));
|
||||||
|
const m3u8Text = await m3u8Response.text();
|
||||||
|
const lines = m3u8Text.split('\n');
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||||
|
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
||||||
|
let quality = 'Unknown';
|
||||||
|
if (resolutionMatch) {
|
||||||
|
const [width, height] = resolutionMatch[1].split('x');
|
||||||
|
quality = `${height}p`;
|
||||||
|
}
|
||||||
|
if (i + 1 < lines.length) {
|
||||||
|
let streamPath = lines[i + 1].trim();
|
||||||
|
let absolutePath;
|
||||||
|
if (streamPath.startsWith('/api/')) {
|
||||||
|
absolutePath = 'https://1anime.app' + streamPath;
|
||||||
|
} else if (streamPath.startsWith('http')) {
|
||||||
|
absolutePath = 'https://1anime.app/api/m3u8-proxy?url=' + encodeURIComponent(streamPath);
|
||||||
|
} else {
|
||||||
|
const baseUrl = file.substring(0, file.lastIndexOf('/') + 1);
|
||||||
|
absolutePath = 'https://1anime.app/api/m3u8-proxy?url=' + encodeURIComponent(baseUrl + streamPath);
|
||||||
|
}
|
||||||
|
streams.push({
|
||||||
|
title: `${quality} - ${baseTitle}`,
|
||||||
|
streamUrl: absolutePath
|
||||||
|
});
|
||||||
|
pushedQualities++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to extract qualities:", e);
|
||||||
|
}
|
||||||
|
if (pushedQualities === 0) {
|
||||||
|
streams.push({ title: baseTitle, streamUrl: "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(file) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const track of tracks) {
|
||||||
|
if (track.file && track.label) {
|
||||||
|
subtitles.push({ url: track.file, language: track.label });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`[extractStreamUrl] error for ${type}:`, e.toString());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return JSON.stringify({ streams, subtitles });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Animekai fetch error:" + error);
|
||||||
|
return "https://error.org";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanHtmlSymbols(string) {
|
||||||
|
if (!string) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
.replace(/’/g, "'")
|
||||||
|
.replace(/–/g, "-")
|
||||||
|
.replace(/&#[0-9]+;/g, "")
|
||||||
|
.replace(/\r?\n|\r/g, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanJsonHtml(jsonHtml) {
|
||||||
|
if (!jsonHtml) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return jsonHtml
|
||||||
|
.replace(/\\"/g, "\"")
|
||||||
|
.replace(/\\'/g, "'")
|
||||||
|
.replace(/\\\\/g, "\\")
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\t/g, "\t")
|
||||||
|
.replace(/\\r/g, "\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeHtmlEntities(text) {
|
||||||
|
if (!text) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/"/g, "\"")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/ /g, " ");
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeKai (Hardsub)",
|
||||||
|
"iconUrl": "https://apktodo.io/uploads/2025/5/animekai-icon.jpg",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.1.6",
|
||||||
|
"language": "English",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animekai.to/",
|
||||||
|
"searchBaseUrl": "https://animekai.to/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animekai/hardsub/animekai.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"note": "Make sure you're on the latest version of Sora.",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Animeler",
|
||||||
|
"iconUrl": "https://animeler.pw/wp-content/uploads/cropped-animelerpw-224x224.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.1",
|
||||||
|
"language": "Turkish",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animeler.pw/",
|
||||||
|
"searchBaseUrl": "https://animeler.pw/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animeler/animeler.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeMeow",
|
||||||
|
"iconUrl": "https://files.catbox.moe/5phbht.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animemeow.xyz/",
|
||||||
|
"searchBaseUrl": "https://animemeow.xyz/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animemeow/animemeow.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeNana",
|
||||||
|
"iconUrl": "https://animenana.com/favicon.ico",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.1",
|
||||||
|
"language": "English (Hardsub)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animenana.com/",
|
||||||
|
"searchBaseUrl": "https://animenana.com/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animenana/animenana.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeNix",
|
||||||
|
"iconUrl": "https://i3.wp.com/animenix.com/wp-content/uploads/2024/11/cropped-favicon-1-192x192.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.yourupload.com/",
|
||||||
|
"searchBaseUrl": "https://www.yourupload.com/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animenix/animenix.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Animeq",
|
||||||
|
"iconUrl": "https://animeq.blog/wp-content/uploads/2025/06/Favicon-AnimeQ-1.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Portuguese",
|
||||||
|
"streamType": "mp4",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animeq.blog/",
|
||||||
|
"searchBaseUrl": "https://animeq.blog/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animeq/animeq.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeSaturn",
|
||||||
|
"iconUrl": "https://www.animesaturn.cx/immagini/apple-touch-icon.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Italian (SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.animesaturn.cx/",
|
||||||
|
"searchBaseUrl": "https://www.animesaturn.cx/?q=%s",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animesaturn/animesaturn.js",
|
||||||
|
"asyncJS": true,
|
||||||
|
"type": "anime",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimesDrive",
|
||||||
|
"iconUrl": "https://animesdrive.blog/wp-content/uploads/2025/08/cropped-ico-1-192x192.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Portuguese",
|
||||||
|
"streamType": "mp4",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animesdrive.blog/",
|
||||||
|
"searchBaseUrl": "https://animesdrive.blog/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animesdrive/animesdrive.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimesOnline",
|
||||||
|
"iconUrl": "https://animesonline.cloud/wp-content/uploads/2025/06/Icone.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Portuguese",
|
||||||
|
"streamType": "mp4",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animesonline.cloud",
|
||||||
|
"searchBaseUrl": "https://animesonline.cloud",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animesonline/animesonline.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimesRoll",
|
||||||
|
"iconUrl": "https://cdn.countryflags.com/thumbs/portugal/flag-button-round-250.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Portuguese",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.anroll.net/",
|
||||||
|
"searchBaseUrl": "https://www.anroll.net/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animesroll/animesroll.js",
|
||||||
|
"type": "animes",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
// AnimeVost for Sora (AsyncJS)
|
||||||
|
// Author: emp0ry
|
||||||
|
// Version: 1.0.2
|
||||||
|
|
||||||
|
const API_BASE = "https://api.animevost.org/v1/";
|
||||||
|
const FORM_CT = "application/x-www-form-urlencoded; charset=UTF-8";
|
||||||
|
const SITE_BASE = "https://animevost.org";
|
||||||
|
const DEFAULT_SUBTITLE = "https://none.com";
|
||||||
|
|
||||||
|
// --- utils ---
|
||||||
|
function encodeForm(fields) {
|
||||||
|
return Object.keys(fields)
|
||||||
|
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(fields[k])}`)
|
||||||
|
.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function postForm(url, fields) {
|
||||||
|
const bodyStr = encodeForm(fields);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resA = await fetchv2(url, { "Content-Type": FORM_CT }, "POST", bodyStr);
|
||||||
|
if (resA && typeof resA.text === "function") return resA;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
return await fetchv2(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": FORM_CT },
|
||||||
|
body: bodyStr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseJsonSafe(res) {
|
||||||
|
const txt = await res.text();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(txt);
|
||||||
|
} catch (_) {
|
||||||
|
return JSON.parse(txt.replace(/^\uFEFF/, "").trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanTitle(raw) {
|
||||||
|
if (!raw || typeof raw !== "string") return "Unknown title";
|
||||||
|
|
||||||
|
let t = raw.split(" /")[0];
|
||||||
|
return t.replace(/\s*\[.*?\]\s*$/g, "").trim() || "Unknown title";
|
||||||
|
}
|
||||||
|
|
||||||
|
function htmlToText(html) {
|
||||||
|
if (!html || typeof html !== "string") return "";
|
||||||
|
|
||||||
|
return html
|
||||||
|
.replace(/<br\s*\/?>/gi, "\n")
|
||||||
|
.replace(/<\/p>/gi, "\n")
|
||||||
|
.replace(/<[^>]+>/g, "")
|
||||||
|
.replace(/\n{3,}/g, "\n\n")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanUrl(url) {
|
||||||
|
const s = String(url || "").trim();
|
||||||
|
return s || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _safeJsonParse(value, fallback) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch (_) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack payload into href to avoid relying on global state.
|
||||||
|
function makeHrefFromPayload(obj) {
|
||||||
|
return `animevost://payload/${encodeURIComponent(JSON.stringify(obj || {}))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPayloadFromHref(href) {
|
||||||
|
const m = String(href || "").match(/^animevost:\/\/payload\/(.+)$/);
|
||||||
|
if (!m) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(decodeURIComponent(m[1]));
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIdFromAny(hrefOrId) {
|
||||||
|
const p = readPayloadFromHref(hrefOrId);
|
||||||
|
if (p && p.id) return parseInt(p.id, 10);
|
||||||
|
|
||||||
|
const m1 = String(hrefOrId || "").match(/^animevost:\/\/release\/(\d+)$/);
|
||||||
|
if (m1) return parseInt(m1[1], 10);
|
||||||
|
|
||||||
|
const m2 = String(hrefOrId || "").match(/[?&]id=(\d+)/);
|
||||||
|
if (m2) return parseInt(m2[1], 10);
|
||||||
|
|
||||||
|
if (/^\d+$/.test(String(hrefOrId || ""))) {
|
||||||
|
return parseInt(hrefOrId, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same packing style as AniLiberty / SameBand.
|
||||||
|
function _packEpisode(payload) {
|
||||||
|
return "animevost:" + encodeURIComponent(JSON.stringify(payload || {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function _unpackEpisode(href) {
|
||||||
|
const raw = String(href || "");
|
||||||
|
if (!raw.startsWith("animevost:")) return null;
|
||||||
|
|
||||||
|
return _safeJsonParse(decodeURIComponent(raw.slice("animevost:".length)), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _streamHeaders() {
|
||||||
|
return {
|
||||||
|
"User-Agent": "Mozilla/5.0",
|
||||||
|
"Referer": SITE_BASE + "/"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- search: POST /search ---
|
||||||
|
async function searchResults(keyword) {
|
||||||
|
try {
|
||||||
|
let res = await postForm(API_BASE + "search", { name: String(keyword) });
|
||||||
|
let json = await parseJsonSafe(res);
|
||||||
|
|
||||||
|
if (json?.error || !Array.isArray(json?.data) || json.data.length === 0) {
|
||||||
|
res = await postForm(API_BASE + "search", { name: `"${String(keyword)}"` });
|
||||||
|
json = await parseJsonSafe(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json?.error) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = Array.isArray(json?.data) ? json.data : [];
|
||||||
|
if (!list.length) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tiles = list.map(item => {
|
||||||
|
const payload = {
|
||||||
|
id: item.id,
|
||||||
|
title: cleanTitle(item.title),
|
||||||
|
description: htmlToText(item.description || ""),
|
||||||
|
year: item.year || "",
|
||||||
|
type: item.type || "",
|
||||||
|
image: item.urlImagePreview || ""
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: payload.title,
|
||||||
|
image: payload.image,
|
||||||
|
href: makeHrefFromPayload(payload)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return JSON.stringify(tiles);
|
||||||
|
} catch (_) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- details: from payload ---
|
||||||
|
async function extractDetails(href) {
|
||||||
|
try {
|
||||||
|
const p = readPayloadFromHref(href);
|
||||||
|
|
||||||
|
const out = [{
|
||||||
|
description: p?.description || "No description available.",
|
||||||
|
aliases: `Type: ${p?.type || "Unknown"}`,
|
||||||
|
airdate: p?.year ? String(p.year) : "Unknown"
|
||||||
|
}];
|
||||||
|
|
||||||
|
return JSON.stringify(out);
|
||||||
|
} catch (_) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- episodes: POST /playlist ---
|
||||||
|
async function extractEpisodes(href) {
|
||||||
|
try {
|
||||||
|
const p = readPayloadFromHref(href);
|
||||||
|
const id = p?.id ?? parseIdFromAny(href);
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await postForm(API_BASE + "playlist", { id: String(id) });
|
||||||
|
const arr = await parseJsonSafe(res);
|
||||||
|
|
||||||
|
if (!Array.isArray(arr)) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = arr.map((ep, idx) => {
|
||||||
|
const name = ep?.name || "";
|
||||||
|
const m = name.match(/(\d+)/);
|
||||||
|
const num = m ? parseInt(m[1], 10) : (idx + 1);
|
||||||
|
|
||||||
|
// AnimeVost usually provides only hd/std.
|
||||||
|
// Match Sora picker format used in AniLiberty/SameBand:
|
||||||
|
// hd -> 720p
|
||||||
|
// std -> 480p
|
||||||
|
const url1080 = null;
|
||||||
|
const url720 = cleanUrl(ep?.hd);
|
||||||
|
const url480 = cleanUrl(ep?.std);
|
||||||
|
|
||||||
|
const fallback = url720 || url480;
|
||||||
|
if (!fallback) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
href: _packEpisode({
|
||||||
|
url1080,
|
||||||
|
url720,
|
||||||
|
url480,
|
||||||
|
fallback
|
||||||
|
}),
|
||||||
|
number: num,
|
||||||
|
title: name || `Episode ${num}`,
|
||||||
|
image: ep?.preview || ""
|
||||||
|
};
|
||||||
|
}).filter(Boolean);
|
||||||
|
|
||||||
|
return JSON.stringify(out);
|
||||||
|
} catch (_) {
|
||||||
|
return JSON.stringify([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- stream: returns quality picker JSON ---
|
||||||
|
async function extractStreamUrl(href) {
|
||||||
|
try {
|
||||||
|
const payload = _unpackEpisode(href);
|
||||||
|
|
||||||
|
// Backward compatibility for old AnimeVost episode href format.
|
||||||
|
if (!payload) {
|
||||||
|
return href;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url1080 = cleanUrl(payload.url1080);
|
||||||
|
const url720 = cleanUrl(payload.url720);
|
||||||
|
const url480 = cleanUrl(payload.url480);
|
||||||
|
const fallback = cleanUrl(payload.fallback);
|
||||||
|
|
||||||
|
const headers = _streamHeaders();
|
||||||
|
const streams = [];
|
||||||
|
|
||||||
|
if (url1080) {
|
||||||
|
streams.push({
|
||||||
|
title: "1080p",
|
||||||
|
streamUrl: url1080,
|
||||||
|
url1080,
|
||||||
|
url720,
|
||||||
|
url480,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url720) {
|
||||||
|
streams.push({
|
||||||
|
title: "720p",
|
||||||
|
streamUrl: url720,
|
||||||
|
url1080,
|
||||||
|
url720,
|
||||||
|
url480,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url480) {
|
||||||
|
streams.push({
|
||||||
|
title: "480p",
|
||||||
|
streamUrl: url480,
|
||||||
|
url1080,
|
||||||
|
url720,
|
||||||
|
url480,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!streams.length && fallback) {
|
||||||
|
streams.push({
|
||||||
|
title: "720p",
|
||||||
|
streamUrl: fallback,
|
||||||
|
url1080: null,
|
||||||
|
url720: fallback,
|
||||||
|
url480: null,
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
streams,
|
||||||
|
subtitle: DEFAULT_SUBTITLE
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
return JSON.stringify({
|
||||||
|
streams: [],
|
||||||
|
subtitle: DEFAULT_SUBTITLE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
"name": "emp0ry",
|
"name": "emp0ry",
|
||||||
"icon": "https://avatars.githubusercontent.com/u/64217088"
|
"icon": "https://avatars.githubusercontent.com/u/64217088"
|
||||||
},
|
},
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"language": "Russian",
|
"language": "Russian",
|
||||||
"streamType": "MP4",
|
"streamType": "MP4",
|
||||||
"quality": "720p",
|
"quality": "720p",
|
||||||
@@ -16,5 +16,10 @@
|
|||||||
"streamAsyncJS": false,
|
"streamAsyncJS": false,
|
||||||
"softsub": false,
|
"softsub": false,
|
||||||
"type": "anime",
|
"type": "anime",
|
||||||
"downloadSupport": false
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsShirox": true
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimeYTX",
|
||||||
|
"iconUrl": "https://i1.wp.com/animeytx.net/wp-content/uploads/2024/09/cropped-hgn584ghj45-1-192x192.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animeytx.net/",
|
||||||
|
"searchBaseUrl": "https://animeytx.net/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animeytx/animeytx.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
async function searchResults(keyword) {
|
||||||
|
const regex = /<a href="([^"]+)">\s*<img src="([^"]+)"[^>]*>\s*<h3>([^<]+)<\/h3>/g;
|
||||||
|
const results = [];
|
||||||
|
try {
|
||||||
|
const response = await fetchv2("https://animeyy.com/?act=ajax&code=search_manga&keyword=" + encodeURIComponent(keyword));
|
||||||
|
const html = await response.text();
|
||||||
|
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(html)) !== null) {
|
||||||
|
let title = match[3].trim();
|
||||||
|
title = title.replace(/'/g, "'").replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
const imageUrl = "https://animeyy.com/" + match[2].trim();
|
||||||
|
const passthroughImage = "https://passthrough-worker.simplepostrequest.workers.dev/?simple=" + encodeURIComponent(imageUrl) + "&referer=https://animeyy.com/";
|
||||||
|
results.push({
|
||||||
|
title: title,
|
||||||
|
image: passthroughImage,
|
||||||
|
href: "https://animeyy.com" + match[1].trim()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(results);
|
||||||
|
} catch (err) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
title: "Error",
|
||||||
|
image: "Error",
|
||||||
|
href: "Error"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractDetails(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetchv2(url);
|
||||||
|
const html = await response.text();
|
||||||
|
|
||||||
|
const aliasesMatch = html.match(/<strong>Alternative:<\/strong>\s*([^<]+)/);
|
||||||
|
let aliases = aliasesMatch ? aliasesMatch[1].trim() : "N/A";
|
||||||
|
aliases = aliases.replace(/'/g, "'").replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
|
||||||
|
const descMatch = html.match(/<div id="summary_shortened">([\s\S]*?)<\/div>/);
|
||||||
|
let description = descMatch ? descMatch[1].trim() : "N/A";
|
||||||
|
description = description.replace(/'/g, "'").replace(/"/g, '"').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: description,
|
||||||
|
aliases: aliases,
|
||||||
|
airdate: "N/A"
|
||||||
|
}]);
|
||||||
|
} catch (err) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: "Error",
|
||||||
|
aliases: "Error",
|
||||||
|
airdate: "Error"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractEpisodes(url) {
|
||||||
|
const results = [];
|
||||||
|
const episodesRegex = /<li class="wp-manga-chapter"><a href="([^"]+)">(\d+)<\/a>/g;
|
||||||
|
try {
|
||||||
|
const response = await fetchv2(url);
|
||||||
|
const html = await response.text();
|
||||||
|
|
||||||
|
let match;
|
||||||
|
while ((match = episodesRegex.exec(html)) !== null) {
|
||||||
|
results.push({
|
||||||
|
href: "https://animeyy.com" + match[1].trim(),
|
||||||
|
number: parseInt(match[2], 10)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(results.reverse());
|
||||||
|
} catch (err) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
href: "Error",
|
||||||
|
number: "Error"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractStreamUrl(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetchv2(url);
|
||||||
|
const html = await response.text();
|
||||||
|
|
||||||
|
const iframeMatch = html.match(/<iframe src="([^"]+)"/);
|
||||||
|
if (iframeMatch) {
|
||||||
|
const streamUrl = iframeMatch[1].replace('/embed/', '/anime/');
|
||||||
|
return JSON.stringify({
|
||||||
|
streams: [
|
||||||
|
{
|
||||||
|
title: "Server 1",
|
||||||
|
streamUrl: streamUrl,
|
||||||
|
headers: {
|
||||||
|
Referer: "https://www.animeyy.com/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
subtitle: ""
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return JSON.stringify({
|
||||||
|
streams: [],
|
||||||
|
subtitle: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return JSON.stringify({
|
||||||
|
streams: [],
|
||||||
|
subtitle: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Animez",
|
||||||
|
"iconUrl": "https://files.catbox.moe/gnkywd.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "English (Hardsub)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animeyy.com/",
|
||||||
|
"searchBaseUrl": "https://animeyy.com/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animez/animez.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AniWatch",
|
||||||
|
"iconUrl": "https://aniwatchtv.to/apple-touch-icon.png?v=0.1",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "English (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://megacloud.blog/",
|
||||||
|
"searchBaseUrl": "https://bshar1865-hianime.vercel.app/api/v2/hianime/search?q=%s",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/aniwatch/aniwatch.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AnimesRoll",
|
||||||
|
"iconUrl": "https://files.catbox.moe/9lx2bp.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.anroll.net/",
|
||||||
|
"searchBaseUrl": "https://www.anroll.net/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/anroll/anroll.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "ArabLionz",
|
||||||
|
"iconUrl": "https://arablionztv.cam/wp-content/uploads/2023/04/vRA6ewKk_400x400-280x280.jpeg",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Arabic",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://arablionztv.cam/",
|
||||||
|
"searchBaseUrl": "https://arablionztv.cam/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/arablionz/arablionz.js",
|
||||||
|
"type": "shows/movies",
|
||||||
|
"asyncJS": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "ArabSeed",
|
||||||
|
"iconUrl": "https://a.asd.homes/wp-content/themes/Elshaikh2021/UI/images/logo333.webp",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.2",
|
||||||
|
"language": "Arabic",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://a.asd.homes/",
|
||||||
|
"searchBaseUrl": "https://a.asd.homes/?s=%s",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/arabseed/arabseed.js",
|
||||||
|
"type": "shows/movies/anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,980 @@
|
|||||||
|
//
|
||||||
|
//
|
||||||
|
// Main functions
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
const DENO_PROXY_PREFIX = "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=";
|
||||||
|
const ANIKAI_HOME_TITLE_REGEX = /<title>Home - AnimeKai - Watch Free Anime Online, Stream Subbed & Dubbed Anime in HD<\/title>/i;
|
||||||
|
const ANIKAI_CHECK_TIMEOUT_MS = 900;
|
||||||
|
let animekaiBlockCheckPromise = null;
|
||||||
|
|
||||||
|
function proxyUrl(url) {
|
||||||
|
return DENO_PROXY_PREFIX + encodeURIComponent(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isAnimekaiBlockedForUser() {
|
||||||
|
if (!animekaiBlockCheckPromise) {
|
||||||
|
animekaiBlockCheckPromise = (async () => {
|
||||||
|
let timeoutId;
|
||||||
|
try {
|
||||||
|
const htmlText = await Promise.race([
|
||||||
|
fetchv2("https://anikai.to/home").then(response => response.text()),
|
||||||
|
new Promise(resolve => {
|
||||||
|
timeoutId = setTimeout(() => resolve(""), ANIKAI_CHECK_TIMEOUT_MS);
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !ANIKAI_HOME_TITLE_REGEX.test(htmlText);
|
||||||
|
} catch (error) {
|
||||||
|
if (timeoutId) {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
console.error("Animekai accessibility check failed:" + error);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
return animekaiBlockCheckPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchResults(query) {
|
||||||
|
const encodeQuery = keyword => encodeURIComponent(keyword);
|
||||||
|
const MIN_RESULTS_FAST_RETURN = 8;
|
||||||
|
const HIGH_CONFIDENCE_SCORE = 900;
|
||||||
|
const queryNormalized = query.toLowerCase().trim();
|
||||||
|
const queryTokens = queryNormalized.split(/\s+/).filter(Boolean);
|
||||||
|
const isSpecificQuery = queryTokens.length <= 2 && queryNormalized.length >= 5;
|
||||||
|
|
||||||
|
const decodeHtmlEntities = (str) => {
|
||||||
|
if (!str) return str;
|
||||||
|
return str.replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec))
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>');
|
||||||
|
};
|
||||||
|
|
||||||
|
const fuzzyMatch = (query, title) => {
|
||||||
|
const q = query.toLowerCase().trim();
|
||||||
|
const t = title.toLowerCase().trim();
|
||||||
|
|
||||||
|
if (t === q) return 1000;
|
||||||
|
|
||||||
|
if (t.startsWith(q + ' ') || t.startsWith(q + ':') || t.startsWith(q + '-')) return 950;
|
||||||
|
|
||||||
|
const wordBoundaryRegex = new RegExp(`\\b${q.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`);
|
||||||
|
if (wordBoundaryRegex.test(t)) return 900;
|
||||||
|
|
||||||
|
const qTokens = q.split(/\s+/).filter(token => token.length > 0);
|
||||||
|
const tTokens = t.split(/[\s\-:]+/).filter(token => token.length > 0);
|
||||||
|
|
||||||
|
const stopwords = new Set(['the', 'a', 'an', 'and', 'or', 'of', 'in', 'on', 'at', 'to', 'for', 'with']);
|
||||||
|
|
||||||
|
let score = 0;
|
||||||
|
let exactMatches = 0;
|
||||||
|
let partialMatches = 0;
|
||||||
|
let significantMatches = 0;
|
||||||
|
|
||||||
|
qTokens.forEach(qToken => {
|
||||||
|
const isStopword = stopwords.has(qToken);
|
||||||
|
let bestMatch = 0;
|
||||||
|
let hasExactMatch = false;
|
||||||
|
|
||||||
|
tTokens.forEach(tToken => {
|
||||||
|
let matchScore = 0;
|
||||||
|
|
||||||
|
if (tToken === qToken) {
|
||||||
|
matchScore = isStopword ? 25 : 120;
|
||||||
|
hasExactMatch = true;
|
||||||
|
if (!isStopword) significantMatches++;
|
||||||
|
}
|
||||||
|
else if (qToken.includes(tToken) && tToken.length >= 3 && qToken.length <= tToken.length + 2) {
|
||||||
|
matchScore = isStopword ? 8 : 40;
|
||||||
|
if (!isStopword) significantMatches++;
|
||||||
|
}
|
||||||
|
else if (tToken.startsWith(qToken) && qToken.length >= 3) {
|
||||||
|
matchScore = isStopword ? 12 : 70;
|
||||||
|
if (!isStopword) significantMatches++;
|
||||||
|
}
|
||||||
|
else if (qToken.length >= 4 && tToken.length >= 4) {
|
||||||
|
if (Math.abs(qToken.length - tToken.length) > 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qToken[0] !== tToken[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dist = levenshteinDistance(qToken, tToken);
|
||||||
|
const maxLen = Math.max(qToken.length, tToken.length);
|
||||||
|
const similarity = 1 - (dist / maxLen);
|
||||||
|
|
||||||
|
if (similarity > 0.8) {
|
||||||
|
matchScore = Math.floor(similarity * 60);
|
||||||
|
if (!isStopword) significantMatches++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bestMatch = Math.max(bestMatch, matchScore);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bestMatch > 0) {
|
||||||
|
score += bestMatch;
|
||||||
|
if (hasExactMatch) exactMatches++;
|
||||||
|
else partialMatches++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const significantTokens = qTokens.filter(t => !stopwords.has(t)).length;
|
||||||
|
|
||||||
|
const requiredMatches = Math.max(1, Math.ceil(significantTokens * 0.8));
|
||||||
|
if (significantMatches < requiredMatches) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exactMatches + partialMatches >= qTokens.length) {
|
||||||
|
score += 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
score += exactMatches * 20;
|
||||||
|
|
||||||
|
const extraWords = tTokens.length - qTokens.length;
|
||||||
|
if (extraWords > 2) {
|
||||||
|
score -= (extraWords - 2) * 25;
|
||||||
|
}
|
||||||
|
|
||||||
|
let orderBonus = 0;
|
||||||
|
for (let i = 0; i < qTokens.length - 1; i++) {
|
||||||
|
const currentTokenIndex = tTokens.findIndex(t => t.includes(qTokens[i]));
|
||||||
|
const nextTokenIndex = tTokens.findIndex(t => t.includes(qTokens[i + 1]));
|
||||||
|
|
||||||
|
if (currentTokenIndex !== -1 && nextTokenIndex !== -1 && currentTokenIndex < nextTokenIndex) {
|
||||||
|
orderBonus += 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
score += orderBonus;
|
||||||
|
|
||||||
|
return Math.max(0, score);
|
||||||
|
};
|
||||||
|
|
||||||
|
const levenshteinDistance = (a, b) => {
|
||||||
|
const matrix = [];
|
||||||
|
|
||||||
|
for (let i = 0; i <= b.length; i++) {
|
||||||
|
matrix[i] = [i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let j = 0; j <= a.length; j++) {
|
||||||
|
matrix[0][j] = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 1; i <= b.length; i++) {
|
||||||
|
for (let j = 1; j <= a.length; j++) {
|
||||||
|
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
||||||
|
matrix[i][j] = matrix[i - 1][j - 1];
|
||||||
|
} else {
|
||||||
|
matrix[i][j] = Math.min(
|
||||||
|
matrix[i - 1][j - 1] + 1,
|
||||||
|
matrix[i][j - 1] + 1,
|
||||||
|
matrix[i - 1][j] + 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matrix[b.length][a.length];
|
||||||
|
};
|
||||||
|
|
||||||
|
const animekaiSearch = async () => {
|
||||||
|
const searchBaseUrl = "https://anikai.to/browser?keyword=";
|
||||||
|
const baseUrl = "https://anikai.to";
|
||||||
|
|
||||||
|
const posterHrefRegex = /href="[^"]*" class="poster"/g;
|
||||||
|
const titleRegex = /class="title"[^>]*title="[^"]*"/g;
|
||||||
|
const imageRegex = /data-src="[^"]*"/g;
|
||||||
|
const extractHrefRegex = /href="([^"]*)"/;
|
||||||
|
const extractImageRegex = /data-src="([^"]*)"/;
|
||||||
|
const extractTitleRegex = /title="([^"]*)"/;
|
||||||
|
let useProxy = true;
|
||||||
|
|
||||||
|
const extractResultsFromHTML = (htmlText) => {
|
||||||
|
const results = [];
|
||||||
|
const posterMatches = htmlText.match(posterHrefRegex) || [];
|
||||||
|
const titleMatches = htmlText.match(titleRegex) || [];
|
||||||
|
const imageMatches = htmlText.match(imageRegex) || [];
|
||||||
|
const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < minLength; i++) {
|
||||||
|
const hrefMatch = posterMatches[i].match(extractHrefRegex);
|
||||||
|
const fullHref = hrefMatch ? (hrefMatch[1].startsWith("http") ? hrefMatch[1] : baseUrl + hrefMatch[1]) : null;
|
||||||
|
|
||||||
|
const imageMatch = imageMatches[i].match(extractImageRegex);
|
||||||
|
const imageSrc = imageMatch ? imageMatch[1] : null;
|
||||||
|
|
||||||
|
const titleMatch = titleMatches[i].match(extractTitleRegex);
|
||||||
|
const cleanTitle = titleMatch ? decodeHtmlEntities(titleMatch[1]) : null;
|
||||||
|
|
||||||
|
if (fullHref && imageSrc && cleanTitle) {
|
||||||
|
results.push({
|
||||||
|
href: `Animekai:${fullHref}`,
|
||||||
|
image: useProxy ? proxyUrl(imageSrc) : imageSrc,
|
||||||
|
title: cleanTitle
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const encodedQuery = encodeQuery(query);
|
||||||
|
useProxy = await isAnimekaiBlockedForUser();
|
||||||
|
const fetchPages = async (pages) => {
|
||||||
|
const urls = pages.map(page =>
|
||||||
|
page === 1
|
||||||
|
? `${searchBaseUrl}${encodedQuery}`
|
||||||
|
: `${searchBaseUrl}${encodedQuery}&page=${page}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const responses = await Promise.all(urls.map(url => fetchv2(useProxy ? proxyUrl(url) : url)));
|
||||||
|
const htmlTexts = await Promise.all(responses.map(res => res.text()));
|
||||||
|
|
||||||
|
const allResults = [];
|
||||||
|
htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html)));
|
||||||
|
return allResults;
|
||||||
|
};
|
||||||
|
|
||||||
|
const page1Results = await fetchPages([1]);
|
||||||
|
return {
|
||||||
|
page1Results,
|
||||||
|
fetchRemaining: () => fetchPages([2, 3])
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Animekai search error:" + error);
|
||||||
|
return {
|
||||||
|
page1Results: [],
|
||||||
|
fetchRemaining: async () => []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const oneMoviesSearch = async () => {
|
||||||
|
const searchBaseUrl = "https://1movies.bz/browser?keyword=";
|
||||||
|
const baseUrl = "https://1movies.bz";
|
||||||
|
|
||||||
|
const posterHrefRegex = /href="([^"]*)" class="poster"/g;
|
||||||
|
const titleRegex = /class="title" href="[^"]*">([^<]*)</g;
|
||||||
|
const imageRegex = /data-src="([^"]*)"/g;
|
||||||
|
|
||||||
|
const extractResultsFromHTML = (htmlText) => {
|
||||||
|
const results = [];
|
||||||
|
const posterMatches = [...htmlText.matchAll(posterHrefRegex)];
|
||||||
|
const titleMatches = [...htmlText.matchAll(titleRegex)];
|
||||||
|
const imageMatches = [...htmlText.matchAll(imageRegex)];
|
||||||
|
const minLength = Math.min(posterMatches.length, titleMatches.length, imageMatches.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < minLength; i++) {
|
||||||
|
const href = posterMatches[i][1];
|
||||||
|
const fullHref = href.startsWith("http") ? href : baseUrl + href;
|
||||||
|
|
||||||
|
const imageSrc = imageMatches[i][1];
|
||||||
|
const title = decodeHtmlEntities(titleMatches[i][1]);
|
||||||
|
|
||||||
|
results.push({ href: fullHref, image: "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(imageSrc), title });
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const encodedQuery = encodeQuery(query);
|
||||||
|
const fetchPages = async (pages) => {
|
||||||
|
const urls = pages.map(page =>
|
||||||
|
page === 1
|
||||||
|
? `${searchBaseUrl}${encodedQuery}`
|
||||||
|
: `${searchBaseUrl}${encodedQuery}&page=${page}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const responses = await Promise.all(urls.map(url => fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url))));
|
||||||
|
const htmlTexts = await Promise.all(responses.map(res => res.text()));
|
||||||
|
|
||||||
|
const allResults = [];
|
||||||
|
htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html)));
|
||||||
|
return allResults;
|
||||||
|
};
|
||||||
|
|
||||||
|
const page1Results = await fetchPages([1]);
|
||||||
|
return {
|
||||||
|
page1Results,
|
||||||
|
fetchRemaining: () => fetchPages([2, 3])
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("1Movies search error:" + error);
|
||||||
|
return {
|
||||||
|
page1Results: [],
|
||||||
|
fetchRemaining: async () => []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const animekaiPromise = animekaiSearch();
|
||||||
|
|
||||||
|
const animekaiData = await animekaiPromise;
|
||||||
|
|
||||||
|
const dedupeResults = (results) => {
|
||||||
|
const seen = new Set();
|
||||||
|
return results.filter(item => {
|
||||||
|
const key = `${item.href}|${item.title}`;
|
||||||
|
if (seen.has(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen.add(key);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const scoreCache = new Map();
|
||||||
|
const rankResults = (results) => {
|
||||||
|
const scoredResults = results.map(r => {
|
||||||
|
const cacheKey = r.title;
|
||||||
|
const cached = scoreCache.get(cacheKey);
|
||||||
|
const score = cached !== undefined ? cached : fuzzyMatch(query, r.title);
|
||||||
|
|
||||||
|
if (cached === undefined) {
|
||||||
|
scoreCache.set(cacheKey, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...r,
|
||||||
|
score
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const sorted = scoredResults
|
||||||
|
.filter(r => r.score > 50)
|
||||||
|
.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
|
return {
|
||||||
|
topScore: sorted[0]?.score || 0,
|
||||||
|
items: sorted.map(({ score, ...rest }) => rest)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldFastReturn = (resultCount, topScore) => {
|
||||||
|
if (resultCount >= MIN_RESULTS_FAST_RETURN) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSpecificQuery && resultCount >= 1 && topScore >= HIGH_CONFIDENCE_SCORE;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mergedResults = dedupeResults([...animekaiData.page1Results]);
|
||||||
|
|
||||||
|
let rankedResults = rankResults(mergedResults);
|
||||||
|
let filteredResults = rankedResults.items;
|
||||||
|
|
||||||
|
if (shouldFastReturn(filteredResults.length, rankedResults.topScore)) {
|
||||||
|
return JSON.stringify(filteredResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
const oneMoviesData = await oneMoviesSearch();
|
||||||
|
|
||||||
|
mergedResults = dedupeResults([
|
||||||
|
...mergedResults,
|
||||||
|
...oneMoviesData.page1Results
|
||||||
|
]);
|
||||||
|
|
||||||
|
rankedResults = rankResults(mergedResults);
|
||||||
|
filteredResults = rankedResults.items;
|
||||||
|
|
||||||
|
if (shouldFastReturn(filteredResults.length, rankedResults.topScore)) {
|
||||||
|
return JSON.stringify(filteredResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [animekaiExtra, oneMoviesExtra] = await Promise.all([
|
||||||
|
animekaiData.fetchRemaining(),
|
||||||
|
oneMoviesData.fetchRemaining()
|
||||||
|
]);
|
||||||
|
|
||||||
|
mergedResults = dedupeResults([
|
||||||
|
...mergedResults,
|
||||||
|
...animekaiExtra,
|
||||||
|
...oneMoviesExtra
|
||||||
|
]);
|
||||||
|
|
||||||
|
rankedResults = rankResults(mergedResults);
|
||||||
|
filteredResults = rankedResults.items;
|
||||||
|
|
||||||
|
return JSON.stringify(filteredResults.length > 0 ? filteredResults : [{
|
||||||
|
href: "",
|
||||||
|
image: "",
|
||||||
|
title: "No results found, please refine query."
|
||||||
|
}]);
|
||||||
|
} catch (error) {
|
||||||
|
return JSON.stringify([{
|
||||||
|
href: "",
|
||||||
|
image: "",
|
||||||
|
title: "Search failed: " + error.message
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractDetails(url) {
|
||||||
|
|
||||||
|
if (url.startsWith("Animekai:")) {
|
||||||
|
const actualUrl = url.replace("Animekai:", "").trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const useProxy = await isAnimekaiBlockedForUser();
|
||||||
|
const response = await fetchv2(useProxy ? proxyUrl(actualUrl) : actualUrl);
|
||||||
|
const htmlText = await response.text();
|
||||||
|
|
||||||
|
const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
|
||||||
|
const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
|
||||||
|
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
|
||||||
|
aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not available",
|
||||||
|
airdate: "If stream doesn't load try later or disable VPN/DNS"
|
||||||
|
}]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Animekai details:" + error);
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: "Error loading description",
|
||||||
|
aliases: "Aliases: Unknown",
|
||||||
|
airdate: "Aired: Unknown"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url));
|
||||||
|
const htmlText = await response.text();
|
||||||
|
|
||||||
|
const descriptionMatch = (/<div class="description text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
|
||||||
|
const aliasesMatch = (/<small class="al-title text-expand">([\s\S]*?)<\/small>/.exec(htmlText) || [])[1];
|
||||||
|
const airdateMatch = (/<li>Released:\s*<span[^>]*>(.*?)<\/span>/.exec(htmlText) || [])[1];
|
||||||
|
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: descriptionMatch ? cleanHtmlSymbols(descriptionMatch) : "Not available",
|
||||||
|
aliases: aliasesMatch ? cleanHtmlSymbols(aliasesMatch) : "Not aliases",
|
||||||
|
airdate: airdateMatch ? cleanHtmlSymbols(airdateMatch) : "Not available"
|
||||||
|
}]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching 1Movies details:"+ error);
|
||||||
|
return JSON.stringify([{
|
||||||
|
description: "Error loading description",
|
||||||
|
aliases: "Not available",
|
||||||
|
airdate: "Not available"
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractEpisodes(url) {
|
||||||
|
try {
|
||||||
|
if (url.startsWith("Animekai:")) {
|
||||||
|
const actualUrl = url.replace("Animekai:", "").trim();
|
||||||
|
const useProxy = await isAnimekaiBlockedForUser();
|
||||||
|
const htmlText = await (await fetchv2(useProxy ? proxyUrl(actualUrl) : actualUrl)).text();
|
||||||
|
const animeIdMatch = (htmlText.match(/<div class="rate-box"[^>]*data-id="([^"]+)"/) || [])[1];
|
||||||
|
if (!animeIdMatch) return JSON.stringify([{ error: "AniID not found" }]);
|
||||||
|
|
||||||
|
const tokenResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(animeIdMatch)}`);
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
const token = tokenData.result;
|
||||||
|
|
||||||
|
const episodeListUrl = `https://anikai.to/ajax/episodes/list?ani_id=${animeIdMatch}&_=${token}`;
|
||||||
|
const episodeListData = await (await fetchv2(useProxy ? proxyUrl(episodeListUrl) : episodeListUrl)).json();
|
||||||
|
const cleanedHtml = cleanJsonHtml(episodeListData.result);
|
||||||
|
|
||||||
|
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
|
||||||
|
const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];
|
||||||
|
|
||||||
|
const episodes = episodeMatches.map(([_, episodeNum, episodeToken]) => ({
|
||||||
|
number: parseInt(episodeNum, 10),
|
||||||
|
href: `Animekai:https://anikai.to/ajax/links/list?token=${episodeToken}&_=ENCRYPT_ME`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return JSON.stringify(episodes);
|
||||||
|
} else {
|
||||||
|
const htmlText = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url))).text();
|
||||||
|
const movieIDMatch = (htmlText.match(/<div class="detail-lower"[^>]*id="movie-rating"[^>]*data-id="([^"]+)"/) || [])[1];
|
||||||
|
if (!movieIDMatch) return JSON.stringify([{ error: "MovieID not found" }]);
|
||||||
|
|
||||||
|
const tokenResponse = await fetchv2("https://enc-dec.app/api/enc-movies-flix?text=" + encodeURIComponent(movieIDMatch));
|
||||||
|
const temp = await tokenResponse.json();
|
||||||
|
const token = temp.result;
|
||||||
|
|
||||||
|
const episodeListUrl = `https://1movies.bz/ajax/episodes/list?id=${movieIDMatch}&_=${token}`;
|
||||||
|
const episodeListData = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(episodeListUrl))).json();
|
||||||
|
const cleanedHtml = cleanJsonHtml(episodeListData.result);
|
||||||
|
|
||||||
|
const episodeRegex = /<a[^>]+eid="([^"]+)"[^>]+num="([^"]+)"[^>]*>/g;
|
||||||
|
const episodeMatches = [...cleanedHtml.matchAll(episodeRegex)];
|
||||||
|
|
||||||
|
const episodes = episodeMatches.map(([_, episodeToken, episodeNum]) => ({
|
||||||
|
number: parseInt(episodeNum, 10),
|
||||||
|
href: `https://1movies.bz/ajax/links/list?eid=${episodeToken}&_=ENCRYPT_ME`
|
||||||
|
}));
|
||||||
|
|
||||||
|
return JSON.stringify(episodes);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching episodes:" + err);
|
||||||
|
return JSON.stringify([{ number: 1, href: "Error fetching episodes" }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractStreamUrl(url) {
|
||||||
|
let source, actualUrl;
|
||||||
|
|
||||||
|
if (url.startsWith("Animekai:")) {
|
||||||
|
source = "Animekai";
|
||||||
|
actualUrl = url.replace("Animekai:", "").trim();
|
||||||
|
} else if (url.includes("1movies.bz")) {
|
||||||
|
source = "1Movies";
|
||||||
|
actualUrl = url.trim();
|
||||||
|
} else {
|
||||||
|
console.log("Failed to match URL:", url);
|
||||||
|
return "Invalid URL format: " + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source === "Animekai") {
|
||||||
|
const headers = {
|
||||||
|
"Referer": "https://anikai.to/",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
||||||
|
"Accept": "text/html, */*; q=0.01",
|
||||||
|
"Accept-Language": "en-US,en;q=0.5",
|
||||||
|
"Sec-Fetch-Dest": "empty",
|
||||||
|
"Sec-Fetch-Mode": "cors",
|
||||||
|
"Sec-Fetch-Site": "same-origin",
|
||||||
|
"Pragma": "no-cache",
|
||||||
|
"Cache-Control": "no-cache"
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const useProxy = await isAnimekaiBlockedForUser();
|
||||||
|
const tokenMatch = actualUrl.match(/token=([^&]+)/);
|
||||||
|
if (tokenMatch && tokenMatch[1]) {
|
||||||
|
const rawToken = tokenMatch[1];
|
||||||
|
const encryptResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(rawToken)}`);
|
||||||
|
const encryptData = await encryptResponse.json();
|
||||||
|
const encryptedToken = encryptData.result;
|
||||||
|
actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetchv2(useProxy ? proxyUrl(actualUrl) : actualUrl);
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
let ajaxResultHtml = "";
|
||||||
|
try {
|
||||||
|
const parsedAjax = JSON.parse(text);
|
||||||
|
ajaxResultHtml = parsedAjax?.result || "";
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
const cleanedHtml = cleanJsonHtml(text);
|
||||||
|
const cleanedAjaxResultHtml = cleanJsonHtml(ajaxResultHtml);
|
||||||
|
const serverHtmlSource = cleanedAjaxResultHtml || cleanedHtml;
|
||||||
|
|
||||||
|
const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/;
|
||||||
|
const softsubRegex = /<div class="server-items lang-group" data-id="softsub"[^>]*>([\s\S]*?)<\/div>/;
|
||||||
|
const dubRegex = /<div class="server-items lang-group" data-id="dub"[^>]*>([\s\S]*?)<\/div>/;
|
||||||
|
const subContent = subRegex.exec(serverHtmlSource)?.[1]?.trim() || "";
|
||||||
|
const softsubContent = softsubRegex.exec(serverHtmlSource)?.[1]?.trim() || "";
|
||||||
|
const dubContent = dubRegex.exec(serverHtmlSource)?.[1]?.trim() || "";
|
||||||
|
|
||||||
|
const extractServerId = (content) => {
|
||||||
|
if (!content) return null;
|
||||||
|
const spanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>/g;
|
||||||
|
const ids = [];
|
||||||
|
let match;
|
||||||
|
while ((match = spanRegex.exec(content)) !== null) ids.push(match[1]);
|
||||||
|
return ids.length > 1 ? ids[1] : ids[0] ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const serverIdDub = extractServerId(dubContent);
|
||||||
|
const serverIdSoftsub = extractServerId(softsubContent);
|
||||||
|
const serverIdSub = extractServerId(subContent);
|
||||||
|
|
||||||
|
const tokenRequestData = [
|
||||||
|
{ name: "Dub", data: serverIdDub },
|
||||||
|
{ name: "Softsub", data: serverIdSoftsub },
|
||||||
|
{ name: "Sub", data: serverIdSub }
|
||||||
|
].filter(item => item.data);
|
||||||
|
|
||||||
|
const tokenResults = await Promise.all(tokenRequestData.map(item =>
|
||||||
|
fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(item.data)}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => ({ name: item.name, data: json.result }))
|
||||||
|
.catch(err => ({ name: item.name, error: err.toString() }))
|
||||||
|
));
|
||||||
|
|
||||||
|
const serverIdMap = { "Dub": serverIdDub, "Softsub": serverIdSoftsub, "Sub": serverIdSub };
|
||||||
|
|
||||||
|
const streamUrls = tokenResults.map(result => ({
|
||||||
|
type: result.name,
|
||||||
|
url: `https://anikai.to/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data}`
|
||||||
|
}));
|
||||||
|
|
||||||
|
const streamResponses = await Promise.all(
|
||||||
|
streamUrls.map(async ({ type, url }) => {
|
||||||
|
try {
|
||||||
|
const res = await fetchv2(useProxy ? proxyUrl(url) : url);
|
||||||
|
const json = await res.json();
|
||||||
|
return { type, result: json.result };
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Error fetching ${type} stream:` + error);
|
||||||
|
return { type, result: null };
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const decryptResults = await Promise.all(
|
||||||
|
streamResponses
|
||||||
|
.filter(item => item.result)
|
||||||
|
.map(item =>
|
||||||
|
fetchv2(
|
||||||
|
"https://enc-dec.app/api/dec-kai",
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
"POST",
|
||||||
|
JSON.stringify({ text: item.result })
|
||||||
|
)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => {
|
||||||
|
console.log(`decrypted${item.type} URL:` + json.result?.url);
|
||||||
|
return { name: item.type, url: json.result?.url || null };
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(`Error parsing ${item.type} result:` + err);
|
||||||
|
return { name: item.type, url: null };
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const urlMap = Object.fromEntries(decryptResults.map(i => [i.name, i.url]));
|
||||||
|
const decryptedSub = urlMap.Sub;
|
||||||
|
const decryptedDub = urlMap.Dub;
|
||||||
|
const decryptedRaw = urlMap.Softsub;
|
||||||
|
|
||||||
|
async function getStream(url) {
|
||||||
|
try {
|
||||||
|
if (url.includes("anikai.to/iframe")) {
|
||||||
|
const iframePageRes = await fetchv2(url, headers);
|
||||||
|
const iframePageText = await iframePageRes.text();
|
||||||
|
const iframeSrcMatch = iframePageText.match(/<iframe[^>]+src="([^"]+)"/i);
|
||||||
|
if (iframeSrcMatch && iframeSrcMatch[1]) {
|
||||||
|
url = iframeSrcMatch[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetchv2(url.replace("/e/", "/media/").replace("/e2/", "/media/"), headers);
|
||||||
|
const responseJson = await response.json();
|
||||||
|
const result = responseJson?.result;
|
||||||
|
const finalResponse = await fetchv2(
|
||||||
|
"https://enc-dec.app/api/dec-mega",
|
||||||
|
{ "Content-Type": "application/json" },
|
||||||
|
"POST",
|
||||||
|
JSON.stringify({ text: result, agent: headers["User-Agent"] })
|
||||||
|
);
|
||||||
|
const finalJson = await finalResponse.json();
|
||||||
|
return finalJson?.result?.sources?.[0]?.file || null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [subStream, dubStream, rawStream] = await Promise.all([
|
||||||
|
decryptedSub ? getStream(decryptedSub) : Promise.resolve(null),
|
||||||
|
decryptedDub ? getStream(decryptedDub) : Promise.resolve(null),
|
||||||
|
decryptedRaw ? getStream(decryptedRaw) : Promise.resolve(null)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const streams = [];
|
||||||
|
|
||||||
|
async function addStreamQualities(m3u8Link, baseTitle) {
|
||||||
|
let pushedQualities = 0;
|
||||||
|
try {
|
||||||
|
const proxyReqUrl = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(m3u8Link);
|
||||||
|
const m3u8Response = await fetchv2(proxyReqUrl);
|
||||||
|
const m3u8Text = await m3u8Response.text();
|
||||||
|
const lines = m3u8Text.split('\n');
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||||
|
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
||||||
|
const nameMatch = line.match(/NAME="([^"]+)"/i) || line.match(/BANDWIDTH=(\d+)/i);
|
||||||
|
|
||||||
|
let quality = 'Unknown';
|
||||||
|
if (resolutionMatch) {
|
||||||
|
const [width, height] = resolutionMatch[1].split('x');
|
||||||
|
quality = `${height}p`;
|
||||||
|
} else if (nameMatch && nameMatch[1]) {
|
||||||
|
quality = nameMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i + 1 < lines.length) {
|
||||||
|
const streamPath = lines[i + 1].trim();
|
||||||
|
let absolutePath;
|
||||||
|
if (streamPath.startsWith('/api/')) {
|
||||||
|
absolutePath = 'https://1anime.app' + streamPath;
|
||||||
|
} else if (streamPath.startsWith('http')) {
|
||||||
|
absolutePath = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(streamPath);
|
||||||
|
} else {
|
||||||
|
const baseUrl = m3u8Link.substring(0, m3u8Link.lastIndexOf('/') + 1);
|
||||||
|
absolutePath = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(baseUrl + streamPath);
|
||||||
|
}
|
||||||
|
streams.push({
|
||||||
|
title: `${quality} - ${baseTitle}`,
|
||||||
|
streamUrl: absolutePath
|
||||||
|
});
|
||||||
|
pushedQualities++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to extract qualities:", e);
|
||||||
|
}
|
||||||
|
if (pushedQualities === 0) {
|
||||||
|
streams.push({ title: baseTitle, streamUrl: "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(m3u8Link) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subStream) await addStreamQualities(subStream, "Hardsub English");
|
||||||
|
if (dubStream) await addStreamQualities(dubStream, "Dubbed English");
|
||||||
|
if (rawStream) await addStreamQualities(rawStream, "Original audio");
|
||||||
|
|
||||||
|
const final = { streams, subtitles: "" };
|
||||||
|
console.log("RETURN: " + JSON.stringify(final));
|
||||||
|
return JSON.stringify(final);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Animekai fetch error:" + error);
|
||||||
|
return "https://error.org";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (source === "1Movies") {
|
||||||
|
const headers = {
|
||||||
|
"Referer": "https://1movies.bz/",
|
||||||
|
"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"
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const eidMatch = actualUrl.match(/eid=([^&]+)/);
|
||||||
|
if (eidMatch && eidMatch[1]) {
|
||||||
|
const rawEpisodeToken = eidMatch[1];
|
||||||
|
const encryptResponse = await fetchv2(`https://enc-dec.app/api/enc-movies-flix?text=${rawEpisodeToken}`);
|
||||||
|
const encryptData = await encryptResponse.json();
|
||||||
|
const encryptedToken = encryptData.result;
|
||||||
|
actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetchv2(actualUrl);
|
||||||
|
const responseData = await response.json();
|
||||||
|
const cleanedHtml = cleanJsonHtml(responseData.result);
|
||||||
|
|
||||||
|
const spanRegex = /<div class="server wnav-item"[^>]*data-lid="([^"]+)"[^>]*>/g;
|
||||||
|
const ids = [];
|
||||||
|
let match;
|
||||||
|
while ((match = spanRegex.exec(cleanedHtml)) !== null) ids.push(match[1]);
|
||||||
|
|
||||||
|
if (ids.length === 0) {
|
||||||
|
console.log("No servers found");
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
const serverId = ids.length > 1 ? ids[1] : ids[0];
|
||||||
|
const tokenData = await fetchv2(`https://enc-dec.app/api/enc-movies-flix?text=${encodeURIComponent(serverId)}`)
|
||||||
|
.then(res => res.json());
|
||||||
|
const token = tokenData.result;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
console.log("Token not found");
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
const streamUrl = `https://1movies.bz/ajax/links/view?id=${serverId}&_=${token}`;
|
||||||
|
const streamResponse = await fetchv2(streamUrl);
|
||||||
|
const streamData = await streamResponse.json();
|
||||||
|
|
||||||
|
if (!streamData.result) {
|
||||||
|
console.log("Stream result not found");
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
const decryptData = await fetchv2(
|
||||||
|
`https://enc-dec.app/api/dec-movies-flix?text=${streamData.result}`,
|
||||||
|
headers
|
||||||
|
).then(res => res.json());
|
||||||
|
|
||||||
|
console.log("Decrypted response:" + JSON.stringify(decryptData));
|
||||||
|
const decryptedUrl = decryptData.result?.url;
|
||||||
|
|
||||||
|
if (!decryptedUrl) {
|
||||||
|
console.log("Decryption failed");
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
const subListEncoded = decryptedUrl.split("sub.list=")[1]?.split("&")[0];
|
||||||
|
let subtitles = "N/A";
|
||||||
|
if (subListEncoded) {
|
||||||
|
try {
|
||||||
|
const subListUrl = decodeURIComponent(subListEncoded);
|
||||||
|
const subResponse = await fetchv2(subListUrl);
|
||||||
|
subtitles = await subResponse.json();
|
||||||
|
} catch {
|
||||||
|
subtitles = "N/A";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const englishSubUrl = Array.isArray(subtitles)
|
||||||
|
? subtitles.find(sub => sub.label === "English")?.file.replace(/\\\//g, "/")
|
||||||
|
: "N/A";
|
||||||
|
|
||||||
|
let finalUrl = decryptedUrl;
|
||||||
|
if (finalUrl.includes(".to/iframe")) {
|
||||||
|
try {
|
||||||
|
const iframeResponse = await fetchv2(finalUrl, headers);
|
||||||
|
const iframeHtml = await iframeResponse.text();
|
||||||
|
const iframeSrcMatch = iframeHtml.match(/<iframe[^>]+src="([^"]+)"/i);
|
||||||
|
if (iframeSrcMatch && iframeSrcMatch[1]) {
|
||||||
|
finalUrl = iframeSrcMatch[1];
|
||||||
|
} else {
|
||||||
|
console.log("No iframe src found in:", finalUrl);
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error fetching iframe:", e);
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mediaResponse = await fetchv2(finalUrl.replace("/e/", "/media/"), headers);
|
||||||
|
const mediaJson = await mediaResponse.json();
|
||||||
|
const result = mediaJson?.result;
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
console.log("Media result not found");
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalResponse = await fetchv2(`https://enc-dec.app/api/dec-rapid?text=${encodeURIComponent(result)}&agent=${encodeURIComponent(headers["User-Agent"])}`);
|
||||||
|
const finalJson = JSON.parse(await finalResponse.text());
|
||||||
|
|
||||||
|
const m3u8Link = finalJson?.result?.sources?.[0]?.file;
|
||||||
|
const streams = [];
|
||||||
|
if (m3u8Link) {
|
||||||
|
let pushedQualities = 0;
|
||||||
|
try {
|
||||||
|
const m3u8Response = await fetchv2("https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(m3u8Link));
|
||||||
|
const m3u8Text = await m3u8Response.text();
|
||||||
|
const lines = m3u8Text.split('\n');
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||||
|
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
|
||||||
|
const nameMatch = line.match(/NAME="([^"]+)"/i) || line.match(/BANDWIDTH=(\d+)/i);
|
||||||
|
|
||||||
|
let quality = 'Unknown';
|
||||||
|
if (resolutionMatch) {
|
||||||
|
const [width, height] = resolutionMatch[1].split('x');
|
||||||
|
quality = `${height}p`;
|
||||||
|
} else if (nameMatch && nameMatch[1]) {
|
||||||
|
quality = nameMatch[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i + 1 < lines.length) {
|
||||||
|
const streamPath = lines[i + 1].trim();
|
||||||
|
let absolutePath;
|
||||||
|
if (streamPath.startsWith('/api/')) {
|
||||||
|
absolutePath = 'https://1anime.app' + streamPath;
|
||||||
|
} else if (streamPath.startsWith('http')) {
|
||||||
|
absolutePath = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(streamPath);
|
||||||
|
} else {
|
||||||
|
const baseUrl = m3u8Link.substring(0, m3u8Link.lastIndexOf('/') + 1);
|
||||||
|
absolutePath = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(baseUrl + streamPath);
|
||||||
|
}
|
||||||
|
streams.push({
|
||||||
|
title: quality,
|
||||||
|
streamUrl: absolutePath
|
||||||
|
});
|
||||||
|
pushedQualities++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to extract qualities:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pushedQualities === 0) {
|
||||||
|
streams.push({
|
||||||
|
title: "Source",
|
||||||
|
streamUrl: "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(m3u8Link)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnValue = {
|
||||||
|
streams,
|
||||||
|
subtitles: englishSubUrl !== "N/A" ? englishSubUrl : ""
|
||||||
|
};
|
||||||
|
console.log("RETURN: " + JSON.stringify(returnValue));
|
||||||
|
return JSON.stringify(returnValue);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log("1Movies fetch error:" + error);
|
||||||
|
return "https://error.org";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// Helper functions
|
||||||
|
///
|
||||||
|
///
|
||||||
|
|
||||||
|
function cleanHtmlSymbols(string) {
|
||||||
|
if (!string) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
.replace(/’/g, "'")
|
||||||
|
.replace(/–/g, "-")
|
||||||
|
.replace(/&#[0-9]+;/g, "")
|
||||||
|
.replace(/\r?\n|\r/g, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanJsonHtml(jsonHtml) {
|
||||||
|
if (!jsonHtml) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return jsonHtml
|
||||||
|
.replace(/\\"/g, "\"")
|
||||||
|
.replace(/\\'/g, "'")
|
||||||
|
.replace(/\\\\/g, "\\")
|
||||||
|
.replace(/\\n/g, "\n")
|
||||||
|
.replace(/\\t/g, "\t")
|
||||||
|
.replace(/\\r/g, "\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeHtmlEntities(text) {
|
||||||
|
if (!text) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/"/g, "\"")
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/ /g, " ");
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Ashi (あし) - Literally Everything",
|
||||||
|
"iconUrl": "https://files.catbox.moe/ptq3a5.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.3.3",
|
||||||
|
"language": "English",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://animekai.to/",
|
||||||
|
"searchBaseUrl": "https://animekai.to/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/ashi/ashi.js",
|
||||||
|
"type": "anime/movies/shows",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AsiaLiveAction",
|
||||||
|
"iconUrl": "https://asialiveaction.com/wp-content/uploads/cropped-favicon-200x200.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://asialiveaction.com/",
|
||||||
|
"searchBaseUrl": "https://asialiveaction.com/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/asialiveaction/asialiveaction.js",
|
||||||
|
"type": "shows/movies",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -7,5 +7,6 @@
|
|||||||
"author": {
|
"author": {
|
||||||
"name": "50/50",
|
"name": "50/50",
|
||||||
"iconURL": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
"iconURL": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
}
|
},
|
||||||
|
"supportsLuna": true
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Audible",
|
||||||
|
"iconUrl": "https://m.media-amazon.com/images/I/51iKw5dFQoL.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "English",
|
||||||
|
"streamType": "MP3",
|
||||||
|
"quality": "Audio",
|
||||||
|
"baseUrl": "https://www.audible.co.uk/",
|
||||||
|
"searchBaseUrl": "https://www.audible.co.uk/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/audible/audible.js",
|
||||||
|
"type": "audiobooks",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "AZ-Animex",
|
||||||
|
"iconUrl": "https://www.az-animex.com/wp-content/uploads/2023/11/Logo-icono-144x144-1.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.az-animex.com/",
|
||||||
|
"searchBaseUrl": "https://www.az-animex.com/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/az-animex/az-animex.js",
|
||||||
|
"type": "Anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Beatz-Anime",
|
||||||
|
"iconUrl": "https://files.catbox.moe/vij8dw.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.beatz-anime.net/",
|
||||||
|
"searchBaseUrl": "https://www.beatz-anime.net/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/beatz-anime/beatz-anime.js",
|
||||||
|
"type": "Anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"note": "Use external player!",
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -317,59 +317,58 @@ async function extractEpisodes(url) {
|
|||||||
|
|
||||||
async function extractStreamUrl(ID) {
|
async function extractStreamUrl(ID) {
|
||||||
try {
|
try {
|
||||||
|
let subtitle = null;
|
||||||
|
if (ID.includes('movie')) {
|
||||||
|
const tmdbID = ID.split('/')[2];
|
||||||
|
const subResponse = await fetchv2(`https://sub.wyzie.ru/search?id=${tmdbID}&format=srt`);
|
||||||
|
const subtitles = await subResponse.json();
|
||||||
|
subtitle = subtitles.find(sub => sub.language.toLowerCase() === 'en')?.url || null;
|
||||||
|
} else if (ID.includes('tv')) {
|
||||||
|
const parts = ID.split('/');
|
||||||
|
const tmdbID = parts[2];
|
||||||
|
const seasonNumber = parts[3];
|
||||||
|
const episodeNumber = parts[4];
|
||||||
|
const subResponse = await fetchv2(`https://sub.wyzie.ru/search?id=${tmdbID}&format=srt&season=${seasonNumber}&episode=${episodeNumber}`);
|
||||||
|
const subtitles = await subResponse.json();
|
||||||
|
subtitle = subtitles.find(sub => sub.language.toLowerCase() === 'en')?.url || null;
|
||||||
|
}
|
||||||
const alphaPromise = alpha(ID).then(result => ({ source: 'alpha', data: JSON.parse(result) })).catch(err => ({ source: 'alpha', error: err }));
|
const alphaPromise = alpha(ID).then(result => ({ source: 'alpha', data: JSON.parse(result) })).catch(err => ({ source: 'alpha', error: err }));
|
||||||
const betaPromise = beta(ID).then(result => ({ source: 'beta', data: JSON.parse(result) })).catch(err => ({ source: 'beta', error: err }));
|
|
||||||
const gammaPromise = gamma(ID).then(result => ({ source: 'gamma', data: JSON.parse(result) })).catch(err => ({ source: 'gamma', error: err }));
|
const gammaPromise = gamma(ID).then(result => ({ source: 'gamma', data: JSON.parse(result) })).catch(err => ({ source: 'gamma', error: err }));
|
||||||
const deltaPromise = delta(ID).then(result => ({ source: 'delta', data: JSON.parse(result) })).catch(err => ({ source: 'delta', error: err }));
|
const deltaPromise = delta(ID).then(result => ({ source: 'delta', data: JSON.parse(result) })).catch(err => ({ source: 'delta', error: err }));
|
||||||
const epsilonPromise = epsilon(ID).then(result => ({ source: 'epsilon', data: JSON.parse(result) })).catch(err => ({ source: 'epsilon', error: err }));
|
|
||||||
|
|
||||||
const racePromise = Promise.race([alphaPromise, gammaPromise, deltaPromise]);
|
const racePromise = Promise.race([alphaPromise, gammaPromise, deltaPromise]);
|
||||||
|
|
||||||
const [fastestResult, betaResult, alphaResult] = await Promise.all([racePromise, betaPromise, alphaPromise]);
|
const [fastestResult, alphaResult] = await Promise.all([racePromise, alphaPromise]);
|
||||||
|
|
||||||
let epsilonResult = null;
|
|
||||||
const alphaHas4K = alphaResult && !alphaResult.error && alphaResult.data && alphaResult.data.streams &&
|
|
||||||
alphaResult.data.streams.some((_, i, arr) => i % 2 === 0 && arr[i] && arr[i].toLowerCase().includes('4k'));
|
|
||||||
|
|
||||||
if (!alphaHas4K) {
|
|
||||||
epsilonResult = await epsilonPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resultStreams = [];
|
const resultStreams = [];
|
||||||
let subtitle = null;
|
|
||||||
|
|
||||||
if (fastestResult && !fastestResult.error && fastestResult.data && fastestResult.data.streams && fastestResult.data.streams.length > 0) {
|
if (fastestResult && !fastestResult.error && fastestResult.data && fastestResult.data.streams && fastestResult.data.streams.length > 0) {
|
||||||
const streamUrl = fastestResult.data.streams.length >= 2 ? fastestResult.data.streams[1] : fastestResult.data.streams[0];
|
let streamUrl;
|
||||||
|
let headers = {};
|
||||||
|
if (typeof fastestResult.data.streams[0] === 'string') {
|
||||||
|
streamUrl = fastestResult.data.streams.length >= 2 ? fastestResult.data.streams[1] : fastestResult.data.streams[0];
|
||||||
|
} else {
|
||||||
|
streamUrl = fastestResult.data.streams[0].streamUrl;
|
||||||
|
headers = fastestResult.data.streams[0].headers || {};
|
||||||
|
}
|
||||||
|
|
||||||
resultStreams.push({
|
resultStreams.push({
|
||||||
title: "Default (1080p)",
|
title: "Default (1080p)",
|
||||||
streamUrl: streamUrl,
|
streamUrl: streamUrl,
|
||||||
headers: fastestResult.data.referer ? { "Referer": fastestResult.data.referer } : {}
|
headers: { ...headers, ...(fastestResult.data.referer ? { "Referer": fastestResult.data.referer } : {}) }
|
||||||
});
|
});
|
||||||
|
|
||||||
subtitle = fastestResult.data.subtitles || null;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (betaResult && !betaResult.error && betaResult.data && betaResult.data.streams && betaResult.data.streams.length > 0) {
|
|
||||||
const streamUrl = betaResult.data.streams.length >= 2 ? betaResult.data.streams[1] : betaResult.data.streams[0];
|
|
||||||
|
|
||||||
resultStreams.push({
|
|
||||||
title: "Default (1080p)",
|
|
||||||
streamUrl: streamUrl,
|
|
||||||
headers: betaResult.data.referer ? { "Referer": betaResult.data.referer } : {}
|
|
||||||
});
|
|
||||||
|
|
||||||
subtitle = betaResult.data.subtitles || null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alphaResult && !alphaResult.error && alphaResult.data && alphaResult.data.streams) {
|
if (alphaResult && !alphaResult.error && alphaResult.data && alphaResult.data.streams) {
|
||||||
const streams = alphaResult.data.streams;
|
const streams = alphaResult.data.streams;
|
||||||
let fourKUrl = null;
|
let fourKUrl = null;
|
||||||
|
let fourKHeaders = {};
|
||||||
|
|
||||||
for (let i = 0; i < streams.length; i += 2) {
|
for (let stream of streams) {
|
||||||
if (streams[i] && streams[i].toLowerCase().includes('4k') && streams[i + 1]) {
|
if (stream.title && stream.title.toLowerCase().includes('4k') && stream.streamUrl) {
|
||||||
fourKUrl = streams[i + 1];
|
fourKUrl = stream.streamUrl;
|
||||||
|
fourKHeaders = stream.headers || {};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,36 +377,10 @@ async function extractStreamUrl(ID) {
|
|||||||
resultStreams.push({
|
resultStreams.push({
|
||||||
title: "4K",
|
title: "4K",
|
||||||
streamUrl: fourKUrl,
|
streamUrl: fourKUrl,
|
||||||
headers: {
|
headers: fourKHeaders
|
||||||
"Origin": "https://player.videasy.net",
|
|
||||||
"Referer": "https://player.videasy.net/"
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('Alpha did not return a 4K stream, checking epsilon as fallback');
|
console.log('Alpha did not return a 4K stream');
|
||||||
|
|
||||||
if (epsilonResult && !epsilonResult.error && epsilonResult.data && epsilonResult.data.streams) {
|
|
||||||
const epsilonStreams = epsilonResult.data.streams;
|
|
||||||
let epsilonFourKUrl = null;
|
|
||||||
|
|
||||||
for (const stream of epsilonStreams) {
|
|
||||||
if (stream.title && stream.title.includes('4K') && stream.streamUrl) {
|
|
||||||
epsilonFourKUrl = stream;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (epsilonFourKUrl) {
|
|
||||||
console.log('Using epsilon as 4K fallback provider');
|
|
||||||
resultStreams.push({
|
|
||||||
title: "4K",
|
|
||||||
streamUrl: epsilonFourKUrl.streamUrl,
|
|
||||||
headers: epsilonFourKUrl.headers || { "Referer": "https://mapple.uk/", "Origin": "https://mapple.uk" }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('Epsilon fallback also did not return a 4K stream');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(JSON.stringify({
|
console.log(JSON.stringify({
|
||||||
@@ -457,7 +430,7 @@ async function alpha(ID) {
|
|||||||
const imdbId = cinebyData.external_ids?.imdb_id || '';
|
const imdbId = cinebyData.external_ids?.imdb_id || '';
|
||||||
const tmdbId = cinebyData.id;
|
const tmdbId = cinebyData.id;
|
||||||
|
|
||||||
const fullUrl = `https://api.videasy.net/myflixerzupcloud/sources-with-title?title=${title}&mediaType=movie&year=${year}&episodeId=1&seasonId=1&tmdbId=${tmdbId}&imdbId=${imdbId}`;
|
const fullUrl = `https://api.videasy.net/cdn/sources-with-title?title=${title}&mediaType=movie&year=${year}&episodeId=1&seasonId=1&tmdbId=${tmdbId}&imdbId=${imdbId}`;
|
||||||
|
|
||||||
console.log('Full URL:' + fullUrl);
|
console.log('Full URL:' + fullUrl);
|
||||||
|
|
||||||
@@ -472,9 +445,9 @@ async function alpha(ID) {
|
|||||||
|
|
||||||
const postData = JSON.stringify({
|
const postData = JSON.stringify({
|
||||||
text: encrypted,
|
text: encrypted,
|
||||||
id: tmdbID
|
id: tmdbID.split('-')[0]
|
||||||
});
|
});
|
||||||
|
console.log('Post Data:' + postData);
|
||||||
const decryptedResponse = await fetchv2("https://enc-dec.app/api/dec-videasy", headers, "POST", postData);
|
const decryptedResponse = await fetchv2("https://enc-dec.app/api/dec-videasy", headers, "POST", postData);
|
||||||
const decryptedData = await decryptedResponse.json();
|
const decryptedData = await decryptedResponse.json();
|
||||||
console.log('Decrypted Data:' + JSON.stringify(decryptedData));
|
console.log('Decrypted Data:' + JSON.stringify(decryptedData));
|
||||||
@@ -485,15 +458,22 @@ async function alpha(ID) {
|
|||||||
|
|
||||||
const nonHDRSources = sources.filter(s => !s.quality.includes("HDR"));
|
const nonHDRSources = sources.filter(s => !s.quality.includes("HDR"));
|
||||||
|
|
||||||
const streams = nonHDRSources.flatMap(src => [src.quality, src.url]);
|
const streamObjects = nonHDRSources.map(src => ({
|
||||||
|
title: src.quality,
|
||||||
|
streamUrl: src.url,
|
||||||
|
headers: {
|
||||||
|
"Origin": "https://player.videasy.net",
|
||||||
|
"Referer": "https://player.videasy.net/"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
const englishSubtitle = subtitles.find(
|
const englishSubtitle = subtitles.find(
|
||||||
sub => sub.language.toLowerCase().includes('english')
|
sub => sub.language.toLowerCase().includes('english')
|
||||||
)?.url || null;
|
)?.url || null;
|
||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
streams,
|
streams: streamObjects,
|
||||||
subtitles: englishSubtitle
|
subtitle: englishSubtitle
|
||||||
});
|
});
|
||||||
} else if (ID.includes('tv')) {
|
} else if (ID.includes('tv')) {
|
||||||
const parts = ID.split('/');
|
const parts = ID.split('/');
|
||||||
@@ -501,7 +481,7 @@ async function alpha(ID) {
|
|||||||
const seasonNumber = parts[3];
|
const seasonNumber = parts[3];
|
||||||
const episodeNumber = parts[4];
|
const episodeNumber = parts[4];
|
||||||
|
|
||||||
const cinebyResponse = await soraFetch(`hhttps://db.videasy.net/3/tv/${tmdbID}?append_to_response=external_ids&language=en&api_key=ad301b7cc82ffe19273e55e4d4206885`);
|
const cinebyResponse = await soraFetch(`https://db.videasy.net/3/tv/${tmdbID}?append_to_response=external_ids&language=en&api_key=ad301b7cc82ffe19273e55e4d4206885`);
|
||||||
const cinebyData = await cinebyResponse.json();
|
const cinebyData = await cinebyResponse.json();
|
||||||
|
|
||||||
const title = encodeURIComponent(cinebyData.name);
|
const title = encodeURIComponent(cinebyData.name);
|
||||||
@@ -509,7 +489,7 @@ async function alpha(ID) {
|
|||||||
const imdbId = cinebyData.external_ids?.imdb_id || '';
|
const imdbId = cinebyData.external_ids?.imdb_id || '';
|
||||||
const tmdbId = cinebyData.id;
|
const tmdbId = cinebyData.id;
|
||||||
|
|
||||||
const fullUrl = `https://api.videasy.net/myflixerzupcloud/sources-with-title?title==${title}&mediaType=tv&year=${year}&episodeId=${episodeNumber}&seasonId=${seasonNumber}&tmdbId=${tmdbId}&imdbId=${imdbId}`;
|
const fullUrl = `https://api.videasy.net/cdn/sources-with-title?title=${title}&mediaType=tv&year=${year}&episodeId=${episodeNumber}&seasonId=${seasonNumber}&tmdbId=${tmdbId}&imdbId=${imdbId}`;
|
||||||
|
|
||||||
console.log('Full URL:' + fullUrl);
|
console.log('Full URL:' + fullUrl);
|
||||||
|
|
||||||
@@ -524,7 +504,7 @@ async function alpha(ID) {
|
|||||||
|
|
||||||
const postData = JSON.stringify({
|
const postData = JSON.stringify({
|
||||||
text: encrypted,
|
text: encrypted,
|
||||||
id: tmdbID
|
id: tmdbID.split('-')[0]
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedResponse = await fetchv2("https://enc-dec.app/api/dec-videasy", headers, "POST", postData);
|
const decryptedResponse = await fetchv2("https://enc-dec.app/api/dec-videasy", headers, "POST", postData);
|
||||||
@@ -537,101 +517,26 @@ async function alpha(ID) {
|
|||||||
|
|
||||||
const nonHDRSources = sources.filter(s => !s.quality.includes("HDR"));
|
const nonHDRSources = sources.filter(s => !s.quality.includes("HDR"));
|
||||||
|
|
||||||
const streams = nonHDRSources.flatMap(src => [src.quality, src.url]);
|
const streamObjects = nonHDRSources.map(src => ({
|
||||||
|
title: src.quality,
|
||||||
|
streamUrl: src.url,
|
||||||
|
headers: {
|
||||||
|
"Origin": "https://player.videasy.net",
|
||||||
|
"Referer": "https://player.videasy.net/"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
const englishSubtitle = subtitles.find(
|
const englishSubtitle = subtitles.find(
|
||||||
sub => sub.language.toLowerCase().includes('english')
|
sub => sub.language.toLowerCase().includes('english')
|
||||||
)?.url || null;
|
)?.url || null;
|
||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
streams,
|
streams: streamObjects,
|
||||||
subtitles: englishSubtitle
|
subtitle: englishSubtitle
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function beta(ID) {
|
|
||||||
if (ID.includes('movie')) {
|
|
||||||
const tmdbID = ID.replace('/movie/', '');
|
|
||||||
const headersOne = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI2NTQ0MWU0MTg4NjhhMWI0NDZiM2I0Mzg1MmE4OWQ2NyIsIm5iZiI6MTYzMDg4NDI0My40NzMsInN1YiI6IjYxMzU1MTkzZmQ0YTk2MDA0NDVkMTJjNiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.Hm0W-hUx-7ph-ASvk2TpMxZbMtwVa5DEXWcgNgcqXJM",
|
|
||||||
"Referer": "https://player.smashystream.com/",
|
|
||||||
"Origin": "https://player.smashystream.com",
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
|
|
||||||
};
|
|
||||||
const tmdbResponse = await fetchv2(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/movie/${tmdbID}?append_to_response=external_ids`)}&simple=true`, headersOne);
|
|
||||||
const tmdbData = await tmdbResponse.json();
|
|
||||||
const imdbID = tmdbData.imdb_id;
|
|
||||||
|
|
||||||
const streamResponse = await ilovefeet(imdbID, false, null, null, 'm3u8');
|
|
||||||
|
|
||||||
const streams = [];
|
|
||||||
|
|
||||||
if (streamResponse && streamResponse.defaultUrl) {
|
|
||||||
streams.push("1080p", streamResponse.defaultUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamResponse && streamResponse.vFastUrl) {
|
|
||||||
const fourKResult = await ilovearmpits(streamResponse.vFastUrl);
|
|
||||||
if (fourKResult.available && fourKResult.url) {
|
|
||||||
streams.push("4K", fourKResult.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const final = {
|
|
||||||
streams,
|
|
||||||
subtitles: streamResponse.subtitles || "None",
|
|
||||||
referer: "https://vidfast.pro/"
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(JSON.stringify(final));
|
|
||||||
return JSON.stringify(final);
|
|
||||||
} else if (ID.includes('tv')) {
|
|
||||||
const parts = ID.split('/');
|
|
||||||
const tmdbID = parts[2];
|
|
||||||
const seasonNumber = parts[3];
|
|
||||||
const episodeNumber = parts[4];
|
|
||||||
console.log(`TMDB ID: ${tmdbID}, Season: ${seasonNumber}, Episode: ${episodeNumber}`);
|
|
||||||
const headersOne = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI2NTQ0MWU0MTg4NjhhMWI0NDZiM2I0Mzg1MmE4OWQ2NyIsIm5iZiI6MTYzMDg4NDI0My40NzMsInN1YiI6IjYxMzU1MTkzZmQ0YTk2MDA0NDVkMTJjNiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.Hm0W-hUx-7ph-ASvk2TpMxZbMtwVa5DEXWcgNgcqXJM",
|
|
||||||
"Referer": "https://player.smashystream.com/",
|
|
||||||
"Origin": "https://player.smashystream.com",
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/"
|
|
||||||
};
|
|
||||||
const tmdbResponse = await fetchv2(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://api.themoviedb.org/3/tv/${tmdbID}?append_to_response=external_ids`)}&simple=true`, headersOne);
|
|
||||||
const tmdbData = await tmdbResponse.json();
|
|
||||||
const imdbID = tmdbData.external_ids.imdb_id;
|
|
||||||
|
|
||||||
const streamResponse = await ilovefeet(imdbID, true, seasonNumber, episodeNumber, 'm3u8');
|
|
||||||
|
|
||||||
const streams = [];
|
|
||||||
|
|
||||||
if (streamResponse && streamResponse.defaultUrl) {
|
|
||||||
streams.push("1080p", streamResponse.defaultUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamResponse && streamResponse.vFastUrl) {
|
|
||||||
const fourKResult = await ilovearmpits(streamResponse.vFastUrl);
|
|
||||||
if (fourKResult.available && fourKResult.url) {
|
|
||||||
streams.push("4K", fourKResult.url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const final = {
|
|
||||||
streams,
|
|
||||||
subtitles: streamResponse.subtitles || "None",
|
|
||||||
referer: "https://vidfast.pro/"
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(JSON.stringify(final));
|
|
||||||
return JSON.stringify(final);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function gamma(ID) {
|
async function gamma(ID) {
|
||||||
if (ID.includes('movie')) {
|
if (ID.includes('movie')) {
|
||||||
const parts = ID.split('/');
|
const parts = ID.split('/');
|
||||||
@@ -690,9 +595,9 @@ async function delta(ID) {
|
|||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||||
"X-Api-Key": "24ef089ebcab51d107a4e4709e87861ef609bace89ac23af13235f6ea743488f"
|
"X-Api-Key": "24ef089ebcab51d107a4e4709e87861ef609bace89ac23af13235f6ea743488f"
|
||||||
};
|
};
|
||||||
const response = await fetchv2(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://themoviedb.hexa.watch/api/tmdb/movie/${ID.split('/')[2]}/images`)}&simple=true`, headersOne);
|
const response = await fetchv2(`https://themoviedb.hexa.su/api/tmdb/movie/${ID.split('/')[2]}/images`, headersOne);
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
|
console.log('Encrypted Data:' + data);
|
||||||
const headers = {
|
const headers = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
@@ -725,8 +630,7 @@ async function delta(ID) {
|
|||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
streams,
|
streams,
|
||||||
subtitles: englishSubtitle,
|
subtitles: englishSubtitle
|
||||||
referer: "https://hexa.watch/"
|
|
||||||
});
|
});
|
||||||
} else if (ID.includes('tv')) {
|
} else if (ID.includes('tv')) {
|
||||||
const parts = ID.split('/');
|
const parts = ID.split('/');
|
||||||
@@ -737,7 +641,7 @@ async function delta(ID) {
|
|||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3",
|
||||||
"X-Api-Key": "24ef089ebcab51d107a4e4709e87861ef609bace89ac23af13235f6ea743488f"
|
"X-Api-Key": "24ef089ebcab51d107a4e4709e87861ef609bace89ac23af13235f6ea743488f"
|
||||||
};
|
};
|
||||||
const response = await fetchv2(`https://post-eosin.vercel.app/api/proxy?url=${encodeURIComponent(`https://themoviedb.hexa.watch/api/tmdb/tv/${tmdbID}/season/${seasonNumber}/episode/${episodeNumber}/images`)}&simple=true`, headersOne);
|
const response = await fetchv2(`https://themoviedb.hexa.su/api/tmdb/tv/${tmdbID}/season/${seasonNumber}/episode/${episodeNumber}/images`, headersOne);
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
@@ -772,208 +676,11 @@ async function delta(ID) {
|
|||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
streams,
|
streams,
|
||||||
subtitles: null,
|
subtitles: englishSubtitle
|
||||||
referer: "https://hexa.watch/"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function epsilon(ID) {
|
|
||||||
const actionIdentifier = await ilovethighs("https://mapple.uk/watch/movie/1061474-superman");
|
|
||||||
|
|
||||||
console.log("Action Identifier: " + actionIdentifier);
|
|
||||||
|
|
||||||
const sessionResponse = await soraFetch("https://enc-dec.app/api/enc-mapple");
|
|
||||||
const sessionData = await sessionResponse.json();
|
|
||||||
console.log("Session Data: " + JSON.stringify(sessionData));
|
|
||||||
const sessionID = sessionData.result.sessionId || "N/A";
|
|
||||||
console.log("ID: " + ID);
|
|
||||||
if (ID.startsWith('/movie/') || ID.startsWith('movie/')) {
|
|
||||||
const match = ID.match(/movie\/(\d+)/);
|
|
||||||
const tmdbID = match ? match[1] : '';
|
|
||||||
console.log("Extracted TMDB ID: " + tmdbID);
|
|
||||||
const headers = {
|
|
||||||
"Content-Type": "text/plain",
|
|
||||||
"next-action": actionIdentifier
|
|
||||||
};
|
|
||||||
const postData = `[{"mediaId":${tmdbID},"mediaType":"movie","tv_slug":"","source":"mapple","useFallbackVideo":false,"sessionId":"${sessionID}"}]`;
|
|
||||||
const response = await fetchv2("https://mapple.uk/watch/"+ ID, headers, "POST", postData);
|
|
||||||
const data = await response.text();
|
|
||||||
console.log("Stream data: " + data);
|
|
||||||
|
|
||||||
const lines = data.split('\n');
|
|
||||||
let streamData = null;
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.includes('"success":true')) {
|
|
||||||
streamData = JSON.parse(line.substring(line.indexOf('{')));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamData && streamData.data && streamData.data.stream_url) {
|
|
||||||
const m3u8Response = await soraFetch(streamData.data.stream_url, {
|
|
||||||
headers: {
|
|
||||||
"referer": "https://mapple.uk/",
|
|
||||||
"origin": "https://mapple.uk"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const m3u8Text = await m3u8Response.text();
|
|
||||||
console.log("M3U8 Playlist: " + m3u8Text);
|
|
||||||
|
|
||||||
const streams = [];
|
|
||||||
const lines = m3u8Text.split('\n');
|
|
||||||
const resolutionNames = {
|
|
||||||
"3840x2160": "4K",
|
|
||||||
"1920x1080": "1080p",
|
|
||||||
"1280x720": "720p",
|
|
||||||
"640x360": "360p"
|
|
||||||
};
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].startsWith('#EXT-X-STREAM-INF')) {
|
|
||||||
const resolutionMatch = lines[i].match(/RESOLUTION=(\d+x\d+)/);
|
|
||||||
const streamUrl = lines[i + 1];
|
|
||||||
if (resolutionMatch && streamUrl && streamUrl.trim()) {
|
|
||||||
const resolution = resolutionMatch[1];
|
|
||||||
const friendlyName = resolutionNames[resolution] || resolution;
|
|
||||||
streams.push({
|
|
||||||
title: friendlyName,
|
|
||||||
streamUrl: streamUrl.trim(),
|
|
||||||
headers: {
|
|
||||||
"referer": "https://mapple.uk/",
|
|
||||||
"origin": "https://mapple.uk"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let englishSubtitleUrl = "";
|
|
||||||
try {
|
|
||||||
const subResponse = await soraFetch(`https://mapple.uk/api/subtitles?id=${tmdbID}&mediaType=movie`);
|
|
||||||
const subData = await subResponse.json();
|
|
||||||
const englishSub = subData.find(sub => sub.language === "en");
|
|
||||||
if (englishSub) {
|
|
||||||
englishSubtitleUrl = englishSub.url;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
englishSubtitleUrl = "";
|
|
||||||
}
|
|
||||||
console.log("English Subtitle URL: " + englishSubtitleUrl);
|
|
||||||
console.log("Extracted streams: " + JSON.stringify(streams));
|
|
||||||
|
|
||||||
return JSON.stringify({
|
|
||||||
streams: streams.length > 0 ? streams : [
|
|
||||||
{
|
|
||||||
title: "Mapple Server",
|
|
||||||
streamUrl: streamData.data.stream_url,
|
|
||||||
headers: {
|
|
||||||
"referer": "https://mapple.uk/",
|
|
||||||
"origin": "https://mapple.uk"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
subtitle: englishSubtitleUrl || ""
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error("Failed to extract stream URL");
|
|
||||||
}
|
|
||||||
} else if (ID.startsWith('/tv/') || ID.startsWith('tv/')) {
|
|
||||||
const match = ID.match(/tv\/(\d+)-(\d+)-(\d+)/);
|
|
||||||
if (!match) throw new Error("Invalid TV URL format");
|
|
||||||
|
|
||||||
const tmdbID = match[1];
|
|
||||||
const seasonNumber = match[2];
|
|
||||||
const episodeNumber = match[3];
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
"Content-Type": "text/plain",
|
|
||||||
"next-action": actionIdentifier
|
|
||||||
};
|
|
||||||
const postData = `[{"mediaId":${tmdbID},"mediaType":"tv","tv_slug":"${seasonNumber}-${episodeNumber}","source":"mapple","useFallbackVideo":false,"sessionId":"${sessionID}"}]`;
|
|
||||||
const response = await fetchv2("https://mapple.uk/watch"+ ID, headers, "POST", postData);
|
|
||||||
const data = await response.text();
|
|
||||||
console.log("Stream data: " + data);
|
|
||||||
|
|
||||||
const lines = data.split('\n');
|
|
||||||
let streamData = null;
|
|
||||||
for (const line of lines) {
|
|
||||||
if (line.includes('"success":true')) {
|
|
||||||
streamData = JSON.parse(line.substring(line.indexOf('{')));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (streamData && streamData.data && streamData.data.stream_url) {
|
|
||||||
const m3u8Response = await soraFetch(streamData.data.stream_url, {
|
|
||||||
headers: {
|
|
||||||
"referer": "https://mapple.uk/",
|
|
||||||
"origin": "https://mapple.uk"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const m3u8Text = await m3u8Response.text();
|
|
||||||
console.log("M3U8 Playlist: " + m3u8Text);
|
|
||||||
|
|
||||||
const streams = [];
|
|
||||||
const lines = m3u8Text.split('\n');
|
|
||||||
const resolutionNames = {
|
|
||||||
"3840x2160": "4K",
|
|
||||||
"1920x1080": "1080p",
|
|
||||||
"1280x720": "720p",
|
|
||||||
"640x360": "360p"
|
|
||||||
};
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].startsWith('#EXT-X-STREAM-INF')) {
|
|
||||||
const resolutionMatch = lines[i].match(/RESOLUTION=(\d+x\d+)/);
|
|
||||||
const streamUrl = lines[i + 1];
|
|
||||||
if (resolutionMatch && streamUrl && streamUrl.trim()) {
|
|
||||||
const resolution = resolutionMatch[1];
|
|
||||||
const friendlyName = resolutionNames[resolution] || resolution;
|
|
||||||
streams.push({
|
|
||||||
title: friendlyName,
|
|
||||||
streamUrl: streamUrl.trim(),
|
|
||||||
headers: {
|
|
||||||
"referer": "https://mapple.uk/",
|
|
||||||
"origin": "https://mapple.uk"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let englishSubtitleUrl = "";
|
|
||||||
try {
|
|
||||||
const subResponse = await soraFetch(`https://mapple.uk/api/subtitles?id=${tmdbID}&mediaType=tv&season=${seasonNumber}&episode=${episodeNumber}`);
|
|
||||||
const subData = await subResponse.json();
|
|
||||||
const englishSub = subData.find(sub => sub.language === "en");
|
|
||||||
if (englishSub) {
|
|
||||||
englishSubtitleUrl = englishSub.url;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
englishSubtitleUrl = "";
|
|
||||||
}
|
|
||||||
console.log("English Subtitle URL: " + englishSubtitleUrl);
|
|
||||||
console.log("Extracted streams: " + JSON.stringify(streams));
|
|
||||||
|
|
||||||
return JSON.stringify({
|
|
||||||
streams: streams.length > 0 ? streams : [
|
|
||||||
{
|
|
||||||
title: "Mapple Server",
|
|
||||||
streamUrl: streamData.data.stream_url,
|
|
||||||
headers: {
|
|
||||||
"referer": "https://mapple.uk/",
|
|
||||||
"origin": "https://mapple.uk"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
subtitle: englishSubtitleUrl || ""
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error("Failed to extract stream URL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null, encoding: 'utf-8' }) {
|
async function soraFetch(url, options = { headers: {}, method: 'GET', body: null, encoding: 'utf-8' }) {
|
||||||
try {
|
try {
|
||||||
return await fetchv2(
|
return await fetchv2(
|
||||||
@@ -1362,50 +1069,7 @@ async function ilovefeet(imdbId, isSeries = false, season = null, episode = null
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ilovethighs(watchUrl) {
|
|
||||||
const htmlResponse = await soraFetch(watchUrl);
|
|
||||||
const htmlText = await htmlResponse.text();
|
|
||||||
|
|
||||||
const layoutMatch = htmlText.match(/<script[^>]*src="([^"]*app\/watch\/movie\/[^"]*layout-[^"]*\.js)"[^>]*><\/script><link rel="preload"/);
|
|
||||||
|
|
||||||
if (!layoutMatch || !layoutMatch[1]) {
|
|
||||||
throw new Error("error 1");
|
|
||||||
}
|
|
||||||
|
|
||||||
const beforeLayoutMatch = htmlText.match(/<script[^>]*src="([^"]*\.js)"[^>]*><\/script><script[^>]*src="[^"]*app\/watch\/(movie|tv)\/[^"]*layout-[^"]*\.js"/);
|
|
||||||
|
|
||||||
if (!beforeLayoutMatch || !beforeLayoutMatch[1]) {
|
|
||||||
throw new Error("error 2");
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetUrl = beforeLayoutMatch[1];
|
|
||||||
if (targetUrl.startsWith('/_next/')) {
|
|
||||||
targetUrl = 'https://mapple.uk' + targetUrl;
|
|
||||||
} else if (!targetUrl.startsWith('http')) {
|
|
||||||
targetUrl = 'https://mapple.uk/' + targetUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await soraFetch(targetUrl);
|
|
||||||
const text = await response.text();
|
|
||||||
|
|
||||||
let actionMatch = text.match(/createServerReference\)\("([a-f0-9]{40,})"[^"]*"getStreamUrl/);
|
|
||||||
if (!actionMatch) {
|
|
||||||
actionMatch = text.match(/createServerReference\)\("([a-f0-9]{40,})"/);
|
|
||||||
}
|
|
||||||
if (!actionMatch) {
|
|
||||||
actionMatch = text.match(/"([a-f0-9]{40,})"[^"]*"getStreamUrl/);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actionMatch && actionMatch[1]) {
|
|
||||||
return actionMatch[1];
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error("error 3: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("error 4");
|
|
||||||
}
|
|
||||||
|
|
||||||
function wordArrayToUint8Array(wordArray) {
|
function wordArrayToUint8Array(wordArray) {
|
||||||
const words = wordArray.words;
|
const words = wordArray.words;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Tsumi (詰み) - Literally Everything 2.0",
|
||||||
|
"iconUrl": "https://files.catbox.moe/krovkt.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.7",
|
||||||
|
"language": "English",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "4K",
|
||||||
|
"baseUrl": "https://google.com/",
|
||||||
|
"searchBaseUrl": "https://google.com/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/checkmate/checkmate.js",
|
||||||
|
"type": "anime/movies/shows",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "CineCalidad",
|
||||||
|
"iconUrl": "https://www.cinecalidad.ec/wp-content/themes/Cinecalidad/assets/img/favicon.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.cinecalidad.ec/",
|
||||||
|
"searchBaseUrl": "https://www.cinecalidad.ec/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/cinecalidad/cinecalidad.js",
|
||||||
|
"type": "shows/movies",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "CKSub",
|
||||||
|
"iconUrl": "https://cksub.org/wp-content/uploads/2024/12/cropped-i-am-the-fated-villain-gu-changge-192x192.jpg",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Chinese",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://cksub.org/",
|
||||||
|
"searchBaseUrl": "https://cksub.org/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/cksub/cksub.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Cloudy",
|
||||||
|
"iconUrl": "https://cloudy.pk/wp-content/uploads/2018/02/cloudy.pk-icon-200x200.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Hindi (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://cloudy.pk/",
|
||||||
|
"searchBaseUrl": "https://cloudy.pk/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/cloudy/cloudy.js",
|
||||||
|
"type": "shows/movies",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||
@@ -15,6 +15,36 @@ const allowEnglishInLanguages = false; // [true, false]
|
|||||||
const removeUnknownLanguages = false; // [true, false]
|
const removeUnknownLanguages = false; // [true, false]
|
||||||
// Settings end
|
// Settings end
|
||||||
|
|
||||||
|
function btoa(str) {
|
||||||
|
if (typeof globalThis !== "undefined" && typeof globalThis.btoa === "function" && globalThis.btoa !== btoa) {
|
||||||
|
return globalThis.btoa(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof Buffer !== "undefined") {
|
||||||
|
return Buffer.from(str, "utf8").toString("base64");
|
||||||
|
}
|
||||||
|
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
let result = "";
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < str.length) {
|
||||||
|
const byte1 = str.charCodeAt(i++) & 0xff;
|
||||||
|
const hasByte2 = i < str.length;
|
||||||
|
const byte2 = hasByte2 ? str.charCodeAt(i++) & 0xff : 0;
|
||||||
|
const hasByte3 = i < str.length;
|
||||||
|
const byte3 = hasByte3 ? str.charCodeAt(i++) & 0xff : 0;
|
||||||
|
|
||||||
|
const chunk = (byte1 << 16) | (byte2 << 8) | byte3;
|
||||||
|
result += chars[(chunk >> 18) & 63];
|
||||||
|
result += chars[(chunk >> 12) & 63];
|
||||||
|
result += hasByte2 ? chars[(chunk >> 6) & 63] : "=";
|
||||||
|
result += hasByte3 ? chars[chunk & 63] : "=";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
async function searchResults(keyword) {
|
async function searchResults(keyword) {
|
||||||
try {
|
try {
|
||||||
const moviesresponse = await fetchv2(
|
const moviesresponse = await fetchv2(
|
||||||
@@ -186,8 +216,8 @@ async function extractStreamUrl(ID) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const endpoint = type === "movie"
|
const endpoint = type === "movie"
|
||||||
? "https://comet.elfhosted.com/" + encodedConfig + "/stream/movie/" + actualID + ".json"
|
? "https://comet.feels.legal/" + encodedConfig + "/stream/movie/" + actualID + ".json"
|
||||||
: "https://comet.elfhosted.com/" + encodedConfig + "/stream/series/" + actualID + ".json";
|
: "https://comet.feels.legal/" + encodedConfig + "/stream/series/" + actualID + ".json";
|
||||||
|
|
||||||
const response = await fetchv2(endpoint);
|
const response = await fetchv2(endpoint);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Comet",
|
||||||
|
"iconUrl": "https://i.imgur.com/jmVoVMu.jpeg",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.2",
|
||||||
|
"language": "English",
|
||||||
|
"streamType": "MKV",
|
||||||
|
"quality": "4K",
|
||||||
|
"baseUrl": "https://www.google.com/",
|
||||||
|
"searchBaseUrl": "https://www.google.com/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/comet/comet.js",
|
||||||
|
"type": "anime/movies/shows",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": true,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"settings": true,
|
||||||
|
"supportsLuna": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "CrimsonFanSubs",
|
||||||
|
"iconUrl": "https://i3.wp.com/crimsonfansubs.com/wp-content/uploads/2021/06/cropped-favicon-image-192x192.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Chinese",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://crimsonfansubs.com/",
|
||||||
|
"searchBaseUrl": "https://crimsonfansubs.com/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/crimsonfansubs/crimsonfansubs.js",
|
||||||
|
"type": "anime",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": false,
|
||||||
|
"supportsMojuru": true,
|
||||||
|
"supportsDartotsu": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsAnymex": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true,
|
||||||
|
"supportsShirox": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"sourceName": "Cuevana3",
|
||||||
|
"iconUrl": "https://www.cuevana3.eu/apple-touch-icon.png",
|
||||||
|
"author": {
|
||||||
|
"name": "50/50",
|
||||||
|
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
|
||||||
|
},
|
||||||
|
"version": "1.0.0",
|
||||||
|
"language": "Spanish (DUB/SUB)",
|
||||||
|
"streamType": "HLS",
|
||||||
|
"quality": "1080p",
|
||||||
|
"baseUrl": "https://www.cuevana3.eu/",
|
||||||
|
"searchBaseUrl": "https://www.cuevana3.eu/",
|
||||||
|
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/cuevana3/cuevana3.js",
|
||||||
|
"type": "shows/movies",
|
||||||
|
"asyncJS": true,
|
||||||
|
"softsub": false,
|
||||||
|
"downloadSupport": true,
|
||||||
|
"supportsSora": true,
|
||||||
|
"supportsLuna": true,
|
||||||
|
"supportsTsumi": true,
|
||||||
|
"supportsHiyoku": true
|
||||||
|
}
|
||||||