Prefer reachable Megaplay CDN sources
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
# Changelog
|
# 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
|
## 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.
|
- 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:
|
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`
|
- AniNeko `3.0.3`
|
||||||
- AnimePahe `4.0.1`
|
- AnimePahe `4.0.1`
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ const cheerio = require("cheerio");
|
|||||||
const baseUrl = "https://anikototv.to";
|
const baseUrl = "https://anikototv.to";
|
||||||
const megaPlayEncKey = "i?LMTAx0Q6,:}50U";
|
const megaPlayEncKey = "i?LMTAx0Q6,:}50U";
|
||||||
const megaPlayEncIv = "W0;27ToaUpl_P%'c";
|
const megaPlayEncIv = "W0;27ToaUpl_P%'c";
|
||||||
|
const megaPlaySourceVariants = ["bcdn", "tcdn", ""];
|
||||||
|
|
||||||
function decodeMegaPlayEnc(value) {
|
function decodeMegaPlayEnc(value) {
|
||||||
if (!value || typeof value !== "string") return null;
|
if (!value || typeof value !== "string") return null;
|
||||||
@@ -428,8 +429,11 @@ async function processServer(server) {
|
|||||||
|
|
||||||
const domainName = new URL(iframeUrl).origin;
|
const domainName = new URL(iframeUrl).origin;
|
||||||
const playerReferer = domainName + "/";
|
const playerReferer = domainName + "/";
|
||||||
const sourcesRes = await global.axios.get(
|
let sourcesRes = null;
|
||||||
`${domainName}/stream/getSources?id=${playerDbId}${type ? `&type=${encodeURIComponent(type)}` : ""}${cidu ? `&cidu=${encodeURIComponent(cidu)}` : ""}`,
|
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: {
|
headers: {
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
@@ -438,11 +442,12 @@ async function processServer(server) {
|
|||||||
timeout: 7000,
|
timeout: 7000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
decodedSourcesData =
|
||||||
const decodedSourcesData =
|
|
||||||
sourcesRes.data?.sources || sourcesRes.data?.file
|
sourcesRes.data?.sources || sourcesRes.data?.file
|
||||||
? sourcesRes.data
|
? sourcesRes.data
|
||||||
: decodeMegaPlayEnc(sourcesRes.data?.enc);
|
: decodeMegaPlayEnc(sourcesRes.data?.enc);
|
||||||
|
if (decodedSourcesData?.sources || decodedSourcesData?.file) break;
|
||||||
|
}
|
||||||
|
|
||||||
if (decodedSourcesData && (decodedSourcesData.sources || decodedSourcesData.file)) {
|
if (decodedSourcesData && (decodedSourcesData.sources || decodedSourcesData.file)) {
|
||||||
const rawSrc = decodedSourcesData.sources || decodedSourcesData.file;
|
const rawSrc = decodedSourcesData.sources || decodedSourcesData.file;
|
||||||
@@ -482,6 +487,7 @@ async function processServer(server) {
|
|||||||
return {
|
return {
|
||||||
url: sUrl,
|
url: sUrl,
|
||||||
lang: t.label || t.language || "English",
|
lang: t.label || t.language || "English",
|
||||||
|
referer: playerReferer,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -622,7 +628,7 @@ async function fetchEpisodeSources(episodeIdStr, category = null) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: "anikoto",
|
name: "anikoto",
|
||||||
version: "5.0.4",
|
version: "5.0.5",
|
||||||
SearchAnime,
|
SearchAnime,
|
||||||
AnimeInfo,
|
AnimeInfo,
|
||||||
fetchEpisodeSources,
|
fetchEpisodeSources,
|
||||||
|
|||||||
+4
-3
@@ -4952,7 +4952,7 @@ provider.fetchEpisode("123").then((data) => {
|
|||||||
|
|
||||||
def test_anikoto_process_server_decodes_encrypted_megaplay_sources(self):
|
def test_anikoto_process_server_decodes_encrypted_megaplay_sources(self):
|
||||||
script = r"""
|
script = r"""
|
||||||
const encryptedPayload = "wdeBruh3qqn_i5wUNnyaPQQl1wp7r0SrL6KQPNFd24_nH3bjQ-zZlyDs9ryZRqOqluMFxGXpzqi4xA9i5N3T4SDfaQAFzkb6BCj2QorrmxfAd1_nBhcma5SSJZ9rSUWBrBzsNlbhi3a9aCjlekwZGRoHmqEcH3gn2pl0RNQ2JsM";
|
const encryptedPayload = "wdeBruh3qqn_i5wUNnyaPbW9ZonPa4hISsp_E11QSCvgMDwqFDKp4YU05edNoqP7oBt0VrdAY8ZIJSs6S-axabGaqAYhyjxDjPGgJ58M9y9y4vWP8mNi0PiPKVQ2bHBRhkqlVFSz5WHTMs6CrfU3KVwQZ1ZH2ymo75JCecqjQJo";
|
||||||
global.setDynamicReferer = () => {};
|
global.setDynamicReferer = () => {};
|
||||||
global.setFallbackReferer = () => {};
|
global.setFallbackReferer = () => {};
|
||||||
global.axios = {
|
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" }] } };
|
return { data: { enc: encryptedPayload, tracks: [{ file: "https://cdn.example/sub.vtt", label: "English" }] } };
|
||||||
}
|
}
|
||||||
throw new Error(`unexpected url ${url}`);
|
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)
|
source = json.loads(proc.stdout)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
source["url"],
|
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["type"], "dub")
|
||||||
self.assertEqual(source["subtitles"][0]["url"], "https://cdn.example/sub.vtt")
|
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):
|
def test_provider_downloader_tries_next_same_provider_source_after_failure(self):
|
||||||
calls = []
|
calls = []
|
||||||
|
|||||||
Reference in New Issue
Block a user