diff --git a/animekai/animekai.js b/animekai/animekai.js index 64fee87..2f8ddd2 100644 --- a/animekai/animekai.js +++ b/animekai/animekai.js @@ -117,20 +117,27 @@ async function extractEpisodes(url) { async function extractStreamUrl(url) { const headers = { "Referer": "https://anikai.to/", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0", + "Accept": "text/html, */*; q=0.01", + "Accept-Language": "en-US,en;q=0.5", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "Pragma": "no-cache", + "Cache-Control": "no-cache" }; - let actualUrl = url; try { - const tokenMatch = actualUrl.match(/token=([^&]+)/); - if (tokenMatch && tokenMatch[1]) { - const rawToken = tokenMatch[1]; - const encryptResponse = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(rawToken)}`); - const encryptData = await encryptResponse.json(); - const encryptedToken = encryptData.result; - actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`); - } - + const tokenMatch = url.match(/token=([^&]+)/); + if (!tokenMatch?.[1]) throw new Error("No token found in URL"); + const rawToken = tokenMatch[1]; + + const encTokenRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(rawToken)}`); + const encTokenData = await encTokenRes.json(); + const encryptedToken = encTokenData.result; + + const actualUrl = url.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`); + const response = await fetchv2(actualUrl); const text = await response.text(); @@ -144,115 +151,96 @@ async function extractStreamUrl(url) { const cleanedAjaxResultHtml = cleanJsonHtml(ajaxResultHtml); const serverHtmlSource = cleanedAjaxResultHtml || cleanedHtml; - const subRegex = /
]*>([\s\S]*?)<\/div>/; - const softsubRegex = /
]*>([\s\S]*?)<\/div>/; - const dubRegex = /
]*>([\s\S]*?)<\/div>/; - const subMatch = subRegex.exec(serverHtmlSource); - const softsubMatch = softsubRegex.exec(serverHtmlSource); - const dubMatch = dubRegex.exec(serverHtmlSource); - - const subContent = subMatch ? subMatch[1].trim() : ""; - const softsubContent = softsubMatch ? softsubMatch[1].trim() : ""; - const dubContent = dubMatch ? dubMatch[1].trim() : ""; - - const extractServerId = (content) => { - if (!content) return null; - const preferred = /]*data-lid="([^"]+)"[^>]*>\s*Server\s*1\s*<\/span>/i.exec(content); - if (preferred?.[1]) return preferred[1]; - return /]*data-lid="([^"]+)"/i.exec(content)?.[1] || null; + const extractServerIds = (type) => { + const regex = new RegExp(`
]*>([\\s\\S]*?)<\\/div>`); + const content = regex.exec(serverHtmlSource)?.[1] ?? ""; + const spanRegex = /]*data-lid="([^"]+)"[^>]*>/g; + const ids = []; + let match; + while ((match = spanRegex.exec(content)) !== null) ids.push(match[1]); + console.log(`[extractStreamUrl] ${type} ids:`, ids); + return ids.length > 1 ? ids[1] : ids[0] ?? null; }; - const serverIdDub = extractServerId(dubContent); - const serverIdSoftsub = extractServerId(softsubContent); - const serverIdSub = extractServerId(subContent); + const dubType = url.includes("dub") ? "dub" : "sub"; + const types = dubType === "sub" ? ["sub", "softsub"] : ["dub"]; - const tokenRequestData = [ - { name: "Dub", data: serverIdDub }, - { name: "Softsub", data: serverIdSoftsub }, - { name: "Sub", data: serverIdSub } - ].filter(item => item.data); + const servers = types + .map(type => ({ type, lid: extractServerIds(type) })) + .filter(s => s.lid); - const tokenPromises = tokenRequestData.map(item => - fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(item.data)}`) - .then(res => res.json()) - .then(json => ({ name: item.name, data: json.result })) - .catch(err => ({ name: item.name, error: err.toString() })) - ); - const tokenResults = await Promise.all(tokenPromises); + const streams = []; + const subtitles = []; - const serverIdMap = { - "Dub": serverIdDub, - "Softsub": serverIdSoftsub, - "Sub": serverIdSub - }; - - const streamUrls = tokenResults.map(result => ({ - type: result.name, - url: `https://anikai.to/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data}` - })); - - const streamResponses = await Promise.all( - streamUrls.map(async ({ type, url }) => { - try { - const res = await fetchv2(url); - const json = await res.json(); - return { type, result: json.result }; - } catch { - return { type, result: null }; - } - }) - ); - - const decryptPromises = streamResponses - .filter(item => item.result) - .map(item => - fetchv2(`https://enc-dec.app/api/dec-kai?text=${item.result}`, headers) - .then(res => res.json()) - .then(json => ({ name: item.type, url: json.result?.url || null })) - .catch(() => ({ name: item.type, url: null })) - ); - const decryptResults = await Promise.all(decryptPromises); - - const urlMap = Object.fromEntries(decryptResults.map(i => [i.name, i.url])); - - const decryptedSub = urlMap.Sub; - const decryptedDub = urlMap.Dub; - const decryptedRaw = urlMap.Softsub; - - async function getStream(url) { + await Promise.all(servers.map(async ({ type, lid }) => { try { - const response = await fetchv2(url.replace("/e/", "/media/"), headers); - const responseJson = await response.json(); - const result = responseJson?.result; + const decLidRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(lid)}`); + const decLidData = await decLidRes.json(); + const decodedLid = decLidData.result; - const finalResponse = await fetchv2( + const viewRes = await fetchv2(`https://anikai.to/ajax/links/view?id=${lid}&_=${decodedLid}`); + const viewJson = await viewRes.json(); + const encodedResult = viewJson.result; + + const decRes = await fetchv2( + "https://enc-dec.app/api/dec-kai", + { "Content-Type": "application/json" }, + "POST", + JSON.stringify({ text: encodedResult }) + ); + const decJson = await decRes.json(); + let iframeUrl = decJson.result?.url ?? null; + + if (!iframeUrl) return; + + if (iframeUrl.includes("anikai.to/iframe")) { + const iframePageRes = await fetchv2(iframeUrl, headers); + const iframePageText = await iframePageRes.text(); + const iframeSrcMatch = iframePageText.match(/]+src="([^"]+)"/i); + if (iframeSrcMatch && iframeSrcMatch[1]) { + iframeUrl = iframeSrcMatch[1]; + console.log(`[extractStreamUrl] fallback iframeUrl for ${type}:`, iframeUrl); + } + } + + const mediaUrl = iframeUrl.replace("/e/", "/media/").replace("/e2/", "/media/"); + + const mediaRes = await fetchv2(mediaUrl, headers); + const mediaJson = await mediaRes.json(); + const encodedM3u8 = mediaJson?.result; + + if (!encodedM3u8) return; + + const finalRes = await fetchv2( "https://enc-dec.app/api/dec-mega", { "Content-Type": "application/json" }, "POST", - JSON.stringify({ text: result, agent: headers["User-Agent"] }) + JSON.stringify({ text: encodedM3u8, agent: headers["User-Agent"] }) ); + const finalJson = await finalRes.json(); - const finalJson = await finalResponse.json(); - return finalJson?.result?.sources?.[0]?.file || null; - } catch { - return null; + const sources = finalJson?.result?.sources ?? []; + const tracks = finalJson?.result?.tracks ?? []; + + const file = sources[0]?.file ?? null; + + if (file) { + const titleMap = { sub: "Hardsub English", softsub: "Original audio", dub: "Dubbed English" }; + streams.push({ title: titleMap[type] || type, streamUrl: "https://1anime.app/api/m3u8-proxy?url=" + file }); + } + + for (const track of tracks) { + if (track.file && track.label) { + subtitles.push({ url: track.file, language: track.label }); + } + } + + } catch (e) { + console.log(`[extractStreamUrl] error for ${type}:`, e.toString()); } - } + })); - const [subStream, dubStream, rawStream] = await Promise.all([ - decryptedSub ? getStream(decryptedSub) : Promise.resolve(null), - decryptedDub ? getStream(decryptedDub) : Promise.resolve(null), - decryptedRaw ? getStream(decryptedRaw) : Promise.resolve(null) - ]); - - console.log("[extractStreamUrl] Sub:", subStream, "Dub:", dubStream, "Softsub:", rawStream); - - const streams = []; - if (subStream) streams.push({ title: "Hardsub English", streamUrl: subStream }); - if (dubStream) streams.push({ title: "Dubbed English", streamUrl: dubStream }); - if (rawStream) streams.push({ title: "Original audio", streamUrl: rawStream }); - - return JSON.stringify({ streams, subtitles: "" }); + return JSON.stringify({ streams, subtitles }); } catch (error) { console.error("Animekai fetch error:" + error); @@ -260,6 +248,9 @@ async function extractStreamUrl(url) { } } + + + function cleanHtmlSymbols(string) { if (!string) { return "";