mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 10:51:17 +00:00
anime(animekai): Added kai decoders and anime details
This commit is contained in:
@@ -6,20 +6,21 @@ const mangayomiSources = [{
|
|||||||
"iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://animekai.to/",
|
"iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://animekai.to/",
|
||||||
"typeSource": "single",
|
"typeSource": "single",
|
||||||
"itemType": 1,
|
"itemType": 1,
|
||||||
"version": "0.0.3",
|
"version": "0.1.0",
|
||||||
"pkgPath": "anime/src/en/animekai.js"
|
"pkgPath": "anime/src/en/animekai.js"
|
||||||
}];
|
}];
|
||||||
|
|
||||||
class DefaultExtension extends MProvider {
|
class DefaultExtension extends MProvider {
|
||||||
getHeaders(url) {
|
|
||||||
throw new Error("getHeaders not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.client = new Client();
|
this.client = new Client();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHeaders(url) {
|
||||||
|
throw new Error("getHeaders not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
getPreference(key) {
|
getPreference(key) {
|
||||||
return new SharedPreferences().get(key);
|
return new SharedPreferences().get(key);
|
||||||
}
|
}
|
||||||
@@ -120,8 +121,108 @@ class DefaultExtension extends MProvider {
|
|||||||
var language = getFilter(filters[8].state)
|
var language = getFilter(filters[8].state)
|
||||||
return await this.searchPage({ query, type, genre, status, sort, season, year, rating, country, language, page });
|
return await this.searchPage({ query, type, genre, status, sort, season, year, rating, country, language, page });
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDetail(url) {
|
async getDetail(url) {
|
||||||
throw new Error("getDetail not implemented");
|
function statusCode(status) {
|
||||||
|
return {
|
||||||
|
"Releasing": 0,
|
||||||
|
"Completed": 1,
|
||||||
|
"Not Yet Aired": 4,
|
||||||
|
}[status] ?? 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
var slug = url
|
||||||
|
var link = this.getBaseUrl() + slug
|
||||||
|
var body = await this.getPage(slug)
|
||||||
|
|
||||||
|
var mainSection = body.selectFirst(".watch-section")
|
||||||
|
|
||||||
|
var imageUrl = mainSection.selectFirst("div.poster").selectFirst("img").getSrc
|
||||||
|
|
||||||
|
var namePref = this.getPreference("animekai_title_lang")
|
||||||
|
var nameSection = mainSection.selectFirst("div.title")
|
||||||
|
var name = namePref.includes("jp") ? nameSection.attr(namePref) : nameSection.text
|
||||||
|
|
||||||
|
var description = mainSection.selectFirst("div.desc").text
|
||||||
|
|
||||||
|
var detailSection = mainSection.select("div.detail > div")
|
||||||
|
|
||||||
|
var genre = []
|
||||||
|
var status = 5
|
||||||
|
detailSection.forEach(item => {
|
||||||
|
var itemText = item.text.trim()
|
||||||
|
|
||||||
|
if (itemText.includes("Genres")) {
|
||||||
|
genre = itemText.replace("Genres: ", "").split(", ")
|
||||||
|
}
|
||||||
|
if (itemText.includes("Status")) {
|
||||||
|
var statusText = item.selectFirst("span").text
|
||||||
|
status = statusCode(statusText)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var chapters = []
|
||||||
|
var animeId = body.selectFirst("#anime-rating").attr("data-id")
|
||||||
|
|
||||||
|
var token = await this.generateToken(animeId)
|
||||||
|
var res = await this.request(`/ajax/episodes/list?ani_id=${animeId}&_=${token}`)
|
||||||
|
body = JSON.parse(res)
|
||||||
|
if (body.status == 200) {
|
||||||
|
var doc = new Document(body["result"])
|
||||||
|
var episodes = doc.selectFirst("div.eplist.titles").select("li")
|
||||||
|
var showUncenEp = this.getPreference("animekai_show_uncen_epsiodes")
|
||||||
|
|
||||||
|
for (var item of episodes) {
|
||||||
|
var aTag = item.selectFirst("a")
|
||||||
|
|
||||||
|
var num = parseInt(aTag.attr("num"))
|
||||||
|
var title = aTag.selectFirst("span").text
|
||||||
|
title = title.includes("Episode") ? "" : `: ${title}`
|
||||||
|
var epName = `Episode ${num}${title}`
|
||||||
|
|
||||||
|
|
||||||
|
var langs = aTag.attr("langs")
|
||||||
|
var scanlator = langs === "1" ? "SUB" : "SUB, DUB"
|
||||||
|
|
||||||
|
var token = aTag.attr("token")
|
||||||
|
|
||||||
|
var epData = {
|
||||||
|
name: epName,
|
||||||
|
url: token,
|
||||||
|
scanlator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the episode is uncensored
|
||||||
|
var slug = aTag.attr("slug")
|
||||||
|
if (slug.includes("uncen")) {
|
||||||
|
|
||||||
|
// if dont show uncensored episodes, skip this episode
|
||||||
|
if (!showUncenEp) continue
|
||||||
|
|
||||||
|
scanlator += ", UNCENSORED"
|
||||||
|
epName = `Episode ${num}: (Uncensored)`
|
||||||
|
// Build for uncensored episode
|
||||||
|
epData = {
|
||||||
|
name: epName,
|
||||||
|
url: token,
|
||||||
|
scanlator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the episode already exists as censored if so, add to existing data
|
||||||
|
var exData = chapters[num - 1]
|
||||||
|
if (exData) {
|
||||||
|
exData.url += "||" + epData.url
|
||||||
|
exData.scanlator += ", " + epData.scanlator
|
||||||
|
chapters[num - 1] = exData
|
||||||
|
continue
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chapters.push(epData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chapters.reverse()
|
||||||
|
return { name, imageUrl, link, description, genre, status, chapters }
|
||||||
}
|
}
|
||||||
// For novel html content
|
// For novel html content
|
||||||
async getHtmlContent(url) {
|
async getHtmlContent(url) {
|
||||||
@@ -291,7 +392,143 @@ class DefaultExtension extends MProvider {
|
|||||||
entries: ["English", "Romaji"],
|
entries: ["English", "Romaji"],
|
||||||
entryValues: ["title", "data-jp"]
|
entryValues: ["title", "data-jp"]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
key: "animekai_show_uncen_epsiodes",
|
||||||
|
switchPreferenceCompat: {
|
||||||
|
title: 'Show uncensored episodes',
|
||||||
|
summary: "",
|
||||||
|
value: true
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------AnimeKai Decoders----------------
|
||||||
|
// Credits :- https://github.com/amarullz/kaicodex/
|
||||||
|
|
||||||
|
base64Decoder(base64) {
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||||
|
let binary = '';
|
||||||
|
|
||||||
|
base64 = base64.replace(/=+$/, '');
|
||||||
|
|
||||||
|
for (let i = 0; i < base64.length; i++) {
|
||||||
|
const index = chars.indexOf(base64[i]);
|
||||||
|
if (index === -1) continue; // skip invalid characters
|
||||||
|
binary += index.toString(2).padStart(6, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded = '';
|
||||||
|
for (let i = 0; i < binary.length; i += 8) {
|
||||||
|
const byte = binary.substring(i, i + 8);
|
||||||
|
if (byte.length < 8) continue;
|
||||||
|
decoded += String.fromCharCode(parseInt(byte, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
base64Encoder(str) {
|
||||||
|
const base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
var out, i, len;
|
||||||
|
var c1, c2, c3;
|
||||||
|
len = str.length;
|
||||||
|
i = 0;
|
||||||
|
out = "";
|
||||||
|
while (i < len) {
|
||||||
|
c1 = str.charCodeAt(i++) & 0xff;
|
||||||
|
if (i == len) {
|
||||||
|
out += base64EncodeChars.charAt(c1 >> 2);
|
||||||
|
out += base64EncodeChars.charAt((c1 & 0x3) << 4);
|
||||||
|
out += "==";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c2 = str.charCodeAt(i++);
|
||||||
|
if (i == len) {
|
||||||
|
out += base64EncodeChars.charAt(c1 >> 2);
|
||||||
|
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
|
||||||
|
out += base64EncodeChars.charAt((c2 & 0xF) << 2);
|
||||||
|
out += "=";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
c3 = str.charCodeAt(i++);
|
||||||
|
out += base64EncodeChars.charAt(c1 >> 2);
|
||||||
|
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
|
||||||
|
out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
|
||||||
|
out += base64EncodeChars.charAt(c3 & 0x3F);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform(key, text) {
|
||||||
|
const v = Array.from({ length: 256 }, (_, i) => i);
|
||||||
|
let c = 0;
|
||||||
|
const f = [];
|
||||||
|
|
||||||
|
for (let w = 0; w < 256; w++) {
|
||||||
|
c = (c + v[w] + key.charCodeAt(w % key.length)) % 256;
|
||||||
|
[v[w], v[c]] = [v[c], v[w]];
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = 0, w = 0, sum = 0;
|
||||||
|
while (a < text.length) {
|
||||||
|
w = (w + 1) % 256;
|
||||||
|
sum = (sum + v[w]) % 256;
|
||||||
|
[v[w], v[sum]] = [v[sum], v[w]];
|
||||||
|
f.push(String.fromCharCode(text.charCodeAt(a) ^ v[(v[w] + v[sum]) % 256]));
|
||||||
|
a++;
|
||||||
|
}
|
||||||
|
return f.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseString(input) {
|
||||||
|
return input.split('').reverse().join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
substitute(input, keys, values) {
|
||||||
|
const map = {};
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
map[keys[i]] = values[i] || keys[i];
|
||||||
|
}
|
||||||
|
return input.split('').map(char => map[char] || char).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDecoderPattern() {
|
||||||
|
const preferences = new SharedPreferences();
|
||||||
|
let pattern = preferences.getString("anime_kai_decoder_pattern", "");
|
||||||
|
var pattern_ts = parseInt(preferences.getString("anime_kai_decoder_pattern_ts", "0"));
|
||||||
|
var now_ts = parseInt(new Date().getTime() / 1000);
|
||||||
|
|
||||||
|
// pattern is checked from API every 30 minutes
|
||||||
|
if (now_ts - pattern_ts > 30 * 60) {
|
||||||
|
var res = await this.client.get("https://raw.githubusercontent.com/amarullz/kaicodex/refs/heads/main/generated/kai_codex.json")
|
||||||
|
pattern = res.body
|
||||||
|
preferences.setString("anime_kai_decoder_pattern", pattern);
|
||||||
|
preferences.setString("anime_kai_decoder_pattern_ts", `${now_ts}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
async patternExecutor(key, type, id) {
|
||||||
|
var result = id
|
||||||
|
var pattern = await this.getDecoderPattern()
|
||||||
|
var logic = pattern[key][type]
|
||||||
|
logic.forEach(step => {
|
||||||
|
var method = step[0]
|
||||||
|
if (method == "urlencode") result = encodeURIComponent(result);
|
||||||
|
else if (method == "rc4") result = this.transform(step[1], result);
|
||||||
|
else if (method == "reverse") result = this.reverseString(result);
|
||||||
|
else if (method == "substitute") result = this.substitute(result, step[1], step[2]);
|
||||||
|
else if (method == "safeb64_decode") result = this.base64Decoder(result);
|
||||||
|
else if (method == "safeb64_encode") result = this.base64Encoder(result);
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateToken(id) {
|
||||||
|
var token = await this.patternExecutor("kai", "encrypt", id)
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user