import 'package:mangayomi/bridge_lib.dart'; import 'dart:convert'; class MangaBox extends MProvider { MangaBox(); final Client client = Client(); @override Future getPopular(MSource source, int page) async { final res = (await client.get(Uri.parse( "${source.baseUrl}/${popularUrlPath(source.name, page)}"))) .body; return mangaRes(res); } @override Future getLatestUpdates(MSource source, int page) async { final res = (await client.get( Uri.parse("${source.baseUrl}/${latestUrlPath(source.name, page)}"))) .body; return mangaRes(res); } @override Future search( MSource source, String query, int page, FilterList filterList) async { final filters = filterList.filters; String url = ""; if (query.isNotEmpty && (source.name != "Manganato" && source.name != "Mangabat")) { url = "${source.baseUrl}/${simpleQueryPath(source.name, page, query)}"; } else { url = source.baseUrl; if (source.name == "Manganato" || source.name == "Mangabat") { url += "/advanced_search?page=$page&keyw=${normalizeSearchQuery(query)}"; String genreInclude = ""; String genreExclude = ""; for (var filter in filters) { if (filter.type == "KeywordFilter") { final key = filter.values[filter.state].value; url += "${ll(url)}keyt=$key"; } else if (filter.type == "SortFilter") { final sort = filter.values[filter.state].value; url += "${ll(url)}orby=$sort"; } else if (filter.type == "StatusFilter") { final status = filter.values[filter.state].value; url += "${ll(url)}sts=$status"; } else if (filter.type == "GenreListFilter") { final included = (filter.state as List) .where((e) => e.state == 1 ? true : false) .toList(); final excluded = (filter.state as List) .where((e) => e.state == 2 ? true : false) .toList(); if (included.isNotEmpty) { for (var val in included) { genreInclude += "_${val.value}"; } } if (excluded.isNotEmpty) { for (var val in excluded) { genreExclude += "_${val.value}"; } } } } url += "${ll(url)}g_i=$genreInclude"; url += "${ll(url)}g_e=$genreExclude"; } else { for (var filter in filters) { if (filter.type == "SortFilter") { final sort = filter.values[filter.state].value; url += "${ll(url)}type=$sort"; } else if (filter.type == "StatusFilter") { final status = filter.values[filter.state].value; url += "${ll(url)}state=$status"; } else if (filter.type == "GenreListFilter") { final genre = filter.values[filter.state].value; url += "${ll(url)}category=$genre"; } } } } final res = (await client.get(Uri.parse(url))).body; List mangaList = []; List 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 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 getDetail(MSource source, String url) async { final statusList = [ {"Ongoing": 0, "Completed": 1} ]; MManga manga = MManga(); final res = (await client.get(Uri.parse(url))).body; List author = xpath(res, '//*[@class="table-label" and contains(text(), "Author")]/parent::tr/td[2]/text()'); if (author.isEmpty) { author = xpath(res, '//li[contains(text(), "Author")]/a/text()'); } if (author.isNotEmpty) { manga.author = author.first; } final alternative = xpath(res, '//*[@class="table-label" and contains(text(), "Alternative")]/parent::tr/td[2]/text()'); if (author.isNotEmpty) { manga.author = author.first; } List description = xpath(res, '//*[@id="panel-story-info-description" ]/text()'); if (description.isEmpty) { description = xpath(res, '//*[@id="story_discription" ]/text()'); } if (description.isNotEmpty) { manga.description = description.first.replaceAll("\n", ' '); if (alternative.isNotEmpty) { manga.description = "${manga.description}\n\nAlternative Name: ${alternative.first}"; } } List status = xpath( res, '//*[@class="table-label" and contains(text(), "Status")]/parent::tr/td[2]/text()', ''); if (status.isEmpty) { status = xpath(res, '//li[contains(text(), "Status")]/a/text()', ''); } if (status.isNotEmpty) { manga.status = parseStatus(status.first, statusList); } manga.genre = xpath(res, '//*[@class="table-label" and contains(text(), "Genres")]/parent::tr/td[2]/a/text()'); if (manga.genre.isEmpty) { manga.genre = xpath(res, '//li[contains(text(), "Genres")]/a/text()'); } List chapUrls = xpath(res, '//*[@class="row-content-chapter"]/li/a/@href'); if (chapUrls.isEmpty) { chapUrls = xpath(res, '//div[@id="chapter_list"]/ul/li/a/@href'); } List chaptersNames = xpath(res, '//*[@class="row-content-chapter"]/li/a/text()'); if (chaptersNames.isEmpty) { chaptersNames = xpath(res, '//div[@id="chapter_list"]/ul/li/a/text()'); } List chapterDates = xpath(res, '//*[@class="row-content-chapter"]/li/span[last()]/text()'); if (chapterDates.isEmpty) { chapterDates = xpath(res, '//div[@id="chapter_list"]/ul/li/p/text()'); } List dateUploads = parseDates(chapterDates, source.dateFormat, source.dateFormatLocale); List? chaptersList = []; for (var i = 0; i < chaptersNames.length; i++) { MChapter chapter = MChapter(); chapter.name = chaptersNames[i]; chapter.url = chapUrls[i]; chapter.dateUpload = dateUploads[i]; chaptersList.add(chapter); } manga.chapters = chaptersList; return manga; } @override Future> getPageList(MSource source, String url) async { final res = (await client.get(Uri.parse(url))).body; List 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 mangaList = []; List urls = []; urls = xpath(res, '//*[ @class^="genres-item" or @class="list-truyen-item-wrap" or @class="story-item"]/h3/a/@href'); List 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 == "Manganato") { return "genre-all/$page?type=topview"; } else if (sourceName == "Mangabat") { return "manga-list-all/$page?type=topview"; } else if (sourceName == "Mangairo") { return "manga-list/type-topview/ctg-all/state-all/page-$page"; } return "manga_list?type=topview&category=all&state=all&page=$page"; } String latestUrlPath(String sourceName, int page) { if (sourceName == "Manganato") { return "genre-all/$page"; } else if (sourceName == "Mangabat") { return "manga-list-all/$page"; } else if (sourceName == "Mangairo") { return "manga-list/type-latest/ctg-all/state-all/page-$page"; } return "manga_list?type=latest&category=all&state=all&page=$page"; } String simpleQueryPath(String sourceName, int page, String query) { if (sourceName == "Mangakakalot") { return "search/story/${normalizeSearchQuery(query)}?page=$page"; } else if (sourceName == "Mangairo") { return "list/search/${normalizeSearchQuery(query)}?page=$page"; } else if (sourceName == "Mangabat") { return "search/manga/${normalizeSearchQuery(query)}?page=$page"; } else if (sourceName == "Manganato") { return "search/story/${normalizeSearchQuery(query)}?page=$page"; } return "search/${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 getFilterList(MSource source) { if (source.name == "Mangairo") { return []; } return [ SelectFilter("KeywordFilter", "Keyword search:", 0, [ SelectFilterOption("Everything", ""), SelectFilterOption("Title", "title"), SelectFilterOption("Alt title", "alternative"), SelectFilterOption("Author", "author"), ]), 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"), ]), if (source.name == "Manganato" || source.name == "Mangabat") GroupFilter("GenreListFilter", "Category:", [ TriStateFilter("Action", "2"), TriStateFilter("Adult", "3"), TriStateFilter("Adventure", "4"), TriStateFilter("Comedy", "6"), TriStateFilter("Cooking", "7"), TriStateFilter("Doujinshi", "9"), TriStateFilter("Drama", "10"), TriStateFilter("Ecchi", "11"), TriStateFilter("Fantasy", "12"), TriStateFilter("Gender bender", "13"), TriStateFilter("Harem", "14"), TriStateFilter("Historical", "15"), TriStateFilter("Horror", "16"), TriStateFilter("Isekai", "45"), TriStateFilter("Josei", "17"), TriStateFilter("Manhua", "44"), TriStateFilter("Manhwa", "43"), TriStateFilter("Martial arts", "19"), TriStateFilter("Mature", "20"), TriStateFilter("Mecha", "21"), TriStateFilter("Medical", "22"), TriStateFilter("Mystery", "24"), TriStateFilter("One shot", "25"), TriStateFilter("Psychological", "26"), TriStateFilter("Romance", "27"), TriStateFilter("School life", "28"), TriStateFilter("Sci fi", "29"), TriStateFilter("Seinen", "30"), TriStateFilter("Shoujo", "31"), TriStateFilter("Shoujo ai", "32"), TriStateFilter("Shounen", "33"), TriStateFilter("Shounen ai", "34"), TriStateFilter("Slice of life", "35"), TriStateFilter("Smut", "36"), TriStateFilter("Sports", "37"), TriStateFilter("Supernatural", "38"), TriStateFilter("Tragedy", "39"), TriStateFilter("Webtoons", "40"), TriStateFilter("Yaoi", "41"), TriStateFilter("Yuri", "42"), ]), if (source.name != "Manganato" && source.name != "Mangabat") SelectFilter("GenreListFilter", "Category:", 0, [ SelectFilterOption("ALL", "all"), SelectFilterOption("Action", "2"), SelectFilterOption("Adult", "3"), SelectFilterOption("Adventure", "4"), SelectFilterOption("Comedy", "6"), SelectFilterOption("Cooking", "7"), SelectFilterOption("Doujinshi", "9"), SelectFilterOption("Drama", "10"), SelectFilterOption("Ecchi", "11"), SelectFilterOption("Fantasy", "12"), SelectFilterOption("Gender bender", "13"), SelectFilterOption("Harem", "14"), SelectFilterOption("Historical", "15"), SelectFilterOption("Horror", "16"), SelectFilterOption("Isekai", "45"), SelectFilterOption("Josei", "17"), SelectFilterOption("Manhua", "44"), SelectFilterOption("Manhwa", "43"), SelectFilterOption("Martial arts", "19"), SelectFilterOption("Mature", "20"), SelectFilterOption("Mecha", "21"), SelectFilterOption("Medical", "22"), SelectFilterOption("Mystery", "24"), SelectFilterOption("One shot", "25"), SelectFilterOption("Psychological", "26"), SelectFilterOption("Romance", "27"), SelectFilterOption("School life", "28"), SelectFilterOption("Sci fi", "29"), SelectFilterOption("Seinen", "30"), SelectFilterOption("Shoujo", "31"), SelectFilterOption("Shoujo ai", "32"), SelectFilterOption("Shounen", "33"), SelectFilterOption("Shounen ai", "34"), SelectFilterOption("Slice of life", "35"), SelectFilterOption("Smut", "36"), SelectFilterOption("Sports", "37"), SelectFilterOption("Supernatural", "38"), SelectFilterOption("Tragedy", "39"), SelectFilterOption("Webtoons", "40"), SelectFilterOption("Yaoi", "41"), SelectFilterOption("Yuri", "42"), ]), ]; } String ll(String url) { if (url.contains("?")) { return "&"; } return "?"; } } Map getHeader(String url) { final headers = {'referer': '$url/'}; return headers; } MangaBox main() { return MangaBox(); }