From 5f08f77f74f199db482bd179003f800c55cc7aa5 Mon Sep 17 00:00:00 2001 From: kodjomoustapha <107993382+kodjodevf@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:20:55 +0100 Subject: [PATCH] New source: AnimePahe (EN) --- anime/source_generator.dart | 4 +- anime/src/en/animepahe/animepahe.dart | 223 ++++++++++++++++++++++++++ anime/src/en/animepahe/icon.png | Bin 0 -> 1934 bytes anime/src/en/animepahe/source.dart | 16 ++ 4 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 anime/src/en/animepahe/animepahe.dart create mode 100644 anime/src/en/animepahe/icon.png create mode 100644 anime/src/en/animepahe/source.dart diff --git a/anime/source_generator.dart b/anime/source_generator.dart index 159feed5..386735ff 100644 --- a/anime/source_generator.dart +++ b/anime/source_generator.dart @@ -10,6 +10,7 @@ import 'src/all/nyaa/source.dart'; import 'src/all/yomiroll/source.dart'; import 'src/ar/okanime/source.dart'; import 'src/de/aniflix/source.dart'; +import 'src/en/animepahe/source.dart'; import 'src/en/aniwave/source.dart'; import 'src/en/dramacool/source.dart'; import 'src/en/gogoanime/source.dart'; @@ -52,7 +53,8 @@ void main() { aniflix, ...animeworldindiaSourcesList, nyaaSource, - yomirollSource + yomirollSource, + animepaheSource ]; final List> jsonList = _sourcesList.map((source) => source.toJson()).toList(); diff --git a/anime/src/en/animepahe/animepahe.dart b/anime/src/en/animepahe/animepahe.dart new file mode 100644 index 00000000..ca6a13c0 --- /dev/null +++ b/anime/src/en/animepahe/animepahe.dart @@ -0,0 +1,223 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; +import 'dart:math'; + +class AnimePahe extends MProvider { + AnimePahe(this.source); + + final MSource source; + + final Client client = Client(source); + + @override + String get baseUrl => getPreferenceValue(source.id, "preferred_domain"); + + @override + Future getPopular(int page) async { + return await getLatestUpdates(page); + } + + @override + Future getLatestUpdates(int page) async { + final res = + (await client.get(Uri.parse("$baseUrl/api?m=airing&page=$page"))).body; + final jsonResult = json.decode(res); + final hasNextPage = jsonResult["current_page"] < jsonResult["last_page"]; + List animeList = []; + for (var item in jsonResult["data"]) { + MManga anime = MManga(); + anime.name = item["anime_title"]; + anime.imageUrl = item["snapshot"]; + anime.link = "/anime/?anime_id=${item["id"]}&name=${item["anime_title"]}"; + anime.artist = item["fansub"]; + animeList.add(anime); + } + return MPages(animeList, hasNextPage); + } + + @override + Future search(String query, int page, FilterList filterList) async { + final res = + (await client.get(Uri.parse("$baseUrl/api?m=search&l=8&q=$query"))) + .body; + final jsonResult = json.decode(res); + List animeList = []; + for (var item in jsonResult["data"]) { + MManga anime = MManga(); + anime.name = item["title"]; + anime.imageUrl = item["poster"]; + anime.link = "/anime/?anime_id=${item["id"]}&name=${item["title"]}"; + animeList.add(anime); + } + return MPages(animeList, false); + } + + @override + Future getDetail(String url) async { + final statusList = [ + {"Currently Airing": 0, "Finished Airing": 1} + ]; + MManga anime = MManga(); + final id = substringBefore(substringAfterLast(url, "?anime_id="), "&name="); + final name = substringAfterLast(url, "&name="); + print(name); + final session = await getSession(name, id); + print(session); + final res = + (await client.get(Uri.parse("$baseUrl/anime/$session?anime_id=$id"))) + .body; + final document = parseHtml(res); + final status = + (document.xpathFirst('//div/p[contains(text(),"Status:")]/text()') ?? + "") + .replaceAll("Status:\n", "") + .trim(); + anime.status = parseStatus(status, statusList); + + anime.name = document.selectFirst("div.title-wrapper > h1 > span").text; + anime.author = + (document.xpathFirst('//div/p[contains(text(),"Studio:")]/text()') ?? + "") + .replaceAll("Studio:\n", "") + .trim(); + anime.imageUrl = document.selectFirst("div.anime-poster a").attr("href"); + anime.genre = + xpath(res, '//*[contains(@class,"anime-genre")]/ul/li/text()'); + final synonyms = + (document.xpathFirst('//div/p[contains(text(),"Synonyms:")]/text()') ?? + "") + .replaceAll("Synonyms:\n", "") + .trim(); + anime.description = document.selectFirst("div.anime-summary").text; + if (synonyms.isNotEmpty) { + anime.description += "\n\n$synonyms"; + } + final epUrl = "$baseUrl/api?m=release&id=$session&sort=episode_desc&page=1"; + final resEp = (await client.get(Uri.parse(epUrl))).body; + final episodes = await recursivePages(epUrl, resEp, session); + + anime.chapters = episodes; + return anime; + } + + Future> recursivePages( + String url, String res, String session) async { + final jsonResult = json.decode(res); + final page = jsonResult["current_page"]; + final hasNextPage = page < jsonResult["last_page"]; + List animeList = []; + for (var item in jsonResult["data"]) { + MChapter episode = MChapter(); + episode.name = "Episode ${item["episode"]}"; + episode.url = "/play/$session/${item["session"]}"; + episode.dateUpload = + parseDates([item["created_at"]], "yyyy-MM-dd HH:mm:ss", "en")[0]; + animeList.add(episode); + } + if (hasNextPage) { + final newUrl = "${substringBeforeLast(url, "&page=")}&page=${page + 1}"; + final newRes = (await client.get(Uri.parse(newUrl))).body; + animeList.addAll(await recursivePages(newUrl, newRes, session)); + } + return animeList; + } + + Future getSession(String title, String animeId) async { + final res = + (await client.get(Uri.parse("$baseUrl/api?m=search&q=$title"))).body; + return substringBefore( + substringAfter( + substringAfter(res, "\"id\":$animeId"), "\"session\":\""), + "\""); + } + + @override + Future> getVideoList(String url) async { + final res = (await client.get(Uri.parse("${source.baseUrl}$url"))); + + final document = parseHtml(res.body); + final buttons = document.select("div#resolutionMenu > button"); + List videos = []; + for (var i = 0; i < buttons.length; i++) { + final btn = buttons[i]; + final kwikLink = btn.attr("data-src"); + final quality = btn.text; + final ress = (await client.get(Uri.parse(kwikLink), + headers: {"Referer": "https://animepahe.com"})); + final script = substringAfterLast( + xpath(ress.body, '//script[contains(text(),"eval(function")]/text()') + .first, + "eval(function("); + final videoUrl = substringBefore( + substringAfter(unpackJs("eval(function($script"), "const source=\\'"), + "\\';"); + MVideo video = MVideo(); + video + ..url = videoUrl + ..originalUrl = videoUrl + ..quality = quality + ..headers = {"referer": "https://kwik.cx"}; + videos.add(video); + } + return sortVideos(videos); + } + + List sortVideos(List videos) { + String quality = getPreferenceValue(source.id, "preferred_quality"); + + videos.sort((MVideo a, MVideo b) { + int qualityMatchA = 0; + if (a.quality.contains(quality)) { + qualityMatchA = 1; + } + int qualityMatchB = 0; + if (b.quality.contains(quality)) { + qualityMatchB = 1; + } + if (qualityMatchA != qualityMatchB) { + return qualityMatchB - qualityMatchA; + } + + final regex = RegExp(r'(\d+)p'); + final matchA = regex.firstMatch(a.quality); + final matchB = regex.firstMatch(b.quality); + final int qualityNumA = int.tryParse(matchA?.group(1) ?? '0') ?? 0; + final int qualityNumB = int.tryParse(matchB?.group(1) ?? '0') ?? 0; + return qualityNumB - qualityNumA; + }); + + return videos; + } + + @override + List getSourcePreferences() { + return [ + ListPreference( + key: "preferred_domain", + title: "Preferred domain", + summary: "", + valueIndex: 1, + entries: [ + "animepahe.com", + "animepahe.ru", + "animepahe.org" + ], + entryValues: [ + "https://animepahe.com", + "https://animepahe.ru", + "https://animepahe.org" + ]), + ListPreference( + key: "preferred_quality", + title: "Preferred Quality", + summary: "", + valueIndex: 0, + entries: ["1080p", "720p", "360p"], + entryValues: ["1080", "720", "360"]), + ]; + } +} + +AnimePahe main(MSource source) { + return AnimePahe(source); +} diff --git a/anime/src/en/animepahe/icon.png b/anime/src/en/animepahe/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4c3b0098ef33a9f6b2a15ea2a15ea7b605af17e6 GIT binary patch literal 1934 zcmYjSX;f3!7QP`N31p%P8Axt!5+Knq2qiM97={1|G7|`c$V{OEb*?x-EItL)Dk_rJ z!lF_Y1raJ#T?}Fwj35F*K*a)56l_7XP?tmCy#xMvd#$_gx6iltclJ4Z-E|9s1AR%x zY-0$5ND8?u6uje?A3PfPL7$_5KtsX)N^cPUzZn@BK@gotq|j(IfX&$OXO>E5GFLFn zGY2bVGWj#0GMFIbAbbXEITPjyXbctrR4VYME0kbahl2o2BoY}6h8c`dD5e~)fDI$Y z#>Oxrpi&ut1q7FCL8o(QG!}(IHG_Ff=CU}@1PUM-U|=i`0!k3X5&#e~nLIN23y?z~ z5Lg@zolXahNhA^uB4EK>HrJfT<1yJ>4##q(8nUzmz?j1mfHpuT*afTuz+$oZd_D}r zpb&%<3I+Jj6wEhA5D_>6jPR*cDwD}wk#Yw+a(U()WH}8Ohsy_Tv|+*}9I?jOw}jy9 z!?F)zIQ;$hOu3Pw4;6i?==ARxG<5z)Kn;lqTa6kB*z0P{X5MTaJr52%fg>X$;i{hcQ} zb8~YAB(L`Nj%LHKB$|tp>xRarrXGXH!NI{Fe=41rm>3-$YiYToMMvb3JjTYx?Ilj> z)O8KN{q_~rbxVArgk#0yS;Uw+rhT2Umm*E3xkS<(H%dKaK8nGiAwU11;83N7NW9lX zI)I8kkfR2d`E^ECGR^fzf;^w>mHEZ`UoKwqQEXbp6`3QJzAU>dIR8sV{@vXV9`^Qj z8AK-0T&Jg}`4+;iuI{r0U!871L-2LtSS6>Xd3np#WcRI^->h?!DkCBrupv84+=Pgw zNNjCu?~uLk0GMD$M`x*#JT@*NCN}QK(Sl;4Zzw~;=JErAL&NE7cbm9XUb%XlBvUZ$ z8|yW%!SJ$ng$9PPt;9_H24a}g`X_5}m!Da_HI4a;bP20}L0l>)Sdk1DITIYZ-CN1i z*K228F_Sj-j;aM))S}BQg}yLj^@b&g?3JA(el*k{`y`lzAu}91Z{WkcV2N%*U3#o~ zLVI(_{NG0|HbRU~&pg_oOXrjVzQSIG%tM(rI{P$z7cJZr_r`cNf0K0;fn`$Mpq@}Z zkp+JjVU|2LT$mv#G)QO&cA~GgjjRs#u5-s3*!DpQ>CtHl`I?)ag5AetML@Do=ZX5V z>2Oj(@;aOOj~(+%Q3b@O_z7C*!M zcFzN{_mY||7yZ#tLMvGWJB$@85*B{DCJl*T4*au$?r zOzS?shp@Wt;_2Ri;ml7PL$U8(J6X=XC-jWm6pFstlj^5Ab*ulBe*e=Wo`2+QzB64C z_opqs77MNU*{uK`r%>LwnN@pzx;Z&6565T+V}gX`uC8d?(;aBJYqE0Mvn^Vscg{D8 zqw%wzsLE3A7w(=ja0yV~dK7-@SR>y#l`>O5b`5ur)~`35h!78;HO&?dI}__<=Uz1> zW8RbIQ5lnuilx+iNSKCp^YKD1pmoLS#gJ7!$MD;turB7dCyyULZ3ETiZ+~<0)q}uZ z_U+~XQ7cuQ)|`W#6hlXMm6Rr1WsghJcZ&-82`=LUcLeL)260+!WQN6dYpCktfAXEX zpc)0CX%oH{H-*mlKwWBQsk8rUpdqMCZcJG@-j@5_C>uODan`VQTV4k%rwIC9y#1Gw z^ZfMhk@!7g_pT4w533zT7mbNKBqNo#A|#0Qyy>Q`iq zBi$kbb)d;+U+m#6Qr?XPsh~4Zl&KBH^rZ{!zKU|bHt-_$!s8j!GgFy|OP_82(6Xv} zv*+`KsmvYO8w0wz8pfI4oc@*3kLRKmO3+Vr)#b&+zFMJMd3Ko8-L&lB$x%jk#fe2| z%fQkJN<)xNlUw;N@AA{F`aM6d4XWRJ$(_U2$x}Q%8w{_-)r9p@#*y@YCf2LcmD~P( z8y!&gQva<%9*=OKIjcw2w(iIk`MuhytwS1<$AZYzs-mjV^|jH1U9m^2{D!+ID#A=` z>6sB5x^*~gU-P?{Ran9^bz!mW!|ay8f%xQ-EvT!ys%|ZoUGspuJrF-s>0jGgmn*ba b@5nNEb)?b(AET^Y{v{~917$Uy@!$OqQYX%U literal 0 HcmV?d00001 diff --git a/anime/src/en/animepahe/source.dart b/anime/src/en/animepahe/source.dart new file mode 100644 index 00000000..2635e4b8 --- /dev/null +++ b/anime/src/en/animepahe/source.dart @@ -0,0 +1,16 @@ +import '../../../../model/source.dart'; + +Source get animepaheSource => _animepaheSource; +const _animepaheVersion = "0.0.1"; +const _animepaheSourceCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/anime/src/ar/animepahe/animepahe.dart"; +Source _animepaheSource = Source( + name: "animepahe", + baseUrl: "https://www.animepahe.ru", + lang: "en", + typeSource: "single", + iconUrl: + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/anime/src/ar/animepahe/icon.png", + sourceCodeUrl: _animepaheSourceCodeUrl, + version: _animepaheVersion, + isManga: false);