Prefer reachable Megaplay CDN sources

This commit is contained in:
Dymas
2026-09-10 09:28:14 +02:00
parent 6e68f762b7
commit 9c4c6437f4
5 changed files with 32 additions and 20 deletions
+5
View File
@@ -1,5 +1,10 @@
# Changelog
## 0.52.32 - 2026-09-10
- Preferred reachable alternate Megaplay CDN source variants for Anikoto streams, fixing real episode downloads where the default decoded CDN playlist returned `HTTP 403`.
- Passed the Megaplay referer through Anikoto subtitle metadata so subtitles can download from the selected provider source.
## 0.52.31 - 2026-09-10
- Fixed Anikoto Megaplay source resolution by decoding encrypted `enc` source responses, restoring playable dub links that were visible on the website but returned as empty sources in Kaizoku.
+1 -1
View File
@@ -113,7 +113,7 @@ Kaizoku uses additive SQLite migrations for queue and watchlist schema changes.
The Anikoto, AniNeko, and AnimePahe parser modules in `providers/extensions/Anime/` are adapted from [TheYogMehta/extensions](https://github.com/TheYogMehta/extensions) and retain their GPL/license headers. The bundled anime provider versions are currently:
- Anikoto `5.0.4`
- Anikoto `5.0.5`
- AniNeko `3.0.3`
- AnimePahe `4.0.1`
+1 -1
View File
@@ -1 +1 @@
0.52.31
0.52.32
+21 -15
View File
@@ -28,6 +28,7 @@ const cheerio = require("cheerio");
const baseUrl = "https://anikototv.to";
const megaPlayEncKey = "i?LMTAx0Q6,:}50U";
const megaPlayEncIv = "W0;27ToaUpl_P%'c";
const megaPlaySourceVariants = ["bcdn", "tcdn", ""];
function decodeMegaPlayEnc(value) {
if (!value || typeof value !== "string") return null;
@@ -428,21 +429,25 @@ async function processServer(server) {
const domainName = new URL(iframeUrl).origin;
const playerReferer = domainName + "/";
const sourcesRes = await global.axios.get(
`${domainName}/stream/getSources?id=${playerDbId}${type ? `&type=${encodeURIComponent(type)}` : ""}${cidu ? `&cidu=${encodeURIComponent(cidu)}` : ""}`,
{
headers: {
"X-Requested-With": "XMLHttpRequest",
Referer: playerReferer,
let sourcesRes = null;
let decodedSourcesData = null;
for (const variant of megaPlaySourceVariants) {
sourcesRes = await global.axios.get(
`${domainName}/stream/getSources?id=${playerDbId}${type ? `&type=${encodeURIComponent(type)}` : ""}${cidu ? `&cidu=${encodeURIComponent(cidu)}` : ""}${variant ? `&s=${encodeURIComponent(variant)}` : ""}`,
{
headers: {
"X-Requested-With": "XMLHttpRequest",
Referer: playerReferer,
},
timeout: 7000,
},
timeout: 7000,
},
);
const decodedSourcesData =
sourcesRes.data?.sources || sourcesRes.data?.file
? sourcesRes.data
: decodeMegaPlayEnc(sourcesRes.data?.enc);
);
decodedSourcesData =
sourcesRes.data?.sources || sourcesRes.data?.file
? sourcesRes.data
: decodeMegaPlayEnc(sourcesRes.data?.enc);
if (decodedSourcesData?.sources || decodedSourcesData?.file) break;
}
if (decodedSourcesData && (decodedSourcesData.sources || decodedSourcesData.file)) {
const rawSrc = decodedSourcesData.sources || decodedSourcesData.file;
@@ -482,6 +487,7 @@ async function processServer(server) {
return {
url: sUrl,
lang: t.label || t.language || "English",
referer: playerReferer,
};
});
@@ -622,7 +628,7 @@ async function fetchEpisodeSources(episodeIdStr, category = null) {
module.exports = {
name: "anikoto",
version: "5.0.4",
version: "5.0.5",
SearchAnime,
AnimeInfo,
fetchEpisodeSources,
+4 -3
View File
@@ -4952,7 +4952,7 @@ provider.fetchEpisode("123").then((data) => {
def test_anikoto_process_server_decodes_encrypted_megaplay_sources(self):
script = r"""
const encryptedPayload = "wdeBruh3qqn_i5wUNnyaPQQl1wp7r0SrL6KQPNFd24_nH3bjQ-zZlyDs9ryZRqOqluMFxGXpzqi4xA9i5N3T4SDfaQAFzkb6BCj2QorrmxfAd1_nBhcma5SSJZ9rSUWBrBzsNlbhi3a9aCjlekwZGRoHmqEcH3gn2pl0RNQ2JsM";
const encryptedPayload = "wdeBruh3qqn_i5wUNnyaPbW9ZonPa4hISsp_E11QSCvgMDwqFDKp4YU05edNoqP7oBt0VrdAY8ZIJSs6S-axabGaqAYhyjxDjPGgJ58M9y9y4vWP8mNi0PiPKVQ2bHBRhkqlVFSz5WHTMs6CrfU3KVwQZ1ZH2ymo75JCecqjQJo";
global.setDynamicReferer = () => {};
global.setFallbackReferer = () => {};
global.axios = {
@@ -4973,7 +4973,7 @@ global.axios = {
`
};
}
if (url.includes("/stream/getSources?id=179510&type=dub&cidu=6aa1ade0620e3")) {
if (url.includes("/stream/getSources?id=179510&type=dub&cidu=6aa1ade0620e3&s=bcdn")) {
return { data: { enc: encryptedPayload, tracks: [{ file: "https://cdn.example/sub.vtt", label: "English" }] } };
}
throw new Error(`unexpected url ${url}`);
@@ -4999,10 +4999,11 @@ provider.processServer({ name: "HD-2", type: "dub", linkId: "dub-link" }).then((
source = json.loads(proc.stdout)
self.assertEqual(
source["url"],
"https://cdn.imgnex.top/anime/a7e93c78e547abf7dce1e0f3f0b77977/0eb80d76b8867b42b6e6e62ebe515704/master.m3u8",
"https://ncdn.imgnex.top/anime/a7e93c78e547abf7dce1e0f3f0b77977/0eb80d76b8867b42b6e6e62ebe515704/master.m3u8",
)
self.assertEqual(source["type"], "dub")
self.assertEqual(source["subtitles"][0]["url"], "https://cdn.example/sub.vtt")
self.assertEqual(source["subtitles"][0]["referer"], "https://megaplay.buzz/")
def test_provider_downloader_tries_next_same_provider_source_after_failure(self):
calls = []