]*>([\s\S]*?)<\/div>/;
const dubRegex = /
]*>([\s\S]*?)<\/div>/;
const subMatch = subRegex.exec(cleanedHtml);
const softsubMatch = softsubRegex.exec(cleanedHtml);
const dubMatch = dubRegex.exec(cleanedHtml);
const sub = subMatch ? subMatch[1].trim() : "";
const softsub = softsubMatch ? softsubMatch[1].trim() : "";
const dub = dubMatch ? dubMatch[1].trim() : "";
const serverSpanRegex = server === "Server 1" ?
/]*data-lid="([^"]+)"[^>]*>Server 1<\/span>/ :
/]*data-lid="([^"]+)"[^>]*>Server 2<\/span>/;
const isDub = category === 'dub' || dubRequested === 'true';
const serverIdDub = serverSpanRegex.exec(dub)?.[1];
const serverIdSoftsub = serverSpanRegex.exec(softsub)?.[1];
const serverIdSub = serverSpanRegex.exec(sub)?.[1];
const tokenRequestData = [
{ name: "Dub", data: serverIdDub },
{ name: "Softsub", data: serverIdSoftsub },
{ name: "Sub", data: serverIdSub }
].filter(item => item.data !== undefined);
const tokenResults = await Promise.all(
tokenRequestData.map(async (item) => {
const response = await fetch(`https://enc-dec.app/api/enc-kai?text=${encodeURIComponent(item.data)}`);
return { name: item.name, data: await response.json() };
})
);
const serverIdMap = Object.fromEntries(tokenRequestData.map(item => [item.name, item.data]));
const streamUrls = tokenResults.map((result) => {
return {
type: result.name,
url: `${this.baseUrl}/ajax/links/view?id=${serverIdMap[result.name]}&_=${result.data.result}`
};
});
const decryptedUrls = await processStreams(streamUrls);
const headers = {
"Referer": "https://animekai.to/",
"User-Agent": this.userAgent
};
let streamUrl = "";
if (isDub && decryptedUrls.Dub) {
streamUrl = decryptedUrls.Dub;
} else {
streamUrl = decryptedUrls.Sub || decryptedUrls.Softsub;
}
if (!streamUrl) {
throw new Error("Unable to find a valid source");
}
const streams = await fetch(streamUrl.replace("/e/", "/media/"), {
headers: headers
});
const responseJson = await streams.json();
const result = responseJson?.result;
const postData = {
"text": result,
"agent": this.userAgent
};
const finalJson = await fetch("https://enc-dec.app/api/dec-mega", {
method: "POST",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(postData)
}).then(res => res.json());
if (!finalJson || finalJson.status !== 200) throw new Error("Failed to decrypt the final stream URL");
if (!finalJson.result.sources || finalJson.result.sources.length === 0) throw new Error("No video sources found");
const m3u8Link = finalJson.result.sources[0].file;
const playlistResponse = await fetch(m3u8Link);
const playlistText = await playlistResponse.text();
const regex = /#EXT-X-STREAM-INF:BANDWIDTH=\d+,RESOLUTION=(\d+x\d+)\s*(.*)/g;
const videoSources = [];
let resolutionMatch;
while ((resolutionMatch = regex.exec(playlistText)) !== null) {
let url = "";
if (resolutionMatch[2].includes("list")) {
url = `${m3u8Link.split(',')[0]}/${resolutionMatch[2]}`;
} else {
url = `${m3u8Link.split('/list')[0]}/${resolutionMatch[2]}`;
}
videoSources.push({
url: url,
type: "m3u8",
quality: resolutionMatch[1].split('x')[1] + 'p',
subtitles: [],
subOrDub: isDub ? "dub" : "sub"
});
}
if (videoSources.length === 0) {
videoSources.push({
url: m3u8Link,
type: "m3u8",
quality: "auto",
subtitles: [],
subOrDub: isDub ? "dub" : "sub"
});
}
return {
server: server || "default",
headers: {
"Referer": this.baseUrl,
"User-Agent": this.userAgent
},
videoSources: videoSources,
};
} catch (e) {
console.error(e);
throw new Error(e.message || "Error finding server");
}
}
normalizeQuery(query) {
return query
.replace(/\b(\d+)(st|nd|rd|th)\b/g, "$1")
.replace(/\s+/g, " ")
.replace(/(\d+)\s*Season/i, "$1")
.replace(/Season\s*(\d+)/i, "$1")
.trim();
}
async _makeRequest(url) {
const response = await fetch(url, {
method: "GET",
headers: {
"DNT": "1",
"User-Agent": this.userAgent,
"Cookie": "__ddg1_=;__ddg2_=;",
},
});
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.statusText}`);
}
return response;
}
async GETText(url) {
const res = await this._makeRequest(url);
return await res.text();
}
async GETJson(url) {
const res = await this._makeRequest(url);
return await res.json();
}
isSubOrDubOrBoth(elem) {
const sub = elem.find("span.sub").text();
const dub = elem.find("span.dub").text();
if (sub !== "" && dub !== "") return "both";
if (sub !== "") return "sub";
return "dub";
}
cleanJsonHtml(jsonHtml) {
if (!jsonHtml) return "";
return jsonHtml
.replace(/\\"/g, "\"")
.replace(/\\'/g, "'")
.replace(/\\\\/g, "\\")
.replace(/\\n/g, "\n")
.replace(/\\t/g, "\t")
.replace(/\\r/g, "\r");
}
}
async function processStreams(streamUrls) {
const streamResponses = await Promise.all(
streamUrls.map(async ({ type, url }) => {
try {
const json = await fetch(url).then(r => r.json());
return { type, result: json.result };
} catch (error) {
console.log(`Error fetching ${type} stream:`, error);
return { type, result: null };
}
})
);
const decryptResults = await Promise.all(
streamResponses
.filter(item => item.result !== null)
.map(async item => {
const result = await fetch("https://enc-dec.app/api/dec-kai", {
headers: { 'Content-Type': 'application/json' },
method: "POST",
body: JSON.stringify({ text: item.result })
}).then(res => res.json());
return { [item.type]: result.result.url };
})
);
return Object.assign({}, ...decryptResults);
}
module.exports = AnimeKai;