]*title="([^"]+)"/g;
-
+ const re = /data-number="([^"]+)"[^>]*data-id="([^"]+)"/gi;
let m;
- while ((m = regex.exec(html)) !== null) {
+ while ((m = re.exec(html)) !== null) {
+ const numRaw = String(m[1] || "").trim();
+ const epId = String(m[2] || "").trim();
+ const num = parseFloat(numRaw);
+ if (!epId || !isFinite(num)) continue;
episodes.push({
- id: `${m[2]}/${subOrDub}`,
- number: parseInt(m[1], 10),
- url: this.baseUrl + m[3],
- title: this._decodeHtml(m[4] || "")
+ id: epId,
+ number: num,
+ title: "",
+ url: ""
});
}
- return episodes;
- }
-
- async findEpisodeServer(episode, _server) {
- if (typeof episode === "string") {
- try { episode = JSON.parse(episode); } catch (e) {}
+ if (episodes.length === 0) {
+ const re2 = /data-episode-id="([^"]+)"[^>]*data-num="([^"]+)"/gi;
+ while ((m = re2.exec(html)) !== null) {
+ const epId = String(m[1] || "").trim();
+ const numRaw = String(m[2] || "").trim();
+ const num = parseFloat(numRaw);
+ if (!epId || !isFinite(num)) continue;
+ episodes.push({ id: epId, number: num, title: "", url: "" });
+ }
}
- const parts = String((episode && episode.id) || "").split("/");
- const id = parts[0];
- const subOrDub = parts[1] || "sub";
+ episodes.sort((a, b) => (a.number || 0) - (b.number || 0));
+ return JSON.stringify(episodes);
+ }
- const serverName = _server && _server !== "default" ? String(_server) : "HD-1";
- if (serverName === "HD-4") return null;
+ findEpisodeServer(episodeObj, serverName) {
+ let ep = episodeObj;
+ if (typeof ep === "string") {
+ try { ep = JSON.parse(ep); } catch (e) { ep = {}; }
+ }
- const serverJson = await this._getJson(
- `${this.baseUrl}/ajax/v2/episode/servers?episodeId=${encodeURIComponent(id)}`,
- { "X-Requested-With": "XMLHttpRequest" }
- );
- const serverHtml = String((serverJson && serverJson.html) || "");
+ const epId = this._safeStr(ep && ep.id ? ep.id : "").trim();
+ if (!epId) throw new Error("Missing episode id");
- const regex = new RegExp(
- `
]*class="item server-item"[^>]*data-type="${subOrDub}"[^>]*data-id="(\\\\d+)"[^>]*>\\\\s*
]*>\\\\s*${serverName}\\\\s*`,
- "i"
- );
+ const server = String(serverName || "").trim() || "HD-1";
- const mm = regex.exec(serverHtml);
- if (!mm) throw new Error(`Server "${serverName}" (${subOrDub}) not found`);
- const serverId = mm[1];
+ const j = this._getJson(`${this.baseUrl}/ajax/v2/episode/servers?episodeId=${encodeURIComponent(epId)}`, this._headers());
+ const html = this._decodeHtml(String(j.html || j.result || ""));
- const sourcesJson = await this._getJson(
+ let serverId = "";
+
+ const re = new RegExp(`data-id="([^"]+)"[^>]*>\\s*${server.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*<`, "i");
+ const mm = re.exec(html);
+ if (mm) serverId = String(mm[1] || "").trim();
+
+ if (!serverId) {
+ const re2 = /data-id="([^"]+)"/i;
+ const mm2 = re2.exec(html);
+ if (mm2) serverId = String(mm2[1] || "").trim();
+ }
+
+ if (!serverId) throw new Error("No server id found");
+
+ const sourcesJson = this._getJson(
`${this.baseUrl}/ajax/v2/episode/sources?id=${encodeURIComponent(serverId)}`,
{ "X-Requested-With": "XMLHttpRequest" }
);
@@ -353,14 +301,14 @@ class HiAnime {
let requiredHeaders = {};
try {
- decryptData = await this.extractMegaCloud(embed);
+ decryptData = this.extractMegaCloudSync(embed);
requiredHeaders = (decryptData && decryptData.headersProvided) ? decryptData.headersProvided : {};
} catch (e) {
decryptData = null;
}
if (!decryptData) {
- decryptData = await this._getJson(
+ decryptData = this._getJson(
`https://ac-api.ofchaos.com/api/anime/embed/convert/v2?embedUrl=${encodeURIComponent(embed)}`,
{}
);
@@ -373,7 +321,9 @@ class HiAnime {
};
}
- const sources = (decryptData && decryptData.sources) ? decryptData.sources : [];
+ if (!decryptData || !decryptData.sources) throw new Error("No video sources from any decrypter");
+
+ const sources = decryptData.sources || [];
const streamSource =
sources.find((s) => s && s.type === "hls" && s.file) ||
sources.find((s) => s && s.type === "mp4" && s.file) ||
@@ -381,7 +331,7 @@ class HiAnime {
if (!streamSource || !streamSource.file) throw new Error("No valid stream file found");
- const tracks = (decryptData && decryptData.tracks) ? decryptData.tracks : [];
+ const tracks = decryptData.tracks || [];
const subtitles = (tracks || [])
.filter((t) => t && String(t.kind || "").toLowerCase() === "captions" && t.file)
.map((track, index) => ({
@@ -391,24 +341,29 @@ class HiAnime {
isDefault: !!track.default
}));
- return {
- server: serverName,
+ const outType = (String(streamSource.type || "").toLowerCase() === "hls") ? "m3u8" : "mp4";
+
+ return JSON.stringify({
+ server: server,
headers: requiredHeaders || {},
videoSources: [
{
url: String(streamSource.file),
- type: String(streamSource.type || "").toLowerCase() === "hls" ? "m3u8" : "mp4",
+ type: outType,
quality: "auto",
- subtitles
+ subtitles: subtitles
}
]
- };
+ });
}
- async extractMegaCloud(embedUrl) {
- const u = new URL(String(embedUrl));
- const protocol = String(u.protocol || "https:").replace(":", "");
- const host = String(u.host || "");
+ extractMegaCloudSync(embedUrl) {
+ const s = String(embedUrl || "");
+ const mm = s.match(/^(https?):\/\/([^\/]+)(\/.*)?$/i);
+ if (!mm) throw new Error("Invalid embedUrl");
+
+ const protocol = mm[1].toLowerCase();
+ const host = mm[2];
const baseDomain = `${protocol}://${host}/`;
const headers = {
@@ -420,25 +375,25 @@ class HiAnime {
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
};
- const html = await this._getText(embedUrl, headers);
+ const html = this._getText(embedUrl, headers);
const fileIdMatch = html.match(/
\s*File\s+#([a-zA-Z0-9]+)\s*-/i);
if (!fileIdMatch) throw new Error("file_id not found in embed page");
const fileId = fileIdMatch[1];
let nonce = null;
-
const match48 = html.match(/\b[a-zA-Z0-9]{48}\b/);
if (match48) nonce = match48[0];
-
- if (!nonce) {
- const match3x16 = [...html.matchAll(/["']([A-Za-z0-9]{16})["']/g)];
- if (match3x16.length >= 3) nonce = match3x16[0][1] + match3x16[1][1] + match3x16[2][1];
+ else {
+ const match3x16 = [];
+ const re = /["']([A-Za-z0-9]{16})["']/g;
+ let m;
+ while ((m = re.exec(html)) !== null) match3x16.push(m[1]);
+ if (match3x16.length >= 3) nonce = match3x16[0] + match3x16[1] + match3x16[2];
}
-
if (!nonce) throw new Error("nonce not found");
- const sourcesJson = await this._getJson(
+ const sourcesJson = this._getJson(
`${baseDomain}embed-2/v3/e-1/getSources?id=${encodeURIComponent(fileId)}&_k=${encodeURIComponent(nonce)}`,
headers
);