]*title="([^"]+)"/g;
+
+ let match;
+ while ((match = regex.exec(html)) !== null) {
+ episodes.push({
+ id: `${match[2]}/${subOrDub}`,
+ number: parseInt(match[1], 10),
+ url: this.baseUrl + match[3],
+ title: match[4],
+ });
}
+
+ return episodes;
+ }
+
+ findEpisodeServer(episode, _server) {
+ if (typeof episode === "string") {
+ try {
+ episode = JSON.parse(episode);
+ } catch (e) {}
+ }
+
+ const parts = String((episode && episode.id) || "").split("/");
+ const id = parts[0];
+ const subOrDub = parts[1] || "sub";
+ const serverName = _server !== "default" ? _server : "HD-1";
+
+ if (_server === "HD-4") return null;
+
+ const serverJson = this._getJson(
+ `${this.baseUrl}/ajax/v2/episode/servers?episodeId=${id}`,
+ { "X-Requested-With": "XMLHttpRequest" }
+ );
+
+ const serverHtml = String(serverJson.html || "");
+ const regex = new RegExp(
+ `
]*class="item server-item"[^>]*data-type="${subOrDub}"[^>]*data-id="(\\d+)"[^>]*>\\s*
]*>\\s*${serverName}\\s*`,
+ "i"
+ );
+
+ const match = regex.exec(serverHtml);
+ if (!match) throw new Error(`Server "${serverName}" (${subOrDub}) not found`);
+
+ const serverId = match[1];
+
+ const sourcesJson = this._getJson(
+ `${this.baseUrl}/ajax/v2/episode/sources?id=${serverId}`,
+ { "X-Requested-With": "XMLHttpRequest" }
+ );
+
+ let decryptData = null;
+ let requiredHeaders = {};
+
+ try {
+ decryptData = this.extractMegaCloudSync(sourcesJson.link);
+ requiredHeaders = decryptData.headersProvided || {};
+ } catch (e) {}
+
+ if (!decryptData) {
+ decryptData = this._getJson(
+ `https://ac-api.ofchaos.com/api/anime/embed/convert/v2?embedUrl=${encodeURIComponent(
+ sourcesJson.link
+ )}`,
+ {}
+ );
+ requiredHeaders = {
+ Referer: "https://megacloud.club/",
+ Origin: "https://megacloud.club",
+ "User-Agent":
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
+ "X-Requested-With": "XMLHttpRequest",
+ };
+ }
+
+ const sources = decryptData.sources || [];
+ const streamSource =
+ sources.find((s) => s.type === "hls") || sources.find((s) => s.type === "mp4");
+
+ if (!streamSource || !streamSource.file) throw new Error("No valid stream file found");
+
+ const subtitles = (decryptData.tracks || [])
+ .filter((t) => t.kind === "captions")
+ .map((track, index) => ({
+ id: `sub-${index}`,
+ language: track.label || "Unknown",
+ url: track.file,
+ isDefault: !!track.default,
+ }));
+
+ return {
+ server: serverName,
+ headers: requiredHeaders,
+ videoSources: [
+ {
+ url: streamSource.file,
+ type: streamSource.type === "hls" ? "m3u8" : "mp4",
+ quality: "auto",
+ subtitles,
+ },
+ ],
+ };
+ }
+
+ safeString(str) {
+ return typeof str === "string" ? str : "";
+ }
+
+ normalizeSeasonParts(title) {
+ const s = this.safeString(title);
+ return s
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "")
+ .replace(/\d+(st|nd|rd|th)/g, (m) => m.replace(/st|nd|rd|th/, ""))
+ .replace(/season|cour|part/g, "");
+ }
+
+ extractMegaCloudSync(embedUrl) {
+ const s = String(embedUrl);
+ const mm = s.match(/^(https?):\/\/([^\/]+)(\/.*)?$/i);
+ if (!mm) throw new Error("Invalid embedUrl: " + s);
+
+ const protocol = mm[1].toLowerCase();
+ const host = mm[2];
+ const baseDomain = `${protocol}://${host}/`;
+
+ const headers = {
+ Accept: "*/*",
+ "X-Requested-With": "XMLHttpRequest",
+ Referer: baseDomain,
+ Origin: `${protocol}://${host}`,
+ "User-Agent":
+ "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 = 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];
+ else {
+ const match3x16 = [...html.matchAll(/["']([A-Za-z0-9]{16})["']/g)];
+ if (match3x16.length >= 3) {
+ nonce = match3x16[0][1] + match3x16[1][1] + match3x16[2][1];
+ }
+ }
+ if (!nonce) throw new Error("nonce not found");
+
+ const sourcesJson = this._getJson(
+ `${baseDomain}embed-2/v3/e-1/getSources?id=${fileId}&_k=${nonce}`,
+ headers
+ );
+
+ return {
+ sources: sourcesJson.sources || [],
+ tracks: sourcesJson.tracks || [],
+ intro: sourcesJson.intro || null,
+ outro: sourcesJson.outro || null,
+ server: sourcesJson.server || null,
+ headersProvided: headers,
+ };
+ }
}
-module.exports = HiAnime;
\ No newline at end of file
+module.exports = HiAnime;