Files
kodjodevf-mangayomi-extensions/dart/manga/multisrc/mangabox/mangabox.dart
Moustapha Kodjo Amadou 1facf4bb1f Update and refactor
2025-05-10 11:47:34 +01:00

379 lines
12 KiB
Dart

import 'package:mangayomi/bridge_lib.dart';
class MangaBox extends MProvider {
MangaBox({required this.source});
MSource source;
final Client client = Client();
@override
Future<MPages> getPopular(int page) async {
final res =
(await client.get(
Uri.parse("${source.baseUrl}/${popularUrlPath(source.name, page)}"),
headers: getHeader(source.baseUrl),
)).body;
return mangaRes(res);
}
@override
Future<MPages> getLatestUpdates(int page) async {
final res =
(await client.get(
Uri.parse("${source.baseUrl}/${latestUrlPath(source.name, page)}"),
headers: getHeader(source.baseUrl),
)).body;
return mangaRes(res);
}
@override
Future<MPages> search(String query, int page, FilterList filterList) async {
final filters = filterList.filters;
String url = "";
if (query.isNotEmpty) {
url = "${source.baseUrl}/${simpleQueryPath(source.name, page, query)}";
} else {
String genre = "all";
String sort = "latest";
String status = "all";
for (var filter in filters) {
if (filter.type == "GenreListFilter") {
genre = filter.values[filter.state].value;
} else if (filter.type == "SortFilter") {
sort = filter.values[filter.state].value;
} else if (filter.type == "StatusFilter") {
status = filter.values[filter.state].value;
}
}
url =
"${source.baseUrl}/genre/$genre?type=$sort&state=$status&page=$page";
}
final res =
(await client.get(
Uri.parse(url),
headers: getHeader(source.baseUrl),
)).body;
List<MManga> mangaList = [];
List<String> urls = [];
urls = xpath(
res,
'//*[ @class^="genres-item" or @class="list-truyen-item-wrap" or @class="story-item" or @class="story_item_right"]/h3/a/@href',
);
List<String> names = [];
names = xpath(
res,
'//*[ @class^="genres-item" or @class="list-truyen-item-wrap" or @class="story-item" or @class="story_item_right"]/h3/a/text()',
);
final images = xpath(
res,
'//*[@class="search-story-item" or @class="story_item" or @class="content-genres-item" or @class="list-story-item" or @class="story-item" or @class="list-truyen-item-wrap"]/a/img/@src',
);
if (names.isEmpty) {
urls = xpath(
res,
'//*[ @class^="genres-item" or @class="list-truyen-item-wrap" or @class="story-item"]/h2/a/@href',
);
names = xpath(
res,
'//*[ @class^="genres-item" or @class="list-truyen-item-wrap" or @class="story-item"]/h2/a/text()',
);
}
if (names.isEmpty) {
names = xpath(
res,
'//*[@class="search-story-item" or @class="list-story-item"]/a/@title',
);
urls = xpath(
res,
'//*[@class="search-story-item" or @class="list-story-item"]/a/@href',
);
}
for (var i = 0; i < names.length; i++) {
MManga manga = MManga();
manga.name = names[i];
manga.imageUrl = images[i];
manga.link = urls[i];
mangaList.add(manga);
}
return MPages(mangaList, true);
}
@override
Future<MManga> getDetail(String url) async {
final statusList = [
{"Ongoing": 0, "Completed": 1},
];
MManga manga = MManga();
final res =
(await client.get(
Uri.parse(url),
headers: getHeader(source.baseUrl),
)).body;
final document = parseHtml(res);
manga.author =
document.xpathFirst(
'//*[@class="table-label" and contains(text(), "Author")]/parent::tr/td[2]/text()|//li[contains(text(), "Author")]/a/text()',
) ??
"";
final alternative =
document.xpathFirst(
'//*[@class="table-label" and contains(text(), "Alternative")]/parent::tr/td[2]/text()',
) ??
"";
final description =
document.xpathFirst(
'//*[@id="contentBox" ]/text() | //*[@id="story_discription" ]/text() | //div[@id="noidungm"]/text()',
) ??
"";
if (description.isNotEmpty) {
manga.description = description
.split("summary:", ' ')
.last
.split("Summary:", ' ')
.last
.replaceAll("\n", ' ')
.replaceAll("Description :", "");
if (alternative.isNotEmpty) {
manga.description =
"${manga.description}\n\nAlternative Name: $alternative";
}
}
final status =
document.xpathFirst(
'//*[@class="table-label" and contains(text(), "Status")]/parent::tr/td[2]/text() | //li[contains(text(), "Status")]/text() | //li[contains(text(), "Status")]/a/text()',
) ??
"";
if (status.isNotEmpty) {
manga.status = parseStatus(status.split(":").last.trim(), statusList);
}
manga.genre = document.xpath(
'//*[@class="table-label" and contains(text(), "Genres")]/parent::tr/td[2]/a/text() | //li[contains(text(), "Genres")]/a/text()',
);
final chaptersElements = document.select(
"div.chapter-list div.row, ul.row-content-chapter li, div#chapter_list li",
);
List<MChapter>? chaptersList = [];
for (var element in chaptersElements) {
final a = element.selectFirst("a");
MChapter chapter = MChapter();
chapter.name = a.text;
final dates = element.select("span");
String dateStr = "";
if (dates != null && dates.isNotEmpty) {
dateStr = dates.last.text;
} else {
dateStr = element.selectFirst("p")?.text ?? "";
}
chapter.url = a.getHref;
chapter.dateUpload =
dateStr.isEmpty
? DateTime.now().millisecondsSinceEpoch.toString()
: parseDates(
[dateStr],
source.dateFormat,
source.dateFormatLocale,
)[0];
chaptersList.add(chapter);
}
manga.chapters = chaptersList;
return manga;
}
@override
Future<List<String>> getPageList(String url) async {
final res =
(await client.get(
Uri.parse(url),
headers: getHeader(source.baseUrl),
)).body;
List<String> pageUrls = [];
final urls = xpath(
res,
'//div[@class="container-chapter-reader" or @class="panel-read-story"]/img/@src',
);
for (var url in urls) {
if (url.startsWith("https://convert_image_digi.mgicdn.com")) {
pageUrls.add(
"https://images.weserv.nl/?url=${substringAfter(url, "//")}",
);
} else {
pageUrls.add(url);
}
}
return pageUrls;
}
MPages mangaRes(String res) {
List<MManga> mangaList = [];
List<String> urls = [];
urls = xpath(
res,
'//*[ @class^="genres-item" or @class="list-truyen-item-wrap" or @class="story-item"]/h3/a/@href',
);
List<String> names = [];
names = xpath(
res,
'//*[ @class^="genres-item" or @class="list-truyen-item-wrap" or @class="story-item"]/h3/a/text()',
);
final images = xpath(
res,
'//*[ @class="content-genres-item" or @class="list-story-item" or @class="story-item" or @class="list-truyen-item-wrap"]/a/img/@src',
);
if (names.isEmpty) {
names = xpath(res, '//*[@class="list-story-item"]/a/@title');
urls = xpath(res, '//*[@class="list-story-item"]/a/@href');
}
for (var i = 0; i < names.length; i++) {
MManga manga = MManga();
manga.name = names[i];
manga.imageUrl = images[i];
manga.link = urls[i];
mangaList.add(manga);
}
return MPages(mangaList, true);
}
String popularUrlPath(String sourceName, int page) {
if (sourceName != "Mangairo") {
return "manga-list/hot-manga?page=$page";
} else {
return "manga-list/type-topview/ctg-all/state-all/page-$page";
}
}
String latestUrlPath(String sourceName, int page) {
if (sourceName != "Mangairo") {
return "manga-list/latest-manga?page=$page";
} else {
return "manga-list/type-latest/ctg-all/state-all/page-$page";
}
}
String simpleQueryPath(String sourceName, int page, String query) {
if (sourceName == "Mangairo") {
return "list/search/${normalizeSearchQuery(query)}?page=$page";
} else {
return "search/story/${normalizeSearchQuery(query)}?page=$page";
}
}
String normalizeSearchQuery(String query) {
String str = query.toLowerCase();
str = str.replaceAll(RegExp(r'[àáạảãâầấậẩẫăằắặẳẵ]'), 'a');
str = str.replaceAll(RegExp(r'[èéẹẻẽêềếệểễ]'), 'e');
str = str.replaceAll(RegExp(r'[ìíịỉĩ]'), 'i');
str = str.replaceAll(RegExp(r'[òóọỏõôồốộổỗơờớợởỡ]'), 'o');
str = str.replaceAll(RegExp(r'[ùúụủũưừứựửữ]'), 'u');
str = str.replaceAll(RegExp(r'[ỳýỵỷỹ]'), 'y');
str = str.replaceAll(RegExp(r'đ'), 'd');
str = str.replaceAll(
RegExp(
r"""!|@|%|\^|\*|\(|\)|\+|=|<|>|\?|/|,|\.|:|;|'| |"|&|#|\[|]|~|-|$|_""",
),
"_",
);
str = str.replaceAll(RegExp(r'_+'), '_');
str = str.replaceAll(RegExp(r'^_+|_+$'), '');
return str;
}
@override
List<dynamic> getFilterList() {
if (source.name == "Mangairo") {
return [];
}
return [
HeaderFilter("NOTE: The filter is ignored when using text search."),
SelectFilter("SortFilter", "Order by:", 0, [
SelectFilterOption("Latest", "latest"),
SelectFilterOption("Newest", "newest"),
SelectFilterOption("Top read", "topview"),
]),
SelectFilter("StatusFilter", "Status:", 0, [
SelectFilterOption("ALL", "all"),
SelectFilterOption("Completed", "completed"),
SelectFilterOption("Ongoing", "ongoing"),
SelectFilterOption("Dropped", "drop"),
]),
SelectFilter("GenreListFilter", "Category:", 0, [
SelectFilterOption("ALL", "all"),
SelectFilterOption("Action", "action"),
SelectFilterOption("Adult", "adult"),
SelectFilterOption("Adventure", "adventure"),
SelectFilterOption("Comedy", "comedy"),
SelectFilterOption("Cooking", "cooking"),
SelectFilterOption("Doujinshi", "doujinshi"),
SelectFilterOption("Drama", "drama"),
SelectFilterOption("Ecchi", "ecchi"),
SelectFilterOption("Fantasy", "fantasy"),
SelectFilterOption("Gender Bender", "gender-bender"),
SelectFilterOption("Harem", "harem"),
SelectFilterOption("Historical", "historical"),
SelectFilterOption("Horror", "horror"),
SelectFilterOption("Isekai", "isekai"),
SelectFilterOption("Josei", "josei"),
SelectFilterOption("Manhua", "manhua"),
SelectFilterOption("Manhwa", "manhwa"),
SelectFilterOption("Martial arts", "martial-arts"),
SelectFilterOption("Mature", "mature"),
SelectFilterOption("Mecha", "mecha"),
SelectFilterOption("Medical", "medical"),
SelectFilterOption("Mystery", "mystery"),
SelectFilterOption("One shot", "one-shot"),
SelectFilterOption("Psychological", "psychological"),
SelectFilterOption("Reincarnation", "reincarnation"),
SelectFilterOption("Romance", "romance"),
SelectFilterOption("School life", "school-life"),
SelectFilterOption("Sci fi", "sci-fi"),
SelectFilterOption("Seinen", "seinen"),
SelectFilterOption("Shoujo", "shoujo"),
SelectFilterOption("Shoujo ai", "shoujo-ai"),
SelectFilterOption("Shounen", "shounen"),
SelectFilterOption("Shounen ai", "shounen-ai"),
SelectFilterOption("Slice of life", "slice-of-life"),
SelectFilterOption("Smut", "smut"),
SelectFilterOption("Sports", "sports"),
SelectFilterOption("Supernatural", "supernatural"),
SelectFilterOption("Survival", "survival"),
SelectFilterOption("System", "system"),
SelectFilterOption("Thriller", "thriller"),
SelectFilterOption("Tragedy", "tragedy"),
SelectFilterOption("Webtoons", "webtoons"),
SelectFilterOption("Yaoi", "yaoi"),
SelectFilterOption("Yuri", "yuri"),
]),
];
}
String ll(String url) {
if (url.contains("?")) {
return "&";
}
return "?";
}
}
Map<String, String> getHeader(String url) {
final Map<String, String> headers = {
"Referer": "$url/",
"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",
};
return headers;
}
MangaBox main(MSource source) {
return MangaBox(source: source);
}