diff --git a/dart/anime/anime_source_list.dart b/dart/anime/anime_source_list.dart index 126f488a..302c95fc 100644 --- a/dart/anime/anime_source_list.dart +++ b/dart/anime/anime_source_list.dart @@ -10,6 +10,7 @@ import 'src/en/animepahe/source.dart'; import 'src/en/gogoanime/source.dart'; import 'src/en/kisskh/source.dart'; import 'src/en/nineanimetv/source.dart'; +import 'src/en/vumeto/source.dart'; import 'src/es/animeonlineninja/source.dart'; import 'src/fr/animesama/source.dart'; import 'src/fr/anizone/source.dart'; @@ -53,4 +54,5 @@ List dartAnimesourceList = [ aniZoneSource, animeonlineninjaSource, kisskhSource, + vumetoSource, ]; diff --git a/dart/anime/src/en/vumeto/icon.png b/dart/anime/src/en/vumeto/icon.png new file mode 100644 index 00000000..56be717a Binary files /dev/null and b/dart/anime/src/en/vumeto/icon.png differ diff --git a/dart/anime/src/en/vumeto/source.dart b/dart/anime/src/en/vumeto/source.dart new file mode 100644 index 00000000..c9472df2 --- /dev/null +++ b/dart/anime/src/en/vumeto/source.dart @@ -0,0 +1,19 @@ +// from https://github.com/MiraiEnoki/anymex-extensions/blob/main/dart/anime/src/en/vumeto + +import '../../../../../model/source.dart'; + +Source get vumetoSource => _vumetoSource; +const _vumetoVersion = "0.0.5"; +const _vumetoSourceCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/vumeto/vumeto.dart"; +Source _vumetoSource = Source( + name: "Vumeto", + baseUrl: "https://vumeto.com", + lang: "en", + typeSource: "single", + iconUrl: + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/vumeto/icon.png", + sourceCodeUrl: _vumetoSourceCodeUrl, + version: _vumetoVersion, + itemType: ItemType.anime, +); diff --git a/dart/anime/src/en/vumeto/vumeto.dart b/dart/anime/src/en/vumeto/vumeto.dart new file mode 100644 index 00000000..e18e51b9 --- /dev/null +++ b/dart/anime/src/en/vumeto/vumeto.dart @@ -0,0 +1,327 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class Vumeto extends MProvider { + Vumeto({required this.source}); + + MSource source; + + final Client client = Client(source); + + @override + bool get supportsLatest => true; + + @override + Map get headers => { + "Cookie": + "_ga=GA1.1.2064759276.1741681027; _ga_5HMNDC3ZE4=GS1.1.1741824276.8.1.1741824749.0.0.0", + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36", + "Referer": "https://vumeto.com/", + }; + + @override + Future getPopular(int page) async { + final url = 'https://vumeto.com/most-popular'; + final resp = await client.get(Uri.parse(url)); + + final document = parseHtml(resp.body); + + return MPages(scrapeAnimeList(document), false); + } + + @override + Future getLatestUpdates(int page) async { + final url = 'https://vumeto.com/recently-updated'; + final resp = await client.get(Uri.parse(url)); + + final document = parseHtml(resp.body); + + return MPages(scrapeAnimeList(document), false); + } + + @override + Future search(String query, int page, FilterList filterList) async { + final url = 'https://vumeto.com/search?q=$query'; + final resp = await client.get(Uri.parse(url)); + + final document = parseHtml(resp.body); + + return MPages(scrapeAnimeList(document), false); + } + + String fixUrl(String encodedUrl) { + return encodedUrl + .split('url=') + .last + .split('&w') + .first + .replaceAll('%3A', ':') + .replaceAll('%2F', '/') + .replaceAll('%3F', '?') + .replaceAll('%3D', '=') + .replaceAll('%26', '&'); + } + + List scrapeAnimeList(MDocument document) { + List? animeElements = document.getElementsByClassName( + 'relative group border-0', + ); + List results = []; + + if (animeElements != null) { + for (var anime in animeElements) { + String? title = anime.selectFirst('h3')?.text ?? ''; + String? animeUrl = anime.selectFirst('a')?.attr('href') ?? ''; + String? imageUrl = anime.selectFirst('img')?.attr('src') ?? ''; + + MManga manga = MManga(); + manga.name = title; + manga.link = + "https://vumeto.com/watch/" + + animeUrl.replaceAll('/info/', '').split('/').first + + '?ep=1'; + manga.imageUrl = fixUrl(imageUrl.split('url=').last); + + results.add(manga); + } + } + return results; + } + + @override + Future getDetail(String url) async { + final statusList = [ + {"Releasing": 0, "Finished": 1}, + ]; + + final uri = Uri.parse(url); + final resp = await client.get(uri, headers); + final document = parseHtml(resp.body); + + final description = document.selectFirst("meta[name='description']").attr("content") ?? ''; + + MStatus status = MStatus.unknown; + final statusStart = resp.body.indexOf(":", resp.body.indexOf("\\\"status\\\"")); + final statusEnd = resp.body.indexOf("\\\",", statusStart); + if (statusStart != -1 && statusEnd != -1) { + final rawStatus = resp.body.substring(statusStart + 1, statusEnd); + status = parseStatus(rawStatus.replaceAll("\\\"", ""), statusList); + } + + final genresStart = resp.body.indexOf("[", resp.body.indexOf("\\\"genres\\\":")); + final genresEnd = resp.body.indexOf("]", genresStart); + var genres = []; + if (genresStart != -1 && genresEnd != -1) { + final genreLinks = resp.body.substring(genresStart + 1, genresEnd).split(","); + genres = genreLinks.map((String e) => e.replaceAll("\\\"", "")).toList(); + } + + List chapters = []; + final scripts = document.getElementsByTagName("script"); + + String jsonData = ""; + for (var script in scripts!) { + if (script.text!.contains("episodesData")) { + final regex = RegExp( + r'self\.__next_f\.push\(\[1,".*?",null,(.*?)\]\)', + dotAll: true, + ); + final match = regex.firstMatch(script.text!); + + if (match != null && match.groupCount >= 1) { + String cleaned = match.group(1)!.replaceAll(r'\', ''); + + jsonData = cleaned.substring(0, cleaned.length - 3); + break; + } else { + print("Regex did not match."); + } + } + } + Map parsedData = json.decode(jsonData); + List episodesData = parsedData['episodesData']; + for (var ep in episodesData) { + MChapter ch = MChapter(); + final number = + (ep?['episodeNo'] ?? episodesData.indexOf(ep) + 1).toString(); + + ch.name = "Episode $number"; + + ch.url = url.split('?').first + '?ep=$number'; + + if (!chapters.any((c) => c.name == ch.name)) { + chapters.add(ch); + } + } + + MManga result = MManga(); + result.description = description; + result.status = status; + result.genre = genres; + result.chapters = chapters.reversed.toList(); + + return result; + } + + String stripTags(String htmlString) { + final RegExp exp = RegExp( + r'<[^>]*>', + multiLine: true, + caseSensitive: false, + ); + return htmlString.replaceAll(exp, '').trim(); + } + + @override + Future> getVideoList(String url) async { + try { + final resp = await client.get(Uri.parse(url), headers); + + final document = parseHtml(resp.body); + + final scripts = document.getElementsByTagName("script"); + if (scripts.isEmpty) { + print("No