From 8467a2abd0f61727df432cc1e16f67f2257502e7 Mon Sep 17 00:00:00 2001 From: MrGus Date: Sun, 21 Dec 2025 16:22:05 +0100 Subject: [PATCH] Update anime/hianime/source.js --- anime/hianime/source.js | 719 ++++++++++++++++------------------------ 1 file changed, 283 insertions(+), 436 deletions(-) diff --git a/anime/hianime/source.js b/anime/hianime/source.js index ac81aad..04d40db 100644 --- a/anime/hianime/source.js +++ b/anime/hianime/source.js @@ -3,11 +3,9 @@ class HiAnime { this.type = "anime-streaming"; this.version = "1.0"; this.baseUrl = "https://hianime.to"; - console.log("[HiAnime] Constructor initialized"); } getSettings() { - console.log("[HiAnime] getSettings called"); return { episodeServers: ["HD-1", "HD-2", "HD-3", "HD-4"], supportsDub: true @@ -57,467 +55,316 @@ class HiAnime { } search(query) { - console.log("[HiAnime] search called with:", JSON.stringify(query)); + let searchQuery = query; - try { - let searchQuery = query; - - if (typeof query === "string") { - try { - searchQuery = JSON.parse(query); - } catch (e) { - searchQuery = { query: query, dub: false }; - } + if (typeof query === "string") { + try { + searchQuery = JSON.parse(query); + } catch (e) { + searchQuery = { query: query, dub: false }; } - - console.log("[HiAnime] Parsed search query:", JSON.stringify(searchQuery)); - - const queryText = searchQuery.query || searchQuery.title || ""; - if (!queryText) { - console.error("[HiAnime] No query text provided"); - return []; - } - - const media = searchQuery.media || {}; - const startDate = media.startDate || {}; - const year = startDate.year || null; - const month = startDate.month || null; - - let url = `${this.baseUrl}/search?keyword=${encodeURIComponent( - queryText - )}&sort=default`; - if (year) url += `&sy=${year}`; - if (month) url += `&sm=${month}`; - - console.log("[HiAnime] Fetching URL:", url); - - const response = fetch(url); - console.log("[HiAnime] Fetch response status:", response.status); - - const html = response.text(); - console.log("[HiAnime] HTML length:", html.length); - - const regex = - /]*href="\/watch\/([^"]+)"[^>]*title="([^"]+)"[^>]*data-id="(\d+)"/gi; - const matches = [...html.matchAll(regex)]; - console.log("[HiAnime] Found", matches.length, "raw matches"); - - const dubFlag = !!searchQuery.dub; - const queryKey = this.normalizeKey(queryText); - - let results = matches.map((m, idx) => { - const watchSlug = m[1]; - const title = this.decodeHtml(m[2]); - const idNum = m[3]; - - console.log( - `[HiAnime] Match ${idx + 1}: id=${idNum}, title="${title}"` - ); - - const imgRe = new RegExp( - `]+href="\\/watch\\/${watchSlug - .replace(/[.*+?^${}()|[\]\\]/g, "\\$&") - }"[\\s\\S]*?]+(?:data-src|src)="([^"]+)"`, - "i" - ); - const imgM = html.match(imgRe); - const image = imgM ? imgM[1] : null; - - return { - id: `${idNum}/${dubFlag ? "dub" : "sub"}`, - title, - image, - url: `${this.baseUrl}/watch/${watchSlug}`, - subOrDub: dubFlag ? "dub" : "sub", - _score: this.scoreTitle(queryKey, title) - }; - }); - - const byId = {}; - for (const r of results) { - const key = r.id; - if (!byId[key] || r._score > byId[key]._score) byId[key] = r; - } - results = Object.values(byId); - - results.sort((a, b) => (b._score || 0) - (a._score || 0)); - - results = results.map((r) => { - const out = Object.assign({}, r); - delete out._score; - return out; - }); - - console.log( - "[HiAnime] Returning", - results.length, - "results (sorted best-first)" - ); - if (results[0]) - console.log("[HiAnime] Top result:", results[0].id, results[0].title); - - return results; - } catch (error) { - console.error("[HiAnime] search error:", error); - throw error; } + + const queryText = searchQuery.query || searchQuery.title || ""; + if (!queryText) return []; + + const media = searchQuery.media || {}; + const startDate = media.startDate || {}; + const year = startDate.year || null; + const month = startDate.month || null; + + let url = `${this.baseUrl}/search?keyword=${encodeURIComponent( + queryText + )}&sort=default`; + if (year) url += `&sy=${year}`; + if (month) url += `&sm=${month}`; + + const response = fetch(url); + const html = response.text(); + + const regex = + /]*href="\/watch\/([^"]+)"[^>]*title="([^"]+)"[^>]*data-id="(\d+)"/gi; + const matches = [...html.matchAll(regex)]; + + const dubFlag = !!searchQuery.dub; + const queryKey = this.normalizeKey(queryText); + + let results = matches.map((m) => { + const watchSlug = m[1]; + const title = this.decodeHtml(m[2]); + const idNum = m[3]; + + const imgRe = new RegExp( + `]+href="\\/watch\\/${watchSlug + .replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + }"[\\s\\S]*?]+(?:data-src|src)="([^"]+)"`, + "i" + ); + const imgM = html.match(imgRe); + const image = imgM ? imgM[1] : null; + + return { + id: `${idNum}/${dubFlag ? "dub" : "sub"}`, + title, + image, + url: `${this.baseUrl}/watch/${watchSlug}`, + subOrDub: dubFlag ? "dub" : "sub", + _score: this.scoreTitle(queryKey, title) + }; + }); + + const byId = {}; + for (const r of results) { + const key = r.id; + if (!byId[key] || r._score > byId[key]._score) byId[key] = r; + } + results = Object.values(byId); + + results.sort((a, b) => (b._score || 0) - (a._score || 0)); + + return results.map((r) => { + const out = Object.assign({}, r); + delete out._score; + return out; + }); } findEpisodes(animeId) { - console.log("[HiAnime] findEpisodes called with:", animeId); + let id, subOrDub; - try { - let id, subOrDub; - - if (typeof animeId === "string") { - if (animeId.includes("/")) { - [id, subOrDub] = animeId.split("/"); - } else { - try { - const parsed = JSON.parse(animeId); - id = parsed.id || parsed.animeId || animeId; - subOrDub = parsed.subOrDub || "sub"; - } catch (e) { - id = animeId; - subOrDub = "sub"; - } - } - } else if (typeof animeId === "object" && animeId) { - id = animeId.id || animeId.animeId; - subOrDub = animeId.subOrDub || "sub"; + if (typeof animeId === "string") { + if (animeId.includes("/")) { + [id, subOrDub] = animeId.split("/"); } else { - id = String(animeId); - subOrDub = "sub"; - } - - if (id && id.includes("/")) [id, subOrDub] = id.split("/"); - - console.log( - "[HiAnime] Parsed episode params: id=", - id, - "subOrDub=", - subOrDub - ); - - const url = `${this.baseUrl}/ajax/v2/episode/list/${id}`; - console.log("[HiAnime] Fetching episodes from:", url); - - const response = this.fetchAjax(url); - console.log("[HiAnime] Episodes fetch status:", response.status); - - const json = response.json(); - console.log("[HiAnime] Episodes JSON keys:", Object.keys(json).join(", ")); - - const html = this.safeString(json.html); - console.log("[HiAnime] Episodes HTML length:", html.length); - - const episodes = []; - - const regex = - /]*\bep-item\b[^>]*data-number="(\d+)"[^>]*data-id="(\d+)"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi; - - let match; - let matchCount = 0; - - while ((match = regex.exec(html)) !== null) { - matchCount++; - - const number = parseInt(match[1], 10); - const epId = match[2]; - const href = match[3]; - const inner = match[4] || ""; - - let title = ""; - const t1 = inner.match(/title="([^"]+)"/i); - if (t1) title = this.decodeHtml(t1[1]); - if (!title) { - const t2 = inner.match(/class="ep-name[^"]*"[^>]*>([^<]+)/i); - if (t2) title = this.decodeHtml(t2[1]); + try { + const parsed = JSON.parse(animeId); + id = parsed.id || parsed.animeId || animeId; + subOrDub = parsed.subOrDub || "sub"; + } catch (e) { + id = animeId; + subOrDub = "sub"; } - if (!title) title = `Episode ${number}`; - - const episode = { - id: `${epId}/${subOrDub}`, - number, - url: this.baseUrl + href, - title - }; - - console.log( - `[HiAnime] Episode ${matchCount}: num=${episode.number}, id=${episode.id}, title="${episode.title}"` - ); - episodes.push(episode); } - - console.log("[HiAnime] Total episodes found:", episodes.length); - - if (episodes.length === 0) { - console.warn( - "[HiAnime] No episodes parsed. HTML preview:", - html.slice(0, 300) - ); - } - - return episodes; - } catch (error) { - console.error("[HiAnime] findEpisodes error:", error); - throw error; + } else if (typeof animeId === "object" && animeId) { + id = animeId.id || animeId.animeId; + subOrDub = animeId.subOrDub || "sub"; + } else { + id = String(animeId); + subOrDub = "sub"; } + + if (id && id.includes("/")) [id, subOrDub] = id.split("/"); + + const url = `${this.baseUrl}/ajax/v2/episode/list/${id}`; + const response = this.fetchAjax(url); + const json = response.json(); + const html = this.safeString(json.html); + + const episodes = []; + const regex = + /]*\bep-item\b[^>]*data-number="(\d+)"[^>]*data-id="(\d+)"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi; + + let match; + while ((match = regex.exec(html)) !== null) { + const number = parseInt(match[1], 10); + const epId = match[2]; + const href = match[3]; + const inner = match[4] || ""; + + let title = ""; + const t1 = inner.match(/title="([^"]+)"/i); + if (t1) title = this.decodeHtml(t1[1]); + if (!title) { + const t2 = inner.match(/class="ep-name[^"]*"[^>]*>([^<]+)/i); + if (t2) title = this.decodeHtml(t2[1]); + } + if (!title) title = `Episode ${number}`; + + episodes.push({ + id: `${epId}/${subOrDub}`, + number, + url: this.baseUrl + href, + title + }); + } + + return episodes; } findEpisodeServer(episode, _server) { - console.log( - "[HiAnime] findEpisodeServer called with episode:", - JSON.stringify(episode), - "server:", - _server + let episodeId, subOrDub; + + if (typeof episode === "string") { + if (episode.includes("/")) { + [episodeId, subOrDub] = episode.split("/"); + } else { + try { + const parsed = JSON.parse(episode); + episodeId = parsed.id || parsed.episodeId || episode; + subOrDub = parsed.subOrDub || "sub"; + } catch (e) { + episodeId = episode; + subOrDub = "sub"; + } + } + } else if (typeof episode === "object" && episode) { + const epId = episode.id || episode.episodeId || ""; + if (epId.includes("/")) { + [episodeId, subOrDub] = epId.split("/"); + } else { + episodeId = epId; + subOrDub = episode.subOrDub || "sub"; + } + } else { + episodeId = String(episode); + subOrDub = "sub"; + } + + if (episodeId && episodeId.includes("/")) + [episodeId, subOrDub] = episodeId.split("/"); + + let serverName = _server && _server !== "default" ? _server : "HD-1"; + + const serversUrl = `${this.baseUrl}/ajax/v2/episode/servers?episodeId=${episodeId}`; + const serverResponse = this.fetchAjax(serversUrl); + const serverJson = serverResponse.json(); + const serverHtml = this.safeString(serverJson.html); + + const escapedName = serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + + const blockRe = new RegExp( + `]*\\bserver-item\\b[^>]*data-type="${subOrDub}"[^>]*data-id="(\\d+)"[\\s\\S]*?>[\\s\\S]*?${escapedName}[\\s\\S]*?<\\/div>`, + "i" ); - try { - let episodeId, subOrDub; + let match = blockRe.exec(serverHtml); - if (typeof episode === "string") { - if (episode.includes("/")) { - [episodeId, subOrDub] = episode.split("/"); - } else { - try { - const parsed = JSON.parse(episode); - episodeId = parsed.id || parsed.episodeId || episode; - subOrDub = parsed.subOrDub || "sub"; - } catch (e) { - episodeId = episode; - subOrDub = "sub"; - } - } - } else if (typeof episode === "object" && episode) { - const epId = episode.id || episode.episodeId || ""; - if (epId.includes("/")) { - [episodeId, subOrDub] = epId.split("/"); - } else { - episodeId = epId; - subOrDub = episode.subOrDub || "sub"; - } - } else { - episodeId = String(episode); - subOrDub = "sub"; - } - - if (episodeId && episodeId.includes("/")) - [episodeId, subOrDub] = episodeId.split("/"); - - console.log( - "[HiAnime] Parsed server params: episodeId=", - episodeId, - "subOrDub=", - subOrDub - ); - - let serverName = - _server && _server !== "default" ? _server : "HD-1"; - console.log("[HiAnime] Using server:", serverName); - - const serversUrl = `${this.baseUrl}/ajax/v2/episode/servers?episodeId=${episodeId}`; - console.log("[HiAnime] Fetching servers from:", serversUrl); - - const serverResponse = this.fetchAjax(serversUrl); - console.log("[HiAnime] Servers fetch status:", serverResponse.status); - - const serverJson = serverResponse.json(); - const serverHtml = this.safeString(serverJson.html); - console.log("[HiAnime] Server HTML length:", serverHtml.length); - - const escapedName = serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - - const blockRe = new RegExp( - `]*\\bserver-item\\b[^>]*data-type="${subOrDub}"[^>]*data-id="(\\d+)"[\\s\\S]*?>[\\s\\S]*?${escapedName}[\\s\\S]*?<\\/div>`, + if (!match) { + const altRe = new RegExp( + `data-type="${subOrDub}"[^>]*data-id="(\\d+)"[\\s\\S]*?>[\\s\\S]*?]*>[\\s\\S]*?${escapedName}[\\s\\S]*?<\\/a>`, "i" ); - - let match = blockRe.exec(serverHtml); - - if (!match) { - const altRe = new RegExp( - `data-type="${subOrDub}"[^>]*data-id="(\\d+)"[\\s\\S]*?>[\\s\\S]*?]*>[\\s\\S]*?${escapedName}[\\s\\S]*?<\\/a>`, - "i" - ); - match = altRe.exec(serverHtml); - } - - if (!match) { - console.error( - `[HiAnime] Server "${serverName}" (${subOrDub}) not found in HTML` - ); - console.warn( - "[HiAnime] Server HTML preview:", - serverHtml.slice(0, 300) - ); - throw new Error(`Server "${serverName}" (${subOrDub}) not found`); - } - - const serverId = match[1]; - console.log("[HiAnime] Found serverId:", serverId); - - const sourcesUrl = `${this.baseUrl}/ajax/v2/episode/sources?id=${serverId}`; - console.log("[HiAnime] Fetching sources from:", sourcesUrl); - - const sourcesResponse = this.fetchAjax(sourcesUrl); - console.log("[HiAnime] Sources fetch status:", sourcesResponse.status); - - const sourcesJson = sourcesResponse.json(); - console.log("[HiAnime] Sources JSON link:", sourcesJson.link); - - let decryptData = null; - let requiredHeaders = {}; - - try { - console.log("[HiAnime] Attempting primary decrypter..."); - decryptData = this.extractMegaCloud(sourcesJson.link, true); - if (decryptData && decryptData.headersProvided) { - requiredHeaders = decryptData.headersProvided; - } - console.log("[HiAnime] Primary decrypter succeeded"); - } catch (err) { - console.warn("[HiAnime] Primary decrypter failed:", err); - } - - if (!decryptData) { - console.log("[HiAnime] Trying fallback API..."); - const fallbackUrl = `https://ac-api.ofchaos.com/api/anime/embed/convert/v2?embedUrl=${encodeURIComponent( - sourcesJson.link - )}`; - const fallbackResponse = fetch(fallbackUrl); - console.log("[HiAnime] Fallback fetch status:", fallbackResponse.status); - - decryptData = fallbackResponse.json(); - - 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" - }; - console.log("[HiAnime] Fallback succeeded"); - } - - const sourcesArr = decryptData && decryptData.sources ? decryptData.sources : []; - const streamSource = - sourcesArr.find((s) => s && s.type === "hls") || - sourcesArr.find((s) => s && s.type === "mp4"); - - if (!streamSource || !streamSource.file) { - console.error("[HiAnime] No valid stream file found in sources"); - throw new Error("No valid stream file found"); - } - - console.log("[HiAnime] Stream URL:", streamSource.file); - - const tracksArr = decryptData && decryptData.tracks ? decryptData.tracks : []; - const subtitles = tracksArr - .filter((t) => t && t.kind === "captions" && t.file) - .map((track, index) => ({ - id: `sub-${index}`, - language: track.label || "Unknown", - url: track.file, - isDefault: !!track.default - })); - - console.log("[HiAnime] Found", subtitles.length, "subtitles"); - - const result = { - server: serverName, - headers: requiredHeaders, - videoSources: [ - { - url: streamSource.file, - type: streamSource.type === "hls" ? "m3u8" : "mp4", - quality: "auto", - subtitles - } - ] - }; - - console.log("[HiAnime] Returning server result"); - return result; - } catch (error) { - console.error("[HiAnime] findEpisodeServer error:", error); - throw error; + match = altRe.exec(serverHtml); } + + if (!match) { + throw new Error(`Server "${serverName}" (${subOrDub}) not found`); + } + + const serverId = match[1]; + + const sourcesUrl = `${this.baseUrl}/ajax/v2/episode/sources?id=${serverId}`; + const sourcesResponse = this.fetchAjax(sourcesUrl); + const sourcesJson = sourcesResponse.json(); + + let decryptData = null; + let requiredHeaders = {}; + + try { + decryptData = this.extractMegaCloud(sourcesJson.link, true); + if (decryptData && decryptData.headersProvided) { + requiredHeaders = decryptData.headersProvided; + } + } catch (err) {} + + if (!decryptData) { + const fallbackUrl = `https://ac-api.ofchaos.com/api/anime/embed/convert/v2?embedUrl=${encodeURIComponent( + sourcesJson.link + )}`; + const fallbackResponse = fetch(fallbackUrl); + decryptData = fallbackResponse.json(); + + 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 sourcesArr = decryptData && decryptData.sources ? decryptData.sources : []; + const hls = sourcesArr.find((s) => s && s.type === "hls" && s.file); + const mp4 = sourcesArr.find((s) => s && s.type === "mp4" && s.file); + + const streamSource = hls || mp4; + if (!streamSource || !streamSource.file) { + throw new Error("No valid stream file found"); + } + + const tracksArr = decryptData && decryptData.tracks ? decryptData.tracks : []; + const subtitles = tracksArr + .filter((t) => t && t.kind === "captions" && t.file) + .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 + } + ] + }; } extractMegaCloud(embedUrl, returnHeaders) { - console.log("[HiAnime] extractMegaCloud called with:", embedUrl); + const url = new URL(embedUrl); + const baseDomain = `${url.protocol}//${url.host}/`; - try { - const url = new URL(embedUrl); - const baseDomain = `${url.protocol}//${url.host}/`; + const headers = { + "Accept": "*/*", + "X-Requested-With": "XMLHttpRequest", + "Referer": baseDomain, + "Origin": `${url.protocol}//${url.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 headers = { - "Accept": "*/*", - "X-Requested-With": "XMLHttpRequest", - "Referer": baseDomain, - "Origin": `${url.protocol}//${url.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 response = fetch(embedUrl, { headers }); + const html = response.text(); - const response = fetch(embedUrl, { headers }); - console.log("[HiAnime] MegaCloud embed fetch status:", response.status); - - const html = response.text(); - console.log("[HiAnime] MegaCloud HTML length:", html.length); - - const fileIdMatch = html.match(/\s*File\s+#([a-zA-Z0-9]+)\s*-/i); - if (!fileIdMatch) { - console.error("[HiAnime] file_id not found in embed page"); - throw new Error("file_id not found in embed page"); - } - const fileId = fileIdMatch[1]; - console.log("[HiAnime] Found fileId:", fileId); - - let nonce = null; - const match48 = html.match(/\b[a-zA-Z0-9]{48}\b/); - if (match48) { - nonce = match48[0]; - console.log("[HiAnime] Found 48-char nonce"); - } 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]; - console.log("[HiAnime] Found 3x16-char nonce"); - } - } - - if (!nonce) { - console.error("[HiAnime] nonce not found"); - throw new Error("nonce not found"); - } - - const sourcesUrl = `${baseDomain}embed-2/v3/e-1/getSources?id=${fileId}&_k=${nonce}`; - console.log("[HiAnime] Fetching sources from:", sourcesUrl); - - const sourcesResponse = fetch(sourcesUrl, { headers }); - console.log("[HiAnime] Sources fetch status:", sourcesResponse.status); - - const sourcesJson = sourcesResponse.json(); - console.log( - "[HiAnime] Sources has", - (sourcesJson.sources || []).length, - "sources" - ); - - return { - sources: sourcesJson.sources || [], - tracks: sourcesJson.tracks || [], - intro: sourcesJson.intro || null, - outro: sourcesJson.outro || null, - server: sourcesJson.server || null, - headersProvided: returnHeaders ? headers : undefined - }; - } catch (error) { - console.error("[HiAnime] extractMegaCloud error:", error); - throw error; + const fileIdMatch = html.match(/<title>\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 sourcesUrl = `${baseDomain}embed-2/v3/e-1/getSources?id=${fileId}&_k=${nonce}`; + const sourcesResponse = fetch(sourcesUrl, { headers }); + const sourcesJson = sourcesResponse.json(); + + return { + sources: sourcesJson.sources || [], + tracks: sourcesJson.tracks || [], + intro: sourcesJson.intro || null, + outro: sourcesJson.outro || null, + server: sourcesJson.server || null, + headersProvided: returnHeaders ? headers : undefined + }; } }