From 99136d104580a9706b25fe55e81576dc053b555f Mon Sep 17 00:00:00 2001 From: kodjomoustapha <107993382+kodjodevf@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:46:11 +0100 Subject: [PATCH] New source: AnimesVision (PT-BR) --- anime/source_generator.dart | 4 +- anime/src/pt/animesvision/animesvision.dart | 287 ++++++++++++++++++++ anime/src/pt/animesvision/icon.png | Bin 0 -> 3437 bytes anime/src/pt/animesvision/source.dart | 16 ++ 4 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 anime/src/pt/animesvision/animesvision.dart create mode 100644 anime/src/pt/animesvision/icon.png create mode 100644 anime/src/pt/animesvision/source.dart diff --git a/anime/source_generator.dart b/anime/source_generator.dart index 521a8e25..298c0ff9 100644 --- a/anime/source_generator.dart +++ b/anime/source_generator.dart @@ -27,6 +27,7 @@ import 'src/id/nimegami/source.dart'; import 'src/id/oploverz/source.dart'; import 'src/id/otakudesu/source.dart'; import 'src/it/animesaturn/source.dart'; +import 'src/pt/animesvision/source.dart'; import 'src/sq/filma24/source.dart'; void main() { @@ -56,7 +57,8 @@ void main() { nyaaSource, yomirollSource, animepaheSource, - animetoast + animetoast, + animesvision ]; final List> jsonList = _sourcesList.map((source) => source.toJson()).toList(); diff --git a/anime/src/pt/animesvision/animesvision.dart b/anime/src/pt/animesvision/animesvision.dart new file mode 100644 index 00000000..84784a30 --- /dev/null +++ b/anime/src/pt/animesvision/animesvision.dart @@ -0,0 +1,287 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:math'; + +class AnimesVision extends MProvider { + AnimesVision({required this.source}); + + MSource source; + + final Client client = Client(source); + + @override + String get baseUrl => source.baseUrl; + + 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); +} diff --git a/anime/src/pt/animesvision/icon.png b/anime/src/pt/animesvision/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..523b2380f117d2d74d05be0ae56e6b2d9152b8f6 GIT binary patch literal 3437 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 P00000NkvXXu0mjf)d;NR literal 0 HcmV?d00001 diff --git a/anime/src/pt/animesvision/source.dart b/anime/src/pt/animesvision/source.dart new file mode 100644 index 00000000..a6784b57 --- /dev/null +++ b/anime/src/pt/animesvision/source.dart @@ -0,0 +1,16 @@ +import '../../../../model/source.dart'; + +Source get animesvision => _animesvision; +const _animesvisionVersion = "0.0.1"; +const _animesvisionCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/anime/src/pt/animesvision/animesvision.dart"; +Source _animesvision = Source( + name: "AnimesVision", + baseUrl: "https://animes.vision", + lang: "pt-br", + typeSource: "single", + iconUrl: + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/anime/src/pt/animesvision/icon.png", + sourceCodeUrl: _animesvisionCodeUrl, + version: _animesvisionVersion, + isManga: false);