diff --git a/anime/hianime/source.js b/anime/hianime/source.js index ab8e3eb..1588d67 100644 --- a/anime/hianime/source.js +++ b/anime/hianime/source.js @@ -1,11 +1,13 @@ class HiAnime { constructor() { this.type = "anime-streaming"; - this.version = "1.0" + 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 @@ -13,19 +15,53 @@ class HiAnime { } async search(query) { - const normalize = (str) => this.safeString(str).toLowerCase().replace(/[^a-z0-9]+/g, ""); + console.log("[HiAnime] search called with:", JSON.stringify(query)); + + try { + const normalize = (str) => this.safeString(str).toLowerCase().replace(/[^a-z0-9]+/g, ""); - const start = query.media.startDate; - const fetchMatches = async (url) => { - const html = await fetch(url).then(res => res.text()); + let searchQuery = query; + 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 || new Date().getFullYear(); + const month = startDate.month || 1; + + const url = `${this.baseUrl}/search?keyword=${encodeURIComponent(queryText)}&sy=${year}&sm=${month}&sort=default`; + 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 = /]+title="([^"]+)"[^>]+data-id="(\d+)"/g; + const matches = [...html.matchAll(regex)]; + console.log("[HiAnime] Found", matches.length, "raw matches"); - return [...html.matchAll(regex)].map(m => { + const results = matches.map((m, idx) => { const id = m[3]; const pageUrl = m[1]; const title = m[2]; + console.log(`[HiAnime] Match ${idx + 1}: id=${id}, title="${title}"`); + const jnameRegex = new RegExp( `

[\\s\\S]*?]+href="\\/${pageUrl}[^"]*"[^>]+data-jname="([^"]+)"`, "i" @@ -41,98 +77,203 @@ class HiAnime { const image = imageMatch ? imageMatch[1] : null; return { - id, - pageUrl, - title, - image, - normTitleJP: normalize(this.normalizeSeasonParts(jname)), - normTitle: normalize(this.normalizeSeasonParts(title)), + id: `${id}/${searchQuery.dub ? "dub" : "sub"}`, + title: title, + image: image, + url: `${this.baseUrl}/${pageUrl}`, + subOrDub: searchQuery.dub ? "dub" : "sub" }; }); - }; - let url = `${this.baseUrl}/search?keyword=${encodeURIComponent(query.query)}&sy=${start.year}&sm=${start.month}&sort=default`; - let matches = await fetchMatches(url); + console.log("[HiAnime] Returning", results.length, "results"); + return results; - if (matches.length === 0) return []; - - return matches.map(m => ({ - id: `${m.id}/${query.dub ? "dub" : "sub"}`, - title: m.title, - image: m.image, - url: `${this.baseUrl}/${m.pageUrl}`, - subOrDub: query.dub ? "dub" : "sub" - })); + } catch (error) { + console.error("[HiAnime] search error:", error); + throw error; + } } async findEpisodes(animeId) { - const [id, subOrDub] = animeId.split("/"); - const res = await fetch(`${this.baseUrl}/ajax/v2/episode/list/${id}`, { - headers: { "X-Requested-With": "XMLHttpRequest" } - }); - const json = await res.json(); - const html = json.html; - console.log(html) + console.log("[HiAnime] findEpisodes called with:", animeId); + + 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") { + id = animeId.id || animeId.animeId; + subOrDub = animeId.subOrDub || "sub"; + } else { + id = String(animeId); + subOrDub = "sub"; + } + if (id && id.includes("/")) { + [id, subOrDub] = id.split("/"); + } - const episodes = []; - const regex = /]*class="[^"]*\bep-item\b[^"]*"[^>]*data-number="(\d+)"[^>]*data-id="(\d+)"[^>]*href="([^"]+)"[\s\S]*?
]*title="([^"]+)"/g; + console.log("[HiAnime] Parsed episode params: id=", id, "subOrDub=", subOrDub); - 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], + const url = `${this.baseUrl}/ajax/v2/episode/list/${id}`; + console.log("[HiAnime] Fetching episodes from:", url); + + const response = fetch(url, { + headers: { "X-Requested-With": "XMLHttpRequest" } }); - } - return episodes; + console.log("[HiAnime] Episodes fetch status:", response.status); + + const json = response.json(); + console.log("[HiAnime] Episodes JSON keys:", Object.keys(json).join(", ")); + + const html = json.html || ""; + console.log("[HiAnime] Episodes HTML length:", html.length); + + const episodes = []; + const regex = /]*class="[^"]*\bep-item\b[^"]*"[^>]*data-number="(\d+)"[^>]*data-id="(\d+)"[^>]*href="([^"]+)"[\s\S]*?
]*title="([^"]+)"/g; + + let match; + let matchCount = 0; + while ((match = regex.exec(html)) !== null) { + matchCount++; + const episode = { + id: `${match[2]}/${subOrDub}`, + number: parseInt(match[1], 10), + url: this.baseUrl + match[3], + title: match[4], + }; + 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); + return episodes; + + } catch (error) { + console.error("[HiAnime] findEpisodes error:", error); + throw error; + } } async findEpisodeServer(episode, _server) { - const [id, subOrDub] = episode.id.split("/"); - let serverName = _server !== "default" ? _server : "HD-1"; + console.log("[HiAnime] findEpisodeServer called with episode:", JSON.stringify(episode), "server:", _server); + + try { + 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") { + 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 (_server === "HD-1" || _server === "HD-2" || _server === "HD-3") { - const serverJson = await fetch(`${this.baseUrl}/ajax/v2/episode/servers?episodeId=${id}`, { + 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); + + if (_server === "HD-4") { + console.log("[HiAnime] HD-4 not supported, returning null"); + return null; + } + + const serversUrl = `${this.baseUrl}/ajax/v2/episode/servers?episodeId=${episodeId}`; + console.log("[HiAnime] Fetching servers from:", serversUrl); + + const serverResponse = fetch(serversUrl, { headers: { "X-Requested-With": "XMLHttpRequest" } - }).then(res => res.json()); + }); + + console.log("[HiAnime] Servers fetch status:", serverResponse.status); + + const serverJson = serverResponse.json(); + const serverHtml = serverJson.html || ""; + console.log("[HiAnime] Server HTML length:", serverHtml.length); - const serverHtml = 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`); + if (!match) { + console.error(`[HiAnime] Server "${serverName}" (${subOrDub}) not found in HTML`); + throw new Error(`Server "${serverName}" (${subOrDub}) not found`); + } const serverId = match[1]; + console.log("[HiAnime] Found serverId:", serverId); - const sourcesJson = await fetch(`${this.baseUrl}/ajax/v2/episode/sources?id=${serverId}`, { + const sourcesUrl = `${this.baseUrl}/ajax/v2/episode/sources?id=${serverId}`; + console.log("[HiAnime] Fetching sources from:", sourcesUrl); + + const sourcesResponse = fetch(sourcesUrl, { headers: { "X-Requested-With": "XMLHttpRequest" } - }).then(res => res.json()); + }); + + 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 { - decryptData = await this.extractMegaCloud(sourcesJson.link, true); + 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("Primary decrypter failed:", err); + console.warn("[HiAnime] Primary decrypter failed:", err); } if (!decryptData) { - console.warn("Primary decrypter failed — trying ShadeOfChaos fallback..."); - const fallbackRes = await fetch( - `https://ac-api.ofchaos.com/api/anime/embed/convert/v2?embedUrl=${encodeURIComponent(sourcesJson.link)}` - ); - decryptData = await fallbackRes.json(); + 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/", @@ -140,13 +281,19 @@ class HiAnime { "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 streamSource = - decryptData.sources.find((s) => s.type === "hls") || - decryptData.sources.find((s) => s.type === "mp4"); + (decryptData.sources || []).find((s) => s.type === "hls") || + (decryptData.sources || []).find((s) => s.type === "mp4"); - if (!streamSource?.file) throw new Error("No valid stream file found"); + 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 subtitles = (decryptData.tracks || []) .filter((t) => t.kind === "captions") @@ -157,19 +304,25 @@ class HiAnime { isDefault: !!track.default, })); - return { + 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 + subtitles: subtitles }] }; - } - else if (_server === "HD-4") { - return null; + + console.log("[HiAnime] Returning server result"); + return result; + + } catch (error) { + console.error("[HiAnime] findEpisodeServer error:", error); + throw error; } } @@ -185,47 +338,75 @@ class HiAnime { .replace(/season|cour|part/g, ""); } - async extractMegaCloud(embedUrl, returnHeaders = false) { - const url = new URL(embedUrl); - const baseDomain = `${url.protocol}//${url.host}/`; + extractMegaCloud(embedUrl, returnHeaders) { + console.log("[HiAnime] extractMegaCloud called with:", embedUrl); + + 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 html = await fetch(embedUrl, { headers }).then((r) => r.text()); - 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]; + const response = fetch(embedUrl, { headers: headers }); + console.log("[HiAnime] MegaCloud embed fetch status:", response.status); + + const html = response.text(); + console.log("[HiAnime] MegaCloud HTML length:", html.length); - 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]; + const fileIdMatch = html.match(/<title>\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: 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; } - if (!nonce) throw new Error("nonce not found"); - - const sourcesJson = await fetch( - `${baseDomain}embed-2/v3/e-1/getSources?id=${fileId}&_k=${nonce}`, - { headers } - ).then((r) => r.json()); - - return { - sources: sourcesJson.sources, - tracks: sourcesJson.tracks || [], - intro: sourcesJson.intro || null, - outro: sourcesJson.outro || null, - server: sourcesJson.server || null, - headersProvided: returnHeaders ? headers : undefined - }; } }