mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 02:41:39 +00:00
added new source: Vumeto
This commit is contained in:
@@ -10,6 +10,7 @@ import 'src/en/animepahe/source.dart';
|
|||||||
import 'src/en/gogoanime/source.dart';
|
import 'src/en/gogoanime/source.dart';
|
||||||
import 'src/en/kisskh/source.dart';
|
import 'src/en/kisskh/source.dart';
|
||||||
import 'src/en/nineanimetv/source.dart';
|
import 'src/en/nineanimetv/source.dart';
|
||||||
|
import 'src/en/vumeto/source.dart';
|
||||||
import 'src/es/animeonlineninja/source.dart';
|
import 'src/es/animeonlineninja/source.dart';
|
||||||
import 'src/fr/animesama/source.dart';
|
import 'src/fr/animesama/source.dart';
|
||||||
import 'src/fr/anizone/source.dart';
|
import 'src/fr/anizone/source.dart';
|
||||||
@@ -53,4 +54,5 @@ List<Source> dartAnimesourceList = [
|
|||||||
aniZoneSource,
|
aniZoneSource,
|
||||||
animeonlineninjaSource,
|
animeonlineninjaSource,
|
||||||
kisskhSource,
|
kisskhSource,
|
||||||
|
vumetoSource,
|
||||||
];
|
];
|
||||||
|
|||||||
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