Merge pull request #285 from xMohnad/manga/webtoons

Fix: Manga/webtoons
This commit is contained in:
Moustapha Kodjo Amadou
2025-06-15 17:51:50 +01:00
committed by GitHub

View File

@@ -1,3 +1,4 @@
// prettier-ignore
const mangayomiSources = [{ const mangayomiSources = [{
"name": "Webtoons", "name": "Webtoons",
"langs": ["en", "fr", "id", "th", "es", "zh", "de"], "langs": ["en", "fr", "id", "th", "es", "zh", "de"],
@@ -14,371 +15,567 @@ const mangayomiSources = [{
}]; }];
class DefaultExtension extends MProvider { class DefaultExtension extends MProvider {
headers = { headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" "User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
};
getHeaders(url) {
return {
Referer: this.source.baseUrl,
}; };
getHeaders(url) { }
return {
Referer: this.source.baseUrl mangaFromElement(doc) {
} const list = [];
for (const el of doc.select(
`div.webtoon_list_wrap li a, ul.webtoon_list li a`,
)) {
const imageUrl = el.selectFirst("img").getSrc;
const name = el.selectFirst("strong.title").text;
const link = el.getHref;
list.push({ name, imageUrl, link });
} }
async getItem(url, selector) { return list;
const res = await new Client().get(this.source.baseUrl + url, this.headers); }
const doc = new Document(res.body);
const mangas = []; async getPopular(page) {
const elements = doc.select(selector); const baseUrl = this.source.baseUrl;
for (const element of elements) { const res = await new Client().get(
const linkElement = element.selectFirst("a"); `${baseUrl}/${this.langCode()}/originals`,
const imageElement = linkElement.selectFirst("img"); );
const imageUrl = imageElement.attr("src"); const doc = new Document(res.body);
const name = element.selectFirst("p.subj").text;
const link = linkElement.attr("href"); return {
const genre = []; list: this.mangaFromElement(doc),
if (element.selectFirst("p.genre").text === "") { hasNextPage: false,
genre.push(element.selectFirst("span.genre").text); };
} else { }
genre.push(element.selectFirst("p.genre").text);
} async getLatestUpdates(page) {
mangas.push({ const baseUrl = this.source.baseUrl;
name: name, const res = await new Client().get(
imageUrl: imageUrl, `${baseUrl}/${this.langCode()}/originals?sortOrder=UPDATE`,
link: link, );
genre: genre const doc = new Document(res.body);
});
} return {
return mangas; list: this.mangaFromElement(doc),
hasNextPage: false,
};
}
async search(query, page, filters) {
const keyword = query.trim().replace(/\s+/g, "+");
const baseurl = this.source.baseUrl;
let url = `${baseurl}/${this.langCode()}`;
let hasNextPage = false;
const getFilterValue = (type, defaultValue = "") => {
const filter = filters.find((f) => f.type === type);
return filter?.values?.[filter.state]?.value ?? defaultValue;
};
if (query) {
url += `/search/${getFilterValue("searchType")}?keyword=${keyword}&page=${page}`;
} else {
const sortOrder = getFilterValue("sortOrder");
const rankingType = getFilterValue("rankingType");
const weekday = getFilterValue("weekday");
const genreType = getFilterValue("genre");
if (rankingType) {
// const genreParam = genreType ? `&subTabGenreCode=${genreType}` : "";
url += `/ranking/${rankingType}`;
} else if (weekday) {
url += `/originals/${weekday}?sortOrder=${sortOrder}`;
} else if (genreType) {
url += `/genres/${genreType}?sortOrder=${sortOrder}`;
}
} }
async getPopular(page) { const res = await new Client().get(url);
const baseUrl = this.source.baseUrl; const doc = new Document(res.body);
const client = new Client(); const list = this.mangaFromElement(doc);
const res = await client.get(`${baseUrl}/${this.langCode()}/originals`); if (query) {
const doc = new Document(res.body); hasNextPage = list.length !== 0;
const days = ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"];
let dayElements = [];
for (const day of days) {
const elements = doc.select(`div.daily_section._list_${day} li`);
dayElements.push(elements);
}
const maxElements = Math.max(...dayElements.map(elements => elements.length));
let list = [];
for (let i = 0; i < maxElements; i++) {
for (let j = 0; j < days.length; j++) {
const elements = dayElements[j];
if (i < elements.length) {
const element = elements[i];
const linkElement = element.selectFirst("a");
const imageElement = linkElement.selectFirst("img");
const imageUrl = imageElement.attr("src");
const name = element.selectFirst("p.subj").text;
const link = linkElement.attr("href");
list.push({ name, imageUrl, link });
}
}
}
const completed = doc.select("div.daily_lst.comp li");
for (const element of completed) {
const linkElement = element.selectFirst("a");
const imageElement = linkElement.selectFirst("img");
const imageUrl = imageElement.attr("src");
const name = element.selectFirst("p.subj").text;
const link = linkElement.attr("href");
list.push({ name, imageUrl, link });
}
return {
list: list,
hasNextPage: false
};
} }
async getLatestUpdates(page) { return {
const Day = ['SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY']; list,
const list = await this.getItem(`/${this.langCode()}/originals?sortOrder=UPDATE`, "div.daily_section._list_" + Day[new Date().getDay()] + " li"); hasNextPage,
return { };
list: list, }
hasNextPage: false
}; async getDetail(url) {
const res = await new Client().get(url);
const doc = new Document(res.body);
const info = doc.selectFirst("div.cont_box");
const title = info.selectFirst("h1.subj, h3.subj").text.trim();
const genre =
Array.from(info.select("p.genre")).map((el) => el.text) != ""
? Array.from(info.select("p.genre")).map((el) => el.text)
: [info.selectFirst("div.info h2").text];
const author =
info
.selectFirst("div.author_area")
.text.replace(/\s+/g, " ")
.replace(/author info/g, "")
.trim() ?? info.selectFirst("a.author").text;
const status_str = info.selectFirst("p.day_info").text;
var status;
if (status_str == "COMPLETED") {
status = 1;
} else {
status = 0;
} }
const desc = info.selectFirst("p.summary").text.replace(/\s+/g, " ").trim();
const chapters = [];
let tester = "";
let page = 1;
async search(query, page, filters) { while (tester !== "#1") {
let keyword = query.trim().replace(/\s+/g, '+'); const res = await new Client().get(url + `&page=${page}`);
let hasNextPage = true; const doc = new Document(res.body);
let type_originals = "WEBTOON"; const info = doc.selectFirst("div.cont_box");
let type_canvas = "CHALLENGE"; const elements = info.select("div.detail_lst li");
let fetch_originals = "ul.card_lst li";
let fetch_canvas = "div.challenge_lst.search li";
let list_originals = [];
let list_canvas = [];
let list = [];
if (query !== "") { for (const element of elements) {
list_originals = await this.getItem(`/${this.langCode()}/search?keyword=${keyword}&searchType=` + type_originals + `&page=${page}`, fetch_originals); tester = element.selectFirst("span.tx").text.trim();
list_canvas = await this.getItem(`/${this.langCode()}/search?keyword=${keyword}&searchType=` + type_canvas + `&page=${page}`, fetch_canvas); const dateString = element.selectFirst("span.date").text.trim();
if (filters) { } const date = new Date(
list = list_originals.concat(list_canvas); this.formatDateString(dateString, this.source.lang),
} else { );
const millisecondsSinceEpoch = date.getTime();
} const millisecondsString = millisecondsSinceEpoch.toString();
chapters.push({
if (list.length === 0) { hasNextPage = false; } name: tester + " " + element.selectFirst("span.subj span").text,
return { url: element.selectFirst("a").attr("href"),
list: list, dateUpload: millisecondsString,
hasNextPage: hasNextPage });
}; }
page++;
} }
return {
name: title,
link: url,
genre: genre,
description: desc,
author: author,
status: status,
episodes: chapters,
};
}
async getDetail(url) { langCode() {
const res = await new Client().get(url); return {
const doc = new Document(res.body); en: "en",
const info = doc.selectFirst("div.cont_box"); fr: "fr",
const cover = info.selectFirst("div.detail_body")?.attr("style")?.match(/url\(['"]?(.*?)['"]?\)/)?.[1] ?? info.selectFirst("span.thmb img")?.attr("src"); id: "id",
const title = info.selectFirst("h1.subj, h3.subj").text.trim(); th: "th",
const genre = Array.from(info.select("p.genre")).map(el => el.text) != '' ? Array.from(info.select("p.genre")).map(el => el.text) : [info.selectFirst("div.info h2").text]; es: "es",
const author = info.selectFirst("div.author_area").text.replace(/\s+/g, ' ').replace(/author info/g, '').trim() ?? info.selectFirst("a.author").text; zh: "zh-hant",
const status_str = info.selectFirst("p.day_info").text; de: "de",
var status; }[this.source.lang];
if (status_str == "COMPLETED") { }
status = 1;
} else { formatDateString(dateStr, lang) {
status = 0; // Month translations for supported languages
const monthTranslations = {
en: [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
fr: [
"janv.",
"févr.",
"mars",
"avr.",
"mai",
"juin",
"juil.",
"août",
"sept.",
"oct.",
"nov.",
"déc.",
],
id: [
"Jan",
"Feb",
"Mar",
"Apr",
"Mei",
"Jun",
"Jul",
"Agt",
"Sep",
"Okt",
"Nov",
"Des",
],
th: [
"ม.ค.",
"ก.พ.",
"มี.ค.",
"เม.ย.",
"พ.ค.",
"มิ.ย.",
"ก.ค.",
"ส.ค.",
"ก.ย.",
"ต.ค.",
"พ.ย.",
"ธ.ค.",
],
es: [
"ene.",
"feb.",
"mar.",
"abr.",
"may.",
"jun.",
"jul.",
"ago.",
"sep.",
"oct.",
"nov.",
"dic.",
],
zh: [], // No need for month names; uses yyyy年MM月dd日 format
de: [], // No need for month names; uses dd.MM.yyyy format
};
const months = monthTranslations[lang];
let parts;
let month;
let day;
let year;
// Handle formats based on the language
switch (lang) {
case "zh":
// Expected format: yyyy年MM月dd日
const match = dateStr.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/);
year = match[1];
month = match[2];
day = match[3];
case "de":
// Expected format: dd.MM.yyyy
parts = dateStr.split(".");
if (parts.length === 3) {
month = parts[1];
day = parts[0];
year = parts[2];
} }
const desc = info.selectFirst("p.summary").text.replace(/\s+/g, ' ').trim(); case "es":
const chapters = []; case "fr":
let tester = ""; case "id":
let page = 1; case "th":
// Expected format: dd MMM yyyy
while (tester !== "#1") { parts = dateStr.split(" ");
const res = await new Client().get(url + `&page=${page}`); if (parts.length === 3) {
const doc = new Document(res.body); month = months.indexOf(parts[1]) + 1;
const info = doc.selectFirst("div.cont_box"); day = parts[0];
const elements = info.select("div.detail_lst li"); year = parts[2];
}
for (const element of elements) { break;
tester = element.selectFirst("span.tx").text.trim(); default:
const dateString = element.selectFirst("span.date").text.trim(); parts = dateStr.split(" ");
const date = new Date(this.formatDateString(dateString, this.source.lang)); if (parts.length === 3) {
const millisecondsSinceEpoch = date.getTime(); month = months.indexOf(parts[0]) + 1;
const millisecondsString = millisecondsSinceEpoch.toString(); day = parts[1].replace(",", "");
chapters.push({ year = parts[2];
name: tester + " " + element.selectFirst("span.subj span").text,
url: element.selectFirst('a').attr("href"),
dateUpload: millisecondsString
});
}
page++;
} }
return {
name: title,
link: url,
genre: genre,
description: desc,
imageUrl: cover,
author: author,
status: status,
episodes: chapters
};
} }
if (!month || !year || !day) {
return Date.now();
langCode() {
return {
en: "en",
fr: "fr",
id: "id",
th: "th",
es: "es",
zh: "zh-hant",
de: "de"
}[this.source.lang];
} }
return `${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}`;
}
formatDateString(dateStr, lang) { async getPageList(url) {
// Month translations for supported languages const res = await new Client().get(url);
const monthTranslations = { const doc = new Document(res.body);
en: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], const urls = [];
fr: ["janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."], const imageElement = doc.selectFirst("div#_imageList");
id: ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agt", "Sep", "Okt", "Nov", "Des"], const img_urls = imageElement.select("img");
th: ["ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."], for (let i = 0; i < img_urls.length; i++) {
es: ["ene.", "feb.", "mar.", "abr.", "may.", "jun.", "jul.", "ago.", "sep.", "oct.", "nov.", "dic."], urls.push(img_urls[i].attr("data-url"));
zh: [], // No need for month names; uses yyyy年MM月dd日 format
de: [] // No need for month names; uses dd.MM.yyyy format
};
const months = monthTranslations[lang];
let parts;
let month;
let day;
let year;
// Handle formats based on the language
switch (lang) {
case "zh":
// Expected format: yyyy年MM月dd日
const match = dateStr.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/);
year = match[1];
month = match[2];
day = match[3];
case "de":
// Expected format: dd.MM.yyyy
parts = dateStr.split('.');
if (parts.length === 3) {
month = parts[1];
day = parts[0];
year = parts[2];
}
case "es":
case "fr":
case "id":
case "th":
// Expected format: dd MMM yyyy
parts = dateStr.split(' ');
if (parts.length === 3) {
month = months.indexOf(parts[1]) + 1;
day = parts[0];
year = parts[2];
}
break;
default:
parts = dateStr.split(' ');
if (parts.length === 3) {
month = months.indexOf(parts[0]) + 1;
day = parts[1].replace(',', '');
year = parts[2];
}
}
if (!month || !year || !day) {
return Date.now();
}
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
} }
return urls;
}
async getPageList(url) { getFilterList() {
const res = await new Client().get(url); return [
const doc = new Document(res.body); {
const urls = []; type: "header",
const imageElement = doc.selectFirst('div#_imageList'); name: "Filter Priority: Search > Ranking > Day > Genre | Sort applies to Day/Genre",
const img_urls = imageElement.select('img'); type_name: "HeaderFilter",
for (let i = 0; i < img_urls.length; i++) { },
urls.push(img_urls[i].attr("data-url")); {
} type: "separator",
return urls; type_name: "SeparatorFilter",
} },
{
type: "searchType",
name: "Search Type",
type_name: "SelectFilter",
values: [
{
type_name: "SelectOption",
name: "Originals",
value: "originals",
},
{
type_name: "SelectOption",
name: "Canvas",
value: "canvas",
},
],
state: 0,
},
{
type: "separator",
type_name: "SeparatorFilter",
},
{
type: "rankingType",
name: "Ranking Category",
type_name: "SelectFilter",
values: [
{
type_name: "SelectOption",
name: "Not Selected",
value: "",
},
{
type_name: "SelectOption",
name: "Trending",
value: "trending",
},
{
type_name: "SelectOption",
name: "Popular",
value: "popular",
},
{
type_name: "SelectOption",
name: "Originals",
value: "originals",
},
{
type_name: "SelectOption",
name: "Canvas",
value: "canvas",
},
],
},
{
type: "separator",
type_name: "SeparatorFilter",
},
getFilterList() { {
return [{ type: "sortOrder",
type: "sort", name: "Sort By (For Schedule & Genres)",
name: "Official or Challenge", type_name: "SelectFilter",
type_name: "SelectFilter", values: [
values: [{ { type_name: "SelectOption", name: "Popular (MANA)", value: "MANA" },
type_name: "SelectOption", { type_name: "SelectOption", name: "Likes", value: "LIKEIT" },
name: "Any", { type_name: "SelectOption", name: "Newest", value: "UPDATE" },
value: "any" ],
}, state: 0,
{ appliesTo: ["weekday", "genre"],
type_name: "SelectOption", },
name: "Official only",
value: "official"
},
{
type_name: "SelectOption",
name: "Challenge only",
value: "challenge"
}]
},
{
type: "categories",
name: "Genre",
type_name: "SelectFilter",
values: [{
type_name: "SelectOption",
name: "All",
value: ""
},
{
type_name: "SelectOption",
name: "Action",
value: "action"
},
{
type_name: "SelectOption",
name: "Comedy",
value: "comedy"
},
{
type_name: "SelectOption",
name: "Drama",
value: "drama"
},
{
type_name: "SelectOption",
name: "Fantasy",
value: "fantasy"
},
{
type_name: "SelectOption",
name: "Heartwarming",
value: "heartwarming"
},
{
type_name: "SelectOption",
name: "Historical",
value: "historical"
},
{
type_name: "SelectOption",
name: "Horror",
value: "horror"
},
{
type_name: "SelectOption",
name: "Informative",
value: "tiptoon"
},
{
type_name: "SelectOption",
name: "Mystery",
value: "mystery"
},
{
type_name: "SelectOption",
name: "Romance",
value: "romance"
},
{
type_name: "SelectOption",
name: "Sci-fi",
value: "sf"
},
{
type_name: "SelectOption",
name: "Slice of life",
value: "slice_of_life"
},
{
type_name: "SelectOption",
name: "Sports",
value: "sports"
},
{
type_name: "SelectOption",
name: "Superhero",
value: "super_hero"
},
{
type_name: "SelectOption",
name: "Supernatural",
value: "supernatural"
}]
}
];
}
{
type: "weekday",
name: "Update Schedule",
type_name: "SelectFilter",
values: [
{
type_name: "SelectOption",
name: "Day",
value: "",
data: "",
},
{
type_name: "SelectOption",
name: "Monday",
value: "monday",
data: "MONDAY",
},
{
type_name: "SelectOption",
name: "Tuesday",
value: "tuesday",
data: "TUESDAY",
},
{
type_name: "SelectOption",
name: "Wednesday",
value: "wednesday",
data: "WEDNESDAY",
},
{
type_name: "SelectOption",
name: "Thursday",
value: "thursday",
data: "THURSDAY",
},
{
type_name: "SelectOption",
name: "Friday",
value: "friday",
data: "FRIDAY",
},
{
type_name: "SelectOption",
name: "Saturday",
value: "saturday",
data: "SATURDAY",
},
{
type_name: "SelectOption",
name: "Sunday",
value: "sunday",
data: "SUNDAY",
},
{
type_name: "SelectOption",
name: "Completed",
value: "complete",
data: "COMPLETE",
},
],
},
{
type: "genre",
name: "Genre",
type_name: "SelectFilter",
values: [
{
type_name: "SelectOption",
name: "All Genres",
value: "",
data: "",
},
{
type_name: "SelectOption",
name: "Drama",
value: "drama",
data: "DRAMA",
},
{
type_name: "SelectOption",
name: "Fantasy",
value: "fantasy",
data: "FANTASY",
},
{
type_name: "SelectOption",
name: "Comedy",
value: "comedy",
data: "COMEDY",
},
{
type_name: "SelectOption",
name: "Action",
value: "action",
data: "ACTION",
},
{
type_name: "SelectOption",
name: "Slice of Life",
value: "slice_of_life",
data: "SLICE_OF_LIFE",
},
{
type_name: "SelectOption",
name: "Romance",
value: "romance",
data: "ROMANCE",
},
{
type_name: "SelectOption",
name: "Superhero",
value: "super_hero",
data: "SUPER_HERO",
},
{
type_name: "SelectOption",
name: "Sci-Fi",
value: "sf",
data: "SF",
},
{
type_name: "SelectOption",
name: "Thriller",
value: "thriller",
data: "THRILLER",
},
{
type_name: "SelectOption",
name: "Supernatural",
value: "supernatural",
data: "SUPERNATURAL",
},
{
type_name: "SelectOption",
name: "Mystery",
value: "mystery",
data: "MYSTERY",
},
{
type_name: "SelectOption",
name: "Sports",
value: "sports",
data: "SPORTS",
},
{
type_name: "SelectOption",
name: "Historical",
value: "historical",
data: "HISTORICAL",
},
{
type_name: "SelectOption",
name: "Heartwarming",
value: "heartwarming",
data: "HEARTWARMING",
},
{
type_name: "SelectOption",
name: "Horror",
value: "horror",
data: "HORROR",
},
{
type_name: "SelectOption",
name: "Graphic Novel",
value: "graphic_novel",
data: "GRAPHIC_NOVEL",
},
{
type_name: "SelectOption",
name: "Informative",
value: "tiptoon",
data: "TIPTOON",
},
],
},
];
}
} }