diff --git a/anime/hianime/source.js b/anime/hianime/source.js index 0eb025f..4db73b7 100644 --- a/anime/hianime/source.js +++ b/anime/hianime/source.js @@ -1,15 +1,15 @@ -console.log("[HiAnime] source.js loaded"); +console.log("[HiAnime-TV] source.js loaded"); class HiAnime { constructor() { - console.log("[HiAnime] constructor()"); - this.type = "anime-streaming"; + console.log("[HiAnime-TV] constructor()"); + this.type = "anime-board"; this.version = "1.0"; this.baseUrl = "https://hianime.to"; } getSettings() { - console.log("[HiAnime] getSettings()"); + console.log("[HiAnime-TV] getSettings()"); return { episodeServers: ["HD-1", "HD-2", "HD-3", "HD-4"], supportsDub: true @@ -17,373 +17,346 @@ class HiAnime { } safeString(str) { - return typeof str === "string" ? str : ""; + return (typeof str === "string" ? str : ""); } - decodeHtml(s) { + 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, ""); + } + + _decodeHtml(s) { return this.safeString(s) .replace(/&/g, "&") .replace(/"/g, '"') + .replace(/'/g, "'") .replace(/'/g, "'") .replace(/</g, "<") - .replace(/>/g, ">"); + .replace(/>/g, ">") + .replace(/&#(\d+);?/g, (m, d) => { + const n = parseInt(d, 10); + return Number.isFinite(n) ? String.fromCharCode(n) : m; + }); } - normalizeKey(s) { - return this.decodeHtml(this.safeString(s)) - .toLowerCase() - .replace(/[^a-z0-9]+/g, ""); + _normalize(str) { + return this.safeString(str).toLowerCase().replace(/[^a-z0-9]+/g, ""); } - scoreTitle(queryKey, title) { - const t = this.normalizeKey(title); - if (!t) return 0; - if (t === queryKey) return 1000; - if (t.includes(queryKey) || queryKey.includes(t)) return 700; - - const qTokens = queryKey.match(/[a-z0-9]+/g) || []; - const tTokens = t.match(/[a-z0-9]+/g) || []; - let inter = 0; - for (const tok of qTokens) { - if (tTokens.includes(tok)) inter++; - } - return inter * 50; + _escapeRe(s) { + return this.safeString(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } - fetchAjax(url, extraHeaders) { - console.log("[HiAnime] fetchAjax url=", url); + _fetch(url, opts) { + const o = opts || {}; const headers = Object.assign( - { "X-Requested-With": "XMLHttpRequest", "Referer": this.baseUrl + "/" }, - extraHeaders || {} + { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + "Accept": "text/html,application/json;q=0.9,*/*;q=0.8", + "Referer": this.baseUrl + "/" + }, + o.headers || {} ); - return fetch(url, { headers }); + const method = o.method || "GET"; + const body = (o.body === undefined || o.body === null) ? "" : String(o.body); + + console.log("[HiAnime-TV] fetch", method, url, "headers=", Object.keys(headers).length, "bodyLen=", body.length); + const r = fetch(url, { method, headers, body }); + console.log("[HiAnime-TV] fetchResp status=", r.status, "ok=", r.ok); + return r; } search(query) { - console.log("[HiAnime] search() called query=", query); + console.log("[HiAnime-TV] search() raw=", query); + try { - let searchQuery = query; + let q = query; - if (typeof query === "string") { - try { - searchQuery = JSON.parse(query); - } catch (e) { - searchQuery = { query: query, dub: false }; - } + if (typeof q === "string") { + try { q = JSON.parse(q); } + catch (e) { q = { query: query, dub: false, media: { startDate: {} } }; } } - const queryText = searchQuery.query || searchQuery.title || ""; - if (!queryText) { - console.log("[HiAnime] search() empty queryText"); - return []; - } + q = q || {}; + const queryText = this.safeString(q.query || q.title); + const dub = !!q.dub; - const media = searchQuery.media || {}; - const startDate = media.startDate || {}; - const year = startDate.year || null; - const month = startDate.month || null; + const start = (q.media && q.media.startDate) ? q.media.startDate : {}; + const year = start.year || ""; + const month = start.month || ""; + + console.log("[HiAnime-TV] search() parsed queryText=", queryText, "dub=", dub, "year=", year, "month=", month); + + if (!queryText) return []; + + const normalize = (str) => this._normalize(str); + const fetchMatches = (url) => { + console.log("[HiAnime-TV] search() fetchMatches url=", url); + const html = this._fetch(url).text(); + console.log("[HiAnime-TV] search() htmlLen=", (html || "").length); + + const regex = /]+title="([^"]+)"[^>]+data-id="(\d+)"/g; + const all = [...html.matchAll(regex)]; + + console.log("[HiAnime-TV] search() matchAll count=", all.length); + + return all.map(m => { + const id = m[3]; + const pageUrl = m[1]; + const title = this._decodeHtml(m[2]); + + const jnameRegex = new RegExp( + `

[\\s\\S]*?]+href="\\/${this._escapeRe(pageUrl)}[^"]*"[^>]+data-jname="([^"]+)"`, + "i" + ); + const jnameMatch = html.match(jnameRegex); + const jname = jnameMatch ? this._decodeHtml(jnameMatch[1]) : null; + + const imageRegex = new RegExp( + `]+data-src="([^"]+)"`, + "i" + ); + const imageMatch = html.match(imageRegex); + const image = imageMatch ? imageMatch[1] : null; + + const normTitleJP = normalize(this.normalizeSeasonParts(jname)); + const normTitle = normalize(this.normalizeSeasonParts(title)); + + return { id, pageUrl, title, image, normTitleJP, normTitle }; + }); + }; let url = `${this.baseUrl}/search?keyword=${encodeURIComponent(queryText)}&sort=default`; if (year) url += `&sy=${year}`; if (month) url += `&sm=${month}`; - console.log("[HiAnime] search() url=", url); + console.log("[HiAnime-TV] search() url=", url); - const response = fetch(url); - console.log("[HiAnime] search() status=", response.status, "ok=", response.ok); - - const html = response.text(); - console.log("[HiAnime] search() htmlLen=", (html || "").length); - - const regex = - /]*href="\/watch\/([^"]+)"[^>]*title="([^"]+)"[^>]*data-id="(\d+)"/gi; - const matches = [...html.matchAll(regex)]; - - console.log("[HiAnime] search() matches=", matches.length); - - 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; + const matches = fetchMatches(url); + if (!matches || matches.length === 0) { + console.log("[HiAnime-TV] search() 0 matches"); + return []; } - results = Object.values(byId); - results.sort((a, b) => (b._score || 0) - (a._score || 0)); + const out = matches.map(m => ({ + id: `${m.id}/${dub ? "dub" : "sub"}`, + title: m.title, + image: m.image, + url: `${this.baseUrl}/${m.pageUrl}`, + subOrDub: dub ? "dub" : "sub" + })); - const out = results.map((r) => { - const o = Object.assign({}, r); - delete o._score; - return o; - }); - - console.log("[HiAnime] search() returning=", out.length); + console.log("[HiAnime-TV] search() returning=", out.length, "first=", out[0] ? out[0].title : "none"); return out; } catch (e) { - console.error("[HiAnime] search() ERROR", String(e), e && e.stack ? e.stack : ""); + console.error("[HiAnime-TV] search() ERROR", String(e), e && e.stack ? e.stack : ""); throw e; } } findEpisodes(animeId) { - console.log("[HiAnime] findEpisodes() called animeId=", animeId); + console.log("[HiAnime-TV] findEpisodes() animeId=", animeId); try { - let id, subOrDub; + let raw = animeId; + if (typeof raw !== "string") raw = JSON.stringify(raw); - 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"; + let id = ""; + let subOrDub = "sub"; + + if (raw.includes("/")) { + const parts = raw.split("/"); + id = parts[0]; + subOrDub = parts[1] || "sub"; } else { - id = String(animeId); - subOrDub = "sub"; + try { + const parsed = JSON.parse(raw); + const v = parsed.id || parsed.animeId || raw; + if (String(v).includes("/")) { + const parts = String(v).split("/"); + id = parts[0]; + subOrDub = parts[1] || (parsed.subOrDub || "sub"); + } else { + id = String(v); + subOrDub = parsed.subOrDub || "sub"; + } + } catch (e) { + id = raw; + } } - if (id && id.includes("/")) [id, subOrDub] = id.split("/"); - - console.log("[HiAnime] findEpisodes() parsed id=", id, "subOrDub=", subOrDub); + console.log("[HiAnime-TV] findEpisodes() parsed id=", id, "subOrDub=", subOrDub); const url = `${this.baseUrl}/ajax/v2/episode/list/${id}`; - console.log("[HiAnime] findEpisodes() url=", url); - - const response = this.fetchAjax(url); - console.log("[HiAnime] findEpisodes() status=", response.status, "ok=", response.ok); - - const json = response.json(); + const res = this._fetch(url, { headers: { "X-Requested-With": "XMLHttpRequest" } }); + const json = res.json(); const html = this.safeString(json.html); - console.log("[HiAnime] findEpisodes() htmlLen=", html.length); + console.log("[HiAnime-TV] findEpisodes() status=", res.status, "ok=", res.ok, "htmlLen=", html.length); const episodes = []; - const regex = - /]*\bep-item\b[^>]*data-number="(\d+)"[^>]*data-id="(\d+)"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi; + const regex = /]*class="[^"]*\bep-item\b[^"]*"[^>]*data-number="(\d+)"[^>]*data-id="(\d+)"[^>]*href="([^"]+)"[\s\S]*?
]*title="([^"]+)"/g; 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 + id: `${match[2]}/${subOrDub}`, + number: parseInt(match[1], 10), + url: this.baseUrl + match[3], + title: this._decodeHtml(match[4]) }); } - console.log("[HiAnime] findEpisodes() returning episodes=", episodes.length); + console.log("[HiAnime-TV] findEpisodes() returning=", episodes.length, "firstEp=", episodes[0] ? episodes[0].number : "none"); return episodes; } catch (e) { - console.error("[HiAnime] findEpisodes() ERROR", String(e), e && e.stack ? e.stack : ""); + console.error("[HiAnime-TV] findEpisodes() ERROR", String(e), e && e.stack ? e.stack : ""); throw e; } } findEpisodeServer(episode, _server) { - console.log("[HiAnime] findEpisodeServer() episode=", episode, "server=", _server); + console.log("[HiAnime-TV] findEpisodeServer() episode=", episode, "server=", _server); try { - let episodeId, subOrDub; + let ep = episode; - 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 (typeof ep === "string") { + try { ep = JSON.parse(ep); } catch (e) {} } - if (episodeId && episodeId.includes("/")) [episodeId, subOrDub] = episodeId.split("/"); + let episodeId = ""; + let subOrDub = "sub"; - let serverName = _server && _server !== "default" ? _server : "HD-1"; + if (typeof ep === "string") { + const raw = ep; + if (raw.includes("/")) { + const parts = raw.split("/"); + episodeId = parts[0]; + subOrDub = parts[1] || "sub"; + } else { + episodeId = raw; + } + } else if (ep && typeof ep === "object") { + const rawId = ep.id || ep.episodeId || ""; + if (String(rawId).includes("/")) { + const parts = String(rawId).split("/"); + episodeId = parts[0]; + subOrDub = parts[1] || (ep.subOrDub || "sub"); + } else { + episodeId = String(rawId); + subOrDub = ep.subOrDub || "sub"; + } + } - console.log("[HiAnime] findEpisodeServer() parsed episodeId=", episodeId, "subOrDub=", subOrDub, "serverName=", serverName); + if (!episodeId) throw new Error("Missing episode id"); - const serversUrl = `${this.baseUrl}/ajax/v2/episode/servers?episodeId=${episodeId}`; - console.log("[HiAnime] findEpisodeServer() serversUrl=", serversUrl); + let serverName = (_server && _server !== "default") ? String(_server) : "HD-1"; + console.log("[HiAnime-TV] parsed episodeId=", episodeId, "subOrDub=", subOrDub, "serverName=", serverName); - const serverResponse = this.fetchAjax(serversUrl); - console.log("[HiAnime] servers status=", serverResponse.status, "ok=", serverResponse.ok); + if (serverName === "HD-1" || serverName === "HD-2" || serverName === "HD-3") { - const serverJson = serverResponse.json(); - const serverHtml = this.safeString(serverJson.html); + const serversUrl = `${this.baseUrl}/ajax/v2/episode/servers?episodeId=${episodeId}`; + console.log("[HiAnime-TV] serversUrl=", serversUrl); - console.log("[HiAnime] findEpisodeServer() serverHtmlLen=", serverHtml.length); + const serverJson = this._fetch(serversUrl, { headers: { "X-Requested-With": "XMLHttpRequest" } }).json(); + const serverHtml = this.safeString(serverJson.html); - const escapedName = serverName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + console.log("[HiAnime-TV] serverHtmlLen=", serverHtml.length); - const blockRe = new RegExp( - `]*\\bserver-item\\b[^>]*data-type="${subOrDub}"[^>]*data-id="(\\d+)"[\\s\\S]*?>[\\s\\S]*?${escapedName}[\\s\\S]*?<\\/div>`, - "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>`, + const regex = new RegExp( + `]*class="item server-item"[^>]*data-type="${this._escapeRe(subOrDub)}"[^>]*data-id="(\\d+)"[^>]*>\\s*]*>\\s*${this._escapeRe(serverName)}\\s*`, "i" ); - match = altRe.exec(serverHtml); - } - if (!match) { - console.error("[HiAnime] findEpisodeServer() server not found:", serverName, subOrDub); - throw new Error(`Server "${serverName}" (${subOrDub}) not found`); - } + const m = regex.exec(serverHtml); + if (!m) throw new Error(`Server "${serverName}" (${subOrDub}) not found`); - const serverId = match[1]; - console.log("[HiAnime] findEpisodeServer() serverId=", serverId); + const serverId = m[1]; + console.log("[HiAnime-TV] serverId=", serverId); - const sourcesUrl = `${this.baseUrl}/ajax/v2/episode/sources?id=${serverId}`; - console.log("[HiAnime] findEpisodeServer() sourcesUrl=", sourcesUrl); + const sourcesUrl = `${this.baseUrl}/ajax/v2/episode/sources?id=${serverId}`; + console.log("[HiAnime-TV] sourcesUrl=", sourcesUrl); - const sourcesResponse = this.fetchAjax(sourcesUrl); - console.log("[HiAnime] sources status=", sourcesResponse.status, "ok=", sourcesResponse.ok); + const sourcesJson = this._fetch(sourcesUrl, { headers: { "X-Requested-With": "XMLHttpRequest" } }).json(); + console.log("[HiAnime-TV] sourcesJson has link=", !!sourcesJson.link); - const sourcesJson = sourcesResponse.json(); + let decryptData = null; + let requiredHeaders = {}; - console.log("[HiAnime] findEpisodeServer() sourcesJson has link=", !!sourcesJson.link); - - let decryptData = null; - let requiredHeaders = {}; - - try { - decryptData = this.extractMegaCloud(sourcesJson.link, true); - if (decryptData && decryptData.headersProvided) { - requiredHeaders = decryptData.headersProvided; + try { + decryptData = this.extractMegaCloud(sourcesJson.link, true); + if (decryptData && decryptData.headersProvided) requiredHeaders = decryptData.headersProvided; + console.log("[HiAnime-TV] extractMegaCloud ok sources=", (decryptData && decryptData.sources ? decryptData.sources.length : 0)); + } catch (err) { + console.warn("[HiAnime-TV] Primary decrypter failed:", String(err)); } - console.log("[HiAnime] findEpisodeServer() megacloud decrypt ok sources=", (decryptData && decryptData.sources ? decryptData.sources.length : 0)); - } catch (err) { - console.warn("[HiAnime] extractMegaCloud failed, will fallback", String(err)); - } - if (!decryptData) { - const fallbackUrl = `https://ac-api.ofchaos.com/api/anime/embed/convert/v2?embedUrl=${encodeURIComponent(sourcesJson.link)}`; - console.log("[HiAnime] fallbackUrl=", fallbackUrl); + if (!decryptData) { + console.warn("[HiAnime-TV] Trying fallback decrypt..."); + const fallbackUrl = `https://ac-api.ofchaos.com/api/anime/embed/convert/v2?embedUrl=${encodeURIComponent(sourcesJson.link)}`; + console.log("[HiAnime-TV] fallbackUrl=", fallbackUrl); - const fallbackResponse = fetch(fallbackUrl); - console.log("[HiAnime] fallback status=", fallbackResponse.status, "ok=", fallbackResponse.ok); + const fallbackRes = this._fetch(fallbackUrl); + decryptData = fallbackRes.json(); - 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" + }; - 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-TV] fallback decrypt sources=", (decryptData && decryptData.sources ? decryptData.sources.length : 0)); + } - console.log("[HiAnime] fallback decrypt sources=", (decryptData && decryptData.sources ? decryptData.sources.length : 0)); - } + const sourcesArr = (decryptData && decryptData.sources) ? decryptData.sources : []; + const streamSource = + sourcesArr.find((s) => s && s.type === "hls" && s.file) || + sourcesArr.find((s) => s && s.type === "mp4" && s.file); - 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); + if (!streamSource || !streamSource.file) throw new Error("No valid stream file found"); - const streamSource = hls || mp4; - if (!streamSource || !streamSource.file) { - console.error("[HiAnime] No valid stream file in sourcesArr len=", sourcesArr.length); - throw new Error("No valid stream file found"); - } + const subtitles = (decryptData.tracks || []) + .filter((t) => t && t.kind === "captions" && t.file) + .map((track, index) => ({ + id: `sub-${index}`, + language: track.label || "Unknown", + url: track.file, + isDefault: !!track.default + })); - 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-TV] FINAL stream file=", streamSource.file, "type=", streamSource.type, "subs=", subtitles.length); - console.log("[HiAnime] findEpisodeServer() resolved file=", streamSource.file, "type=", streamSource.type); - - return { - server: serverName, - headers: requiredHeaders, - videoSources: [ - { + return { + server: serverName, + headers: requiredHeaders, + videoSources: [{ url: streamSource.file, type: streamSource.type === "hls" ? "m3u8" : "mp4", quality: "auto", subtitles - } - ] - }; + }] + }; + } + + if (serverName === "HD-4") { + console.warn("[HiAnime-TV] HD-4 not implemented"); + return null; + } + + console.warn("[HiAnime-TV] Unsupported server=", serverName); + return null; } catch (e) { - console.error("[HiAnime] findEpisodeServer() ERROR", String(e), e && e.stack ? e.stack : ""); + console.error("[HiAnime-TV] findEpisodeServer() ERROR", String(e), e && e.stack ? e.stack : ""); throw e; } } - extractMegaCloud(embedUrl, returnHeaders) { - console.log("[HiAnime] extractMegaCloud() embedUrl=", embedUrl, "returnHeaders=", returnHeaders); + extractMegaCloud(embedUrl, returnHeaders = false) { + console.log("[HiAnime-TV] extractMegaCloud() embedUrl=", embedUrl, "returnHeaders=", returnHeaders); + const url = new URL(embedUrl); const baseDomain = `${url.protocol}//${url.host}/`; @@ -392,45 +365,39 @@ class HiAnime { "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" + "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 }); - console.log("[HiAnime] embed status=", response.status, "ok=", response.ok); + const r = this._fetch(embedUrl, { headers }); + const html = r.text(); - const html = response.text(); - console.log("[HiAnime] extractMegaCloud() embed htmlLen=", (html || "").length); + console.log("[HiAnime-TV] extractMegaCloud() embed status=", r.status, "ok=", r.ok, "htmlLen=", (html || "").length); 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"); - } + 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 { + 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"); - } + if (!nonce) throw new Error("nonce not found"); const sourcesUrl = `${baseDomain}embed-2/v3/e-1/getSources?id=${fileId}&_k=${nonce}`; - console.log("[HiAnime] extractMegaCloud() sourcesUrl=", sourcesUrl); + console.log("[HiAnime-TV] extractMegaCloud() sourcesUrl=", sourcesUrl); - const sourcesResponse = fetch(sourcesUrl, { headers }); - console.log("[HiAnime] sources status=", sourcesResponse.status, "ok=", sourcesResponse.ok); + const sr = this._fetch(sourcesUrl, { headers }); + const sourcesJson = sr.json(); - const sourcesJson = sourcesResponse.json(); - console.log("[HiAnime] extractMegaCloud() sources=", (sourcesJson.sources ? sourcesJson.sources.length : 0), "tracks=", (sourcesJson.tracks ? sourcesJson.tracks.length : 0)); + console.log("[HiAnime-TV] extractMegaCloud() sources status=", sr.status, "ok=", sr.ok, + "sources=", (sourcesJson.sources ? sourcesJson.sources.length : 0), + "tracks=", (sourcesJson.tracks ? sourcesJson.tracks.length : 0) + ); return { sources: sourcesJson.sources || [], @@ -444,4 +411,4 @@ class HiAnime { } module.exports = HiAnime; -console.log("[HiAnime] module.exports set to HiAnime class"); +console.log("[HiAnime-TV] module.exports set");