import 'package:mangayomi/bridge_lib.dart'; import 'dart:convert'; class UHDMovies extends MProvider { UHDMovies({required this.source}); MSource source; final Client client = Client(); @override bool get supportsLatest => false; @override String get baseUrl => getPreferenceValue(source.id, "pref_domain_new"); @override Future getPopular(int page) async { final res = (await client.get(Uri.parse("$baseUrl/page/$page"))).body; return animeFromElement(res); } @override Future getLatestUpdates(int page) async { return MPages([], false); } @override Future search(String query, int page, FilterList filterList) async { final res = (await client.get( Uri.parse("$baseUrl/page/$page/?s=${query.replaceAll(" ", "+")}"), )).body; return animeFromElement(res); } @override Future getDetail(String url) async { url = getUrlWithoutDomain(url); final res = (await client.get(Uri.parse("$baseUrl${url}"))).body; 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) { List moviesTitles = []; moviesTitles = xpath( res, '//*[contains(@style, "center") or contains(@class, "maxbutton")]/parent::p//preceding-sibling::p[contains(@style, "center")]/text()', ); List titles = []; if (moviesTitles.isEmpty) { moviesTitles = xpath(res, '//p[contains(@style, "center")]/text()'); } for (var title in moviesTitles) { if (title.isNotEmpty && !title.contains('Download') && !title.contains('Note:') && !title.contains('Copyright')) { titles.add(title.split('[').first.trim()); } } for (var i = 0; i < titles.length; i++) { final title = titles[i]; final quality = RegExp(r'\d{3,4}p').firstMatch(title)?.group(0) ?? ""; final url = episodesUrls[i]; MChapter ep = MChapter(); ep.name = title; ep.url = url; ep.scanlator = quality; 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; ep.scanlator = quality; episodesList.add(ep); } } } anime.chapters = episodesList.reversed.toList(); return anime; } @override Future> getVideoList(String url) async { final res = await getMediaUrl(url); return await extractVideos(res); } @override List getSourcePreferences() { return [ EditTextPreference( key: "pref_domain_new", title: "Currently used domain", summary: "", value: "https://uhdmovies.fans", dialogTitle: "Currently used domain", dialogMessage: "", text: "https://uhdmovies.fans", ), ]; } Future> extractVideos(String url) async { List videos = []; for (int type = 1; type < 3; type++) { url = url.replaceAll("/file/", "/wfile/") + "?type=$type"; final res = (await client.get(Uri.parse(url))).body; 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 client.get(Uri.parse(finalUrl))).body; } else if (url.contains("r?key=")) { res = (await client.get(Uri.parse(url))).body; 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 client.get(Uri.parse(url))).body; 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 client.get( Uri.parse(nextUrl), headers: {"referer": url, "Cookie": "$cookieName=$cookieValue"}, )).body; final lastRes = parseHtml( response, ).selectFirst("meta[http-equiv]").attr("content"); 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 client.post( Uri.parse(urlR.first), headers: {"referer": url}, body: body, )).body; return recursiveDoc(url, response); } } UHDMovies main(MSource source) { return UHDMovies(source: source); }