diff --git a/dart/manga/manga_source_list.dart b/dart/manga/manga_source_list.dart index 59f16583..4bd6058f 100644 --- a/dart/manga/manga_source_list.dart +++ b/dart/manga/manga_source_list.dart @@ -5,12 +5,14 @@ import 'multisrc/mangareader/sources.dart'; import 'multisrc/mmrcms/sources.dart'; import 'multisrc/nepnep/sources.dart'; import 'src/en/mangahere/source.dart'; +import 'src/en/mangapark/source.dart'; List dartMangasourceList = [ ...madaraSourcesList, ...mangareaderSourcesList, ...mmrcmsSourcesList, mangahereSource, + mangaparkSource, ...nepnepSourcesList, ...mangaboxSourcesList, ]; diff --git a/dart/manga/src/en/mangapark/icon.png b/dart/manga/src/en/mangapark/icon.png new file mode 100644 index 00000000..09ba83a5 Binary files /dev/null and b/dart/manga/src/en/mangapark/icon.png differ diff --git a/dart/manga/src/en/mangapark/mangapark.dart b/dart/manga/src/en/mangapark/mangapark.dart new file mode 100644 index 00000000..13956be4 --- /dev/null +++ b/dart/manga/src/en/mangapark/mangapark.dart @@ -0,0 +1,540 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class MangaPark extends MProvider { + MangaPark({required this.source}); + + MSource source; + + final Client client = Client(source); + + @override + Future getMangaItems(int page, final method) async { + List mangaList = []; + + + final res = await client.post( + Uri.parse("${source.apiUrl}"), + headers: { + "Accept": "*/*", + "accept-language": "en-US,en;q=0.9,de;q=0.8,ja;q=0.7,es;q=0.6,nl;q=0.5", + "content-type": "application/json", + "referer": "https://mangapark.io/", + "cookie": "nsfw=${preferenceNsfwContent()}; imgser=${preferenceImgServer()}; wd=2504x735" + }, + body: jsonEncode({ + "query": """ + query get_latestReleases(\$select: LatestReleases_Select) { + get_latestReleases(select: \$select) { + paging { + total pages page init size skip limit prev next + } + items { + id + data { + id dbStatus name origLang tranLang sfw_result + urlPath urlCover600 urlCoverOri + is_hot is_new follows + last_chapterNodes(amount: 1) { + id + data { + id dateCreate dbStatus isFinal dname urlPath is_new + } + } + } + sser_follow + } + } + } + """, + "variables": { + "select": { + "where": method, + "init": 24, + "size": 24, + "page": page + } + } + })); + + final json = jsonDecode(res.body); + final data = json["data"]["get_latestReleases"]; + final items = data["items"]; + final bool isNextPage = data["paging"]["pages"] > page; + + for (var item in items) { + final mangaData = item["data"]; + MManga manga = MManga(); + manga.name = mangaData["name"]; + manga.imageUrl = "${source.baseUrl}${mangaData["urlCover600"]}"; + manga.link = "${source.baseUrl}${mangaData["urlPath"]}"; + mangaList.add(manga); + } + + return MPages(mangaList, isNextPage); + } + + @override + Future getPopular(int page) async { + return getMangaItems(page, "popular"); + } + + @override + Future getLatestUpdates(int page) async { + return getMangaItems(page, "release"); + } + + @override + Future search(String query, int page, FilterList filterList) async { + List mangaList = []; + final filters = filterList.filters; + String url = ""; + String genres = "&genres="; + String translFrom = "&orig="; + String translTo = "&lang="; + + + for (var filter in filters) { + if (filter.type == "SearchFilter") { + if (filter.state.toString() != "") { + query = filter.state.toString(); + } + } else if (filter.type == "GenresFilter") { + for (int i = 0; i < filter.state.length; i++) { + if (filter.state[i].state.toString() == "2") { + genres += "|${filter.state[i].value.toString()},"; + } else if (filter.state[i].state.toString() == "1") { + genres += "${filter.state[i].value.toString()},"; + } + } + } else if (filter.type == "TranslatedFromFilter") { + for (int i = 0; i < filter.state.length; i++) { + if (filter.state[i].state) { + translFrom += "${filter.state[i].value.toString()},"; + } + } + } else if (filter.type == "TranslatedToFilter") { + for (int i = 0; i < filter.state.length; i++) { + if (filter.state[i].state) { + translTo += "${filter.state[i].value.toString()},"; + } + } + } else if (filter.type == "OrigWorkFilter") { + url += "&status=${filter.values[filter.state].value.toString()}"; + } else if (filter.type == "MparkUplFilter") { + url += "&upload=${filter.values[filter.state].value.toString()}"; + } else if (filter.type == "ChapCountFilter") { + url += "&chapters=${filter.values[filter.state].value.toString()}"; + } else if (filter.type == "SortFilter") { + url += "&sortby=${filter.values[filter.state].value.toString()}"; + } + } + url += genres + translFrom + translTo; + + final res = await client.get(Uri.parse("${source.baseUrl}/search?word=${Uri.encodeComponent(query.trim())}&page=$page$url"), + headers: { + "Accept": "*/*", + "accept-language": "en-US,en;q=0.9,de;q=0.8,ja;q=0.7,es;q=0.6,nl;q=0.5", + "referer": "https://mangapark.io/", + "cookie": "nsfw=${preferenceNsfwContent()}; imgser=${preferenceImgServer()}; wd=2504x735" + }); + final doc = parseHtml(res.body); + + final elements = doc.select("div.grid.gap-5.grid-cols-1.border-t.border-t-base-200.pt-5 > div"); + if (elements.length == 0) { + return MPages(mangaList, false); + } + for (final element in elements) { + final nameElement = element.selectFirst("h3 > a > span"); + final imageElement = element.selectFirst("div > div > a > img"); + final urlElement = element.selectFirst("div > div > a"); + final name = nameElement.text ?? "No Name"; + final image = imageElement.attr("src").contains(source.baseUrl) ? imageElement.attr("src") : "${source.baseUrl}${imageElement.attr("src")}"; + final url = urlElement.attr("href").contains(source.baseUrl) ? urlElement.attr("href") : "${source.baseUrl}${urlElement.attr("href")}"; + + MManga manga = MManga(); + + manga.name = name; + manga.imageUrl = image; + manga.link = url; + + mangaList.add(manga); + } + final lastPageNumber = int.tryParse(doc.selectFirst("div.flex.items-center.flex-wrap.space-x-1.my-10.justify-center > a:last-child")?.text.trim()); + bool hasNextPage = (lastPageNumber > page); + + return MPages(mangaList, hasNextPage); + } + + @override + Future getDetail(String url) async { + final statusList = [{ + "Pending": 0, + "Ongoing": 0, + "Completed": 1, + "Hiatus": 2, + "Cancelled": 3, + "Unknown": 5, + }]; + + final res = await client.get(Uri.parse("$url"), + headers: { + "Accept": "*/*", + "accept-language": "en-US,en;q=0.9,de;q=0.8,ja;q=0.7,es;q=0.6,nl;q=0.5", + "referer": "https://mangapark.io/", + "cookie": "nsfw=${preferenceNsfwContent()}; imgser=${preferenceImgServer()}; wd=2504x735" + }); + final doc = parseHtml(res.body); + + final statusElement = doc.selectFirst("div:nth-child(2) > div:nth-child(4) > span.font-bold.uppercase"); + final authorElements = doc.select("div.flex.flex-col > div.flex > div.grow.pl-3.space-y-2 > div.mt-2.text-sm > a"); + final genreList = doc.select("div.flex.items-center.flex-wrap > span > span:nth-child(1)"); + final descriptionElement = doc.selectFirst("div.limit-html-p"); + + final author = authorElements.isNotEmpty ? authorElements.map((e) => e.text).join(" | ") : "Anonymous"; + final genres = genreList.map((e) => (e.text as String).replaceAll(",", "").trim()).toList() ?? []; + + final status = statusElement?.text ?? "Unknown"; + final description = descriptionElement.text ?? "no Description..."; + final chapters = getChapters(doc); + + + MManga manga = MManga(); + + manga.genre = genres; + manga.author = author; + manga.chapters = chapters; + manga.description = description; + manga.status = parseStatus(status, statusList); + + return manga; + } + + List getChapters(MDocument doc) { + List chapters = []; + if (doc.selectFirst("div.group.flex.flex-col").outerHtml == null) { + throw("Something went wrong. if this is a age-restricted manga, try to turn of the NSFW filter in the settings, then reload the manga."); + } + + MElement chapterList = doc.selectFirst("div.group.flex.flex-col"); + + for (MElement chapterElement in chapterList.select("div.justify-between")) { + var chapter = MChapter(); + + final chapterName = chapterElement.selectFirst("div > a"); + final scanlatorElement = chapterElement.selectFirst("a.link-hover > span"); + + final name = chapterName.text ?? "No Name Given"; + final url = chapterName.attr("href").contains(source.baseUrl) ? chapterName.attr("href") : "${source.baseUrl}${chapterName.attr("href")}"; + final uploadDate = chapterElement.selectFirst("time")?.attr("data-time"); + final scanlator = scanlatorElement.text ?? "Anonymous"; + + chapter.name = name; + chapter.url = url; + chapter.dateUpload = uploadDate; + chapter.scanlator = scanlator; + chapters.add(chapter); + } + return chapters; + } + + @override + Future>> getPageList(String url) async{ + List> images = []; + final res = await client.get(Uri.parse(url), + headers: { + "cookie": "${preferenceImgServer() == "" ? "" : "imgser=${preferenceImgServer()};"} nsfw=${preferenceNsfwContent()}; wd=2504x1362", + "referer": url, + }); + final doc = parseHtml(res.body); + + final imageObjects = doc.select('div[data-name="image-item"] img'); + + for (final imageObject in imageObjects) { + final imageUrl = imageObject.attr("src"); + images.add({ + "url": imageUrl.trim(), + "headers": { + "referer": url, + } + }); + } + return images; + } + + @override + List getFilterList() { + return [ + TextFilter("SearchFilter", "Search..."), + SelectFilter("SortFilter", "Order By", 0, [ + SelectFilterOption("Rating Score", "field_score"), + SelectFilterOption("Most Follows", "field_follow"), + SelectFilterOption("Most Reviews", "field_review"), + SelectFilterOption("Most Comments", "field_comment"), + SelectFilterOption("Most Chapters", "field_chapter"), + SelectFilterOption("New Chapters", "field_update"), + SelectFilterOption("Recently Created", "field_create"), + SelectFilterOption("Name A-Z", "field_name"), + SelectFilterOption("Most Views: Last 60 Minutes", "views_h001"), + SelectFilterOption("Most Views: Last 12 Hours", "views_h012"), + SelectFilterOption("Most Views: Last 24 Hours", "views_h024"), + SelectFilterOption("Most Views: Last 7 Days", "views_d007"), + SelectFilterOption("Most Views: Last 30 Days", "views_d030"), + SelectFilterOption("Most Views: Last 90 Days", "views_d090"), + SelectFilterOption("Most Views: Last 180 Days", "views_d180"), + SelectFilterOption("Most Views: Last 360 Days", "views_d360"), + SelectFilterOption("Most Views: All Time", "views_d000"), + SelectFilterOption("Emotion: Awesome", "emotion_e1"), + SelectFilterOption("Emotion: Funny", "emotion_e2"), + SelectFilterOption("Emotion: Love", "emotion_e3"), + SelectFilterOption("Emotion: Hot", "emotion_e4"), + SelectFilterOption("Emotion: Sweet", "emotion_e5"), + SelectFilterOption("Emotion: Cool", "emotion_e6"), + SelectFilterOption("Emotion: Scared", "emotion_e7"), + SelectFilterOption("Emotion: Angry", "emotion_e8"), + SelectFilterOption("Emotion: Sad", "emotion_e9"), + ]), + SeparatorFilter(), + GroupFilter("GenresFilter", "Manga Style", [ + TriStateFilter("Artbook", "artbook"), + TriStateFilter("Cartoon", "cartoon"), + TriStateFilter("Comic", "comic"), + TriStateFilter("Doujinshi", "doujinshi"), + TriStateFilter("Imageset", "imageset"), + TriStateFilter("Manga", "manga"), + TriStateFilter("Manhua", "manhua"), + TriStateFilter("Manhwa", "manhwa"), + TriStateFilter("Webtoon", "webtoon"), + TriStateFilter("Western", "western"), + TriStateFilter("Oneshot", "oneshot"), + TriStateFilter("4-Koma", "_4_koma"), + TriStateFilter("Art-by-AI", "ai_art"), + TriStateFilter("Story-by-AI", "ai_story"), + ]), + SeparatorFilter(), + GroupFilter("GenresFilter", "General Filters", [ + TriStateFilter("Shoujo(G)", "shoujo"), + TriStateFilter("Shounen(B)", "shounen"), + TriStateFilter("Josei(W)", "josei"), + TriStateFilter("Seinen(M)", "seinen"), + TriStateFilter("Yuri(GL)", "yuri"), + TriStateFilter("Yaoi(BL)", "yaoi"), + TriStateFilter("Futa(⚤)", "futa"), + TriStateFilter("Bara(ML)", "bara"), + TriStateFilter("Kodomo(Kid)", "kodomo"), + TriStateFilter("Silver & Golden", "old_people"), + TriStateFilter("Shoujo ai", "shoujo_ai"), + TriStateFilter("Shounen ai", "shounen_ai"), + TriStateFilter("Non-human", "non_human"), + ]), + GroupFilter("GenresFilter", "Explicit Filters", [ + TriStateFilter("Gore", "gore"), + TriStateFilter("Bloody", "bloody"), + TriStateFilter("Violence", "violence"), + TriStateFilter("Ecchi", "ecchi"), + TriStateFilter("Adult", "adult"), + TriStateFilter("Mature", "mature"), + TriStateFilter("Smut", "smut"), + TriStateFilter("Hentai", "hentai"), + ]), + GroupFilter("GenresFilter", "Detailed Filters (A-M)", [ + TriStateFilter("Action", "action"), + TriStateFilter("Adaptation", "adaptation"), + TriStateFilter("Adventure", "adventure"), + TriStateFilter("Age Gap", "age_gap"), + TriStateFilter("Aliens", "aliens"), + TriStateFilter("Animals", "animals"), + TriStateFilter("Anthology", "anthology"), + TriStateFilter("Beasts", "beasts"), + TriStateFilter("Bodyswap", "bodyswap"), + TriStateFilter("Blackmail", "blackmail"), + TriStateFilter("Brocon/Siscon", "brocon_siscon"), + TriStateFilter("Cars", "cars"), + TriStateFilter("Cheating/Infidelity", "cheating_infidelity"), + TriStateFilter("Childhood Friends", "childhood_friends"), + TriStateFilter("College life", "college_life"), + TriStateFilter("Comedy", "comedy"), + TriStateFilter("Contest winning", "contest_winning"), + TriStateFilter("Cooking", "cooking"), + TriStateFilter("Crime", "crime"), + TriStateFilter("Crossdressing", "crossdressing"), + TriStateFilter("Cultivation", "cultivation"), + TriStateFilter("Death Game", "death_game"), + TriStateFilter("DegenerateMC", "degeneratemc"), + TriStateFilter("Delinquents", "delinquents"), + TriStateFilter("Dementia", "dementia"), + TriStateFilter("Demons", "demons"), + TriStateFilter("Drama", "drama"), + TriStateFilter("Fantasy", "fantasy"), + TriStateFilter("Fan-Colored", "fan_colored"), + TriStateFilter("Fetish", "fetish"), + TriStateFilter("Full Color", "full_color"), + TriStateFilter("Game", "game"), + TriStateFilter("Gender Bender", "gender_bender"), + TriStateFilter("Genderswap", "genderswap"), + TriStateFilter("Ghosts", "ghosts"), + TriStateFilter("Gyaru", "gyaru"), + TriStateFilter("Harem", "harem"), + TriStateFilter("Harlequin", "harlequin"), + TriStateFilter("Historical", "historical"), + TriStateFilter("Horror", "horror"), + TriStateFilter("Incest", "incest"), + TriStateFilter("Isekai", "isekai"), + TriStateFilter("Kids", "kids"), + TriStateFilter("Loli", "loli"), + TriStateFilter("Magic", "magic"), + TriStateFilter("Magical Girls", "magical_girls"), + TriStateFilter("Martial Arts", "martial_arts"), + TriStateFilter("Master-Servant", "master_servant"), + TriStateFilter("Mecha", "mecha"), + TriStateFilter("Medical", "medical"), + TriStateFilter("Military", "military"), + TriStateFilter("Monster Girls", "monster_girls"), + TriStateFilter("Monsters", "monsters"), + TriStateFilter("Music", "music"), + TriStateFilter("Mystery", "mystery"), + ]), + GroupFilter("GenresFilter", "Detailed Filters (N-Z)", [ + TriStateFilter("Netori", "netori"), + TriStateFilter("Netorare/NTR", "netorare"), + TriStateFilter("Ninja", "ninja"), + TriStateFilter("Office Workers", "office_workers"), + TriStateFilter("Omegaverse", "omegaverse"), + TriStateFilter("Parody", "parody"), + TriStateFilter("Philosophical", "philosophical"), + TriStateFilter("Police", "police"), + TriStateFilter("Post-Apocalyptic", "post_apocalyptic"), + TriStateFilter("Psychological", "psychological"), + TriStateFilter("Reincarnation", "reincarnation"), + TriStateFilter("Revenge", "revenge"), + TriStateFilter("Reverse Harem", "reverse_harem"), + TriStateFilter("Romance", "romance"), + TriStateFilter("Samurai", "samurai"), + TriStateFilter("School Life", "school_life"), + TriStateFilter("Sci-Fi", "sci_fi"), + TriStateFilter("Shota", "shota"), + TriStateFilter("Showbiz", "showbiz"), + TriStateFilter("Slice of Life", "slice_of_life"), + TriStateFilter("SM/BDSM", "sm_bdsm"), + TriStateFilter("Space", "space"), + TriStateFilter("Sports", "sports"), + TriStateFilter("Spy", "spy"), + TriStateFilter("Step Family", "step_family"), + TriStateFilter("Super Power", "super_power"), + TriStateFilter("Superhero", "superhero"), + TriStateFilter("Supernatural", "supernatural"), + TriStateFilter("Survival", "survival"), + TriStateFilter("Teacher-Student", "teacher_student"), + TriStateFilter("Thriller", "thriller"), + TriStateFilter("Time Travel", "time_travel"), + TriStateFilter("Traditional Games", "traditional_games"), + TriStateFilter("Tragedy", "tragedy"), + TriStateFilter("Vampires", "vampires"), + TriStateFilter("Video Games", "video_games"), + TriStateFilter("Villainess", "villainess"), + TriStateFilter("Virtual Reality", "virtual_reality"), + TriStateFilter("Wuxia", "wuxia"), + TriStateFilter("Xianxia", "xianxia"), + TriStateFilter("Xuanhuan", "xuanhuan"), + TriStateFilter("Zombies", "zombies"), + ]), + SeparatorFilter(), + GroupFilter("TranslatedFromFilter", "Original Work Language", [ + CheckBoxFilter("Chinese", "zh"), + CheckBoxFilter("English", "en"), + CheckBoxFilter("Japanese", "jp"), + CheckBoxFilter("Korean", "ko"), + ]), + GroupFilter("TranslatedToFilter", "Translated Language", [ + CheckBoxFilter("Dutch", "nl"), + CheckBoxFilter("English", "en"), + CheckBoxFilter("German", "de"), + CheckBoxFilter("Japanese", "jp"), + CheckBoxFilter("Spanish", "es"), + CheckBoxFilter("Spanish (LA)", "es_419"), + ]), + SeparatorFilter(), + SelectFilter("OrigWorkFilter", "Original Work Status", 0, [ + SelectFilterOption("Any", ""), + SelectFilterOption("Pending", "pending"), + SelectFilterOption("Ongoing", "ongoing"), + SelectFilterOption("Completed", "completed"), + SelectFilterOption("Hiatus", "hiatus"), + SelectFilterOption("Cancelled", "cancelled"), + ]), + SelectFilter("MparkUplFilter", "MangaPark Upload Status", 0, [ + SelectFilterOption("Any", ""), + SelectFilterOption("Pending", "pending"), + SelectFilterOption("Ongoing", "ongoing"), + SelectFilterOption("Completed", "completed"), + SelectFilterOption("Hiatus", "hiatus"), + SelectFilterOption("Cancelled", "cancelled"), + ]), + SeparatorFilter(), + SelectFilter("ChapCountFilter", "Number of Chapters", 0, [ + SelectFilterOption("Any", ""), + SelectFilterOption("0", "0"), + SelectFilterOption("1+", "1"), + SelectFilterOption("10+", "10"), + SelectFilterOption("20+", "20"), + SelectFilterOption("30+", "30"), + SelectFilterOption("40+", "40"), + SelectFilterOption("50+", "50"), + SelectFilterOption("60+", "60"), + SelectFilterOption("70+", "70"), + SelectFilterOption("80+", "80"), + SelectFilterOption("90+", "90"), + SelectFilterOption("100+", "100"), + SelectFilterOption("200+", "200"), + SelectFilterOption("300+", "300"), + SelectFilterOption("299~200", "200-299"), + SelectFilterOption("199~100", "100-199"), + SelectFilterOption("99~90", "90-99"), + SelectFilterOption("89~80", "80-89"), + SelectFilterOption("79~70", "70-79"), + SelectFilterOption("69~60", "60-69"), + SelectFilterOption("59~50", "50-59"), + SelectFilterOption("49~40", "40-49"), + SelectFilterOption("39~30", "30-39"), + SelectFilterOption("29~20", "20-29"), + SelectFilterOption("19~10", "10-19"), + SelectFilterOption("9~1", "1-9"), + ]), + ]; + } + + @override + List getSourcePreferences() { + return [ + ListPreference( + key: "NsfwFilter", + title: "Display NSFW content", + summary: "", + valueIndex: 0, + entries: ["False", "True"], + entryValues: [0, 2], + ), + ListPreference( + key: "ImgServer", + title: "Choose Image Server", + summary: "", + valueIndex: 0, + entries: ["Default", "mpfip.org", "mpizz.org", "mpmok.org", "mpqom.org", "mpqrc.org", "mprnm.org", "mpubn.org", "mpujj.org", "mpvim.org", "mypypl.org"], + entryValues: ["", "mpfip.org", "mpizz.org", "mpmok.org", "mpqom.org", "mpqrc.org", "mprnm.org", "mpubn.org", "mpujj.org", "mpvim.org", "mypypl.org"], + ), + ]; + } + + int preferenceNsfwContent() { + return getPreferenceValue(source.id, "NsfwFilter"); + } + + String preferenceImgServer() { + return getPreferenceValue(source.id, "ImgServer"); + } + +} + +MangaPark main(MSource source) { + return MangaPark(source:source); +} diff --git a/dart/manga/src/en/mangapark/source.dart b/dart/manga/src/en/mangapark/source.dart new file mode 100644 index 00000000..730731d0 --- /dev/null +++ b/dart/manga/src/en/mangapark/source.dart @@ -0,0 +1,19 @@ +import '../../../../../model/source.dart'; + +Source get mangaparkSource => _mangaparkSource; +const _mangaparkVersion = "1.0.0"; +const _mangaparkSourceCodeUrl = "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/en/mangahere/mangahere.dart"; +const _mangaparkIconUrl = "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/en/mangapark/icon.png"; +Source _mangaparkSource = Source( + name: "MangaPark", + baseUrl: "http://www.mangapark.io", + apiUrl: "http://www.mangapark.io/apo/", + lang: "en", + typeSource: "single", + iconUrl: _mangaparkIconUrl, + sourceCodeUrl: _mangaparkSourceCodeUrl, + itemType: ItemType.manga, + version: _mangaparkVersion, + dateFormat: "MMM dd yyyy", + dateFormatLocale: "en", +);