This commit is contained in:
+89
-98
@@ -117,19 +117,26 @@ async function extractEpisodes(url) {
|
|||||||
async function extractStreamUrl(url) {
|
async function extractStreamUrl(url) {
|
||||||
const headers = {
|
const headers = {
|
||||||
"Referer": "https://anikai.to/",
|
"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 {
|
try {
|
||||||
const tokenMatch = actualUrl.match(/token=([^&]+)/);
|
const tokenMatch = url.match(/token=([^&]+)/);
|
||||||
if (tokenMatch && tokenMatch[1]) {
|
if (!tokenMatch?.[1]) throw new Error("No token found in URL");
|
||||||
const rawToken = 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 encTokenRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(rawToken)}`);
|
||||||
const encryptedToken = encryptData.result;
|
const encTokenData = await encTokenRes.json();
|
||||||
actualUrl = actualUrl.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
|
const encryptedToken = encTokenData.result;
|
||||||
}
|
|
||||||
|
const actualUrl = url.replace('&_=ENCRYPT_ME', `&_=${encryptedToken}`);
|
||||||
|
|
||||||
const response = await fetchv2(actualUrl);
|
const response = await fetchv2(actualUrl);
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
@@ -144,115 +151,96 @@ async function extractStreamUrl(url) {
|
|||||||
const cleanedAjaxResultHtml = cleanJsonHtml(ajaxResultHtml);
|
const cleanedAjaxResultHtml = cleanJsonHtml(ajaxResultHtml);
|
||||||
const serverHtmlSource = cleanedAjaxResultHtml || cleanedHtml;
|
const serverHtmlSource = cleanedAjaxResultHtml || cleanedHtml;
|
||||||
|
|
||||||
const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/;
|
const extractServerIds = (type) => {
|
||||||
const softsubRegex = /<div class="server-items lang-group" data-id="softsub"[^>]*>([\s\S]*?)<\/div>/;
|
const regex = new RegExp(`<div class="server-items lang-group" data-id="${type}"[^>]*>([\\s\\S]*?)<\\/div>`);
|
||||||
const dubRegex = /<div class="server-items lang-group" data-id="dub"[^>]*>([\s\S]*?)<\/div>/;
|
const content = regex.exec(serverHtmlSource)?.[1] ?? "";
|
||||||
const subMatch = subRegex.exec(serverHtmlSource);
|
const spanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>/g;
|
||||||
const softsubMatch = softsubRegex.exec(serverHtmlSource);
|
const ids = [];
|
||||||
const dubMatch = dubRegex.exec(serverHtmlSource);
|
let match;
|
||||||
|
while ((match = spanRegex.exec(content)) !== null) ids.push(match[1]);
|
||||||
const subContent = subMatch ? subMatch[1].trim() : "";
|
console.log(`[extractStreamUrl] ${type} ids:`, ids);
|
||||||
const softsubContent = softsubMatch ? softsubMatch[1].trim() : "";
|
return ids.length > 1 ? ids[1] : ids[0] ?? null;
|
||||||
const dubContent = dubMatch ? dubMatch[1].trim() : "";
|
|
||||||
|
|
||||||
const extractServerId = (content) => {
|
|
||||||
if (!content) return null;
|
|
||||||
const preferred = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>\s*Server\s*1\s*<\/span>/i.exec(content);
|
|
||||||
if (preferred?.[1]) return preferred[1];
|
|
||||||
return /<span class="server"[^>]*data-lid="([^"]+)"/i.exec(content)?.[1] || null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const serverIdDub = extractServerId(dubContent);
|
const dubType = url.includes("dub") ? "dub" : "sub";
|
||||||
const serverIdSoftsub = extractServerId(softsubContent);
|
const types = dubType === "sub" ? ["sub", "softsub"] : ["dub"];
|
||||||
const serverIdSub = extractServerId(subContent);
|
|
||||||
|
|
||||||
const tokenRequestData = [
|
const servers = types
|
||||||
{ name: "Dub", data: serverIdDub },
|
.map(type => ({ type, lid: extractServerIds(type) }))
|
||||||
{ name: "Softsub", data: serverIdSoftsub },
|
.filter(s => s.lid);
|
||||||
{ name: "Sub", data: serverIdSub }
|
|
||||||
].filter(item => item.data);
|
|
||||||
|
|
||||||
const tokenPromises = tokenRequestData.map(item =>
|
const streams = [];
|
||||||
fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(item.data)}`)
|
const subtitles = [];
|
||||||
.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 serverIdMap = {
|
await Promise.all(servers.map(async ({ type, lid }) => {
|
||||||
"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 {
|
try {
|
||||||
const res = await fetchv2(url);
|
const decLidRes = await fetchv2(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(lid)}`);
|
||||||
const json = await res.json();
|
const decLidData = await decLidRes.json();
|
||||||
return { type, result: json.result };
|
const decodedLid = decLidData.result;
|
||||||
} catch {
|
|
||||||
return { type, result: null };
|
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(/<iframe[^>]+src="([^"]+)"/i);
|
||||||
|
if (iframeSrcMatch && iframeSrcMatch[1]) {
|
||||||
|
iframeUrl = iframeSrcMatch[1];
|
||||||
|
console.log(`[extractStreamUrl] fallback iframeUrl for ${type}:`, iframeUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const decryptPromises = streamResponses
|
const mediaUrl = iframeUrl.replace("/e/", "/media/").replace("/e2/", "/media/");
|
||||||
.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 mediaRes = await fetchv2(mediaUrl, headers);
|
||||||
|
const mediaJson = await mediaRes.json();
|
||||||
|
const encodedM3u8 = mediaJson?.result;
|
||||||
|
|
||||||
const decryptedSub = urlMap.Sub;
|
if (!encodedM3u8) return;
|
||||||
const decryptedDub = urlMap.Dub;
|
|
||||||
const decryptedRaw = urlMap.Softsub;
|
|
||||||
|
|
||||||
async function getStream(url) {
|
const finalRes = await fetchv2(
|
||||||
try {
|
|
||||||
const response = await fetchv2(url.replace("/e/", "/media/"), headers);
|
|
||||||
const responseJson = await response.json();
|
|
||||||
const result = responseJson?.result;
|
|
||||||
|
|
||||||
const finalResponse = await fetchv2(
|
|
||||||
"https://enc-dec.app/api/dec-mega",
|
"https://enc-dec.app/api/dec-mega",
|
||||||
{ "Content-Type": "application/json" },
|
{ "Content-Type": "application/json" },
|
||||||
"POST",
|
"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();
|
const sources = finalJson?.result?.sources ?? [];
|
||||||
return finalJson?.result?.sources?.[0]?.file || null;
|
const tracks = finalJson?.result?.tracks ?? [];
|
||||||
} catch {
|
|
||||||
return null;
|
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [subStream, dubStream, rawStream] = await Promise.all([
|
} catch (e) {
|
||||||
decryptedSub ? getStream(decryptedSub) : Promise.resolve(null),
|
console.log(`[extractStreamUrl] error for ${type}:`, e.toString());
|
||||||
decryptedDub ? getStream(decryptedDub) : Promise.resolve(null),
|
}
|
||||||
decryptedRaw ? getStream(decryptedRaw) : Promise.resolve(null)
|
}));
|
||||||
]);
|
|
||||||
|
|
||||||
console.log("[extractStreamUrl] Sub:", subStream, "Dub:", dubStream, "Softsub:", rawStream);
|
return JSON.stringify({ streams, subtitles });
|
||||||
|
|
||||||
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: "" });
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Animekai fetch error:" + error);
|
console.error("Animekai fetch error:" + error);
|
||||||
@@ -260,6 +248,9 @@ async function extractStreamUrl(url) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function cleanHtmlSymbols(string) {
|
function cleanHtmlSymbols(string) {
|
||||||
if (!string) {
|
if (!string) {
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
Reference in New Issue
Block a user