mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 10:51:17 +00:00
635 lines
15 KiB
JavaScript
635 lines
15 KiB
JavaScript
// prettier-ignore
|
|
const mangayomiSources = [{
|
|
"name": "Webtoons",
|
|
"langs": ["en", "fr", "id", "th", "es", "zh", "de"],
|
|
"baseUrl": "https://www.webtoons.com",
|
|
"apiUrl": "",
|
|
"iconUrl": "https://upload.wikimedia.org/wikipedia/commons/0/09/Naver_Line_Webtoon_logo.png",
|
|
"typeSource": "single",
|
|
"isManga": true,
|
|
"isNsfw": false,
|
|
"version": "0.0.45",
|
|
"dateFormat": "",
|
|
"dateFormatLocale": "",
|
|
"pkgPath": "manga/src/all/webtoons.js"
|
|
}];
|
|
|
|
class DefaultExtension extends MProvider {
|
|
headers = {
|
|
"User-Agent":
|
|
"Mozilla/5.0 (Linux; Android 13; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36",
|
|
};
|
|
|
|
mobileUrl = "https://m.webtoons.com";
|
|
|
|
getFormattedUrl(preferenceKey) {
|
|
const preference = new SharedPreferences();
|
|
let url = preference.get(preferenceKey) || this.source.baseUrl;
|
|
|
|
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
}
|
|
|
|
getBaseUrl() {
|
|
return this.getFormattedUrl("domain_url");
|
|
}
|
|
|
|
getMobileUrl() {
|
|
return this.getFormattedUrl("mobile_url");
|
|
}
|
|
|
|
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 });
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
async getPopular(page) {
|
|
const res = await new Client().get(
|
|
`${this.getBaseUrl()}/${this.langCode()}/originals`,
|
|
);
|
|
const doc = new Document(res.body);
|
|
|
|
return {
|
|
list: this.mangaFromElement(doc),
|
|
hasNextPage: false,
|
|
};
|
|
}
|
|
|
|
async getLatestUpdates(page) {
|
|
const res = await new Client().get(
|
|
`${this.getBaseUrl()}/${this.langCode()}/originals?sortOrder=UPDATE`,
|
|
);
|
|
const doc = new Document(res.body);
|
|
|
|
return {
|
|
list: this.mangaFromElement(doc),
|
|
hasNextPage: false,
|
|
};
|
|
}
|
|
|
|
async search(query, page, filters) {
|
|
const keyword = query.trim().replace(/\s+/g, "+");
|
|
let url = `${this.getBaseUrl()}/${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}`;
|
|
}
|
|
}
|
|
|
|
const res = await new Client().get(url);
|
|
const doc = new Document(res.body);
|
|
const list = this.mangaFromElement(doc);
|
|
if (query) {
|
|
hasNextPage = list.length !== 0;
|
|
}
|
|
|
|
return {
|
|
list,
|
|
hasNextPage,
|
|
};
|
|
}
|
|
|
|
async getDetail(url) {
|
|
let res = await new Client().get(url);
|
|
let doc = new Document(res.body);
|
|
|
|
const info = doc.selectFirst("div.cont_box");
|
|
const name = info.selectFirst("h1.subj, h3.subj").text;
|
|
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 dayInfoText = info?.selectFirst("p.day_info")?.text || "";
|
|
const status =
|
|
dayInfoText.includes("UP") ||
|
|
dayInfoText.includes("EVERY") ||
|
|
dayInfoText.includes("NOUVEAU")
|
|
? 0
|
|
: dayInfoText.includes("END") ||
|
|
dayInfoText.includes("TERMINÉ") ||
|
|
dayInfoText.includes("COMPLETED")
|
|
? 1
|
|
: -1; // UNKNOWN
|
|
|
|
const description = info
|
|
.selectFirst("p.summary")
|
|
.text.replace(/\s+/g, " ")
|
|
.trim();
|
|
|
|
// chapters
|
|
const chapters = [];
|
|
res = await new Client().get(
|
|
url.replace(this.getBaseUrl(), this.getMobileUrl()),
|
|
this.headers,
|
|
);
|
|
doc = new Document(res.body);
|
|
for (const el of doc.select("ul#_episodeList li[id*=episode] a")) {
|
|
const url = el.getHref.replace(this.getMobileUrl(), this.getBaseUrl());
|
|
let name = el.selectFirst(".sub_title > span.ellipsis")?.text;
|
|
const chapterElement = el.selectFirst("div.row > div.num");
|
|
if (chapterElement) {
|
|
const chapterText = chapterElement.text;
|
|
const hashIndex = chapterText.indexOf("#");
|
|
if (hashIndex > -1) {
|
|
name += " Ch. " + chapterText.substring(hashIndex + 1);
|
|
}
|
|
}
|
|
const dateUpload = new Date(
|
|
this.formatDateString(
|
|
el.selectFirst(".sub_info .date")?.text,
|
|
this.source.lang,
|
|
),
|
|
)
|
|
.getTime()
|
|
.toString();
|
|
|
|
chapters.push({
|
|
name,
|
|
url,
|
|
dateUpload,
|
|
});
|
|
}
|
|
|
|
return {
|
|
name,
|
|
link: url,
|
|
genre,
|
|
description,
|
|
author,
|
|
status,
|
|
episodes: chapters,
|
|
};
|
|
}
|
|
|
|
langCode() {
|
|
return {
|
|
en: "en",
|
|
fr: "fr",
|
|
id: "id",
|
|
th: "th",
|
|
es: "es",
|
|
zh: "zh-hant",
|
|
de: "de",
|
|
}[this.source.lang];
|
|
}
|
|
|
|
formatDateString(dateStr, lang) {
|
|
// 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];
|
|
}
|
|
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")}`;
|
|
}
|
|
|
|
async getPageList(url) {
|
|
const res = await new Client().get(url);
|
|
const doc = new Document(res.body);
|
|
const urls = [];
|
|
const imageElement = doc.selectFirst("div#_imageList");
|
|
const img_urls = imageElement.select("img");
|
|
for (let i = 0; i < img_urls.length; i++) {
|
|
urls.push(img_urls[i].attr("data-url"));
|
|
}
|
|
return urls;
|
|
}
|
|
|
|
getFilterList() {
|
|
return [
|
|
{
|
|
type: "header",
|
|
name: "Filter Priority: Search > Ranking > Day > Genre | Sort applies to Day/Genre",
|
|
type_name: "HeaderFilter",
|
|
},
|
|
{
|
|
type: "separator",
|
|
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",
|
|
},
|
|
|
|
{
|
|
type: "sortOrder",
|
|
name: "Sort By (For Schedule & Genres)",
|
|
type_name: "SelectFilter",
|
|
values: [
|
|
{ type_name: "SelectOption", name: "Popular (MANA)", value: "MANA" },
|
|
{ type_name: "SelectOption", name: "Likes", value: "LIKEIT" },
|
|
{ type_name: "SelectOption", name: "Newest", value: "UPDATE" },
|
|
],
|
|
state: 0,
|
|
appliesTo: ["weekday", "genre"],
|
|
},
|
|
|
|
{
|
|
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",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
// Preferences
|
|
getSourcePreferences() {
|
|
return [
|
|
{
|
|
key: "domain_url",
|
|
editTextPreference: {
|
|
title: "Override BaseUrl",
|
|
summary: "",
|
|
value: this.source.baseUrl,
|
|
dialogTitle: "URL",
|
|
dialogMessage: "",
|
|
},
|
|
},
|
|
{
|
|
key: "mobile_url",
|
|
editTextPreference: {
|
|
title: "Override mobileUrl",
|
|
summary: "",
|
|
value: this.mobileUrl,
|
|
dialogTitle: "URL",
|
|
dialogMessage: "",
|
|
},
|
|
},
|
|
];
|
|
}
|
|
}
|