From 971052c1c9dfecf45d968855040472a5b2eb7ab0 Mon Sep 17 00:00:00 2001 From: KptnFishy <120373460+KptnFishy@users.noreply.github.com> Date: Sun, 1 Jun 2025 18:49:17 +0200 Subject: [PATCH 1/4] Create mangabuddy.dart --- dart/manga/src/en/mangabuddy/mangabuddy.dart | 309 +++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 dart/manga/src/en/mangabuddy/mangabuddy.dart diff --git a/dart/manga/src/en/mangabuddy/mangabuddy.dart b/dart/manga/src/en/mangabuddy/mangabuddy.dart new file mode 100644 index 00000000..df9a5572 --- /dev/null +++ b/dart/manga/src/en/mangabuddy/mangabuddy.dart @@ -0,0 +1,309 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class MangaBuddy extends MProvider { + MangaBuddy({required this.source}); + + MSource source; + + final Client client = Client(source); + + @override + bool get supportsLatest => true; + + @override + Map get headers => {}; + + @override + MPages mangaFromElements(List elements, bool hasNextPage) { + List mangaList = []; + + for (var i = 0; i < elements.length; i++) { + final title = elements[i].selectFirst("div.meta > div.title > h3 > a"); + final imageElement = elements[i].selectFirst("div.thumb > a > img"); + final image = imageElement?.attr("data-src") ?? + imageElement?.getSrc ?? + ""; + + MManga manga = MManga(); + manga.name = title.text ?? title.attr("title"); + manga.imageUrl = image; + manga.link = title.attr("href").contains(source.baseUrl) ? title.attr("href") : "${source.baseUrl}${title.attr("href")}"; + mangaList.add(manga); + } + + + return MPages(mangaList, hasNextPage); + } + + @override + Future getPopular(int page) async { + final res = await client.get(Uri.parse("${source.baseUrl}/popular?page=$page")); + final doc = parseHtml(res.body); + + final nextElement = doc.selectFirst("a.page-link[title='Next']"); + bool hasNext = nextElement.text != null; + + return mangaFromElements(doc.select("div.list.manga-list > div.book-item > div.book-detailed-item"), true); + } + + @override + Future getLatestUpdates(int page) async { + final res = await client.get(Uri.parse("${source.baseUrl}/latest?page=$page")); + final doc = parseHtml(res.body); + + final nextElement = doc.selectFirst("a.page-link[title='Next']"); + bool hasNext = nextElement.text != null; + + return mangaFromElements(doc.select("div.list.manga-list > div.book-item > div.book-detailed-item"), true); + } + + @override + Future search(String initialQuery, int page, FilterList filterList) async { + final filters = filterList.filters; + final filterString = ""; + final query = initialQuery; + for (var filter in filters) { + + if (filter.type == "SearchFilter"){ + query = filter.state.toString(); + } else if (filter.type == "GenresFilter") { + for (var genre in filter.state) { + if (genre.state == true) { + filterString += ("&genre[]=${genre.value.toString()}"); + } + } + } else if (filter.type == "StatusFilter") { + filterString += ("&status=${filter.values[filter.state].value.toString()}"); + } else if (filter.type == "OrderFilter") { + filterString += ("&sort=${filter.values[filter.state].value.toString()}"); + } + } + + + final res = await client.get(Uri.parse("${source.baseUrl}/search?$filterString&q=$query&page=$page")); + final doc = parseHtml(res.body); + + return mangaFromElements(doc.select("div.list.manga-list > div.book-item > div.book-detailed-item"), true); + } + + @override + Future getDetail(String url) async { + final statusList = [{ + "Ongoing": 0, + "Completed": 1, + }]; + final res = await client.get(Uri.parse(url)); + final doc = parseHtml(res.body); + + MManga manga = MManga(); + + final chapterIdElement = doc.selectFirst("div.layout > script"); + final idRegex = RegExp(r"var\s+bookId\s*=\s*(\d+);"); + + final imageElement = doc.selectFirst("div.book-info div.img-cover > img"); + final statusElement = doc.selectFirst("div.book-info div.detail > div.meta.box.mt-1.p-10 > p > a[href^='/status/'] > span"); + final authorElements = doc.select("div.book-info div.detail > div.meta.box.mt-1.p-10 > p > a[href^='/authors/'] > span"); + final genreList = doc.select("div.book-info div.detail > div.meta.box.mt-1.p-10 > p > a[href^='/genres/']"); + final descriptionElement = doc.selectFirst("div.section-body.summary > p.content"); + + final chapterIdMatch = idRegex.firstMatch(chapterIdElement?.text); + final chapterId = chapterIdMatch != null ? chapterIdMatch.group(1) ?? "" : ""; + + final image = imageElement?.attr("data-src") ?? imageElement?.getSrc ?? ""; + final status = statusElement.text ?? "Ongoing"; + final author = authorElements.isNotEmpty ? authorElements.map((e) => e.text).join(" | ") : "unknown"; + final genres = genreList.map((e) => (e.text as String).replaceAll(",", "").trim()).toList(); + + final description = descriptionElement?.text ?? ""; + + manga.author = author; + manga.description = description; + manga.imageUrl = image; + manga.genre = genres; + + manga.chapters = await getChapters(chapterId); + manga.status = parseStatus(status, statusList); + return manga; + } + + @override + Future> getChapters(String chapterId) async { + List chapters = []; + + final res = await client.get(Uri.parse("${source.baseUrl}/api/manga/$chapterId/chapters?source=detail")); + MDocument doc = parseHtml(res.body); + + MElement chapterList = doc.selectFirst("ul.chapter-list"); + + for (MElement chapterElement in chapterList.select("li")) { + var chapter = MChapter(); + + final name = chapterElement.selectFirst("strong.chapter-title")?.text; + final url = chapterElement.selectFirst("a")?.attr("href"); + final uploadDate = chapterElement.selectFirst("time.chapter-update")?.text; + + chapter.name = name; + chapter.url = url; + chapter.dateUpload = parseDateToUnix(uploadDate).toString(); + + chapters.add(chapter); + } + + return chapters; + } + + @override + int parseDateToUnix(String dateStr) { + + const monthMap = { + 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, + 'May': 5, 'Jun': 6, 'Jul': 7, 'Aug': 8, + 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12, + }; + + + final parts = dateStr.split(' '); + if (parts.length != 3) return DateTime.now().millisecondsSinceEpoch; + + final monthStr = parts[0]; + final dayStr = parts[1].replaceAll(',', ''); + final yearStr = parts[2]; + + final month = monthMap[monthStr] ?? 1; + final day = int.tryParse(dayStr) ?? 1; + final year = int.tryParse(yearStr) ?? DateTime.now().year; + + final dt = DateTime(year, month, day); + return dt.millisecondsSinceEpoch; + } + + @override + Future>> getPageList(String url) async { + List> images = []; + + final res = await client.get(Uri.parse("${source.baseUrl}$url")); + final doc = parseHtml(res.body); + + final imageScript = doc.select("div#viewer-page.main-container.viewer > script"); + + final rawImageText = imageScript[imageScript.length-1]?.text ?? ""; + + final imageList = rawImageText.replaceAll("var chapImages =", "").replaceAll("'", "").trim().split(","); + + for (final image in imageList) { + images.add({ + "url": image.trim(), + "headers": { + "Referer": source.baseUrl, + } + }); + } + + return images; + } + + + + @override + List getFilterList() { + return [ + TextFilter("SearchFilter", "Search..."), + GroupFilter("GenresFilter", "Genres A-M", [ + CheckBoxFilter("Action", "action"), + CheckBoxFilter("Adaptation", "adaptation"), + CheckBoxFilter("Adult", "adult"), + CheckBoxFilter("Adventure", "adventure"), + CheckBoxFilter("Animal", "animal"), + CheckBoxFilter("Anthology", "anthology"), + CheckBoxFilter("Cartoon", "cartoon"), + CheckBoxFilter("Comedy", "comedy"), + CheckBoxFilter("Comic", "comic"), + CheckBoxFilter("Cooking", "cooking"), + CheckBoxFilter("Demons", "demons"), + CheckBoxFilter("Doujinshi", "doujinshi"), + CheckBoxFilter("Drama", "drama"), + CheckBoxFilter("Ecchi", "ecchi"), + CheckBoxFilter("Fantasy", "fantasy"), + CheckBoxFilter("Full Color", "full-color"), + CheckBoxFilter("Game", "game"), + CheckBoxFilter("Gender bender", "gender-bender"), + CheckBoxFilter("Ghosts", "ghosts"), + CheckBoxFilter("Harem", "harem"), + CheckBoxFilter("Historical", "historical"), + CheckBoxFilter("Horror", "horror"), + CheckBoxFilter("Isekai", "isekai"), + CheckBoxFilter("Josei", "josei"), + CheckBoxFilter("Long strip", "long-strip"), + CheckBoxFilter("Mafia", "mafia"), + CheckBoxFilter("Magic", "magic"), + CheckBoxFilter("Manga", "manga"), + CheckBoxFilter("Manhua", "manhua"), + CheckBoxFilter("Manhwa", "manhwa"), + CheckBoxFilter("Martial arts", "martial-arts"), + CheckBoxFilter("Mature", "mature"), + CheckBoxFilter("Mecha", "mecha"), + CheckBoxFilter("Medical", "medical"), + CheckBoxFilter("Military", "military"), + CheckBoxFilter("Monster", "monster"), + CheckBoxFilter("Monster girls", "monster-girls"), + CheckBoxFilter("Monsters", "monsters"), + CheckBoxFilter("Music", "music"), + CheckBoxFilter("Mystery", "mystery"), + ]), + GroupFilter("GenresFilter", "Genres N-Z", [ + CheckBoxFilter("Office", "office"), + CheckBoxFilter("Office workers", "office-workers"), + CheckBoxFilter("One shot", "one-shot"), + CheckBoxFilter("Police", "police"), + CheckBoxFilter("Psychological", "psychological"), + CheckBoxFilter("Reincarnation", "reincarnation"), + CheckBoxFilter("Romance", "romance"), + CheckBoxFilter("School life", "school-life"), + CheckBoxFilter("Sci fi", "sci-fi"), + CheckBoxFilter("Science fiction", "science-fiction"), + CheckBoxFilter("Seinen", "seinen"), + CheckBoxFilter("Shoujo", "shoujo"), + CheckBoxFilter("Shoujo ai", "shoujo-ai"), + CheckBoxFilter("Shounen", "shounen"), + CheckBoxFilter("Shounen ai", "shounen-ai"), + CheckBoxFilter("Slice of life", "slice-of-life"), + CheckBoxFilter("Smut", "smut"), + CheckBoxFilter("Soft Yaoi", "soft-yaoi"), + CheckBoxFilter("Sports", "sports"), + CheckBoxFilter("Super Power", "super-power"), + CheckBoxFilter("Superhero", "superhero"), + CheckBoxFilter("Supernatural", "supernatural"), + CheckBoxFilter("Thriller", "thriller"), + CheckBoxFilter("Time travel", "time-travel"), + CheckBoxFilter("Tragedy", "tragedy"), + CheckBoxFilter("Vampire", "vampire"), + CheckBoxFilter("Vampires", "vampires"), + CheckBoxFilter("Video games", "video-games"), + CheckBoxFilter("Villainess", "villainess"), + CheckBoxFilter("Web comic", "web-comic"), + CheckBoxFilter("Webtoons", "webtoons"), + CheckBoxFilter("Yaoi", "yaoi"), + CheckBoxFilter("Yuri", "yuri"), + CheckBoxFilter("Zombies", "zombies"), + ]), + SeparatorFilter(), + SelectFilter("StatusFilter", "Status", 0, [ + SelectFilterOption("All (Default)", "all"), + SelectFilterOption("Ongoing", "ongoing"), + SelectFilterOption("Completed", "completed"), + ]), + SelectFilter("OrderFilter", "Order By", 0, [ + SelectFilterOption("Views (Default)", "views"), + SelectFilterOption("Latest Updated", "updated_at"), + SelectFilterOption("Creation Date", "created_at"), + SelectFilterOption("Name A-Z", "name"), + SelectFilterOption("Rating", "rating"), + ]), + ]; + } +} + +MangaBuddy main(MSource source) { + return MangaBuddy(source:source); +} From 39f1f9c7315b31e815edbce68c018b87e08012ad Mon Sep 17 00:00:00 2001 From: KptnFishy <120373460+KptnFishy@users.noreply.github.com> Date: Sun, 1 Jun 2025 19:05:51 +0200 Subject: [PATCH 2/4] Added source.dart for the mangabuddy extension --- dart/manga/src/en/mangabuddy/source.dart | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 dart/manga/src/en/mangabuddy/source.dart diff --git a/dart/manga/src/en/mangabuddy/source.dart b/dart/manga/src/en/mangabuddy/source.dart new file mode 100644 index 00000000..ef8a9f34 --- /dev/null +++ b/dart/manga/src/en/mangabuddy/source.dart @@ -0,0 +1,19 @@ +import '../../../../../model/source.dart'; + +Source get mangabuddySource => _mangabuddySource; +const _mangabuddyVersion = "0.0.1"; +const _mangabuddySourceCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/en/mangabuddy/mangabuddy.dart"; +Source _mangabuddySource = Source( + name: "MangaBuddy", + baseUrl: "http://www.mangabuddy.com", + lang: "en", + typeSource: "single", + isNsfw: true, + iconUrl: "https://mangabuddy.com/static/sites/mangabuddy/icons/favicon.ico", + sourceCodeUrl: _mangabuddySourceCodeUrl, + itemType: ItemType.manga, + version: _mangabuddyVersion, + dateFormat: "MMM dd,yyyy", + dateFormatLocale: "en", +); From 1413911ef1053b9e818979c54f5fbf8ca144b21c Mon Sep 17 00:00:00 2001 From: KptnFishy <120373460+KptnFishy@users.noreply.github.com> Date: Sun, 1 Jun 2025 19:09:20 +0200 Subject: [PATCH 3/4] Add files via upload --- dart/manga/src/en/mangabuddy/icon.png | Bin 0 -> 15406 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 dart/manga/src/en/mangabuddy/icon.png diff --git a/dart/manga/src/en/mangabuddy/icon.png b/dart/manga/src/en/mangabuddy/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9f7260b245c6b856ae3abebe4150b120ff370ca5 GIT binary patch literal 15406 zcmeHN32YTr6diUHVv8&$2CxWdQHaI`F&ISz!yX`nL?lWK1WYvI9tDNCBpRav3K$7n zVnn(ZX(@EIm2MO$g#s;wiiJXf7P`>Az25WqcQSuEpU(d+pMLC1CU54wcjumW-+C8K z>!Nkj{QWh~1GHt`HEpb>X#oK@z61Md+7>Q*{BcM9LzeLeF>+uF;Q z=@4Vfvq@jDVM7c0qTpaEJ9LOj5)xeE7yefCRi{r=b7LbVO`7BpJ@B9X{dc;|@-g>6 zYS=I(KkzTh$|CU3e)nB6_dkE#Ix5Y`XhDDI?Y9;ErKBWkZf>S)IXP5UT1u6tPSM`c zqZMw@L5>(hGk?sFjMrYHNS-5EOP5l_z4y}oIddrP>8DlOlmQo7lKEcnR95cNIe4uEZ>C-8E`2+;_iDe* zxVnBvBbqvKzHEk#sy<_zr#3{>Z} z6hda`Q^CfKQbd*c`BYg@puWqG9kaY2Sji#rg%|XHp#NxNu|H$}XJ$zV|S?)g;>)$Sa=rNLH|NA#6NQwyjvESBT zyVffIm`5Jb>Fn}9`Q3Lq%|E~WM#sPWlA7x4^|GU%eMT4d?9sog3JaB7Bl`7IX8|gj zo0=&1i!YQ;BMtMu|GriE$Bi9J4Xm3=!ajn3NvgeciK>f=*|;%`{&NJ#as4(&fLZQl*i{zlFjYZfxsKO?`@Q)(t#Zq zioJ-V7(AhWyRr-XU&;fyqimwp7V&d4I|)~7JU)IrXBKjv#(9kw$IGoX4{ZFT2j9Tz zXTZLm%WFF-ZZj@m$x=-FG`ilD^8rCzh})-xS}B&hfUV0 z5r4qV2bG9U&nzpB?;pO8%#*go&?+cKL&dV+U#fFi>GtRj32QE@-H)f__@RV z{Vnlht_U6SlK6OmuVNOk*P$G1s&MO8y$&z!iC_FF;AO^-IV^}gDJ|O-GXO=O+Zwp&@YdBri zdkeo2v%F&*G8Zjs5eMK3t5vx-@MDhPUV=NKB;5IM#|UMgf)y^iW!}KwP*p|Q@4csD z5pfSfYT`TXx$tm;pUEy(t9D+&Z#aKb_Z^}84s zC(}sfhyk>N)vArR@vHO8bpO0%`1xLLOhpFy|IK=u@&0f7{>Jx*Gp74Pd*VmDsEkwc zv}s28WW=BT>Z?w=p>6yY_vg0YKl0&+P8f~&kAL-*Q=Q;P3>E6IFG^~GpW~V&8Sl@~ zAI?QYNR`Sx#gDzWI6B(tU+^76C*41Lw$j;+mGFJT-h#Ufe8R?rm=hT{=$?qx(i7{E z(0vg1o5EvScjby+g%`xi2(8^btZ&~NcM(IJ3;u=u`TE(j%I1J?V}cwJSH=O!#$xqe z$oCuY{*(1fu}kQUxINA;^cnv!Cg}2_Guy?EcHtZcfBM&7o#G4dU-eW=5nJGmoN1qTY<7vDg>^QJ@z|W5}5-|#} zH$g|t`SeplECTd$V293%`4%~7kcyedxzosTg{Xt=$A|&(kBFavy$9G4zX9DH>*nzC zA#4=z`OF8Plt^s05%6kC3j%&Oq~*Fiv<1yIjY Date: Sun, 1 Jun 2025 19:14:51 +0200 Subject: [PATCH 4/4] Changed icon source to from mangabuddy to github --- dart/manga/src/en/mangabuddy/source.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dart/manga/src/en/mangabuddy/source.dart b/dart/manga/src/en/mangabuddy/source.dart index ef8a9f34..064de9d0 100644 --- a/dart/manga/src/en/mangabuddy/source.dart +++ b/dart/manga/src/en/mangabuddy/source.dart @@ -10,7 +10,7 @@ Source _mangabuddySource = Source( lang: "en", typeSource: "single", isNsfw: true, - iconUrl: "https://mangabuddy.com/static/sites/mangabuddy/icons/favicon.ico", + iconUrl: "https://github.com/KptnFishy/mangayomi-extensions/blob/patch-1/dart/manga/src/en/mangabuddy/icon.png", sourceCodeUrl: _mangabuddySourceCodeUrl, itemType: ItemType.manga, version: _mangabuddyVersion,