1
0
forked from 50n50/sources

Compare commits

139 Commits

Author SHA1 Message Date
Gitea Actions Bot 400cf96447 chore: update library.json 2026-06-02 11:53:00 +00:00
sobet 059bc19173 Update animeworld/animeworld.js 2026-06-02 11:52:41 +00:00
50n50 74f821afd0 Upload files to "anistream" 2026-06-01 17:18:27 +00:00
Gitea Actions Bot 6ce219db8d chore: update library.json 2026-06-01 17:13:16 +00:00
50n50 0d335470ea Update anistream/anistream.json 2026-06-01 17:12:58 +00:00
Gitea Actions Bot 0c67e8db83 chore: update library.json 2026-06-01 17:11:42 +00:00
aka paul 6307f70497 push 2026-06-01 19:11:20 +02:00
50n50 1358cbbfff Update index.json 2026-06-01 16:32:40 +00:00
Gitea Actions Bot cd001b9eab chore: update library.json 2026-06-01 16:32:04 +00:00
aka paul 02147221e8 Push 2026-06-01 18:31:41 +02:00
aka paul aa6a0dd3f9 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-05-29 17:08:22 +02:00
aka paul 61d2d36b69 fix 2026-05-29 17:08:09 +02:00
Gitea Actions Bot 8c689ff476 chore: update library.json 2026-05-29 15:07:16 +00:00
aka paul 5cf145e516 add 2026-05-29 17:06:32 +02:00
Gitea Actions Bot 8ac56b2787 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-05-27 19:44:45 +00:00
aka paul 8c448c853e add subtitles 2026-05-27 21:43:10 +02:00
Gitea Actions Bot 4062b4a7be chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-05-27 19:19:42 +00:00
aka paul 9b18dd39d6 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-05-27 21:18:21 +02:00
aka paul eb3154f765 Add Shirox 2026-05-27 21:14:19 +02:00
Gitea Actions Bot 9be224e467 chore: update library.json 2026-05-27 19:02:38 +00:00
aka paul ebfcd4d6dc Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-05-27 21:02:00 +02:00
aka paul d7146f94da Update index.json 2026-05-27 21:00:06 +02:00
Gitea Actions Bot 64ef556d75 chore: update library.json 2026-05-27 18:31:01 +00:00
aka paul 597f279e49 update 2026-05-27 20:30:23 +02:00
Gitea Actions Bot caffaabc88 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-05-10 11:35:04 +00:00
aka paul 4fbb2bc138 jythy 2026-05-10 13:34:03 +02:00
Gitea Actions Bot 01b25ec53d chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-05-10 11:33:26 +00:00
aka paul c4fccf85bd fix 2026-05-10 13:32:22 +02:00
Gitea Actions Bot aa56034bf7 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-05-10 10:59:30 +00:00
aka paul 6400b9c508 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-05-10 12:58:28 +02:00
aka paul 92fa874883 update 2026-05-10 12:58:17 +02:00
Gitea Actions Bot fab874dbc5 chore: update library.json 2026-05-10 10:39:24 +00:00
aka paul c2efefaf0e Update animekai/animekai.json 2026-05-10 10:38:23 +00:00
aka paul 11d6986232 Update animekai/animekai.js 2026-05-10 10:38:09 +00:00
Gitea Actions Bot 4bbde44022 chore: update library.json 2026-05-08 16:24:42 +00:00
aka paul 7cced4311b removal 2026-05-08 18:24:05 +02:00
aka paul e324070a32 Fix subtitles 2026-05-05 17:11:07 +02:00
Gitea Actions Bot 7b038968f3 chore: update library.json 2026-05-05 14:11:33 +00:00
sobet 837659590b Update streamingunity/streamingunity.json 2026-05-05 14:10:37 +00:00
sobet db7958ff83 Update streamingunity/streamingunity.js 2026-05-05 14:10:20 +00:00
Gitea Actions Bot b9472ac7a2 chore: update library.json 2026-04-30 15:14:13 +00:00
aka paul 6d987a6242 fix 2026-04-30 17:13:44 +02:00
aka paul 5a4ff66443 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-04-30 16:52:54 +02:00
aka paul 286041b093 Remove proxy and fix subtitles 2026-04-30 16:52:45 +02:00
Gitea Actions Bot d1e170cfd4 chore: update library.json 2026-04-30 14:44:53 +00:00
aka paul d55b0dbcfc Fix subtitles videasy 2026-04-30 16:44:23 +02:00
Gitea Actions Bot af610ae9c5 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-27 18:38:55 +00:00
aka paul 3c756e2890 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-04-27 20:37:28 +02:00
aka paul 5a83892bc1 b, 2026-04-27 20:37:18 +02:00
Gitea Actions Bot a651afa1ba chore: update library.json 2026-04-27 18:36:47 +00:00
aka paul 951663b6d0 update too 2026-04-27 20:36:11 +02:00
aka paul 7b03037a59 update 2026-04-27 20:35:54 +02:00
Gitea Actions Bot 388f6f4991 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-24 14:28:45 +00:00
aka paul b6d9e70dc7 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-04-24 16:27:30 +02:00
aka paul 3b720ebd25 Add modules by emp0ry 2026-04-24 16:27:23 +02:00
Gitea Actions Bot efa598937b chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-23 20:16:24 +00:00
aka paul 6f0a381e77 Update ashi.json 2026-04-23 22:15:00 +02:00
aka paul db52efdb5b Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-04-23 22:13:12 +02:00
aka paul f7e4efd254 fix 2026-04-23 22:12:20 +02:00
Gitea Actions Bot a853ec6aee chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-23 20:11:39 +00:00
aka paul c10e3b71b7 fix 2026-04-23 22:09:43 +02:00
aka paul 84a8284a50 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-04-23 22:07:41 +02:00
aka paul cbabd0643e fix 2026-04-23 22:07:29 +02:00
Gitea Actions Bot 7a2c05abb6 chore: update library.json 2026-04-23 20:01:04 +00:00
aka paul 5a8c5cbb62 fix 2026-04-23 22:00:27 +02:00
Gitea Actions Bot 3d44fa0bb2 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-23 14:28:35 +00:00
aka paul 1098637c15 Update gojowtf/gojowtf.json 2026-04-23 14:26:17 +00:00
aka paul 7d4e12986b Update gojowtf/gojowtf.js 2026-04-23 14:26:02 +00:00
Gitea Actions Bot 904a711918 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-23 14:11:19 +00:00
aka paul 2c2d6b05a1 Update gojowtf/gojowtf.json 2026-04-23 14:10:06 +00:00
aka paul 72cb66f38a Update gojowtf/gojowtf.json 2026-04-23 14:09:16 +00:00
Gitea Actions Bot d009e3ed98 chore: update library.json 2026-04-23 14:08:58 +00:00
aka paul c2b6118abc Update gojowtf/gojowtf.js 2026-04-23 14:08:30 +00:00
Gitea Actions Bot 9546ddee03 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-18 18:33:10 +00:00
aka paul 03f3bc6ffc Update dora-video/dora-video.json 2026-04-18 18:31:25 +00:00
aka paul 17b3606d4f Update dora-video/dora-video.js 2026-04-18 18:31:13 +00:00
Gitea Actions Bot 5605f80b0f chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-17 18:48:51 +00:00
aka paul 9643b4a963 Update animedefenders/animedefenders.json 2026-04-17 18:47:02 +00:00
aka paul 58788a8cf2 Update animedefenders/animedefenders.js 2026-04-17 18:46:51 +00:00
Gitea Actions Bot 4ad76c5519 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-17 12:16:49 +00:00
aka paul d56e8784f6 Update animedefenders/animedefenders.json 2026-04-17 12:14:54 +00:00
aka paul c3dcbceefb Update animedefenders/animedefenders.js 2026-04-17 12:14:39 +00:00
Gitea Actions Bot 29c1eeb15e chore: update library.json 2026-04-16 16:09:55 +00:00
aka paul e5f71d3e0f Add .gitea/workflows/library.yml 2026-04-16 16:09:28 +00:00
Gitea Actions Bot b3c073a4f6 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-13 16:01:46 +00:00
aka paul 81ef2f9398 Add Hiyoku value 2026-04-13 18:00:46 +02:00
aka paul 1ced6f7e76 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-04-11 22:33:18 +02:00
aka paul 85e42df413 add Tsumi 2026-04-11 22:32:51 +02:00
Gitea Actions Bot 79990ff562 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-11 20:29:23 +00:00
aka paul ab217a7b5a add Anymex 2026-04-11 22:28:31 +02:00
Gitea Actions Bot b1675936a6 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-10 13:38:03 +00:00
aka paul dd92aea52e Update animepahe/animepahe.json 2026-04-10 13:37:18 +00:00
aka paul 284414a994 Update animepahe/animepahe.js 2026-04-10 13:37:08 +00:00
Gitea Actions Bot ea29804e86 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-04-02 21:21:39 +00:00
aka paul 6f9b7112ec Update animepahe/animepahe.json 2026-04-02 21:21:02 +00:00
aka paul cddda574cc Update animepahe/animepahe.js 2026-04-02 21:20:18 +00:00
aka paul 98ae0a541b Update 2026-03-28 13:10:11 +01:00
aka paul 5e26b3b171 Fix 2026-03-28 13:07:42 +01:00
Gitea Actions Bot 352b88b030 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-03-25 17:21:16 +00:00
aka paul 441dbbd9a2 Update animelib/animelib.json 2026-03-25 17:20:35 +00:00
aka paul 611cbc8e1a Update animelib/animelib.js 2026-03-25 17:20:22 +00:00
Gitea Actions Bot 2fc80e1206 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-03-25 16:57:57 +00:00
aka paul e4345774bd Update ashi/ashi.json 2026-03-25 16:57:10 +00:00
aka paul 6acea93a3c Update ashi/ashi.js 2026-03-25 16:56:54 +00:00
aka paul 58f398383e Update animekai/dub/animekai.json 2026-03-25 16:51:06 +00:00
aka paul e2bc89511c Update animekai/dub/animekai.js 2026-03-25 16:50:55 +00:00
aka paul 19ffb8254b Update animekai/hardsub/animekai.json 2026-03-25 16:49:16 +00:00
aka paul 484e9b20e4 Update animekai/hardsub/animekai.js 2026-03-25 16:49:05 +00:00
Gitea Actions Bot 2e823488d5 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-03-25 16:47:19 +00:00
aka paul f700830e2f Update animekai/animekai.json 2026-03-25 16:46:27 +00:00
aka paul 14f0aea33d Update animekai/animekai.js 2026-03-25 16:45:27 +00:00
Gitea Actions Bot 35986ceb2b chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-03-14 23:43:00 +00:00
aka paul 8a7a8f3a6a Update hianime/hianime.json 2026-03-14 23:42:20 +00:00
aka paul df457e7ade Update hianime/hianime.js 2026-03-14 23:42:03 +00:00
Gitea Actions Bot f8a332fdb7 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-03-14 21:13:56 +00:00
aka paul aff574469c Update ashi/ashi.json 2026-03-14 21:13:12 +00:00
aka paul 71e8747998 Update ashi/ashi.js 2026-03-14 21:04:25 +00:00
aka paul b856529fce Update turkish123/turkish123.json 2026-03-14 11:41:42 +00:00
Gitea Actions Bot d09299eea2 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-03-14 11:41:15 +00:00
aka paul cbdac19e16 Update turkish123/turkish123.json 2026-03-14 11:40:24 +00:00
aka paul 78992bbd25 Update turkish123/turkish123.js 2026-03-14 11:40:09 +00:00
aka paul a32db404cd Update lncrawler/lncrawler.js 2026-03-14 11:34:14 +00:00
aka paul 207dfbcac9 Update lncrawler/lncrawler.js 2026-03-14 11:30:24 +00:00
aka paul 0149e192d4 Update lncrawler/lncrawler.js 2026-03-14 11:28:53 +00:00
aka paul 0c4b055562 push 2026-03-13 23:56:02 +01:00
Gitea Actions Bot 6f51c9a900 chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-03-09 16:02:19 +00:00
aka paul d4c72a54ee add support 2026-03-09 17:01:36 +01:00
aka paul cfac44b405 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-03-09 16:26:51 +01:00
aka paul e2413ee76c sort 2026-03-09 16:26:43 +01:00
aka paul c8092410a5 Delete asset.png 2026-03-09 15:26:25 +00:00
aka paul c477fb7bb0 sort 2026-03-09 16:25:36 +01:00
aka paul 2a3817f04f sort 2026-03-09 16:24:48 +01:00
Gitea Actions Bot cb3aa79a4b chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-03-09 15:20:20 +00:00
aka paul a9963bf9a1 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-03-09 16:19:37 +01:00
aka paul 68850c048e should be alright 2026-03-09 16:18:24 +01:00
Gitea Actions Bot b00be4f74d chore: sync version strings from individual JSON files
Automatically updated version fields in index.json to match their respective source files

Generated by sync-versions workflow
2026-03-09 13:51:40 +00:00
aka paul a88ed329a5 Merge branch 'main' of https://git.luna-app.eu/50n50/sources 2026-03-09 14:49:08 +01:00
aka paul 5c34fb248b test 2026-03-09 14:48:59 +01:00
aka paul 1c0d5e9b09 Merge pull request 'Moved AniWorld modules to Cufiy/sora-modules repo' (#10) from Cufiy/sources-fork:main into main
Reviewed-on: https://git.luna-app.eu/50n50/sources/pulls/10
2026-03-09 00:05:22 +00:00
372 changed files with 9188 additions and 5619 deletions
@@ -18,5 +18,7 @@
"downloadSupport": true, "downloadSupport": true,
"note": "USE AN EXTERNAL PLAYER (E.G., VLC/MPV)", "note": "USE AN EXTERNAL PLAYER (E.G., VLC/MPV)",
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
@@ -60,13 +60,13 @@ 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 {
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url)); const response = await fetchv2(url);
const htmlText = await response.text(); const htmlText = await response.text();
const descriptionMatch = (/<div class="description text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1]; const descriptionMatch = (/<div class="description text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
@@ -90,7 +90,7 @@ async function extractDetails(url) {
async function extractEpisodes(movieUrl) { async function extractEpisodes(movieUrl) {
try { try {
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(movieUrl)); const response = await fetchv2(movieUrl);
const htmlText = await response.text(); const htmlText = await response.text();
const movieIDMatch = (htmlText.match(/<div class="detail-lower"[^>]*id="movie-rating"[^>]*data-id="([^"]+)"/) || [])[1]; const movieIDMatch = (htmlText.match(/<div class="detail-lower"[^>]*id="movie-rating"[^>]*data-id="([^"]+)"/) || [])[1];
if (!movieIDMatch) { if (!movieIDMatch) {
@@ -105,7 +105,7 @@ async function extractEpisodes(movieUrl) {
const token = movieIdTokenData.result; const token = movieIdTokenData.result;
const episodeListUrl = `https://1movies.bz/ajax/episodes/list?id=${movieIDMatch}&_=${token}`; const episodeListUrl = `https://1movies.bz/ajax/episodes/list?id=${movieIDMatch}&_=${token}`;
const episodeListResponse = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(episodeListUrl)); const episodeListResponse = await fetchv2(episodeListUrl);
const episodeListData = await episodeListResponse.json(); const episodeListData = await episodeListResponse.json();
const cleanedHtml = cleanJsonHtml(episodeListData.result); const cleanedHtml = cleanJsonHtml(episodeListData.result);
@@ -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,27 +143,24 @@ 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("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(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 tokenPromises = [ const tokenData = await fetchv2(`https://enc-dec.app/api/enc-movies-flix?text=${encodeURIComponent(serverId)}`)
fetchv2(`https://enc-dec.app/api/enc-movies-flix?text=${encodeURIComponent(serverId)}`) .then(res => res.json());
];
const tokenResponses = await Promise.all(tokenPromises);
const tokenData = await tokenResponses[0].json();
const token = tokenData.result; const token = tokenData.result;
if (!token) { if (!token) {
@@ -167,7 +169,7 @@ async function extractStreamUrl(url) {
} }
const streamUrl = `https://1movies.bz/ajax/links/view?id=${serverId}&_=${token}`; const streamUrl = `https://1movies.bz/ajax/links/view?id=${serverId}&_=${token}`;
const streamResponse = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(streamUrl)); const streamResponse = await fetchv2(streamUrl);
const streamData = await streamResponse.json(); const streamData = await streamResponse.json();
if (!streamData.result) { if (!streamData.result) {
@@ -175,42 +177,53 @@ async function extractStreamUrl(url) {
return "error"; return "error";
} }
const decryptPromises = [ const decryptData = await fetchv2(
fetchv2("https://enc-dec.app/api/dec-movies-flix", { "Content-Type": "application/json" }, "POST", JSON.stringify({ text: streamData.result })) `https://enc-dec.app/api/dec-movies-flix?text=${streamData.result}`,
]; headers
).then(res => res.json());
const decryptResponses = await Promise.all(decryptPromises); const decryptedUrl = decryptData.result?.url;
const decryptData = await decryptResponses[0].json();
const decryptedUrl = decryptData.result.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;
@@ -219,59 +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",
"agent": "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 finalPromises = [ const streams = [];
fetchv2("https://enc-dec.app/api/dec-mega", { "Content-Type": "application/json" }, "POST", JSON.stringify(postData)) 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 finalResponses = await Promise.all(finalPromises); for (let i = 0; i < lines.length; i++) {
const finalJson = await finalResponses[0].json(); const line = lines[i].trim();
if (line.startsWith('#EXT-X-STREAM-INF:')) {
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
let quality = 'Unknown';
const m3u8Link = finalJson?.result?.sources?.[0]?.file; if (resolutionMatch) {
const [width, height] = resolutionMatch[1].split('x');
quality = `${height}p`;
}
const m3u8Response = await fetchv2(m3u8Link); if (i + 1 < lines.length) {
const m3u8Text = await m3u8Response.text(); let streamPath = lines[i + 1].trim();
let absolutePath;
const baseUrl = m3u8Link.substring(0, m3u8Link.lastIndexOf('/') + 1); if (streamPath.startsWith('/api/')) {
absolutePath = 'https://1anime.app' + streamPath;
const streams = []; } else if (streamPath.startsWith('http')) {
const lines = m3u8Text.split('\n'); absolutePath = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(streamPath);
} else {
for (let i = 0; i < lines.length; i++) { const baseUrl = m3u8Link.substring(0, m3u8Link.lastIndexOf('/') + 1);
const line = lines[i].trim(); absolutePath = "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(baseUrl + streamPath);
if (line.startsWith('#EXT-X-STREAM-INF:')) { }
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/); streams.push({
let quality = 'Unknown'; title: quality,
streamUrl: absolutePath
if (resolutionMatch) { });
const [width, height] = resolutionMatch[1].split('x'); pushedQualities++;
quality = `${height}p`; }
}
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";
} }
} }
@@ -5,7 +5,7 @@
"name": "50/50", "name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s" "icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
}, },
"version": "1.2.1", "version": "1.2.5",
"language": "English", "language": "English",
"streamType": "HLS", "streamType": "HLS",
"quality": "1080p", "quality": "1080p",
@@ -17,5 +17,7 @@
"softsub": true, "softsub": true,
"downloadSupport": true, "downloadSupport": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
@@ -18,5 +18,8 @@
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,6 +17,11 @@
"softsub": true, "softsub": true,
"downloadSupport": true, "downloadSupport": true,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
+697
View File
@@ -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 = {
"&amp;": "&",
"&lt;": "<",
"&gt;": ">",
"&quot;": '"',
"&#39;": "'",
"&apos;": "'",
"&nbsp;": " ",
"&laquo;": "«",
"&raquo;": "»"
};
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 (_) {}
+25
View File
@@ -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
}
@@ -24,6 +24,24 @@ 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
// ------------------------------------------------------------ // ------------------------------------------------------------
@@ -117,8 +135,11 @@ async function extractEpisodes(url) {
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
@@ -137,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
@@ -162,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;
} }
@@ -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.3", "version": "1.0.4",
"language": "Russian", "language": "Russian",
"streamType": "HLS", "streamType": "HLS",
"quality": "1080p", "quality": "1080p",
@@ -18,6 +18,8 @@
"type": "anime", "type": "anime",
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsShirox": true
} }
@@ -16,5 +16,8 @@
"type": "anime", "type": "anime",
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": 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,7 +1,9 @@
async function getDomainsList() { async function getDomainsList() {
try { try {
console.log("Fetching domains from anime-sama.pw...");
const response = await fetchv2("https://anime-sama.pw/"); const response = await fetchv2("https://anime-sama.pw/");
const html = await response.text(); const html = await response.text();
console.log("Fetched HTML length:", html.length);
const domainRegex = /{ name: '([^']+)' }/g; const domainRegex = /{ name: '([^']+)' }/g;
const domains = []; const domains = [];
@@ -10,37 +12,47 @@ async function getDomainsList() {
domains.push(match[1]); domains.push(match[1]);
} }
console.log("Parsed domains:", domains);
return domains.length > 0 ? domains : ["anime-sama.tv"]; return domains.length > 0 ? domains : ["anime-sama.tv"];
} catch (err) { } catch (err) {
console.error("Error in getDomainsList:", err);
return ["anime-sama.tv"]; return ["anime-sama.tv"];
} }
} }
async function searchResults(keyword) { async function searchResults(keyword) {
console.log("searchResults keyword:", keyword);
const domains = await getDomainsList(); 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]; const firstDomain = domains[0];
console.log("Trying first domain:", firstDomain);
const firstResult = await trySearch(firstDomain, keyword, regex); const firstResult = await trySearch(firstDomain, keyword, regex);
if (firstResult && firstResult.length > 0) { if (firstResult && firstResult.length > 0) {
console.log("Results found in first domain:", firstDomain);
return JSON.stringify(firstResult); return JSON.stringify(firstResult);
} }
console.log("No results in first domain, trying others...");
const otherDomains = domains.slice(1); const otherDomains = domains.slice(1);
const promises = otherDomains.map(domain => trySearch(domain, keyword, regex)); const promises = otherDomains.map(domain => trySearch(domain, keyword, regex));
const results = await Promise.all(promises); const results = await Promise.all(promises);
for (let result of results) { for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result && result.length > 0) { if (result && result.length > 0) {
console.log("Results found in domain:", otherDomains[i]);
return JSON.stringify(result); return JSON.stringify(result);
} }
} }
console.log("No results found in any domain.");
return JSON.stringify([]); return JSON.stringify([]);
} }
async function trySearch(domain, keyword, regex) { async function trySearch(domain, keyword, regex) {
try { try {
console.log("trySearch domain:", domain, "keyword:", keyword);
const headers = { const headers = {
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With": "XMLHttpRequest", "X-Requested-With": "XMLHttpRequest",
@@ -54,6 +66,7 @@ async function trySearch(domain, keyword, regex) {
`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 = []; const results = [];
let match; let match;
@@ -66,8 +79,10 @@ async function trySearch(domain, keyword, regex) {
}); });
} }
console.log("trySearch found results:", results.length);
return results; return results;
} catch (err) { } catch (err) {
console.error("trySearch error for domain", domain, ":", err);
return []; return [];
} }
} }
@@ -16,5 +16,8 @@
"type": "anime", "type": "anime",
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

@@ -19,5 +19,8 @@
"downloadSupport": true, "downloadSupport": true,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -14,6 +14,11 @@
"scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animebalkan/animebalkan.js", "scriptUrl": "https://git.luna-app.eu/50n50/sources/raw/branch/main/animebalkan/animebalkan.js",
"type": "anime", "type": "anime",
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

@@ -18,5 +18,8 @@
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -19,5 +19,8 @@
"note": "Use external player.", "note": "Use external player.",
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -16,5 +16,8 @@
"type": "anime", "type": "anime",
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,6 +17,11 @@
"softsub": true, "softsub": true,
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
+337
View File
@@ -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(/&#8217;/g, "'")
.replace(/&#8211;/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(/&#039;/g, "'")
.replace(/&quot;/g, "\"")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&nbsp;/g, " ");
}
@@ -5,7 +5,7 @@
"name": "50/50", "name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s" "icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
}, },
"version": "1.0.0", "version": "1.0.4",
"language": "English", "language": "English",
"streamType": "HLS", "streamType": "HLS",
"quality": "1080p", "quality": "1080p",
@@ -17,6 +17,11 @@
"softsub": false, "softsub": false,
"downloadSupport": true, "downloadSupport": true,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -18,6 +18,11 @@
"downloadSupport": true, "downloadSupport": true,
"note": "Make sure you're on the latest version of Sora.", "note": "Make sure you're on the latest version of Sora.",
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
+324
View File
@@ -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(/&#8217;/g, "'")
.replace(/&#8211;/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(/&#039;/g, "'")
.replace(/&quot;/g, "\"")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&nbsp;/g, " ");
}
@@ -5,7 +5,7 @@
"name": "50/50", "name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s" "icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
}, },
"version": "1.1.3", "version": "1.1.7",
"language": "English", "language": "English",
"streamType": "HLS", "streamType": "HLS",
"quality": "1080p", "quality": "1080p",
@@ -18,6 +18,11 @@
"downloadSupport": true, "downloadSupport": true,
"note": "Make sure you're on the latest version of Sora.", "note": "Make sure you're on the latest version of Sora.",
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
+317
View File
@@ -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(/&#8217;/g, "'")
.replace(/&#8211;/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(/&#039;/g, "'")
.replace(/&quot;/g, "\"")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&nbsp;/g, " ");
}
@@ -5,7 +5,7 @@
"name": "50/50", "name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s" "icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
}, },
"version": "1.1.1", "version": "1.1.6",
"language": "English", "language": "English",
"streamType": "HLS", "streamType": "HLS",
"quality": "1080p", "quality": "1080p",
@@ -18,6 +18,11 @@
"downloadSupport": true, "downloadSupport": true,
"note": "Make sure you're on the latest version of Sora.", "note": "Make sure you're on the latest version of Sora.",
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,6 +17,11 @@
"softsub": true, "softsub": true,
"downloadSupport": true, "downloadSupport": true,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -18,5 +18,8 @@
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,6 +17,11 @@
"softsub": false, "softsub": false,
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,6 +17,11 @@
"softsub": false, "softsub": false,
"downloadSupport": true, "downloadSupport": true,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -18,5 +18,8 @@
"downloadSupport": true, "downloadSupport": true,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -15,6 +15,11 @@
"asyncJS": true, "asyncJS": true,
"type": "anime", "type": "anime",
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -18,5 +18,8 @@
"downloadSupport": true, "downloadSupport": true,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -18,5 +18,8 @@
"downloadSupport": true, "downloadSupport": true,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,5 +17,7 @@
"softsub": false, "softsub": false,
"downloadSupport": false, "downloadSupport": false,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
+309
View File
@@ -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.1", "version": "1.0.2",
"language": "Russian", "language": "Russian",
"streamType": "MP4", "streamType": "MP4",
"quality": "720p", "quality": "720p",
@@ -18,6 +18,8 @@
"type": "anime", "type": "anime",
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsShirox": true
} }
@@ -18,5 +18,8 @@
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -18,5 +18,10 @@
"downloadSupport": false, "downloadSupport": false,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true, "supportsLuna": true,
"supportsMojuru": true "supportsMojuru": true,
"supportsDartotsu": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,6 +17,11 @@
"softsub": true, "softsub": true,
"downloadSupport": true, "downloadSupport": true,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -18,5 +18,8 @@
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -16,5 +16,7 @@
"asyncJS": true, "asyncJS": true,
"downloadSupport": true, "downloadSupport": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
@@ -16,5 +16,7 @@
"asyncJS": true, "asyncJS": true,
"downloadSupport": true, "downloadSupport": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
+438 -209
View File
@@ -4,8 +4,52 @@
// //
// //
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 &amp; 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) { async function searchResults(query) {
const encodeQuery = keyword => encodeURIComponent(keyword); 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) => { const decodeHtmlEntities = (str) => {
if (!str) return str; if (!str) return str;
@@ -59,6 +103,14 @@ async function searchResults(query) {
if (!isStopword) significantMatches++; if (!isStopword) significantMatches++;
} }
else if (qToken.length >= 4 && tToken.length >= 4) { 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 dist = levenshteinDistance(qToken, tToken);
const maxLen = Math.max(qToken.length, tToken.length); const maxLen = Math.max(qToken.length, tToken.length);
const similarity = 1 - (dist / maxLen); const similarity = 1 - (dist / maxLen);
@@ -149,6 +201,7 @@ async function searchResults(query) {
const extractHrefRegex = /href="([^"]*)"/; const extractHrefRegex = /href="([^"]*)"/;
const extractImageRegex = /data-src="([^"]*)"/; const extractImageRegex = /data-src="([^"]*)"/;
const extractTitleRegex = /title="([^"]*)"/; const extractTitleRegex = /title="([^"]*)"/;
let useProxy = true;
const extractResultsFromHTML = (htmlText) => { const extractResultsFromHTML = (htmlText) => {
const results = []; const results = [];
@@ -170,7 +223,7 @@ async function searchResults(query) {
if (fullHref && imageSrc && cleanTitle) { if (fullHref && imageSrc && cleanTitle) {
results.push({ results.push({
href: `Animekai:${fullHref}`, href: `Animekai:${fullHref}`,
image: "https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(imageSrc), image: useProxy ? proxyUrl(imageSrc) : imageSrc,
title: cleanTitle title: cleanTitle
}); });
} }
@@ -181,21 +234,33 @@ async function searchResults(query) {
try { try {
const encodedQuery = encodeQuery(query); const encodedQuery = encodeQuery(query);
const urls = [ useProxy = await isAnimekaiBlockedForUser();
`${searchBaseUrl}${encodedQuery}`, const fetchPages = async (pages) => {
`${searchBaseUrl}${encodedQuery}&page=2`, const urls = pages.map(page =>
`${searchBaseUrl}${encodedQuery}&page=3` 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 responses = await Promise.all(urls.map(url => fetchv2(useProxy ? proxyUrl(url) : url)));
const htmlTexts = await Promise.all(responses.map(res => res.text())); const htmlTexts = await Promise.all(responses.map(res => res.text()));
const allResults = []; const allResults = [];
htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html))); htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html)));
return allResults; return allResults;
};
const page1Results = await fetchPages([1]);
return {
page1Results,
fetchRemaining: () => fetchPages([2, 3])
};
} catch (error) { } catch (error) {
console.error("Animekai search error:" + error); console.error("Animekai search error:" + error);
return []; return {
page1Results: [],
fetchRemaining: async () => []
};
} }
}; };
@@ -228,41 +293,123 @@ async function searchResults(query) {
try { try {
const encodedQuery = encodeQuery(query); const encodedQuery = encodeQuery(query);
const urls = [ const fetchPages = async (pages) => {
`${searchBaseUrl}${encodedQuery}`, const urls = pages.map(page =>
`${searchBaseUrl}${encodedQuery}&page=2`, page === 1
`${searchBaseUrl}${encodedQuery}&page=3` ? `${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 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 htmlTexts = await Promise.all(responses.map(res => res.text()));
const allResults = []; const allResults = [];
htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html))); htmlTexts.forEach(html => allResults.push(...extractResultsFromHTML(html)));
return allResults; return allResults;
};
const page1Results = await fetchPages([1]);
return {
page1Results,
fetchRemaining: () => fetchPages([2, 3])
};
} catch (error) { } catch (error) {
console.error("1Movies search error:" + error); console.error("1Movies search error:" + error);
return []; return {
page1Results: [],
fetchRemaining: async () => []
};
} }
}; };
try { try {
const [animekaiResults, oneMoviesResults] = await Promise.all([ const animekaiPromise = animekaiSearch();
animekaiSearch(),
oneMoviesSearch() 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
]); ]);
const mergedResults = [...animekaiResults, ...oneMoviesResults]; rankedResults = rankResults(mergedResults);
filteredResults = rankedResults.items;
const scoredResults = mergedResults.map(r => ({ if (shouldFastReturn(filteredResults.length, rankedResults.topScore)) {
...r, return JSON.stringify(filteredResults);
score: fuzzyMatch(query, r.title) }
}));
const filteredResults = scoredResults const [animekaiExtra, oneMoviesExtra] = await Promise.all([
.filter(r => r.score > 50) animekaiData.fetchRemaining(),
.sort((a, b) => b.score - a.score) oneMoviesData.fetchRemaining()
.map(({ score, ...rest }) => rest); ]);
mergedResults = dedupeResults([
...mergedResults,
...animekaiExtra,
...oneMoviesExtra
]);
rankedResults = rankResults(mergedResults);
filteredResults = rankedResults.items;
return JSON.stringify(filteredResults.length > 0 ? filteredResults : [{ return JSON.stringify(filteredResults.length > 0 ? filteredResults : [{
href: "", href: "",
@@ -284,7 +431,8 @@ async function extractDetails(url) {
const actualUrl = url.replace("Animekai:", "").trim(); const actualUrl = url.replace("Animekai:", "").trim();
try { try {
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl)); const useProxy = await isAnimekaiBlockedForUser();
const response = await fetchv2(useProxy ? proxyUrl(actualUrl) : actualUrl);
const htmlText = await response.text(); const htmlText = await response.text();
const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1]; const descriptionMatch = (/<div class="desc text-expand">([\s\S]*?)<\/div>/.exec(htmlText) || [])[1];
@@ -332,7 +480,8 @@ async function extractEpisodes(url) {
try { try {
if (url.startsWith("Animekai:")) { if (url.startsWith("Animekai:")) {
const actualUrl = url.replace("Animekai:", "").trim(); const actualUrl = url.replace("Animekai:", "").trim();
const htmlText = await (await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl))).text(); 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]; 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" }]);
@@ -341,7 +490,7 @@ async function extractEpisodes(url) {
const token = tokenData.result; const token = tokenData.result;
const episodeListUrl = `https://anikai.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("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(episodeListUrl))).json(); const episodeListData = await (await fetchv2(useProxy ? proxyUrl(episodeListUrl) : episodeListUrl)).json();
const cleanedHtml = cleanJsonHtml(episodeListData.result); const cleanedHtml = cleanJsonHtml(episodeListData.result);
const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g; const episodeRegex = /<a[^>]+num="([^"]+)"[^>]+token="([^"]+)"[^>]*>/g;
@@ -397,7 +546,20 @@ async function extractStreamUrl(url) {
} }
if (source === "Animekai") { 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 { try {
const useProxy = await isAnimekaiBlockedForUser();
const tokenMatch = actualUrl.match(/token=([^&]+)/); const tokenMatch = actualUrl.match(/token=([^&]+)/);
if (tokenMatch && tokenMatch[1]) { if (tokenMatch && tokenMatch[1]) {
const rawToken = tokenMatch[1]; const rawToken = tokenMatch[1];
@@ -407,22 +569,38 @@ async function extractStreamUrl(url) {
actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`); actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
} }
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl)); const response = await fetchv2(useProxy ? proxyUrl(actualUrl) : actualUrl);
const text = await response.text(); const text = await response.text();
let ajaxResultHtml = "";
try {
const parsedAjax = JSON.parse(text);
ajaxResultHtml = parsedAjax?.result || "";
} catch {}
const cleanedHtml = cleanJsonHtml(text); 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 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 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 dubRegex = /<div class="server-items lang-group" data-id="dub"[^>]*>([\s\S]*?)<\/div>/;
const subMatch = subRegex.exec(cleanedHtml); const subContent = subRegex.exec(serverHtmlSource)?.[1]?.trim() || "";
const softsubMatch = softsubRegex.exec(cleanedHtml); const softsubContent = softsubRegex.exec(serverHtmlSource)?.[1]?.trim() || "";
const dubMatch = dubRegex.exec(cleanedHtml); const dubContent = dubRegex.exec(serverHtmlSource)?.[1]?.trim() || "";
const subContent = subMatch ? subMatch[1].trim() : "";
const softsubContent = softsubMatch ? softsubMatch[1].trim() : ""; const extractServerId = (content) => {
const dubContent = dubMatch ? dubMatch[1].trim() : ""; if (!content) return null;
const serverSpanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>Server 1<\/span>/; const spanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>/g;
const serverIdDub = serverSpanRegex.exec(dubContent)?.[1]; const ids = [];
const serverIdSoftsub = serverSpanRegex.exec(softsubContent)?.[1]; let match;
const serverIdSub = serverSpanRegex.exec(subContent)?.[1]; 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 = [ const tokenRequestData = [
{ name: "Dub", data: serverIdDub }, { name: "Dub", data: serverIdDub },
@@ -430,109 +608,80 @@ async function extractStreamUrl(url) {
{ name: "Sub", data: serverIdSub } { name: "Sub", data: serverIdSub }
].filter(item => item.data); ].filter(item => item.data);
const tokenPromises = tokenRequestData.map(item => const tokenResults = await Promise.all(tokenRequestData.map(item =>
fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(item.data)}`) fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(item.data)}`)
.then(res => res.json()) .then(res => res.json())
.then(json => ({ name: item.name, data: json.result })) .then(json => ({ name: item.name, data: json.result }))
.catch(err => ({ name: item.name, error: err.toString() })) .catch(err => ({ name: item.name, error: err.toString() }))
); ));
const tokenResults = await Promise.all(tokenPromises);
const streamUrls = tokenResults.map(result => { const serverIdMap = { "Dub": serverIdDub, "Softsub": serverIdSoftsub, "Sub": serverIdSub };
const serverIdMap = {
"Dub": serverIdDub,
"Softsub": serverIdSoftsub,
"Sub": serverIdSub
};
return {
type: result.name,
url: `https://anikai.to/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data}`
};
});
const processStreams = async (streamUrls) => { const streamUrls = tokenResults.map(result => ({
const streamResponses = await Promise.all( type: result.name,
streamUrls.map(async ({ type, url }) => { url: `https://anikai.to/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data}`
try { }));
const res = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(url));
const json = await res.json();
return {
type: type,
result: json.result
};
} catch (error) {
console.log(`Error fetching ${type} stream:` + error);
return {
type: type,
result: null
};
}
})
);
const decryptRequestData = streamResponses const streamResponses = await Promise.all(
.filter(item => item.result) streamUrls.map(async ({ type, url }) => {
.map(item => ({
name: item.type,
data: item.result
}));
if (decryptRequestData.length === 0) {
return {};
}
const decryptPromises = decryptRequestData.map(item =>
fetchv2(`https://enc-dec.app/api/dec-kai?text=${encodeURIComponent(item.data)}`)
.then(res => res.json())
.then(json => ({ name: item.name, data: JSON.stringify(json.result) }))
.catch(err => ({ name: item.name, error: err.toString() }))
);
const decryptResults = await Promise.all(decryptPromises);
const finalResults = {};
decryptResults.forEach(result => {
try { try {
const parsed = JSON.parse(result.data); const res = await fetchv2(useProxy ? proxyUrl(url) : url);
finalResults[result.name] = parsed.url; const json = await res.json();
console.log(`decrypted${result.name} URL:` + parsed.url); return { type, result: json.result };
} catch (error) { } catch (error) {
console.log(`Error parsing ${result.name} result:` + error); console.log(`Error fetching ${type} stream:` + error);
finalResults[result.name] = null; return { type, result: null };
} }
}); })
);
return finalResults; 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 decryptedUrls = await processStreams(streamUrls); const urlMap = Object.fromEntries(decryptResults.map(i => [i.name, i.url]));
const decryptedSub = decryptedUrls.Sub; const decryptedSub = urlMap.Sub;
const decryptedDub = decryptedUrls.Dub; const decryptedDub = urlMap.Dub;
const decryptedRaw = decryptedUrls.Softsub; const decryptedRaw = urlMap.Softsub;
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"
};
async function getStream(url) { async function getStream(url) {
try { try {
const response = await fetchv2(url.replace("/e/", "/media/"), headers); 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 responseJson = await response.json();
const result = responseJson?.result; const result = responseJson?.result;
const postData = {
"text": result,
"agent": headers["User-Agent"]
};
const finalResponse = await fetchv2( const finalResponse = await fetchv2(
"https://enc-dec.app/api/dec-mega", "https://enc-dec.app/api/dec-mega",
{ "Content-Type": "application/json" }, { "Content-Type": "application/json" },
"POST", "POST",
JSON.stringify(postData) JSON.stringify({ text: result, agent: headers["User-Agent"] })
); );
const finalJson = await finalResponse.json(); const finalJson = await finalResponse.json();
return finalJson?.result?.sources?.[0]?.file || null; return finalJson?.result?.sources?.[0]?.file || null;
} catch { } catch {
@@ -540,23 +689,67 @@ async function extractStreamUrl(url) {
} }
} }
const streams = [];
const [subStream, dubStream, rawStream] = await Promise.all([ const [subStream, dubStream, rawStream] = await Promise.all([
decryptedSub ? getStream(decryptedSub) : Promise.resolve(null), decryptedSub ? getStream(decryptedSub) : Promise.resolve(null),
decryptedDub ? getStream(decryptedDub) : Promise.resolve(null), decryptedDub ? getStream(decryptedDub) : Promise.resolve(null),
decryptedRaw ? getStream(decryptedRaw) : Promise.resolve(null) decryptedRaw ? getStream(decryptedRaw) : Promise.resolve(null)
]); ]);
if (subStream) streams.push({ title: "Hardsub English", streamUrl: subStream }); const streams = [];
if (dubStream) streams.push({ title: "Dubbed English", streamUrl: dubStream });
if (rawStream) streams.push({ title: "Original audio", streamUrl: rawStream });
const final = { async function addStreamQualities(m3u8Link, baseTitle) {
streams, let pushedQualities = 0;
subtitles: "" 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)); console.log("RETURN: " + JSON.stringify(final));
return JSON.stringify(final); return JSON.stringify(final);
@@ -564,7 +757,13 @@ async function extractStreamUrl(url) {
console.log("Animekai fetch error:" + error); console.log("Animekai fetch error:" + error);
return "https://error.org"; return "https://error.org";
} }
} else if (source === "1Movies") { } 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 { try {
const eidMatch = actualUrl.match(/eid=([^&]+)/); const eidMatch = actualUrl.match(/eid=([^&]+)/);
if (eidMatch && eidMatch[1]) { if (eidMatch && eidMatch[1]) {
@@ -575,26 +774,23 @@ async function extractStreamUrl(url) {
actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`); actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
} }
const response = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(actualUrl)); const response = await fetchv2(actualUrl);
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 tokenData = await fetchv2(`https://enc-dec.app/api/enc-movies-flix?text=${encodeURIComponent(serverId)}`)
const tokenPromises = [ .then(res => res.json());
fetchv2(`https://enc-dec.app/api/enc-movies-flix?text=${encodeURIComponent(serverId)}`)
];
const tokenResponses = await Promise.all(tokenPromises);
const tokenData = await tokenResponses[0].json();
const token = tokenData.result; const token = tokenData.result;
if (!token) { if (!token) {
@@ -603,7 +799,7 @@ async function extractStreamUrl(url) {
} }
const streamUrl = `https://1movies.bz/ajax/links/view?id=${serverId}&_=${token}`; const streamUrl = `https://1movies.bz/ajax/links/view?id=${serverId}&_=${token}`;
const streamResponse = await fetchv2("https://deno-proxies-sznvnpnxwhbv.deno.dev/?url=" + encodeURIComponent(streamUrl)); const streamResponse = await fetchv2(streamUrl);
const streamData = await streamResponse.json(); const streamData = await streamResponse.json();
if (!streamData.result) { if (!streamData.result) {
@@ -611,93 +807,126 @@ async function extractStreamUrl(url) {
return "error"; return "error";
} }
const decryptPromises = [ const decryptData = await fetchv2(
fetchv2("https://enc-dec.app/api/dec-movies-flix", { "Content-Type": "application/json" }, "POST", JSON.stringify({ text: streamData.result })) `https://enc-dec.app/api/dec-movies-flix?text=${streamData.result}`,
]; headers
).then(res => res.json());
const decryptResponses = await Promise.all(decryptPromises);
const decryptData = await decryptResponses[0].json();
console.log("Decrypted response:" + JSON.stringify(decryptData)); console.log("Decrypted response:" + JSON.stringify(decryptData));
const decryptedUrl = decryptData.result.url; const decryptedUrl = decryptData.result?.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;
if (!result) { if (!result) {
console.log("Media result not found"); console.log("Media result not found");
return "error"; return "error";
} }
const finalResponse = await fetchv2(`https://enc-dec.app/api/dec-rapid?text=${encodeURIComponent(result)}&agent=${encodeURIComponent("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://enc-dec.app/api/dec-rapid?text=${encodeURIComponent(result)}&agent=${encodeURIComponent(headers["User-Agent"])}`);
const finalJsonText = await finalResponse.text(); const finalJson = JSON.parse(await finalResponse.text());
const finalJson = JSON.parse(finalJsonText);
const m3u8Link = finalJson?.result?.sources?.[0]?.file; const m3u8Link = finalJson?.result?.sources?.[0]?.file;
const m3u8Response = await fetchv2(m3u8Link);
const m3u8Text = await m3u8Response.text();
const baseUrl = m3u8Link.substring(0, m3u8Link.lastIndexOf('/') + 1);
const streams = []; const streams = [];
const lines = m3u8Text.split('\n'); 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++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim(); const line = lines[i].trim();
if (line.startsWith('#EXT-X-STREAM-INF:')) { if (line.startsWith('#EXT-X-STREAM-INF:')) {
const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/); const resolutionMatch = line.match(/RESOLUTION=(\d+x\d+)/);
let quality = 'Unknown'; const nameMatch = line.match(/NAME="([^"]+)"/i) || line.match(/BANDWIDTH=(\d+)/i);
if (resolutionMatch) { let quality = 'Unknown';
const [width, height] = resolutionMatch[1].split('x'); if (resolutionMatch) {
quality = `${height}p`; 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 (i + 1 < lines.length) { if (pushedQualities === 0) {
const streamPath = lines[i + 1].trim(); streams.push({
const streamUrl = baseUrl + streamPath; title: "Source",
streamUrl: "https://1anime.app/api/m3u8-proxy?url=" + encodeURIComponent(m3u8Link)
streams.push({ });
title: quality,
streamUrl: streamUrl
});
}
} }
} }
const returnValue = { const returnValue = {
streams: streams, streams,
subtitles: englishSubUrl !== "N/A" ? englishSubUrl : "" subtitles: englishSubUrl !== "N/A" ? englishSubUrl : ""
}; };
console.log("RETURN: " + JSON.stringify(returnValue)); console.log("RETURN: " + JSON.stringify(returnValue));
return JSON.stringify(returnValue); return JSON.stringify(returnValue);
} catch (error) { } catch (error) {
console.log("1Movies fetch error:" + error); console.log("1Movies fetch error:" + error);
return "https://error.org"; return "https://error.org";
+5 -3
View File
@@ -1,11 +1,11 @@
{ {
"sourceName": "Ashi (あし) - Literally Everything", "sourceName": "Ashi (あし) - Literally Everything",
"iconUrl": "https://files.catbox.moe/y8v199.png", "iconUrl": "https://files.catbox.moe/ptq3a5.png",
"author": { "author": {
"name": "50/50", "name": "50/50",
"icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s" "icon": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQ3122kQwublLkZ6rf1fEpUP79BxZOFmH9BSA&s"
}, },
"version": "1.2.1", "version": "1.3.3",
"language": "English", "language": "English",
"streamType": "HLS", "streamType": "HLS",
"quality": "1080p", "quality": "1080p",
@@ -17,5 +17,7 @@
"softsub": true, "softsub": true,
"downloadSupport": true, "downloadSupport": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
@@ -17,5 +17,7 @@
"softsub": false, "softsub": false,
"downloadSupport": false, "downloadSupport": false,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
@@ -17,5 +17,7 @@
"softsub": true, "softsub": true,
"downloadSupport": false, "downloadSupport": false,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
@@ -18,5 +18,8 @@
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -19,5 +19,8 @@
"note": "Use external player!", "note": "Use external player!",
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,5 +17,7 @@
"softsub": true, "softsub": true,
"downloadSupport": false, "downloadSupport": false,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
@@ -17,5 +17,7 @@
"softsub": false, "softsub": false,
"downloadSupport": false, "downloadSupport": false,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
@@ -18,5 +18,8 @@
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,5 +17,7 @@
"softsub": false, "softsub": false,
"downloadSupport": false, "downloadSupport": false,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }
@@ -17,6 +17,11 @@
"softsub": false, "softsub": false,
"downloadSupport": false, "downloadSupport": false,
"supportsMojuru": true, "supportsMojuru": true,
"supportsDartotsu": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsAnymex": true,
"supportsTsumi": true,
"supportsHiyoku": true,
"supportsShirox": true
} }
@@ -17,5 +17,7 @@
"softsub": false, "softsub": false,
"downloadSupport": true, "downloadSupport": true,
"supportsSora": true, "supportsSora": true,
"supportsLuna": true "supportsLuna": true,
"supportsTsumi": true,
"supportsHiyoku": true
} }

Some files were not shown because too many files have changed in this diff Show More