Merge pull request #219 from Schnitzel5/anime/vumeto

added new source: Vumeto
This commit is contained in:
Moustapha Kodjo Amadou
2025-04-13 14:12:43 +01:00
committed by GitHub
4 changed files with 348 additions and 0 deletions

View File

@@ -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<Source> dartAnimesourceList = [
aniZoneSource,
animeonlineninjaSource,
kisskhSource,
vumetoSource,
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -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,
);

View File

@@ -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<String, String> 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<MPages> 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<MPages> 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<MPages> 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<MManga> scrapeAnimeList(MDocument document) {
List<MElement>? animeElements = document.getElementsByClassName(
'relative group border-0',
);
List<MManga> 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<MManga> 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<MChapter> 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<String, dynamic> parsedData = json.decode(jsonData);
List<dynamic> 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<List<MVideo>> 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 <script> tags found.");
return [];
}
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;
}
}
}
if (jsonData.isEmpty) {
print("Could not find video data in any script tag.");
return [];
}
final Map<String, dynamic> parsedData = json.decode(jsonData);
final List<dynamic> episodesData = parsedData['episodesData'];
List<Map<String, dynamic>> extractedData = [];
if (episodesData.isNotEmpty) {
final index = int.parse(url.split('ep=').last);
final episode = episodesData[index - 1];
for (var sub in episode['sub']) {
for (var source in sub['sources']) {
String quality = "AUTO";
if (stringContains(sub['serverName'], 'Kiwi')) {
quality = '${source['quality'].toString()}P';
}
final String serverName =
"${sub['serverName']}- SUB - ${quality ?? "AUTO"}";
final String m3u8Url = source['url'];
List<Map<String, dynamic>> subtitles = [];
for (var track in sub['tracks'] ?? []) {
if (track['kind'] == "captions") {
final String label =
(track['label'] is String)
? track['label'].toString()
: 'Unknown';
subtitles.add({
'url': track['file'],
'default': track['default'] == true,
"label": label,
});
}
}
if (source['isProxy'] == true || source['isProxy'] == 'true') {
m3u8Url = 'https://proxy.vumeto.com/fetch?url=' + m3u8Url;
}
extractedData.add({
'serverName': serverName,
'm3u8Url': m3u8Url,
'subtitles': subtitles,
});
}
}
for (var dub in episode['dub']) {
for (var source in dub['sources']) {
String quality = 'AUTO';
if (stringContains(dub['serverName'], 'Kiwi')) {
quality = '${source['quality'].toString()}P';
}
final String serverName =
"${dub['serverName']}- DUB - ${quality ?? "AUTO"}";
final String m3u8Url = source['url'];
List<Map<String, dynamic>> subtitles = [];
if (source['isProxy'] == true || source['isProxy'] == 'true') {
m3u8Url = 'https://proxy.vumeto.com/fetch?url=' + m3u8Url;
}
extractedData.add({
'serverName': serverName,
'm3u8Url': m3u8Url,
'subtitles': subtitles,
});
}
}
}
List<MVideo> data =
extractedData.map((videoData) {
MVideo video = MVideo();
video.url = videoData['m3u8Url'] ?? '';
video.url = video.url.replaceAll(
RegExp(r'\b[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.net\b'),
'stormywind74.xyz',
);
video.quality = videoData['serverName'] ?? '';
video.originalUrl = videoData['m3u8Url'] ?? '';
List<MTrack>? subtitles =
(videoData['subtitles'] as List<dynamic>?)?.map((subtitle) {
MTrack track = MTrack();
return track
..file = subtitle['url'] ?? ''
..label = subtitle['label'];
}).toList();
video.subtitles = subtitles;
return video;
}).toList();
return data;
} catch (e) {
print("Error in getVideoList: $e");
return [];
}
}
bool stringContains(String text, String search) {
return RegExp(search, caseSensitive: false).hasMatch(text);
}
@override
List<dynamic> getFilterList() {
// TODO: implement
}
@override
List<dynamic> getSourcePreferences() {
// TODO: implement
}
}
Vumeto main(MSource source) {
return Vumeto(source: source);
}