mirror of
https://github.com/kodjodevf/mangayomi-extensions.git
synced 2026-02-14 02:41:39 +00:00
add novel support
This commit is contained in:
@@ -10,7 +10,9 @@ import 'src/en/animepahe/source.dart';
|
|||||||
import 'src/en/dramacool/source.dart';
|
import 'src/en/dramacool/source.dart';
|
||||||
import 'src/en/gogoanime/source.dart';
|
import 'src/en/gogoanime/source.dart';
|
||||||
import 'src/en/nineanimetv/source.dart';
|
import 'src/en/nineanimetv/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/hi/yomovies/source.dart';
|
import 'src/hi/yomovies/source.dart';
|
||||||
import 'src/en/kisskh/source.dart';
|
import 'src/en/kisskh/source.dart';
|
||||||
import 'src/en/uhdmovies/source.dart';
|
import 'src/en/uhdmovies/source.dart';
|
||||||
@@ -50,5 +52,7 @@ List<Source> dartAnimesourceList = [
|
|||||||
animepaheSource,
|
animepaheSource,
|
||||||
animetoast,
|
animetoast,
|
||||||
animesvision,
|
animesvision,
|
||||||
diziwatchSource
|
diziwatchSource,
|
||||||
|
aniZoneSource,
|
||||||
|
animeonlineninjaSource
|
||||||
];
|
];
|
||||||
|
|||||||
309
dart/anime/src/es/animeonlineninja/animeonlineninja.dart
Normal file
309
dart/anime/src/es/animeonlineninja/animeonlineninja.dart
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
import 'package:mangayomi/bridge_lib.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class AnimeOnlineNinja extends MProvider {
|
||||||
|
AnimeOnlineNinja({required this.source});
|
||||||
|
|
||||||
|
MSource source;
|
||||||
|
|
||||||
|
final Client client = Client(source);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get supportsLatest => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MPages> getPopular(int page) async {
|
||||||
|
final res =
|
||||||
|
(await client.get(Uri.parse("${source.baseUrl}/tendencias"))).body;
|
||||||
|
return parseAnimeList(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MPages> search(String query, int page, FilterList filterList) async {
|
||||||
|
String pageStr = page == 1 ? "" : "page/$page/";
|
||||||
|
final res = (await client.get(Uri.parse(
|
||||||
|
"${source.baseUrl}/$pageStr?s=${query.replaceAll(" ", "+")}")))
|
||||||
|
.body;
|
||||||
|
return parseAnimeList(res,
|
||||||
|
selector: "div.result-item div.image a",
|
||||||
|
hasNextPage: parseHtml(res)
|
||||||
|
.selectFirst(
|
||||||
|
"div.pagination > *:last-child:not(span):not(.current)")
|
||||||
|
?.text !=
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MManga> getDetail(String url) async {
|
||||||
|
final res = (await client.get(Uri.parse("${source.baseUrl}$url"))).body;
|
||||||
|
MManga anime = MManga();
|
||||||
|
final document = parseHtml(res);
|
||||||
|
anime.description = document.selectFirst("div#info").text;
|
||||||
|
anime.genre = document
|
||||||
|
.selectFirst("div.sheader")
|
||||||
|
.select("div.data > div.sgeneros > a")
|
||||||
|
.map((e) => e.text)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<MChapter>? episodesList = [];
|
||||||
|
final seasonElements = document.select("div#seasons > div");
|
||||||
|
if (seasonElements.isEmpty) {
|
||||||
|
MChapter episode = MChapter();
|
||||||
|
episode.name = "Película";
|
||||||
|
episode.url = getUrlWithoutDomain(url);
|
||||||
|
episodesList.add(episode);
|
||||||
|
} else {
|
||||||
|
for (var seasonElement in seasonElements) {
|
||||||
|
final seasonName = seasonElement.selectFirst("span.se-t").text;
|
||||||
|
for (var epElement in seasonElement.select("ul.episodios > li")) {
|
||||||
|
final href = epElement.selectFirst("a[href]");
|
||||||
|
final epNum = epElement.selectFirst('div.numerando')?.text ?? "0 - 0";
|
||||||
|
MChapter episode = MChapter();
|
||||||
|
episode.name =
|
||||||
|
"Season $seasonName x ${substringAfter(epNum, '- ')} ${href.text}";
|
||||||
|
episode.url = getUrlWithoutDomain(href!.getHref);
|
||||||
|
episodesList.add(episode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anime.chapters = episodesList.reversed.toList();
|
||||||
|
return anime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<MVideo>> getVideoList(String url) async {
|
||||||
|
final res = (await client.get(Uri.parse("${source.baseUrl}$url"))).body;
|
||||||
|
final document = parseHtml(res);
|
||||||
|
final players = document.select("ul#playeroptionsul li");
|
||||||
|
List<MVideo> videos = [];
|
||||||
|
for (var player in players) {
|
||||||
|
final name = player.selectFirst("span.title").text;
|
||||||
|
final type = player.attr("data-type");
|
||||||
|
final id = player.attr("data-post");
|
||||||
|
final num = player.attr("data-nume");
|
||||||
|
final resUrl = (await client.get(Uri.parse(
|
||||||
|
"${source.baseUrl}/wp-json/dooplayer/v1/post/$id?type=$type&source=$num")))
|
||||||
|
.body;
|
||||||
|
final url =
|
||||||
|
substringBefore(substringAfter(resUrl, "\"embed_url\":\""), "\",")
|
||||||
|
.replaceAll("\\", "");
|
||||||
|
videos.addAll(await extractVideos(url, name));
|
||||||
|
}
|
||||||
|
return sortVideos(videos, source.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MVideo>> extractVideos(String url, String lang) async {
|
||||||
|
List<MVideo> videos = [];
|
||||||
|
List<MVideo> a = [];
|
||||||
|
if (url.contains("saidochesto.top") || lang == "MULTISERVER") {
|
||||||
|
return await extractFromMulti(url);
|
||||||
|
} else if (["filemoon", "moon", "filemooon"].any((a) => url.contains(a))) {
|
||||||
|
a = await filemoonExtractor(url, "$lang Filemoon - ", "");
|
||||||
|
} else if (["https://dood", "https://ds2play", "https://d0"]
|
||||||
|
.any((a) => url.contains(a))) {
|
||||||
|
a = await doodExtractor(url, "$lang DoodStream");
|
||||||
|
} else if (["streamtape", "stp", "stape"].any((a) => url.contains(a))) {
|
||||||
|
a = await streamTapeExtractor(url, "$lang StreamTape");
|
||||||
|
} else if (url.contains("uqload")) {
|
||||||
|
a = await uqloadExtractor(url, lang);
|
||||||
|
} else if (url.contains("wolfstream")) {
|
||||||
|
final resUrl = (await client.get(Uri.parse(url))).body;
|
||||||
|
final jsData =
|
||||||
|
parseHtml(resUrl).selectFirst("script:contains(sources)").text;
|
||||||
|
final videoUrl =
|
||||||
|
substringBefore(substringAfter(jsData, "{file:\""), "\"");
|
||||||
|
|
||||||
|
MVideo video = MVideo();
|
||||||
|
video
|
||||||
|
..url = videoUrl
|
||||||
|
..originalUrl = videoUrl
|
||||||
|
..quality = "$lang WolfStream";
|
||||||
|
|
||||||
|
a = [video];
|
||||||
|
} else if (["wishembed", "streamwish", "strwish", "wish"]
|
||||||
|
.any((a) => url.contains(a))) {
|
||||||
|
a = await streamWishExtractor(url, "$lang StreamWish");
|
||||||
|
} else if (url.contains("mp4upload")) {
|
||||||
|
a = await mp4UploadExtractor(url, null, "$lang", "");
|
||||||
|
} else if (["vidhide", "filelions.top", "vid."]
|
||||||
|
.any((a) => url.contains(a))) {
|
||||||
|
a = await streamHideExtractor(url, lang);
|
||||||
|
}
|
||||||
|
videos.addAll(a);
|
||||||
|
|
||||||
|
return videos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MVideo>> streamHideExtractor(String url, String prefix) async {
|
||||||
|
final res = (await client.get(Uri.parse(url))).body;
|
||||||
|
final masterUrl = substringBefore(
|
||||||
|
substringAfter(
|
||||||
|
substringAfter(
|
||||||
|
substringAfter(unpackJs(res), "sources:"), "file:\""),
|
||||||
|
"src:\""),
|
||||||
|
'"');
|
||||||
|
final masterPlaylistRes = (await client.get(Uri.parse(masterUrl))).body;
|
||||||
|
List<MVideo> videos = [];
|
||||||
|
for (var it in substringAfter(masterPlaylistRes, "#EXT-X-STREAM-INF:")
|
||||||
|
.split("#EXT-X-STREAM-INF:")) {
|
||||||
|
final quality =
|
||||||
|
"${substringBefore(substringBefore(substringAfter(substringAfter(it, "RESOLUTION="), "x"), ","), "\n")}p";
|
||||||
|
|
||||||
|
String videoUrl = substringBefore(substringAfter(it, "\n"), "\n");
|
||||||
|
|
||||||
|
if (!videoUrl.startsWith("http")) {
|
||||||
|
videoUrl =
|
||||||
|
"${masterUrl.split("/").sublist(0, masterUrl.split("/").length - 1).join("/")}/$videoUrl";
|
||||||
|
}
|
||||||
|
|
||||||
|
MVideo video = MVideo();
|
||||||
|
video
|
||||||
|
..url = videoUrl
|
||||||
|
..originalUrl = videoUrl
|
||||||
|
..quality = "$prefix StreamHideVid - $quality";
|
||||||
|
videos.add(video);
|
||||||
|
}
|
||||||
|
return videos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MVideo>> uqloadExtractor(String url, String lang) async {
|
||||||
|
final res = (await client.get(Uri.parse(url))).body;
|
||||||
|
final js = xpath(res, '//script[contains(text(), "sources:")]/text()');
|
||||||
|
if (js.isEmpty) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final videoUrl =
|
||||||
|
substringBefore(substringAfter(js.first, "sources: [\""), '"');
|
||||||
|
MVideo video = MVideo();
|
||||||
|
video
|
||||||
|
..url = videoUrl
|
||||||
|
..originalUrl = videoUrl
|
||||||
|
..quality = "$lang Uqload"
|
||||||
|
..headers = {"Referer": "${Uri.parse(url).origin}/"};
|
||||||
|
return [video];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MVideo>> extractFromMulti(String url) async {
|
||||||
|
final res = (await client.get(Uri.parse(url))).body;
|
||||||
|
final document = parseHtml(res);
|
||||||
|
|
||||||
|
final prefLang = getPreferenceValue(source.id, "preferred_lang");
|
||||||
|
String langSelector = "";
|
||||||
|
if (prefLang.isEmpty) {
|
||||||
|
langSelector = "div";
|
||||||
|
} else {
|
||||||
|
langSelector = "div.OD_$prefLang";
|
||||||
|
}
|
||||||
|
List<MVideo> videos = [];
|
||||||
|
for (var element in document.select("div.ODDIV $langSelector > li")) {
|
||||||
|
final hosterUrl =
|
||||||
|
substringBefore(substringAfter(element.attr("onclick"), "('"), "')");
|
||||||
|
String lang = "";
|
||||||
|
if (langSelector == "div") {
|
||||||
|
lang = substringBefore(
|
||||||
|
substringAfter(element.parent?.attr("class"), "OD_", ""), " ");
|
||||||
|
} else {
|
||||||
|
lang = prefLang;
|
||||||
|
}
|
||||||
|
videos.addAll(await extractVideos(hosterUrl, lang));
|
||||||
|
}
|
||||||
|
|
||||||
|
return videos;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPages parseAnimeList(String res,
|
||||||
|
{String selector = "article.w_item_a > a", bool hasNextPage = false}) {
|
||||||
|
final elements = parseHtml(res).select(selector);
|
||||||
|
List<MManga> animeList = [];
|
||||||
|
for (var element in elements) {
|
||||||
|
final url = getUrlWithoutDomain(element.getHref);
|
||||||
|
if (!url.startsWith("/episodio/")) {
|
||||||
|
MManga anime = MManga();
|
||||||
|
final img = element.selectFirst("img");
|
||||||
|
anime.name = img.attr("alt");
|
||||||
|
anime.imageUrl = img?.attr("data-src") ??
|
||||||
|
img?.attr("data-lazy-src") ??
|
||||||
|
img?.attr("srcset") ??
|
||||||
|
img?.getSrc;
|
||||||
|
anime.link = url;
|
||||||
|
animeList.add(anime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MPages(animeList, hasNextPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<dynamic> getSourcePreferences() {
|
||||||
|
return [
|
||||||
|
ListPreference(
|
||||||
|
key: "preferred_lang",
|
||||||
|
title: "Preferred language",
|
||||||
|
summary: "",
|
||||||
|
valueIndex: 0,
|
||||||
|
entries: ["SUB", "All", "ES", "LAT"],
|
||||||
|
entryValues: ["SUB", "", "ES", "LAT"]),
|
||||||
|
ListPreference(
|
||||||
|
key: "preferred_server1",
|
||||||
|
title: "Preferred server",
|
||||||
|
summary: "",
|
||||||
|
valueIndex: 0,
|
||||||
|
entries: [
|
||||||
|
"Filemoon",
|
||||||
|
"DoodStream",
|
||||||
|
"StreamTape",
|
||||||
|
"Uqload",
|
||||||
|
"WolfStream",
|
||||||
|
"saidochesto.top",
|
||||||
|
"VidHide",
|
||||||
|
"StreamWish",
|
||||||
|
"Mp4Upload"
|
||||||
|
],
|
||||||
|
entryValues: [
|
||||||
|
"Filemoon",
|
||||||
|
"DoodStream",
|
||||||
|
"StreamTape",
|
||||||
|
"Uqload",
|
||||||
|
"WolfStream",
|
||||||
|
"saidochesto.top",
|
||||||
|
"VidHide",
|
||||||
|
"StreamWish",
|
||||||
|
"Mp4Upload"
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
||||||
|
String prefLang = getPreferenceValue(source.id, "preferred_lang");
|
||||||
|
String server = getPreferenceValue(sourceId, "preferred_server1");
|
||||||
|
videos.sort((MVideo a, MVideo b) {
|
||||||
|
int qualityMatchA = 0;
|
||||||
|
|
||||||
|
if (a.quality.toLowerCase().contains(prefLang.toLowerCase()) &&
|
||||||
|
a.quality.toLowerCase().contains(server.toLowerCase())) {
|
||||||
|
qualityMatchA = 1;
|
||||||
|
}
|
||||||
|
int qualityMatchB = 0;
|
||||||
|
if (b.quality.toLowerCase().contains(prefLang.toLowerCase()) &&
|
||||||
|
b.quality.toLowerCase().contains(server.toLowerCase())) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimeOnlineNinja main(MSource source) {
|
||||||
|
return AnimeOnlineNinja(source: source);
|
||||||
|
}
|
||||||
BIN
dart/anime/src/es/animeonlineninja/icon.png
Normal file
BIN
dart/anime/src/es/animeonlineninja/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.9 KiB |
16
dart/anime/src/es/animeonlineninja/source.dart
Normal file
16
dart/anime/src/es/animeonlineninja/source.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import '../../../../../model/source.dart';
|
||||||
|
|
||||||
|
Source get animeonlineninjaSource => _animeonlineninjaSource;
|
||||||
|
const _animeonlineninjaVersion = "0.0.2";
|
||||||
|
const _animeonlineninjaSourceCodeUrl =
|
||||||
|
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/es/animeonlineninja/animeonlineninja.dart";
|
||||||
|
Source _animeonlineninjaSource = Source(
|
||||||
|
name: "AnimeOnline.Ninja",
|
||||||
|
baseUrl: "https://ww3.animeonline.ninja",
|
||||||
|
lang: "es",
|
||||||
|
typeSource: "single",
|
||||||
|
iconUrl:
|
||||||
|
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/es/animeonlineninja/icon.png",
|
||||||
|
sourceCodeUrl: _animeonlineninjaSourceCodeUrl,
|
||||||
|
version: _animeonlineninjaVersion,
|
||||||
|
isManga: false);
|
||||||
490
dart/anime/src/fr/anizone/anizone.dart
Normal file
490
dart/anime/src/fr/anizone/anizone.dart
Normal file
@@ -0,0 +1,490 @@
|
|||||||
|
import 'package:mangayomi/bridge_lib.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class AniZone extends MProvider {
|
||||||
|
AniZone({required this.source});
|
||||||
|
|
||||||
|
final MSource source;
|
||||||
|
final Client client = Client(source);
|
||||||
|
|
||||||
|
// Constants for the xpath
|
||||||
|
static const String urlXpath =
|
||||||
|
'//*[contains(@class,"flw-item item-qtip")]/div[@class="film-poster"]/a/@href';
|
||||||
|
static const String nameXpath =
|
||||||
|
'//*[contains(@class,"flw-item item-qtip")]/div[@class="film-detail"]/h3/text()';
|
||||||
|
static const String imageXpath =
|
||||||
|
'//*[contains(@class,"flw-item item-qtip")]/div[@class="film-poster"]/img/@data-src';
|
||||||
|
|
||||||
|
// Methods for fetching the manga list (popular, latest & search)
|
||||||
|
Future<MPages> _getMangaList(String url) async {
|
||||||
|
final doc = (await client.get(Uri.parse(url))).body;
|
||||||
|
List<MManga> animeList = [];
|
||||||
|
|
||||||
|
final urls = xpath(doc, urlXpath);
|
||||||
|
final names = xpath(doc, nameXpath);
|
||||||
|
final images = xpath(doc, imageXpath);
|
||||||
|
|
||||||
|
if (urls.isEmpty || names.isEmpty || images.isEmpty) {
|
||||||
|
return MPages([], false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < names.length; i++) {
|
||||||
|
MManga anime = MManga();
|
||||||
|
anime.name = names[i];
|
||||||
|
anime.imageUrl = images[i];
|
||||||
|
anime.link = urls[i];
|
||||||
|
animeList.add(anime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MPages(animeList, urls.isNotEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MPages> getPopular(int page) async {
|
||||||
|
return _getMangaList("${source.baseUrl}/most-popular/?page=$page");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MPages> getLatestUpdates(int page) async {
|
||||||
|
return _getMangaList("${source.baseUrl}/recently-added/?page=$page");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<MPages> search(String query, int page, FilterList filterList) async {
|
||||||
|
String baseUrl = "${source.baseUrl}/filter?keyword=$query";
|
||||||
|
|
||||||
|
Map<String, List<String>> filterMap = {
|
||||||
|
"type": [],
|
||||||
|
"status": [],
|
||||||
|
"season": [],
|
||||||
|
"lang": [],
|
||||||
|
"genre": []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Regroupement des filtres avec une logique générique
|
||||||
|
final filterHandlers = {
|
||||||
|
"TypeFilter": "type",
|
||||||
|
"LanguageFilter": "lang",
|
||||||
|
"SaisonFilter": "season",
|
||||||
|
"StatusFilter": "status",
|
||||||
|
"GenreFilter": "genre"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var filter in filterList.filters) {
|
||||||
|
if (filterHandlers.containsKey(filter.type)) {
|
||||||
|
var key = filterHandlers[filter.type]!;
|
||||||
|
for (var stateItem in filter.state as List) {
|
||||||
|
if (stateItem.state == true) {
|
||||||
|
filterMap[key]?.add(stateItem.value as String);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//add filters to the url dynamically
|
||||||
|
for (var entry in filterMap.entries) {
|
||||||
|
List<String> values = entry.value;
|
||||||
|
if (values.isNotEmpty) {
|
||||||
|
baseUrl += '&${entry.key}=${values.join("%2C")}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _getMangaList("$baseUrl&page=$page");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<MManga> getDetail(String url) async {
|
||||||
|
MManga anime = MManga();
|
||||||
|
try {
|
||||||
|
final doc = (await client.get(Uri.parse(url))).body;
|
||||||
|
final description = xpath(doc, '//p[contains(@class,"short")]/text()');
|
||||||
|
anime.description = description.isNotEmpty ? description.first : "";
|
||||||
|
|
||||||
|
final statusList = xpath(doc,
|
||||||
|
'//div[contains(@class,"col2")]//div[contains(@class,"item")]//div[contains(@class,"item-content")]/text()');
|
||||||
|
if (statusList.isNotEmpty) {
|
||||||
|
if (statusList[0] == "Terminer") {
|
||||||
|
anime.status = MStatus.completed;
|
||||||
|
} else if (statusList[0] == "En cours") {
|
||||||
|
anime.status = MStatus.ongoing;
|
||||||
|
} else {
|
||||||
|
anime.status = MStatus.unknown;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anime.status = MStatus.unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
anime.genre = xpath(doc,
|
||||||
|
'//div[contains(@class,"item")]//div[contains(@class,"item-content")]//a[contains(@href,"genre")]/text()');
|
||||||
|
|
||||||
|
final regex = RegExp(r'(\d+)$');
|
||||||
|
final match = regex.firstMatch(url);
|
||||||
|
|
||||||
|
if (match == null) {
|
||||||
|
throw Exception('Numéro de l\'épisode non trouvé dans l\'URL.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final res = (await client.get(Uri.parse(
|
||||||
|
"${source.baseUrl}/ajax/episode/list/${match.group(1)}")))
|
||||||
|
.body;
|
||||||
|
|
||||||
|
List<MChapter> episodesList = [];
|
||||||
|
|
||||||
|
final episodeElements =
|
||||||
|
parseHtml(json.decode(res)["html"]).select(".ep-item");
|
||||||
|
|
||||||
|
// Associer chaque titre à son URL et récupérer les vidéos
|
||||||
|
for (var element in episodeElements) {
|
||||||
|
MChapter episode = MChapter();
|
||||||
|
episode.name = element.attr("title");
|
||||||
|
|
||||||
|
String id = substringAfterLast(element.attr("href"), "=");
|
||||||
|
episode.url = "${source.baseUrl}/ajax/episode/servers?episodeId=$id";
|
||||||
|
episodesList.add(episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
anime.chapters = episodesList.reversed.toList();
|
||||||
|
|
||||||
|
return anime;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Erreur lors de la récupération des détails: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<MVideo>> getVideoList(String url) async {
|
||||||
|
final videoRes = (await client
|
||||||
|
.get(Uri.parse(url), headers: {"Referer": "${source.baseUrl}/"}))
|
||||||
|
.body;
|
||||||
|
|
||||||
|
final lang = xpath(videoRes.replaceAll(r'\', ''),
|
||||||
|
'//div[contains(@class,"item server-item")]/@data-type');
|
||||||
|
final links = xpath(videoRes.replaceAll(r'\', ''),
|
||||||
|
'//div[contains(@class,"item server-item")]/@data-id');
|
||||||
|
final playersNames = xpath(videoRes.replaceAll(r'\', ''),
|
||||||
|
'//div[contains(@class,"item server-item")]/text()');
|
||||||
|
List<Map<String, String>> players = [];
|
||||||
|
for (int j = 0; j < links.length; j++) {
|
||||||
|
// schema of players https://v1.animesz.xyz/ajax/episode/servers?episodeId=(id_episode)
|
||||||
|
// schema or url https://v1.animesz.xyz/ajax/episode/sources?id=(player_id)&epid=(id_episode)
|
||||||
|
if (playersNames.isNotEmpty && playersNames[j] == "sibnet") {
|
||||||
|
final playerUrl =
|
||||||
|
"https://video.sibnet.ru/shell.php?videoid=${links[j]}";
|
||||||
|
players.add({"lang": lang[j], "player": playerUrl});
|
||||||
|
} else if (playersNames.isNotEmpty && playersNames[j] == "sendvid") {
|
||||||
|
final playerUrl = "https://sendvid.com/embed/${links[j]}";
|
||||||
|
players.add({"lang": lang[j], "player": playerUrl});
|
||||||
|
} else if (playersNames.isNotEmpty && playersNames[j] == "VidCDN") {
|
||||||
|
final playerUrl =
|
||||||
|
"https://r.vidcdn.xyz/v1/api/get_sources/${links[j].replaceFirst(RegExp(r'vidcdn$'), '')}";
|
||||||
|
players.add({"lang": lang[j], "player": playerUrl});
|
||||||
|
} else if (playersNames.isNotEmpty && playersNames[j] == "Voe") {
|
||||||
|
final playerUrl = "https://voe.sx/e/${links[j]}";
|
||||||
|
players.add({"lang": lang[j], "player": playerUrl});
|
||||||
|
} else if (playersNames.isNotEmpty && playersNames[j] == "Fmoon") {
|
||||||
|
final playerUrl =
|
||||||
|
"https://filemoon.sx/e/${links[j]}&data-realid=${links[j]}&epid=${substringAfter(url, "episodeId=")}";
|
||||||
|
players.add({"lang": lang[j], "player": playerUrl});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MVideo> videos = [];
|
||||||
|
for (var player in players) {
|
||||||
|
String lang = (player["lang"] as String).toUpperCase();
|
||||||
|
String playerUrl = player["player"];
|
||||||
|
List<MVideo> a = [];
|
||||||
|
if (playerUrl.contains("sendvid")) {
|
||||||
|
a = await sendVidExtractorr(playerUrl, "$lang ");
|
||||||
|
} else if (playerUrl.contains("sibnet.ru")) {
|
||||||
|
a = await sibnetExtractor(playerUrl, lang);
|
||||||
|
} else if (playerUrl.contains("voe.sx")) {
|
||||||
|
a = await voeExtractor(playerUrl, "$lang ");
|
||||||
|
} else if (playerUrl.contains("vidcdn")) {
|
||||||
|
a = await vidcdnExtractor(playerUrl, lang);
|
||||||
|
} else if (playerUrl.contains("filemoon")) {
|
||||||
|
a = await filemoonExtractor(playerUrl, "$lang Filemoon - ", "");
|
||||||
|
} else if (playerUrl.contains("vidhide")) {
|
||||||
|
a = await streamHideExtractor(playerUrl, lang);
|
||||||
|
}
|
||||||
|
videos.addAll(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortVideos(videos, source.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MVideo>> streamHideExtractor(String url, String prefix) async {
|
||||||
|
final res = (await client.get(Uri.parse(url))).body;
|
||||||
|
final masterUrl = substringBefore(
|
||||||
|
substringAfter(
|
||||||
|
substringAfter(
|
||||||
|
substringAfter(unpackJs(res), "sources:"), "file:\""),
|
||||||
|
"src:\""),
|
||||||
|
'"');
|
||||||
|
final masterPlaylistRes = (await client.get(Uri.parse(masterUrl))).body;
|
||||||
|
List<MVideo> videos = [];
|
||||||
|
for (var it in substringAfter(masterPlaylistRes, "#EXT-X-STREAM-INF:")
|
||||||
|
.split("#EXT-X-STREAM-INF:")) {
|
||||||
|
final quality =
|
||||||
|
"${substringBefore(substringBefore(substringAfter(substringAfter(it, "RESOLUTION="), "x"), ","), "\n")}p";
|
||||||
|
|
||||||
|
String videoUrl = substringBefore(substringAfter(it, "\n"), "\n");
|
||||||
|
|
||||||
|
if (!videoUrl.startsWith("http")) {
|
||||||
|
videoUrl =
|
||||||
|
"${masterUrl.split("/").sublist(0, masterUrl.split("/").length - 1).join("/")}/$videoUrl";
|
||||||
|
}
|
||||||
|
|
||||||
|
MVideo video = MVideo();
|
||||||
|
video
|
||||||
|
..url = videoUrl
|
||||||
|
..originalUrl = videoUrl
|
||||||
|
..quality = "$prefix StreamHideVid - $quality";
|
||||||
|
videos.add(video);
|
||||||
|
}
|
||||||
|
return videos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<dynamic> getFilterList() {
|
||||||
|
return [
|
||||||
|
GroupFilter("TypeFilter", "Type", [
|
||||||
|
CheckBoxFilter("Film", "1"),
|
||||||
|
CheckBoxFilter("Anime", "2"),
|
||||||
|
CheckBoxFilter("OVA", "3"),
|
||||||
|
CheckBoxFilter("ONA", "4"),
|
||||||
|
CheckBoxFilter("Special", "5"),
|
||||||
|
CheckBoxFilter("Music", "6"),
|
||||||
|
]),
|
||||||
|
GroupFilter("LanguageFilter", "Langue", [
|
||||||
|
CheckBoxFilter("VF", "3"),
|
||||||
|
CheckBoxFilter("VOSTFR", "4"),
|
||||||
|
CheckBoxFilter("Multicc", "2"),
|
||||||
|
CheckBoxFilter("EN", "1"),
|
||||||
|
]),
|
||||||
|
GroupFilter("SaisonFilter", "Saison", [
|
||||||
|
CheckBoxFilter("Printemps", "1"),
|
||||||
|
CheckBoxFilter("Été", "2"),
|
||||||
|
CheckBoxFilter("Automne", "3"),
|
||||||
|
CheckBoxFilter("Hiver", "4"),
|
||||||
|
]),
|
||||||
|
GroupFilter("StatusFilter", "Statut", [
|
||||||
|
CheckBoxFilter("Terminés", "1"),
|
||||||
|
CheckBoxFilter("En cours", "2"),
|
||||||
|
CheckBoxFilter("Pas encore diffusés", "3"),
|
||||||
|
]),
|
||||||
|
GroupFilter("GenreFilter", "Genre", [
|
||||||
|
CheckBoxFilter("Action", "1"),
|
||||||
|
CheckBoxFilter("Aventure", "2"),
|
||||||
|
CheckBoxFilter("Voitures", "3"),
|
||||||
|
CheckBoxFilter("Comédie", "4"),
|
||||||
|
CheckBoxFilter("Démence", "5"),
|
||||||
|
CheckBoxFilter("Démons", "6"),
|
||||||
|
CheckBoxFilter("Drame", "8"),
|
||||||
|
CheckBoxFilter("Ecchi", "9"),
|
||||||
|
CheckBoxFilter("Fantastique", "10"),
|
||||||
|
CheckBoxFilter("Jeu", "11"),
|
||||||
|
CheckBoxFilter("Harem", "35"),
|
||||||
|
CheckBoxFilter("Historique", "13"),
|
||||||
|
CheckBoxFilter("Horreur", "14"),
|
||||||
|
CheckBoxFilter("Isekai", "44"),
|
||||||
|
CheckBoxFilter("Josei", "43"),
|
||||||
|
CheckBoxFilter("Enfants", "25"),
|
||||||
|
CheckBoxFilter("Magie", "16"),
|
||||||
|
CheckBoxFilter("Arts martiaux", "17"),
|
||||||
|
CheckBoxFilter("Mecha", "18"),
|
||||||
|
CheckBoxFilter("Militaire", "38"),
|
||||||
|
CheckBoxFilter("Musique", "19"),
|
||||||
|
CheckBoxFilter("Mystère", "7"),
|
||||||
|
CheckBoxFilter("Parodie", "20"),
|
||||||
|
CheckBoxFilter("Police", "39"),
|
||||||
|
CheckBoxFilter("Psychologique", "40"),
|
||||||
|
CheckBoxFilter("Romance", "22"),
|
||||||
|
CheckBoxFilter("Samouraï", "21"),
|
||||||
|
CheckBoxFilter("École", "23"),
|
||||||
|
CheckBoxFilter("Science-Fiction", "24"),
|
||||||
|
CheckBoxFilter("Seinen", "42"),
|
||||||
|
CheckBoxFilter("Shoujo Ai", "26"),
|
||||||
|
CheckBoxFilter("Shoujo", "25"),
|
||||||
|
CheckBoxFilter("Shounen Ai", "28"),
|
||||||
|
CheckBoxFilter("Tranche de vie", "36"),
|
||||||
|
CheckBoxFilter("Shounen", "27"),
|
||||||
|
CheckBoxFilter("Espace", "29"),
|
||||||
|
CheckBoxFilter("Sports", "30"),
|
||||||
|
CheckBoxFilter("Super Pouvoir", "31"),
|
||||||
|
CheckBoxFilter("Surnaturel", "37"),
|
||||||
|
CheckBoxFilter("Vampire", "32"),
|
||||||
|
CheckBoxFilter("Yaoi", "33"),
|
||||||
|
CheckBoxFilter("Yuri", "34"),
|
||||||
|
])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<dynamic> getSourcePreferences() {
|
||||||
|
return [
|
||||||
|
ListPreference(
|
||||||
|
key: "preferred_quality",
|
||||||
|
title: "Qualité préférée",
|
||||||
|
summary: "",
|
||||||
|
valueIndex: 0,
|
||||||
|
entries: ["1080p", "720p", "480p", "360p"],
|
||||||
|
entryValues: ["1080", "720", "480", "360"]),
|
||||||
|
ListPreference(
|
||||||
|
key: "voices_preference",
|
||||||
|
title: "Préférence des voix",
|
||||||
|
summary: "",
|
||||||
|
valueIndex: 0,
|
||||||
|
entries: ["Préférer VOSTFR", "Préférer VF"],
|
||||||
|
entryValues: ["vostfr", "vf"]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
||||||
|
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
||||||
|
String voice = getPreferenceValue(sourceId, "voices_preference");
|
||||||
|
|
||||||
|
videos.sort((MVideo a, MVideo b) {
|
||||||
|
int qualityMatchA = 0;
|
||||||
|
if (a.quality.contains(quality) &&
|
||||||
|
a.quality.toLowerCase().contains(voice)) {
|
||||||
|
qualityMatchA = 1;
|
||||||
|
}
|
||||||
|
int qualityMatchB = 0;
|
||||||
|
if (b.quality.contains(quality) &&
|
||||||
|
b.quality.toLowerCase().contains(voice)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MVideo>> sendVidExtractorr(String url, String prefix) async {
|
||||||
|
final res = (await client.get(Uri.parse(url))).body;
|
||||||
|
final document = parseHtml(res);
|
||||||
|
final masterUrl = document.selectFirst("source#video_source")?.attr("src");
|
||||||
|
print(masterUrl);
|
||||||
|
if (masterUrl == null) return [];
|
||||||
|
final masterHeaders = {
|
||||||
|
"Accept": "*/*",
|
||||||
|
"Host": Uri.parse(masterUrl).host,
|
||||||
|
"Origin": "https://${Uri.parse(url).host}",
|
||||||
|
"Referer": "https://${Uri.parse(url).host}/",
|
||||||
|
};
|
||||||
|
List<MVideo> videos = [];
|
||||||
|
if (masterUrl.contains(".m3u8")) {
|
||||||
|
final masterPlaylistRes = (await client.get(Uri.parse(masterUrl))).body;
|
||||||
|
|
||||||
|
for (var it in substringAfter(masterPlaylistRes, "#EXT-X-STREAM-INF:")
|
||||||
|
.split("#EXT-X-STREAM-INF:")) {
|
||||||
|
final quality =
|
||||||
|
"${substringBefore(substringBefore(substringAfter(substringAfter(it, "RESOLUTION="), "x"), ","), "\n")}p";
|
||||||
|
|
||||||
|
String videoUrl = substringBefore(substringAfter(it, "\n"), "\n");
|
||||||
|
|
||||||
|
if (!videoUrl.startsWith("http")) {
|
||||||
|
videoUrl =
|
||||||
|
"${masterUrl.split("/").sublist(0, masterUrl.split("/").length - 1).join("/")}/$videoUrl";
|
||||||
|
}
|
||||||
|
final videoHeaders = {
|
||||||
|
"Accept": "*/*",
|
||||||
|
"Host": Uri.parse(videoUrl).host,
|
||||||
|
"Origin": "https://${Uri.parse(url).host}",
|
||||||
|
"Referer": "https://${Uri.parse(url).host}/",
|
||||||
|
};
|
||||||
|
var video = MVideo();
|
||||||
|
video
|
||||||
|
..url = videoUrl
|
||||||
|
..originalUrl = videoUrl
|
||||||
|
..quality = prefix + "Sendvid:$quality"
|
||||||
|
..headers = videoHeaders;
|
||||||
|
videos.add(video);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var video = MVideo();
|
||||||
|
video
|
||||||
|
..url = masterUrl
|
||||||
|
..originalUrl = masterUrl
|
||||||
|
..quality = prefix + "Sendvid:default"
|
||||||
|
..headers = masterHeaders;
|
||||||
|
videos.add(video);
|
||||||
|
}
|
||||||
|
|
||||||
|
return videos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<MVideo>> vidcdnExtractor(String url, String prefix) async {
|
||||||
|
final res = await client.get(Uri.parse(url));
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
print("Erreur lors de la récupération de la page : ${res.statusCode}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
final jsonResponse = jsonDecode(res.body);
|
||||||
|
|
||||||
|
String masterUrl = jsonResponse['sources'][0]['file'] ?? '';
|
||||||
|
final quality = jsonResponse['quality'] ?? '';
|
||||||
|
|
||||||
|
List<MVideo> videos = [];
|
||||||
|
|
||||||
|
final masterPlaylistRes = await client.get(Uri.parse(masterUrl));
|
||||||
|
if (masterPlaylistRes.statusCode != 200) {
|
||||||
|
print(
|
||||||
|
"Error lors de la récupération de la playlist M3U8 : ${masterPlaylistRes.statusCode}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final masterPlaylistBody = masterPlaylistRes.body;
|
||||||
|
|
||||||
|
final playlistLines = masterPlaylistBody.split("\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < playlistLines.length; i++) {
|
||||||
|
final line = playlistLines[i];
|
||||||
|
if (line.startsWith("#EXT-X-STREAM-INF")) {
|
||||||
|
final resolutionLine = line.split("RESOLUTION=").last;
|
||||||
|
final resolution = resolutionLine.split(",").first;
|
||||||
|
final width = int.parse(resolution.split("x").first);
|
||||||
|
final height = int.parse(resolution.split("x").last);
|
||||||
|
|
||||||
|
String videoQuality;
|
||||||
|
if (height >= 1080) {
|
||||||
|
videoQuality = "1080p";
|
||||||
|
} else if (height >= 720) {
|
||||||
|
videoQuality = "720p";
|
||||||
|
} else if (height >= 480) {
|
||||||
|
videoQuality = "480p";
|
||||||
|
} else if (height >= 360) {
|
||||||
|
videoQuality = "360p";
|
||||||
|
} else {
|
||||||
|
videoQuality = "${height}p";
|
||||||
|
}
|
||||||
|
|
||||||
|
String videoUrl = playlistLines[i + 1].trim();
|
||||||
|
|
||||||
|
if (!videoUrl.startsWith("http")) {
|
||||||
|
videoUrl =
|
||||||
|
"${masterUrl.substring(0, masterUrl.lastIndexOf('/'))}/$videoUrl";
|
||||||
|
}
|
||||||
|
|
||||||
|
var video = MVideo();
|
||||||
|
video
|
||||||
|
..url = masterUrl
|
||||||
|
..originalUrl = masterUrl
|
||||||
|
..quality = "$prefix VidCDN:$videoQuality";
|
||||||
|
videos.add(video);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return videos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AniZone main(MSource source) {
|
||||||
|
return AniZone(source: source);
|
||||||
|
}
|
||||||
BIN
dart/anime/src/fr/anizone/icon.png
Normal file
BIN
dart/anime/src/fr/anizone/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
16
dart/anime/src/fr/anizone/source.dart
Normal file
16
dart/anime/src/fr/anizone/source.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import '../../../../../model/source.dart';
|
||||||
|
|
||||||
|
Source get aniZoneSource => _aniZoneSource;
|
||||||
|
const _aniZoneVersion = "0.0.2";
|
||||||
|
const _aniZoneSourceCodeUrl =
|
||||||
|
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/anizone/anizone.dart";
|
||||||
|
Source _aniZoneSource = Source(
|
||||||
|
name: "AniZone",
|
||||||
|
baseUrl: "https://v1.animesz.xyz",
|
||||||
|
lang: "fr",
|
||||||
|
typeSource: "single",
|
||||||
|
iconUrl:
|
||||||
|
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/anizone/icon.png",
|
||||||
|
sourceCodeUrl: _aniZoneSourceCodeUrl,
|
||||||
|
version: _aniZoneVersion,
|
||||||
|
isManga: false);
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import '../../../../../model/source.dart';
|
import '../../../../../model/source.dart';
|
||||||
|
|
||||||
Source get animesaturn => _animesaturn;
|
Source get animesaturn => _animesaturn;
|
||||||
const _animesaturnVersion = "0.0.35";
|
const _animesaturnVersion = "0.0.4";
|
||||||
const _animesaturnCodeUrl =
|
const _animesaturnCodeUrl =
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/it/animesaturn/animesaturn.dart";
|
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/it/animesaturn/animesaturn.dart";
|
||||||
Source _animesaturn = Source(
|
Source _animesaturn = Source(
|
||||||
name: "AnimeSaturn",
|
name: "AnimeSaturn",
|
||||||
baseUrl: "https://www.animesaturn.tv",
|
baseUrl: "https://www.animesaturn.cx",
|
||||||
lang: "it",
|
lang: "it",
|
||||||
typeSource: "single",
|
typeSource: "single",
|
||||||
iconUrl:
|
iconUrl:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import '../../../../model/source.dart';
|
import '../../../../model/source.dart';
|
||||||
import 'src/beastscans/beastscans.dart';
|
import 'src/beastscans/beastscans.dart';
|
||||||
import 'src/lelmanga/lelmanga.dart';
|
import 'src/lelmanga/lelmanga.dart';
|
||||||
import 'src/asurascans/asurascans.dart';
|
|
||||||
import 'src/komiklab/komiklab.dart';
|
import 'src/komiklab/komiklab.dart';
|
||||||
import 'src/azurescans/azurescans.dart';
|
import 'src/azurescans/azurescans.dart';
|
||||||
import 'src/cosmicscans/cosmicscans.dart';
|
import 'src/cosmicscans/cosmicscans.dart';
|
||||||
@@ -104,8 +103,6 @@ List<Source> _mangareaderSourcesList = [
|
|||||||
beastscansSource,
|
beastscansSource,
|
||||||
//Lelmanga (FR)
|
//Lelmanga (FR)
|
||||||
lelmangaSource,
|
lelmangaSource,
|
||||||
//Asura Scans (EN)
|
|
||||||
asurascansSource,
|
|
||||||
//KomikLab Scans (EN)
|
//KomikLab Scans (EN)
|
||||||
komiklabSource,
|
komiklabSource,
|
||||||
//Azure Scans (EN)
|
//Azure Scans (EN)
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import '../../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get asurascansSource => _asurascansSource;
|
|
||||||
|
|
||||||
Source _asurascansSource = Source(
|
|
||||||
name: "Asura Scans",
|
|
||||||
baseUrl: "https://asuratoon.com/",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "mangareader",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/multisrc/mangareader/src/asurascans/icon.png",
|
|
||||||
dateFormat: "MMM d, yyyy",
|
|
||||||
dateFormatLocale: "en_us",
|
|
||||||
);
|
|
||||||
@@ -159,6 +159,11 @@ class MangaDex extends MProvider {
|
|||||||
headers: headers))
|
headers: headers))
|
||||||
.body;
|
.body;
|
||||||
MManga manga = MManga();
|
MManga manga = MManga();
|
||||||
|
final coverUrl = jsonPathToString(
|
||||||
|
res, r'$..data.relationships[*].attributes.fileName', '');
|
||||||
|
if (coverUrl != null) {
|
||||||
|
manga.imageUrl = "https://uploads.mangadex.org/covers/${url.replaceAll("/manga/", "")}/${coverUrl}";
|
||||||
|
}
|
||||||
manga.author = jsonPathToString(
|
manga.author = jsonPathToString(
|
||||||
res, r'$..data.relationships[*].attributes.name', ', ');
|
res, r'$..data.relationships[*].attributes.name', ', ');
|
||||||
|
|
||||||
@@ -382,8 +387,6 @@ class MangaDex extends MProvider {
|
|||||||
GroupFilter("ContentRatingList", "Content rating", [
|
GroupFilter("ContentRatingList", "Content rating", [
|
||||||
CheckBoxFilter("Safe", "contentRating[]=safe", state: true),
|
CheckBoxFilter("Safe", "contentRating[]=safe", state: true),
|
||||||
CheckBoxFilter("Suggestive", "contentRating[]=suggestive", state: true),
|
CheckBoxFilter("Suggestive", "contentRating[]=suggestive", state: true),
|
||||||
CheckBoxFilter("Erotica", "contentRating[]=erotica"),
|
|
||||||
CheckBoxFilter("Pornographic", "contentRating[]=pornographic"),
|
|
||||||
]),
|
]),
|
||||||
GroupFilter("DemographicList", "Publication demographic", [
|
GroupFilter("DemographicList", "Publication demographic", [
|
||||||
CheckBoxFilter("None", "publicationDemographic[]=none"),
|
CheckBoxFilter("None", "publicationDemographic[]=none"),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import '../../../../../model/source.dart';
|
|||||||
const _apiUrl = 'https://api.mangadex.org';
|
const _apiUrl = 'https://api.mangadex.org';
|
||||||
const _baseUrl = 'https://mangadex.org';
|
const _baseUrl = 'https://mangadex.org';
|
||||||
const _isNsfw = true;
|
const _isNsfw = true;
|
||||||
const _mangadexVersion = "0.0.9";
|
const _mangadexVersion = "0.1.1";
|
||||||
const _mangadexSourceCodeUrl =
|
const _mangadexSourceCodeUrl =
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/mangadex/mangadex.dart";
|
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/manga/src/all/mangadex/mangadex.dart";
|
||||||
String _iconUrl =
|
String _iconUrl =
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
202
javascript/anime/src/all/netflixmirror.js
Normal file
202
javascript/anime/src/all/netflixmirror.js
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
const mangayomiSources = [{
|
||||||
|
"name": "NetflixMirror",
|
||||||
|
"lang": "all",
|
||||||
|
"baseUrl": "https://iosmirror.cc",
|
||||||
|
"apiUrl": "",
|
||||||
|
"iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/all.netflixmirror.png",
|
||||||
|
"typeSource": "single",
|
||||||
|
"isManga": false,
|
||||||
|
"version": "0.0.45",
|
||||||
|
"dateFormat": "",
|
||||||
|
"dateFormatLocale": "",
|
||||||
|
"pkgPath": "anime/src/all/netflixmirror.js"
|
||||||
|
}];
|
||||||
|
|
||||||
|
class DefaultExtension extends MProvider {
|
||||||
|
async getCookie() {
|
||||||
|
const addhash = new Document((await new Client().get(`${this.source.baseUrl}/home`, { "cookie": "" })).body).selectFirst("body").attr("data-addhash");
|
||||||
|
await new Client().get(`https://userverify.netmirror.app/verify?dp1=${addhash}&a=y`);
|
||||||
|
let body;
|
||||||
|
let res;
|
||||||
|
do {
|
||||||
|
res = await new Client().post(`${this.source.baseUrl}/verify2.php`, { "cookie": "" }, { "verify": addhash });
|
||||||
|
body = res.body;
|
||||||
|
} while (!body.includes('"statusup":"All Done"'));
|
||||||
|
return res.headers["set-cookie"];
|
||||||
|
}
|
||||||
|
async request(url, cookie) {
|
||||||
|
cookie = cookie ?? await this.getCookie();
|
||||||
|
return (await new Client().get(this.source.baseUrl + url, { "cookie": `ott=nf; hd=on; ${cookie}` })).body;
|
||||||
|
}
|
||||||
|
async getPopular(page) {
|
||||||
|
return await this.getPages(await this.request("/home"), ".tray-container, #top10")
|
||||||
|
}
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
return await this.getPages(await this.request("/home"), ".inner-mob-tray-container")
|
||||||
|
}
|
||||||
|
async getPages(body, selector) {
|
||||||
|
const elements = new Document(body).select(selector);
|
||||||
|
const cookie = await this.getCookie();
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
const linkElement = element.selectFirst("article, .top10-post");
|
||||||
|
const id = linkElement.selectFirst("a").attr("data-post");
|
||||||
|
if (id.length > 0) {
|
||||||
|
const imageUrl = linkElement.selectFirst(".card-img-container img, .top10-img img").attr("data-src");
|
||||||
|
list.push({ name: JSON.parse(await this.request(`/post.php?id=${id}`, cookie)).title, imageUrl, link: id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async search(query, page, filters) {
|
||||||
|
const data = JSON.parse(await this.request(`/search.php?s=${query}`));
|
||||||
|
const list = [];
|
||||||
|
data.searchResult.map(async (res) => {
|
||||||
|
const id = res.id;
|
||||||
|
list.push({ name: res.t, imageUrl: `https://img.nfmirrorcdn.top/poster/v/${id}.jpg`, link: id });
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getDetail(url) {
|
||||||
|
const cookie = await this.getCookie();
|
||||||
|
const data = JSON.parse(await this.request(`/post.php?id=${url}`, cookie));
|
||||||
|
const name = data.title;
|
||||||
|
const genre = [data.ua, ...(data.genre || '').split(',').map(g => g.trim())];
|
||||||
|
const description = data.desc;
|
||||||
|
let episodes = [];
|
||||||
|
if (data.episodes[0] === null) {
|
||||||
|
episodes.push({ name, url: JSON.stringify({ id: url, name }) });
|
||||||
|
} else {
|
||||||
|
episodes = data.episodes.map(ep => ({
|
||||||
|
name: `${ep.s.replace('S', 'Season ')} ${ep.ep.replace('E', 'Episode ')} : ${ep.t}`,
|
||||||
|
url: JSON.stringify({ id: ep.id, name })
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (data.nextPageShow === 1) {
|
||||||
|
const eps = await this.getEpisodes(name, url, data.nextPageSeason, 2, cookie);
|
||||||
|
episodes.push(...eps);
|
||||||
|
}
|
||||||
|
episodes.reverse();
|
||||||
|
if (data.season && data.season.length > 1) {
|
||||||
|
let newEpisodes = [];
|
||||||
|
const seasonsToProcess = data.season.slice(0, -1);
|
||||||
|
await Promise.all(seasonsToProcess.map(async (season) => {
|
||||||
|
const eps = await this.getEpisodes(name, url, season.id, 1, cookie);
|
||||||
|
newEpisodes.push(...eps);
|
||||||
|
}));
|
||||||
|
newEpisodes.reverse();
|
||||||
|
episodes.push(...newEpisodes);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
description, status: 1, genre, episodes
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async getEpisodes(name, eid, sid, page, cookie) {
|
||||||
|
const episodes = [];
|
||||||
|
let pg = page;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(await this.request(`/episodes.php?s=${sid}&series=${eid}&page=${pg}`, cookie));
|
||||||
|
|
||||||
|
data.episodes?.forEach(ep => {
|
||||||
|
episodes.push({
|
||||||
|
name: `${ep.s.replace('S', 'Season ')} ${ep.ep.replace('E', 'Episode ')} : ${ep.t}`,
|
||||||
|
url: JSON.stringify({ id: ep.id, name })
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.nextPageShow === 0) break;
|
||||||
|
pg++;
|
||||||
|
} catch (_) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return episodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getVideoList(url) {
|
||||||
|
const baseUrl = this.source.baseUrl;
|
||||||
|
const urlData = JSON.parse(url);
|
||||||
|
const data = JSON.parse(await this.request(`/playlist.php?id=${urlData.id}&t=${urlData.name}`));
|
||||||
|
const videoList = [];
|
||||||
|
for (const playlist of data) {
|
||||||
|
for (const source of playlist.sources) {
|
||||||
|
try {
|
||||||
|
const subtitles = [];
|
||||||
|
playlist.tracks.filter(track => track.kind === 'captions').forEach(track => {
|
||||||
|
subtitles.push({
|
||||||
|
label: track.label,
|
||||||
|
file: track.file
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const link = baseUrl + source.file;
|
||||||
|
const headers =
|
||||||
|
{
|
||||||
|
'Host': link.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/)[1],
|
||||||
|
'Origin': baseUrl,
|
||||||
|
'Referer': `${baseUrl}/`
|
||||||
|
};
|
||||||
|
const resp = await new Client().get(link, headers);
|
||||||
|
|
||||||
|
if (resp.statusCode === 200) {
|
||||||
|
const masterPlaylist = resp.body;
|
||||||
|
const audios = [];
|
||||||
|
masterPlaylist.substringAfter('#EXT-X-MEDIA:').split('#EXT-X-MEDIA:').forEach(it => {
|
||||||
|
if (it.includes('TYPE=AUDIO')) {
|
||||||
|
const audioInfo = it.substringAfter('TYPE=AUDIO').substringBefore('\n');
|
||||||
|
const language = audioInfo.substringAfter('NAME="').substringBefore('"');
|
||||||
|
const url = audioInfo.substringAfter('URI="').substringBefore('"');
|
||||||
|
audios.push({ file: url, label: language });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!masterPlaylist.includes('#EXT-X-STREAM-INF:')) {
|
||||||
|
if (audios.length === 0) {
|
||||||
|
videoList.push({ url: link, quality: source.label, originalUrl: link, subtitles, headers });
|
||||||
|
} else {
|
||||||
|
videoList.push({ url: link, quality: source.label, originalUrl: link, subtitles, audios, headers });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
masterPlaylist.substringAfter('#EXT-X-STREAM-INF:').split('#EXT-X-STREAM-INF:').forEach(it => {
|
||||||
|
|
||||||
|
const quality = `${it.substringAfter('RESOLUTION=').substringAfter('x').substringBefore(',')}p (${source.label})`;
|
||||||
|
let videoUrl = it.substringAfter('\n').substringBefore('\n');
|
||||||
|
|
||||||
|
if (!videoUrl.startsWith('http')) {
|
||||||
|
videoUrl = resp.request.url.substringBeforeLast('/') + `/${videoUrl}`;
|
||||||
|
}
|
||||||
|
const headers =
|
||||||
|
{
|
||||||
|
'Host': videoUrl.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/)[1],
|
||||||
|
'Origin': baseUrl,
|
||||||
|
'Referer': `${baseUrl}/`
|
||||||
|
};
|
||||||
|
if (audios.length === 0) {
|
||||||
|
videoList.push({ url: videoUrl, quality, originalUrl: videoUrl, subtitles, headers });
|
||||||
|
} else {
|
||||||
|
videoList.push({ url: videoUrl, quality, originalUrl: videoUrl, subtitles, audios, headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return videoList;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,16 +7,20 @@ const mangayomiSources = [{
|
|||||||
"typeSource": "single",
|
"typeSource": "single",
|
||||||
"itemType": "anime",
|
"itemType": "anime",
|
||||||
"isNsfw": false,
|
"isNsfw": false,
|
||||||
"version": "0.0.15",
|
"version": "0.0.28",
|
||||||
"dateFormat": "",
|
"dateFormat": "",
|
||||||
"dateFormatLocale": "",
|
"dateFormatLocale": "",
|
||||||
"pkgPath": "anime/src/de/aniworld.js"
|
"pkgPath": "anime/src/de/aniworld.js"
|
||||||
}];
|
}];
|
||||||
|
|
||||||
class DefaultExtension extends MProvider {
|
class DefaultExtension extends MProvider {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
this.client = new Client();
|
||||||
|
}
|
||||||
async getPopular(page) {
|
async getPopular(page) {
|
||||||
const baseUrl = this.source.baseUrl;
|
const baseUrl = this.source.baseUrl;
|
||||||
const res = await new Client().get(`${baseUrl}/beliebte-animes`);
|
const res = await this.client.get(`${baseUrl}/beliebte-animes`);
|
||||||
const elements = new Document(res.body).select("div.seriesListContainer div");
|
const elements = new Document(res.body).select("div.seriesListContainer div");
|
||||||
const list = [];
|
const list = [];
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
@@ -33,7 +37,7 @@ class DefaultExtension extends MProvider {
|
|||||||
}
|
}
|
||||||
async getLatestUpdates(page) {
|
async getLatestUpdates(page) {
|
||||||
const baseUrl = this.source.baseUrl;
|
const baseUrl = this.source.baseUrl;
|
||||||
const res = await new Client().get(`${baseUrl}/neu`);
|
const res = await this.client.get(`${baseUrl}/neu`);
|
||||||
const elements = new Document(res.body).select("div.seriesListContainer div");
|
const elements = new Document(res.body).select("div.seriesListContainer div");
|
||||||
const list = [];
|
const list = [];
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
@@ -50,13 +54,13 @@ class DefaultExtension extends MProvider {
|
|||||||
}
|
}
|
||||||
async search(query, page, filters) {
|
async search(query, page, filters) {
|
||||||
const baseUrl = this.source.baseUrl;
|
const baseUrl = this.source.baseUrl;
|
||||||
const res = await new Client().get(`${baseUrl}/animes`);
|
const res = await this.client.get(`${baseUrl}/animes`);
|
||||||
const elements = new Document(res.body).select("#seriesContainer > div > ul > li > a").filter(e => e.attr("title").toLowerCase().includes(query.toLowerCase()));
|
const elements = new Document(res.body).select("#seriesContainer > div > ul > li > a").filter(e => e.attr("title").toLowerCase().includes(query.toLowerCase()));
|
||||||
const list = [];
|
const list = [];
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
const name = element.text;
|
const name = element.text;
|
||||||
const link = element.attr("href");
|
const link = element.attr("href");
|
||||||
const img = new Document((await new Client().get(baseUrl + link)).body).selectFirst("div.seriesCoverBox img").attr("data-src");
|
const img = new Document((await this.client.get(baseUrl + link)).body).selectFirst("div.seriesCoverBox img").attr("data-src");
|
||||||
const imageUrl = baseUrl + img;
|
const imageUrl = baseUrl + img;
|
||||||
list.push({ name, imageUrl, link });
|
list.push({ name, imageUrl, link });
|
||||||
}
|
}
|
||||||
@@ -67,7 +71,7 @@ class DefaultExtension extends MProvider {
|
|||||||
}
|
}
|
||||||
async getDetail(url) {
|
async getDetail(url) {
|
||||||
const baseUrl = this.source.baseUrl;
|
const baseUrl = this.source.baseUrl;
|
||||||
const res = await new Client().get(baseUrl + url);
|
const res = await this.client.get(baseUrl + url);
|
||||||
const document = new Document(res.body);
|
const document = new Document(res.body);
|
||||||
const imageUrl = baseUrl +
|
const imageUrl = baseUrl +
|
||||||
document.selectFirst("div.seriesCoverBox img").attr("data-src");
|
document.selectFirst("div.seriesCoverBox img").attr("data-src");
|
||||||
@@ -81,22 +85,23 @@ class DefaultExtension extends MProvider {
|
|||||||
author = produzent[0].select("li").map(e => e.text).join(", ");
|
author = produzent[0].select("li").map(e => e.text).join(", ");
|
||||||
}
|
}
|
||||||
const seasonsElements = document.select("#stream > ul:nth-child(1) > li > a");
|
const seasonsElements = document.select("#stream > ul:nth-child(1) > li > a");
|
||||||
let episodes = [];
|
|
||||||
|
const promises = [];
|
||||||
|
const episodes = [];
|
||||||
for (const element of seasonsElements) {
|
for (const element of seasonsElements) {
|
||||||
const eps = await this.parseEpisodesFromSeries(element);
|
promises.push(this.parseEpisodesFromSeries(element));
|
||||||
for (const ep of eps) {
|
}
|
||||||
episodes.push(ep);
|
for (const p of (await Promise.allSettled(promises))) {
|
||||||
|
if (p.status == 'fulfilled') {
|
||||||
|
episodes.push(...p.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
episodes.reverse();
|
episodes.reverse();
|
||||||
|
return { name, imageUrl, description, author, status: 5, genre, episodes };
|
||||||
return {
|
|
||||||
name, imageUrl, description, author, status: 5, genre, episodes
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
async parseEpisodesFromSeries(element) {
|
async parseEpisodesFromSeries(element) {
|
||||||
const seasonId = element.getHref;
|
const seasonId = element.getHref;
|
||||||
const res = await new Client().get(this.source.baseUrl + seasonId);
|
const res = await this.client.get(this.source.baseUrl + seasonId);
|
||||||
const episodeElements = new Document(res.body).select("table.seasonEpisodesList tbody tr");
|
const episodeElements = new Document(res.body).select("table.seasonEpisodesList tbody tr");
|
||||||
const list = [];
|
const list = [];
|
||||||
for (const episodeElement of episodeElements) {
|
for (const episodeElement of episodeElements) {
|
||||||
@@ -105,94 +110,71 @@ class DefaultExtension extends MProvider {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
episodeFromElement(element) {
|
episodeFromElement(element) {
|
||||||
|
const titleAnchor = element.selectFirst("td.seasonEpisodeTitle a");
|
||||||
|
const episodeSpan = titleAnchor.selectFirst("span");
|
||||||
|
const url = titleAnchor.attr("href");
|
||||||
|
const episodeSeasonId = element.attr("data-episode-season-id");
|
||||||
|
let episode = episodeSpan.text.replace(/'/g, "'");
|
||||||
let name = "";
|
let name = "";
|
||||||
let url = "";
|
if (url.includes("/film")) {
|
||||||
if (element.selectFirst("td.seasonEpisodeTitle a").attr("href").includes("/film")) {
|
name = `Film ${episodeSeasonId} : ${episode}`;
|
||||||
const num = element.attr("data-episode-season-id");
|
|
||||||
name = `Film ${num}` + " : " + element.selectFirst("td.seasonEpisodeTitle a span").text;
|
|
||||||
url = element.selectFirst("td.seasonEpisodeTitle a").attr("href");
|
|
||||||
} else {
|
} else {
|
||||||
const season =
|
const seasonMatch = url.match(/staffel-(\d+)\/episode/);
|
||||||
element.selectFirst("td.seasonEpisodeTitle a").attr("href").substringAfter("staffel-").substringBefore("/episode");;
|
name = `Staffel ${seasonMatch[1]} Folge ${episodeSeasonId} : ${episode}`;
|
||||||
const num = element.attr("data-episode-season-id");
|
|
||||||
name = `Staffel ${season} Folge ${num}` + " : " + element.selectFirst("td.seasonEpisodeTitle a span").text;
|
|
||||||
url = element.selectFirst("td.seasonEpisodeTitle a").attr("href");
|
|
||||||
}
|
}
|
||||||
if (name.length > 0 && url.length > 0) {
|
return name && url ? { name, url } : {};
|
||||||
return { name, url }
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}
|
}
|
||||||
async getVideoList(url) {
|
async getVideoList(url) {
|
||||||
const baseUrl = this.source.baseUrl;
|
const baseUrl = this.source.baseUrl;
|
||||||
const res = await new Client().get(baseUrl + url);
|
const res = await this.client.get(baseUrl + url, {
|
||||||
|
'Accept': '*/*',
|
||||||
|
'Referer': baseUrl + url,
|
||||||
|
'Priority': 'u=0, i',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
|
||||||
|
});
|
||||||
const document = new Document(res.body);
|
const document = new Document(res.body);
|
||||||
const redirectlink = document.select("ul.row li");
|
let promises = [];
|
||||||
const preference = new SharedPreferences();
|
|
||||||
const hosterSelection = preference.get("hoster_selection");
|
|
||||||
const videos = [];
|
const videos = [];
|
||||||
for (const element of redirectlink) {
|
|
||||||
try {
|
const redirectsElements = document.select("ul.row li");
|
||||||
|
const hosterSelection = new SharedPreferences().get("hoster_selection_new");
|
||||||
|
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||||
|
|
||||||
|
for (const element of redirectsElements) {
|
||||||
|
const host = element.selectFirst("a h4").text;
|
||||||
|
|
||||||
|
if (hosterSelection.includes(host)) {
|
||||||
const langkey = element.attr("data-lang-key");
|
const langkey = element.attr("data-lang-key");
|
||||||
let language = "";
|
const lang = (langkey == 1 || langkey == 3) ? 'Deutscher' : 'Englischer';
|
||||||
if (langkey.includes("3")) {
|
const type = (langkey == 1) ? 'Dub' : 'Sub';
|
||||||
language = "Deutscher Sub";
|
const redirect = baseUrl + element.selectFirst("a.watchEpisode").attr("href");
|
||||||
} else if (langkey.includes("1")) {
|
promises.push((async (redirect, lang, type, host) => {
|
||||||
language = "Deutscher Dub";
|
const location = (await dartClient.get(redirect)).headers.location;
|
||||||
} else if (langkey.includes("2")) {
|
return await extractAny(location, host.toLowerCase(), lang, type, host);
|
||||||
language = "Englischer Sub";
|
})(redirect, lang, type, host));
|
||||||
}
|
}
|
||||||
const redirectgs = baseUrl + element.selectFirst("a.watchEpisode").attr("href");
|
}
|
||||||
const hoster = element.selectFirst("a h4").text;
|
for (const p of (await Promise.allSettled(promises))) {
|
||||||
|
if (p.status == 'fulfilled') {
|
||||||
if (hoster == "Streamtape" && hosterSelection.includes("Streamtape")) {
|
videos.push.apply(videos, p.value);
|
||||||
const body = (await new Client().get(redirectgs)).body;
|
|
||||||
const quality = `Streamtape ${language}`;
|
|
||||||
const vids = await streamTapeExtractor(body.match(/https:\/\/streamtape\.com\/e\/[a-zA-Z0-9]+/g)[0], quality);
|
|
||||||
for (const vid of vids) {
|
|
||||||
videos.push(vid);
|
|
||||||
}
|
|
||||||
} else if (hoster == "VOE" && hosterSelection.includes("VOE")) {
|
|
||||||
const body = (await new Client().get(redirectgs)).body;
|
|
||||||
const quality = `VOE ${language}`;
|
|
||||||
const vids = await voeExtractor(body.match(/https:\/\/voe\.sx\/e\/[a-zA-Z0-9]+/g)[0], quality);
|
|
||||||
for (const vid of vids) {
|
|
||||||
videos.push(vid);
|
|
||||||
}
|
|
||||||
} else if (hoster == "Vidoza" && hosterSelection.includes("Vidoza")) {
|
|
||||||
const body = (await new Client().get(redirectgs)).body;
|
|
||||||
const quality = `Vidoza ${language}`;
|
|
||||||
const match = body.match(/https:\/\/[^\s]*\.vidoza\.net\/[^\s]*\.mp4/g);
|
|
||||||
if (match.length > 0) {
|
|
||||||
videos.push({ url: match[0], originalUrl: match[0], quality });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.sortVideos(videos);
|
return this.sortVideos(videos);
|
||||||
}
|
}
|
||||||
sortVideos(videos) {
|
sortVideos(videos) {
|
||||||
const preference = new SharedPreferences();
|
const preference = new SharedPreferences();
|
||||||
const hoster = preference.get("preferred_hoster");
|
const hoster = RegExp(preference.get("preferred_hoster_new"));
|
||||||
const subPreference = preference.get("preferred_lang");
|
const lang = RegExp(preference.get("preferred_lang"));
|
||||||
videos.sort((a, b) => {
|
videos.sort((a, b) => {
|
||||||
let qualityMatchA = 0;
|
let qualityMatchA = hoster.test(a.quality) * lang.test(a.quality);
|
||||||
if (a.quality.includes(hoster) &&
|
let qualityMatchB = hoster.test(b.quality) * lang.test(b.quality);
|
||||||
a.quality.includes(subPreference)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
let qualityMatchB = 0;
|
|
||||||
if (b.quality.includes(hoster) &&
|
|
||||||
b.quality.includes(subPreference)) {
|
|
||||||
qualityMatchB = 1;
|
|
||||||
}
|
|
||||||
return qualityMatchB - qualityMatchA;
|
return qualityMatchB - qualityMatchA;
|
||||||
});
|
});
|
||||||
return videos;
|
return videos;
|
||||||
}
|
}
|
||||||
getSourcePreferences() {
|
getSourcePreferences() {
|
||||||
|
const hosterOptions = ["Streamtape", "VOE", "Vidoza", "Doodstream"];
|
||||||
|
const languageOptions = ["Deutscher Sub", "Deutscher Dub", "Englischer Sub"];
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
"key": "preferred_lang",
|
"key": "preferred_lang",
|
||||||
@@ -200,58 +182,92 @@ class DefaultExtension extends MProvider {
|
|||||||
"title": "Bevorzugte Sprache",
|
"title": "Bevorzugte Sprache",
|
||||||
"summary": "",
|
"summary": "",
|
||||||
"valueIndex": 0,
|
"valueIndex": 0,
|
||||||
"entries": [
|
"entries": languageOptions,
|
||||||
"Deutscher Sub",
|
"entryValues": languageOptions
|
||||||
"Deutscher Dub",
|
|
||||||
"Englischer Sub"
|
|
||||||
],
|
|
||||||
"entryValues": [
|
|
||||||
"Deutscher Sub",
|
|
||||||
"Deutscher Dub",
|
|
||||||
"Englischer Sub"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "preferred_hoster",
|
"key": "preferred_hoster_new",
|
||||||
"listPreference": {
|
"listPreference": {
|
||||||
"title": "Standard-Hoster",
|
"title": "Standard-Hoster",
|
||||||
"summary": "",
|
"summary": "",
|
||||||
"valueIndex": 0,
|
"valueIndex": 0,
|
||||||
"entries": [
|
"entries": hosterOptions,
|
||||||
"Streamtape",
|
"entryValues": hosterOptions
|
||||||
"VOE",
|
|
||||||
"Vidoza"
|
|
||||||
],
|
|
||||||
"entryValues": [
|
|
||||||
"Streamtape",
|
|
||||||
"VOE",
|
|
||||||
"Vidoza"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "hoster_selection",
|
"key": "hoster_selection_new",
|
||||||
"multiSelectListPreference": {
|
"multiSelectListPreference": {
|
||||||
"title": "Hoster auswählen",
|
"title": "Hoster auswählen",
|
||||||
"summary": "",
|
"summary": "",
|
||||||
"entries": [
|
"entries": hosterOptions,
|
||||||
"Streamtape",
|
"entryValues": hosterOptions,
|
||||||
"VOE",
|
"values": hosterOptions
|
||||||
"Vidoza"
|
|
||||||
],
|
|
||||||
"entryValues": [
|
|
||||||
"Streamtape",
|
|
||||||
"VOE",
|
|
||||||
"Vidoza"
|
|
||||||
],
|
|
||||||
"values": [
|
|
||||||
"Streamtape",
|
|
||||||
"VOE",
|
|
||||||
"Vidoza"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function doodExtractor(url) {
|
||||||
|
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||||
|
let response = await dartClient.get(url);
|
||||||
|
while ("location" in response.headers) {
|
||||||
|
response = await dartClient.get(response.headers.location);
|
||||||
|
}
|
||||||
|
const newUrl = response.request.url;
|
||||||
|
const doodhost = newUrl.match(/https:\/\/(.*?)\//, newUrl)[0].slice(8, -1);
|
||||||
|
const md5 = response.body.match(/'\/pass_md5\/(.*?)',/, newUrl)[0].slice(11, -2);
|
||||||
|
const token = md5.substring(md5.lastIndexOf("/") + 1);
|
||||||
|
const expiry = new Date().valueOf();
|
||||||
|
const randomString = getRandomString(10);
|
||||||
|
|
||||||
|
response = await new Client().get(`https://${doodhost}/pass_md5/${md5}`, { "Referer": newUrl });
|
||||||
|
const videoUrl = `${response.body}${randomString}?token=${token}&expiry=${expiry}`;
|
||||||
|
const headers = { "User-Agent": "Mangayomi", "Referer": doodhost };
|
||||||
|
return [{ url: videoUrl, originalUrl: videoUrl, headers: headers, quality: '' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function vidozaExtractor(url) {
|
||||||
|
let response = await new Client({ 'useDartHttpClient': true, "followRedirects": true }).get(url);
|
||||||
|
const videoUrl = response.body.match(/https:\/\/\S*\.mp4/)[0];
|
||||||
|
return [{ url: videoUrl, originalUrl: videoUrl, quality: '' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
_streamTapeExtractor = streamTapeExtractor;
|
||||||
|
streamTapeExtractor = async (url) => {
|
||||||
|
return await _streamTapeExtractor(url, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
_voeExtractor = voeExtractor;
|
||||||
|
voeExtractor = async (url) => {
|
||||||
|
return (await _voeExtractor(url, '')).map(v => {
|
||||||
|
v.quality = v.quality.replace(/Voe: (\d+p?)/i, '$1');
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractAny(link, method, lang, type, host) {
|
||||||
|
const m = extractAny.methods[method];
|
||||||
|
return (!m) ? [] : (await m(link)).map(v => {
|
||||||
|
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
extractAny.methods = {
|
||||||
|
'doodstream': doodExtractor,
|
||||||
|
'streamtape': streamTapeExtractor,
|
||||||
|
'vidoza': vidozaExtractor,
|
||||||
|
'voe': voeExtractor
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRandomString(length) {
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||||
|
const charArray = new Array(length);
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
charArray[i] = chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
}
|
||||||
|
return charArray.join("");
|
||||||
|
}
|
||||||
|
|||||||
273
javascript/anime/src/de/serienstream.js
Normal file
273
javascript/anime/src/de/serienstream.js
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
const mangayomiSources = [{
|
||||||
|
"name": "SerienStream",
|
||||||
|
"lang": "de",
|
||||||
|
"baseUrl": "https://s.to",
|
||||||
|
"apiUrl": "",
|
||||||
|
"iconUrl": "https://s.to/favicon.ico",
|
||||||
|
"typeSource": "single",
|
||||||
|
"isManga": false,
|
||||||
|
"isNsfw": false,
|
||||||
|
"version": "0.0.2",
|
||||||
|
"dateFormat": "",
|
||||||
|
"dateFormatLocale": "",
|
||||||
|
"pkgPath": "anime/src/de/serienstream.js"
|
||||||
|
}];
|
||||||
|
|
||||||
|
class DefaultExtension extends MProvider {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
this.client = new Client();
|
||||||
|
}
|
||||||
|
async getPopular(page) {
|
||||||
|
const baseUrl = this.source.baseUrl;
|
||||||
|
const res = await this.client.get(`${baseUrl}/beliebte-serien`);
|
||||||
|
const elements = new Document(res.body).select("div.seriesListContainer div");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
const linkElement = element.selectFirst("a");
|
||||||
|
const name = element.selectFirst("h3").text;
|
||||||
|
const imageUrl = baseUrl + linkElement.selectFirst("img").attr("data-src");
|
||||||
|
const link = linkElement.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
const baseUrl = this.source.baseUrl;
|
||||||
|
const res = await this.client.get(`${baseUrl}/neu`);
|
||||||
|
const elements = new Document(res.body).select("div.seriesListContainer div");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
const linkElement = element.selectFirst("a");
|
||||||
|
const name = element.selectFirst("h3").text;
|
||||||
|
const imageUrl = baseUrl + linkElement.selectFirst("img").attr("data-src");
|
||||||
|
const link = linkElement.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async search(query, page, filters) {
|
||||||
|
const baseUrl = this.source.baseUrl;
|
||||||
|
const res = await this.client.get(`${baseUrl}/serien`);
|
||||||
|
const elements = new Document(res.body).select("#seriesContainer > div > ul > li > a").filter(e => e.attr("title").toLowerCase().includes(query.toLowerCase()));
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
const name = element.text;
|
||||||
|
const link = element.attr("href");
|
||||||
|
const img = new Document((await this.client.get(baseUrl + link)).body).selectFirst("div.seriesCoverBox img").attr("data-src");
|
||||||
|
const imageUrl = baseUrl + img;
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getDetail(url) {
|
||||||
|
const baseUrl = this.source.baseUrl;
|
||||||
|
const res = await this.client.get(baseUrl + url);
|
||||||
|
const document = new Document(res.body);
|
||||||
|
const imageUrl = baseUrl +
|
||||||
|
document.selectFirst("div.seriesCoverBox img").attr("data-src");
|
||||||
|
const name = document.selectFirst("div.series-title h1 span").text;
|
||||||
|
const genre = document.select("div.genres ul li").map(e => e.text);
|
||||||
|
const description = document.selectFirst("p.seri_des").attr("data-full-description");
|
||||||
|
const produzent = document.select("div.cast li")
|
||||||
|
.filter(e => e.outerHtml.includes("Produzent:"));
|
||||||
|
let author = "";
|
||||||
|
if (produzent.length > 0) {
|
||||||
|
author = produzent[0].select("li").map(e => e.text).join(", ");
|
||||||
|
}
|
||||||
|
const seasonsElements = document.select("#stream > ul:nth-child(1) > li > a");
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
const episodes = [];
|
||||||
|
for (const element of seasonsElements) {
|
||||||
|
promises.push(this.parseEpisodesFromSeries(element));
|
||||||
|
}
|
||||||
|
for (const p of (await Promise.allSettled(promises))) {
|
||||||
|
if (p.status == 'fulfilled') {
|
||||||
|
episodes.push(...p.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
episodes.reverse();
|
||||||
|
return { name, imageUrl, description, author, status: 5, genre, episodes };
|
||||||
|
}
|
||||||
|
async parseEpisodesFromSeries(element) {
|
||||||
|
const seasonId = element.getHref;
|
||||||
|
const res = await this.client.get(this.source.baseUrl + seasonId);
|
||||||
|
const episodeElements = new Document(res.body).select("table.seasonEpisodesList tbody tr");
|
||||||
|
const list = [];
|
||||||
|
for (const episodeElement of episodeElements) {
|
||||||
|
list.push(this.episodeFromElement(episodeElement));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
episodeFromElement(element) {
|
||||||
|
const titleAnchor = element.selectFirst("td.seasonEpisodeTitle a");
|
||||||
|
const episodeSpan = titleAnchor.selectFirst("span");
|
||||||
|
const url = titleAnchor.attr("href");
|
||||||
|
const episodeSeasonId = element.attr("data-episode-season-id");
|
||||||
|
let episode = episodeSpan.text.replace(/'/g, "'");
|
||||||
|
let name = "";
|
||||||
|
if (url.includes("/film")) {
|
||||||
|
name = `Film ${episodeSeasonId} : ${episode}`;
|
||||||
|
} else {
|
||||||
|
const seasonMatch = url.match(/staffel-(\d+)\/episode/);
|
||||||
|
name = `Staffel ${seasonMatch[1]} Folge ${episodeSeasonId} : ${episode}`;
|
||||||
|
}
|
||||||
|
return name && url ? { name, url } : {};
|
||||||
|
}
|
||||||
|
async getVideoList(url) {
|
||||||
|
const baseUrl = this.source.baseUrl;
|
||||||
|
const res = await this.client.get(baseUrl + url, {
|
||||||
|
'Accept': '*/*',
|
||||||
|
'Referer': baseUrl + url,
|
||||||
|
'Priority': 'u=0, i',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0'
|
||||||
|
});
|
||||||
|
const document = new Document(res.body);
|
||||||
|
let promises = [];
|
||||||
|
const videos = [];
|
||||||
|
|
||||||
|
const redirectsElements = document.select("ul.row li");
|
||||||
|
const hosterSelection = new SharedPreferences().get("hoster_selection_new");
|
||||||
|
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||||
|
|
||||||
|
for (const element of redirectsElements) {
|
||||||
|
const host = element.selectFirst("a h4").text;
|
||||||
|
|
||||||
|
if (hosterSelection.includes(host)) {
|
||||||
|
const langkey = element.attr("data-lang-key");
|
||||||
|
const lang = (langkey == 1 || langkey == 3) ? 'Deutscher' : 'Englischer';
|
||||||
|
const type = (langkey == 1) ? 'Dub' : 'Sub';
|
||||||
|
const redirect = baseUrl + element.selectFirst("a.watchEpisode").attr("href");
|
||||||
|
promises.push((async (redirect, lang, type, host) => {
|
||||||
|
const location = (await dartClient.get(redirect)).headers.location;
|
||||||
|
return await extractAny(location, host.toLowerCase(), lang, type, host);
|
||||||
|
})(redirect, lang, type, host));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const p of (await Promise.allSettled(promises))) {
|
||||||
|
if (p.status == 'fulfilled') {
|
||||||
|
videos.push.apply(videos, p.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.sortVideos(videos);
|
||||||
|
}
|
||||||
|
sortVideos(videos) {
|
||||||
|
const preference = new SharedPreferences();
|
||||||
|
const hoster = RegExp(preference.get("preferred_hoster_new"));
|
||||||
|
const lang = RegExp(preference.get("preferred_lang"));
|
||||||
|
videos.sort((a, b) => {
|
||||||
|
let qualityMatchA = hoster.test(a.quality) * lang.test(a.quality);
|
||||||
|
let qualityMatchB = hoster.test(b.quality) * lang.test(b.quality);
|
||||||
|
return qualityMatchB - qualityMatchA;
|
||||||
|
});
|
||||||
|
return videos;
|
||||||
|
}
|
||||||
|
getSourcePreferences() {
|
||||||
|
const hosterOptions = ["Streamtape", "VOE", "Vidoza", "Doodstream"];
|
||||||
|
const languageOptions = ["Deutscher Sub", "Deutscher Dub", "Englischer Sub"];
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"key": "preferred_lang",
|
||||||
|
"listPreference": {
|
||||||
|
"title": "Bevorzugte Sprache",
|
||||||
|
"summary": "",
|
||||||
|
"valueIndex": 0,
|
||||||
|
"entries": languageOptions,
|
||||||
|
"entryValues": languageOptions
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "preferred_hoster_new",
|
||||||
|
"listPreference": {
|
||||||
|
"title": "Standard-Hoster",
|
||||||
|
"summary": "",
|
||||||
|
"valueIndex": 0,
|
||||||
|
"entries": hosterOptions,
|
||||||
|
"entryValues": hosterOptions
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "hoster_selection_new",
|
||||||
|
"multiSelectListPreference": {
|
||||||
|
"title": "Hoster auswählen",
|
||||||
|
"summary": "",
|
||||||
|
"entries": hosterOptions,
|
||||||
|
"entryValues": hosterOptions,
|
||||||
|
"values": hosterOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doodExtractor(url) {
|
||||||
|
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||||
|
let response = await dartClient.get(url);
|
||||||
|
while ("location" in response.headers) {
|
||||||
|
response = await dartClient.get(response.headers.location);
|
||||||
|
}
|
||||||
|
const newUrl = response.request.url;
|
||||||
|
const doodhost = newUrl.match(/https:\/\/(.*?)\//, newUrl)[0].slice(8, -1);
|
||||||
|
const md5 = response.body.match(/'\/pass_md5\/(.*?)',/, newUrl)[0].slice(11, -2);
|
||||||
|
const token = md5.substring(md5.lastIndexOf("/") + 1);
|
||||||
|
const expiry = new Date().valueOf();
|
||||||
|
const randomString = getRandomString(10);
|
||||||
|
|
||||||
|
response = await new Client().get(`https://${doodhost}/pass_md5/${md5}`, { "Referer": newUrl });
|
||||||
|
const videoUrl = `${response.body}${randomString}?token=${token}&expiry=${expiry}`;
|
||||||
|
const headers = { "User-Agent": "Mangayomi", "Referer": doodhost };
|
||||||
|
return [{ url: videoUrl, originalUrl: videoUrl, headers: headers, quality: '' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function vidozaExtractor(url) {
|
||||||
|
let response = await new Client({ 'useDartHttpClient': true, "followRedirects": true }).get(url);
|
||||||
|
const videoUrl = response.body.match(/https:\/\/\S*\.mp4/)[0];
|
||||||
|
return [{ url: videoUrl, originalUrl: videoUrl, quality: '' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
_streamTapeExtractor = streamTapeExtractor;
|
||||||
|
streamTapeExtractor = async (url) => {
|
||||||
|
return await _streamTapeExtractor(url, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
_voeExtractor = voeExtractor;
|
||||||
|
voeExtractor = async (url) => {
|
||||||
|
return (await _voeExtractor(url, '')).map(v => {
|
||||||
|
v.quality = v.quality.replace(/Voe: (\d+p?)/i, '$1');
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractAny(link, method, lang, type, host) {
|
||||||
|
const m = extractAny.methods[method];
|
||||||
|
return (!m) ? [] : (await m(link)).map(v => {
|
||||||
|
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
extractAny.methods = {
|
||||||
|
'doodstream': doodExtractor,
|
||||||
|
'streamtape': streamTapeExtractor,
|
||||||
|
'vidoza': vidozaExtractor,
|
||||||
|
'voe': voeExtractor
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRandomString(length) {
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||||
|
const charArray = new Array(length);
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
charArray[i] = chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
}
|
||||||
|
return charArray.join("");
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ const mangayomiSources = [{
|
|||||||
"typeSource": "single",
|
"typeSource": "single",
|
||||||
"itemType": "anime",
|
"itemType": "anime",
|
||||||
"isNsfw": false,
|
"isNsfw": false,
|
||||||
"version": "0.0.25",
|
"version": "0.0.35",
|
||||||
"dateFormat": "",
|
"dateFormat": "",
|
||||||
"dateFormatLocale": "",
|
"dateFormatLocale": "",
|
||||||
"pkgPath": "anime/src/en/allanime.js"
|
"pkgPath": "anime/src/en/allanime.js"
|
||||||
@@ -162,7 +162,7 @@ class DefaultExtension extends MProvider {
|
|||||||
const encodedGql = `?variables=%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%22showId%22:%20%22${ep.showId}%22,%0A%20%20%20%20%20%20%20%20%20%20%22episodeString%22:%20%22${ep.episodeString}%22,%0A%20%20%20%20%20%20%20%20%20%20%22translationType%22:%20%22${translationType[0]}%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20&query=%0A%20%20%20%20%20%20%20%20query(%0A%20%20%20%20%20%20%20%20%20%20$showId:%20String!%0A%20%20%20%20%20%20%20%20%20%20$episodeString:%20String!%0A%20%20%20%20%20%20%20%20%20%20$translationType:%20VaildTranslationTypeEnumType!%0A%20%20%20%20%20%20%20%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20episode(%0A%20%20%20%20%20%20%20%20%20%20%20%20showId:%20$showId%0A%20%20%20%20%20%20%20%20%20%20%20%20episodeString:%20$episodeString%0A%20%20%20%20%20%20%20%20%20%20%20%20translationType:%20$translationType%0A%20%20%20%20%20%20%20%20%20%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sourceUrls%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20`;
|
const encodedGql = `?variables=%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%22showId%22:%20%22${ep.showId}%22,%0A%20%20%20%20%20%20%20%20%20%20%22episodeString%22:%20%22${ep.episodeString}%22,%0A%20%20%20%20%20%20%20%20%20%20%22translationType%22:%20%22${translationType[0]}%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20&query=%0A%20%20%20%20%20%20%20%20query(%0A%20%20%20%20%20%20%20%20%20%20$showId:%20String!%0A%20%20%20%20%20%20%20%20%20%20$episodeString:%20String!%0A%20%20%20%20%20%20%20%20%20%20$translationType:%20VaildTranslationTypeEnumType!%0A%20%20%20%20%20%20%20%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20episode(%0A%20%20%20%20%20%20%20%20%20%20%20%20showId:%20$showId%0A%20%20%20%20%20%20%20%20%20%20%20%20episodeString:%20$episodeString%0A%20%20%20%20%20%20%20%20%20%20%20%20translationType:%20$translationType%0A%20%20%20%20%20%20%20%20%20%20)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20sourceUrls%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20`;
|
||||||
const videoJson = JSON.parse(await this.request(encodedGql));
|
const videoJson = JSON.parse(await this.request(encodedGql));
|
||||||
const videos = [];
|
const videos = [];
|
||||||
const altHosterSelection = preferences.get('alt_hoster_selection');
|
const altHosterSelection = preferences.get('alt_hoster_selection1');
|
||||||
for (const video of videoJson.data.episode.sourceUrls) {
|
for (const video of videoJson.data.episode.sourceUrls) {
|
||||||
const videoUrl = this.decryptSource(video.sourceUrl);
|
const videoUrl = this.decryptSource(video.sourceUrl);
|
||||||
let quality = "";
|
let quality = "";
|
||||||
@@ -193,7 +193,7 @@ class DefaultExtension extends MProvider {
|
|||||||
videos.push(vid);
|
videos.push(vid);
|
||||||
}
|
}
|
||||||
} else if (videoUrl.includes("streamlare.com") && altHosterSelection.some(element => 'streamlare' === element)) {
|
} else if (videoUrl.includes("streamlare.com") && altHosterSelection.some(element => 'streamlare' === element)) {
|
||||||
const vids = await streamlareExtractor(videoUrl);
|
const vids = await streamlareExtractor(videoUrl, 'Streamlare ');
|
||||||
for (const vid of vids) {
|
for (const vid of vids) {
|
||||||
videos.push(vid);
|
videos.push(vid);
|
||||||
}
|
}
|
||||||
@@ -203,7 +203,7 @@ class DefaultExtension extends MProvider {
|
|||||||
videos.push(vid);
|
videos.push(vid);
|
||||||
}
|
}
|
||||||
} else if (videoUrl.includes("wish") && altHosterSelection.some(element => 'streamwish' === element)) {
|
} else if (videoUrl.includes("wish") && altHosterSelection.some(element => 'streamwish' === element)) {
|
||||||
const vids = await streamwishExtractor(videoUrl);
|
const vids = await streamWishExtractor(videoUrl, 'StreamWish ');
|
||||||
for (const vid of vids) {
|
for (const vid of vids) {
|
||||||
videos.push(vid);
|
videos.push(vid);
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ class DefaultExtension extends MProvider {
|
|||||||
}
|
}
|
||||||
sortVideos(videos) {
|
sortVideos(videos) {
|
||||||
const preferences = new SharedPreferences();
|
const preferences = new SharedPreferences();
|
||||||
const hoster = preferences.get("preferred_hoster");
|
const hoster = preferences.get("preferred_hoster1");
|
||||||
const quality = preferences.get("preferred_quality");
|
const quality = preferences.get("preferred_quality");
|
||||||
videos.sort((a, b) => {
|
videos.sort((a, b) => {
|
||||||
let qualityMatchA = 0;
|
let qualityMatchA = 0;
|
||||||
@@ -290,7 +290,7 @@ class DefaultExtension extends MProvider {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "preferred_hoster_",
|
"key": "preferred_hoster1",
|
||||||
"listPreference": {
|
"listPreference": {
|
||||||
"title": "Preferred Video Server",
|
"title": "Preferred Video Server",
|
||||||
"summary": "",
|
"summary": "",
|
||||||
@@ -302,7 +302,7 @@ class DefaultExtension extends MProvider {
|
|||||||
"filemoon",
|
"filemoon",
|
||||||
"streamwish"
|
"streamwish"
|
||||||
],
|
],
|
||||||
"entryValues_": [
|
"entryValues": [
|
||||||
"Ac", "Ak", "Kir", "Rab", "Luf-mp4",
|
"Ac", "Ak", "Kir", "Rab", "Luf-mp4",
|
||||||
"Si-Hls", "S-mp4", "Ac-Hls", "Uv-mp4", "Pn-Hls",
|
"Si-Hls", "S-mp4", "Ac-Hls", "Uv-mp4", "Pn-Hls",
|
||||||
"vidstreaming", "okru", "mp4upload", "streamlare", "doodstream",
|
"vidstreaming", "okru", "mp4upload", "streamlare", "doodstream",
|
||||||
@@ -312,7 +312,7 @@ class DefaultExtension extends MProvider {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "alt_hoster_selection_",
|
"key": "alt_hoster_selection1",
|
||||||
"multiSelectListPreference": {
|
"multiSelectListPreference": {
|
||||||
"title": "Enable/Disable Alternative Hosts",
|
"title": "Enable/Disable Alternative Hosts",
|
||||||
"summary": "",
|
"summary": "",
|
||||||
|
|||||||
1128
javascript/anime/src/es/animefenix.js
Normal file
1128
javascript/anime/src/es/animefenix.js
Normal file
File diff suppressed because it is too large
Load Diff
1398
javascript/anime/src/es/jkanime.js
Normal file
1398
javascript/anime/src/es/jkanime.js
Normal file
File diff suppressed because it is too large
Load Diff
717
javascript/anime/src/es/tioanime.js
Normal file
717
javascript/anime/src/es/tioanime.js
Normal file
@@ -0,0 +1,717 @@
|
|||||||
|
const mangayomiSources = [{
|
||||||
|
"name": "TioAnime",
|
||||||
|
"lang": "es",
|
||||||
|
"baseUrl": "https://tioanime.com",
|
||||||
|
"apiUrl": "",
|
||||||
|
"iconUrl": "https://tioanime.com/assets/img/tio_fb.jpg",
|
||||||
|
"typeSource": "single",
|
||||||
|
"isManga": false,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dateFormat": "",
|
||||||
|
"dateFormatLocale": "",
|
||||||
|
"pkgPath": "anime/src/es/tioanime.js"
|
||||||
|
}];
|
||||||
|
|
||||||
|
class DefaultExtension extends MProvider {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
this.client = new Client();
|
||||||
|
}
|
||||||
|
getHeaders(url) {
|
||||||
|
throw new Error("getHeaders not implemented");
|
||||||
|
}
|
||||||
|
async parseAnimeList(url) {
|
||||||
|
const res = await this.client.get(url);
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
const elements = doc.select("ul.animes > li");
|
||||||
|
const list = [];
|
||||||
|
|
||||||
|
for (const element of elements) {
|
||||||
|
const name = element.selectFirst(".title").text;
|
||||||
|
const imageUrl = this.source.baseUrl + element.selectFirst("img").getSrc;
|
||||||
|
const link = element.selectFirst("a").getHref;
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
const hasNextPage = doc.selectFirst("li.page-item.active + li").text == "»";
|
||||||
|
return { "list": list, "hasNextPage": hasNextPage };
|
||||||
|
}
|
||||||
|
statusFromString(status) {
|
||||||
|
return {
|
||||||
|
"En emision": 0,
|
||||||
|
"Finalizado": 1
|
||||||
|
}[status] ?? 5;
|
||||||
|
}
|
||||||
|
async getPopular(page) {
|
||||||
|
return this.parseAnimeList(`${this.source.baseUrl}/directorio?p=${page}`);
|
||||||
|
}
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
return this.parseAnimeList(`${this.source.baseUrl}/directorio?p=${page}`);
|
||||||
|
}
|
||||||
|
async search(query, page, filters) {
|
||||||
|
query = query.trim().replaceAll(/\ +/g, "+");
|
||||||
|
let url = `${this.source.baseUrl}/directorio?q=${query}&p=${page}`;
|
||||||
|
return this.parseAnimeList(url);
|
||||||
|
}
|
||||||
|
async getDetail(url) {
|
||||||
|
const res = await this.client.get(this.source.baseUrl + url);
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
const detail = {};
|
||||||
|
|
||||||
|
const info = doc.selectFirst("article.anime-single");
|
||||||
|
const episodeCount = parseInt(/episodes = \[(\d+)/.exec(doc.select("script").pop().innerHtml)[1]);
|
||||||
|
const episodeUrl = url.replace("/anime", "/ver") + "-";
|
||||||
|
|
||||||
|
detail.name = info.selectFirst("h1").text;
|
||||||
|
detail.status = this.statusFromString(info.selectFirst("a").text);
|
||||||
|
detail.imageUrl = this.source.baseUrl + info.selectFirst("img").getSrc;
|
||||||
|
detail.description = info.selectFirst("p.sinopsis").text.trim();
|
||||||
|
detail.genre = info.select("p.genres a").map(e => e.text.trim());
|
||||||
|
detail.episodes = [];
|
||||||
|
for (let i = 0; i < episodeCount; i++) {
|
||||||
|
const name = `Episodio ${i + 1}`;
|
||||||
|
const url = episodeUrl + (i + 1);
|
||||||
|
detail.episodes.push({ name, url });
|
||||||
|
}
|
||||||
|
detail.episodes.reverse();
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
// For anime episode video list
|
||||||
|
async getVideoList(url) {
|
||||||
|
const res = await this.client.get(this.source.baseUrl + url);
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
let promises = [];
|
||||||
|
const videos = [];
|
||||||
|
|
||||||
|
// get type
|
||||||
|
const type = /\blatino\b/i.test(url) ? 'Dub' : 'Sub';
|
||||||
|
|
||||||
|
// get links
|
||||||
|
const raws = [...doc.select("script").pop().innerHtml.matchAll(/\[".*?\]/g)];
|
||||||
|
|
||||||
|
// extract videos
|
||||||
|
for (const raw of raws) {
|
||||||
|
const data = JSON.parse(raw[0]);
|
||||||
|
const host = data[0];
|
||||||
|
const link = data[1];
|
||||||
|
promises.push(extractAny(link, host.toLowerCase(), 'Español', type, host));
|
||||||
|
}
|
||||||
|
for (const p of (await Promise.allSettled(promises))) {
|
||||||
|
if (p.status == 'fulfilled') {
|
||||||
|
videos.push.apply(videos, p.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sortVideos(videos);
|
||||||
|
}
|
||||||
|
getFilterList() {
|
||||||
|
throw new Error("getFilterList not implemented");
|
||||||
|
}
|
||||||
|
getSourcePreferences() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'lang',
|
||||||
|
listPreference: {
|
||||||
|
title: 'Preferred Language',
|
||||||
|
summary: 'If available, this language will be chosen by default. Priority = 0 (lower is better)',
|
||||||
|
valueIndex: 0,
|
||||||
|
entries: [
|
||||||
|
'Español'
|
||||||
|
],
|
||||||
|
entryValues: [
|
||||||
|
'Español'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
listPreference: {
|
||||||
|
title: 'Preferred Type',
|
||||||
|
summary: 'If available, this type will be chosen by default. Priority = 1 (lower is better)',
|
||||||
|
valueIndex: 0,
|
||||||
|
entries: [
|
||||||
|
'Sub'
|
||||||
|
],
|
||||||
|
entryValues: [
|
||||||
|
'Sub'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'res',
|
||||||
|
listPreference: {
|
||||||
|
title: 'Preferred Resolution',
|
||||||
|
summary: 'If available, this resolution will be chosen by default. Priority = 2 (lower is better)',
|
||||||
|
valueIndex: 0,
|
||||||
|
entries: [
|
||||||
|
'1080p',
|
||||||
|
'720p',
|
||||||
|
'480p'
|
||||||
|
],
|
||||||
|
entryValues: [
|
||||||
|
'1080p',
|
||||||
|
'720p',
|
||||||
|
'480p'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'host',
|
||||||
|
listPreference: {
|
||||||
|
title: 'Preferred Hoster',
|
||||||
|
summary: 'If available, this hoster will be chosen by default. Priority = 3 (lower is better)',
|
||||||
|
valueIndex: 0,
|
||||||
|
entries: [
|
||||||
|
'Okru',
|
||||||
|
'VidGuard',
|
||||||
|
'Voe',
|
||||||
|
'YourUpload'
|
||||||
|
],
|
||||||
|
entryValues: [
|
||||||
|
'Okru',
|
||||||
|
'VidGuard',
|
||||||
|
'Voe',
|
||||||
|
'YourUpload'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************************************************************************************
|
||||||
|
*
|
||||||
|
* mangayomi-js-helpers v1.0
|
||||||
|
*
|
||||||
|
* # Video Extractors
|
||||||
|
* - vidGuardExtractor
|
||||||
|
* - doodExtractor
|
||||||
|
* - vidozaExtractor
|
||||||
|
* - okruExtractor
|
||||||
|
* - amazonExtractor
|
||||||
|
* - vidHideExtractor
|
||||||
|
* - filemoonExtractor
|
||||||
|
* - mixdropExtractor
|
||||||
|
* - burstcloudExtractor (not working, see description)
|
||||||
|
*
|
||||||
|
* # Video Extractor Format Wrappers
|
||||||
|
* - streamWishExtractor
|
||||||
|
* - voeExtractor
|
||||||
|
* - mp4UploadExtractor
|
||||||
|
* - yourUploadExtractor
|
||||||
|
* - streamTapeExtractor
|
||||||
|
* - sendVidExtractor
|
||||||
|
*
|
||||||
|
* # Video Extractor helpers
|
||||||
|
* - extractAny
|
||||||
|
*
|
||||||
|
* # Playlist Extractors
|
||||||
|
* - m3u8Extractor
|
||||||
|
* - jwplayerExtractor
|
||||||
|
*
|
||||||
|
* # Extension
|
||||||
|
* - sortVideos()
|
||||||
|
*
|
||||||
|
* # Encoding/Decoding
|
||||||
|
* - Uint8Array.fromBase64()
|
||||||
|
* - Uint8Array.prototype.toBase64()
|
||||||
|
* - Uint8Array.prototype.decode()
|
||||||
|
* - String.prototype.encode()
|
||||||
|
* - String.prototype.decode()
|
||||||
|
*
|
||||||
|
* # Random string
|
||||||
|
* - getRandomString()
|
||||||
|
*
|
||||||
|
* # URL
|
||||||
|
* - absUrl()
|
||||||
|
*
|
||||||
|
***************************************************************************************************/
|
||||||
|
|
||||||
|
async function vidGuardExtractor(url) {
|
||||||
|
// get html
|
||||||
|
const res = await new Client().get(url);
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
const script = doc.selectFirst('script:contains(eval)');
|
||||||
|
|
||||||
|
// eval code
|
||||||
|
const code = script.text;
|
||||||
|
eval?.('var window = {};');
|
||||||
|
eval?.(code);
|
||||||
|
const playlistUrl = globalThis.window.svg.stream;
|
||||||
|
|
||||||
|
// decode sig
|
||||||
|
const encoded = playlistUrl.match(/sig=(.*?)&/)[1];
|
||||||
|
const charCodes = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < encoded.length; i += 2) {
|
||||||
|
charCodes.push(parseInt(encoded.slice(i, i + 2), 16) ^ 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded = Uint8Array.fromBase64(
|
||||||
|
String.fromCharCode(...charCodes))
|
||||||
|
.slice(5, -5)
|
||||||
|
.reverse();
|
||||||
|
|
||||||
|
for (let i = 0; i < decoded.length; i += 2) {
|
||||||
|
let tmp = decoded[i];
|
||||||
|
decoded[i] = decoded[i + 1];
|
||||||
|
decoded[i + 1] = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded = decoded.decode();
|
||||||
|
return await m3u8Extractor(playlistUrl.replace(encoded, decoded), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doodExtractor(url) {
|
||||||
|
const dartClient = new Client({ 'useDartHttpClient': true, "followRedirects": false });
|
||||||
|
let response = await dartClient.get(url);
|
||||||
|
while ("location" in response.headers) {
|
||||||
|
response = await dartClient.get(response.headers.location);
|
||||||
|
}
|
||||||
|
const newUrl = response.request.url;
|
||||||
|
const doodhost = newUrl.match(/https:\/\/(.*?)\//, newUrl)[0].slice(8, -1);
|
||||||
|
const md5 = response.body.match(/'\/pass_md5\/(.*?)',/, newUrl)[0].slice(11, -2);
|
||||||
|
const token = md5.substring(md5.lastIndexOf("/") + 1);
|
||||||
|
const expiry = new Date().valueOf();
|
||||||
|
const randomString = getRandomString(10);
|
||||||
|
|
||||||
|
response = await new Client().get(`https://${doodhost}/pass_md5/${md5}`, { "Referer": newUrl });
|
||||||
|
const videoUrl = `${response.body}${randomString}?token=${token}&expiry=${expiry}`;
|
||||||
|
const headers = { "User-Agent": "Mangayomi", "Referer": doodhost };
|
||||||
|
return [{ url: videoUrl, originalUrl: videoUrl, headers: headers, quality: '' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function vidozaExtractor(url) {
|
||||||
|
let response = await new Client({ 'useDartHttpClient': true, "followRedirects": true }).get(url);
|
||||||
|
const videoUrl = response.body.match(/https:\/\/\S*\.mp4/)[0];
|
||||||
|
return [{ url: videoUrl, originalUrl: videoUrl, quality: '' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function okruExtractor(url) {
|
||||||
|
const res = await new Client().get(url);
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
const tag = doc.selectFirst('div[data-options]');
|
||||||
|
const playlistUrl = tag.attr('data-options').match(/hlsManifestUrl.*?(h.*?id=\d+)/)[1].replaceAll('\\\\u0026', '&');
|
||||||
|
return await m3u8Extractor(playlistUrl, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function amazonExtractor(url) {
|
||||||
|
const res = await new Client().get(url);
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
const videoUrl = doc.selectFirst('video').getSrc;
|
||||||
|
return videoUrl ? [{ url: videoUrl, originalUrl: videoUrl, headers: null, quality: '' }] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function vidHideExtractor(url) {
|
||||||
|
const res = await new Client().get(url);
|
||||||
|
return await jwplayerExtractor(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function filemoonExtractor(url) {
|
||||||
|
let res = await new Client().get(url);
|
||||||
|
const src = res.body.match(/iframe src="(.*?)"/)?.[1];
|
||||||
|
if (src) {
|
||||||
|
res = await new Client().get(src, {
|
||||||
|
'Referer': url,
|
||||||
|
'Accept-Language': 'de,en-US;q=0.7,en;q=0.3'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return await jwplayerExtractor(res.body);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mixdropExtractor(url) {
|
||||||
|
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'};
|
||||||
|
let res = await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(url, headers);
|
||||||
|
while ("location" in res.headers) {
|
||||||
|
res = await new Client({ 'useDartHttpClient': true, "followRedirects": false }).get(res.headers.location, headers);
|
||||||
|
}
|
||||||
|
const newUrl = res.request.url;
|
||||||
|
let doc = new Document(res.body);
|
||||||
|
|
||||||
|
const code = doc.selectFirst('script:contains(MDCore):contains(eval)').text;
|
||||||
|
const unpacked = unpackJs(code);
|
||||||
|
let videoUrl = unpacked.match(/wurl="(.*?)"/)?.[1];
|
||||||
|
|
||||||
|
if (!videoUrl) return [];
|
||||||
|
|
||||||
|
videoUrl = 'https:' + videoUrl;
|
||||||
|
headers.referer = newUrl;
|
||||||
|
|
||||||
|
return [{url: videoUrl, originalUrl: videoUrl, quality: '', headers: headers}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Does not work: Client always sets 'charset=utf-8' in Content-Type. */
|
||||||
|
async function burstcloudExtractor(url) {
|
||||||
|
let client = new Client();
|
||||||
|
let res = await client.get(url);
|
||||||
|
|
||||||
|
const id = res.body.match(/data-file-id="(.*?)"/)[1];
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
|
'Referer': url,
|
||||||
|
};
|
||||||
|
const data = {
|
||||||
|
'fileId': id
|
||||||
|
};
|
||||||
|
|
||||||
|
res = await client.post(`https://www.burstcloud.co/file/play-request/`, headers, data);
|
||||||
|
const videoUrl = res.body.match(/cdnUrl":"(.*?)"/)[1];
|
||||||
|
return [{
|
||||||
|
url: videoUrl,
|
||||||
|
originalUrl: videoUrl,
|
||||||
|
headers: { 'Referer': url.match(/.*?:\/\/.*?\//) },
|
||||||
|
quality: ''
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
_streamWishExtractor = streamWishExtractor;
|
||||||
|
streamWishExtractor = async (url) => {
|
||||||
|
return (await _streamWishExtractor(url, '')).map(v => {
|
||||||
|
v.quality = v.quality.slice(3, -1);
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_voeExtractor = voeExtractor;
|
||||||
|
voeExtractor = async (url) => {
|
||||||
|
return (await _voeExtractor(url, '')).map(v => {
|
||||||
|
v.quality = v.quality.replace(/Voe: (\d+p?)/i, '$1');
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_mp4UploadExtractor = mp4UploadExtractor;
|
||||||
|
mp4UploadExtractor = async (url) => {
|
||||||
|
return (await _mp4UploadExtractor(url)).map(v => {
|
||||||
|
v.quality = v.quality.match(/\d+p/)?.[0] ?? '';
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_yourUploadExtractor = yourUploadExtractor;
|
||||||
|
yourUploadExtractor = async (url) => {
|
||||||
|
return (await _yourUploadExtractor(url))
|
||||||
|
.filter(v => !v.url.includes('/novideo'))
|
||||||
|
.map(v => {
|
||||||
|
v.quality = '';
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_streamTapeExtractor = streamTapeExtractor;
|
||||||
|
streamTapeExtractor = async (url) => {
|
||||||
|
return await _streamTapeExtractor(url, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendVidExtractor = sendVidExtractor;
|
||||||
|
sendVidExtractor = async (url) => {
|
||||||
|
let res = await new Client().get(url);
|
||||||
|
var videoUrl, quality;
|
||||||
|
try {
|
||||||
|
videoUrl = res.body.match(/og:video" content="(.*?\.mp4.*?)"/)[1];
|
||||||
|
quality = res.body.match(/og:video:height" content="(.*?)"/)?.[1];
|
||||||
|
quality = quality ? quality + 'p' : '';
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
if (!videoUrl) {
|
||||||
|
return _sendVidExtractor(url, null, '');
|
||||||
|
}
|
||||||
|
return [{url: videoUrl, originalUrl: videoUrl, quality: quality, headers: null}];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractAny(url, method, lang, type, host) {
|
||||||
|
const m = extractAny.methods[method];
|
||||||
|
return (!m) ? [] : (await m(url)).map(v => {
|
||||||
|
v.quality = v.quality ? `${lang} ${type} ${v.quality} ${host}` : `${lang} ${type} ${host}`;
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
extractAny.methods = {
|
||||||
|
'amazon': amazonExtractor,
|
||||||
|
'burstcloud': burstcloudExtractor,
|
||||||
|
'doodstream': doodExtractor,
|
||||||
|
'filemoon': filemoonExtractor,
|
||||||
|
'mixdrop': mixdropExtractor,
|
||||||
|
'mp4upload': mp4UploadExtractor,
|
||||||
|
'okru': okruExtractor,
|
||||||
|
'sendvid': sendVidExtractor,
|
||||||
|
'streamtape': streamTapeExtractor,
|
||||||
|
'streamwish': vidHideExtractor,
|
||||||
|
'vidguard': vidGuardExtractor,
|
||||||
|
'vidhide': vidHideExtractor,
|
||||||
|
'vidoza': vidozaExtractor,
|
||||||
|
'voe': voeExtractor,
|
||||||
|
'yourupload': yourUploadExtractor
|
||||||
|
};
|
||||||
|
|
||||||
|
async function m3u8Extractor(url, headers = null) {
|
||||||
|
// https://developer.apple.com/documentation/http-live-streaming/creating-a-multivariant-playlist
|
||||||
|
// https://developer.apple.com/documentation/http-live-streaming/adding-alternate-media-to-a-playlist
|
||||||
|
// define attribute lists
|
||||||
|
const streamAttributes = [
|
||||||
|
['avg_bandwidth', /AVERAGE-BANDWIDTH=(\d+)/],
|
||||||
|
['bandwidth', /\bBANDWIDTH=(\d+)/],
|
||||||
|
['resolution', /\bRESOLUTION=([\dx]+)/],
|
||||||
|
['framerate', /\bFRAME-RATE=([\d\.]+)/],
|
||||||
|
['codecs', /\bCODECS="(.*?)"/],
|
||||||
|
['video', /\bVIDEO="(.*?)"/],
|
||||||
|
['audio', /\bAUDIO="(.*?)"/],
|
||||||
|
['subtitles', /\bSUBTITLES="(.*?)"/],
|
||||||
|
['captions', /\bCLOSED-CAPTIONS="(.*?)"/]
|
||||||
|
];
|
||||||
|
const mediaAttributes = [
|
||||||
|
['type', /\bTYPE=([\w-]*)/],
|
||||||
|
['group', /\bGROUP-ID="(.*?)"/],
|
||||||
|
['lang', /\bLANGUAGE="(.*?)"/],
|
||||||
|
['name', /\bNAME="(.*?)"/],
|
||||||
|
['autoselect', /\bAUTOSELECT=(\w*)/],
|
||||||
|
['default', /\bDEFAULT=(\w*)/],
|
||||||
|
['instream-id', /\bINSTREAM-ID="(.*?)"/],
|
||||||
|
['assoc-lang', /\bASSOC-LANGUAGE="(.*?)"/],
|
||||||
|
['channels', /\bCHANNELS="(.*?)"/],
|
||||||
|
['uri', /\bURI="(.*?)"/]
|
||||||
|
];
|
||||||
|
const streams = [], videos = {}, audios = {}, subtitles = {}, captions = {};
|
||||||
|
const dict = { 'VIDEO': videos, 'AUDIO': audios, 'SUBTITLES': subtitles, 'CLOSED-CAPTIONS': captions };
|
||||||
|
|
||||||
|
const res = await new Client().get(url, headers);
|
||||||
|
const text = res.body;
|
||||||
|
|
||||||
|
// collect media
|
||||||
|
for (const match of text.matchAll(/#EXT-X-MEDIA:(.*)/g)) {
|
||||||
|
const info = match[1], medium = {};
|
||||||
|
for (const attr of mediaAttributes) {
|
||||||
|
const m = info.match(attr[1]);
|
||||||
|
medium[attr[0]] = m ? m[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = medium.type;
|
||||||
|
delete medium.type;
|
||||||
|
const group = medium.group;
|
||||||
|
delete medium.group;
|
||||||
|
|
||||||
|
const typedict = dict[type];
|
||||||
|
if (typedict[group] == undefined)
|
||||||
|
typedict[group] = [];
|
||||||
|
typedict[group].push(medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect streams
|
||||||
|
for (const match of text.matchAll(/#EXT-X-STREAM-INF:(.*)\s*(.*)/g)) {
|
||||||
|
const info = match[1], stream = { 'url': absUrl(match[2], url) };
|
||||||
|
for (const attr of streamAttributes) {
|
||||||
|
const m = info.match(attr[1]);
|
||||||
|
stream[attr[0]] = m ? m[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream['video'] = videos[stream.video] ?? null;
|
||||||
|
stream['audio'] = audios[stream.audio] ?? null;
|
||||||
|
stream['subtitles'] = subtitles[stream.subtitles] ?? null;
|
||||||
|
stream['captions'] = captions[stream.captions] ?? null;
|
||||||
|
|
||||||
|
// format resolution or bandwidth
|
||||||
|
let quality;
|
||||||
|
if (stream.resolution) {
|
||||||
|
quality = stream.resolution.match(/x(\d+)/)[1] + 'p';
|
||||||
|
} else {
|
||||||
|
quality = (parseInt(stream.avg_bandwidth ?? stream.bandwidth) / 1000000) + 'Mb/s'
|
||||||
|
}
|
||||||
|
|
||||||
|
// add stream to list
|
||||||
|
const subs = stream.subtitles?.map((s) => {
|
||||||
|
return { file: s.uri, label: s.name };
|
||||||
|
});
|
||||||
|
const auds = stream.audio?.map((a) => {
|
||||||
|
return { file: a.uri, label: a.name };
|
||||||
|
});
|
||||||
|
streams.push({
|
||||||
|
url: stream.url,
|
||||||
|
quality: quality,
|
||||||
|
originalUrl: stream.url,
|
||||||
|
headers: headers,
|
||||||
|
subtitles: subs ?? null,
|
||||||
|
audios: auds ?? null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return streams.length ? streams : [{
|
||||||
|
url: url,
|
||||||
|
quality: '',
|
||||||
|
originalUrl: url,
|
||||||
|
headers: headers,
|
||||||
|
subtitles: null,
|
||||||
|
audios: null
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function jwplayerExtractor(text, headers) {
|
||||||
|
// https://docs.jwplayer.com/players/reference/playlists
|
||||||
|
const getsetup = /setup\(({[\s\S]*?})\)/;
|
||||||
|
const getsources = /sources:\s*(\[[\s\S]*?\])/;
|
||||||
|
const gettracks = /tracks:\s*(\[[\s\S]*?\])/;
|
||||||
|
const unpacked = unpackJs(text);
|
||||||
|
|
||||||
|
const videos = [], subtitles = [];
|
||||||
|
|
||||||
|
const data = eval('(' + (getsetup.exec(text) || getsetup.exec(unpacked))?.[1] + ')');
|
||||||
|
|
||||||
|
if (data){
|
||||||
|
var sources = data.sources;
|
||||||
|
var tracks = data.tracks;
|
||||||
|
} else {
|
||||||
|
var sources = eval('(' + (getsources.exec(text) || getsources.exec(unpacked))?.[1] + ')');
|
||||||
|
var tracks = eval('(' + (gettracks.exec(text) || gettracks.exec(unpacked))?.[1] + ')');
|
||||||
|
}
|
||||||
|
for (t of tracks) {
|
||||||
|
if (t.type == "captions") {
|
||||||
|
subtitles.push({file: t.file, label: t.label});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (s of sources) {
|
||||||
|
if (s.file.includes('master.m3u8')) {
|
||||||
|
videos.push(...(await m3u8Extractor(s.file, headers)));
|
||||||
|
} else if (s.file.includes('.mpd')) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
videos.push({url: s.file, originalUrl: s.file, quality: '', headers: headers});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return videos.map(v => {
|
||||||
|
v.subtitles = subtitles;
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortVideos(videos) {
|
||||||
|
const pref = new SharedPreferences();
|
||||||
|
const getres = RegExp('(\\d+)p?', 'i');
|
||||||
|
const lang = RegExp(pref.get('lang'), 'i');
|
||||||
|
const type = RegExp(pref.get('type'), 'i');
|
||||||
|
const res = RegExp(getres.exec(pref.get('res'))[1], 'i');
|
||||||
|
const host = RegExp(pref.get('host'), 'i');
|
||||||
|
|
||||||
|
let getScore = (q, hasRes) => {
|
||||||
|
const bLang = lang.test(q), bType = type.test(q), bRes = res.test(q), bHost = host.test(q);
|
||||||
|
if (hasRes) {
|
||||||
|
return bLang * (8 + bType * (4 + bRes * (2 + bHost * 1)));
|
||||||
|
} else {
|
||||||
|
return bLang * (8 + bType * (4 + (bHost * 3)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return videos.sort((a, b) => {
|
||||||
|
const resA = getres.exec(a.quality)?.[1];
|
||||||
|
const resB = getres.exec(b.quality)?.[1];
|
||||||
|
const score = getScore(b.quality, resB) - getScore(a.quality, resA);
|
||||||
|
|
||||||
|
if (score) return score;
|
||||||
|
|
||||||
|
const qA = resA ? a.quality.replace(resA, (9999 - parseInt(resA)).toString()) : a.quality;
|
||||||
|
const qB = resA ? b.quality.replace(resB, (9999 - parseInt(resB)).toString()) : b.quality;
|
||||||
|
|
||||||
|
return qA.localeCompare(qB);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8Array.fromBase64 = function (b64) {
|
||||||
|
// [00,01,02,03,04,05,06,07,08,\t,\n,0b,0c,\r,0e,0f,10,11,12,13,14,15,16,17,18,19,1a,1b,1c,1d,1e,1f,' ', !, ", #, $, %, &, ', (, ), *, +,',', -, ., /, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <,'=', >, ?, @,A,B,C,D,E,F,G,H,I,J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, [, \, ], ^, _, `, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, {, |, }, ~,7f]
|
||||||
|
const m = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]
|
||||||
|
let data = [], val = 0, bits = -8
|
||||||
|
for (const c of b64) {
|
||||||
|
let n = m[c.charCodeAt(0)];
|
||||||
|
if (n == -1) break;
|
||||||
|
val = (val << 6) + n;
|
||||||
|
bits += 6;
|
||||||
|
for (; bits >= 0; bits -= 8)
|
||||||
|
data.push((val >> bits) & 0xFF);
|
||||||
|
}
|
||||||
|
return new Uint8Array(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8Array.prototype.toBase64 = function () {
|
||||||
|
const m = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||||
|
let b64 = '', val = 0, bits = -6;
|
||||||
|
for (const b of this) {
|
||||||
|
val = (val << 8) + b;
|
||||||
|
bits += 8;
|
||||||
|
while (bits >= 0) {
|
||||||
|
b64 += m[(val >> bits) & 0x3F];
|
||||||
|
bits -= 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bits > -6)
|
||||||
|
b64 += m[(val << -bits) & 0x3F];
|
||||||
|
return b64 + ['', '', '==', '='][b64.length % 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8Array.prototype.decode = function (encoding = 'utf-8') {
|
||||||
|
encoding = encoding.toLowerCase();
|
||||||
|
if (encoding == 'utf-8') {
|
||||||
|
return decodeUTF8(this);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String.prototype.encode = function (encoding = 'utf-8') {
|
||||||
|
encoding = encoding.toLowerCase();
|
||||||
|
if (encoding == 'utf-8') {
|
||||||
|
return encodeUTF8(this);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String.decode = function (data, encoding = 'utf-8') {
|
||||||
|
encoding = encoding.toLowerCase();
|
||||||
|
if (encoding == 'utf-8') {
|
||||||
|
return decodeUTF8(data);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeUTF8(data) {
|
||||||
|
const codes = [];
|
||||||
|
for (let i = 0; i < data.length;) {
|
||||||
|
const c = data[i++];
|
||||||
|
const len = (c > 0xBF) + (c > 0xDF) + (c > 0xEF);
|
||||||
|
let val = c & (0xFF >> (len + 1));
|
||||||
|
for (const end = i + len; i < end; i++) {
|
||||||
|
val = (val << 6) + (data[i] & 0x3F);
|
||||||
|
}
|
||||||
|
codes.push(val);
|
||||||
|
}
|
||||||
|
return String.fromCharCode(...codes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeUTF8(string) {
|
||||||
|
const data = [];
|
||||||
|
for (const c of string) {
|
||||||
|
const code = c.charCodeAt(0);
|
||||||
|
const len = (code > 0x7F) + (code > 0x7FF) + (code > 0xFFFF);
|
||||||
|
let bits = len * 6;
|
||||||
|
|
||||||
|
data.push((len ? ~(0xFF >> len + 1) : (0)) + (code >> bits));
|
||||||
|
while (bits > 0) {
|
||||||
|
data.push(0x80 + ((code >> (bits -= 6)) & 0x3F))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Uint8Array(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomString(length) {
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const random = Math.floor(Math.random() * 61);
|
||||||
|
result += chars[random];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function absUrl(url, base) {
|
||||||
|
if (url.search(/^\w+:\/\//) == 0) {
|
||||||
|
return url;
|
||||||
|
} else if (url.startsWith('/')) {
|
||||||
|
return base.slice(0, base.lastIndexOf('/')) + url;
|
||||||
|
} else {
|
||||||
|
return base.slice(0, base.lastIndexOf('/') + 1) + url;
|
||||||
|
}
|
||||||
|
}
|
||||||
218
javascript/anime/src/zh/wogg.js
Normal file
218
javascript/anime/src/zh/wogg.js
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
const mangayomiSources = [{
|
||||||
|
"name": "玩偶哥哥",
|
||||||
|
"lang": "zh",
|
||||||
|
"baseUrl": "https://www.wogg.net",
|
||||||
|
"apiUrl": "",
|
||||||
|
"iconUrl": "https://imgsrc.baidu.com/forum/pic/item/4b90f603738da977d5da660af651f8198618e31f.jpg",
|
||||||
|
"typeSource": "single",
|
||||||
|
"isManga": false,
|
||||||
|
"isNsfw": false,
|
||||||
|
"version": "0.0.2",
|
||||||
|
"dateFormat": "",
|
||||||
|
"dateFormatLocale": "",
|
||||||
|
"pkgPath": "anime/src/zh/wogg.js"
|
||||||
|
}];
|
||||||
|
class DefaultExtension extends MProvider {
|
||||||
|
patternQuark = /(https:\/\/pan\.quark\.cn\/s\/[^"]+)/;
|
||||||
|
patternUc = /(https:\/\/drive\.uc\.cn\/s\/[^"]+)/;
|
||||||
|
getHeaders(url) {
|
||||||
|
throw new Error("getHeaders not implemented");
|
||||||
|
}
|
||||||
|
async getPopular(page) {
|
||||||
|
const baseUrl = new SharedPreferences().get("url");
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl, { "Referer": baseUrl });
|
||||||
|
const elements = new Document(response.body).select("div.module-item");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
let oneA = element.selectFirst('.module-item-cover .module-item-pic a');
|
||||||
|
const name = oneA.attr("title");
|
||||||
|
const imageUrl = element.selectFirst(".module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const link = oneA.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
const baseUrl = new SharedPreferences().get("url");
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl + `/vodshow/1--------${page}---.html`, { "Referer": baseUrl });
|
||||||
|
const elements = new Document(response.body).select("div.module-item");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
let oneA = element.selectFirst('.module-item-cover .module-item-pic a');
|
||||||
|
const name = oneA.attr("title");
|
||||||
|
const imageUrl = element.selectFirst(".module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const link = oneA.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async search(query, page, filters) {
|
||||||
|
const baseUrl = new SharedPreferences().get("url");
|
||||||
|
if (query == "") {
|
||||||
|
var categories;
|
||||||
|
for (const filter of filters) {
|
||||||
|
if (filter["type"] == "categories") {
|
||||||
|
categories = filter["values"][filter["state"]]["value"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl + `/vodshow/${categories}--------${page}---.html`, { "Referer": baseUrl });
|
||||||
|
const elements = new Document(response.body).select("div.module-item");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
let oneA = element.selectFirst('.module-item-cover .module-item-pic a');
|
||||||
|
const name = oneA.attr("title");
|
||||||
|
const imageUrl = element.selectFirst(".module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const link = oneA.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl + `/vodsearch/${query}----------${page}---.html`, { "Referer": baseUrl });
|
||||||
|
const elements = new Document(response.body).select(".module-search-item");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
let oneA = element.selectFirst('.video-info .video-info-header a');
|
||||||
|
const name = oneA.attr("title");
|
||||||
|
const imageUrl = element.selectFirst(".video-cover .module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const link = oneA.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getDetail(url) {
|
||||||
|
const baseUrl = new SharedPreferences().get("url");
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl + url, { "Referer": baseUrl });
|
||||||
|
const document = new Document(response.body);
|
||||||
|
const imageUrl = document.selectFirst("div.video-cover .module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const name = document.selectFirst("div.video-info .video-info-header h1").text;
|
||||||
|
const description = document.selectFirst("div.video-info .video-info-content").text.replace("[收起部分]", "").replace("[展开全部]", "");
|
||||||
|
const type_name = "电影";
|
||||||
|
let quark_share_url_list = [], uc_share_url_list = []
|
||||||
|
const share_url_list = document.select("div.module-row-one .module-row-info")
|
||||||
|
.map(e => {
|
||||||
|
const url = e.selectFirst(".module-row-title p").text;
|
||||||
|
const quarkMatches = url.match(this.patternQuark);
|
||||||
|
|
||||||
|
if (quarkMatches && quarkMatches[1]) {
|
||||||
|
quark_share_url_list.push(quarkMatches[1]);
|
||||||
|
}
|
||||||
|
const ucMatches = url.match(this.patternUc);
|
||||||
|
if (ucMatches && ucMatches[1]) {
|
||||||
|
uc_share_url_list.push(ucMatches[1]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(url => url !== null);
|
||||||
|
let quark_episodes = await quarkFilesExtractor(quark_share_url_list, new SharedPreferences().get("quarkCookie"));
|
||||||
|
let uc_episodes = await ucFilesExtractor(uc_share_url_list, new SharedPreferences().get("ucCookie"));
|
||||||
|
let episodes = [...quark_episodes, ...uc_episodes];
|
||||||
|
return {
|
||||||
|
name, imageUrl, description, episodes
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// For anime episode video list
|
||||||
|
async getVideoList(url) {
|
||||||
|
const videos = [];
|
||||||
|
const parts = url.split('++');
|
||||||
|
const type = parts[0].toLowerCase();
|
||||||
|
|
||||||
|
let vids;
|
||||||
|
if (type === 'quark') {
|
||||||
|
let cookie = new SharedPreferences().get("quarkCookie");
|
||||||
|
if (cookie == "") {
|
||||||
|
throw new Error("请先在本扩展设置中填写夸克Cookies, 需要夸克VIP账号 \n Please fill in the Quark Cookies in this extension settings first, you need a Quark VIP account");
|
||||||
|
} else {
|
||||||
|
vids = await quarkVideosExtractor(url, cookie);
|
||||||
|
}
|
||||||
|
} else if (type === 'uc') {
|
||||||
|
let cookie = new SharedPreferences().get("ucCookie");
|
||||||
|
if (cookie == "") {
|
||||||
|
throw new Error("请先在本扩展设置中填写UC云盘Cookies \n Please fill in the UC Cloud Cookies in this extension settings first");
|
||||||
|
} else {
|
||||||
|
vids = await ucVideosExtractor(url, cookie);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error("不支持的链接类型");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const vid of vids) {
|
||||||
|
videos.push(vid);
|
||||||
|
}
|
||||||
|
return videos;
|
||||||
|
}
|
||||||
|
getFilterList() {
|
||||||
|
return [{
|
||||||
|
type: "categories",
|
||||||
|
name: "影片類型",
|
||||||
|
type_name: "SelectFilter",
|
||||||
|
values: [
|
||||||
|
{ type_name: "SelectOption", value: "1", name: "电影" },
|
||||||
|
{ type_name: "SelectOption", value: "2", name: "剧集" },
|
||||||
|
{ type_name: "SelectOption", value: "3", name: "动漫" },
|
||||||
|
{ type_name: "SelectOption", value: "4", name: "综艺" },
|
||||||
|
{ type_name: "SelectOption", value: "5", name: "音乐" },
|
||||||
|
{ type_name: "SelectOption", value: "6", name: "短剧" },
|
||||||
|
{ type_name: "SelectOption", value: "44", name: "臻彩视界" }
|
||||||
|
]
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
getSourcePreferences() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"key": "quarkCookie",
|
||||||
|
"editTextPreference": {
|
||||||
|
"title": "夸克Cookies",
|
||||||
|
"summary": "填写获取到的夸克Cookies",
|
||||||
|
"value": "",
|
||||||
|
"dialogTitle": "Cookies",
|
||||||
|
"dialogMessage": "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ucCookie",
|
||||||
|
"editTextPreference": {
|
||||||
|
"title": "UC云盘Cookies",
|
||||||
|
"summary": "填写获取到的UC云盘Cookies",
|
||||||
|
"value": "",
|
||||||
|
"dialogTitle": "Cookies",
|
||||||
|
"dialogMessage": "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "url",
|
||||||
|
"listPreference": {
|
||||||
|
"title": "Website Url",
|
||||||
|
"summary": "",
|
||||||
|
"valueIndex": 0,
|
||||||
|
"entries": [
|
||||||
|
"wogg.net",
|
||||||
|
"wogg.xxooo.cf",
|
||||||
|
"wogg.888484.xyz",
|
||||||
|
"wogg.bf",
|
||||||
|
"wogg.333232.xyz"
|
||||||
|
],
|
||||||
|
"entryValues": [
|
||||||
|
"https://www.wogg.net",
|
||||||
|
"https://wogg.xxooo.cf",
|
||||||
|
"https://wogg.888484.xyz",
|
||||||
|
"https://www.wogg.bf",
|
||||||
|
"https://wogg.333232.xyz"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
211
javascript/anime/src/zh/yydsys.js
Normal file
211
javascript/anime/src/zh/yydsys.js
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
const mangayomiSources = [{
|
||||||
|
"name": "多多影音",
|
||||||
|
"lang": "zh",
|
||||||
|
"baseUrl": "https://tv.yydsys.top",
|
||||||
|
"apiUrl": "",
|
||||||
|
"iconUrl": "https://tv.yydsys.top/template/DYXS2/static/picture/logo.png",
|
||||||
|
"typeSource": "single",
|
||||||
|
"isManga": false,
|
||||||
|
"isNsfw": false,
|
||||||
|
"version": "0.0.2",
|
||||||
|
"dateFormat": "",
|
||||||
|
"dateFormatLocale": "",
|
||||||
|
"pkgPath": "anime/src/zh/yydsys.js"
|
||||||
|
}];
|
||||||
|
class DefaultExtension extends MProvider {
|
||||||
|
patternQuark = /(https:\/\/pan\.quark\.cn\/s\/[^"]+)/;
|
||||||
|
patternUc = /(https:\/\/drive\.uc\.cn\/s\/[^"]+)/;
|
||||||
|
getHeaders(url) {
|
||||||
|
throw new Error("getHeaders not implemented");
|
||||||
|
}
|
||||||
|
async getPopular(page) {
|
||||||
|
const baseUrl = new SharedPreferences().get("url");
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl, { "Referer": baseUrl });
|
||||||
|
const elements = new Document(response.body).select("div.module-item");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
let oneA = element.selectFirst('.module-item-cover .module-item-pic a');
|
||||||
|
const name = oneA.attr("title");
|
||||||
|
const imageUrl = element.selectFirst(".module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const link = oneA.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
const baseUrl = new SharedPreferences().get("url");
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl + `/index.php/vod/show/id/1/page/${page}.html`, { "Referer": baseUrl });
|
||||||
|
const elements = new Document(response.body).select("div.module-item");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
let oneA = element.selectFirst('.module-item-cover .module-item-pic a');
|
||||||
|
const name = oneA.attr("title");
|
||||||
|
const imageUrl = element.selectFirst(".module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const link = oneA.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async search(query, page, filters) {
|
||||||
|
const baseUrl = new SharedPreferences().get("url");
|
||||||
|
if (query == "") {
|
||||||
|
var categories;
|
||||||
|
for (const filter of filters) {
|
||||||
|
if (filter["type"] == "categories") {
|
||||||
|
categories = filter["values"][filter["state"]]["value"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl + `/index.php/vod/show/id/${categories}/page/${page}.html`, { "Referer": baseUrl });
|
||||||
|
const elements = new Document(response.body).select("div.module-item");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
let oneA = element.selectFirst('.module-item-cover .module-item-pic a');
|
||||||
|
const name = oneA.attr("title");
|
||||||
|
const imageUrl = element.selectFirst(".module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const link = oneA.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl + `/index.php/vod/search/page/${page}/wd/${query}.html`, { "Referer": baseUrl });
|
||||||
|
const elements = new Document(response.body).select(".module-search-item");
|
||||||
|
const list = [];
|
||||||
|
for (const element of elements) {
|
||||||
|
let oneA = element.selectFirst('.video-info .video-info-header a');
|
||||||
|
const name = oneA.attr("title");
|
||||||
|
const imageUrl = element.selectFirst(".video-cover .module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const link = oneA.attr("href");
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
list: list,
|
||||||
|
hasNextPage: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async getDetail(url) {
|
||||||
|
const baseUrl = new SharedPreferences().get("url");
|
||||||
|
const response = await new Client({ 'useDartHttpClient': true }).get(baseUrl + url, { "Referer": baseUrl });
|
||||||
|
const document = new Document(response.body);
|
||||||
|
const imageUrl = document.selectFirst("div.video-cover .module-item-cover .module-item-pic img").attr("data-src");
|
||||||
|
const name = document.selectFirst("div.video-info .video-info-header h1").text;
|
||||||
|
const description = document.selectFirst("div.video-info .video-info-content").text.replace("[收起部分]", "").replace("[展开全部]", "");
|
||||||
|
const type_name = "电影";
|
||||||
|
let quark_share_url_list = [], uc_share_url_list = []
|
||||||
|
const share_url_list = document.select("div.module-row-one .module-row-info")
|
||||||
|
.map(e => {
|
||||||
|
const url = e.selectFirst(".module-row-title p").text;
|
||||||
|
const quarkMatches = url.match(this.patternQuark);
|
||||||
|
|
||||||
|
if (quarkMatches && quarkMatches[1]) {
|
||||||
|
quark_share_url_list.push(quarkMatches[1]);
|
||||||
|
}
|
||||||
|
const ucMatches = url.match(this.patternUc);
|
||||||
|
if (ucMatches && ucMatches[1]) {
|
||||||
|
uc_share_url_list.push(ucMatches[1]);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(url => url !== null);
|
||||||
|
let quark_episodes = await quarkFilesExtractor(quark_share_url_list, new SharedPreferences().get("quarkCookie"));
|
||||||
|
let uc_episodes = await ucFilesExtractor(uc_share_url_list, new SharedPreferences().get("ucCookie"));
|
||||||
|
let episodes = [...quark_episodes, ...uc_episodes];
|
||||||
|
return {
|
||||||
|
name, imageUrl, description, episodes
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// For anime episode video list
|
||||||
|
async getVideoList(url) {
|
||||||
|
const videos = [];
|
||||||
|
const parts = url.split('++');
|
||||||
|
const type = parts[0].toLowerCase();
|
||||||
|
|
||||||
|
let vids;
|
||||||
|
if (type === 'quark') {
|
||||||
|
let cookie = new SharedPreferences().get("quarkCookie");
|
||||||
|
if (cookie == "") {
|
||||||
|
throw new Error("请先在本扩展设置中填写夸克Cookies, 需要夸克VIP账号 \n Please fill in the Quark Cookies in this extension settings first, you need a Quark VIP account");
|
||||||
|
} else {
|
||||||
|
vids = await quarkVideosExtractor(url, cookie);
|
||||||
|
}
|
||||||
|
} else if (type === 'uc') {
|
||||||
|
let cookie = new SharedPreferences().get("ucCookie");
|
||||||
|
if (cookie == "") {
|
||||||
|
throw new Error("请先在本扩展设置中填写UC云盘Cookies \n Please fill in the UC Cloud Cookies in this extension settings first");
|
||||||
|
} else {
|
||||||
|
vids = await ucVideosExtractor(url, cookie);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error("不支持的链接类型");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const vid of vids) {
|
||||||
|
videos.push(vid);
|
||||||
|
}
|
||||||
|
return videos;
|
||||||
|
}
|
||||||
|
getFilterList() {
|
||||||
|
return [{
|
||||||
|
type: "categories",
|
||||||
|
name: "影片類型",
|
||||||
|
type_name: "SelectFilter",
|
||||||
|
values: [
|
||||||
|
{ type_name: "SelectOption", value: "1", name: "电影" },
|
||||||
|
{ type_name: "SelectOption", value: "2", name: "剧集" },
|
||||||
|
{ type_name: "SelectOption", value: "4", name: "动漫" },
|
||||||
|
{ type_name: "SelectOption", value: "3", name: "综艺" },
|
||||||
|
{ type_name: "SelectOption", value: "5", name: "短剧" },
|
||||||
|
{ type_name: "SelectOption", value: "20", name: "纪录片" }
|
||||||
|
]
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
getSourcePreferences() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"key": "quarkCookie",
|
||||||
|
"editTextPreference": {
|
||||||
|
"title": "夸克Cookies",
|
||||||
|
"summary": "填写获取到的夸克Cookies",
|
||||||
|
"value": "",
|
||||||
|
"dialogTitle": "Cookies",
|
||||||
|
"dialogMessage": "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ucCookie",
|
||||||
|
"editTextPreference": {
|
||||||
|
"title": "UC云盘Cookies",
|
||||||
|
"summary": "填写获取到的UC云盘Cookies",
|
||||||
|
"value": "",
|
||||||
|
"dialogTitle": "Cookies",
|
||||||
|
"dialogMessage": "",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "url",
|
||||||
|
"listPreference": {
|
||||||
|
"title": "Website Url",
|
||||||
|
"summary": "",
|
||||||
|
"valueIndex": 0,
|
||||||
|
"entries": [
|
||||||
|
"tv.yydsys.top",
|
||||||
|
"tv.yydsys.cc",
|
||||||
|
],
|
||||||
|
"entryValues": [
|
||||||
|
"https://tv.yydsys.top",
|
||||||
|
"https://tv.yydsys.cc",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
javascript/icon/all.netflixmirror.png
Normal file
BIN
javascript/icon/all.netflixmirror.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
526
javascript/manga/src/all/mangafire.js
Normal file
526
javascript/manga/src/all/mangafire.js
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
const mangayomiSources = [{
|
||||||
|
"name": "Mangafire",
|
||||||
|
"langs": ["en", "ja", "fr", "es", "es-la", "pt", "pt-br"],
|
||||||
|
"baseUrl": "https://mangafire.to",
|
||||||
|
"apiUrl": "",
|
||||||
|
"iconUrl": "https://mangafire.to/assets/sites/mangafire/favicon.png?v3",
|
||||||
|
"typeSource": "single",
|
||||||
|
"isManga": true,
|
||||||
|
"version": "0.1.2",
|
||||||
|
"dateFormat": "",
|
||||||
|
"dateFormatLocale": "",
|
||||||
|
"pkgPath": "manga/src/all/mangafire.js"
|
||||||
|
}];
|
||||||
|
|
||||||
|
class DefaultExtension extends MProvider {
|
||||||
|
mangaListFromPage(res) {
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
const elements = doc.select("div.unit");
|
||||||
|
const list = [];
|
||||||
|
|
||||||
|
for (const element of elements){
|
||||||
|
const name = element.selectFirst("div.info > a").text;
|
||||||
|
const imageUrl = element.selectFirst("img").getSrc;
|
||||||
|
const link = element.selectFirst("a").getHref;
|
||||||
|
list.push({name, imageUrl, link});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasNextPage = doc.selectFirst("li.page-item.active + li").text != "";
|
||||||
|
return { "list": list, "hasNextPage": hasNextPage };
|
||||||
|
}
|
||||||
|
|
||||||
|
statusFromString(status){
|
||||||
|
return {
|
||||||
|
"Releasing": 0,
|
||||||
|
"Completed": 1,
|
||||||
|
"On_Hiatus": 2,
|
||||||
|
"Discontinued": 3,
|
||||||
|
"Unrealeased": 4,
|
||||||
|
}[status] ?? 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
parseDate(date) {
|
||||||
|
const months = {
|
||||||
|
"jan": "01", "feb": "02", "mar": "03", "apr": "04", "may": "05", "jun": "06", "jul": "07", "aug": "08", "sep": "09", "oct": "10", "nov": "11", "dec": "12"
|
||||||
|
};
|
||||||
|
date = date.toLowerCase().replace(",", "").split(" ");
|
||||||
|
|
||||||
|
if (!(date[0] in months)) {
|
||||||
|
return String(new Date().valueOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
date[0] = months[date[0]];
|
||||||
|
date = [date[2], date[0], date[1]];
|
||||||
|
date = date.join("-");
|
||||||
|
return String(new Date(date).valueOf());
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPopular(page) {
|
||||||
|
console.log(`${this.source.baseUrl}/filter?keyword=&language=${this.source.lang}&sort=trending&page=${page}`);
|
||||||
|
const res = await new Client().get(`${this.source.baseUrl}/filter?keyword=&language=${this.source.lang}&sort=trending&page=${page}`);
|
||||||
|
return this.mangaListFromPage(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
const res = await new Client().get(`${this.source.baseUrl}/filter?keyword=&language=${this.source.lang}&sort=recently_updated&page=${page}`);
|
||||||
|
return this.mangaListFromPage(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(query, page, filters) {
|
||||||
|
query = query.trim().replaceAll(/\ +/g, "+");
|
||||||
|
let url = `${this.source.baseUrl}/filter?keyword=${query}`;
|
||||||
|
|
||||||
|
// Search sometimes failed because filters were empty. I experienced this mostly on android...
|
||||||
|
if (!filters || filters.length == 0) {
|
||||||
|
const res = await new Client().get(`${url}&language=${this.source.lang}&page=${page}`);
|
||||||
|
return this.mangaListFromPage(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const filter of filters[0].state) {
|
||||||
|
if (filter.state == true)
|
||||||
|
url += `&type%5B%5D=${filter.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const filter of filters[1].state) {
|
||||||
|
if (filter.state == 1)
|
||||||
|
url += `&genre%5B%5D=${filter.value}`;
|
||||||
|
else if (filter.state == 2)
|
||||||
|
url += `&genre%5B%5D=-${filter.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// &genre_mode=and
|
||||||
|
|
||||||
|
for (const filter of filters[2].state) {
|
||||||
|
if (filter.state == true)
|
||||||
|
url += `&status%5B%5D=${filter.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
url += `&language=${this.source.lang}`;
|
||||||
|
url += `&minchap=${filters[3].values[filters[3].state].value}`;
|
||||||
|
url += `&sort=${filters[4].values[filters[4].state].value}`;
|
||||||
|
|
||||||
|
const res = await new Client().get(`${url}&page=${page}`);
|
||||||
|
return this.mangaListFromPage(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDetail(url) {
|
||||||
|
// get urls
|
||||||
|
const id = url.split(".").pop();
|
||||||
|
const infoUrl = this.source.baseUrl + url;
|
||||||
|
const chapterUrl = this.source.baseUrl + `/ajax/read/${id}/chapter/${this.source.lang}`;
|
||||||
|
const detail = {};
|
||||||
|
|
||||||
|
// request
|
||||||
|
const idRes = await new Client().get(chapterUrl);
|
||||||
|
const idDoc = new Document(JSON.parse(idRes.body).result.html);
|
||||||
|
const infoRes = await new Client().get(infoUrl);
|
||||||
|
const infoDoc = new Document(infoRes.body);
|
||||||
|
|
||||||
|
// extract info
|
||||||
|
const info = infoDoc.selectFirst("div.info");
|
||||||
|
const sidebar = infoDoc.select("aside.sidebar div.meta div");
|
||||||
|
detail.name = info.selectFirst("h1").text;
|
||||||
|
detail.status = this.statusFromString(info.selectFirst("p").text);
|
||||||
|
detail.imageUrl = infoDoc.selectFirst("div.poster img").getSrc;
|
||||||
|
detail.author = sidebar[0].selectFirst("a").text;
|
||||||
|
detail.description = infoDoc.selectFirst("div#synopsis").text.trim();
|
||||||
|
detail.genre = sidebar[2].select("a");
|
||||||
|
detail.genre.forEach((e, i) => {
|
||||||
|
detail.genre[i] = e.text;
|
||||||
|
});
|
||||||
|
|
||||||
|
// get chapter
|
||||||
|
const ids = idDoc.select("a");
|
||||||
|
const chapRes = await new Client().get(this.source.baseUrl + `/ajax/manga/${id}/chapter/${this.source.lang}`);
|
||||||
|
const chapDoc = new Document(JSON.parse(chapRes.body).result);
|
||||||
|
const chapElements = chapDoc.selectFirst(".scroll-sm").children;
|
||||||
|
detail.chapters = [];
|
||||||
|
for (let i = 0; i < ids.length; i++) {
|
||||||
|
const name = ids[i].text;
|
||||||
|
const id = ids[i].attr("data-id");
|
||||||
|
const url = this.source.baseUrl + `/ajax/read/chapter/${id}`;
|
||||||
|
let dateUpload;
|
||||||
|
try {
|
||||||
|
dateUpload = this.parseDate(chapElements[i].selectFirst("span + span").text);
|
||||||
|
} catch (_) {
|
||||||
|
dateUpload = null
|
||||||
|
}
|
||||||
|
|
||||||
|
detail.chapters.push({ name, url, dateUpload });
|
||||||
|
}
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For manga chapter pages
|
||||||
|
async getPageList(url) {
|
||||||
|
const res = await new Client().get(url);
|
||||||
|
const data = JSON.parse(res.body);
|
||||||
|
const pages = [];
|
||||||
|
data.result.images.forEach(img => {
|
||||||
|
pages.push(img[0]);
|
||||||
|
});
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilterList() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
name: "Type",
|
||||||
|
state: [
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Manga",
|
||||||
|
value: "manga"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "One-Shot",
|
||||||
|
value: "one_shot"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Doujinshi",
|
||||||
|
value: "doujinshi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Novel",
|
||||||
|
value: "novel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Manhwa",
|
||||||
|
value: "manhwa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Manhua",
|
||||||
|
value: "manhua"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
name: "Genre",
|
||||||
|
state: [
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Action",
|
||||||
|
value: "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Adventure",
|
||||||
|
value: "78"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Avant Garde",
|
||||||
|
value: "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Boys Love",
|
||||||
|
value: "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Comedy",
|
||||||
|
value: "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Demons",
|
||||||
|
value: "77"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Drama",
|
||||||
|
value: "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Ecchi",
|
||||||
|
value: "7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Fantasy",
|
||||||
|
value: "79"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Girls Love",
|
||||||
|
value: "9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Gourmet",
|
||||||
|
value: "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Harem",
|
||||||
|
value: "11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Horror",
|
||||||
|
value: "530"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Isekai",
|
||||||
|
value: "13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Iyashikei",
|
||||||
|
value: "531"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Josei",
|
||||||
|
value: "15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Kids",
|
||||||
|
value: "532"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Magic",
|
||||||
|
value: "539"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Mahou Shoujo",
|
||||||
|
value: "533"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Martial Arts",
|
||||||
|
value: "534"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Mecha",
|
||||||
|
value: "19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Military",
|
||||||
|
value: "535"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Music",
|
||||||
|
value: "21"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Mystery",
|
||||||
|
value: "22"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Parody",
|
||||||
|
value: "23"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Psychological",
|
||||||
|
value: "536"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Reverse Harem",
|
||||||
|
value: "25"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Romance",
|
||||||
|
value: "26"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "School",
|
||||||
|
value: "73"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Sci-Fi",
|
||||||
|
value: "28"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Seinen",
|
||||||
|
value: "537"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Shoujo",
|
||||||
|
value: "30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Shounen",
|
||||||
|
value: "31"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Slice of Life",
|
||||||
|
value: "538"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Space",
|
||||||
|
value: "33"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Sports",
|
||||||
|
value: "34"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "SuperPower",
|
||||||
|
value: "75"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Supernatural",
|
||||||
|
value: "76"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Suspense",
|
||||||
|
value: "37"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Thriller",
|
||||||
|
value: "38"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "TriState",
|
||||||
|
name: "Vampire",
|
||||||
|
value: "39"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "GroupFilter",
|
||||||
|
name: "Status",
|
||||||
|
state: [
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Releasing",
|
||||||
|
value: "releasing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Completed",
|
||||||
|
value: "completed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Hiatus",
|
||||||
|
value: "on_hiatus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Discontinued",
|
||||||
|
value: "discontinued"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "CheckBox",
|
||||||
|
name: "Not Yet Published",
|
||||||
|
value: "info"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectFilter",
|
||||||
|
type: "length",
|
||||||
|
name: "Length",
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: ">= 1 chapters",
|
||||||
|
value: "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: ">= 3 chapters",
|
||||||
|
value: "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: ">= 5 chapters",
|
||||||
|
value: "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: ">= 10 chapters",
|
||||||
|
value: "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: ">= 20 chapters",
|
||||||
|
value: "20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: ">= 30 chapters",
|
||||||
|
value: "30"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: ">= 50 chapters",
|
||||||
|
value: "50"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectFilter",
|
||||||
|
type: "sort",
|
||||||
|
name: "Sort",
|
||||||
|
state: 3,
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: "Added",
|
||||||
|
value: "recently_added"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: "Updated",
|
||||||
|
value: "recently_updated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: "Trending",
|
||||||
|
value: "trending"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: "Most Relevance",
|
||||||
|
value: "most_relevance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type_name: "SelectOption",
|
||||||
|
name: "Name",
|
||||||
|
value: "title_az"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getSourcePreferences() {
|
||||||
|
throw new Error("getSourcePreferences not implemented");
|
||||||
|
}
|
||||||
|
}
|
||||||
149
javascript/manga/src/en/asurascans.js
Normal file
149
javascript/manga/src/en/asurascans.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
const mangayomiSources = [{
|
||||||
|
"id": 524070078,
|
||||||
|
"name": "Asura Scans",
|
||||||
|
"lang": "en",
|
||||||
|
"baseUrl": "https://asuracomic.net",
|
||||||
|
"apiUrl": "",
|
||||||
|
"iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/en.asurascans.png",
|
||||||
|
"typeSource": "single",
|
||||||
|
"isManga": true,
|
||||||
|
"version": "0.1.65",
|
||||||
|
"dateFormat": "",
|
||||||
|
"dateFormatLocale": "",
|
||||||
|
"pkgPath": "manga/src/en/asurascans.js"
|
||||||
|
}];
|
||||||
|
|
||||||
|
class DefaultExtension extends MProvider {
|
||||||
|
getHeaders(url) {
|
||||||
|
return {
|
||||||
|
Referer: this.source.baseUrl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
mangaListFromPage(res) {
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
const mangaElements = doc.select("div.grid > a[href]");
|
||||||
|
const list = [];
|
||||||
|
for (const element of mangaElements) {
|
||||||
|
const name = element.selectFirst("span.block").text;
|
||||||
|
const imageUrl = element.selectFirst("img").getSrc;
|
||||||
|
const link = element.getHref;
|
||||||
|
list.push({ name, imageUrl, link });
|
||||||
|
}
|
||||||
|
const hasNextPage = doc.selectFirst("a.flex.bg-themecolor:contains(Next)").text != "";
|
||||||
|
return { "list": list, hasNextPage };
|
||||||
|
}
|
||||||
|
toStatus(status) {
|
||||||
|
if (status == "Ongoing")
|
||||||
|
return 0;
|
||||||
|
else if (status == "Completed")
|
||||||
|
return 1;
|
||||||
|
else if (status == "Hiatus")
|
||||||
|
return 2;
|
||||||
|
else if (status == "Dropped")
|
||||||
|
return 3;
|
||||||
|
else
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
parseDate(date) {
|
||||||
|
const months = {
|
||||||
|
"january": "01", "february": "02", "march": "03", "april": "04",
|
||||||
|
"may": "05", "june": "06", "july": "07", "august": "08",
|
||||||
|
"september": "09", "october": "10", "november": "11", "december": "12"
|
||||||
|
};
|
||||||
|
date = date.toLowerCase().replace(/(st|nd|rd|th)/g, "").split(" ");
|
||||||
|
if (!(date[0] in months)) {
|
||||||
|
return String(new Date().valueOf());
|
||||||
|
}
|
||||||
|
date[0] = months[date[0]];
|
||||||
|
const formattedDate = `${date[2]}-${date[0]}-${date[1].padStart(2, "0")}`; // Format YYYY-MM-DD
|
||||||
|
return String(new Date(formattedDate).valueOf());
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPopular(page) {
|
||||||
|
const baseUrl = new SharedPreferences().get("overrideBaseUrl1");
|
||||||
|
const res = await new Client().get(`${baseUrl}/series?name=&status=-1&types=-1&order=rating&page=${page}`);
|
||||||
|
return this.mangaListFromPage(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatestUpdates(page) {
|
||||||
|
const baseUrl = new SharedPreferences().get("overrideBaseUrl1");
|
||||||
|
const res = await new Client().get(`${baseUrl}/series?genres=&status=-1&types=-1&order=update&page=${page}`);
|
||||||
|
return this.mangaListFromPage(res);
|
||||||
|
}
|
||||||
|
async search(query, page, filters) {
|
||||||
|
const baseUrl = new SharedPreferences().get("overrideBaseUrl1");
|
||||||
|
const res = await new Client().get(`${baseUrl}/series?name=${query}&page=${page}`);
|
||||||
|
return this.mangaListFromPage(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDetail(url) {
|
||||||
|
const baseUrl = new SharedPreferences().get("overrideBaseUrl1");
|
||||||
|
const res = await new Client().get(baseUrl + "/" + url);
|
||||||
|
const doc = new Document(res.body);
|
||||||
|
const imageUrl = doc.selectFirst("img[alt=poster]")?.getSrc;
|
||||||
|
const description = doc.selectFirst("span.font-medium.text-sm")?.text.trim();
|
||||||
|
const author = doc.selectFirst("h3:contains('Author')").nextElementSibling.text.trim();
|
||||||
|
const artist = doc.selectFirst("h3:contains('Artist')").nextElementSibling.text.trim();
|
||||||
|
const status = this.toStatus(doc.selectFirst("h3:contains('Status')").nextElementSibling.text.trim());
|
||||||
|
const genre = doc.select("div[class^=space] > div.flex > button.text-white")
|
||||||
|
.map((el) => el.text.trim());
|
||||||
|
const chapters = [];
|
||||||
|
const chapterElements = doc.select("div.scrollbar-thumb-themecolor > div.group");
|
||||||
|
for (const element of chapterElements) {
|
||||||
|
const url = element.selectFirst("a").getHref;
|
||||||
|
const chNumber = element.selectFirst("h3 > a").text;
|
||||||
|
const chTitle = element.select("h3 > a > span").map((span) => span.text.trim()).join(" ").trim();
|
||||||
|
const name = chTitle == "" ? chNumber : `${chNumber} - ${chTitle}`;
|
||||||
|
|
||||||
|
let dateUpload;
|
||||||
|
try {
|
||||||
|
const dateText = element.selectFirst("h3 + h3").text.trim();
|
||||||
|
const cleanDateText = dateText.replace(/(\d+)(st|nd|rd|th)/, "$1");
|
||||||
|
dateUpload = this.parseDate(cleanDateText);
|
||||||
|
} catch (_) {
|
||||||
|
dateUpload = null
|
||||||
|
}
|
||||||
|
chapters.push({ name, url, dateUpload });
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
imageUrl,
|
||||||
|
description,
|
||||||
|
genre,
|
||||||
|
author,
|
||||||
|
artist,
|
||||||
|
status,
|
||||||
|
chapters
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getPageList(url) {
|
||||||
|
const baseUrl = new SharedPreferences().get("overrideBaseUrl1");
|
||||||
|
const res = await new Client().get(baseUrl + "/series/" + url);
|
||||||
|
const scriptData = new Document(res.body).select("script:contains(self.__next_f.push)").map((e) => e.text.substringAfter("\"").substringBeforeLast("\"")).join("");
|
||||||
|
console.log(scriptData);
|
||||||
|
const match = scriptData.match(/\\"pages\\":(\[.*?])/);
|
||||||
|
if (!match) {
|
||||||
|
throw new Error("Failed to find chapter pages");
|
||||||
|
}
|
||||||
|
const pagesData = match[1];
|
||||||
|
|
||||||
|
const pageList = JSON.parse(pagesData.replace(/\\(.)/g, "$1"))
|
||||||
|
.sort((a, b) => a.order - b.order);
|
||||||
|
return pageList;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSourcePreferences() {
|
||||||
|
return [{
|
||||||
|
"key": "overrideBaseUrl1",
|
||||||
|
"editTextPreference": {
|
||||||
|
"title": "Override BaseUrl",
|
||||||
|
"summary": "https://asuracomic.net",
|
||||||
|
"value": "https://asuracomic.net",
|
||||||
|
"dialogTitle": "Override BaseUrl",
|
||||||
|
"dialogMessage": "",
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -63,9 +63,10 @@ class Source {
|
|||||||
dateFormatLocale = json['dateFormatLocale'] ?? "";
|
dateFormatLocale = json['dateFormatLocale'] ?? "";
|
||||||
hasCloudflare = json['hasCloudflare'] ?? false;
|
hasCloudflare = json['hasCloudflare'] ?? false;
|
||||||
iconUrl = json['iconUrl'] ?? "";
|
iconUrl = json['iconUrl'] ?? "";
|
||||||
id = (json['id'] ?? sourceCodeLang == 0
|
id = (json['id'] ??
|
||||||
? 'mangayomi-"${json['lang'] ?? ""}"."${json['name'] ?? ""}"'
|
(sourceCodeLang == 0
|
||||||
: 'mangayomi-js-"${json['lang'] ?? ""}"."${json['name'] ?? ""}"')
|
? 'mangayomi-"${json['lang'] ?? ""}"."${json['name'] ?? ""}"'
|
||||||
|
: 'mangayomi-js-"${json['lang'] ?? ""}"."${json['name'] ?? ""}"'))
|
||||||
.hashCode;
|
.hashCode;
|
||||||
isFullData = json['isFullData'] ?? false;
|
isFullData = json['isFullData'] ?? false;
|
||||||
itemType = json['itemType'] ?? ItemType.manga;
|
itemType = json['itemType'] ?? ItemType.manga;
|
||||||
|
|||||||
@@ -65,19 +65,31 @@ List<Source> _searchJsSources(Directory dir) {
|
|||||||
if (entity is Directory) {
|
if (entity is Directory) {
|
||||||
sourceList.addAll(_searchJsSources(entity));
|
sourceList.addAll(_searchJsSources(entity));
|
||||||
} else if (entity is File && entity.path.endsWith('.js')) {
|
} else if (entity is File && entity.path.endsWith('.js')) {
|
||||||
final RegExp regex = RegExp(
|
final regex = RegExp(r'const\s+mangayomiSources\s*=\s*(\[.*?\]);',
|
||||||
r'const\s+mangayomiSources\s*=\s*(\[.*?\]);',
|
|
||||||
dotAll: true);
|
dotAll: true);
|
||||||
final defaultSource = Source();
|
final defaultSource = Source();
|
||||||
Match? match = regex.firstMatch(entity.readAsStringSync());
|
final match = regex.firstMatch(entity.readAsStringSync());
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
sourceList.addAll((jsonDecode(match.group(1)!) as List)
|
for (var sourceJson in jsonDecode(match.group(1)!) as List) {
|
||||||
.map((e) => Source.fromJson(e)
|
final langs = sourceJson["langs"] as List?;
|
||||||
..sourceCodeLanguage = 1
|
Source source = Source.fromJson(sourceJson)
|
||||||
..appMinVerReq = defaultSource.appMinVerReq
|
..sourceCodeLanguage = 1
|
||||||
..sourceCodeUrl =
|
..appMinVerReq = defaultSource.appMinVerReq
|
||||||
"https://raw.githubusercontent.com/Schnitzel5/mangayomi-extensions/$branchName/javascript/${e["pkgPath"] ?? e["pkgName"]}")
|
..sourceCodeUrl =
|
||||||
.toList());
|
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/javascript/${sourceJson["pkgPath"] ?? sourceJson["pkgName"]}";
|
||||||
|
if (sourceJson["id"] != null) {
|
||||||
|
source = source..id = int.tryParse("${sourceJson["id"]}");
|
||||||
|
}
|
||||||
|
if (langs?.isNotEmpty ?? false) {
|
||||||
|
for (var lang in langs!) {
|
||||||
|
sourceList.add(Source.fromJson(source.toJson())
|
||||||
|
..lang = lang
|
||||||
|
..id = 'mangayomi-js-"$lang"."${source.name}"'.hashCode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sourceList.add(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user