contentapi.ts
Version:
Nodejs library that provides high-level APIs for obtaining information on various entertainment media such as books, movies, comic books, anime, manga, and so on.
332 lines • 13.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const cheerio_1 = require("cheerio");
class AnimeKaiPro {
constructor() {
this.api = "https://animekai.to";
}
getSettings() {
return {
episodeServers: ["Server 1", "Server 2"],
supportsDub: true,
};
}
async search(query) {
let normalizedQuery = this.normalizeQuery(query["query"]);
console.log("Normalized Query: " + normalizedQuery);
const url = `${this.api}/browser?keyword=${encodeURIComponent(normalizedQuery)}`;
try {
const data = await this._makeRequest(url);
const $ = (0, cheerio_1.load)(data);
const animes = [];
$("div.aitem-wrapper>div.aitem").each((_, elem) => {
var _a, _b, _c;
const id = (_b = (_a = $(elem).find("a.poster").attr("href")) === null || _a === void 0 ? void 0 : _a.slice(1)) !== null && _b !== void 0 ? _b : "";
const title = (_c = $(elem).find("a.title").attr("title")) !== null && _c !== void 0 ? _c : "";
const subOrDub = this.isSubOrDubOrBoth($, elem);
const url = `${this.api}/${id.slice(1)}`;
const anime = {
id: `${id}?dub=${query['dub']}`,
url: url,
title: title,
subOrDub: subOrDub,
};
animes.push(anime);
});
return animes;
}
catch (ex) {
throw new Error(ex);
}
}
async findEpisodes(id) {
const url = `${this.api}/${id.split('?dub')[0]}`;
const rateBoxIdRegex = /<div class="rate-box"[^>]*data-id="([^"]+)"/;
try {
let data = await this._makeRequest(url);
const idMatch = data.match(rateBoxIdRegex);
const aniId = idMatch ? idMatch[1] : null;
const urlFetchToken = KAICODEX.enc(aniId);
const fetchUrlListApi = `${this.api}/ajax/episodes/list?ani_id=${aniId}&_=${urlFetchToken}`;
const responseTextListApi = await this._makeRequest(fetchUrlListApi);
data = await JSON.parse(responseTextListApi);
const episodes = [];
const $ = (0, cheerio_1.load)(data.result);
$('ul.range>li>a').each((_, elem) => {
const title = $(elem).find('span').text().replace(/\s/g, ' ');
const number = parseInt($(elem).attr('num'), 10);
const token = $(elem).attr('token');
const tokenEncoded = KAICODEX.enc(token);
const episodeUrl = `https://animekai.to/ajax/links/list?token=${token}&_=${tokenEncoded}`;
episodes.push({
id: token !== null && token !== void 0 ? token : "",
number: number,
title: title,
url: `${episodeUrl}?dub=${id.split('?dub=')[1]}`
});
});
return episodes;
}
catch (ex) {
throw new Error(ex);
}
}
async findEpisodeServer(episode, _server) {
let server = "Server 1";
if (_server !== "default")
server = _server;
const episodeUrl = episode.url.replace('\u0026', '&').split('?dub')[0];
const dubRequested = episode.url.split('?dub=')[1];
try {
const responseText = await this._makeRequest(episodeUrl);
const cleanedHtml = cleanJsonHtml(responseText);
const subRegex = /<div class="server-items lang-group" data-id="sub"[^>]*>([\s\S]*?)<\/div>/;
const softsubRegex = /<div class="server-items lang-group" data-id="softsub"[^>]*>([\s\S]*?)<\/div>/;
const dubRegex = /<div class="server-items lang-group" data-id="dub"[^>]*>([\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() : "";
let dataLid = "";
let fetchUrlServerApi = "";
let KaiMegaUrlJson = "";
let megaELinkJson = "";
let megaEmbeddedUrl = "";
let megaMediaUrl = "";
let streamUrlJson = "";
let streamUrl = "";
let serverSpanRegex = "";
if (server == "Server 1")
serverSpanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>Server 1<\/span>/;
else
serverSpanRegex = /<span class="server"[^>]*data-lid="([^"]+)"[^>]*>Server 2<\/span>/;
const serverMatch = dubRequested === 'true' ? serverSpanRegex.exec(dub) : serverSpanRegex.exec(sub);
if (serverMatch && serverMatch[1]) {
dataLid = serverMatch[1];
let dataLidToken = KAICODEX.enc(dataLid);
fetchUrlServerApi = `https://animekai.to/ajax/links/view?id=${dataLid}&_=${dataLidToken}`;
const responseTextServerApi = await this._makeRequest(fetchUrlServerApi);
const dataServerApi = await JSON.parse(responseTextServerApi);
KaiMegaUrlJson = KAICODEX.dec(dataServerApi.result);
megaELinkJson = JSON.parse(KaiMegaUrlJson);
megaEmbeddedUrl = megaELinkJson.url;
megaMediaUrl = megaEmbeddedUrl.replace("/e/", "/media/");
const mediaText = await this._makeRequest(megaMediaUrl);
const mediaJson = await JSON.parse(mediaText);
streamUrlJson = mediaJson.result;
streamUrlJson = KAICODEX.decMega(streamUrlJson);
const parsedStreamData = JSON.parse(streamUrlJson);
if (parsedStreamData && parsedStreamData.sources && parsedStreamData.sources.length > 0) {
streamUrl = parsedStreamData.sources[0].file;
}
else {
console.log('No stream sources found in the response' + parsedStreamData);
}
}
if (streamUrl == "") {
throw new Error("Unable to find a valid source");
}
const streams = await fetch(streamUrl, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"DNT": "1"
}
});
const regex = /#EXT-X-STREAM-INF:BANDWIDTH=\d+,RESOLUTION=(\d+x\d+)\s*(.*)/g;
const videoSources = [];
let resolutionMatch;
const streamText = await streams.text();
while ((resolutionMatch = regex.exec(streamText)) !== null) {
let url = "";
if (server == "Server 2") {
console.log("yes");
url = `${streamUrl.split(',')[0]}/${resolutionMatch[2]}`;
}
else {
console.log("no");
url = `${streamUrl.split('/list')[0]}/${resolutionMatch[2]}`;
}
videoSources.push({
quality: resolutionMatch[1].split('x')[1] + 'p',
subtitles: [],
type: 'm3u8',
url: url
});
console.log(videoSources);
}
const episodeServer = {
server: server,
headers: {
"Access-Control-Allow-Origin": "*",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0'
},
videoSources: [videoSources]
};
return episodeServer;
}
catch (e) {
throw new Error(e);
}
}
normalizeQuery(query) {
let normalizedQuery = 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();
return normalizedQuery;
}
async _makeRequest(url) {
const response = await fetch(url, {
method: "GET",
headers: {
"DNT": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36 Edg/134.0.0.0",
Cookie: "__ddg1_=;__ddg2_=;",
},
});
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.statusText}`);
}
const body = await response.text();
return body;
}
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";
}
}
const KAICODEX = {
enc(n) {
var u = KAICODEX.safeBtoa;
var a = KAICODEX.rc4;
var s = KAICODEX.replaceChars;
var r = KAICODEX.reverseString;
n = u(s(u(a('sXmH96C4vhRrgi8', r(r(u(a('kOCJnByYmfI', s(s(r(u(a('0DU8ksIVlFcia2', n))), '1wctXeHqb2', '1tecHq2Xbw'), '48KbrZx1ml', 'Km8Zb4lxr1'))))))), 'hTn79AMjduR5', 'djn5uT7AMR9h'));
return encodeURIComponent(n);
},
encPlain(n) {
return KAICODEX.safeBtoa(KAICODEX.rc4('n1PEbDBiipbJZvZc', encodeURIComponent(n)));
},
dec(n) {
var u = KAICODEX.safeAtob;
var a = KAICODEX.rc4;
var s = KAICODEX.replaceChars;
var r = KAICODEX.reverseString;
n = a('0DU8ksIVlFcia2', u(r(s(s(a('kOCJnByYmfI', u(r(r(a('sXmH96C4vhRrgi8', u(s(u(n), 'djn5uT7AMR9h', 'hTn79AMjduR5'))))))), 'Km8Zb4lxr1', '48KbrZx1ml'), '1tecHq2Xbw', '1wctXeHqb2'))));
return decodeURIComponent(n);
},
decPlain(n) {
return decodeURIComponent(KAICODEX.rc4('n1PEbDBiipbJZvZc', KAICODEX.safeAtob(n)));
},
decMega(n) {
var o = KAICODEX.safeAtob;
var e = KAICODEX.rc4;
var c = KAICODEX.replaceChars;
var v = KAICODEX.reverseString;
n = c(e('fnxEj3tD4Bl0X', o(e('IjilzMV57GrnF', o(c(v(c(e('PlzI69YVCtGwoa8', o(o(n))), 'c2IfHZwSX1mj', 'mwfXcS2ZjI1H')), '82NkgQDYbIF', '82IQNkFgYbD'))))), 'crwkth05iJR8', 'JRkt8rw0i5ch');
return decodeURIComponent(n);
},
rc4: function (key, str) {
var s = [], j = 0, x, res = '';
for (var i = 0; i < 256; i++) {
s[i] = i;
}
for (i = 0; i < 256; i++) {
j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
x = s[i];
s[i] = s[j];
s[j] = x;
}
i = 0;
j = 0;
for (var y = 0; y < str.length; y++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
x = s[i];
s[i] = s[j];
s[j] = x;
res += String.fromCharCode(str.charCodeAt(y) ^ s[(s[i] + s[j]) % 256]);
}
return res;
},
safeBtoa: function (s) {
return btoa(s).replace(/\//g, '_').replace(/\+/g, '-').replace(/=/g, '');
},
safeAtob: function (s) {
return atob(s.replace(/_/g, '/').replace(/-/g, '+'));
},
reverseString: function (s) {
return s.split('').reverse().join('');
},
replaceChars: function (s, f, r) {
let i = f.length;
let m = {};
while (i-- && (m[f[i]] = r[i])) { }
return s.split("").map((v) => m[v] || v).join('');
}
};
function btoa(input) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let str = String(input);
let output = '';
for (let block = 0, charCode, i = 0, map = chars; str.charAt(i | 0) || (map = '=', i % 1); output += map.charAt(63 & (block >> (8 - (i % 1) * 8)))) {
charCode = str.charCodeAt(i += 3 / 4);
if (charCode > 0xFF) {
throw new Error("btoa failed: The string contains characters outside of the Latin1 range.");
}
block = (block << 8) | charCode;
}
return output;
}
function atob(input) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let str = String(input).replace(/=+$/, '');
let output = '';
if (str.length % 4 == 1) {
throw new Error("atob failed: The input is not correctly encoded.");
}
for (let bc = 0, bs, buffer, i = 0; (buffer = str.charAt(i++)); ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4)
? output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6)))
: 0) {
buffer = chars.indexOf(buffer);
}
return output;
}
function cleanHtmlSymbols(string) {
if (!string)
return "";
return string
.replace(/’/g, "'")
.replace(/–/g, "-")
.replace(/&#[0-9]+;/g, "")
.replace(/\r?\n|\r/g, " ")
.replace(/\s+/g, " ")
.trim();
}
function 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');
}
function LoadDoc(data) {
throw new Error('Function not implemented.');
}
exports.default = AnimeKaiPro;
//# sourceMappingURL=animekaipro.js.map