import 'package:mangayomi/bridge_lib.dart'; import 'dart:math'; class AnimesVision extends MProvider { AnimesVision({required this.source}); MSource source; final Client client = Client(); @override String get baseUrl => source.baseUrl; @override Map get headers => { "Referer": baseUrl, "Accept-Language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7", }; @override Future getPopular(int page) async { final res = (await client.get(Uri.parse(baseUrl), headers: headers)).body; final document = parseHtml(res); final elements = document.select( "div#anime-trending div.item > a.film-poster", ); List animeList = []; for (var element in elements) { var anime = MManga(); var img = element.selectFirst("img"); anime.name = img.attr("title"); anime.link = getUrlWithoutDomain(element.attr("href")); anime.imageUrl = img.attr("src"); animeList.add(anime); } return MPages(animeList, hasNextPage(document)); } @override Future getLatestUpdates(int page) async { final res = (await client.get( Uri.parse("$baseUrl/lancamentos?page=$page"), headers: headers, )).body; final document = parseHtml(res); final elements = document.select( "div.container div.screen-items > div.item", ); List animeList = []; for (var element in elements) { var anime = MManga(); anime.name = substringAfter(element.selectFirst("h3").text, "-").trim(); anime.link = getUrlWithoutDomain(element.selectFirst("a").attr("href")); anime.imageUrl = element.selectFirst("img")?.attr("src") ?? ""; animeList.add(anime); } return MPages(animeList, hasNextPage(document)); } @override Future search(String query, int page, FilterList filterList) async { final res = (await client.get( Uri.parse("$baseUrl/search-anime?nome=$query&page=$page"), )).body; final document = parseHtml(res); final elements = document.select("div.film_list-wrap div.film-poster"); List animeList = []; for (var element in elements) { var anime = MManga(); final elementA = element.selectFirst("a"); anime.name = elementA.attr("title"); anime.link = getUrlWithoutDomain(elementA.attr("href")); anime.imageUrl = element.selectFirst("img").attr("data-src"); animeList.add(anime); } return MPages(animeList, hasNextPage(document)); } @override Future getDetail(String url) async { final statusList = [ {"Atualmente sendo exibido": 0, "Fim da exibição": 1}, ]; MManga anime = MManga(); final res = (await client.get(Uri.parse("$baseUrl$url"))).body; var document = await getRealDoc(parseHtml(res), "$baseUrl$url"); final content = document.selectFirst("div#ani_detail div.anis-content"); final detail = content.selectFirst("div.anisc-detail"); final infos = content.selectFirst("div.anisc-info"); anime.imageUrl = content.selectFirst("img")?.attr("src"); anime.name = detail.selectFirst("h2.film-name").text; anime.genre = getInfo(infos, "Gêneros").split(","); anime.author = getInfo(infos, "Produtores"); anime.artist = getInfo(infos, "Estúdios"); anime.status = parseStatus(getInfo(infos, "Status"), statusList); String description = getInfo(infos, "Sinopse"); if (getInfo(infos, "Inglês").isNotEmpty) description += '\n\nTítulo em inglês: ${getInfo(infos, "Inglês")}'; anime.description = description; if (getInfo(infos, "Japonês").isNotEmpty) description += '\nTítulo em Japonês: ${getInfo(infos, "Japonês")}'; if (getInfo(infos, "Foi ao ar em").isNotEmpty) description += '\nFoi ao ar em: ${getInfo(infos, "Foi ao ar em")}'; if (getInfo(infos, "Temporada").isNotEmpty) description += '\nTemporada: ${getInfo(infos, "Temporada")}'; if (getInfo(infos, "Duração").isNotEmpty) description += '\nDuração: ${getInfo(infos, "Duração")}'; if (getInfo(infos, "Fansub").isNotEmpty) description += '\nFansub: ${getInfo(infos, "Fansub")}'; anime.description = description; List episodeList = []; for (var element in document.select("div.container div.screen-items > div.item") ?? []) { episodeList.add(episodeFromElement(element)); } while (hasNextPage(document)) { if (episodeList.isNotEmpty) { final nextUrl = nextPageElements( document, )[0].selectFirst("a").attr("href"); document = parseHtml((await client.get(Uri.parse(nextUrl))).body); } for (var element in document.select("div.container div.screen-items > div.item") ?? []) { episodeList.add(episodeFromElement(element)); } } anime.chapters = episodeList.reversed.toList(); return anime; } @override Future> getVideoList(String url) async { final res = (await client.get(Uri.parse("$baseUrl$url"))).body; final document = parseHtml(res); final encodedScript = document .selectFirst("div.player-frame div#playerglobalapi ~ script") .text; final decodedScript = decodeScriptFromString(encodedScript); List videos = []; for (RegExpMatch match in RegExp( r'"file":"(\S+?)",.*?"label":"(.*?)"', ).allMatches(decodedScript)) { final videoUrl = match.group(1)!.replaceAll('\\', ''); final qualityName = match.group(2); var video = MVideo(); video.url = videoUrl; video.headers = headers; video.quality = 'PlayerVision $qualityName'; video.originalUrl = videoUrl; videos.add(video); } return videos; } bool hasNextPage(MDocument document) { return nextPageElements(document).isNotEmpty; } List nextPageElements(MDocument document) { final elements = document .select("ul.pagination li.page-item") .where( (MElement e) => e.outerHtml.contains("›") && !e.outerHtml.contains("disabled"), ) .toList(); return elements; } Future getRealDoc(MDocument document, String originalUrl) async { if (["/episodio-", "/filme-"].any((e) => originalUrl.contains(e))) { final url = document.selectFirst("h2.film-name > a").attr("href"); final res = (await client.get(Uri.parse(url))).body; return parseHtml(res); } return document; } String getInfo(MElement element, String key) { final divs = element .select("div.item") .where((MElement e) => e.outerHtml.contains(key)) .toList(); String text = ""; if (divs.isNotEmpty) { MElement div = divs[0]; var elementsA = div.select("a[href]"); if (elementsA.isEmpty) { String selector = div.outerHtml.contains("w-hide") ? "div.text" : "span.name"; text = div.selectFirst(selector).text.trim(); } else { text = elementsA.map((MElement e) => e.text.trim()).toList().join(', '); } } return text; } MChapter episodeFromElement(MElement element) { var anime = MChapter(); anime.url = getUrlWithoutDomain(element.selectFirst("a").attr("href")); anime.name = element.selectFirst("h3").text.trim(); return anime; } List sortVideos(List videos, int sourceId) { String quality = getPreferenceValue(sourceId, "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_quality", title: "Qualidade preferida", summary: "", valueIndex: 1, entries: ["480p", "720p", "1080p", "4K"], entryValues: ["1080", "720", "480", "4K"], ), ]; } int convertToNum(String thing, int limit) { int result = 0; int i = 0; for (var n in thing.split('').reversed.toList()) { final a = int.tryParse(n) ?? 0; result += a * pow(limit, i).toInt(); i++; } return result; } String decodeScript( String encodedString, String magicStr, int offset, int limit, ) { RegExp regex = RegExp('\\w'); List parts = encodedString.split(magicStr[limit]); List decodedParts = []; for (String part in parts.sublist(0, parts.length - 1)) { String replaced = part; for (Match match in regex.allMatches(part)) { replaced = replaced.replaceFirst( match.group(0)!, magicStr.indexOf(match.group(0)!).toString(), ); } int charInt = convertToNum(replaced, limit) - offset; decodedParts.add(String.fromCharCode(charInt)); } return decodedParts.join(''); } String decodeScriptFromString(String script) { RegExp regex = RegExp(r'\}\("(\w+)",.*?"(\w+)",(\d+),(\d+),.*?\)'); Match? match = regex.firstMatch(script); if (match != null) { return decodeScript( match.group(1)!, match.group(2)!, int.tryParse(match.group(3)!) ?? 0, int.tryParse(match.group(4)!) ?? 0, ); } else { return script; } } } AnimesVision main(MSource source) { return AnimesVision(source: source); }