From ec214635f67d03fdac2a073050d22d16fa27349b Mon Sep 17 00:00:00 2001 From: kodjomoustapha <107993382+kodjodevf@users.noreply.github.com> Date: Tue, 12 Dec 2023 22:24:30 +0100 Subject: [PATCH] New source UHDMovies (EN) --- anime/source_generator.dart | 5 +- anime/src/en/uhdmovies/icon.png | Bin 0 -> 3627 bytes anime/src/en/uhdmovies/source.dart | 16 ++ anime/src/en/uhdmovies/uhdmovies-v0.0.1.dart | 236 ++++++++++++++++++ ...akufr-v0.0.5.dart => otakufr-v0.0.55.dart} | 7 +- anime/src/fr/otakufr/source.dart | 2 +- 6 files changed, 258 insertions(+), 8 deletions(-) create mode 100644 anime/src/en/uhdmovies/icon.png create mode 100644 anime/src/en/uhdmovies/source.dart create mode 100644 anime/src/en/uhdmovies/uhdmovies-v0.0.1.dart rename anime/src/fr/otakufr/{otakufr-v0.0.5.dart => otakufr-v0.0.55.dart} (99%) diff --git a/anime/source_generator.dart b/anime/source_generator.dart index d9bed9f8..3e4a2b3a 100644 --- a/anime/source_generator.dart +++ b/anime/source_generator.dart @@ -8,6 +8,7 @@ import 'src/ar/okanime/source.dart'; import 'src/en/aniwave/source.dart'; import 'src/en/gogoanime/source.dart'; import 'src/en/kisskh/source.dart'; +import 'src/en/uhdmovies/source.dart'; import 'src/fr/animesultra/source.dart'; import 'src/fr/franime/source.dart'; import 'src/fr/otakufr/source.dart'; @@ -16,7 +17,6 @@ import 'src/id/oploverz/source.dart'; import 'src/id/otakudesu/source.dart'; import 'src/it/animesaturn/source.dart'; - void main() { List _sourcesList = [ gogoanimeSource, @@ -31,7 +31,8 @@ void main() { oploverz, aniwave, ...dopeflixSourcesList, - animesaturn + animesaturn, + uhdmoviesSource ]; final List> jsonList = _sourcesList.map((source) => source.toJson()).toList(); diff --git a/anime/src/en/uhdmovies/icon.png b/anime/src/en/uhdmovies/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..073e44f12dec67ba315fb678518fbc08d8a66f32 GIT binary patch literal 3627 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 _uhdmoviesSource; +const _uhdmoviesVersion = "0.0.1"; +const _uhdmoviesSourceCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/src/en/uhdmovies/uhdmovies-v$_uhdmoviesVersion.dart"; +Source _uhdmoviesSource = Source( + name: "uhdmovies", + baseUrl: "https://uhdmovies.zip", + lang: "en", + typeSource: "single", + iconUrl: + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/src/en/uhdmovies/icon.png", + sourceCodeUrl: _uhdmoviesSourceCodeUrl, + version: _uhdmoviesVersion, + isManga: false); diff --git a/anime/src/en/uhdmovies/uhdmovies-v0.0.1.dart b/anime/src/en/uhdmovies/uhdmovies-v0.0.1.dart new file mode 100644 index 00000000..25b18c82 --- /dev/null +++ b/anime/src/en/uhdmovies/uhdmovies-v0.0.1.dart @@ -0,0 +1,236 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class UHDMovies extends MProvider { + UHDMovies(); + + @override + bool get supportsLatest => false; + + @override + Future getPopular(MSource source, int page) async { + final data = {"url": "${preferenceBaseUrl(source.id)}/page/$page"}; + final res = await http('GET', json.encode(data)); + return animeFromElement(res); + } + + @override + Future getLatestUpdates(MSource source, int page) async { + return MPages([], false); + } + + @override + Future search( + MSource source, String query, int page, FilterList filterList) async { + final url = + '${preferenceBaseUrl(source.id)}/page/$page/?s=${query.replaceAll(" ", "+")}'; + final data = {"url": url}; + final res = await http('GET', json.encode(data)); + return animeFromElement(res); + } + + @override + Future getDetail(MSource source, String url) async { + url = Uri.parse(url).path; + final data = {"url": "${preferenceBaseUrl(source.id)}${url}"}; + String res = await http('GET', json.encode(data)); + MManga anime = MManga(); + final description = xpath(res, '//pre/span/text()'); + if (description.isNotEmpty) { + anime.description = description.first; + } + anime.status = MStatus.ongoing; + final episodesTitles = xpath(res, + '//*[contains(@style, "center") or contains(@class, "maxbutton")]/a[contains(@class, "maxbutton") or contains(@href, "?sid=")]/text()'); + final episodesUrls = xpath(res, + '//*[contains(@style, "center") or contains(@class, "maxbutton")]/a[contains(@class, "maxbutton") or contains(@href, "?sid=")]/@href'); + bool isSeries = false; + if (episodesTitles.first.contains("Episode") || + episodesTitles.first.contains("Zip") || + episodesTitles.first.contains("Pack")) { + isSeries = true; + } + List? episodesList = []; + if (!isSeries) { + final moviesTitles = xpath(res, + '//*[contains(@style, "center") or contains(@class, "maxbutton")]/parent::p//preceding-sibling::p[contains(@style, "center")]/text()'); + List titles = []; + for (var title in moviesTitles) { + if (title.isNotEmpty && !title.contains('Download')) { + titles.add(title.split('[').first.trim()); + } + } + for (var i = 0; i < titles.length; i++) { + final title = titles[i]; + final url = episodesUrls[i]; + MChapter ep = MChapter(); + ep.name = title; + ep.url = url; + episodesList.add(ep); + } + } else { + List seasonTitles = []; + final episodeTitles = xpath(res, + '//*[contains(@style, "center") or contains(@class, "maxbutton")]/parent::p//preceding-sibling::p[contains(@style, "center") and not(text()^="Episode")]/text()'); + List titles = []; + for (var title in episodeTitles) { + if (title.isNotEmpty) { + titles.add(title.split('[').first.trim()); + } + } + int number = 0; + for (var i = 0; i < episodesTitles.length; i++) { + final episode = episodesTitles[i]; + final episodeUrl = episodesUrls[i]; + if (!episode.contains("Zip") || !episode.contains("Pack")) { + if (episode == "Episode 1" && seasonTitles.contains("Episode 1")) { + number++; + } else if (episode == "Episode 1") { + seasonTitles.add(episode); + } + final season = + RegExp(r'S(\d{2})').firstMatch(titles[number]).group(1); + final quality = + RegExp(r'\d{3,4}p').firstMatch(titles[number]).group(0); + MChapter ep = MChapter(); + ep.name = "Season $season $episode $quality"; + ep.url = episodeUrl; + episodesList.add(ep); + } + } + } + anime.chapters = episodesList.reversed.toList(); + return anime; + } + + @override + Future> getVideoList(MSource source, String url) async { + final res = await getMediaUrl(url); + return await extractVideos(res); + } + + @override + List getSourcePreferences(MSource source) { + return [ + EditTextPreference( + key: "pref_domain", + title: "Currently used domain", + summary: "", + value: "https://uhdmovies.zip", + dialogTitle: "Currently used domain", + dialogMessage: "", + text: "https://uhdmovies.zip"), + ]; + } + + String preferenceBaseUrl(int sourceId) { + return getPreferenceValue(sourceId, "pref_domain"); + } + + Future> extractVideos(String url) async { + List videos = []; + for (int type = 1; type < 3; type++) { + url = url.replaceAll("/file/", "/wfile/") + "?type=$type"; + final res = await http('GET', json.encode({"url": url})); + final links = xpath(res, '//div[@class="mb-4"]/a/@href'); + for (int i = 0; i < links.length; i++) { + final link = links[i]; + String decodedLink = link; + if (!link.contains("workers.dev")) { + decodedLink = utf8 + .decode(base64Url.decode(substringAfter(link, "download?url="))); + } + MVideo video = MVideo(); + video + ..url = decodedLink + ..originalUrl = decodedLink + ..quality = "CF $type Worker ${i + 1}"; + videos.add(video); + } + } + return videos; + } + + Future getMediaUrl(String url) async { + String res = ""; + String host = ""; + if (url.contains("?sid=")) { + final finalUrl = await redirectorBypasser(url); + host = Uri.parse(finalUrl).host; + res = await http('GET', json.encode({"url": finalUrl})); + } else if (url.contains("r?key=")) { + res = await http('GET', json.encode({"url": url})); + host = Uri.parse(url).host; + } else { + return ""; + } + final path = substringBefore(substringAfter(res, "replace(\""), "\""); + if (path == "/404") return ""; + return "https://$host$path"; + } + + Future redirectorBypasser(String url) async { + final res = await http('GET', json.encode({"url": url})); + String lastDoc = await recursiveDoc(url, res); + final js = xpath(lastDoc, '//script[contains(text(), "/?go=")]/text()'); + if (js.isEmpty) return ""; + String script = js.first; + String nextUrl = + substringBefore(substringAfter(script, "\"href\",\""), '"'); + if (!nextUrl.contains("http")) return ""; + String cookieName = substringAfter(nextUrl, "go="); + String cookieValue = + substringBefore(substringAfter(script, "'$cookieName', '"), "'"); + final response = await http( + 'GET', + json.encode({ + "url": nextUrl, + "headers": {"referer": url, "Cookie": "$cookieName=$cookieValue"} + })); + final lastRes = querySelectorAll(response, + selector: "meta[http-equiv]", + typeElement: 3, + attributes: "content", + typeRegExp: 0) + .first; + return substringAfter(lastRes, "url="); + } + + MPages animeFromElement(String res) { + List animeList = []; + final urls = xpath(res, '//*[@class="entry-image"]/a/@href'); + final names = xpath(res, '//*[@class="entry-image"]/a/@title'); + final images = xpath(res, '//*[@class="entry-image"]/a/img/@src'); + + for (var i = 0; i < names.length; i++) { + MManga anime = MManga(); + anime.name = names[i].replaceAll("Download", ""); + anime.imageUrl = images[i]; + anime.link = urls[i]; + animeList.add(anime); + } + final nextPage = xpath(res, '//a[@class="next page-numbers"]/@href'); + return MPages(animeList, nextPage.isNotEmpty); + } + + Future recursiveDoc(String url, String html) async { + final urlR = xpath(html, '//form[@id="landing"]/@action'); + if (urlR.isEmpty) return html; + final name = xpath(html, '//input/@name').first; + final value = xpath(html, '//input/@value').first; + final body = {"$name": value}; + final response = await http( + 'POST', + json.encode({ + "useFormBuilder": true, + "body": body, + "url": urlR.first, + "headers": {"referer": url} + })); + return recursiveDoc(url, response); + } +} + +UHDMovies main() { + return UHDMovies(); +} diff --git a/anime/src/fr/otakufr/otakufr-v0.0.5.dart b/anime/src/fr/otakufr/otakufr-v0.0.55.dart similarity index 99% rename from anime/src/fr/otakufr/otakufr-v0.0.5.dart rename to anime/src/fr/otakufr/otakufr-v0.0.55.dart index 29b40b13..86fca4d9 100644 --- a/anime/src/fr/otakufr/otakufr-v0.0.5.dart +++ b/anime/src/fr/otakufr/otakufr-v0.0.55.dart @@ -6,11 +6,8 @@ class OtakuFr extends MProvider { @override Future getPopular(MSource source, int page) async { - final data = { - "url": "${source.baseUrl}/toute-la-liste-affiches/page/$page/?q=." - }; + final data = {"url": "${source.baseUrl}/en-cours/page/$page"}; final res = await http('GET', json.encode(data)); - List animeList = []; final urls = xpath(res, '//*[@class="list"]/article/div/div/figure/a/@href'); @@ -285,7 +282,7 @@ class OtakuFr extends MProvider { return [ ListPreference( key: "preferred_quality", - title: "Preferred Quality", + title: "Qualité préférée", summary: "", valueIndex: 1, entries: ["1080p", "720p", "480p", "360p"], diff --git a/anime/src/fr/otakufr/source.dart b/anime/src/fr/otakufr/source.dart index 07c2e23d..29067005 100644 --- a/anime/src/fr/otakufr/source.dart +++ b/anime/src/fr/otakufr/source.dart @@ -1,7 +1,7 @@ import '../../../../model/source.dart'; Source get otakufr => _otakufr; -const otakufrVersion = "0.0.5"; +const otakufrVersion = "0.0.55"; const otakufrCodeUrl = "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/src/fr/otakufr/otakufr-v$otakufrVersion.dart"; Source _otakufr = Source(