mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 19:01:15 +00:00
added new source: Vumeto
This commit is contained in:
BIN
dart/anime/src/en/vumeto/icon.png
Normal file
BIN
dart/anime/src/en/vumeto/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
19
dart/anime/src/en/vumeto/source.dart
Normal file
19
dart/anime/src/en/vumeto/source.dart
Normal 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,
|
||||
);
|
||||
327
dart/anime/src/en/vumeto/vumeto.dart
Normal file
327
dart/anime/src/en/vumeto/vumeto.dart
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user