Files
kodjodevf-mangayomi-extensions/javascript/manga/src/en/readcomiconline.js

340 lines
12 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const mangayomiSources = [{
"name": "ReadComicOnline",
"lang": "en",
"baseUrl": "https://readcomiconline.li",
"apiUrl": "",
"iconUrl": "https://www.google.com/s2/favicons?sz=256&domain=https://readcomiconline.li/",
"typeSource": "single",
"itemType": 0,
"version": "0.1.1",
"pkgPath": ""
}];
class DefaultExtension extends MProvider {
constructor() {
super();
this.client = new Client();
}
getPreference(key) {
return new SharedPreferences().get(key);
}
getHeaders() {
return {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.6832.64 Safari/537.36",
"Referer": this.source.baseUrl,
"Origin": this.source.baseUrl,
}
}
async request(slug) {
var url = slug
var baseUrl = this.source.baseUrl
if (!slug.includes(baseUrl)) url = baseUrl + slug;
var res = await this.client.get(url, this.getHeaders());
return new Document(res.body);
}
async getListPage(slug, page) {
var url = `${slug}page=${page}`
var doc = await this.request(url);
var baseUrl = this.source.baseUrl
var list = []
var comicList = doc.select(".list-comic > .item")
comicList.forEach(item => {
var name = item.selectFirst(".title").text;
var link = item.selectFirst("a").getHref
var imageSlug = item.selectFirst("img").getSrc
var imageUrl = imageSlug.includes("http") ? imageSlug : `${baseUrl}${imageSlug}`;
list.push({ name, link, imageUrl });
});
var pager = doc.select("ul.pager > li")
var hasNextPage = false
if (pager.length > 0) hasNextPage = pager[pager.length - 1].text.includes("Last") ? true : false;
return { list, hasNextPage }
}
async getPopular(page) {
return await this.getListPage("/ComicList/MostPopular?", page)
}
get supportsLatest() {
throw new Error("supportsLatest not implemented");
}
async getLatestUpdates(page) {
return await this.getListPage("/ComicList/LatestUpdate?", page)
}
async search(query, page, filters) {
function getFilter(state) {
var rd = ""
state.forEach(item => {
if (item.state) {
rd += `${item.value},`
}
})
return rd.slice(0, -1)
}
var genre = getFilter(filters[0].state)
var status = filters[1].values[filters[1].state].value
var year = filters[2].values[filters[2].state].value
var slug = `/AdvanceSearch?comicName=${query}&ig=${encodeURIComponent(genre)}&status=${status}&pubDate=${year}&`
return await this.getListPage(slug, page)
}
async getDetail(url) {
function statusCode(status) {
return {
"Ongoing": 0,
"Completed": 1,
}[status] ?? 5;
}
var link = this.source.baseUrl + url
var doc = await this.request(url)
var detailsSection = doc.selectFirst(".barContent")
var name = detailsSection.selectFirst("a").text
var imageSlug = doc.selectFirst(".rightBox").selectFirst("img").getSrc
var imageUrl = imageSlug.includes("http") ? imageSlug : `${this.source.baseUrl}${imageSlug}`;
var pTag = detailsSection.select("p")
var description = pTag[pTag.length - 2].text
var status = 5
var genre = []
var author = ""
var artist = ""
pTag.forEach(p => {
var itemText = p.text.trim()
if (itemText.includes("Genres")) {
genre = itemText.replace("Genres:", "").trim().split(", ")
} else if (itemText.includes("Status")) {
var sts = itemText.replace("Status: ", "").trim().split("\n")[0]
status = statusCode(sts)
} else if (itemText.includes("Writer")) {
author = itemText.replace("Writer: ", "")
} else if (itemText.includes("Artist")) {
artist = itemText.replace("Artist: ", "")
}
})
var chapters = []
var tr = doc.selectFirst("table").select("tr")
tr.splice(0, 2) // 1st item in the table is headers & 2nd item is a line break
tr.forEach(item => {
var tds = item.select("td")
var aTag = tds[0].selectFirst("a")
var chapLink = aTag.getHref
var chapTitle = aTag.text.trim().replace(`${name} `, "")
chapTitle = chapTitle[0] == "_" ? chapTitle.substring(1,) : chapTitle
var uploadDate = tds[1].text.trim()
var date = new Date(uploadDate);
var dateUpload = date.getTime().toString();
chapters.push({ url: chapLink, name: chapTitle, dateUpload })
})
return { name, link, imageUrl, description, genre, status, author, artist, chapters }
}
// For manga chapter pages
async getPageList(url) {
var pages = [];
var hdr = this.getHeaders()
let match;
var imageQuality = this.getPreference("readcomiconline_page_quality");
var doc = await this.request(url)
var html = doc.html
// Find host url for images
var baseUrlOverride = ""
const hostRegex = /return\s+baeu\s*\(\s*l\s*,\s*'([^']+?)'\s*\);?/g;
match = hostRegex.exec(html)
if (match.length > 0) {
baseUrlOverride = match[1]
if (baseUrlOverride.slice(-1) != "/") baseUrlOverride += "/"
}
const pageRegex = /pht\s*=\s*'([^']+?)';?/g;
while ((match = pageRegex.exec(html)) !== null) {
var encodedImageUrl = match[1]
var decodedImageUrl = this.decodeImageUrl(encodedImageUrl, imageQuality, baseUrlOverride)
pages.push({
url: decodedImageUrl,
headers: hdr,
});
}
return pages;
}
getFilterList() {
function formateState(type_name, items, values) {
var state = [];
for (var i = 0; i < items.length; i++) {
state.push({ type_name: type_name, name: items[i], value: values[i] })
}
return state;
}
var filters = [];
// Genre
var items = [
"Action", "Adventure", "Anthology", "Anthropomorphic", "Biography", "Children", "Comedy",
"Crime", "Drama", "Family", "Fantasy", "Fighting", "Graphic Novels", "Historical", "Horror",
"Leading Ladies", "LGBTQ", "Literature", "Manga", "Martial Arts", "Mature", "Military",
"Mini-Series", "Movies & TV", "Music", "Mystery", "Mythology", "Personal", "Political",
"Post-Apocalyptic", "Psychological", "Pulp", "Religious", "Robots", "Romance", "School Life",
"Sci-Fi", "Slice of Life", "Sport", "Spy", "Superhero", "Supernatural", "Suspense", "Teen",
"Thriller", "Vampires", "Video Games", "War", "Western", "Zombies"
];
var values = [
"1", "2", "38", "46", "41", "49", "3",
"17", "19", "25", "20", "31", "5", "28", "15",
"35", "51", "44", "40", "4", "8", "33",
"56", "47", "55", "23", "21", "48", "42",
"43", "27", "39", "53", "9", "32", "52",
"16", "50", "54", "30", "22", "24", "29", "57",
"18", "34", "37", "26", "45", "36"
];
filters.push({
type_name: "GroupFilter",
name: "Genres",
state: formateState("CheckBox", items, values)
})
// Status
items = ["Any", "Ongoing", "Completed"];
values = ["", "Ongoing", "Completed"];
filters.push({
type_name: "SelectFilter",
name: "Status",
state: 0,
values: formateState("SelectOption", items, values)
})
// Years
const currentYear = new Date().getFullYear();
items = Array.from({ length: currentYear - 1919 }, (_, i) => (1920 + i).toString()).reverse()
items = ["All", ...items]
values = ["", ...items]
filters.push({
type_name: "SelectFilter",
name: "Year",
state: 0,
values: formateState("SelectOption", items, values)
})
return filters;
}
getSourcePreferences() {
return [
{
key: "readcomiconline_page_quality",
listPreference: {
title: 'Preferred image quality',
summary: '',
valueIndex: 2,
entries: ["Low", "Medium", "High", "Highest"],
entryValues: ["l", "m", "h", "vh"]
}
},
]
}
// -------- ReadComicOnline Image Decoder --------
// Source:- https://readcomiconline.li/Scripts/rguard.min.js
base64UrlDecode(input) {
let base64 = input
.replace(/-/g, "+")
.replace(/_/g, "/");
while (base64.length % 4 !== 0) {
base64 += "=";
}
const base64abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const outputBytes = [];
for (let i = 0; i < base64.length; i += 4) {
const c1 = base64abc.indexOf(base64[i]);
const c2 = base64abc.indexOf(base64[i + 1]);
const c3 = base64abc.indexOf(base64[i + 2]);
const c4 = base64abc.indexOf(base64[i + 3]);
const triplet = (c1 << 18) | (c2 << 12) | ((c3 & 63) << 6) | (c4 & 63);
outputBytes.push((triplet >> 16) & 0xFF);
if (base64[i + 2] !== "=") outputBytes.push((triplet >> 8) & 0xFF);
if (base64[i + 3] !== "=") outputBytes.push(triplet & 0xFF);
}
// Convert bytes to ISO-8859-1 string
return String.fromCharCode(...outputBytes);
}
extractBeforeDecode(url) {
return url.substring(15, 33) + url.substring(50);
}
finalizeDecodedString(decoded) {
return decoded.substring(0, decoded.length - 11) + decoded[decoded.length - 2] + decoded[decoded.length - 1];
}
decoderFunction(encodedUrl) {
var decodedUrl = this.extractBeforeDecode(encodedUrl);
decodedUrl = this.finalizeDecodedString(decodedUrl);
decodedUrl = decodeURIComponent(this.base64UrlDecode(decodedUrl));
decodedUrl = decodedUrl.substring(0, 13) + decodedUrl.substring(17);
return decodedUrl.slice(0, -2) + "=s1600"
}
decodeImageUrl(encodedImageUrl, imageQuality, baseUrlOverride) {
// Default image qualities
var IMAGEQUALITY = [
{ "l": "900", "m": "0", "h": "1600", "vh": "2041" },
{ "l": "900", "m": "1600", "h": "2041", "vh": "0" }
]
let finalUrl;
var qType = 0
// Check if the url starts with https, if not then decode the url
if (!encodedImageUrl.startsWith("https")) {
encodedImageUrl = encodedImageUrl
.replace(/6UUQS__ACd__/g, 'b')
.replace(/pw_.g28x/g, "b")
var encodedUrl = encodedImageUrl.split("=s")[0]
var decodedUrl = this.decoderFunction(encodedUrl);
var queryParams = encodedImageUrl.substring(encodedImageUrl.indexOf("?"));
finalUrl = baseUrlOverride + decodedUrl + queryParams
} else {
// If the url starts with https, then just override the base url
qType = 1
finalUrl = baseUrlOverride + encodedImageUrl.split(".com/")[1]
}
return finalUrl.replace("s1600", `s${IMAGEQUALITY[qType][imageQuality]}`)
}
}