Prefer reachable Megaplay CDN sources
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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,8 +429,11 @@ 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)}` : ""}`,
|
||||
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",
|
||||
@@ -438,11 +442,12 @@ async function processServer(server) {
|
||||
timeout: 7000,
|
||||
},
|
||||
);
|
||||
|
||||
const decodedSourcesData =
|
||||
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
@@ -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 = []
|
||||
|
||||
Reference in New Issue
Block a user