Update
@@ -4,7 +4,7 @@ This repository contains the available extension catalogues for the Mangayomi ap
|
|||||||
|
|
||||||
## How to add the extensions
|
## How to add the extensions
|
||||||
|
|
||||||
Click on one of the buttons below to add the corresponding repository/repositories:
|
Click on one of the buttons below to add the corresponding repository/repositories (Manga & novels only):
|
||||||
|
|
||||||
<a href="https://intradeus.github.io/http-protocol-redirector?r=mangayomi://add-repo?repo_name=mangayomi-extensions%26repo_url=https://github.com/kodjodevf/mangayomi-extensions%26manga_url=https://kodjodevf.github.io/mangayomi-extensions/index.json%26anime_url=https://kodjodevf.github.io/mangayomi-extensions/anime_index.json%26novel_url=https://kodjodevf.github.io/mangayomi-extensions/novel_index.json"><img alt="Add all repositories" src="images/add-all-repositories.png" height="35"></a>
|
<a href="https://intradeus.github.io/http-protocol-redirector?r=mangayomi://add-repo?repo_name=mangayomi-extensions%26repo_url=https://github.com/kodjodevf/mangayomi-extensions%26manga_url=https://kodjodevf.github.io/mangayomi-extensions/index.json%26anime_url=https://kodjodevf.github.io/mangayomi-extensions/anime_index.json%26novel_url=https://kodjodevf.github.io/mangayomi-extensions/novel_index.json"><img alt="Add all repositories" src="images/add-all-repositories.png" height="35"></a>
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import '../../model/source.dart';
|
|
||||||
import 'multisrc/datalifeengine/sources.dart';
|
|
||||||
import 'multisrc/dopeflix/sources.dart';
|
|
||||||
import 'multisrc/zorotheme/sources.dart';
|
|
||||||
import 'src/all/animeworldindia/sources.dart';
|
|
||||||
import 'src/all/nyaa/source.dart';
|
|
||||||
import 'src/ar/okanime/source.dart';
|
|
||||||
import 'src/de/animetoast/source.dart';
|
|
||||||
import 'src/en/animepahe/source.dart';
|
|
||||||
import 'src/en/gogoanime/source.dart';
|
|
||||||
import 'src/en/kisskh/source.dart';
|
|
||||||
import 'src/en/nineanimetv/source.dart';
|
|
||||||
import 'src/en/vumeto/source.dart';
|
|
||||||
import 'src/es/animeonlineninja/source.dart';
|
|
||||||
import 'src/fr/animesama/source.dart';
|
|
||||||
import 'src/fr/anizone/source.dart';
|
|
||||||
import 'src/hi/yomovies/source.dart';
|
|
||||||
import 'src/en/uhdmovies/source.dart';
|
|
||||||
import 'src/fr/animesultra/source.dart';
|
|
||||||
import 'src/fr/franime/source.dart';
|
|
||||||
import 'src/fr/otakufr/source.dart';
|
|
||||||
import 'src/id/nimegami/source.dart';
|
|
||||||
import 'src/id/oploverz/source.dart';
|
|
||||||
import 'src/id/otakudesu/source.dart';
|
|
||||||
import 'src/it/animesaturn/source.dart';
|
|
||||||
import 'src/pt/animesvision/source.dart';
|
|
||||||
import 'src/sq/filma24/source.dart';
|
|
||||||
import 'src/tr/diziwatch/source.dart';
|
|
||||||
import 'src/en/donghuastream/source.dart';
|
|
||||||
|
|
||||||
List<Source> dartAnimesourceList = [
|
|
||||||
gogoanimeSource,
|
|
||||||
franimeSource,
|
|
||||||
otakufr,
|
|
||||||
animesultraSource,
|
|
||||||
...zorothemeSourcesList,
|
|
||||||
okanimeSource,
|
|
||||||
otakudesu,
|
|
||||||
nimegami,
|
|
||||||
oploverz,
|
|
||||||
...dopeflixSourcesList,
|
|
||||||
animesaturn,
|
|
||||||
uhdmoviesSource,
|
|
||||||
...datalifeengineSourcesList,
|
|
||||||
filma24,
|
|
||||||
yomoviesSource,
|
|
||||||
animesamaSource,
|
|
||||||
nineanimetv,
|
|
||||||
...animeworldindiaSourcesList,
|
|
||||||
nyaaSource,
|
|
||||||
animepaheSource,
|
|
||||||
animetoast,
|
|
||||||
animesvision,
|
|
||||||
diziwatchSource,
|
|
||||||
aniZoneSource,
|
|
||||||
animeonlineninjaSource,
|
|
||||||
kisskhSource,
|
|
||||||
vumetoSource,
|
|
||||||
donghuastreamSource,
|
|
||||||
];
|
|
||||||
@@ -1,457 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
|
|
||||||
class DataLifeEngine extends MProvider {
|
|
||||||
DataLifeEngine({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get supportsLatest => false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => getPreferenceValue(source.id, "overrideBaseUrl");
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl${getPath(source)}page/$page"),
|
|
||||||
)).body;
|
|
||||||
return animeFromElement(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
return MPages([], false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String res = "";
|
|
||||||
if (query.isNotEmpty) {
|
|
||||||
if (query.length < 4)
|
|
||||||
throw "La recherche est suspendue! La chaîne de recherche est vide ou contient moins de 4 caractères.";
|
|
||||||
final headers = {
|
|
||||||
"Host": Uri.parse(baseUrl).host,
|
|
||||||
"Origin": baseUrl,
|
|
||||||
"Referer": "$baseUrl/",
|
|
||||||
};
|
|
||||||
final cleanQuery = query.replaceAll(" ", "+");
|
|
||||||
if (page == 1) {
|
|
||||||
res =
|
|
||||||
(await client.post(
|
|
||||||
Uri.parse(
|
|
||||||
"$baseUrl?do=search&subaction=search&story=$cleanQuery",
|
|
||||||
),
|
|
||||||
headers: headers,
|
|
||||||
)).body;
|
|
||||||
} else {
|
|
||||||
res =
|
|
||||||
(await client.post(
|
|
||||||
Uri.parse(
|
|
||||||
"$baseUrl?do=search&subaction=search&search_start=$page&full_search=0&result_from=11&story=$cleanQuery",
|
|
||||||
),
|
|
||||||
headers: headers,
|
|
||||||
)).body;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String url = "";
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "CategoriesFilter") {
|
|
||||||
if (filter.state != 0) {
|
|
||||||
url = "$baseUrl${filter.values[filter.state].value}page/$page/";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "GenresFilter") {
|
|
||||||
if (filter.state != 0) {
|
|
||||||
url = "$baseUrl${filter.values[filter.state].value}page/$page/";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res = (await client.get(Uri.parse(url))).body;
|
|
||||||
}
|
|
||||||
|
|
||||||
return animeFromElement(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
String res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl${getUrlWithoutDomain(url)}"),
|
|
||||||
)).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final description = xpath(res, '//span[@itemprop="description"]/text()');
|
|
||||||
anime.description = description.isNotEmpty ? description.first : "";
|
|
||||||
anime.genre = xpath(res, '//span[@itemprop="genre"]/a/text()');
|
|
||||||
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
|
|
||||||
if (source.name == "French Anime") {
|
|
||||||
final epsData = xpath(res, '//div[@class="eps"]/text()');
|
|
||||||
for (var epData in epsData.first.split('\n')) {
|
|
||||||
final data = epData.split('!');
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = "Episode ${data.first}";
|
|
||||||
ep.url = data.last;
|
|
||||||
episodesList.add(ep);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final doc = parseHtml(res);
|
|
||||||
final elements =
|
|
||||||
doc
|
|
||||||
.select(".hostsblock div:has(a)")
|
|
||||||
.where(
|
|
||||||
(MElement e) => e.outerHtml.contains("loadVideo('https://"),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
if (elements.isNotEmpty) {
|
|
||||||
for (var element in elements) {
|
|
||||||
element = element as MElement;
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = element.className
|
|
||||||
.replaceAll("ep", "Episode ")
|
|
||||||
.replaceAll("vs", " VOSTFR")
|
|
||||||
.replaceAll("vf", " VF");
|
|
||||||
ep.url = element
|
|
||||||
.select("a")
|
|
||||||
.map(
|
|
||||||
(MElement e) => substringBefore(
|
|
||||||
substringAfter(e.attr('onclick'), "loadVideo('"),
|
|
||||||
"')",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList()
|
|
||||||
.join(",")
|
|
||||||
.replaceAll("/vd.php?u=", "");
|
|
||||||
ep.scanlator = element.className.contains('vf') ? 'VF' : 'VOSTFR';
|
|
||||||
episodesList.add(ep);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = "Film";
|
|
||||||
ep.url = doc
|
|
||||||
.select("a")
|
|
||||||
.where((MElement e) => e.outerHtml.contains("loadVideo('https://"))
|
|
||||||
.map(
|
|
||||||
(MElement e) => substringBefore(
|
|
||||||
substringAfter(e.attr('onclick'), "loadVideo('"),
|
|
||||||
"')",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList()
|
|
||||||
.join(",")
|
|
||||||
.replaceAll("/vd.php?u=", "");
|
|
||||||
episodesList.add(ep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.chapters = episodesList.reversed.toList();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final sUrls = url.split(',');
|
|
||||||
for (var sUrl in sUrls) {
|
|
||||||
List<MVideo> a = [];
|
|
||||||
if (sUrl.contains("dood") || sUrl.contains("d000")) {
|
|
||||||
a = await doodExtractor(sUrl, "DoodStream");
|
|
||||||
} else if ([
|
|
||||||
"streamhide",
|
|
||||||
"guccihide",
|
|
||||||
"streamvid",
|
|
||||||
"dhtpre",
|
|
||||||
].any((a) => sUrl.contains(a))) {
|
|
||||||
a = await streamHideExtractor(sUrl);
|
|
||||||
} else if (sUrl.contains("uqload")) {
|
|
||||||
a = await uqloadExtractor(sUrl);
|
|
||||||
} else if (sUrl.contains("upstream")) {
|
|
||||||
a = await upstreamExtractor(sUrl);
|
|
||||||
} else if (sUrl.contains("sibnet")) {
|
|
||||||
a = await sibnetExtractor(sUrl);
|
|
||||||
} else if (sUrl.contains("ok.ru")) {
|
|
||||||
a = await okruExtractor(sUrl);
|
|
||||||
} else if (sUrl.contains("vidmoly")) {
|
|
||||||
a = await vidmolyExtractor(sUrl);
|
|
||||||
} else if (sUrl.contains("streamtape")) {
|
|
||||||
a = await streamTapeExtractor(sUrl, "");
|
|
||||||
} else if (sUrl.contains("voe.sx")) {
|
|
||||||
a = await voeExtractor(sUrl, "");
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages animeFromElement(String res) {
|
|
||||||
final htmls = parseHtml(res).select("div#dle-content > div.mov");
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var h in htmls) {
|
|
||||||
final html = h.innerHtml;
|
|
||||||
final url = xpath(html, '//a/@href').first;
|
|
||||||
final name = xpath(html, '//a/text()').first;
|
|
||||||
final image = xpath(html, '//div[contains(@class,"mov")]/img/@src').first;
|
|
||||||
final season = xpath(html, '//div/span[@class="block-sai"]/text()');
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name =
|
|
||||||
"$name ${season.isNotEmpty ? season.first.replaceAll("\n", " ") : ""}";
|
|
||||||
anime.imageUrl = "$baseUrl$image";
|
|
||||||
anime.link = url;
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
final hasNextPage = xpath(res, '//span[@class="pnext"]/a/@href').isNotEmpty;
|
|
||||||
return MPages(animeList, hasNextPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> streamHideExtractor(String url) 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 = "StreamHideVid - $quality";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> upstreamExtractor(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final js = xpath(res, '//script[contains(text(), "m3u8")]/text()');
|
|
||||||
if (js.isEmpty) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final masterUrl = substringBefore(
|
|
||||||
substringAfter(unpackJs(js.first), "{file:\""),
|
|
||||||
"\"}",
|
|
||||||
);
|
|
||||||
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 = "Upstream - $quality";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> uqloadExtractor(String url) 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 = "Uqload"
|
|
||||||
..headers = {"Referer": "${Uri.parse(url).origin}/"};
|
|
||||||
return [video];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> vidmolyExtractor(String url) async {
|
|
||||||
final headers = {'Referer': 'https://vidmoly.to'};
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final playListUrlResponse = (await client.get(Uri.parse(url))).body;
|
|
||||||
final playlistUrl =
|
|
||||||
RegExp(r'file:"(\S+?)"').firstMatch(playListUrlResponse)?.group(1) ??
|
|
||||||
"";
|
|
||||||
if (playlistUrl.isEmpty) return [];
|
|
||||||
final masterPlaylistRes = await client.get(
|
|
||||||
Uri.parse(playlistUrl),
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (masterPlaylistRes.statusCode == 200) {
|
|
||||||
for (var it in substringAfter(
|
|
||||||
masterPlaylistRes.body,
|
|
||||||
"#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");
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "Vidmoly $quality"
|
|
||||||
..headers = headers;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getPath() {
|
|
||||||
if (source.name == "French Anime") return "/animes-vostfr/";
|
|
||||||
return "/serie-en-streaming/";
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
if (source.name == "Wiflix")
|
|
||||||
EditTextPreference(
|
|
||||||
key: "overrideBaseUrl",
|
|
||||||
title: "Changer l'url de base",
|
|
||||||
summary: "",
|
|
||||||
value: "https://wiflix-hd.vip",
|
|
||||||
dialogTitle: "Changer l'url de base",
|
|
||||||
dialogMessage: "",
|
|
||||||
text: "https://wiflix-hd.vip",
|
|
||||||
),
|
|
||||||
if (source.name == "French Anime")
|
|
||||||
EditTextPreference(
|
|
||||||
key: "overrideBaseUrl",
|
|
||||||
title: "Changer l'url de base",
|
|
||||||
summary: "",
|
|
||||||
value: "https://french-anime.com",
|
|
||||||
dialogTitle: "Changer l'url de base",
|
|
||||||
dialogMessage: "",
|
|
||||||
text: "https://french-anime.com",
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
HeaderFilter("La recherche de texte ignore les filtres"),
|
|
||||||
if (source.name == "French Anime")
|
|
||||||
SelectFilter("CategoriesFilter", "Catégories", 0, [
|
|
||||||
SelectFilterOption("<Sélectionner>", ""),
|
|
||||||
SelectFilterOption("Action", "/genre/action/"),
|
|
||||||
SelectFilterOption("Aventure", "/genre/aventure/"),
|
|
||||||
SelectFilterOption("Arts martiaux", "/genre/arts-martiaux/"),
|
|
||||||
SelectFilterOption("Combat", "/genre/combat/"),
|
|
||||||
SelectFilterOption("Comédie", "/genre/comedie/"),
|
|
||||||
SelectFilterOption("Drame", "/genre/drame/"),
|
|
||||||
SelectFilterOption("Epouvante", "/genre/epouvante/"),
|
|
||||||
SelectFilterOption("Fantastique", "/genre/fantastique/"),
|
|
||||||
SelectFilterOption("Fantasy", "/genre/fantasy/"),
|
|
||||||
SelectFilterOption("Mystère", "/genre/mystere/"),
|
|
||||||
SelectFilterOption("Romance", "/genre/romance/"),
|
|
||||||
SelectFilterOption("Shonen", "/genre/shonen/"),
|
|
||||||
SelectFilterOption("Surnaturel", "/genre/surnaturel/"),
|
|
||||||
SelectFilterOption("Sci-Fi", "/genre/sci-fi/"),
|
|
||||||
SelectFilterOption("School life", "/genre/school-life/"),
|
|
||||||
SelectFilterOption("Ninja", "/genre/ninja/"),
|
|
||||||
SelectFilterOption("Seinen", "/genre/seinen/"),
|
|
||||||
SelectFilterOption("Horreur", "/genre/horreur/"),
|
|
||||||
SelectFilterOption("Tranche de vie", "/genre/tranchedevie/"),
|
|
||||||
SelectFilterOption("Psychologique", "/genre/psychologique/"),
|
|
||||||
]),
|
|
||||||
if (source.name == "French Anime")
|
|
||||||
SelectFilter("GenresFilter", "Genres", 0, [
|
|
||||||
SelectFilterOption("<Sélectionner>", ""),
|
|
||||||
SelectFilterOption("Animes VF", "/animes-vf/"),
|
|
||||||
SelectFilterOption("Animes VOSTFR", "/animes-vostfr/"),
|
|
||||||
SelectFilterOption("Films VF et VOSTFR", "/films-vf-vostfr/"),
|
|
||||||
]),
|
|
||||||
if (source.name == "Wiflix")
|
|
||||||
SelectFilter("CategoriesFilter", "Catégories", 0, [
|
|
||||||
SelectFilterOption("<Sélectionner>", ""),
|
|
||||||
SelectFilterOption("Séries", "/serie-en-streaming/"),
|
|
||||||
SelectFilterOption("Films", "/film-en-streaming/"),
|
|
||||||
]),
|
|
||||||
if (source.name == "Wiflix")
|
|
||||||
SelectFilter("GenresFilter", "Genres", 0, [
|
|
||||||
SelectFilterOption("<Sélectionner>", ""),
|
|
||||||
SelectFilterOption("Action", "/film-en-streaming/action/"),
|
|
||||||
SelectFilterOption("Animation", "/film-en-streaming/animation/"),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Arts Martiaux",
|
|
||||||
"/film-en-streaming/arts-martiaux/",
|
|
||||||
),
|
|
||||||
SelectFilterOption("Aventure", "/film-en-streaming/aventure/"),
|
|
||||||
SelectFilterOption("Biopic", "/film-en-streaming/biopic/"),
|
|
||||||
SelectFilterOption("Comédie", "/film-en-streaming/comedie/"),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Comédie Dramatique",
|
|
||||||
"/film-en-streaming/comedie-dramatique/",
|
|
||||||
),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Épouvante Horreur",
|
|
||||||
"/film-en-streaming/horreur/",
|
|
||||||
),
|
|
||||||
SelectFilterOption("Drame", "/film-en-streaming/drame/"),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Documentaire",
|
|
||||||
"/film-en-streaming/documentaire/",
|
|
||||||
),
|
|
||||||
SelectFilterOption("Espionnage", "/film-en-streaming/espionnage/"),
|
|
||||||
SelectFilterOption("Famille", "/film-en-streaming/famille/"),
|
|
||||||
SelectFilterOption("Fantastique", "/film-en-streaming/fantastique/"),
|
|
||||||
SelectFilterOption("Guerre", "/film-en-streaming/guerre/"),
|
|
||||||
SelectFilterOption("Historique", "/film-en-streaming/historique/"),
|
|
||||||
SelectFilterOption("Musical", "/film-en-streaming/musical/"),
|
|
||||||
SelectFilterOption("Policier", "/film-en-streaming/policier/"),
|
|
||||||
SelectFilterOption("Romance", "/film-en-streaming/romance/"),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Science-Fiction",
|
|
||||||
"/film-en-streaming/science-fiction/",
|
|
||||||
),
|
|
||||||
SelectFilterOption("Spectacles", "/film-en-streaming/spectacles/"),
|
|
||||||
SelectFilterOption("Thriller", "/film-en-streaming/thriller/"),
|
|
||||||
SelectFilterOption("Western", "/film-en-streaming/western/"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DataLifeEngine main(MSource source) {
|
|
||||||
return DataLifeEngine(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import '../../../../model/source.dart';
|
|
||||||
import 'src/frenchanime/frenchanime.dart';
|
|
||||||
import 'src/wiflix/wiflix.dart';
|
|
||||||
|
|
||||||
const _datalifeengineVersion = "0.0.65";
|
|
||||||
const _datalifeengineSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/multisrc/datalifeengine/datalifeengine.dart";
|
|
||||||
|
|
||||||
List<Source> get datalifeengineSourcesList => _datalifeengineSourcesList;
|
|
||||||
List<Source> _datalifeengineSourcesList =
|
|
||||||
[
|
|
||||||
//French Anime (FR)
|
|
||||||
frenchanimeSource,
|
|
||||||
//Wiflix (FR)
|
|
||||||
wiflixSource,
|
|
||||||
]
|
|
||||||
.map(
|
|
||||||
(e) =>
|
|
||||||
e
|
|
||||||
..sourceCodeUrl = _datalifeengineSourceCodeUrl
|
|
||||||
..version = _datalifeengineVersion,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import '../../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get frenchanimeSource => _frenchanimeSource;
|
|
||||||
|
|
||||||
Source _frenchanimeSource = Source(
|
|
||||||
name: "French Anime",
|
|
||||||
baseUrl: "https://french-anime.com",
|
|
||||||
lang: "fr",
|
|
||||||
typeSource: "datalifeengine",
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/multisrc/datalifeengine/src/frenchanime/icon.png",
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -1,13 +0,0 @@
|
|||||||
import '../../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get wiflixSource => _wiflixSource;
|
|
||||||
|
|
||||||
Source _wiflixSource = Source(
|
|
||||||
name: "Wiflix",
|
|
||||||
baseUrl: "https://wiflix-hd.vip",
|
|
||||||
lang: "fr",
|
|
||||||
typeSource: "datalifeengine",
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/multisrc/datalifeengine/src/wiflix/icon.png",
|
|
||||||
);
|
|
||||||
@@ -1,577 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class DopeFlix extends MProvider {
|
|
||||||
DopeFlix({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => getPreferenceValue(source.id, "preferred_domain");
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"$baseUrl/${getPreferenceValue(source.id, "preferred_popular_page")}?page=$page",
|
|
||||||
),
|
|
||||||
)).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl/home"))).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final path =
|
|
||||||
'//section[contains(text(),"${getPreferenceValue(source.id, "preferred_latest_page")}")]/div/div[@class="film_list-wrap"]/div[@class="flw-item"]/div[@class="film-poster"]';
|
|
||||||
final urls = xpath(res, '$path/a/@href');
|
|
||||||
final names = xpath(res, '$path/a/@title');
|
|
||||||
final images = xpath(res, '$path/img/@data-src');
|
|
||||||
|
|
||||||
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, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String url = "$baseUrl";
|
|
||||||
|
|
||||||
if (query.isNotEmpty) {
|
|
||||||
url += "/search/${query.replaceAll(" ", "-")}?page=$page";
|
|
||||||
} else {
|
|
||||||
url += "/filter/?page=$page";
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "TypeFilter") {
|
|
||||||
final type = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}type=$type";
|
|
||||||
} else if (filter.type == "QualityFilter") {
|
|
||||||
final quality = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}quality=$quality";
|
|
||||||
} else if (filter.type == "ReleaseYearFilter") {
|
|
||||||
final year = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}release_year=$year";
|
|
||||||
} else if (filter.type == "GenresFilter") {
|
|
||||||
final genre = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
if (genre.isNotEmpty) {
|
|
||||||
url += "${ll(url)}genre=";
|
|
||||||
for (var st in genre) {
|
|
||||||
url += "${st.value}-";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filter.type == "CountriesFilter") {
|
|
||||||
final country = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
if (country.isNotEmpty) {
|
|
||||||
url += "${ll(url)}country=";
|
|
||||||
for (var st in country) {
|
|
||||||
url += "${st.value}-";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
url = getUrlWithoutDomain(url);
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final description = xpath(res, '//div[@class="description"]/text()');
|
|
||||||
if (description.isNotEmpty) {
|
|
||||||
anime.description = description.first.replaceAll("Overview:", "");
|
|
||||||
}
|
|
||||||
final author = xpath(res, '//div[contains(text(),"Production")]/a/text()');
|
|
||||||
if (author.isNotEmpty) {
|
|
||||||
anime.author = author.first;
|
|
||||||
}
|
|
||||||
anime.genre = xpath(res, '//div[contains(text(),"Genre")]/a/text()');
|
|
||||||
List<MChapter> episodesList = [];
|
|
||||||
final id = xpath(res, '//div[@class="detail_page-watch"]/@data-id').first;
|
|
||||||
final dataType =
|
|
||||||
xpath(res, '//div[@class="detail_page-watch"]/@data-type').first;
|
|
||||||
if (dataType == "1") {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = "Movie";
|
|
||||||
episode.url = "$baseUrl/ajax/movie/episodes/$id";
|
|
||||||
episodesList.add(episode);
|
|
||||||
} else {
|
|
||||||
final resS =
|
|
||||||
(await client.get(Uri.parse("$baseUrl/ajax/v2/tv/seasons/$id"))).body;
|
|
||||||
|
|
||||||
final seasonIds = xpath(
|
|
||||||
resS,
|
|
||||||
'//a[@class="dropdown-item ss-item"]/@data-id',
|
|
||||||
);
|
|
||||||
final seasonNames = xpath(
|
|
||||||
resS,
|
|
||||||
'//a[@class="dropdown-item ss-item"]/text()',
|
|
||||||
);
|
|
||||||
for (int i = 0; i < seasonIds.length; i++) {
|
|
||||||
final seasonId = seasonIds[i];
|
|
||||||
final seasonName = seasonNames[i];
|
|
||||||
|
|
||||||
final html =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl/ajax/v2/season/episodes/$seasonId"),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
final epsHtmls = parseHtml(html).select("div.eps-item");
|
|
||||||
|
|
||||||
for (var epH in epsHtmls) {
|
|
||||||
final epHtml = epH.outerHtml;
|
|
||||||
final episodeId =
|
|
||||||
xpath(
|
|
||||||
epHtml,
|
|
||||||
'//div[contains(@class,"eps-item")]/@data-id',
|
|
||||||
).first;
|
|
||||||
final epNum =
|
|
||||||
xpath(epHtml, '//div[@class="episode-number"]/text()').first;
|
|
||||||
final epName = xpath(epHtml, '//h3[@class="film-name"]/text()').first;
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = "$seasonName $epNum $epName";
|
|
||||||
episode.url = "$baseUrl/ajax/v2/episode/servers/$episodeId";
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anime.chapters = episodesList.reversed.toList();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
url = getUrlWithoutDomain(url);
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl/$url"))).body;
|
|
||||||
|
|
||||||
final vidsHtmls = parseHtml(res).select("ul.fss-list a.btn-play");
|
|
||||||
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (var vidH in vidsHtmls) {
|
|
||||||
final vidHtml = vidH.outerHtml;
|
|
||||||
final id = xpath(vidHtml, '//a/@data-id').first;
|
|
||||||
final name = xpath(vidHtml, '//span/text()').first;
|
|
||||||
final resSource =
|
|
||||||
(await client.get(Uri.parse("$baseUrl/ajax/sources/$id"))).body;
|
|
||||||
|
|
||||||
final vidUrl = substringBefore(
|
|
||||||
substringAfter(resSource, "\"link\":\""),
|
|
||||||
"\"",
|
|
||||||
);
|
|
||||||
List<MVideo> a = [];
|
|
||||||
String masterUrl = "";
|
|
||||||
String type = "";
|
|
||||||
if (name.contains("DoodStream")) {
|
|
||||||
a = await doodExtractor(vidUrl, "DoodStream");
|
|
||||||
} else if (["Vidcloud", "UpCloud"].contains(name)) {
|
|
||||||
final id = substringBefore(substringAfter(vidUrl, "/embed-4/"), "?");
|
|
||||||
final serverUrl = substringBefore(vidUrl, "/embed");
|
|
||||||
|
|
||||||
final resServer =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$serverUrl/ajax/embed-4/getSources?id=$id"),
|
|
||||||
headers: {"X-Requested-With": "XMLHttpRequest"},
|
|
||||||
)).body;
|
|
||||||
final encrypted = getMapValue(resServer, "encrypted");
|
|
||||||
|
|
||||||
String videoResJson = "";
|
|
||||||
if (encrypted == "true") {
|
|
||||||
final ciphered = getMapValue(resServer, "sources");
|
|
||||||
|
|
||||||
List<List<int>> indexPairs = await generateIndexPairs();
|
|
||||||
|
|
||||||
var password = '';
|
|
||||||
String ciphertext = ciphered;
|
|
||||||
int index = 0;
|
|
||||||
for (List<int> item in json.decode(json.encode(indexPairs))) {
|
|
||||||
int start = item.first + index;
|
|
||||||
int end = start + item.last;
|
|
||||||
String passSubstr = ciphered.substring(start, end);
|
|
||||||
password += passSubstr;
|
|
||||||
ciphertext = ciphertext.replaceFirst(passSubstr, "");
|
|
||||||
index += item.last;
|
|
||||||
}
|
|
||||||
videoResJson = decryptAESCryptoJS(ciphertext, password);
|
|
||||||
masterUrl =
|
|
||||||
((json.decode(videoResJson) as List<Map<String, dynamic>>)
|
|
||||||
.first)['file'];
|
|
||||||
|
|
||||||
type =
|
|
||||||
((json.decode(videoResJson) as List<Map<String, dynamic>>)
|
|
||||||
.first)['type'];
|
|
||||||
} else {
|
|
||||||
masterUrl =
|
|
||||||
((json.decode(resServer)["sources"] as List<Map<String, dynamic>>)
|
|
||||||
.first)['file'];
|
|
||||||
|
|
||||||
type =
|
|
||||||
((json.decode(resServer)["sources"] as List<Map<String, dynamic>>)
|
|
||||||
.first)['type'];
|
|
||||||
}
|
|
||||||
|
|
||||||
final tracks =
|
|
||||||
(json.decode(resServer)['tracks'] as List)
|
|
||||||
.where((e) => e['kind'] == 'captions' ? true : false)
|
|
||||||
.toList();
|
|
||||||
List<MTrack> subtitles = [];
|
|
||||||
|
|
||||||
for (var sub in tracks) {
|
|
||||||
try {
|
|
||||||
MTrack subtitle = MTrack();
|
|
||||||
subtitle
|
|
||||||
..label = sub["label"]
|
|
||||||
..file = sub["file"];
|
|
||||||
subtitles.add(subtitle);
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
subtitles = sortSubs(subtitles, source.id);
|
|
||||||
if (type == "hls") {
|
|
||||||
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 as String).split("/").sublist(0, (masterUrl as String).split("/").length - 1).join("/")}/$videoUrl";
|
|
||||||
}
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "$name - $quality"
|
|
||||||
..subtitles = subtitles;
|
|
||||||
a.add(video);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = masterUrl
|
|
||||||
..originalUrl = masterUrl
|
|
||||||
..quality = "$name - Default"
|
|
||||||
..subtitles = subtitles;
|
|
||||||
a.add(video);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortVideos(videos, source.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<List<int>>> generateIndexPairs() async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("https://rabbitstream.net/js/player/prod/e4-player.min.js"),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
String script = substringBefore(substringAfter(res, "const "), "()");
|
|
||||||
script = script.substring(0, script.lastIndexOf(','));
|
|
||||||
final list =
|
|
||||||
script
|
|
||||||
.split(",")
|
|
||||||
.map((String e) {
|
|
||||||
String value = substringAfter(e, "=");
|
|
||||||
if (value.contains("0x")) {
|
|
||||||
return int.parse(substringAfter(value, "0x"), radix: 16);
|
|
||||||
} else {
|
|
||||||
return int.parse(value);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.toList()
|
|
||||||
.skip(1)
|
|
||||||
.toList();
|
|
||||||
return chunked(
|
|
||||||
list,
|
|
||||||
2,
|
|
||||||
).map((List<int> list) => list.reversed.toList()).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<List<int>> chunked(List<int> list, int size) {
|
|
||||||
List<List<int>> chunks = [];
|
|
||||||
for (int i = 0; i < list.length; i += size) {
|
|
||||||
int end = list.length;
|
|
||||||
if (i + size < list.length) {
|
|
||||||
end = i + size;
|
|
||||||
}
|
|
||||||
chunks.add(list.sublist(i, end));
|
|
||||||
}
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages parseAnimeList(String res) {
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final path =
|
|
||||||
'//div[@class="film_list-wrap"]/div[@class="flw-item"]/div[@class="film-poster"]';
|
|
||||||
final urls = xpath(res, '$path/a/@href');
|
|
||||||
final names = xpath(res, '$path/a/@title');
|
|
||||||
final images = xpath(res, '$path/img/@data-src');
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final pages = xpath(
|
|
||||||
res,
|
|
||||||
'//ul[contains(@class,"pagination")]/li/a[@title="Next"]/@title',
|
|
||||||
);
|
|
||||||
return MPages(animeList, pages.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
SelectFilter("TypeFilter", "Type", 0, [
|
|
||||||
SelectFilterOption("All", "all"),
|
|
||||||
SelectFilterOption("Movies", "movies"),
|
|
||||||
SelectFilterOption("TV Shows", "tv"),
|
|
||||||
]),
|
|
||||||
SelectFilter("QualityFilter", "Quality", 0, [
|
|
||||||
SelectFilterOption("All", "all"),
|
|
||||||
SelectFilterOption("HD", "HD"),
|
|
||||||
SelectFilterOption("SD", "SD"),
|
|
||||||
SelectFilterOption("CAM", "CAM"),
|
|
||||||
]),
|
|
||||||
SelectFilter("ReleaseYearFilter", "Released at", 0, [
|
|
||||||
SelectFilterOption("All", "all"),
|
|
||||||
SelectFilterOption("2024", "2024"),
|
|
||||||
SelectFilterOption("2023", "2023"),
|
|
||||||
SelectFilterOption("2022", "2022"),
|
|
||||||
SelectFilterOption("2021", "2021"),
|
|
||||||
SelectFilterOption("2020", "2020"),
|
|
||||||
SelectFilterOption("2019", "2019"),
|
|
||||||
SelectFilterOption("2018", "2018"),
|
|
||||||
SelectFilterOption("Older", "older-2018"),
|
|
||||||
]),
|
|
||||||
SeparatorFilter(),
|
|
||||||
GroupFilter("GenresFilter", "Genre", [
|
|
||||||
CheckBoxFilter("Action", "10"),
|
|
||||||
CheckBoxFilter("Action & Adventure", "24"),
|
|
||||||
CheckBoxFilter("Adventure", "18"),
|
|
||||||
CheckBoxFilter("Animation", "3"),
|
|
||||||
CheckBoxFilter("Biography", "37"),
|
|
||||||
CheckBoxFilter("Comedy", "7"),
|
|
||||||
CheckBoxFilter("Crime", "2"),
|
|
||||||
CheckBoxFilter("Documentary", "11"),
|
|
||||||
CheckBoxFilter("Drama", "4"),
|
|
||||||
CheckBoxFilter("Family", "9"),
|
|
||||||
CheckBoxFilter("Fantasy", "13"),
|
|
||||||
CheckBoxFilter("History", "19"),
|
|
||||||
CheckBoxFilter("Horror", "14"),
|
|
||||||
CheckBoxFilter("Kids", "27"),
|
|
||||||
CheckBoxFilter("Music", "15"),
|
|
||||||
CheckBoxFilter("Mystery", "1"),
|
|
||||||
CheckBoxFilter("News", "34"),
|
|
||||||
CheckBoxFilter("Reality", "22"),
|
|
||||||
CheckBoxFilter("Romance", "12"),
|
|
||||||
CheckBoxFilter("Sci-Fi & Fantasy", "31"),
|
|
||||||
CheckBoxFilter("Science Fiction", "5"),
|
|
||||||
CheckBoxFilter("Soap", "35"),
|
|
||||||
CheckBoxFilter("Talk", "29"),
|
|
||||||
CheckBoxFilter("Thriller", "16"),
|
|
||||||
CheckBoxFilter("TV Movie", "8"),
|
|
||||||
CheckBoxFilter("War", "17"),
|
|
||||||
CheckBoxFilter("War & Politics", "28"),
|
|
||||||
CheckBoxFilter("Western", "6"),
|
|
||||||
]),
|
|
||||||
GroupFilter("CountriesFilter", "Countries", [
|
|
||||||
CheckBoxFilter("Argentina", "11"),
|
|
||||||
CheckBoxFilter("Australia", "151"),
|
|
||||||
CheckBoxFilter("Austria", "4"),
|
|
||||||
CheckBoxFilter("Belgium", "44"),
|
|
||||||
CheckBoxFilter("Brazil", "190"),
|
|
||||||
CheckBoxFilter("Canada", "147"),
|
|
||||||
CheckBoxFilter("China", "101"),
|
|
||||||
CheckBoxFilter("Czech Republic", "231"),
|
|
||||||
CheckBoxFilter("Denmark", "222"),
|
|
||||||
CheckBoxFilter("Finland", "158"),
|
|
||||||
CheckBoxFilter("France", "3"),
|
|
||||||
CheckBoxFilter("Germany", "96"),
|
|
||||||
CheckBoxFilter("Hong Kong", "93"),
|
|
||||||
CheckBoxFilter("Hungary", "72"),
|
|
||||||
CheckBoxFilter("India", "105"),
|
|
||||||
CheckBoxFilter("Ireland", "196"),
|
|
||||||
CheckBoxFilter("Israel", "24"),
|
|
||||||
CheckBoxFilter("Italy", "205"),
|
|
||||||
CheckBoxFilter("Japan", "173"),
|
|
||||||
CheckBoxFilter("Luxembourg", "91"),
|
|
||||||
CheckBoxFilter("Mexico", "40"),
|
|
||||||
CheckBoxFilter("Netherlands", "172"),
|
|
||||||
CheckBoxFilter("New Zealand", "122"),
|
|
||||||
CheckBoxFilter("Norway", "219"),
|
|
||||||
CheckBoxFilter("Poland", "23"),
|
|
||||||
CheckBoxFilter("Romania", "170"),
|
|
||||||
CheckBoxFilter("Russia", "109"),
|
|
||||||
CheckBoxFilter("South Africa", "200"),
|
|
||||||
CheckBoxFilter("South Korea", "135"),
|
|
||||||
CheckBoxFilter("Spain", "62"),
|
|
||||||
CheckBoxFilter("Sweden", "114"),
|
|
||||||
CheckBoxFilter("Switzerland", "41"),
|
|
||||||
CheckBoxFilter("Taiwan", "119"),
|
|
||||||
CheckBoxFilter("Thailand", "57"),
|
|
||||||
CheckBoxFilter("United Kingdom", "180"),
|
|
||||||
CheckBoxFilter("United States of America", "129"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
if (source.name == "DopeBox")
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_domain",
|
|
||||||
title: "Preferred domain",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["dopebox.to", "dopebox.se"],
|
|
||||||
entryValues: ["https://dopebox.to", "https://dopebox.se"],
|
|
||||||
),
|
|
||||||
if (source.name == "SFlix")
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_domain",
|
|
||||||
title: "Preferred domain",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["sflix.to", "sflix.se"],
|
|
||||||
entryValues: ["https://sflix.to", "https://sflix.se"],
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Preferred Quality",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["1080p", "720p", "480p", "360p"],
|
|
||||||
entryValues: ["1080p", "720p", "480p", "360p"],
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_subLang",
|
|
||||||
title: "Preferred sub language",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 1,
|
|
||||||
entries: [
|
|
||||||
"Arabic",
|
|
||||||
"English",
|
|
||||||
"French",
|
|
||||||
"German",
|
|
||||||
"Hungarian",
|
|
||||||
"Italian",
|
|
||||||
"Japanese",
|
|
||||||
"Portuguese",
|
|
||||||
"Romanian",
|
|
||||||
"Russian",
|
|
||||||
"Spanish",
|
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
"Arabic",
|
|
||||||
"English",
|
|
||||||
"French",
|
|
||||||
"German",
|
|
||||||
"Hungarian",
|
|
||||||
"Italian",
|
|
||||||
"Japanese",
|
|
||||||
"Portuguese",
|
|
||||||
"Romanian",
|
|
||||||
"Russian",
|
|
||||||
"Spanish",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_latest_page",
|
|
||||||
title: "Preferred latest page",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Movies", "TV Shows"],
|
|
||||||
entryValues: ["Latest Movies", "Latest TV Shows"],
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_popular_page",
|
|
||||||
title: "Preferred popular page",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Movies", "TV Shows"],
|
|
||||||
entryValues: ["movie", "tv-show"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
|
||||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
|
||||||
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
if (a.quality.contains(quality)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MTrack> sortSubs(List<MTrack> subs, int sourceId) {
|
|
||||||
String lang = getPreferenceValue(sourceId, "preferred_subLang");
|
|
||||||
|
|
||||||
subs.sort((MTrack a, MTrack b) {
|
|
||||||
int langMatchA = 0;
|
|
||||||
if (a.label.toLowerCase().contains(lang.toLowerCase())) {
|
|
||||||
langMatchA = 1;
|
|
||||||
}
|
|
||||||
int langMatchB = 0;
|
|
||||||
if (b.label.toLowerCase().contains(lang.toLowerCase())) {
|
|
||||||
langMatchB = 1;
|
|
||||||
}
|
|
||||||
return langMatchB - langMatchA;
|
|
||||||
});
|
|
||||||
return subs;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ll(String url) {
|
|
||||||
if (url.contains("?")) {
|
|
||||||
return "&";
|
|
||||||
}
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DopeFlix main(MSource source) {
|
|
||||||
return DopeFlix(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import '../../../../model/source.dart';
|
|
||||||
import 'src/dopebox/dopebox.dart';
|
|
||||||
import 'src/sflix/sflix.dart';
|
|
||||||
|
|
||||||
const _dopeflixVersion = "0.0.6";
|
|
||||||
const _dopeflixSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/multisrc/dopeflix/dopeflix.dart";
|
|
||||||
|
|
||||||
List<Source> get dopeflixSourcesList => _dopeflixSourcesList;
|
|
||||||
List<Source> _dopeflixSourcesList =
|
|
||||||
[
|
|
||||||
//DopeBox (EN)
|
|
||||||
dopeboxSource,
|
|
||||||
//SFlix (EN)
|
|
||||||
sflixSource,
|
|
||||||
]
|
|
||||||
.map(
|
|
||||||
(e) =>
|
|
||||||
e
|
|
||||||
..sourceCodeUrl = _dopeflixSourceCodeUrl
|
|
||||||
..version = _dopeflixVersion,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import '../../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get dopeboxSource => _dopeboxSource;
|
|
||||||
|
|
||||||
Source _dopeboxSource = Source(
|
|
||||||
name: "DopeBox",
|
|
||||||
baseUrl: "https://dopebox.to",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "dopeflix",
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/multisrc/dopeflix/src/dopebox/icon.png",
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
@@ -1,13 +0,0 @@
|
|||||||
import '../../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get sflixSource => _sflixSource;
|
|
||||||
|
|
||||||
Source _sflixSource = Source(
|
|
||||||
name: "SFlix",
|
|
||||||
baseUrl: "https://sflix.to",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "dopeflix",
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/multisrc/dopeflix/src/sflix/icon.png",
|
|
||||||
);
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import '../../../../model/source.dart';
|
|
||||||
import 'src/hianime/hianime.dart';
|
|
||||||
import 'src/kaido/kaido.dart';
|
|
||||||
|
|
||||||
const _zorothemeVersion = "0.1.75";
|
|
||||||
const _zorothemeSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/multisrc/zorotheme/zorotheme.dart";
|
|
||||||
|
|
||||||
List<Source> get zorothemeSourcesList => _zorothemeSourcesList;
|
|
||||||
List<Source> _zorothemeSourcesList =
|
|
||||||
[
|
|
||||||
//AniWatch.to (EN)
|
|
||||||
aniwatchSource,
|
|
||||||
//Kaido.to (EN)
|
|
||||||
kaidoSource,
|
|
||||||
]
|
|
||||||
.map(
|
|
||||||
(e) => e
|
|
||||||
..sourceCodeUrl = _zorothemeSourceCodeUrl
|
|
||||||
..version = _zorothemeVersion,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import '../../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get aniwatchSource => _aniwatchSource;
|
|
||||||
|
|
||||||
Source _aniwatchSource = Source(
|
|
||||||
id: 814067600,
|
|
||||||
name: "HiAnime",
|
|
||||||
baseUrl: "https://hianime.to",
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "zorotheme",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/multisrc/zorotheme/src/hianime/icon.png",
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 23 KiB |
@@ -1,13 +0,0 @@
|
|||||||
import '../../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get kaidoSource => _kaidoSource;
|
|
||||||
|
|
||||||
Source _kaidoSource = Source(
|
|
||||||
name: "Kaido.to",
|
|
||||||
baseUrl: "https://kaido.to",
|
|
||||||
lang: "en",
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
typeSource: "zorotheme",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/multisrc/zorotheme/src/kaido/icon.png",
|
|
||||||
);
|
|
||||||
@@ -1,710 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class ZoroTheme extends MProvider {
|
|
||||||
ZoroTheme({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res = (await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/most-popular?page=$page"),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
return animeElementM(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res = (await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/recently-updated?page=$page"),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
return animeElementM(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String url = "${source.baseUrl}/";
|
|
||||||
|
|
||||||
if (query.isEmpty) {
|
|
||||||
url += "filter?";
|
|
||||||
} else {
|
|
||||||
url += "search?keyword=$query";
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "TypeFilter") {
|
|
||||||
final type = filter.values[filter.state].value;
|
|
||||||
if (type.isNotEmpty) {
|
|
||||||
url += "${ll(url)}type=$type";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "StatusFilter") {
|
|
||||||
final status = filter.values[filter.state].value;
|
|
||||||
if (status.isNotEmpty) {
|
|
||||||
url += "${ll(url)}status=$status";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "RatedFilter") {
|
|
||||||
final rated = filter.values[filter.state].value;
|
|
||||||
if (rated.isNotEmpty) {
|
|
||||||
url += "${ll(url)}rated=$rated";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "ScoreFilter") {
|
|
||||||
final score = filter.values[filter.state].value;
|
|
||||||
if (score.isNotEmpty) {
|
|
||||||
url += "${ll(url)}score=$score";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "SeasonFilter") {
|
|
||||||
final season = filter.values[filter.state].value;
|
|
||||||
if (season.isNotEmpty) {
|
|
||||||
url += "${ll(url)}season=$season";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "LanguageFilter") {
|
|
||||||
final language = filter.values[filter.state].value;
|
|
||||||
if (language.isNotEmpty) {
|
|
||||||
url += "${ll(url)}language=$language";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "SortFilter") {
|
|
||||||
final sort = filter.values[filter.state].value;
|
|
||||||
if (sort.isNotEmpty) {
|
|
||||||
url += "${ll(url)}sort=$sort";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "StartYearFilter") {
|
|
||||||
final sy = filter.values[filter.state].value;
|
|
||||||
if (sy.isNotEmpty) {
|
|
||||||
url += "${ll(url)}sy=$sy";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "StartMonthFilter") {
|
|
||||||
final sm = filter.values[filter.state].value;
|
|
||||||
if (sm.isNotEmpty) {
|
|
||||||
url += "${ll(url)}sm=$sm";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "StartDayFilter") {
|
|
||||||
final sd = filter.values[filter.state].value;
|
|
||||||
if (sd.isNotEmpty) {
|
|
||||||
url += "${ll(url)}sd=$sd";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "EndYearFilter") {
|
|
||||||
final ey = filter.values[filter.state].value;
|
|
||||||
if (ey.isNotEmpty) {
|
|
||||||
url += "${ll(url)}sy=$ey";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "EndMonthFilter") {
|
|
||||||
final em = filter.values[filter.state].value;
|
|
||||||
if (em.isNotEmpty) {
|
|
||||||
url += "${ll(url)}sm=$em";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "EndDayFilter") {
|
|
||||||
final ed = filter.values[filter.state].value;
|
|
||||||
if (ed.isNotEmpty) {
|
|
||||||
url += "${ll(url)}sd=$ed";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "GenreFilter") {
|
|
||||||
final genre = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
if (genre.isNotEmpty) {
|
|
||||||
url += "${ll(url)}genre=";
|
|
||||||
for (var st in genre) {
|
|
||||||
url += "${st.value},";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url += "${ll(url)}page=$page";
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
|
|
||||||
return animeElementM(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"Currently Airing": 0, "Finished Airing": 1},
|
|
||||||
];
|
|
||||||
final res = (await client.get(Uri.parse("${source.baseUrl}$url"))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final status = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="anisc-info"]/div[contains(text(),"Status:")]/span[2]/text()',
|
|
||||||
);
|
|
||||||
if (status.isNotEmpty) {
|
|
||||||
anime.status = parseStatus(status.first, statusList);
|
|
||||||
}
|
|
||||||
|
|
||||||
final author = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="anisc-info"]/div[contains(text(),"Studios:")]/span/text()',
|
|
||||||
);
|
|
||||||
if (author.isNotEmpty) {
|
|
||||||
anime.author = author.first.replaceAll("Studios:", "");
|
|
||||||
}
|
|
||||||
final description = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="anisc-info"]/div[contains(text(),"Overview:")]/text()',
|
|
||||||
);
|
|
||||||
if (description.isNotEmpty) {
|
|
||||||
anime.description = description.first.replaceAll("Overview:", "");
|
|
||||||
}
|
|
||||||
final genre = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="anisc-info"]/div[contains(text(),"Genres:")]/a/text()',
|
|
||||||
);
|
|
||||||
|
|
||||||
anime.genre = genre;
|
|
||||||
final id = substringAfterLast(url, '-');
|
|
||||||
|
|
||||||
final urlEp =
|
|
||||||
"${source.baseUrl}/ajax${ajaxRoute('${source.baseUrl}')}/episode/list/$id";
|
|
||||||
|
|
||||||
final resEp = (await client.get(
|
|
||||||
Uri.parse(urlEp),
|
|
||||||
headers: {"referer": url},
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
final html = json.decode(resEp)["html"];
|
|
||||||
final epElements = parseHtml(html).select("a.ep-item");
|
|
||||||
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
|
|
||||||
for (var epElement in epElements) {
|
|
||||||
final number = epElement.attr("data-number");
|
|
||||||
final title = epElement.attr("title");
|
|
||||||
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = "Episode $number: $title";
|
|
||||||
episode.url = epElement.getHref;
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.chapters = episodesList.reversed.toList();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final id = substringAfterLast(url, '?ep=');
|
|
||||||
|
|
||||||
final res = (await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${source.baseUrl}/ajax${ajaxRoute('${source.baseUrl}')}/episode/servers?episodeId=$id",
|
|
||||||
),
|
|
||||||
headers: {"referer": "${source.baseUrl}/$url"},
|
|
||||||
)).body;
|
|
||||||
final html = json.decode(res)["html"];
|
|
||||||
|
|
||||||
final serverElements = parseHtml(html).select("div.server-item");
|
|
||||||
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final hosterSelection = preferenceHosterSelection(source.id);
|
|
||||||
final typeSelection = preferenceTypeSelection(source.id);
|
|
||||||
for (var serverElement in serverElements) {
|
|
||||||
final name = serverElement.text;
|
|
||||||
final id = serverElement.attr("data-id");
|
|
||||||
final subDub = serverElement.attr("data-type");
|
|
||||||
|
|
||||||
final resE = (await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${source.baseUrl}/ajax${ajaxRoute('${source.baseUrl}')}/episode/sources?id=$id",
|
|
||||||
),
|
|
||||||
headers: {"referer": "${source.baseUrl}/$url"},
|
|
||||||
)).body;
|
|
||||||
String epUrl = substringBefore(substringAfter(resE, "\"link\":\""), "\"");
|
|
||||||
List<MVideo> a = [];
|
|
||||||
if (hosterSelection.contains(name) && typeSelection.contains(subDub)) {
|
|
||||||
if (name.contains("Vidstreaming")) {
|
|
||||||
a = await rapidCloudExtractor(epUrl, "Vidstreaming - $subDub", id);
|
|
||||||
} else if (name.contains("Vidcloud")) {
|
|
||||||
a = await rapidCloudExtractor(epUrl, "Vidcloud - $subDub", id);
|
|
||||||
} else if (name.contains("StreamTape")) {
|
|
||||||
a = await streamTapeExtractor(epUrl, "StreamTape - $subDub");
|
|
||||||
} else if ([
|
|
||||||
"HD-1",
|
|
||||||
"HD-2",
|
|
||||||
"HD-3",
|
|
||||||
].any((element) => name.contains(element))) {
|
|
||||||
a = await rapidCloudExtractor(epUrl, "$name - $subDub", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortVideos(videos, source.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> rapidCloudExtractor(
|
|
||||||
String url,
|
|
||||||
String name,
|
|
||||||
String dataID,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
final headers = {'Referer': 'https://megacloud.club/'};
|
|
||||||
final serverUrl = ['https://megacloud.tv', 'https://rapid-cloud.co'];
|
|
||||||
|
|
||||||
final serverType = RegExp(r'https://megacloud\..*').hasMatch(url) ? 0 : 1;
|
|
||||||
final sourceUrl = [
|
|
||||||
'/embed-2/v2/e-1/getSources?id=',
|
|
||||||
'/ajax/embed-6-v2/getSources?id=',
|
|
||||||
];
|
|
||||||
final sourceSpliter = ['/e-1/', '/embed-6-v2/'];
|
|
||||||
final id = url.split(sourceSpliter[serverType]).last.split('?').first;
|
|
||||||
final response = await client.get(
|
|
||||||
Uri.parse('${serverUrl[serverType]}${sourceUrl[serverType]}$id'),
|
|
||||||
headers: {"X-Requested-With": "XMLHttpRequest"},
|
|
||||||
);
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final resServer = response.body;
|
|
||||||
|
|
||||||
final encrypted = getMapValue(resServer, "encrypted");
|
|
||||||
List<Map<String, dynamic>> videoResJson = [];
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
if (encrypted == "true") {
|
|
||||||
final key = await getWorkingKey(dataID);
|
|
||||||
if (key == null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final sources = await getSource(dataID, key);
|
|
||||||
if (sources == null || sources['sources'] == null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
videoResJson = sources['sources'];
|
|
||||||
} else {
|
|
||||||
videoResJson = json.decode(resServer)["sources"];
|
|
||||||
}
|
|
||||||
|
|
||||||
String masterUrl = videoResJson[0]['file'];
|
|
||||||
String type = videoResJson[0]['type'];
|
|
||||||
|
|
||||||
final tracks = (json.decode(resServer)['tracks'] as List)
|
|
||||||
.where((e) => e['kind'] == 'captions' ? true : false)
|
|
||||||
.toList();
|
|
||||||
List<MTrack> subtitles = [];
|
|
||||||
|
|
||||||
for (var sub in tracks) {
|
|
||||||
try {
|
|
||||||
MTrack subtitle = MTrack();
|
|
||||||
subtitle
|
|
||||||
..label = sub["label"]
|
|
||||||
..file = sub["file"];
|
|
||||||
subtitles.add(subtitle);
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "hls") {
|
|
||||||
final masterPlaylistRes = (await client.get(
|
|
||||||
Uri.parse(masterUrl),
|
|
||||||
headers: headers,
|
|
||||||
)).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";
|
|
||||||
}
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "$name - $quality"
|
|
||||||
..subtitles = subtitles
|
|
||||||
..headers = headers;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = masterUrl
|
|
||||||
..originalUrl = masterUrl
|
|
||||||
..quality = "$name - Default"
|
|
||||||
..subtitles = subtitles
|
|
||||||
..headers = headers;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// credits: https://github.com/50n50/sources/blob/main/hianime/hianime.js
|
|
||||||
Future<String?> getWorkingKey(String dataID) async {
|
|
||||||
try {
|
|
||||||
final res = await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
'https://raw.githubusercontent.com/itzzzme/megacloud-keys/refs/heads/main/key.txt',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final key = res.body.trim();
|
|
||||||
final sources = await getSource(dataID, key);
|
|
||||||
|
|
||||||
if (sources != null && sources['sources'] != null) return key;
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final res = await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
'https://justarion.github.io/keys/e1-player/src/data/keys.json',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final jsonRes = json.decode(res.body);
|
|
||||||
final key = jsonRes['megacloud']['anime']['key'];
|
|
||||||
final sources = await getSource(dataID, key);
|
|
||||||
if (sources != null && sources['sources'] != null) return key;
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final res = await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
'https://raw.githubusercontent.com/yogesh-hacker/MegacloudKeys/refs/heads/main/keys.json',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final jsonRes = json.decode(res.body);
|
|
||||||
final key = jsonRes['mega'];
|
|
||||||
final sources = await getSource(dataID, key);
|
|
||||||
if (sources != null && sources['sources'] != null) return key;
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final res = await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
'https://raw.githubusercontent.com/SpencerDevs/megacloud-key-updater/refs/heads/master/key.txt',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final key = res.body.trim();
|
|
||||||
final sources = await getSource(dataID, key);
|
|
||||||
if (sources != null && sources['sources'] != null) return key;
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// credits: https://github.com/50n50/sources/blob/main/hianime/hianime.js
|
|
||||||
Future<Map<String, dynamic>?> getSource(String sourceId, String key) async {
|
|
||||||
final res1 = await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/ajax/v2/episode/sources?id=$sourceId"),
|
|
||||||
);
|
|
||||||
final json1 = json.decode(res1.body);
|
|
||||||
final link = json1['link'] ?? "";
|
|
||||||
final idMatch = RegExp(r'/e-1/([^/?]+)').firstMatch(link);
|
|
||||||
final streamId = idMatch?.group(1);
|
|
||||||
|
|
||||||
if (streamId == null) return null;
|
|
||||||
|
|
||||||
final res2 = await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
'https://megacloud.blog/embed-2/v2/e-1/getSources?id=$streamId',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
final json2 = json.decode(res2.body);
|
|
||||||
final encrypted = json2['sources'];
|
|
||||||
|
|
||||||
if (encrypted == null) return null;
|
|
||||||
|
|
||||||
final result = <String, dynamic>{};
|
|
||||||
|
|
||||||
final sources = json.decode(decryptAESCryptoJS(encrypted, key));
|
|
||||||
if (sources == null) return null;
|
|
||||||
|
|
||||||
result['sources'] = sources;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages animeElementM(String res) {
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final doc = parseHtml(res);
|
|
||||||
final animeElements = doc.select('.flw-item');
|
|
||||||
for (var element in animeElements) {
|
|
||||||
final linkElement = element.selectFirst('.film-detail h3 a');
|
|
||||||
final imageElement = element.selectFirst('.film-poster img');
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = linkElement.attr('data-jname') ?? linkElement.text;
|
|
||||||
anime.imageUrl = imageElement.getSrc ?? imageElement.attr('data-src');
|
|
||||||
anime.link = linkElement.getHref;
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
final nextPageElement = doc.selectFirst('li.page-item a[title="Next"]');
|
|
||||||
return MPages(animeList, nextPageElement != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
String ajaxRoute(String baseUrl) {
|
|
||||||
if (baseUrl == "https://kaido.to") {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return "/v2";
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SelectFilterOption> yearList = [
|
|
||||||
for (var i = 1917; i < 2026; i++)
|
|
||||||
SelectFilterOption(i.toString(), i.toString()),
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
];
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
SelectFilter("TypeFilter", "Type", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
SelectFilterOption("Movie", "1"),
|
|
||||||
SelectFilterOption("TV", "2"),
|
|
||||||
SelectFilterOption("OVA", "3"),
|
|
||||||
SelectFilterOption("ONA", "4"),
|
|
||||||
SelectFilterOption("Special", "5"),
|
|
||||||
SelectFilterOption("Music", "6"),
|
|
||||||
]),
|
|
||||||
SelectFilter("StatusFilter", "Status", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
SelectFilterOption("Finished Airing", "1"),
|
|
||||||
SelectFilterOption("Currently Airing", "2"),
|
|
||||||
SelectFilterOption("Not yet aired", "3"),
|
|
||||||
]),
|
|
||||||
SelectFilter("RatedFilter", "Rated", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
SelectFilterOption("G", "1"),
|
|
||||||
SelectFilterOption("PG", "2"),
|
|
||||||
SelectFilterOption("PG-13", "3"),
|
|
||||||
SelectFilterOption("R", "4"),
|
|
||||||
SelectFilterOption("R+", "5"),
|
|
||||||
SelectFilterOption("Rx", "6"),
|
|
||||||
]),
|
|
||||||
SelectFilter("ScoreFilter", "Score", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
SelectFilterOption("(1) Appalling", "1"),
|
|
||||||
SelectFilterOption("(2) Horrible", "2"),
|
|
||||||
SelectFilterOption("(3) Very Bad", "3"),
|
|
||||||
SelectFilterOption("(4) Bad", "4"),
|
|
||||||
SelectFilterOption("(5) Average", "5"),
|
|
||||||
SelectFilterOption("(6) Fine", "6"),
|
|
||||||
SelectFilterOption("(7) Good", "7"),
|
|
||||||
SelectFilterOption("(8) Very Good", "8"),
|
|
||||||
SelectFilterOption("(9) Great", "9"),
|
|
||||||
SelectFilterOption("(10) Masterpiece", "10"),
|
|
||||||
]),
|
|
||||||
SelectFilter("SeasonFilter", "Season", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
SelectFilterOption("Spring", "1"),
|
|
||||||
SelectFilterOption("Summer", "2"),
|
|
||||||
SelectFilterOption("Fall", "3"),
|
|
||||||
SelectFilterOption("Winter", "4"),
|
|
||||||
]),
|
|
||||||
SelectFilter("LanguageFilter", "Language", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
SelectFilterOption("SUB", "1"),
|
|
||||||
SelectFilterOption("DUB", "2"),
|
|
||||||
SelectFilterOption("SUB & DUB", "3"),
|
|
||||||
]),
|
|
||||||
SelectFilter("SortFilter", "Sort by", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
SelectFilterOption("Default", "default"),
|
|
||||||
SelectFilterOption("Recently Added", "recently_added"),
|
|
||||||
SelectFilterOption("Recently Updated", "recently_updated"),
|
|
||||||
SelectFilterOption("Score", "score"),
|
|
||||||
SelectFilterOption("Name A-Z", "name_az"),
|
|
||||||
SelectFilterOption("Released Date", "released_date"),
|
|
||||||
SelectFilterOption("Most Watched", "most_watched"),
|
|
||||||
]),
|
|
||||||
SelectFilter(
|
|
||||||
"StartYearFilter",
|
|
||||||
"Start year",
|
|
||||||
0,
|
|
||||||
yearList.reversed.toList(),
|
|
||||||
),
|
|
||||||
SelectFilter("StartMonthFilter", "Start month", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
for (var i = 1; i < 13; i++)
|
|
||||||
SelectFilterOption(i.toString(), i.toString()),
|
|
||||||
]),
|
|
||||||
SelectFilter("StartDayFilter", "Start day", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
for (var i = 1; i < 32; i++)
|
|
||||||
SelectFilterOption(i.toString(), i.toString()),
|
|
||||||
]),
|
|
||||||
SelectFilter("EndYearFilter", "End year", 0, yearList.reversed.toList()),
|
|
||||||
SelectFilter("EndmonthFilter", "End month", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
for (var i = 1; i < 32; i++)
|
|
||||||
SelectFilterOption(i.toString(), i.toString()),
|
|
||||||
]),
|
|
||||||
SelectFilter("EndDayFilter", "End day", 0, [
|
|
||||||
SelectFilterOption("All", ""),
|
|
||||||
for (var i = 1; i < 32; i++)
|
|
||||||
SelectFilterOption(i.toString(), i.toString()),
|
|
||||||
]),
|
|
||||||
GroupFilter("GenreFilter", "Genre", [
|
|
||||||
CheckBoxFilter("Action", "1"),
|
|
||||||
CheckBoxFilter("Adventure", "2"),
|
|
||||||
CheckBoxFilter("Cars", "3"),
|
|
||||||
CheckBoxFilter("Comedy", "4"),
|
|
||||||
CheckBoxFilter("Dementia", "5"),
|
|
||||||
CheckBoxFilter("Demons", "6"),
|
|
||||||
CheckBoxFilter("Drama", "8"),
|
|
||||||
CheckBoxFilter("Ecchi", "9"),
|
|
||||||
CheckBoxFilter("Fantasy", "10"),
|
|
||||||
CheckBoxFilter("Game", "11"),
|
|
||||||
CheckBoxFilter("Harem", "35"),
|
|
||||||
CheckBoxFilter("Historical", "13"),
|
|
||||||
CheckBoxFilter("Horror", "14"),
|
|
||||||
CheckBoxFilter("Isekai", "44"),
|
|
||||||
CheckBoxFilter("Josei", "43"),
|
|
||||||
CheckBoxFilter("Kids", "15"),
|
|
||||||
CheckBoxFilter("Magic", "16"),
|
|
||||||
CheckBoxFilter("Martial Arts", "17"),
|
|
||||||
CheckBoxFilter("Mecha", "18"),
|
|
||||||
CheckBoxFilter("Military", "38"),
|
|
||||||
CheckBoxFilter("Music", "19"),
|
|
||||||
CheckBoxFilter("Mystery", "7"),
|
|
||||||
CheckBoxFilter("Parody", "20"),
|
|
||||||
CheckBoxFilter("Police", "39"),
|
|
||||||
CheckBoxFilter("Psychological", "40"),
|
|
||||||
CheckBoxFilter("Romance", "22"),
|
|
||||||
CheckBoxFilter("Samurai", "21"),
|
|
||||||
CheckBoxFilter("School", "23"),
|
|
||||||
CheckBoxFilter("Sci-Fi", "24"),
|
|
||||||
CheckBoxFilter("Seinen", "42"),
|
|
||||||
CheckBoxFilter("Shoujo", "25"),
|
|
||||||
CheckBoxFilter("Shoujo Ai", "26"),
|
|
||||||
CheckBoxFilter("Shounen", "27"),
|
|
||||||
CheckBoxFilter("Shounen Ai", "28"),
|
|
||||||
CheckBoxFilter("Slice of Life", "36"),
|
|
||||||
CheckBoxFilter("Space", "29"),
|
|
||||||
CheckBoxFilter("Sports", "30"),
|
|
||||||
CheckBoxFilter("Super Power", "31"),
|
|
||||||
CheckBoxFilter("Supernatural", "37"),
|
|
||||||
CheckBoxFilter("Thriller", "41"),
|
|
||||||
CheckBoxFilter("Vampire", "32"),
|
|
||||||
CheckBoxFilter("Yaoi", "33"),
|
|
||||||
CheckBoxFilter("Yuri", "34"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Preferred Quality",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 1,
|
|
||||||
entries: ["1080p", "720p", "480p", "360p"],
|
|
||||||
entryValues: ["1080", "720", "480", "360"],
|
|
||||||
),
|
|
||||||
if (source.name == "HiAnime")
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_server2",
|
|
||||||
title: "Preferred server",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["HD-1", "HD-2", "HD-3", "StreamTape"],
|
|
||||||
entryValues: ["HD-1", "HD-2", "HD-3", "StreamTape"],
|
|
||||||
),
|
|
||||||
if (source.name != "HiAnime")
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_server2",
|
|
||||||
title: "Preferred server",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Vidstreaming", "VidCloud", "StreamTape"],
|
|
||||||
entryValues: ["Vidstreaming", "VidCloud", "StreamTape"],
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_type1",
|
|
||||||
title: "Preferred Type",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Sub", "Dub"],
|
|
||||||
entryValues: ["sub", "dub"],
|
|
||||||
),
|
|
||||||
if (source.name != "HiAnime")
|
|
||||||
MultiSelectListPreference(
|
|
||||||
key: "hoster_selection2",
|
|
||||||
title: "Enable/Disable Hosts",
|
|
||||||
summary: "",
|
|
||||||
entries: ["Vidstreaming", "VidCloud", "StreamTape"],
|
|
||||||
entryValues: ["Vidstreaming", "VidCloud", "StreamTape"],
|
|
||||||
values: ["Vidstreaming", "VidCloud", "StreamTape"],
|
|
||||||
),
|
|
||||||
if (source.name == "HiAnime")
|
|
||||||
MultiSelectListPreference(
|
|
||||||
key: "hoster_selection2",
|
|
||||||
title: "Enable/Disable Hosts",
|
|
||||||
summary: "",
|
|
||||||
entries: ["HD-1", "HD-2", "HD-3", "StreamTape"],
|
|
||||||
entryValues: ["HD-1", "HD-2", "HD-3", "StreamTape"],
|
|
||||||
values: ["HD-1", "HD-2", "HD-3", "StreamTape"],
|
|
||||||
),
|
|
||||||
MultiSelectListPreference(
|
|
||||||
key: "type_selection_1",
|
|
||||||
title: "Enable/Disable Types",
|
|
||||||
summary: "",
|
|
||||||
entries: ["Sub", "Dub", "Raw"],
|
|
||||||
entryValues: ["sub", "dub", "raw"],
|
|
||||||
values: ["sub", "dub", "raw"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
|
||||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
|
||||||
String server = getPreferenceValue(sourceId, "preferred_server2");
|
|
||||||
String type = getPreferenceValue(sourceId, "preferred_type1");
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
|
|
||||||
if (a.quality.contains(quality) &&
|
|
||||||
a.quality.toLowerCase().contains(type.toLowerCase()) &&
|
|
||||||
a.quality.toLowerCase().contains(server.toLowerCase())) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality) &&
|
|
||||||
b.quality.toLowerCase().contains(type.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> preferenceHosterSelection(int sourceId) {
|
|
||||||
return getPreferenceValue(sourceId, "hoster_selection2");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> preferenceTypeSelection(int sourceId) {
|
|
||||||
return getPreferenceValue(sourceId, "type_selection_1");
|
|
||||||
}
|
|
||||||
|
|
||||||
String ll(String url) {
|
|
||||||
if (url.contains("?")) {
|
|
||||||
return "&";
|
|
||||||
}
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ZoroTheme main(MSource source) {
|
|
||||||
return ZoroTheme(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,430 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class AnimeWorldIndia extends MProvider {
|
|
||||||
AnimeWorldIndia({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${source.baseUrl}/advanced-search/page/$page/?s_lang=${source.lang}&s_orderby=viewed",
|
|
||||||
),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${source.baseUrl}/advanced-search/page/$page/?s_lang=${source.lang}&s_orderby=update",
|
|
||||||
),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String url =
|
|
||||||
"${source.baseUrl}/advanced-search/page/$page/?s_keyword=$query&s_lang=${source.lang}";
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "TypeFilter") {
|
|
||||||
final type = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}s_type=$type";
|
|
||||||
} else if (filter.type == "StatusFilter") {
|
|
||||||
final status = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}s_status=$status";
|
|
||||||
} else if (filter.type == "StyleFilter") {
|
|
||||||
final style = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}s_sub_type=$style";
|
|
||||||
} else if (filter.type == "YearFilter") {
|
|
||||||
final year = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}s_year=$year";
|
|
||||||
} else if (filter.type == "SortFilter") {
|
|
||||||
final sort = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}s_orderby=$sort";
|
|
||||||
} else if (filter.type == "GenresFilter") {
|
|
||||||
final genre = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
url += "${ll(url)}s_genre=";
|
|
||||||
if (genre.isNotEmpty) {
|
|
||||||
for (var st in genre) {
|
|
||||||
String value = st.value;
|
|
||||||
url += value.toLowerCase().replaceAll(" ", "-");
|
|
||||||
if (genre.length > 1) {
|
|
||||||
url += "%2C";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (genre.length > 1) {
|
|
||||||
url = substringBeforeLast(url, '%2C');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final isMovie =
|
|
||||||
document.xpath('//li/a[contains(text(),"Movie")]/text()').isNotEmpty;
|
|
||||||
if (isMovie) {
|
|
||||||
anime.status = MStatus.completed;
|
|
||||||
} else {
|
|
||||||
final eps = xpath(
|
|
||||||
res,
|
|
||||||
'//ul/li/a[contains(@href,"${source.baseUrl}/watch")]/text()',
|
|
||||||
);
|
|
||||||
if (eps.isNotEmpty) {
|
|
||||||
final epParts = eps.first
|
|
||||||
.substring(3)
|
|
||||||
.replaceAll(" ", "")
|
|
||||||
.replaceAll("\n", "")
|
|
||||||
.split('/');
|
|
||||||
if (epParts.length == 2) {
|
|
||||||
if (epParts[0].compareTo(epParts[1]) == 0) {
|
|
||||||
anime.status = MStatus.completed;
|
|
||||||
} else {
|
|
||||||
anime.status = MStatus.ongoing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anime.description = document.selectFirst("div[data-synopsis]")?.text ?? "";
|
|
||||||
anime.author = document
|
|
||||||
.xpath('//li[contains(text(),"Producers:")]/span/a/text()')
|
|
||||||
.join(', ');
|
|
||||||
anime.genre = document.xpath(
|
|
||||||
'//span[@class="leading-6"]/a[contains(@class,"border-opacity-30")]/text()',
|
|
||||||
);
|
|
||||||
final seasonsJson =
|
|
||||||
json.decode(
|
|
||||||
substringBeforeLast(
|
|
||||||
substringBefore(
|
|
||||||
substringAfter(res, "var season_list = "),
|
|
||||||
"var season_label =",
|
|
||||||
),
|
|
||||||
";",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
as List<Map<String, dynamic>>;
|
|
||||||
bool isSingleSeason = seasonsJson.length == 1;
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
for (var i = 0; i < seasonsJson.length; i++) {
|
|
||||||
final seasonJson = seasonsJson[i];
|
|
||||||
final seasonName = isSingleSeason ? "" : "Season ${i + 1}";
|
|
||||||
final episodesJson =
|
|
||||||
(seasonJson["episodes"]["all"] as List<Map<String, dynamic>>).reversed
|
|
||||||
.toList();
|
|
||||||
for (var j = 0; j < episodesJson.length; j++) {
|
|
||||||
final episodeJson = episodesJson[j];
|
|
||||||
final episodeTitle = episodeJson["metadata"]["title"] ?? "";
|
|
||||||
String episodeName = "";
|
|
||||||
if (isMovie) {
|
|
||||||
episodeName = "Movie";
|
|
||||||
} else {
|
|
||||||
if (seasonName.isNotEmpty) {
|
|
||||||
episodeName = "$seasonName - ";
|
|
||||||
}
|
|
||||||
episodeName += "Episode ${j + 1} ";
|
|
||||||
if (episodeTitle.isNotEmpty) {
|
|
||||||
episodeName += "- $episodeTitle";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = episodeName;
|
|
||||||
|
|
||||||
episode.dateUpload =
|
|
||||||
"${int.parse(episodeJson["metadata"]["released"] ?? "0") * 1000}";
|
|
||||||
episode.url = "/wp-json/kiranime/v1/episode?id=${episodeJson["id"]}";
|
|
||||||
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;
|
|
||||||
var resJson = substringBefore(
|
|
||||||
substringAfterLast(res, "\"players\":"),
|
|
||||||
",\"noplayer\":",
|
|
||||||
);
|
|
||||||
var streams =
|
|
||||||
(json.decode(resJson) as List<Map<String, dynamic>>)
|
|
||||||
.where(
|
|
||||||
(e) =>
|
|
||||||
(e["type"] == "stream" ? true : false) &&
|
|
||||||
(e["url"] as String).isNotEmpty,
|
|
||||||
)
|
|
||||||
.toList()
|
|
||||||
.where(
|
|
||||||
(e) =>
|
|
||||||
language(source.lang).isEmpty ||
|
|
||||||
language(source.lang) == e["language"]
|
|
||||||
? true
|
|
||||||
: false,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (var stream in streams) {
|
|
||||||
String videoUrl = stream["url"];
|
|
||||||
final language = stream["language"];
|
|
||||||
final video = await mystreamExtractor(videoUrl, language);
|
|
||||||
videos.addAll(video);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortVideos(videos, source.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages parseAnimeList(String res) {
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final document = parseHtml(res);
|
|
||||||
|
|
||||||
for (var element in document.select("div.col-span-1")) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name =
|
|
||||||
element.selectFirst("div.font-medium.line-clamp-2.mb-3").text;
|
|
||||||
anime.link = element.selectFirst("a").getHref;
|
|
||||||
anime.imageUrl =
|
|
||||||
"${source.baseUrl}${getUrlWithoutDomain(element.selectFirst("img").getSrc)}";
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
final hasNextPage =
|
|
||||||
xpath(
|
|
||||||
res,
|
|
||||||
'//li/span[@class="page-numbers current"]/parent::li//following-sibling::li/a/@href',
|
|
||||||
).isNotEmpty;
|
|
||||||
return MPages(animeList, hasNextPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
String language(String lang) {
|
|
||||||
final languages = {
|
|
||||||
"all": "",
|
|
||||||
"bn": "bengali",
|
|
||||||
"en": "english",
|
|
||||||
"hi": "hindi",
|
|
||||||
"ja": "japanese",
|
|
||||||
"ml": "malayalam",
|
|
||||||
"mr": "marathi",
|
|
||||||
"ta": "tamil",
|
|
||||||
"te": "telugu",
|
|
||||||
};
|
|
||||||
return languages[lang] ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> mystreamExtractor(String url, String language) async {
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final streamCode = substringBefore(
|
|
||||||
substringAfter(substringAfter(res, "sniff("), ", \""),
|
|
||||||
'"',
|
|
||||||
);
|
|
||||||
|
|
||||||
final streamUrl =
|
|
||||||
"${substringBefore(url, "/watch")}/m3u8/$streamCode/master.txt?s=1&cache=1";
|
|
||||||
final masterPlaylistRes = (await client.get(Uri.parse(streamUrl))).body;
|
|
||||||
|
|
||||||
List<MTrack> audios = [];
|
|
||||||
for (var it in substringAfter(
|
|
||||||
masterPlaylistRes,
|
|
||||||
"#EXT-X-MEDIA:TYPE=AUDIO",
|
|
||||||
).split("#EXT-X-MEDIA:TYPE=AUDIO")) {
|
|
||||||
final line = substringBefore(
|
|
||||||
substringAfter(it, "#EXT-X-MEDIA:TYPE=AUDIO"),
|
|
||||||
"\n",
|
|
||||||
);
|
|
||||||
final audioUrl = substringBefore(substringAfter(line, "URI=\""), "\"");
|
|
||||||
MTrack audio = MTrack();
|
|
||||||
audio
|
|
||||||
..label = substringBefore(substringAfter(line, "NAME=\""), "\"")
|
|
||||||
..file = audioUrl;
|
|
||||||
audios.add(audio);
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "[$language] MyStream - $quality"
|
|
||||||
..audios = audios;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
SelectFilter("TypeFilter", "Type", 0, [
|
|
||||||
SelectFilterOption("Any", "all"),
|
|
||||||
SelectFilterOption("TV", "tv"),
|
|
||||||
SelectFilterOption("Movie", "movies"),
|
|
||||||
]),
|
|
||||||
SelectFilter("StatusFilter", "Status", 0, [
|
|
||||||
SelectFilterOption("Any", "all"),
|
|
||||||
SelectFilterOption("Currently Airing", "airing"),
|
|
||||||
SelectFilterOption("Finished Airing", "completed"),
|
|
||||||
]),
|
|
||||||
SelectFilter("StyleFilter", "Style", 0, [
|
|
||||||
SelectFilterOption("Any", "all"),
|
|
||||||
SelectFilterOption("Anime", "anime"),
|
|
||||||
SelectFilterOption("Cartoon", "cartoon"),
|
|
||||||
]),
|
|
||||||
SelectFilter("YearFilter", "Year", 0, [
|
|
||||||
SelectFilterOption("Any", "all"),
|
|
||||||
SelectFilterOption("2024", "2024"),
|
|
||||||
SelectFilterOption("2023", "2023"),
|
|
||||||
SelectFilterOption("2022", "2022"),
|
|
||||||
SelectFilterOption("2021", "2021"),
|
|
||||||
SelectFilterOption("2020", "2020"),
|
|
||||||
SelectFilterOption("2019", "2019"),
|
|
||||||
SelectFilterOption("2018", "2018"),
|
|
||||||
SelectFilterOption("2017", "2017"),
|
|
||||||
SelectFilterOption("2016", "2016"),
|
|
||||||
SelectFilterOption("2015", "2015"),
|
|
||||||
SelectFilterOption("2014", "2014"),
|
|
||||||
SelectFilterOption("2013", "2013"),
|
|
||||||
SelectFilterOption("2012", "2012"),
|
|
||||||
SelectFilterOption("2011", "2011"),
|
|
||||||
SelectFilterOption("2010", "2010"),
|
|
||||||
SelectFilterOption("2009", "2009"),
|
|
||||||
SelectFilterOption("2008", "2008"),
|
|
||||||
SelectFilterOption("2007", "2007"),
|
|
||||||
SelectFilterOption("2006", "2006"),
|
|
||||||
SelectFilterOption("2005", "2005"),
|
|
||||||
SelectFilterOption("2004", "2004"),
|
|
||||||
SelectFilterOption("2003", "2003"),
|
|
||||||
SelectFilterOption("2002", "2002"),
|
|
||||||
SelectFilterOption("2001", "2001"),
|
|
||||||
SelectFilterOption("2000", "2000"),
|
|
||||||
SelectFilterOption("1999", "1999"),
|
|
||||||
SelectFilterOption("1998", "1998"),
|
|
||||||
SelectFilterOption("1997", "1997"),
|
|
||||||
SelectFilterOption("1996", "1996"),
|
|
||||||
SelectFilterOption("1995", "1995"),
|
|
||||||
SelectFilterOption("1994", "1994"),
|
|
||||||
SelectFilterOption("1993", "1993"),
|
|
||||||
SelectFilterOption("1992", "1992"),
|
|
||||||
SelectFilterOption("1991", "1991"),
|
|
||||||
SelectFilterOption("1990", "1990"),
|
|
||||||
]),
|
|
||||||
SelectFilter("SortFilter", "Sort", 0, [
|
|
||||||
SelectFilterOption("Default", "default"),
|
|
||||||
SelectFilterOption("Ascending", "title_a_z"),
|
|
||||||
SelectFilterOption("Descending", "title_z_a"),
|
|
||||||
SelectFilterOption("Updated", "update"),
|
|
||||||
SelectFilterOption("Published", "date"),
|
|
||||||
SelectFilterOption("Most Viewed", "viewed"),
|
|
||||||
SelectFilterOption("Favourite", "favorite"),
|
|
||||||
]),
|
|
||||||
GroupFilter("GenresFilter", "Genres", [
|
|
||||||
CheckBoxFilter("Action", "Action"),
|
|
||||||
CheckBoxFilter("Adult Cast", "Adult Cast"),
|
|
||||||
CheckBoxFilter("Adventure", "Adventure"),
|
|
||||||
CheckBoxFilter("Animation", "Animation"),
|
|
||||||
CheckBoxFilter("Comedy", "Comedy"),
|
|
||||||
CheckBoxFilter("Detective", "Detective"),
|
|
||||||
CheckBoxFilter("Drama", "Drama"),
|
|
||||||
CheckBoxFilter("Ecchi", "Ecchi"),
|
|
||||||
CheckBoxFilter("Family", "Family"),
|
|
||||||
CheckBoxFilter("Fantasy", "Fantasy"),
|
|
||||||
CheckBoxFilter("Isekai", "Isekai"),
|
|
||||||
CheckBoxFilter("Kids", "Kids"),
|
|
||||||
CheckBoxFilter("Martial Arts", "Martial Arts"),
|
|
||||||
CheckBoxFilter("Mecha", "Mecha"),
|
|
||||||
CheckBoxFilter("Military", "Military"),
|
|
||||||
CheckBoxFilter("Mystery", "Mystery"),
|
|
||||||
CheckBoxFilter("Otaku Culture", "Otaku Culture"),
|
|
||||||
CheckBoxFilter("Reality", "Reality"),
|
|
||||||
CheckBoxFilter("Romance", "Romance"),
|
|
||||||
CheckBoxFilter("School", "School"),
|
|
||||||
CheckBoxFilter("Sci-Fi", "Sci-Fi"),
|
|
||||||
CheckBoxFilter("Seinen", "Seinen"),
|
|
||||||
CheckBoxFilter("Shounen", "Shounen"),
|
|
||||||
CheckBoxFilter("Slice of Life", "Slice of Life"),
|
|
||||||
CheckBoxFilter("Sports", "Sports"),
|
|
||||||
CheckBoxFilter("Super Power", "Super Power"),
|
|
||||||
CheckBoxFilter("SuperHero", "SuperHero"),
|
|
||||||
CheckBoxFilter("Supernatural", "Supernatural"),
|
|
||||||
CheckBoxFilter("TV Movie", "TV Movie"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Preferred Quality",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["1080p", "720p", "480p", "360p", "240p"],
|
|
||||||
entryValues: ["1080", "720", "480", "360", "240"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
|
||||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
|
|
||||||
if (a.quality.contains(quality)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
String ll(String url) {
|
|
||||||
if (url.contains("?")) {
|
|
||||||
return "&";
|
|
||||||
}
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimeWorldIndia main(MSource source) {
|
|
||||||
return AnimeWorldIndia(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 7.4 KiB |
@@ -1,37 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
const _animeworldindiaVersion = "0.0.35";
|
|
||||||
const _animeworldindiaSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/all/animeworldindia/animeworldindia.dart";
|
|
||||||
|
|
||||||
String _iconUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/all/animeworldindia/icon.png";
|
|
||||||
|
|
||||||
List<String> _languages = [
|
|
||||||
"all",
|
|
||||||
"en",
|
|
||||||
"bn",
|
|
||||||
"hi",
|
|
||||||
"ja",
|
|
||||||
"ml",
|
|
||||||
"mr",
|
|
||||||
"ta",
|
|
||||||
"te",
|
|
||||||
];
|
|
||||||
|
|
||||||
List<Source> get animeworldindiaSourcesList => _animeworldindiaSourcesList;
|
|
||||||
List<Source> _animeworldindiaSourcesList =
|
|
||||||
_languages
|
|
||||||
.map(
|
|
||||||
(e) => Source(
|
|
||||||
name: 'AnimeWorld India',
|
|
||||||
baseUrl: "https://anime-world.in",
|
|
||||||
lang: e,
|
|
||||||
typeSource: "multiple",
|
|
||||||
iconUrl: _iconUrl,
|
|
||||||
version: _animeworldindiaVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
sourceCodeUrl: _animeworldindiaSourceCodeUrl,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,220 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
|
|
||||||
class Nyaa extends MProvider {
|
|
||||||
Nyaa({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res = (await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${getBaseUrl()}/?f=0&c=${getPreferenceValue(source.id, "preferred_categorie_page")}&q=&s=downloads&o=desc&p=$page",
|
|
||||||
),
|
|
||||||
)).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res = (await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${getBaseUrl()}/?f=0&c=${getPreferenceValue(source.id, "preferred_categorie_page")}&q=$page",
|
|
||||||
),
|
|
||||||
)).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String url = "";
|
|
||||||
url =
|
|
||||||
"${getBaseUrl()}/?f=0&c=${getPreferenceValue(source.id, "preferred_categorie_page")}&q=${query.replaceAll(" ", "+")}&p=$page";
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "SortFilter") {
|
|
||||||
url += "${ll(url)}s=${filter.values[filter.state.index].value}";
|
|
||||||
final asc = filter.state.ascending ? "&o=asc" : "&o=desc";
|
|
||||||
url += "${ll(url)}$asc";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
String extractPanelBody(MDocument document) {
|
|
||||||
final panelBody = document.selectFirst('.panel-body');
|
|
||||||
if (panelBody == null) return "";
|
|
||||||
|
|
||||||
final rows = panelBody.select('.row');
|
|
||||||
|
|
||||||
final Map<String, String> info = {};
|
|
||||||
for (var row in rows) {
|
|
||||||
final labels = row.select('.col-md-1');
|
|
||||||
for (var label in labels) {
|
|
||||||
final key = label.text.replaceAll(":", "").trim();
|
|
||||||
final valueDiv = label.nextElementSibling;
|
|
||||||
if (valueDiv == null) continue;
|
|
||||||
|
|
||||||
final links = valueDiv.select('a');
|
|
||||||
String value;
|
|
||||||
if (links.isNotEmpty) {
|
|
||||||
value = links.map((a) => a.text.trim()).join(' - ');
|
|
||||||
} else {
|
|
||||||
value = valueDiv.text.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
info[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final buffer = StringBuffer();
|
|
||||||
buffer.writeln("Torrent Info:\n");
|
|
||||||
info.forEach((k, v) {
|
|
||||||
buffer.writeln("${k.padRight(11)}: $v");
|
|
||||||
});
|
|
||||||
if (getPreferenceValue(source.id, "torrent_description_visible")) {
|
|
||||||
buffer.writeln("\n\n");
|
|
||||||
buffer.writeln("Torrent Description: \n");
|
|
||||||
buffer.writeln(
|
|
||||||
document
|
|
||||||
.select("#torrent-description")
|
|
||||||
.map((e) => e.text.trim())
|
|
||||||
.join("\n\n"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
MManga anime = MManga();
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
|
|
||||||
anime.description = extractPanelBody(document);
|
|
||||||
|
|
||||||
List<MChapter> chapters = [];
|
|
||||||
chapters.add(
|
|
||||||
MChapter(
|
|
||||||
name: "Torrent",
|
|
||||||
url: "${getBaseUrl()}/download/${substringAfterLast(url, '/')}.torrent",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
anime.chapters = chapters;
|
|
||||||
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
var video = MVideo();
|
|
||||||
video
|
|
||||||
..url = url
|
|
||||||
..originalUrl = url
|
|
||||||
..quality = "";
|
|
||||||
return [video];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
SortFilter("SortFilter", "Sort by", SortState(0, true), [
|
|
||||||
SelectFilterOption("None", ""),
|
|
||||||
SelectFilterOption("Size", "size"),
|
|
||||||
SelectFilterOption("Date", "id"),
|
|
||||||
SelectFilterOption("Seeders", "seeders"),
|
|
||||||
SelectFilterOption("Leechers", "leechers"),
|
|
||||||
SelectFilterOption("Download", "downloads"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_categorie_page",
|
|
||||||
title: "Preferred categorie page",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Anime", "Live Action"],
|
|
||||||
entryValues: ["1_0", "4_0"],
|
|
||||||
),
|
|
||||||
SwitchPreferenceCompat(
|
|
||||||
key: "torrent_description_visible",
|
|
||||||
title: "Display Torrent Description",
|
|
||||||
summary:
|
|
||||||
"Enable to show the full torrent description in the details view.",
|
|
||||||
value: false,
|
|
||||||
),
|
|
||||||
EditTextPreference(
|
|
||||||
key: "domain_url",
|
|
||||||
title: 'Edit URL',
|
|
||||||
summary: "",
|
|
||||||
value: source.baseUrl,
|
|
||||||
dialogTitle: "URL",
|
|
||||||
dialogMessage: "",
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
String getBaseUrl() {
|
|
||||||
final baseUrl = getPreferenceValue(source.id, "domain_url")?.trim();
|
|
||||||
|
|
||||||
if (baseUrl == null || baseUrl.isEmpty) {
|
|
||||||
return source.baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseUrl.endsWith("/")
|
|
||||||
? baseUrl.substring(0, baseUrl.length - 1)
|
|
||||||
: baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages parseAnimeList(String res) {
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final document = parseHtml(res);
|
|
||||||
|
|
||||||
final values = document.select(
|
|
||||||
"body > div > div.table-responsive > table > tbody > tr",
|
|
||||||
);
|
|
||||||
for (var value in values) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.imageUrl =
|
|
||||||
"${getBaseUrl()}${getUrlWithoutDomain(value.selectFirst("td:nth-child(1) > a > img").getSrc)}";
|
|
||||||
MElement firstElement = value
|
|
||||||
.select("td > a")
|
|
||||||
.where(
|
|
||||||
(MElement e) =>
|
|
||||||
e.outerHtml.contains("/view/") &&
|
|
||||||
!e.outerHtml.contains("#comments"),
|
|
||||||
)
|
|
||||||
.toList()
|
|
||||||
.first;
|
|
||||||
anime.link =
|
|
||||||
"${getBaseUrl()}${getUrlWithoutDomain(firstElement.getHref)}";
|
|
||||||
anime.name = firstElement.attr("title");
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
|
|
||||||
final hasNextPage = xpath(
|
|
||||||
res,
|
|
||||||
'//ul[@class="pagination"]/li[contains(text(),"»")]/a/@href',
|
|
||||||
).isNotEmpty;
|
|
||||||
return MPages(animeList, hasNextPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
String ll(String url) {
|
|
||||||
if (url.contains("?")) {
|
|
||||||
return "&";
|
|
||||||
}
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Nyaa main(MSource source) {
|
|
||||||
return Nyaa(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
const _nyaaVersion = "0.0.4";
|
|
||||||
const _nyaaSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/all/nyaa/nyaa.dart";
|
|
||||||
|
|
||||||
String _iconUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/all/nyaa/icon.png";
|
|
||||||
|
|
||||||
Source get nyaaSource => _nyaaSource;
|
|
||||||
Source _nyaaSource = Source(
|
|
||||||
name: 'Nyaa',
|
|
||||||
baseUrl: "https://nyaa.si",
|
|
||||||
lang: "all",
|
|
||||||
typeSource: "torrent",
|
|
||||||
iconUrl: _iconUrl,
|
|
||||||
version: _nyaaVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
sourceCodeUrl: _nyaaSourceCodeUrl,
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 5.2 KiB |
@@ -1,234 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class OkAnime extends MProvider {
|
|
||||||
OkAnime({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse(source.baseUrl))).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
String path =
|
|
||||||
'//div[@class="section" and contains(text(),"افضل انميات")]/div[@class="section-content"]/div/div/div[contains(@class,"anime-card")]';
|
|
||||||
final urls = xpath(res, '$path/div[@class="anime-title")]/h4/a/@href');
|
|
||||||
final names = xpath(res, '$path/div[@class="anime-title")]/h4/a/text()');
|
|
||||||
final images = xpath(res, '$path/div[@class="anime-image")]/img/@src');
|
|
||||||
|
|
||||||
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, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/espisode-list?page=$page"),
|
|
||||||
)).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
String path = '//*[contains(@class,"anime-card")]';
|
|
||||||
final urls = xpath(res, '$path/div[@class="anime-title")]/h4/a/@href');
|
|
||||||
final names = xpath(res, '$path/div[@class="anime-title")]/h4/a/text()');
|
|
||||||
final images = xpath(res, '$path/div[@class="episode-image")]/img/@src');
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final nextPage = xpath(
|
|
||||||
res,
|
|
||||||
'//li[@class="page-item"]/a[@rel="next"]/@href',
|
|
||||||
);
|
|
||||||
return MPages(animeList, nextPage.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
String url = "${source.baseUrl}/search/?s=$query";
|
|
||||||
if (page > 1) {
|
|
||||||
url += "&page=$page";
|
|
||||||
}
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
String path = '//*[contains(@class,"anime-card")]';
|
|
||||||
final urls = xpath(res, '$path/div[@class="anime-title")]/h4/a/@href');
|
|
||||||
final names = xpath(res, '$path/div[@class="anime-title")]/h4/a/text()');
|
|
||||||
final images = xpath(res, '$path/div[@class="anime-image")]/img/@src');
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final nextPage = xpath(
|
|
||||||
res,
|
|
||||||
'//li[@class="page-item"]/a[@rel="next"]/@href',
|
|
||||||
);
|
|
||||||
return MPages(animeList, nextPage.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"يعرض الان": 0, "مكتمل": 1},
|
|
||||||
];
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final status = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="full-list-info" and contains(text(),"حالة الأنمي")]/small/a/text()',
|
|
||||||
);
|
|
||||||
if (status.isNotEmpty) {
|
|
||||||
anime.status = parseStatus(status.first, statusList);
|
|
||||||
}
|
|
||||||
anime.description = xpath(res, '//*[@class="review-content"]/text()').first;
|
|
||||||
|
|
||||||
anime.genre = xpath(res, '//*[@class="review-author-info"]/a/text()');
|
|
||||||
final epUrls =
|
|
||||||
xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@class,"anime-card")]/div[@class="anime-title")]/h5/a/@href',
|
|
||||||
).reversed.toList();
|
|
||||||
final names =
|
|
||||||
xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@class,"anime-card")]/div[@class="anime-title")]/h5/a/text()',
|
|
||||||
).reversed.toList();
|
|
||||||
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
for (var i = 0; i < epUrls.length; i++) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = names[i];
|
|
||||||
episode.url = epUrls[i];
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.chapters = episodesList;
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final urls = xpath(res, '//*[@id="streamlinks"]/a/@data-src');
|
|
||||||
final qualities = xpath(res, '//*[@id="streamlinks"]/a/span/text()');
|
|
||||||
final hosterSelection = preferenceHosterSelection(source.id);
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (var i = 0; i < urls.length; i++) {
|
|
||||||
final url = urls[i];
|
|
||||||
final quality = getQuality(qualities[i]);
|
|
||||||
List<MVideo> a = [];
|
|
||||||
|
|
||||||
if (url.contains("https://doo") && hosterSelection.contains("Dood")) {
|
|
||||||
a = await doodExtractor(url, "DoodStream - $quality");
|
|
||||||
} else if (url.contains("mp4upload") &&
|
|
||||||
hosterSelection.contains("Mp4upload")) {
|
|
||||||
a = await mp4UploadExtractor(url, null, "", "");
|
|
||||||
} else if (url.contains("ok.ru") && hosterSelection.contains("Okru")) {
|
|
||||||
a = await okruExtractor(url);
|
|
||||||
} else if (url.contains("voe.sx") && hosterSelection.contains("Voe")) {
|
|
||||||
a = await voeExtractor(url, "VoeSX $quality");
|
|
||||||
} else if (containsVidBom(url) && hosterSelection.contains("VidBom")) {
|
|
||||||
a = await vidBomExtractor(url);
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
return sortVideos(videos, source.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Preferred Quality",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 1,
|
|
||||||
entries: ["1080p", "720p", "480p", "360p"],
|
|
||||||
entryValues: ["1080", "720", "480", "360"],
|
|
||||||
),
|
|
||||||
MultiSelectListPreference(
|
|
||||||
key: "hoster_selection",
|
|
||||||
title: "Enable/Disable Hosts",
|
|
||||||
summary: "",
|
|
||||||
entries: ["Dood", "Voe", "Mp4upload", "VidBom", "Okru"],
|
|
||||||
entryValues: ["Dood", "Voe", "Mp4upload", "VidBom", "Okru"],
|
|
||||||
values: ["Dood", "Voe", "Mp4upload", "VidBom", "Okru"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
|
||||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
|
||||||
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
if (a.quality.contains(quality)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> preferenceHosterSelection(int sourceId) {
|
|
||||||
return getPreferenceValue(sourceId, "hoster_selection");
|
|
||||||
}
|
|
||||||
|
|
||||||
String getQuality(String quality) {
|
|
||||||
quality = quality.replaceAll(" ", "");
|
|
||||||
if (quality == "HD") {
|
|
||||||
return "720p";
|
|
||||||
} else if (quality == "FHD") {
|
|
||||||
return "1080p";
|
|
||||||
} else if (quality == "SD") {
|
|
||||||
return "480p";
|
|
||||||
}
|
|
||||||
return "240p";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool containsVidBom(String url) {
|
|
||||||
url = url;
|
|
||||||
final list = ["vidbam", "vadbam", "vidbom", "vidbm"];
|
|
||||||
for (var n in list) {
|
|
||||||
if (url.contains(n)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OkAnime main(MSource source) {
|
|
||||||
return OkAnime(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get okanimeSource => _okanimeSource;
|
|
||||||
const _okanimeVersion = "0.0.6";
|
|
||||||
const _okanimeSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/ar/okanime/okanime.dart";
|
|
||||||
Source _okanimeSource = Source(
|
|
||||||
name: "Okanime",
|
|
||||||
baseUrl: "https://www.okanime.xyz",
|
|
||||||
lang: "ar",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/ar/okanime/icon.png",
|
|
||||||
sourceCodeUrl: _okanimeSourceCodeUrl,
|
|
||||||
version: _okanimeVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
|
|
||||||
class AnimeToast extends MProvider {
|
|
||||||
AnimeToast({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get supportsLatest => false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => source.baseUrl;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse(baseUrl))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final elements = document.select("div.row div.col-md-4 div.video-item");
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var element in elements) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = element.selectFirst("div.item-thumbnail a").attr("title");
|
|
||||||
anime.link = getUrlWithoutDomain(
|
|
||||||
element.selectFirst("div.item-thumbnail a").attr("href"),
|
|
||||||
);
|
|
||||||
anime.imageUrl = element
|
|
||||||
.selectFirst("div.item-thumbnail a img")
|
|
||||||
.attr("src");
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(Uri.parse("$baseUrl/page/$page/?s=$query"))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final elements = document.select("div.item-thumbnail a[href]");
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var element in elements) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = element.attr("title");
|
|
||||||
anime.link = getUrlWithoutDomain(element.attr("href"));
|
|
||||||
anime.imageUrl = element.selectFirst("a img").attr("src");
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(
|
|
||||||
animeList,
|
|
||||||
document.selectFirst("li.next a")?.attr("href") != null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
MManga anime = MManga();
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
anime.imageUrl = document.selectFirst(".item-content p img").attr("src");
|
|
||||||
anime.genre =
|
|
||||||
(document.xpathFirst('//p[contains(text(),"Genre:")]/text()') ?? "")
|
|
||||||
.replaceAll("Genre:", "")
|
|
||||||
.split(",");
|
|
||||||
anime.description = document.selectFirst("div.item-content div + p").text;
|
|
||||||
final categoryTag = document.xpath('//*[@rel="category tag"]/text()');
|
|
||||||
if (categoryTag.isNotEmpty) {
|
|
||||||
if (categoryTag.contains("Airing")) {
|
|
||||||
anime.status = MStatus.ongoing;
|
|
||||||
} else {
|
|
||||||
anime.status = MStatus.completed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
if (categoryTag.contains("Serie")) {
|
|
||||||
List<MElement> elements = [];
|
|
||||||
if (document.selectFirst("#multi_link_tab0")?.attr("id") != null) {
|
|
||||||
elements = document.select("#multi_link_tab0");
|
|
||||||
} else {
|
|
||||||
elements = document.select("#multi_link_tab1");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var element in elements) {
|
|
||||||
final episodeElement = element.selectFirst("a");
|
|
||||||
final epT = episodeElement.text;
|
|
||||||
if (epT.contains(":") || epT.contains("-")) {
|
|
||||||
final url = episodeElement.attr("href");
|
|
||||||
final document = parseHtml((await client.get(Uri.parse(url))).body);
|
|
||||||
final nUrl = document.selectFirst("#player-embed a").attr("href");
|
|
||||||
final nDoc = parseHtml((await client.get(Uri.parse(nUrl))).body);
|
|
||||||
final nEpEl = nDoc.select("div.tab-pane a");
|
|
||||||
for (var epElement in nEpEl) {
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = epElement.text;
|
|
||||||
ep.url = getUrlWithoutDomain(epElement.attr("href"));
|
|
||||||
episodesList.add(ep);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final episodeElements = element.select("a");
|
|
||||||
for (var epElement in episodeElements) {
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = epElement.text;
|
|
||||||
ep.url = getUrlWithoutDomain(epElement.attr("href"));
|
|
||||||
episodesList.add(ep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = document.selectFirst("h1.light-title")?.text ?? "Film";
|
|
||||||
ep.url = getUrlWithoutDomain(
|
|
||||||
document.selectFirst("link[rel=canonical]").attr("href"),
|
|
||||||
);
|
|
||||||
episodesList.add(ep);
|
|
||||||
}
|
|
||||||
anime.chapters = episodesList.reversed.toList();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> preferenceHosterSelection() {
|
|
||||||
return getPreferenceValue(source.id, "hoster_selection");
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final fEp = document.selectFirst("div.tab-pane");
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
List<MElement> ep = [];
|
|
||||||
int epcu = 100;
|
|
||||||
|
|
||||||
if (fEp.text.contains(":") || fEp.text.contains("-")) {
|
|
||||||
final tx = document.select("div.tab-pane");
|
|
||||||
|
|
||||||
for (var e in tx) {
|
|
||||||
final sUrl = e.selectFirst("a").attr("href");
|
|
||||||
final doc = parseHtml((await client.get(Uri.parse(sUrl))).body);
|
|
||||||
final nUrl = doc.selectFirst("#player-embed a").attr("href");
|
|
||||||
final nDoc = parseHtml((await client.get(Uri.parse(nUrl))).body);
|
|
||||||
epcu =
|
|
||||||
int.tryParse(
|
|
||||||
substringAfter(
|
|
||||||
document.selectFirst("div.tab-pane a.current-link")?.text ?? "",
|
|
||||||
"Ep.",
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
100;
|
|
||||||
ep = nDoc.select("div.tab-pane a");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
epcu =
|
|
||||||
int.tryParse(
|
|
||||||
substringAfter(
|
|
||||||
document.selectFirst("div.tab-pane a.current-link")?.text ?? "",
|
|
||||||
"Ep.",
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
100;
|
|
||||||
ep = document.select("div.tab-pane a");
|
|
||||||
}
|
|
||||||
final hosterSelection = preferenceHosterSelection();
|
|
||||||
for (var e in ep) {
|
|
||||||
if (int.tryParse(substringAfter(e.text, "Ep.")) == epcu) {
|
|
||||||
final epUrl = e.attr("href");
|
|
||||||
final newdoc = parseHtml((await client.get(Uri.parse(epUrl))).body);
|
|
||||||
final elements = newdoc.select("#player-embed");
|
|
||||||
for (var element in elements) {
|
|
||||||
final link = element.selectFirst("a").getHref ?? "";
|
|
||||||
if (link.contains("https://voe.sx") &&
|
|
||||||
hosterSelection.contains("voe")) {
|
|
||||||
videos.addAll(await voeExtractor(link, "Voe"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var element in elements) {
|
|
||||||
List<MVideo> a = [];
|
|
||||||
final link = element.selectFirst("iframe").getSrc ?? "";
|
|
||||||
if ((link.contains("https://dood") ||
|
|
||||||
link.contains("https://ds2play") ||
|
|
||||||
link.contains("https://d0")) &&
|
|
||||||
hosterSelection.contains("dood")) {
|
|
||||||
a = await doodExtractor(link, "DoodStream");
|
|
||||||
} else if (link.contains("filemoon") &&
|
|
||||||
hosterSelection.contains("filemoon")) {
|
|
||||||
a = await filemoonExtractor(link, "", "");
|
|
||||||
} else if (link.contains("mp4upload") &&
|
|
||||||
hosterSelection.contains("mp4upload")) {
|
|
||||||
a = await mp4UploadExtractor(url, null, "", "");
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sortVideos(videos);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos) {
|
|
||||||
String server = getPreferenceValue(source.id, "preferred_hoster");
|
|
||||||
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
if (a.quality.toLowerCase().contains(server)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.toLowerCase().contains(server)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_hoster",
|
|
||||||
title: "Standard-Hoster",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Voe", "DoodStream", "Filemoon", "Mp4upload"],
|
|
||||||
entryValues: ["voe", "doodStream", "filemoon", "mp4upload"],
|
|
||||||
),
|
|
||||||
MultiSelectListPreference(
|
|
||||||
key: "hoster_selection",
|
|
||||||
title: "Hoster auswählen",
|
|
||||||
summary: "",
|
|
||||||
entries: ["Voe", "DoodStream", "Filemoon", "Mp4upload"],
|
|
||||||
entryValues: ["voe", "dood", "filemoon", "mp4upload"],
|
|
||||||
values: ["voe", "dood", "filemoon", "mp4upload"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimeToast main(MSource source) {
|
|
||||||
return AnimeToast(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 5.0 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get animetoast => _animetoast;
|
|
||||||
const _animetoastVersion = "0.0.25";
|
|
||||||
const _animetoastCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/de/animetoast/animetoast.dart";
|
|
||||||
Source _animetoast = Source(
|
|
||||||
name: "AnimeToast",
|
|
||||||
baseUrl: "https://animetoast.cc",
|
|
||||||
lang: "de",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/de/animetoast/icon.png",
|
|
||||||
sourceCodeUrl: _animetoastCodeUrl,
|
|
||||||
version: _animetoastVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,415 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
class AnimePahe extends MProvider {
|
|
||||||
AnimePahe(this.source);
|
|
||||||
|
|
||||||
final MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => getPreferenceValue(source.id, "preferred_domain");
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> get headers => {'cookie': '__ddg1_=;__ddg2_=;'};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
return await getLatestUpdates(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res = (await client.get(
|
|
||||||
Uri.parse("$baseUrl/api?m=airing&page=$page"),
|
|
||||||
headers: headers,
|
|
||||||
)).body;
|
|
||||||
final jsonResult = json.decode(res);
|
|
||||||
final hasNextPage = jsonResult["current_page"] < jsonResult["last_page"];
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var item in jsonResult["data"]) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = item["anime_title"];
|
|
||||||
anime.imageUrl = item["snapshot"];
|
|
||||||
anime.link = "/anime/?anime_id=${item["id"]}&name=${item["anime_title"]}";
|
|
||||||
anime.artist = item["fansub"];
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, hasNextPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res = (await client.get(
|
|
||||||
Uri.parse("$baseUrl/api?m=search&l=8&q=$query"),
|
|
||||||
headers: headers,
|
|
||||||
)).body;
|
|
||||||
final jsonResult = json.decode(res);
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var item in jsonResult["data"]) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = item["title"];
|
|
||||||
anime.imageUrl = item["poster"];
|
|
||||||
anime.link = "/anime/?anime_id=${item["id"]}&name=${item["title"]}";
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"Currently Airing": 0, "Finished Airing": 1},
|
|
||||||
];
|
|
||||||
MManga anime = MManga();
|
|
||||||
final id = substringBefore(substringAfterLast(url, "?anime_id="), "&name=");
|
|
||||||
final name = substringAfterLast(url, "&name=");
|
|
||||||
final session = await getSession(name, id);
|
|
||||||
final res = (await client.get(
|
|
||||||
Uri.parse("$baseUrl/anime/$session?anime_id=$id"),
|
|
||||||
headers: headers,
|
|
||||||
)).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final status =
|
|
||||||
(document.xpathFirst('//div/p[contains(text(),"Status:")]/text()') ??
|
|
||||||
"")
|
|
||||||
.replaceAll("Status:\n", "")
|
|
||||||
.trim();
|
|
||||||
anime.status = parseStatus(status, statusList);
|
|
||||||
|
|
||||||
anime.name = document.selectFirst("div.title-wrapper > h1 > span").text;
|
|
||||||
anime.author =
|
|
||||||
(document.xpathFirst('//div/p[contains(text(),"Studio:")]/text()') ??
|
|
||||||
"")
|
|
||||||
.replaceAll("Studio:\n", "")
|
|
||||||
.trim();
|
|
||||||
anime.imageUrl = document.selectFirst("div.anime-poster a").attr("href");
|
|
||||||
anime.genre = xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@class,"anime-genre")]/ul/li/text()',
|
|
||||||
);
|
|
||||||
final synonyms =
|
|
||||||
(document.xpathFirst('//div/p[contains(text(),"Synonyms:")]/text()') ??
|
|
||||||
"")
|
|
||||||
.replaceAll("Synonyms:\n", "")
|
|
||||||
.trim();
|
|
||||||
anime.description = document.selectFirst("div.anime-summary").text;
|
|
||||||
if (synonyms.isNotEmpty) {
|
|
||||||
anime.description += "\n\n$synonyms";
|
|
||||||
}
|
|
||||||
final epUrl = "$baseUrl/api?m=release&id=$session&sort=episode_desc&page=1";
|
|
||||||
final resEp = (await client.get(Uri.parse(epUrl), headers: headers)).body;
|
|
||||||
final episodes = await recursivePages(epUrl, resEp, session);
|
|
||||||
|
|
||||||
anime.chapters = episodes;
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MChapter>> recursivePages(
|
|
||||||
String url,
|
|
||||||
String res,
|
|
||||||
String session,
|
|
||||||
) async {
|
|
||||||
final jsonResult = json.decode(res);
|
|
||||||
final page = jsonResult["current_page"];
|
|
||||||
final hasNextPage = page < jsonResult["last_page"];
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var item in jsonResult["data"]) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = "Episode ${item["episode"]}";
|
|
||||||
episode.url = "/play/$session/${item["session"]}";
|
|
||||||
episode.dateUpload = parseDates(
|
|
||||||
[item["created_at"]],
|
|
||||||
"yyyy-MM-dd HH:mm:ss",
|
|
||||||
"en",
|
|
||||||
)[0];
|
|
||||||
animeList.add(episode);
|
|
||||||
}
|
|
||||||
if (hasNextPage) {
|
|
||||||
final newUrl = "${substringBeforeLast(url, "&page=")}&page=${page + 1}";
|
|
||||||
final newRes = (await client.get(
|
|
||||||
Uri.parse(newUrl),
|
|
||||||
headers: headers,
|
|
||||||
)).body;
|
|
||||||
animeList.addAll(await recursivePages(newUrl, newRes, session));
|
|
||||||
}
|
|
||||||
return animeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getSession(String title, String animeId) async {
|
|
||||||
final noRedirect = Client(
|
|
||||||
source,
|
|
||||||
json.encode({"followRedirects": false, "useDartHttpClient": true}),
|
|
||||||
);
|
|
||||||
|
|
||||||
final res = await noRedirect.get(
|
|
||||||
Uri.parse("$baseUrl/a/$animeId"),
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
final location =
|
|
||||||
"https://${substringAfterLast(getMapValue(json.encode(res.headers), "location"), "https://")}";
|
|
||||||
|
|
||||||
if (location == '$baseUrl/anime') {
|
|
||||||
final res = (await client.get(
|
|
||||||
Uri.parse("$baseUrl/api?m=search&q=$title"),
|
|
||||||
headers: headers,
|
|
||||||
)).body;
|
|
||||||
return substringBefore(
|
|
||||||
substringAfter(
|
|
||||||
substringAfter(res, "\"id\":$animeId"),
|
|
||||||
"\"session\":\"",
|
|
||||||
),
|
|
||||||
"\"",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return substringAfterLast(location, '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
//by default we use rhttp package but it does not support `followRedirects`
|
|
||||||
//so setting `useDartHttpClient` to true allows us to use a Dart http package that supports `followRedirects`
|
|
||||||
final client = Client(source, json.encode({"useDartHttpClient": true}));
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl$url"), headers: headers));
|
|
||||||
final document = parseHtml(res.body);
|
|
||||||
final downloadLinks = document.select("div#pickDownload > a");
|
|
||||||
final buttons = document.select("div#resolutionMenu > button");
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < buttons.length; i++) {
|
|
||||||
final btn = buttons[i];
|
|
||||||
final audio = btn.attr(
|
|
||||||
"data-audio",
|
|
||||||
); // Get audio type (jpn/eng). Japanese or Dubbed.
|
|
||||||
final kwikLink = btn.attr("data-src");
|
|
||||||
final quality = btn.text;
|
|
||||||
final paheWinLink = downloadLinks[i].attr("href");
|
|
||||||
|
|
||||||
if (getPreferenceValue(source.id, "preffered_link_type")) {
|
|
||||||
final noRedirectClient = Client(
|
|
||||||
source,
|
|
||||||
json.encode({"followRedirects": false, "useDartHttpClient": true}),
|
|
||||||
);
|
|
||||||
final kwikHeaders = (await noRedirectClient.get(
|
|
||||||
Uri.parse("${paheWinLink}/i"),
|
|
||||||
)).headers;
|
|
||||||
final kwikUrl =
|
|
||||||
"https://${substringAfterLast(getMapValue(json.encode(kwikHeaders), "location"), "https://")}";
|
|
||||||
final reskwik = (await client.get(
|
|
||||||
Uri.parse(kwikUrl),
|
|
||||||
headers: {"Referer": "https://kwik.cx/"},
|
|
||||||
));
|
|
||||||
final matches = RegExp(
|
|
||||||
r'\("(\S+)",\d+,"(\S+)",(\d+),(\d+)',
|
|
||||||
).firstMatch(reskwik.body);
|
|
||||||
final token = decrypt(
|
|
||||||
matches!.group(1)!,
|
|
||||||
matches.group(2)!,
|
|
||||||
matches.group(3)!,
|
|
||||||
int.parse(matches.group(4)!),
|
|
||||||
);
|
|
||||||
final url = RegExp(r'action="([^"]+)"').firstMatch(token)!.group(1)!;
|
|
||||||
final tok = RegExp(r'value="([^"]+)"').firstMatch(token)!.group(1)!;
|
|
||||||
var code = 419;
|
|
||||||
var tries = 0;
|
|
||||||
String location = "";
|
|
||||||
|
|
||||||
while (code != 302 && tries < 20) {
|
|
||||||
String cookie = getMapValue(
|
|
||||||
json.encode(res.request.headers),
|
|
||||||
"cookie",
|
|
||||||
);
|
|
||||||
cookie +=
|
|
||||||
"; ${getMapValue(json.encode(reskwik.headers), "set-cookie").replaceAll("path=/;", "")}";
|
|
||||||
final resNo =
|
|
||||||
await Client(
|
|
||||||
source,
|
|
||||||
json.encode({
|
|
||||||
"followRedirects": false,
|
|
||||||
"useDartHttpClient": true,
|
|
||||||
}),
|
|
||||||
).post(
|
|
||||||
Uri.parse(url),
|
|
||||||
headers: {
|
|
||||||
"referer": reskwik.request.url.toString(),
|
|
||||||
"cookie": cookie,
|
|
||||||
"user-agent": getMapValue(
|
|
||||||
json.encode(res.request.headers),
|
|
||||||
"user-agent",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
body: {"_token": tok},
|
|
||||||
);
|
|
||||||
code = resNo.statusCode;
|
|
||||||
tries++;
|
|
||||||
location = getMapValue(json.encode(resNo.headers), "location");
|
|
||||||
}
|
|
||||||
if (tries > 19) {
|
|
||||||
throw ("Failed to extract the stream uri from kwik.");
|
|
||||||
}
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = location
|
|
||||||
..originalUrl = location
|
|
||||||
..quality = quality;
|
|
||||||
videos.add(video);
|
|
||||||
} else {
|
|
||||||
final ress = (await client.get(
|
|
||||||
Uri.parse(kwikLink),
|
|
||||||
headers: {"Referer": "https://animepahe.com"},
|
|
||||||
));
|
|
||||||
final script = substringAfterLast(
|
|
||||||
xpath(
|
|
||||||
ress.body,
|
|
||||||
'//script[contains(text(),"eval(function")]/text()',
|
|
||||||
).first,
|
|
||||||
"eval(function(",
|
|
||||||
);
|
|
||||||
final videoUrl = substringBefore(
|
|
||||||
substringAfter(
|
|
||||||
unpackJsAndCombine("eval(function($script"),
|
|
||||||
"const source=\\'",
|
|
||||||
),
|
|
||||||
"\\';",
|
|
||||||
);
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = quality
|
|
||||||
..headers = {"referer": "https://kwik.cx"};
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sortVideos(videos);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getString(String ctn, int sep) {
|
|
||||||
int b = 10;
|
|
||||||
String cm =
|
|
||||||
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/";
|
|
||||||
final n = cm.substring(0, b);
|
|
||||||
double mx = 0;
|
|
||||||
for (var index = 0; index < ctn.length; index++) {
|
|
||||||
mx +=
|
|
||||||
(int.tryParse(ctn[ctn.length - index - 1], radix: 10) ?? 0.0)
|
|
||||||
.toInt() *
|
|
||||||
(pow(sep, index));
|
|
||||||
}
|
|
||||||
var m = '';
|
|
||||||
while (mx > 0) {
|
|
||||||
m = n[(mx % b).toInt()] + m;
|
|
||||||
mx = (mx - (mx % b)) / b;
|
|
||||||
}
|
|
||||||
return m.isNotEmpty ? m : '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
String decrypt(String fS, String key, String v1, int v2) {
|
|
||||||
var html = "";
|
|
||||||
var i = 0;
|
|
||||||
final ld = int.parse(v1);
|
|
||||||
while (i < fS.length) {
|
|
||||||
var s = "";
|
|
||||||
while (fS[i] != key[v2]) {
|
|
||||||
s += fS[i];
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
for (var index = 0; index < key.length; index++) {
|
|
||||||
s = s.replaceAll(key[index], index.toString());
|
|
||||||
}
|
|
||||||
html += String.fromCharCode(int.parse(getString(s, v2)) - ld);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos) {
|
|
||||||
String quality = getPreferenceValue(source.id, "preferred_quality");
|
|
||||||
String preferredAudio = getPreferenceValue(
|
|
||||||
source.id,
|
|
||||||
"preferred_audio",
|
|
||||||
); // get user's audio preference
|
|
||||||
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
// Prioritize audio first.
|
|
||||||
// Preferred Audio: Videos with matching preferred audio are ranked highest.
|
|
||||||
int audioMatchA = a.quality.contains(preferredAudio) ? 1 : 0;
|
|
||||||
int audioMatchB = b.quality.contains(preferredAudio) ? 1 : 0;
|
|
||||||
if (audioMatchA != audioMatchB) {
|
|
||||||
return audioMatchB - audioMatchA;
|
|
||||||
}
|
|
||||||
|
|
||||||
// quality prioritized next
|
|
||||||
// Preferred Video Quality: If audio matches, videos with preferred video quality are ranked higher.
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
if (a.quality.contains(quality)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_domain",
|
|
||||||
title: "Preferred domain",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 1,
|
|
||||||
entries: ["animepahe.com", "animepahe.ru", "animepahe.org"],
|
|
||||||
entryValues: [
|
|
||||||
"https://animepahe.com",
|
|
||||||
"https://animepahe.ru",
|
|
||||||
"https://animepahe.org",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SwitchPreferenceCompat(
|
|
||||||
key: "preffered_link_type",
|
|
||||||
title: "Use HLS links",
|
|
||||||
summary: "Enable this if you are having Cloudflare issues.",
|
|
||||||
value: false,
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Preferred Quality",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["1080p", "720p", "360p"],
|
|
||||||
entryValues: ["1080", "720", "360"],
|
|
||||||
),
|
|
||||||
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_audio", // Add new preference for audio
|
|
||||||
title: "Preferred Audio",
|
|
||||||
summary: "Select your preferred audio language (Japanese or English).",
|
|
||||||
valueIndex: 0, // Default to Japanese (or whichever you prefer)
|
|
||||||
entries: ["Japanese", "English"],
|
|
||||||
entryValues: ["jpn", "eng"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimePahe main(MSource source) {
|
|
||||||
return AnimePahe(source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get animepaheSource => _animepaheSource;
|
|
||||||
const _animepaheVersion = "0.0.75";
|
|
||||||
const _animepaheSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/animepahe/animepahe.dart";
|
|
||||||
Source _animepaheSource = Source(
|
|
||||||
name: "AnimePahe",
|
|
||||||
baseUrl: "https://www.animepahe.ru",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/animepahe/icon.png",
|
|
||||||
sourceCodeUrl: _animepaheSourceCodeUrl,
|
|
||||||
version: _animepaheVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class DonghuaStream extends MProvider {
|
|
||||||
DonghuaStream({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client(source);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get supportsLatest => true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> get headers => {};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse("${source.baseUrl}/anime?page=${page}&sub=&order=popular"))).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//article[@class="bs"]/div/a/@href');
|
|
||||||
final names = xpath(res, '//article[@class="bs"]/div/a/@title');
|
|
||||||
final images = xpath(res, '//article[@class="bs"]/div/a/div/img/@data-src');
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final nextPage = xpath(res, '//a[@class="r"]/@href');
|
|
||||||
return MPages(animeList, nextPage.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse("${source.baseUrl}/anime?page=${page}&sub=&order=update"))).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//article[@class="bs"]/div/a/@href');
|
|
||||||
final names = xpath(res, '//article[@class="bs"]/div/a/@title');
|
|
||||||
final images = xpath(res, '//article[@class="bs"]/div/a/div/img/@data-src');
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final nextPage = xpath(res, '//a[@class="r"]/@href');
|
|
||||||
return MPages(animeList, nextPage.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res = (await client.get(Uri.parse("${source.baseUrl}/page/${page}/?s=${query}"))).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//article[@class="bs"]/div/a/@href');
|
|
||||||
final names = xpath(res, '//article[@class="bs"]/div/a/@title');
|
|
||||||
final images = xpath(res, '//article[@class="bs"]/div/a/div/img/@src');
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final nextPage = xpath(res, '//a[@class="next page-numbers"]/@href');
|
|
||||||
return MPages(animeList, nextPage.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
var genre = xpath(res,'//div[@class="genxed"]/a/text()');
|
|
||||||
genre.remove('MY FAVOURITE');
|
|
||||||
anime.genre = genre;
|
|
||||||
anime.description = xpath(res,'//div[@class="entry-content"]/p/text()').join("\n");
|
|
||||||
|
|
||||||
final statusList = [{"Status: Ongoing": 0, "Status: Completed": 1}];
|
|
||||||
final infoContent = xpath(res,'//div[@class="info-content"]/div[@class="spe"]/span/text()');
|
|
||||||
anime.status = parseStatus(infoContent[0], statusList);
|
|
||||||
anime.author = infoContent[1].replaceFirst('Network: ','').replaceFirst('Donghua Stream, ','');
|
|
||||||
anime.artist = infoContent[2].replaceFirst('Studio: ','');
|
|
||||||
final epElements = parseHtml(res).select('div.eplister > ul > li >a');
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
|
|
||||||
for (var epElement in epElements) {
|
|
||||||
final number = epElement.selectFirst("div.epl-num").text;
|
|
||||||
final title = epElement.selectFirst("div.epl-title").text;
|
|
||||||
final dateString = epElement.selectFirst("div.epl-date").text;
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = "Episode $number";
|
|
||||||
episode.url = epElement.getHref;
|
|
||||||
episode.dateUpload = parseDates([dateString],"MMMM d, yyyy","en",)[0];
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
anime.chapters = episodesList;
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// For anime episode video list
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
var servers = parseHtml(res).select('select.mirror > option[data-index]');
|
|
||||||
if(servers.length==0){
|
|
||||||
final src_data = parseHtml(res).selectFirst('article[id] > script').attr('src').replaceAll ('data:text/javascript;base64,','');
|
|
||||||
final src_function = utf8.decode(base64Url.decode(src_data));
|
|
||||||
final html_data = RegExp(r'"html":"(.*?)","autoplayIndex"').firstMatch(src_function).group(1);
|
|
||||||
servers = parseHtml(html_data.replaceAll(r'\t', '\t').replaceAll(r'\n', '\n').replaceAll(r'\"', '"').replaceAll(r'\/', '/')).select('select.mirror > option[data-index]');
|
|
||||||
}
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (var i = 0; i < servers.length; i++) {
|
|
||||||
String name = '${servers[i].attr("data-index")}: ${servers[i].text}';
|
|
||||||
String valueHtml = utf8.decode(base64Url.decode(servers[i].attr('value')));
|
|
||||||
final serverUrlList = xpath(valueHtml,'//iframe/@src');
|
|
||||||
if (serverUrlList.length>0){
|
|
||||||
String serverUrl = serverUrlList[0];
|
|
||||||
if(serverUrl.contains('https://geo.dailymotion.com/player')){
|
|
||||||
String videoId = RegExp(r'[?&]video=([a-zA-Z0-9]+)').firstMatch(serverUrl).group(1);
|
|
||||||
videos.addAll(await dailymotionVideosFetcher(videoId,name));
|
|
||||||
}else if(serverUrl.contains('//play.streamplay.co.in/')){
|
|
||||||
String videoId = serverUrl.split('/embed/')[1];
|
|
||||||
videos.addAll(await streamplayVideosFetcher(videoId,name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> dailymotionVideosFetcher(String videoID, String name) async {
|
|
||||||
String metaDataUrl = 'https://www.dailymotion.com/player/metadata/video/$videoID';
|
|
||||||
final res = (await client.get(Uri.parse(metaDataUrl))).body;
|
|
||||||
final jsonRes = json.decode(res);
|
|
||||||
String masterUrl = jsonRes["qualities"]["auto"][0]["url"];
|
|
||||||
return m3u8extractor(masterUrl, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> streamplayVideosFetcher(String videoID, String name) async {
|
|
||||||
String url = 'https://play.streamplay.co.in/embed/'+videoID;
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final match = RegExp(r"eval\(function\(p,a,c,k,e,d\)\{[\s\S]*?\}\((.*?)\)").firstMatch(res);
|
|
||||||
if (match == null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final argsStr = match.group(1);
|
|
||||||
final argsPattern = RegExp(r"'(.*?)',(.*?),(.*?),'(.*?)'\.split");
|
|
||||||
final argsMatches = argsPattern.firstMatch(argsStr);
|
|
||||||
final arg_p = argsMatches.group(1);
|
|
||||||
final arg_a =int.parse(argsMatches.group(2));
|
|
||||||
final arg_c =int.parse(argsMatches.group(3));
|
|
||||||
final arg_k =argsMatches.group(4).split('|').toList();
|
|
||||||
final unpacked_js = unpack(arg_p,arg_a,arg_c,arg_k);
|
|
||||||
final kakenMatch = RegExp(r'window\.kaken\s*=\s*"([^"]+)"').firstMatch(unpacked_js);
|
|
||||||
if (kakenMatch == null) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final kakenValue = kakenMatch.group(1);
|
|
||||||
final apiUrl = 'https://play.streamplay.co.in/api/?$kakenValue';
|
|
||||||
final apiRes = (await client.get(Uri.parse(apiUrl))).body;
|
|
||||||
final jsonRes = json.decode(apiRes);
|
|
||||||
String masterUrl = jsonRes['sources'][0]['file'];
|
|
||||||
List<MTrack> subtitles = [];
|
|
||||||
for (final track in jsonRes['tracks']){
|
|
||||||
MTrack subtitle = MTrack();
|
|
||||||
subtitle.label = name + ' - ' + track['label'];
|
|
||||||
subtitle.file = track['file'];
|
|
||||||
subtitles.add(subtitle);
|
|
||||||
}
|
|
||||||
List<MVideo> videos = await m3u8extractor(masterUrl, name);
|
|
||||||
if(videos.length>0){
|
|
||||||
videos[0].subtitles = subtitles;
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
String unpack(String p, int a, int c, List<String> k) {
|
|
||||||
for (int i = c - 1; i >= 0; i--) {
|
|
||||||
String word = (i < k.length) ? k[i] : baseN(i, a);
|
|
||||||
String pattern = r'\b' + baseN(i, a) + r'\b';
|
|
||||||
p = p.replaceAll(RegExp(pattern), word);
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
String baseN(int num, int base) {
|
|
||||||
const digits = '0123456789abcdefghijklmnopqrstuvwxyz';
|
|
||||||
if (num == 0) return '0';
|
|
||||||
String result = '';
|
|
||||||
while (num > 0) {
|
|
||||||
result = digits[num % base] + result;
|
|
||||||
num ~/= base;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> m3u8extractor(String masterUrl, String name) async {
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
List<MTrack> subtitles = [];
|
|
||||||
final masterPlaylistRes = (await client.get(Uri.parse(masterUrl), headers: headers)).body;
|
|
||||||
final subtitleRegExp = RegExp(r'#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"', dotAll: true);
|
|
||||||
for (final match in subtitleRegExp.allMatches(masterPlaylistRes)) {
|
|
||||||
MTrack subtitle = MTrack();
|
|
||||||
subtitle.label = name + ' - ' + match.group(1) ?? 'Subtitle';
|
|
||||||
subtitle.file = match.group(2) ?? '';
|
|
||||||
subtitles.add(subtitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = "$name - $quality";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
if(videos.length>0){
|
|
||||||
videos[0].subtitles = subtitles;
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DonghuaStream main(MSource source) {
|
|
||||||
return DonghuaStream(source:source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 81 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get donghuastreamSource => _donghuastreamSource;
|
|
||||||
const _donghuastreamVersion = "0.0.2";
|
|
||||||
const _donghuastreamSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/donghuastream/donghuastream.dart";
|
|
||||||
Source _donghuastreamSource = Source(
|
|
||||||
name: "DonghuaStream",
|
|
||||||
baseUrl: "https://donghuastream.org",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/donghuastream/icon.png",
|
|
||||||
sourceCodeUrl: _donghuastreamSourceCodeUrl,
|
|
||||||
version: _donghuastreamVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get gogoanimeSource => _gogoanimeSource;
|
|
||||||
const _gogoanimeVersion = "0.1.2";
|
|
||||||
const _gogoanimeSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/gogoanime/gogoanime.dart";
|
|
||||||
Source _gogoanimeSource = Source(
|
|
||||||
name: "Gogoanime",
|
|
||||||
baseUrl: "https://anitaku.to",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/gogoanime/icon.png",
|
|
||||||
sourceCodeUrl: _gogoanimeSourceCodeUrl,
|
|
||||||
version: _gogoanimeVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,223 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class KissKh extends MProvider {
|
|
||||||
KissKh({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${source.baseUrl}/api/DramaList/List?page=$page&type=0&sub=0&country=0&status=0&order=1&pageSize=40",
|
|
||||||
),
|
|
||||||
)).body;
|
|
||||||
final jsonRes = json.decode(res);
|
|
||||||
final datas = jsonRes["data"];
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
|
|
||||||
for (var data in datas) {
|
|
||||||
var anime = MManga();
|
|
||||||
anime.name = data["title"];
|
|
||||||
anime.imageUrl = data["thumbnail"] ?? "";
|
|
||||||
anime.link =
|
|
||||||
"${source.baseUrl}/api/DramaList/Drama/${data["id"]}?isq=false";
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
|
|
||||||
int lastPage = jsonRes["totalCount"];
|
|
||||||
int pages = jsonRes["page"];
|
|
||||||
return MPages(animeList, pages < lastPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${source.baseUrl}/api/DramaList/List?page=$page&type=0&sub=0&country=0&status=0&order=12&pageSize=40",
|
|
||||||
),
|
|
||||||
)).body;
|
|
||||||
final jsonRes = json.decode(res);
|
|
||||||
final datas = jsonRes["data"];
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
|
|
||||||
for (var data in datas) {
|
|
||||||
var anime = MManga();
|
|
||||||
anime.name = data["title"];
|
|
||||||
anime.imageUrl = data["thumbnail"] ?? "";
|
|
||||||
anime.link =
|
|
||||||
"${source.baseUrl}/api/DramaList/Drama/${data["id"]}?isq=false";
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
|
|
||||||
int lastPage = jsonRes["totalCount"];
|
|
||||||
int pages = jsonRes["page"];
|
|
||||||
return MPages(animeList, pages < lastPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/api/DramaList/Search?q=$query&type=0"),
|
|
||||||
)).body;
|
|
||||||
final jsonRes = json.decode(res);
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var data in jsonRes) {
|
|
||||||
var anime = MManga();
|
|
||||||
anime.name = data["title"];
|
|
||||||
anime.imageUrl = data["thumbnail"] ?? "";
|
|
||||||
anime.link =
|
|
||||||
"${source.baseUrl}/api/DramaList/Drama/${data["id"]}?isq=false";
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"Ongoing": 0, "Completed": 1},
|
|
||||||
];
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
var anime = MManga();
|
|
||||||
final jsonRes = json.decode(res);
|
|
||||||
final status = jsonRes["status"] ?? "";
|
|
||||||
anime.description = jsonRes["description"];
|
|
||||||
anime.status = parseStatus(status, statusList);
|
|
||||||
anime.imageUrl = jsonRes["thumbnail"];
|
|
||||||
var episodes = jsonRes["episodes"];
|
|
||||||
String type = jsonRes["type"];
|
|
||||||
final episodesCount = jsonRes["episodesCount"] as int;
|
|
||||||
final containsAnime = type.contains("Anime");
|
|
||||||
final containsTVSeries = type.contains("TVSeries");
|
|
||||||
final containsHollywood = type.contains("Hollywood");
|
|
||||||
final containsMovie = type.contains("Movie");
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
|
|
||||||
for (var a in episodes) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
String number = (a["number"] as double).toString().replaceAll(".0", "");
|
|
||||||
final id = a["id"];
|
|
||||||
if (containsAnime || containsTVSeries) {
|
|
||||||
episode.name = "Episode $number";
|
|
||||||
} else if (containsHollywood && episodesCount == 1 || containsMovie) {
|
|
||||||
episode.name = "Movie";
|
|
||||||
} else if (containsHollywood && episodesCount > 1) {
|
|
||||||
episode.name = "Episode $number";
|
|
||||||
}
|
|
||||||
episode.url =
|
|
||||||
"${source.baseUrl}/api/DramaList/Episode/$id.png?err=false&ts=&time=";
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.chapters = episodesList;
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final id = substringAfter(substringBefore(url, ".png"), "Episode/");
|
|
||||||
final jsonRes = json.decode(res);
|
|
||||||
|
|
||||||
final subRes =
|
|
||||||
(await client.get(Uri.parse("${source.baseUrl}/api/Sub/$id"))).body;
|
|
||||||
var jsonSubRes = json.decode(subRes);
|
|
||||||
List<MTrack> subtitles = [];
|
|
||||||
|
|
||||||
for (var sub in jsonSubRes) {
|
|
||||||
final subUrl = sub["src"] as String;
|
|
||||||
final label = sub["label"];
|
|
||||||
if (subUrl.endsWith("txt")) {
|
|
||||||
var subtitle = await getSubtitle(subUrl, label);
|
|
||||||
subtitles.add(subtitle);
|
|
||||||
} else {
|
|
||||||
var subtitle = MTrack();
|
|
||||||
subtitle
|
|
||||||
..label = label
|
|
||||||
..file = subUrl;
|
|
||||||
subtitles.add(subtitle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final videoUrl = jsonRes["Video"];
|
|
||||||
var video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "kisskh"
|
|
||||||
..subtitles = subtitles
|
|
||||||
..headers = {
|
|
||||||
"referer": "https://kisskh.me/",
|
|
||||||
"origin": "https://kisskh.me",
|
|
||||||
};
|
|
||||||
return [video];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<MTrack> getSubtitle(String subUrl, String subLang) async {
|
|
||||||
final response = await client.get(
|
|
||||||
Uri.parse(subUrl),
|
|
||||||
headers: {"referer": "https://kisskh.me/", "origin": "https://kisskh.me"},
|
|
||||||
);
|
|
||||||
final subtitleData = response.body;
|
|
||||||
String decrypted = "\n";
|
|
||||||
for (String line in subtitleData.split('\n')) {
|
|
||||||
decrypted += "${decrypt(line.trim())}\n";
|
|
||||||
}
|
|
||||||
var subtitle = MTrack();
|
|
||||||
subtitle
|
|
||||||
..label = subLang
|
|
||||||
..file = decrypted;
|
|
||||||
return subtitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
String decrypt(String data) {
|
|
||||||
final key = utf8.decode([
|
|
||||||
56,
|
|
||||||
48,
|
|
||||||
53,
|
|
||||||
54,
|
|
||||||
52,
|
|
||||||
56,
|
|
||||||
51,
|
|
||||||
54,
|
|
||||||
52,
|
|
||||||
54,
|
|
||||||
51,
|
|
||||||
50,
|
|
||||||
56,
|
|
||||||
55,
|
|
||||||
54,
|
|
||||||
51,
|
|
||||||
]);
|
|
||||||
final iv = utf8.decode([
|
|
||||||
54,
|
|
||||||
56,
|
|
||||||
53,
|
|
||||||
50,
|
|
||||||
54,
|
|
||||||
49,
|
|
||||||
50,
|
|
||||||
51,
|
|
||||||
55,
|
|
||||||
48,
|
|
||||||
49,
|
|
||||||
56,
|
|
||||||
53,
|
|
||||||
50,
|
|
||||||
55,
|
|
||||||
51,
|
|
||||||
]);
|
|
||||||
return cryptoHandler(data, iv, key, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KissKh main(MSource source) {
|
|
||||||
return KissKh(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get kisskhSource => _kisskhSource;
|
|
||||||
const _kisskhVersion = "0.0.7";
|
|
||||||
const _kisskhSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/kisskh/kisskh.dart";
|
|
||||||
Source _kisskhSource = Source(
|
|
||||||
name: "KissKH",
|
|
||||||
baseUrl: "https://kisskh.co",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/kisskh/icon.png",
|
|
||||||
sourceCodeUrl: _kisskhSourceCodeUrl,
|
|
||||||
version: _kisskhVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 6.5 KiB |
@@ -1,580 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class NineAnimeTv extends MProvider {
|
|
||||||
NineAnimeTv({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/filter?sort=all&page=$page"),
|
|
||||||
)).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${source.baseUrl}/filter?sort=recently_updated&page=$page",
|
|
||||||
),
|
|
||||||
)).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String url = "${source.baseUrl}/filter?keyword=$query";
|
|
||||||
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "GenreFilter") {
|
|
||||||
final genre = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
url += "${ll(url)}genre=";
|
|
||||||
if (genre.isNotEmpty) {
|
|
||||||
for (var st in genre) {
|
|
||||||
url += "${st.value}";
|
|
||||||
if (genre.length > 1) {
|
|
||||||
url += "%2C";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (genre.length > 1) {
|
|
||||||
url = substringBeforeLast(url, '%2C');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filter.type == "SeasonFilter") {
|
|
||||||
final season = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
url += "${ll(url)}season=";
|
|
||||||
if (season.isNotEmpty) {
|
|
||||||
for (var st in season) {
|
|
||||||
url += "${st.value}";
|
|
||||||
if (season.length > 1) {
|
|
||||||
url += "%2C";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (season.length > 1) {
|
|
||||||
url = substringBeforeLast(url, '%2C');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filter.type == "YearFilter") {
|
|
||||||
final year = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
url += "${ll(url)}year=";
|
|
||||||
if (year.isNotEmpty) {
|
|
||||||
for (var st in year) {
|
|
||||||
url += "${st.value}";
|
|
||||||
if (year.length > 1) {
|
|
||||||
url += "%2C";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (year.length > 1) {
|
|
||||||
url = substringBeforeLast(url, '%2C');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filter.type == "TypeFilter") {
|
|
||||||
final type = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
url += "${ll(url)}type=";
|
|
||||||
if (type.isNotEmpty) {
|
|
||||||
for (var st in type) {
|
|
||||||
url += "${st.value}";
|
|
||||||
if (type.length > 1) {
|
|
||||||
url += "%2C";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type.length > 1) {
|
|
||||||
url = substringBeforeLast(url, '%2C');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filter.type == "StatusFilter") {
|
|
||||||
final status = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}status=$status";
|
|
||||||
} else if (filter.type == "LanguageFilter") {
|
|
||||||
final language = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
url += "${ll(url)}language=";
|
|
||||||
if (language.isNotEmpty) {
|
|
||||||
for (var st in language) {
|
|
||||||
url += "${st.value}";
|
|
||||||
if (language.length > 1) {
|
|
||||||
url += "%2C";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (language.length > 1) {
|
|
||||||
url = substringBeforeLast(url, '%2C');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filter.type == "SortFilter") {
|
|
||||||
final sort = filter.values[filter.state].value;
|
|
||||||
url += "${ll(url)}sort=$sort";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse("$url&page=$page"))).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"Currently Airing": 0, "Finished Airing": 1},
|
|
||||||
];
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse("${source.baseUrl}$url"))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final infoElement = document.selectFirst("div.film-infor");
|
|
||||||
final status =
|
|
||||||
infoElement.xpathFirst(
|
|
||||||
'//div[contains(text(),"Status:")]/following-sibling::div/span/text()',
|
|
||||||
) ??
|
|
||||||
"";
|
|
||||||
anime.status = parseStatus(status, statusList);
|
|
||||||
anime.description =
|
|
||||||
infoElement.selectFirst("div.film-description > p")?.text ?? "";
|
|
||||||
anime.author =
|
|
||||||
infoElement.xpathFirst(
|
|
||||||
'//div[contains(text(),"Studios:")]/following-sibling::div/a/text()',
|
|
||||||
) ??
|
|
||||||
"";
|
|
||||||
|
|
||||||
anime.genre = infoElement.xpath(
|
|
||||||
'//div[contains(text(),"Genre:")]/following-sibling::div/a/text()',
|
|
||||||
);
|
|
||||||
final id = parseHtml(res).selectFirst("div[data-id]").attr("data-id");
|
|
||||||
|
|
||||||
final resEp =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/ajax/episode/list/$id"),
|
|
||||||
)).body;
|
|
||||||
final html = json.decode(resEp)["html"];
|
|
||||||
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
|
|
||||||
final epsElements = parseHtml(html).select("a");
|
|
||||||
for (var epElement in epsElements) {
|
|
||||||
final id = epElement.attr('data-id');
|
|
||||||
|
|
||||||
final title = epElement.attr('title') ?? "";
|
|
||||||
|
|
||||||
final epNum = epElement.attr('data-number');
|
|
||||||
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = "Episode $epNum $title";
|
|
||||||
episode.url = id;
|
|
||||||
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}/ajax/episode/servers?episodeId=$url"),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
final html = json.decode(res)["html"];
|
|
||||||
|
|
||||||
final serverElements = parseHtml(html).select("div.server-item");
|
|
||||||
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final hosterSelection = preferenceHosterSelection(source.id);
|
|
||||||
final typeSelection = preferenceTypeSelection(source.id);
|
|
||||||
for (var serverElement in serverElements) {
|
|
||||||
final name = serverElement.text;
|
|
||||||
final id = serverElement.attr("data-id");
|
|
||||||
final subDub = serverElement.attr("data-type");
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/ajax/episode/sources?id=$id"),
|
|
||||||
)).body;
|
|
||||||
final epUrl = json.decode(res)["link"];
|
|
||||||
List<MVideo> a = [];
|
|
||||||
|
|
||||||
if (hosterSelection.contains(name) && typeSelection.contains(subDub)) {
|
|
||||||
if (name.contains("Vidstreaming")) {
|
|
||||||
a = await rapidCloudExtractor(epUrl, "Vidstreaming - $subDub");
|
|
||||||
} else if (name.contains("Vidcloud")) {
|
|
||||||
a = await rapidCloudExtractor(epUrl, "Vidcloud - $subDub");
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortVideos(videos, source.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages parseAnimeList(String res) {
|
|
||||||
final elements = parseHtml(res).select("div.film_list-wrap > div");
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var element in elements) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = element.selectFirst("div.film-detail > h3 > a").text;
|
|
||||||
anime.imageUrl = element.selectFirst(" div.film-poster > img").getSrc;
|
|
||||||
anime.link = element.selectFirst("div.film-detail > h3 > a").getHref;
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
|
|
||||||
return MPages(animeList, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> rapidCloudExtractor(String url, String name) async {
|
|
||||||
final serverUrl = ['https://megacloud.tv', 'https://rapid-cloud.co'];
|
|
||||||
|
|
||||||
final serverType =
|
|
||||||
url.startsWith('https://megacloud.tv') ||
|
|
||||||
url.startsWith('https://megacloud.club')
|
|
||||||
? 0
|
|
||||||
: 1;
|
|
||||||
final sourceUrl = [
|
|
||||||
'/embed-2/ajax/e-1/getSources?id=',
|
|
||||||
'/ajax/embed-6-v2/getSources?id=',
|
|
||||||
];
|
|
||||||
final sourceSpliter = ['/e-1/', '/embed-6-v2/'];
|
|
||||||
final id = url.split(sourceSpliter[serverType]).last.split('?').first;
|
|
||||||
final resServer =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse('${serverUrl[serverType]}${sourceUrl[serverType]}$id'),
|
|
||||||
headers: {"X-Requested-With": "XMLHttpRequest"},
|
|
||||||
)).body;
|
|
||||||
final encrypted = getMapValue(resServer, "encrypted");
|
|
||||||
String videoResJson = "";
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
if (encrypted == "true") {
|
|
||||||
final ciphered = getMapValue(resServer, "sources");
|
|
||||||
List<List<int>> indexPairs = await generateIndexPairs(serverType);
|
|
||||||
var password = '';
|
|
||||||
String ciphertext = ciphered;
|
|
||||||
int index = 0;
|
|
||||||
for (List<int> item in json.decode(json.encode(indexPairs))) {
|
|
||||||
int start = item.first + index;
|
|
||||||
int end = start + item.last;
|
|
||||||
String passSubstr = ciphered.substring(start, end);
|
|
||||||
password += passSubstr;
|
|
||||||
ciphertext = ciphertext.replaceFirst(passSubstr, "");
|
|
||||||
index += item.last;
|
|
||||||
}
|
|
||||||
videoResJson = decryptAESCryptoJS(ciphertext, password);
|
|
||||||
} else {
|
|
||||||
videoResJson = json.encode(
|
|
||||||
(json.decode(resServer)["sources"] as List<Map<String, dynamic>>),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String masterUrl =
|
|
||||||
((json.decode(videoResJson) as List<Map<String, dynamic>>)
|
|
||||||
.first)['file'];
|
|
||||||
String type =
|
|
||||||
((json.decode(videoResJson) as List<Map<String, dynamic>>)
|
|
||||||
.first)['type'];
|
|
||||||
|
|
||||||
final tracks =
|
|
||||||
(json.decode(resServer)['tracks'] as List)
|
|
||||||
.where((e) => e['kind'] == 'captions' ? true : false)
|
|
||||||
.toList();
|
|
||||||
List<MTrack> subtitles = [];
|
|
||||||
|
|
||||||
for (var sub in tracks) {
|
|
||||||
try {
|
|
||||||
MTrack subtitle = MTrack();
|
|
||||||
subtitle
|
|
||||||
..label = sub["label"]
|
|
||||||
..file = sub["file"];
|
|
||||||
subtitles.add(subtitle);
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "hls") {
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "$name - $quality"
|
|
||||||
..subtitles = subtitles;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = masterUrl
|
|
||||||
..originalUrl = masterUrl
|
|
||||||
..quality = "$name - Default"
|
|
||||||
..subtitles = subtitles;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<List<int>>> generateIndexPairs(int serverType) async {
|
|
||||||
final jsPlayerUrl = [
|
|
||||||
"https://megacloud.tv/js/player/a/prod/e1-player.min.js",
|
|
||||||
"https://rapid-cloud.co/js/player/prod/e6-player-v2.min.js",
|
|
||||||
];
|
|
||||||
final scriptText =
|
|
||||||
(await client.get(Uri.parse(jsPlayerUrl[serverType]))).body;
|
|
||||||
|
|
||||||
final switchCode = scriptText.substring(
|
|
||||||
scriptText.lastIndexOf('switch'),
|
|
||||||
scriptText.indexOf('=partKey'),
|
|
||||||
);
|
|
||||||
|
|
||||||
List<int> indexes = [];
|
|
||||||
for (var variableMatch
|
|
||||||
in RegExp(r'=(\w+)').allMatches(switchCode).toList()) {
|
|
||||||
final regex = RegExp(
|
|
||||||
',${(variableMatch as RegExpMatch).group(1)}=((?:0x)?([0-9a-fA-F]+))',
|
|
||||||
);
|
|
||||||
Match? match = regex.firstMatch(scriptText);
|
|
||||||
|
|
||||||
if (match != null) {
|
|
||||||
String value = match.group(1);
|
|
||||||
if (value.contains("0x")) {
|
|
||||||
indexes.add(int.parse(substringAfter(value, "0x"), radix: 16));
|
|
||||||
} else {
|
|
||||||
indexes.add(int.parse(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chunked(indexes, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<List<int>> chunked(List<int> list, int size) {
|
|
||||||
List<List<int>> chunks = [];
|
|
||||||
for (int i = 0; i < list.length; i += size) {
|
|
||||||
int end = list.length;
|
|
||||||
if (i + size < list.length) {
|
|
||||||
end = i + size;
|
|
||||||
}
|
|
||||||
chunks.add(list.sublist(i, end));
|
|
||||||
}
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
GroupFilter("GenreFilter", "Genre", [
|
|
||||||
CheckBoxFilter("Action", "1"),
|
|
||||||
CheckBoxFilter("Adventure", "2"),
|
|
||||||
CheckBoxFilter("Cars", "3"),
|
|
||||||
CheckBoxFilter("Comedy", "4"),
|
|
||||||
CheckBoxFilter("Dementia", "5"),
|
|
||||||
CheckBoxFilter("Demons", "6"),
|
|
||||||
CheckBoxFilter("Drama", "8"),
|
|
||||||
CheckBoxFilter("Ecchi", "9"),
|
|
||||||
CheckBoxFilter("Fantasy", "10"),
|
|
||||||
CheckBoxFilter("Game", "11"),
|
|
||||||
CheckBoxFilter("Harem", "35"),
|
|
||||||
CheckBoxFilter("Historical", "13"),
|
|
||||||
CheckBoxFilter("Horror", "14"),
|
|
||||||
CheckBoxFilter("Isekai", "44"),
|
|
||||||
CheckBoxFilter("Josei", "43"),
|
|
||||||
CheckBoxFilter("Kids", "15"),
|
|
||||||
CheckBoxFilter("Magic", "16"),
|
|
||||||
CheckBoxFilter("Martial Arts", "17"),
|
|
||||||
CheckBoxFilter("Mecha", "18"),
|
|
||||||
CheckBoxFilter("Military", "38"),
|
|
||||||
CheckBoxFilter("Music", "19"),
|
|
||||||
CheckBoxFilter("Mystery", "7"),
|
|
||||||
CheckBoxFilter("Parody", "20"),
|
|
||||||
CheckBoxFilter("Police", "39"),
|
|
||||||
CheckBoxFilter("Psychological", "40"),
|
|
||||||
CheckBoxFilter("Romance", "22"),
|
|
||||||
CheckBoxFilter("Samurai", "21"),
|
|
||||||
CheckBoxFilter("School", "23"),
|
|
||||||
CheckBoxFilter("Sci-Fi", "24"),
|
|
||||||
CheckBoxFilter("Seinen", "42"),
|
|
||||||
CheckBoxFilter("Shoujo", "25"),
|
|
||||||
CheckBoxFilter("Shoujo Ai", "26"),
|
|
||||||
CheckBoxFilter("Shounen", "27"),
|
|
||||||
CheckBoxFilter("Shounen Ai", "28"),
|
|
||||||
CheckBoxFilter("Slice of Life", "36"),
|
|
||||||
CheckBoxFilter("Space", "29"),
|
|
||||||
CheckBoxFilter("Sports", "30"),
|
|
||||||
CheckBoxFilter("Super Power", "31"),
|
|
||||||
CheckBoxFilter("Supernatural", "37"),
|
|
||||||
CheckBoxFilter("Thriller", "41"),
|
|
||||||
CheckBoxFilter("Vampire", "32"),
|
|
||||||
]),
|
|
||||||
GroupFilter("SeasonFilter", "Season", [
|
|
||||||
CheckBoxFilter("Fall", "3"),
|
|
||||||
CheckBoxFilter("Summer", "2"),
|
|
||||||
CheckBoxFilter("Spring", "1"),
|
|
||||||
CheckBoxFilter("Winter", "4"),
|
|
||||||
]),
|
|
||||||
GroupFilter("YearFilter", "Year", [
|
|
||||||
CheckBoxFilter("2024", "2024"),
|
|
||||||
CheckBoxFilter("2023", "2023"),
|
|
||||||
CheckBoxFilter("2022", "2022"),
|
|
||||||
CheckBoxFilter("2021", "2021"),
|
|
||||||
CheckBoxFilter("2020", "2020"),
|
|
||||||
CheckBoxFilter("2019", "2019"),
|
|
||||||
CheckBoxFilter("2018", "2018"),
|
|
||||||
CheckBoxFilter("2017", "2017"),
|
|
||||||
CheckBoxFilter("2016", "2016"),
|
|
||||||
CheckBoxFilter("2015", "2015"),
|
|
||||||
CheckBoxFilter("2014", "2014"),
|
|
||||||
CheckBoxFilter("2013", "2013"),
|
|
||||||
CheckBoxFilter("2012", "2012"),
|
|
||||||
CheckBoxFilter("2011", "2011"),
|
|
||||||
CheckBoxFilter("2010", "2010"),
|
|
||||||
CheckBoxFilter("2009", "2009"),
|
|
||||||
CheckBoxFilter("2008", "2008"),
|
|
||||||
CheckBoxFilter("2007", "2007"),
|
|
||||||
CheckBoxFilter("2006", "2006"),
|
|
||||||
CheckBoxFilter("2005", "2005"),
|
|
||||||
CheckBoxFilter("2004", "2004"),
|
|
||||||
CheckBoxFilter("2003", "2003"),
|
|
||||||
CheckBoxFilter("2002", "2002"),
|
|
||||||
CheckBoxFilter("2001", "2001"),
|
|
||||||
]),
|
|
||||||
SelectFilter("SortFilter", "Sort by", 0, [
|
|
||||||
SelectFilterOption("All", "all"),
|
|
||||||
SelectFilterOption("Default", "default"),
|
|
||||||
SelectFilterOption("Recently Added", "recently_added"),
|
|
||||||
SelectFilterOption("Recently Updated", "recently_updated"),
|
|
||||||
SelectFilterOption("Score", "score"),
|
|
||||||
SelectFilterOption("Name A-Z", "name_az"),
|
|
||||||
SelectFilterOption("Released Date", "released_date"),
|
|
||||||
SelectFilterOption("Most Watched", "most_watched"),
|
|
||||||
]),
|
|
||||||
GroupFilter("TypeFilter", "Type", [
|
|
||||||
CheckBoxFilter("Movie", "1"),
|
|
||||||
CheckBoxFilter("TV Series", "2"),
|
|
||||||
CheckBoxFilter("OVA", "3"),
|
|
||||||
CheckBoxFilter("ONA", "4"),
|
|
||||||
CheckBoxFilter("Special", "5"),
|
|
||||||
CheckBoxFilter("Music", "6"),
|
|
||||||
]),
|
|
||||||
SelectFilter("StatusFilter", "Status", 0, [
|
|
||||||
SelectFilterOption("All", "all"),
|
|
||||||
SelectFilterOption("Finished Airing", "1"),
|
|
||||||
SelectFilterOption("Currently Airing", "2"),
|
|
||||||
SelectFilterOption("Not yet aired", "3"),
|
|
||||||
]),
|
|
||||||
GroupFilter("LanguageFilter", "Language", [
|
|
||||||
CheckBoxFilter("Sub", "sub"),
|
|
||||||
CheckBoxFilter("Dub", "dub"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Preferred Quality",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 1,
|
|
||||||
entries: ["1080p", "720p", "480p", "360p"],
|
|
||||||
entryValues: ["1080", "720", "480", "360"],
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_server",
|
|
||||||
title: "Preferred server",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Vidstreaming", "VidCloud"],
|
|
||||||
entryValues: ["Vidstreaming", "VidCloud"],
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_type",
|
|
||||||
title: "Preferred Type",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Sub", "Dub"],
|
|
||||||
entryValues: ["sub", "dub"],
|
|
||||||
),
|
|
||||||
MultiSelectListPreference(
|
|
||||||
key: "hoster_selection",
|
|
||||||
title: "Enable/Disable Hosts",
|
|
||||||
summary: "",
|
|
||||||
entries: ["Vidstreaming", "VidCloud"],
|
|
||||||
entryValues: ["Vidstreaming", "Vidcloud"],
|
|
||||||
values: ["Vidstreaming", "Vidcloud"],
|
|
||||||
),
|
|
||||||
MultiSelectListPreference(
|
|
||||||
key: "type_selection",
|
|
||||||
title: "Enable/Disable Types",
|
|
||||||
summary: "",
|
|
||||||
entries: ["Sub", "Dub"],
|
|
||||||
entryValues: ["sub", "dub"],
|
|
||||||
values: ["sub", "dub"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
|
||||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
|
||||||
String server = getPreferenceValue(sourceId, "preferred_server");
|
|
||||||
String type = getPreferenceValue(sourceId, "preferred_type");
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
|
|
||||||
if (a.quality.contains(quality) &&
|
|
||||||
a.quality.toLowerCase().contains(type.toLowerCase()) &&
|
|
||||||
a.quality.toLowerCase().contains(server.toLowerCase())) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality) &&
|
|
||||||
b.quality.toLowerCase().contains(type.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> preferenceHosterSelection(int sourceId) {
|
|
||||||
return getPreferenceValue(sourceId, "hoster_selection");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> preferenceTypeSelection(int sourceId) {
|
|
||||||
return getPreferenceValue(sourceId, "type_selection");
|
|
||||||
}
|
|
||||||
|
|
||||||
String ll(String url) {
|
|
||||||
if (url.contains("?")) {
|
|
||||||
return "&";
|
|
||||||
}
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NineAnimeTv main(MSource source) {
|
|
||||||
return NineAnimeTv(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get nineanimetv => _nineanimetv;
|
|
||||||
const _nineanimetvVersion = "0.0.55";
|
|
||||||
const _nineanimetvCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/nineanimetv/nineanimetv.dart";
|
|
||||||
Source _nineanimetv = Source(
|
|
||||||
name: "9AnimeTv",
|
|
||||||
baseUrl: "https://9animetv.to",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/nineanimetv/icon.png",
|
|
||||||
sourceCodeUrl: _nineanimetvCodeUrl,
|
|
||||||
version: _nineanimetvVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get uhdmoviesSource => _uhdmoviesSource;
|
|
||||||
const _uhdmoviesVersion = "0.0.5";
|
|
||||||
const _uhdmoviesSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/uhdmovies/uhdmovies.dart";
|
|
||||||
Source _uhdmoviesSource = Source(
|
|
||||||
name: "UHD Movies",
|
|
||||||
baseUrl: "https://uhdmovies.fans",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/uhdmovies/icon.png",
|
|
||||||
sourceCodeUrl: _uhdmoviesSourceCodeUrl,
|
|
||||||
version: _uhdmoviesVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class UHDMovies extends MProvider {
|
|
||||||
UHDMovies({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get supportsLatest => false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => getPreferenceValue(source.id, "pref_domain_new");
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl/page/$page"))).body;
|
|
||||||
return animeFromElement(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
return MPages([], false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl/page/$page/?s=${query.replaceAll(" ", "+")}"),
|
|
||||||
)).body;
|
|
||||||
return animeFromElement(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
url = getUrlWithoutDomain(url);
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl${url}"))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final description = xpath(res, '//pre/span/text()');
|
|
||||||
if (description.isNotEmpty) {
|
|
||||||
anime.description = description.first;
|
|
||||||
}
|
|
||||||
anime.status = MStatus.ongoing;
|
|
||||||
final episodesTitles = xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@style, "center") or contains(@class, "maxbutton")]/a[contains(@class, "maxbutton") or contains(@href, "?sid=")]/text()',
|
|
||||||
);
|
|
||||||
final episodesUrls = xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@style, "center") or contains(@class, "maxbutton")]/a[contains(@class, "maxbutton") or contains(@href, "?sid=")]/@href',
|
|
||||||
);
|
|
||||||
bool isSeries = false;
|
|
||||||
if (episodesTitles.first.contains("Episode") ||
|
|
||||||
episodesTitles.first.contains("Zip") ||
|
|
||||||
episodesTitles.first.contains("Pack")) {
|
|
||||||
isSeries = true;
|
|
||||||
}
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
if (!isSeries) {
|
|
||||||
List<String> moviesTitles = [];
|
|
||||||
moviesTitles = xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@style, "center") or contains(@class, "maxbutton")]/parent::p//preceding-sibling::p[contains(@style, "center")]/text()',
|
|
||||||
);
|
|
||||||
List<String> titles = [];
|
|
||||||
if (moviesTitles.isEmpty) {
|
|
||||||
moviesTitles = xpath(res, '//p[contains(@style, "center")]/text()');
|
|
||||||
}
|
|
||||||
for (var title in moviesTitles) {
|
|
||||||
if (title.isNotEmpty &&
|
|
||||||
!title.contains('Download') &&
|
|
||||||
!title.contains('Note:') &&
|
|
||||||
!title.contains('Copyright')) {
|
|
||||||
titles.add(title.split('[').first.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var i = 0; i < titles.length; i++) {
|
|
||||||
final title = titles[i];
|
|
||||||
final quality = RegExp(r'\d{3,4}p').firstMatch(title)?.group(0) ?? "";
|
|
||||||
final url = episodesUrls[i];
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = title;
|
|
||||||
ep.url = url;
|
|
||||||
ep.scanlator = quality;
|
|
||||||
episodesList.add(ep);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
List<String> seasonTitles = [];
|
|
||||||
final episodeTitles = xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@style, "center") or contains(@class, "maxbutton")]/parent::p//preceding-sibling::p[contains(@style, "center") and not(text()^="Episode")]/text()',
|
|
||||||
);
|
|
||||||
List<String> titles = [];
|
|
||||||
for (var title in episodeTitles) {
|
|
||||||
if (title.isNotEmpty) {
|
|
||||||
titles.add(title.split('[').first.trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int number = 0;
|
|
||||||
for (var i = 0; i < episodesTitles.length; i++) {
|
|
||||||
final episode = episodesTitles[i];
|
|
||||||
final episodeUrl = episodesUrls[i];
|
|
||||||
if (!episode.contains("Zip") || !episode.contains("Pack")) {
|
|
||||||
if (episode == "Episode 1" && seasonTitles.contains("Episode 1")) {
|
|
||||||
number++;
|
|
||||||
} else if (episode == "Episode 1") {
|
|
||||||
seasonTitles.add(episode);
|
|
||||||
}
|
|
||||||
final season =
|
|
||||||
RegExp(r'S(\d{2})').firstMatch(titles[number])?.group(1) ?? "";
|
|
||||||
final quality =
|
|
||||||
RegExp(r'\d{3,4}p').firstMatch(titles[number])?.group(0) ?? "";
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = "Season $season $episode $quality";
|
|
||||||
ep.url = episodeUrl;
|
|
||||||
ep.scanlator = quality;
|
|
||||||
episodesList.add(ep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anime.chapters = episodesList.reversed.toList();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final res = await getMediaUrl(url);
|
|
||||||
return await extractVideos(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
EditTextPreference(
|
|
||||||
key: "pref_domain_new",
|
|
||||||
title: "Currently used domain",
|
|
||||||
summary: "",
|
|
||||||
value: "https://uhdmovies.fans",
|
|
||||||
dialogTitle: "Currently used domain",
|
|
||||||
dialogMessage: "",
|
|
||||||
text: "https://uhdmovies.fans",
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> extractVideos(String url) async {
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (int type = 1; type < 3; type++) {
|
|
||||||
url = url.replaceAll("/file/", "/wfile/") + "?type=$type";
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final links = xpath(res, '//div[@class="mb-4"]/a/@href');
|
|
||||||
for (int i = 0; i < links.length; i++) {
|
|
||||||
final link = links[i];
|
|
||||||
String decodedLink = link;
|
|
||||||
if (!link.contains("workers.dev")) {
|
|
||||||
decodedLink = utf8.decode(
|
|
||||||
base64Url.decode(substringAfter(link, "download?url=")),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = decodedLink
|
|
||||||
..originalUrl = decodedLink
|
|
||||||
..quality = "CF $type Worker ${i + 1}";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getMediaUrl(String url) async {
|
|
||||||
String res = "";
|
|
||||||
String host = "";
|
|
||||||
if (url.contains("?sid=")) {
|
|
||||||
final finalUrl = await redirectorBypasser(url);
|
|
||||||
host = Uri.parse(finalUrl).host;
|
|
||||||
res = (await client.get(Uri.parse(finalUrl))).body;
|
|
||||||
} else if (url.contains("r?key=")) {
|
|
||||||
res = (await client.get(Uri.parse(url))).body;
|
|
||||||
host = Uri.parse(url).host;
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
final path = substringBefore(substringAfter(res, "replace(\""), "\"");
|
|
||||||
if (path == "/404") return "";
|
|
||||||
return "https://$host$path";
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> redirectorBypasser(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
String lastDoc = await recursiveDoc(url, res);
|
|
||||||
final js = xpath(lastDoc, '//script[contains(text(), "/?go=")]/text()');
|
|
||||||
if (js.isEmpty) return "";
|
|
||||||
String script = js.first;
|
|
||||||
String nextUrl = substringBefore(
|
|
||||||
substringAfter(script, "\"href\",\""),
|
|
||||||
'"',
|
|
||||||
);
|
|
||||||
if (!nextUrl.contains("http")) return "";
|
|
||||||
String cookieName = substringAfter(nextUrl, "go=");
|
|
||||||
String cookieValue = substringBefore(
|
|
||||||
substringAfter(script, "'$cookieName', '"),
|
|
||||||
"'",
|
|
||||||
);
|
|
||||||
final response =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(nextUrl),
|
|
||||||
headers: {"referer": url, "Cookie": "$cookieName=$cookieValue"},
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
final lastRes = parseHtml(
|
|
||||||
response,
|
|
||||||
).selectFirst("meta[http-equiv]").attr("content");
|
|
||||||
return substringAfter(lastRes, "url=");
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages animeFromElement(String res) {
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//*[@class="entry-image"]/a/@href');
|
|
||||||
final names = xpath(res, '//*[@class="entry-image"]/a/@title');
|
|
||||||
final images = xpath(res, '//*[@class="entry-image"]/a/img/@src');
|
|
||||||
|
|
||||||
for (var i = 0; i < names.length; i++) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = names[i].replaceAll("Download", "");
|
|
||||||
anime.imageUrl = images[i];
|
|
||||||
anime.link = urls[i];
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
final nextPage = xpath(res, '//a[@class="next page-numbers"]/@href');
|
|
||||||
return MPages(animeList, nextPage.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> recursiveDoc(String url, String html) async {
|
|
||||||
final urlR = xpath(html, '//form[@id="landing"]/@action');
|
|
||||||
if (urlR.isEmpty) return html;
|
|
||||||
final name = xpath(html, '//input/@name').first;
|
|
||||||
final value = xpath(html, '//input/@value').first;
|
|
||||||
final body = {"$name": value};
|
|
||||||
final response =
|
|
||||||
(await client.post(
|
|
||||||
Uri.parse(urlR.first),
|
|
||||||
headers: {"referer": url},
|
|
||||||
body: body,
|
|
||||||
)).body;
|
|
||||||
return recursiveDoc(url, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UHDMovies main(MSource source) {
|
|
||||||
return UHDMovies(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 7.4 KiB |
@@ -1,19 +0,0 @@
|
|||||||
// from https://github.com/MiraiEnoki/anymex-extensions/blob/main/dart/anime/src/en/vumeto
|
|
||||||
|
|
||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get vumetoSource => _vumetoSource;
|
|
||||||
const _vumetoVersion = "0.0.55";
|
|
||||||
const _vumetoSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/vumeto/vumeto.dart";
|
|
||||||
Source _vumetoSource = Source(
|
|
||||||
name: "Vumeto",
|
|
||||||
baseUrl: "https://vumeto.com",
|
|
||||||
lang: "en",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/vumeto/icon.png",
|
|
||||||
sourceCodeUrl: _vumetoSourceCodeUrl,
|
|
||||||
version: _vumetoVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class Vumeto extends MProvider {
|
|
||||||
Vumeto({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get supportsLatest => true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> get headers => {
|
|
||||||
"Cookie":
|
|
||||||
"_ga=GA1.1.2064759276.1741681027; _ga_5HMNDC3ZE4=GS1.1.1741824276.8.1.1741824749.0.0.0",
|
|
||||||
"User-Agent":
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
|
|
||||||
"Referer": "https://vumeto.com/",
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final url = 'https://vumeto.com/most-popular';
|
|
||||||
final resp = await client.get(Uri.parse(url));
|
|
||||||
|
|
||||||
final document = parseHtml(resp.body);
|
|
||||||
|
|
||||||
return MPages(scrapeAnimeList(document), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final url = 'https://vumeto.com/recently-updated';
|
|
||||||
final resp = await client.get(Uri.parse(url));
|
|
||||||
|
|
||||||
final document = parseHtml(resp.body);
|
|
||||||
|
|
||||||
return MPages(scrapeAnimeList(document), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final url = 'https://vumeto.com/search?q=$query';
|
|
||||||
final resp = await client.get(Uri.parse(url));
|
|
||||||
|
|
||||||
final document = parseHtml(resp.body);
|
|
||||||
|
|
||||||
return MPages(scrapeAnimeList(document), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
String fixUrl(String encodedUrl) {
|
|
||||||
return encodedUrl
|
|
||||||
.split('url=')
|
|
||||||
.last
|
|
||||||
.split('&w')
|
|
||||||
.first
|
|
||||||
.replaceAll('%3A', ':')
|
|
||||||
.replaceAll('%2F', '/')
|
|
||||||
.replaceAll('%3F', '?')
|
|
||||||
.replaceAll('%3D', '=')
|
|
||||||
.replaceAll('%26', '&');
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MManga> scrapeAnimeList(MDocument document) {
|
|
||||||
List<MElement>? animeElements = document.getElementsByClassName(
|
|
||||||
'relative group border-0',
|
|
||||||
);
|
|
||||||
List<MManga> results = [];
|
|
||||||
|
|
||||||
if (animeElements != null) {
|
|
||||||
for (var anime in animeElements) {
|
|
||||||
String? title = anime.selectFirst('h3')?.text ?? '';
|
|
||||||
String? animeUrl = anime.selectFirst('a')?.attr('href') ?? '';
|
|
||||||
String? imageUrl = anime.selectFirst('img')?.attr('src') ?? '';
|
|
||||||
|
|
||||||
MManga manga = MManga();
|
|
||||||
manga.name = title;
|
|
||||||
manga.link =
|
|
||||||
"https://vumeto.com/watch/" +
|
|
||||||
animeUrl.replaceAll('/info/', '').split('/').first +
|
|
||||||
'?ep=1';
|
|
||||||
manga.imageUrl = fixUrl(imageUrl.split('url=').last);
|
|
||||||
|
|
||||||
results.add(manga);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"Releasing": 0, "Finished": 1},
|
|
||||||
];
|
|
||||||
|
|
||||||
final uri = Uri.parse(url);
|
|
||||||
final resp = await client.get(uri, headers);
|
|
||||||
final document = parseHtml(resp.body);
|
|
||||||
|
|
||||||
final description =
|
|
||||||
document.selectFirst("meta[name='description']").attr("content") ?? '';
|
|
||||||
|
|
||||||
MStatus status = MStatus.unknown;
|
|
||||||
final statusStart = resp.body.indexOf(
|
|
||||||
":",
|
|
||||||
resp.body.indexOf("\\\"status\\\""),
|
|
||||||
);
|
|
||||||
final statusEnd = resp.body.indexOf("\\\",", statusStart);
|
|
||||||
if (statusStart != -1 && statusEnd != -1) {
|
|
||||||
final rawStatus = resp.body.substring(statusStart + 1, statusEnd);
|
|
||||||
status = parseStatus(rawStatus.replaceAll("\\\"", ""), statusList);
|
|
||||||
}
|
|
||||||
|
|
||||||
final genresStart = resp.body.indexOf(
|
|
||||||
"[",
|
|
||||||
resp.body.indexOf("\\\"genres\\\":"),
|
|
||||||
);
|
|
||||||
final genresEnd = resp.body.indexOf("]", genresStart);
|
|
||||||
var genres = [];
|
|
||||||
if (genresStart != -1 && genresEnd != -1) {
|
|
||||||
final genreLinks = resp.body
|
|
||||||
.substring(genresStart + 1, genresEnd)
|
|
||||||
.split(",");
|
|
||||||
genres = genreLinks.map((String e) => e.replaceAll("\\\"", "")).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MChapter> chapters = [];
|
|
||||||
final scripts = document.getElementsByTagName("script");
|
|
||||||
|
|
||||||
String jsonData = "";
|
|
||||||
for (var script in scripts!) {
|
|
||||||
if (script.text!.contains("episodesData")) {
|
|
||||||
final regex = RegExp(
|
|
||||||
r'self\.__next_f\.push\(\[1,".*?",null,(.*?)\]\)',
|
|
||||||
dotAll: true,
|
|
||||||
);
|
|
||||||
final match = regex.firstMatch(script.text!);
|
|
||||||
|
|
||||||
if (match != null && match.groupCount >= 1) {
|
|
||||||
String cleaned = match.group(1)!.replaceAll(r'\', '');
|
|
||||||
|
|
||||||
jsonData = cleaned.substring(0, cleaned.length - 3);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
print("Regex did not match.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Map<String, dynamic> parsedData = json.decode(jsonData);
|
|
||||||
List<dynamic> episodesData = parsedData['episodesData'];
|
|
||||||
for (var ep in episodesData) {
|
|
||||||
MChapter ch = MChapter();
|
|
||||||
final number =
|
|
||||||
(ep?['episodeNo'] ?? episodesData.indexOf(ep) + 1).toString();
|
|
||||||
|
|
||||||
ch.name = "Episode $number";
|
|
||||||
|
|
||||||
ch.url = url.split('?').first + '?ep=$number';
|
|
||||||
|
|
||||||
if (!chapters.any((c) => c.name == ch.name)) {
|
|
||||||
chapters.add(ch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MManga result = MManga();
|
|
||||||
result.description = description;
|
|
||||||
result.status = status;
|
|
||||||
result.genre = genres;
|
|
||||||
result.chapters = chapters.reversed.toList();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String stripTags(String htmlString) {
|
|
||||||
final RegExp exp = RegExp(
|
|
||||||
r'<[^>]*>',
|
|
||||||
multiLine: true,
|
|
||||||
caseSensitive: false,
|
|
||||||
);
|
|
||||||
return htmlString.replaceAll(exp, '').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
try {
|
|
||||||
final resp = await client.get(Uri.parse(url), headers);
|
|
||||||
|
|
||||||
final document = parseHtml(resp.body);
|
|
||||||
|
|
||||||
final scripts = document.getElementsByTagName("script");
|
|
||||||
if (scripts.isEmpty) {
|
|
||||||
print("No <script> tags found.");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
String jsonData = "";
|
|
||||||
for (var script in scripts) {
|
|
||||||
if (script.text.contains("episodesData")) {
|
|
||||||
final regex = RegExp(
|
|
||||||
r'self\.__next_f\.push\(\[1,".*?",null,(.*?)\]\)',
|
|
||||||
dotAll: true,
|
|
||||||
);
|
|
||||||
final match = regex.firstMatch(script.text);
|
|
||||||
if (match != null && match.groupCount >= 1) {
|
|
||||||
String cleaned = match.group(1)!.replaceAll(r'\', '');
|
|
||||||
jsonData = cleaned.substring(0, cleaned.length - 3);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsonData.isEmpty) {
|
|
||||||
print("Could not find video data in any script tag.");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, dynamic> parsedData = json.decode(jsonData);
|
|
||||||
final List<dynamic> episodesData = parsedData['episodesData'];
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> extractedData = [];
|
|
||||||
|
|
||||||
if (episodesData.isNotEmpty) {
|
|
||||||
final index = int.parse(url.split('ep=').last);
|
|
||||||
final episode = episodesData[index - 1];
|
|
||||||
|
|
||||||
for (var sub in episode['sub']) {
|
|
||||||
for (var source in sub['sources']) {
|
|
||||||
String quality = "AUTO";
|
|
||||||
if (stringContains(sub['serverName'], 'Kiwi')) {
|
|
||||||
quality = '${source['quality'].toString()}P';
|
|
||||||
}
|
|
||||||
final String serverName =
|
|
||||||
"${sub['serverName']}- SUB - ${quality ?? "AUTO"}";
|
|
||||||
final String m3u8Url = source['url'];
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> subtitles = [];
|
|
||||||
for (var track in sub['tracks'] ?? []) {
|
|
||||||
if (track['kind'] == "captions") {
|
|
||||||
final String label =
|
|
||||||
(track['label'] is String)
|
|
||||||
? track['label'].toString()
|
|
||||||
: 'Unknown';
|
|
||||||
subtitles.add({
|
|
||||||
'url': track['file'],
|
|
||||||
'default': track['default'] == true,
|
|
||||||
"label": label,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source['isProxy'] == true || source['isProxy'] == 'true') {
|
|
||||||
m3u8Url = 'https://proxy.vumeto.com/fetch?url=' + m3u8Url;
|
|
||||||
}
|
|
||||||
|
|
||||||
extractedData.add({
|
|
||||||
'serverName': serverName,
|
|
||||||
'm3u8Url': m3u8Url,
|
|
||||||
'subtitles': subtitles,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var dub in episode['dub']) {
|
|
||||||
for (var source in dub['sources']) {
|
|
||||||
String quality = 'AUTO';
|
|
||||||
if (stringContains(dub['serverName'], 'Kiwi')) {
|
|
||||||
quality = '${source['quality'].toString()}P';
|
|
||||||
}
|
|
||||||
final String serverName =
|
|
||||||
"${dub['serverName']}- DUB - ${quality ?? "AUTO"}";
|
|
||||||
final String m3u8Url = source['url'];
|
|
||||||
|
|
||||||
List<Map<String, dynamic>> subtitles = [];
|
|
||||||
if (source['isProxy'] == true || source['isProxy'] == 'true') {
|
|
||||||
m3u8Url = 'https://proxy.vumeto.com/fetch?url=' + m3u8Url;
|
|
||||||
}
|
|
||||||
extractedData.add({
|
|
||||||
'serverName': serverName,
|
|
||||||
'm3u8Url': m3u8Url,
|
|
||||||
'subtitles': subtitles,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<MVideo> data =
|
|
||||||
extractedData.map((videoData) {
|
|
||||||
MVideo video = MVideo();
|
|
||||||
|
|
||||||
video.url = videoData['m3u8Url'] ?? '';
|
|
||||||
video.url = video.url.replaceAll(
|
|
||||||
RegExp(r'\b[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+\.net\b'),
|
|
||||||
'stormywind74.xyz',
|
|
||||||
);
|
|
||||||
video.quality = videoData['serverName'] ?? '';
|
|
||||||
video.originalUrl = videoData['m3u8Url'] ?? '';
|
|
||||||
|
|
||||||
List<MTrack>? subtitles =
|
|
||||||
(videoData['subtitles'] as List<dynamic>?)?.map((subtitle) {
|
|
||||||
MTrack track = MTrack();
|
|
||||||
return track
|
|
||||||
..file = subtitle['url'] ?? ''
|
|
||||||
..label = subtitle['label'];
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
video.subtitles = subtitles;
|
|
||||||
|
|
||||||
return video;
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch (e) {
|
|
||||||
print("Error in getVideoList: $e");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool stringContains(String text, String search) {
|
|
||||||
return RegExp(search, caseSensitive: false).hasMatch(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
// TODO: implement
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
// TODO: implement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Vumeto main(MSource source) {
|
|
||||||
return Vumeto(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,352 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class AnimeOnlineNinja extends MProvider {
|
|
||||||
AnimeOnlineNinja({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@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 Client client = Client(
|
|
||||||
source,
|
|
||||||
json.encode({"useDartHttpClient": true}),
|
|
||||||
);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get animeonlineninjaSource => _animeonlineninjaSource;
|
|
||||||
const _animeonlineninjaVersion = "0.0.35";
|
|
||||||
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,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,555 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class AnimeSama extends MProvider {
|
|
||||||
AnimeSama({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final doc = (await client.get(Uri.parse("${source.baseUrl}/#$page"))).body;
|
|
||||||
final regex = RegExp(
|
|
||||||
r"""^\s*carteClassique\(\s*.*?\s*,\s*"(.*?)".*\)""",
|
|
||||||
multiLine: true,
|
|
||||||
);
|
|
||||||
var matches = regex.allMatches(doc).toList();
|
|
||||||
List<List<RegExpMatch>> chunks = chunked(matches, 5);
|
|
||||||
List<MManga> seasons = [];
|
|
||||||
if (page > 0 && page <= chunks.length) {
|
|
||||||
for (RegExpMatch match in chunks[page - 1]) {
|
|
||||||
seasons.addAll(
|
|
||||||
await fetchAnimeSeasons(
|
|
||||||
"${source.baseUrl}/catalogue/${match.group(1)}",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MPages(seasons, page < chunks.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse(source.baseUrl))).body;
|
|
||||||
var document = parseHtml(res);
|
|
||||||
final latest =
|
|
||||||
document
|
|
||||||
.select("h2")
|
|
||||||
.where(
|
|
||||||
(MElement e) => e.outerHtml.toLowerCase().contains(
|
|
||||||
"derniers épisodes ajoutés",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
final seasonElements =
|
|
||||||
(latest.first.parent.nextElementSibling as MElement)
|
|
||||||
.select("div")
|
|
||||||
.toList();
|
|
||||||
List<MManga> seasons = [];
|
|
||||||
for (var seasonElement in seasonElements) {
|
|
||||||
seasons.addAll(
|
|
||||||
await fetchAnimeSeasons(
|
|
||||||
(seasonElement as MElement).getElementsByTagName("a").first.getHref,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return MPages(seasons, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/catalogue/listing_all.php"),
|
|
||||||
)).body;
|
|
||||||
var databaseElements = parseHtml(res).select(".cardListAnime");
|
|
||||||
List<MElement> elements = [];
|
|
||||||
elements =
|
|
||||||
databaseElements
|
|
||||||
.where(
|
|
||||||
(MElement element) => element
|
|
||||||
.select("h1, p")
|
|
||||||
.any(
|
|
||||||
(MElement e) => e.text.toLowerCase().contains(
|
|
||||||
query.toLowerCase().trim(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "TypeFilter") {
|
|
||||||
final types = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
elements =
|
|
||||||
elements
|
|
||||||
.where(
|
|
||||||
(MElement element) =>
|
|
||||||
types.isEmpty ||
|
|
||||||
types.any((p) => element.className.contains(p.value)),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
} else if (filter.type == "LanguageFilter") {
|
|
||||||
final language = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
elements =
|
|
||||||
elements
|
|
||||||
.where(
|
|
||||||
(MElement element) =>
|
|
||||||
language.isEmpty ||
|
|
||||||
language.any((p) => element.className.contains(p.value)),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
} else if (filter.type == "GenreFilter") {
|
|
||||||
final included =
|
|
||||||
(filter.state as List)
|
|
||||||
.where((e) => e.state == 1 ? true : false)
|
|
||||||
.toList();
|
|
||||||
final excluded =
|
|
||||||
(filter.state as List)
|
|
||||||
.where((e) => e.state == 2 ? true : false)
|
|
||||||
.toList();
|
|
||||||
if (included.isNotEmpty) {
|
|
||||||
elements =
|
|
||||||
elements
|
|
||||||
.where(
|
|
||||||
(MElement element) => included.every(
|
|
||||||
(p) => element.className.contains(p.value),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
if (excluded.isNotEmpty) {
|
|
||||||
elements =
|
|
||||||
elements
|
|
||||||
.where(
|
|
||||||
(MElement element) => excluded.every(
|
|
||||||
(p) => element.className.contains(p.value),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<List<MElement>> chunks = chunked(elements, 5);
|
|
||||||
if (chunks.isEmpty) return MPages([], false);
|
|
||||||
List<MManga> seasons = [];
|
|
||||||
for (var seasonElement in chunks[page - 1]) {
|
|
||||||
seasons.addAll(
|
|
||||||
await fetchAnimeSeasons(
|
|
||||||
seasonElement.getElementsByTagName("a").first.getHref,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return MPages(seasons, page < chunks.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
var animeUrl =
|
|
||||||
"${source.baseUrl}${substringBeforeLast(getUrlWithoutDomain(url), "/")}";
|
|
||||||
var movie = int.tryParse(
|
|
||||||
url.split("#").length >= 2 ? url.split("#")[1] : "",
|
|
||||||
);
|
|
||||||
List<Map<String, dynamic>> playersList = [];
|
|
||||||
for (var lang in ["vostfr", "vf"]) {
|
|
||||||
final players = await fetchPlayers("$animeUrl/$lang");
|
|
||||||
if (players.isNotEmpty) {
|
|
||||||
playersList.add({"players": players, "lang": lang});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int maxLength = 0;
|
|
||||||
for (var sublist in playersList) {
|
|
||||||
for (var innerList in sublist["players"]) {
|
|
||||||
if (innerList.length > maxLength) {
|
|
||||||
maxLength = innerList.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
for (var episodeNumber = 0; episodeNumber < maxLength; episodeNumber++) {
|
|
||||||
List<String> langs = [];
|
|
||||||
bool isVf = false;
|
|
||||||
int iVostfr = 0;
|
|
||||||
int iVf = 0;
|
|
||||||
List<Map<String, dynamic>> players = [];
|
|
||||||
for (var playerList in playersList) {
|
|
||||||
for (var player in playerList["players"]) {
|
|
||||||
if (player.length > episodeNumber) {
|
|
||||||
isVf = playerList["lang"] == "vf";
|
|
||||||
if ((isVf && iVf < 2) || (!isVf && iVostfr < 2)) {
|
|
||||||
var lang = playerList["lang"];
|
|
||||||
if (!langs.contains(lang)) {
|
|
||||||
langs.add(lang);
|
|
||||||
}
|
|
||||||
players.add({"lang": lang, "player": player[episodeNumber]});
|
|
||||||
isVf ? iVf++ : iVostfr++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = movie == null ? 'Episode ${episodeNumber + 1}' : 'Film';
|
|
||||||
episode.scanlator = langs.toSet().toList().join(', ').toUpperCase();
|
|
||||||
episode.url = json.encode(players);
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.chapters =
|
|
||||||
movie == null ? episodesList.reversed.toList() : [episodesList[movie]];
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final players = json.decode(url);
|
|
||||||
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("vidmoly")) {
|
|
||||||
a = await vidmolyExtractor(playerUrl, lang);
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortVideos(videos, source.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> vidmolyExtractor(String url, String lang) async {
|
|
||||||
final headers = {'Referer': 'https://vidmoly.to'};
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final playListUrlResponse = (await client.get(Uri.parse(url))).body;
|
|
||||||
final playlistUrl =
|
|
||||||
RegExp(r'file:"(\S+?)"').firstMatch(playListUrlResponse)?.group(1) ??
|
|
||||||
"";
|
|
||||||
if (playlistUrl.isEmpty) return [];
|
|
||||||
final masterPlaylistRes = await client.get(
|
|
||||||
Uri.parse(playlistUrl),
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (masterPlaylistRes.statusCode == 200) {
|
|
||||||
for (var it in substringAfter(
|
|
||||||
masterPlaylistRes.body,
|
|
||||||
"#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");
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "$lang Vidmoly $quality"
|
|
||||||
..headers = headers;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
GroupFilter("TypeFilter", "Type", [
|
|
||||||
CheckBoxFilter("Anime", "Anime"),
|
|
||||||
CheckBoxFilter("Film", "Film"),
|
|
||||||
CheckBoxFilter("Autres", "Autres"),
|
|
||||||
]),
|
|
||||||
GroupFilter("LanguageFilter", "Langue", [
|
|
||||||
CheckBoxFilter("VF", "VF"),
|
|
||||||
CheckBoxFilter("VOSTFR", "VOSTFR"),
|
|
||||||
]),
|
|
||||||
GroupFilter("GenreFilter", "Genre", [
|
|
||||||
TriStateFilter("Action", "Action"),
|
|
||||||
TriStateFilter("Aventure", "Aventure"),
|
|
||||||
TriStateFilter("Combats", "Combats"),
|
|
||||||
TriStateFilter("Comédie", "Comédie"),
|
|
||||||
TriStateFilter("Drame", "Drame"),
|
|
||||||
TriStateFilter("Ecchi", "Ecchi"),
|
|
||||||
TriStateFilter("École", "School-Life"),
|
|
||||||
TriStateFilter("Fantaisie", "Fantasy"),
|
|
||||||
TriStateFilter("Horreur", "Horreur"),
|
|
||||||
TriStateFilter("Isekai", "Isekai"),
|
|
||||||
TriStateFilter("Josei", "Josei"),
|
|
||||||
TriStateFilter("Mystère", "Mystère"),
|
|
||||||
TriStateFilter("Psychologique", "Psychologique"),
|
|
||||||
TriStateFilter("Quotidien", "Slice-of-Life"),
|
|
||||||
TriStateFilter("Romance", "Romance"),
|
|
||||||
TriStateFilter("Seinen", "Seinen"),
|
|
||||||
TriStateFilter("Shônen", "Shônen"),
|
|
||||||
TriStateFilter("Shôjo", "Shôjo"),
|
|
||||||
TriStateFilter("Sports", "Sports"),
|
|
||||||
TriStateFilter("Surnaturel", "Surnaturel"),
|
|
||||||
TriStateFilter("Tournois", "Tournois"),
|
|
||||||
TriStateFilter("Yaoi", "Yaoi"),
|
|
||||||
TriStateFilter("Yuri", "Yuri"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@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"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MManga>> fetchAnimeSeasons(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
var document = parseHtml(res);
|
|
||||||
String animeName = document.getElementById("titreOeuvre")?.text ?? "";
|
|
||||||
|
|
||||||
var seasonRegex = RegExp(r'panneauAnime\("(.*?)",\s*"(.*?)"\);');
|
|
||||||
var scripts = document
|
|
||||||
.select("h2 + p + div > script, h2 + div > script")
|
|
||||||
.map((MElement element) => element.text)
|
|
||||||
.toList()
|
|
||||||
.join("");
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
List<RegExpMatch> seasonRegexReg = seasonRegex.allMatches(scripts).toList();
|
|
||||||
for (var animeIndex = 0; animeIndex < seasonRegexReg.length; animeIndex++) {
|
|
||||||
final seasonName = seasonRegexReg[animeIndex].group(1);
|
|
||||||
final seasonStem = seasonRegexReg[animeIndex].group(2);
|
|
||||||
if (seasonName != "nom" && seasonStem != "url") {
|
|
||||||
if (seasonStem.toLowerCase().contains("film")) {
|
|
||||||
var moviesUrl = "$url/$seasonStem";
|
|
||||||
var movies = await fetchPlayers(moviesUrl);
|
|
||||||
if (movies.isNotEmpty) {
|
|
||||||
var movieNameRegex = RegExp(
|
|
||||||
"^\\s*newSPF\\(\"(.*)\"\\);",
|
|
||||||
multiLine: true,
|
|
||||||
);
|
|
||||||
var moviesDoc = (await client.get(Uri.parse(moviesUrl))).body;
|
|
||||||
List<RegExpMatch> matches =
|
|
||||||
movieNameRegex.allMatches(moviesDoc).toList();
|
|
||||||
|
|
||||||
for (var i = 0; i < movies.length; i++) {
|
|
||||||
var title = "";
|
|
||||||
if (animeIndex == 0 && movies.length == 1) {
|
|
||||||
title = animeName;
|
|
||||||
} else if (matches.length > i) {
|
|
||||||
title = "$animeName ${(matches[i]).group(1)}";
|
|
||||||
} else if (movies.length == 1) {
|
|
||||||
title = "$animeName Film";
|
|
||||||
} else {
|
|
||||||
title = "$animeName Film ${i + 1}";
|
|
||||||
}
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.imageUrl = document.getElementById("coverOeuvre")?.getSrc;
|
|
||||||
anime.genre = (document.xpathFirst(
|
|
||||||
'//h2[contains(text(),"Genres")]/following-sibling::a/text()',
|
|
||||||
) ??
|
|
||||||
"")
|
|
||||||
.split(",");
|
|
||||||
anime.description =
|
|
||||||
document.xpathFirst(
|
|
||||||
'//h2[contains(text(),"Synopsis")]/following-sibling::p/text()',
|
|
||||||
) ??
|
|
||||||
"";
|
|
||||||
|
|
||||||
anime.name = title;
|
|
||||||
anime.link = "$moviesUrl#$i";
|
|
||||||
anime.status = MStatus.completed;
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.imageUrl = document.getElementById("coverOeuvre")?.getSrc;
|
|
||||||
anime.genre = (document.xpathFirst(
|
|
||||||
'//h2[contains(text(),"Genres")]/following-sibling::a/text()',
|
|
||||||
) ??
|
|
||||||
"")
|
|
||||||
.split(",");
|
|
||||||
anime.description =
|
|
||||||
document.xpathFirst(
|
|
||||||
'//h2[contains(text(),"Synopsis")]/following-sibling::p/text()',
|
|
||||||
) ??
|
|
||||||
"";
|
|
||||||
anime.name =
|
|
||||||
'$animeName ${substringBefore(seasonName, ',').replaceAll('"', "")}';
|
|
||||||
anime.link = "$url/$seasonStem";
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return animeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<List<String>>> fetchPlayers(String url) async {
|
|
||||||
var docUrl = "$url/episodes.js";
|
|
||||||
List<List<String>> players = [];
|
|
||||||
var response = (await client.get(Uri.parse(docUrl))).body;
|
|
||||||
|
|
||||||
if (response == "error") {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var sanitizedDoc = sanitizeEpisodesJs(response);
|
|
||||||
for (var i = 1; i <= 8; i++) {
|
|
||||||
final numPlayers = getPlayers("eps$i", sanitizedDoc);
|
|
||||||
|
|
||||||
if (numPlayers != null) players.add(numPlayers);
|
|
||||||
}
|
|
||||||
|
|
||||||
final asPlayers = getPlayers("epsAS", sanitizedDoc);
|
|
||||||
if (asPlayers != null) players.add(asPlayers);
|
|
||||||
|
|
||||||
if (players.isEmpty) return [];
|
|
||||||
List<List<String>> finalPlayers = [];
|
|
||||||
for (var i = 0; i <= players[0].length; i++) {
|
|
||||||
for (var playerList in players) {
|
|
||||||
if (playerList.length > i) {
|
|
||||||
finalPlayers.add(playerList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return finalPlayers.toSet().toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String>? getPlayers(String playerName, String doc) {
|
|
||||||
var playerRegex = RegExp('$playerName\\s*=\\s*(\\[.*?\\])', dotAll: true);
|
|
||||||
var match = playerRegex.firstMatch(doc);
|
|
||||||
if (match == null) return null;
|
|
||||||
final regex = RegExp(r"""https?://[^\s\',\[\]]+""");
|
|
||||||
final matches = regex.allMatches(match.group(1));
|
|
||||||
List<String> urls = [];
|
|
||||||
for (var match in matches.toList()) {
|
|
||||||
urls.add((match as RegExpMatch).group(0).toString());
|
|
||||||
}
|
|
||||||
return urls;
|
|
||||||
}
|
|
||||||
|
|
||||||
String sanitizeEpisodesJs(String doc) {
|
|
||||||
return doc.replaceAll(
|
|
||||||
RegExp(r'(?<=\[|\,)\s*\"\s*(https?://[^\s\"]+)\s*\"\s*(?=\,|\])'),
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<List<dynamic>> chunked(List<dynamic> list, int size) {
|
|
||||||
List<List<dynamic>> chunks = [];
|
|
||||||
for (int i = 0; i < list.length; i += size) {
|
|
||||||
int end = list.length;
|
|
||||||
if (i + size < list.length) {
|
|
||||||
end = i + size;
|
|
||||||
}
|
|
||||||
chunks.add(list.sublist(i, end));
|
|
||||||
}
|
|
||||||
return chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimeSama main(MSource source) {
|
|
||||||
return AnimeSama(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 4.6 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get animesamaSource => _animesama;
|
|
||||||
const animesamaVersion = "0.0.45";
|
|
||||||
const animesamaCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/animesama/animesama.dart";
|
|
||||||
Source _animesama = Source(
|
|
||||||
name: "Anime-Sama",
|
|
||||||
baseUrl: "https://anime-sama.fr",
|
|
||||||
lang: "fr",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/animesama/icon.png",
|
|
||||||
sourceCodeUrl: animesamaCodeUrl,
|
|
||||||
version: animesamaVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class AnimesUltra extends MProvider {
|
|
||||||
AnimesUltra({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => source.baseUrl;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse(baseUrl))).body;
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@class,"swiper-slide item-qtip")]/div[@class="item"]/a/@href',
|
|
||||||
);
|
|
||||||
final names = xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@class,"swiper-slide item-qtip")]/div[@class="item"]/a/img/@title',
|
|
||||||
);
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//*[contains(@class,"swiper-slide item-qtip")]/div[@class="item"]/a/img/@data-src',
|
|
||||||
);
|
|
||||||
|
|
||||||
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, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse(baseUrl))).body;
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="block_area block_area_home"]/div[@class="tab-content"]/div[contains(@class,"block_area-content block_area-list")]/div[@class="film_list-wrap"]/div[@class="flw-item"]/div[@class="film-poster"]/a/@href',
|
|
||||||
);
|
|
||||||
final names = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="block_area block_area_home"]/div[@class="tab-content"]/div[contains(@class,"block_area-content block_area-list")]/div[@class="film_list-wrap"]/div[@class="flw-item"]/div[@class="film-poster"]/a/@title',
|
|
||||||
);
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="block_area block_area_home"]/div[@class="tab-content"]/div[contains(@class,"block_area-content block_area-list")]/div[@class="film_list-wrap"]/div[@class="flw-item"]/div[@class="film-poster"]/img/@data-src',
|
|
||||||
);
|
|
||||||
|
|
||||||
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, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
query = query.trim().replaceAll(" ", "+");
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"$baseUrl/index.php?do=search&subaction=search&story=$query",
|
|
||||||
),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//*[@class="film-poster"]/a/@href');
|
|
||||||
final names = xpath(res, '//*[@class="film-poster"]/a/@title');
|
|
||||||
final images = xpath(res, '//*[@class="film-poster"]/img/@data-src');
|
|
||||||
|
|
||||||
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.reversed.toList(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"En cours": 0, "Terminé": 1},
|
|
||||||
];
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
var anime = MManga();
|
|
||||||
final doc = parseHtml(res);
|
|
||||||
anime.description =
|
|
||||||
xpath(res, '//*[@class="film-description m-hide"]/text()').first;
|
|
||||||
|
|
||||||
final status =
|
|
||||||
xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="item item-title" and contains(text(),"Status:")]/span[2]/text()',
|
|
||||||
).first;
|
|
||||||
anime.status = parseStatus(status, statusList);
|
|
||||||
anime.genre = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="item item-list" and contains(text(),"Genres:")]/a/text()',
|
|
||||||
);
|
|
||||||
anime.author = doc.xpathFirst(
|
|
||||||
'//*[@class="item item-title" and contains(text(),"Studio:")]/span[2]/text()',
|
|
||||||
);
|
|
||||||
final episodesLength = int.parse(
|
|
||||||
substringBefore(
|
|
||||||
doc.xpathFirst('//*[@class="film-stats"]/span[7]/text()'),
|
|
||||||
"/",
|
|
||||||
).replaceAll("Ep", ""),
|
|
||||||
);
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < episodesLength; i++) {
|
|
||||||
var episode = MChapter();
|
|
||||||
episode.name = "Episode ${i + 1}";
|
|
||||||
episode.url = url.replaceAll('.html', '/episode-${i + 1}.html');
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
anime.chapters = episodesList.reversed.toList();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final resHtml = (await client.get(Uri.parse(url))).body;
|
|
||||||
final id = url.split('/')[4].split('-')[0];
|
|
||||||
final resServer =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl/engine/ajax/full-story.php?newsId=$id"),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
final serverIds = xpath(
|
|
||||||
resHtml,
|
|
||||||
'//*[@class="ps__-list"]/div/@data-server-id',
|
|
||||||
);
|
|
||||||
final serverNames = xpath(resHtml, '//*[@class="ps__-list"]/div/a/text()');
|
|
||||||
List<String> serverUrls = [];
|
|
||||||
for (var id in serverIds) {
|
|
||||||
final serversUrls =
|
|
||||||
xpath(
|
|
||||||
jsonDecode(resServer)["html"],
|
|
||||||
'//*[@id="content_player_${id}"]/text()',
|
|
||||||
).first;
|
|
||||||
serverUrls.add(serversUrls);
|
|
||||||
}
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (var i = 0; i < serverNames.length; i++) {
|
|
||||||
final name = serverNames[i];
|
|
||||||
final url = serverUrls[i];
|
|
||||||
List<MVideo> a = [];
|
|
||||||
if (name.contains("Sendvid")) {
|
|
||||||
a = await sendVidExtractorr(
|
|
||||||
url.replaceAll("https:////", "https://"),
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
} else if (name.contains("Sibnet")) {
|
|
||||||
a = await sibnetExtractor(
|
|
||||||
"https://video.sibnet.ru/shell.php?videoid=$url",
|
|
||||||
);
|
|
||||||
} else if (name.contains("Mytv")) {
|
|
||||||
a = await myTvExtractor("https://www.myvi.tv/embed/$url");
|
|
||||||
} else if (name.contains("Fmoon")) {
|
|
||||||
a = await filemoonExtractor(url, "", "");
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimesUltra main(MSource source) {
|
|
||||||
return AnimesUltra(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 9.6 KiB |
@@ -1,18 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get animesultraSource => _animesultraSource;
|
|
||||||
const _animesultraVersion = "0.0.8";
|
|
||||||
const _animesultraSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/animesultra/animesultra.dart";
|
|
||||||
Source _animesultraSource = Source(
|
|
||||||
name: "AnimesUltra",
|
|
||||||
baseUrl: "https://w2.animesultra.net",
|
|
||||||
lang: "fr",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/animesultra/icon.png",
|
|
||||||
sourceCodeUrl: _animesultraSourceCodeUrl,
|
|
||||||
version: _animesultraVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
isFullData: false,
|
|
||||||
);
|
|
||||||
@@ -1,506 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class AniZone extends MProvider {
|
|
||||||
AniZone({required this.source});
|
|
||||||
|
|
||||||
final MSource source;
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
// 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";
|
|
||||||
|
|
||||||
final filterHandlers = {
|
|
||||||
"TypeFilter": "type",
|
|
||||||
"LanguageFilter": "lang",
|
|
||||||
"SaisonFilter": "season",
|
|
||||||
"StatusFilter": "status",
|
|
||||||
"GenreFilter": "genre",
|
|
||||||
};
|
|
||||||
|
|
||||||
final activeFilterParams = <String, String>{};
|
|
||||||
|
|
||||||
for (var filter in filterList.filters) {
|
|
||||||
final paramKey = filterHandlers[filter.type];
|
|
||||||
if (paramKey != null && filter.state is List) {
|
|
||||||
final selectedValues =
|
|
||||||
(filter.state as List)
|
|
||||||
.where((item) {
|
|
||||||
return item.state == true && item.value != null;
|
|
||||||
})
|
|
||||||
.map((item) => item.value as String)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (selectedValues.isNotEmpty) {
|
|
||||||
activeFilterParams[paramKey] = selectedValues.join("%2C");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeFilterParams.isNotEmpty) {
|
|
||||||
final queryString = activeFilterParams.entries
|
|
||||||
.map((entry) => '${Uri.encodeComponent(entry.key)}=${entry.value}')
|
|
||||||
.join('&');
|
|
||||||
baseUrl += '&$queryString';
|
|
||||||
}
|
|
||||||
|
|
||||||
return _getMangaList("$baseUrl&page=$page");
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
MManga anime = MManga();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 26 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get aniZoneSource => _aniZoneSource;
|
|
||||||
const _aniZoneVersion = "0.0.35";
|
|
||||||
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,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,409 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class FrAnime extends MProvider {
|
|
||||||
FrAnime({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res = await dataBase();
|
|
||||||
|
|
||||||
return animeResList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res = await dataBase();
|
|
||||||
|
|
||||||
List list = json.decode(res);
|
|
||||||
return animeResList(json.encode(list.reversed.toList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res = await dataBase();
|
|
||||||
|
|
||||||
return animeSeachFetch(res, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
MManga anime = MManga();
|
|
||||||
String language = "vo".toString();
|
|
||||||
if (url.contains("lang=")) {
|
|
||||||
language = substringBefore(substringAfter(url, "lang="), "&");
|
|
||||||
}
|
|
||||||
String stem = substringBefore(substringAfterLast(url, "/"), "?");
|
|
||||||
final res = await dataBase();
|
|
||||||
|
|
||||||
final animeByTitleOJson = databaseAnimeByTitleO(res, stem);
|
|
||||||
final seasons = json.decode(animeByTitleOJson)["saisons"];
|
|
||||||
|
|
||||||
var seasonsJson = seasons.first;
|
|
||||||
|
|
||||||
if (url.contains("s=")) {
|
|
||||||
int seasonNumber = int.parse(
|
|
||||||
substringBefore(substringAfter(url, "s="), "&"),
|
|
||||||
);
|
|
||||||
seasonsJson = seasons[seasonNumber - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
|
|
||||||
final episodes = seasonsJson["episodes"];
|
|
||||||
|
|
||||||
for (int i = 0; i < episodes.length; i++) {
|
|
||||||
final ep = episodes[i];
|
|
||||||
|
|
||||||
final lang = ep["lang"];
|
|
||||||
|
|
||||||
final vo = lang["vo"];
|
|
||||||
final vf = lang["vf"];
|
|
||||||
bool hasVostfr = vo["lecteurs"].isNotEmpty;
|
|
||||||
bool hasVf = vf["lecteurs"].isNotEmpty;
|
|
||||||
bool playerIsNotEmpty = false;
|
|
||||||
|
|
||||||
if (language == "vo" && hasVostfr) {
|
|
||||||
playerIsNotEmpty = true;
|
|
||||||
} else if (language == "vf" && hasVf) {
|
|
||||||
playerIsNotEmpty = true;
|
|
||||||
}
|
|
||||||
if (playerIsNotEmpty) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.url = "$url&ep=${i + 1}";
|
|
||||||
String title = ep["title"];
|
|
||||||
episode.name = title.replaceAll('"', "");
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.chapters = episodesList.reversed.toList();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
String language = "vo";
|
|
||||||
String videoBaseUrl = "https://api.franime.fr/api/anime";
|
|
||||||
if (url.contains("lang=")) {
|
|
||||||
language = substringBefore(substringAfter(url, "lang="), "&");
|
|
||||||
}
|
|
||||||
String stem = substringBefore(substringAfterLast(url, "/"), "?");
|
|
||||||
final res = await dataBase();
|
|
||||||
|
|
||||||
final animeByTitleOJson = databaseAnimeByTitleO(res, stem);
|
|
||||||
final animeId = json.decode(animeByTitleOJson)["id"];
|
|
||||||
final seasons = json.decode(animeByTitleOJson)["saisons"];
|
|
||||||
|
|
||||||
var seasonsJson = seasons.first;
|
|
||||||
|
|
||||||
videoBaseUrl += "/$animeId/";
|
|
||||||
|
|
||||||
if (url.contains("s=")) {
|
|
||||||
int seasonNumber = int.parse(
|
|
||||||
substringBefore(substringAfter(url, "s="), "&"),
|
|
||||||
);
|
|
||||||
videoBaseUrl += "${seasonNumber - 1}/";
|
|
||||||
seasonsJson = seasons[seasonNumber - 1];
|
|
||||||
} else {
|
|
||||||
videoBaseUrl += "0/";
|
|
||||||
}
|
|
||||||
final episodesJson = seasonsJson["episodes"];
|
|
||||||
var episode = episodesJson.first;
|
|
||||||
if (url.contains("ep=")) {
|
|
||||||
int episodeNumber = int.parse(substringAfter(url, "ep="));
|
|
||||||
episode = episodesJson[episodeNumber - 1];
|
|
||||||
videoBaseUrl += "${episodeNumber - 1}";
|
|
||||||
} else {
|
|
||||||
videoBaseUrl += "0";
|
|
||||||
}
|
|
||||||
final lang = episode["lang"];
|
|
||||||
|
|
||||||
final vo = lang["vo"];
|
|
||||||
final vf = lang["vf"];
|
|
||||||
bool hasVostfr = vo["lecteurs"].isNotEmpty;
|
|
||||||
bool hasVf = vf["lecteurs"].isNotEmpty;
|
|
||||||
List<String> vostfrPlayers = vo["lecteurs"];
|
|
||||||
List<String> vfPlayers = vf["lecteurs"];
|
|
||||||
List<String> players = [];
|
|
||||||
if (language == "vo" && hasVostfr) {
|
|
||||||
players = vostfrPlayers;
|
|
||||||
} else if (language == "vf" && hasVf) {
|
|
||||||
players = vfPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (var i = 0; i < players.length; i++) {
|
|
||||||
String apiUrl = "$videoBaseUrl/$language/$i";
|
|
||||||
String playerName = players[i];
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
|
|
||||||
final playerUrl =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(apiUrl),
|
|
||||||
headers: {"Referer": "https://franime.fr/"},
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
List<MVideo> a = [];
|
|
||||||
print(playerName);
|
|
||||||
if (playerName.contains("vido")) {
|
|
||||||
videos.add(
|
|
||||||
video
|
|
||||||
..url = playerUrl
|
|
||||||
..originalUrl = playerUrl
|
|
||||||
..quality = "FRAnime (Vido)",
|
|
||||||
);
|
|
||||||
} else if (playerName.contains("sendvid")) {
|
|
||||||
a = await sendVidExtractorr(playerUrl, "");
|
|
||||||
} else if (playerName.contains("sibnet")) {
|
|
||||||
a = await sibnetExtractor(playerUrl);
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages animeResList(String res) {
|
|
||||||
final statusList = [
|
|
||||||
{"EN COURS": 0, "TERMINÉ": 1},
|
|
||||||
];
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
|
|
||||||
var jsonResList = json.decode(res);
|
|
||||||
|
|
||||||
for (var animeJson in jsonResList) {
|
|
||||||
final seasons = animeJson["saisons"];
|
|
||||||
List<bool> vostfrListName = [];
|
|
||||||
List<bool> vfListName = [];
|
|
||||||
for (var season in seasons) {
|
|
||||||
for (var episode in season["episodes"]) {
|
|
||||||
final lang = episode["lang"];
|
|
||||||
final vo = lang["vo"];
|
|
||||||
final vf = lang["vf"];
|
|
||||||
vostfrListName.add(vo["lecteurs"].isNotEmpty);
|
|
||||||
vfListName.add(vf["lecteurs"].isNotEmpty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String titleO = animeJson["titleO"];
|
|
||||||
final title = animeJson["title"];
|
|
||||||
final genre = animeJson["themes"];
|
|
||||||
final description = animeJson["description"];
|
|
||||||
final status = parseStatus(animeJson["status"], statusList);
|
|
||||||
final imageUrl = animeJson["affiche"];
|
|
||||||
bool hasVostfr = vostfrListName.contains(true);
|
|
||||||
bool hasVf = vfListName.contains(true);
|
|
||||||
if (hasVostfr || hasVf) {
|
|
||||||
for (int i = 0; i < seasons.length; i++) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
int ind = i + 1;
|
|
||||||
anime.genre = genre;
|
|
||||||
anime.description = description;
|
|
||||||
String seasonTitle = "".toString();
|
|
||||||
String lang = "";
|
|
||||||
if (title.isEmpty) {
|
|
||||||
seasonTitle = titleO;
|
|
||||||
} else {
|
|
||||||
seasonTitle = title;
|
|
||||||
}
|
|
||||||
if (seasons.length > 1) {
|
|
||||||
seasonTitle += " S$ind";
|
|
||||||
}
|
|
||||||
if (hasVf) {
|
|
||||||
seasonTitle += " VF";
|
|
||||||
lang = "vf".toString();
|
|
||||||
}
|
|
||||||
if (hasVostfr) {
|
|
||||||
seasonTitle += " VOSTFR";
|
|
||||||
lang = "vo".toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.status = status;
|
|
||||||
anime.name = seasonTitle;
|
|
||||||
anime.imageUrl = imageUrl;
|
|
||||||
anime.link =
|
|
||||||
"/anime/${titleO.replaceAll(RegExp("[^A-Za-z0-9 ]"), "").replaceAll(" ", "-").toLowerCase()}?lang=$lang&s=$ind";
|
|
||||||
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MPages(animeList, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages animeSeachFetch(String res, String query) {
|
|
||||||
final statusList = [
|
|
||||||
{"EN COURS": 0, "TERMINÉ": 1},
|
|
||||||
];
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final jsonResList = json.decode(res);
|
|
||||||
for (var animeJson in jsonResList) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
|
|
||||||
final titleO = getMapValue(json.encode(animeJson), "titleO");
|
|
||||||
final titleAlt = getMapValue(
|
|
||||||
json.encode(animeJson),
|
|
||||||
"titles",
|
|
||||||
encode: true,
|
|
||||||
);
|
|
||||||
final containsEn = getMapValue(
|
|
||||||
titleAlt,
|
|
||||||
"en",
|
|
||||||
).toString().toLowerCase().contains(query.toLowerCase());
|
|
||||||
final containsEnJp = getMapValue(
|
|
||||||
titleAlt,
|
|
||||||
"en_jp",
|
|
||||||
).toString().toLowerCase().contains(query.toLowerCase());
|
|
||||||
final containsJaJp = getMapValue(
|
|
||||||
titleAlt,
|
|
||||||
"ja_jp",
|
|
||||||
).toString().toLowerCase().contains(query.toLowerCase());
|
|
||||||
final containsTitleO = titleO.toLowerCase().contains(query.toLowerCase());
|
|
||||||
|
|
||||||
if (containsEn || containsEnJp || containsJaJp || containsTitleO) {
|
|
||||||
final seasons = animeJson["saisons"];
|
|
||||||
List<bool> vostfrListName = [];
|
|
||||||
List<bool> vfListName = [];
|
|
||||||
for (var season in seasons) {
|
|
||||||
for (var episode in season["episodes"]) {
|
|
||||||
final lang = episode["lang"];
|
|
||||||
final vo = lang["vo"];
|
|
||||||
final vf = lang["vf"];
|
|
||||||
vostfrListName.add(vo["lecteurs"].isNotEmpty);
|
|
||||||
vfListName.add(vf["lecteurs"].isNotEmpty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String titleO = animeJson["titleO"];
|
|
||||||
final title = animeJson["title"];
|
|
||||||
final genre = animeJson["themes"];
|
|
||||||
final description = animeJson["description"];
|
|
||||||
final status = parseStatus(animeJson["status"], statusList);
|
|
||||||
final imageUrl = animeJson["affiche"];
|
|
||||||
|
|
||||||
bool hasVostfr = vostfrListName.contains(true);
|
|
||||||
bool hasVf = vfListName.contains(true);
|
|
||||||
if (hasVostfr || hasVf) {
|
|
||||||
for (int i = 0; i < seasons.length; i++) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
int ind = i + 1;
|
|
||||||
anime.genre = genre;
|
|
||||||
anime.description = description;
|
|
||||||
String seasonTitle = "".toString();
|
|
||||||
String lang = "";
|
|
||||||
if (title.isEmpty) {
|
|
||||||
seasonTitle = titleO;
|
|
||||||
} else {
|
|
||||||
seasonTitle = title;
|
|
||||||
}
|
|
||||||
if (seasons.length > 1) {
|
|
||||||
seasonTitle += " S$ind";
|
|
||||||
}
|
|
||||||
if (hasVf) {
|
|
||||||
seasonTitle += " VF";
|
|
||||||
lang = "vf".toString();
|
|
||||||
}
|
|
||||||
if (hasVostfr) {
|
|
||||||
seasonTitle += " VOSTFR";
|
|
||||||
lang = "vo".toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.status = status;
|
|
||||||
anime.name = seasonTitle;
|
|
||||||
anime.imageUrl = imageUrl;
|
|
||||||
anime.link =
|
|
||||||
"/anime/${titleO.replaceAll(RegExp("[^A-Za-z0-9 ]"), "").replaceAll(" ", "-").toLowerCase()}?lang=$lang&s=$ind";
|
|
||||||
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return MPages(animeList, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> dataBase() async {
|
|
||||||
return (await client.get(
|
|
||||||
Uri.parse("https://api.franime.fr/api/animes/"),
|
|
||||||
headers: {"Referer": "https://franime.fr/"},
|
|
||||||
)).body;
|
|
||||||
}
|
|
||||||
|
|
||||||
String databaseAnimeByTitleO(String res, String titleO) {
|
|
||||||
final datas = json.decode(res) as List<Map<String, dynamic>>;
|
|
||||||
for (var data in datas) {
|
|
||||||
String title = (data["titleO"] as String).replaceAll(
|
|
||||||
RegExp("[^A-Za-z0-9 ]"),
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
if (title.replaceAll(" ", "-").toLowerCase() == "${titleO}") {
|
|
||||||
return json.encode(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FrAnime main(MSource source) {
|
|
||||||
return FrAnime(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -1,19 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get franimeSource => _franimeSource;
|
|
||||||
const _franimeVersion = "0.0.8";
|
|
||||||
const _franimeSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/franime/franime.dart";
|
|
||||||
Source _franimeSource = Source(
|
|
||||||
name: "FrAnime",
|
|
||||||
baseUrl: "https://franime.fr",
|
|
||||||
apiUrl: "https://api.franime.fr",
|
|
||||||
lang: "fr",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/franime/icon.png",
|
|
||||||
sourceCodeUrl: _franimeSourceCodeUrl,
|
|
||||||
version: _franimeVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
isFullData: true,
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,507 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class OtakuFr extends MProvider {
|
|
||||||
OtakuFr({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => getPreferenceValue(source.id, "overrideBaseUrl");
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(Uri.parse("$baseUrl/en-cours/page/$page"))).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list"]/article/div/div/figure/a/@href',
|
|
||||||
);
|
|
||||||
final names = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list"]/article/div/div/figure/a/img/@title',
|
|
||||||
);
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list"]/article/div/div/figure/a/img/@src',
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final nextPage = xpath(res, '//a[@class="next page-link"]/@href');
|
|
||||||
return MPages(animeList, nextPage.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl/page/$page/"))).body;
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//*[@class="episode"]/div/a/@href');
|
|
||||||
final namess = xpath(res, '//*[@class="episode"]/div/a/text()');
|
|
||||||
List<String> names = [];
|
|
||||||
for (var name in namess) {
|
|
||||||
names.add(
|
|
||||||
regExp(
|
|
||||||
name,
|
|
||||||
r'(?<=\bS\d\s*|)\d{2}\s*(?=\b(Vostfr|vostfr|VF|Vf|vf|\(VF\)|\(vf\)|\(Vf\)|\(Vostfr\)\b))?',
|
|
||||||
'',
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
.replaceAll(' vostfr', '')
|
|
||||||
.replaceAll(' Vostfr', '')
|
|
||||||
.replaceAll(' VF', '')
|
|
||||||
.replaceAll(' Vf', '')
|
|
||||||
.replaceAll(' vf', '')
|
|
||||||
.replaceAll(' (VF)', '')
|
|
||||||
.replaceAll(' (vf)', '')
|
|
||||||
.replaceAll(' (vf)', '')
|
|
||||||
.replaceAll(' (Vf)', '')
|
|
||||||
.replaceAll(' (Vostfr)', ''),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final images = xpath(res, '//*[@class="episode"]/div/figure/a/img/@src');
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final nextPage = xpath(res, '//a[@class="next page-link"]/@href');
|
|
||||||
return MPages(animeList, nextPage.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String url = "";
|
|
||||||
if (query.isNotEmpty) {
|
|
||||||
url = "$baseUrl/toute-la-liste-affiches/page/$page/?q=$query";
|
|
||||||
} else {
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "GenreFilter") {
|
|
||||||
if (filter.state != 0) {
|
|
||||||
url = "$baseUrl/${filter.values[filter.state].value}page/$page";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "SubPageFilter") {
|
|
||||||
if (url.isEmpty) {
|
|
||||||
if (filter.state != 0) {
|
|
||||||
url = "$baseUrl/${filter.values[filter.state].value}page/$page";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list"]/article/div/div/figure/a/@href',
|
|
||||||
);
|
|
||||||
final names = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list"]/article/div/div/figure/a/img/@title',
|
|
||||||
);
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list"]/article/div/div/figure/a/img/@src',
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final nextPage = xpath(res, '//a[@class="next page-link"]/@href');
|
|
||||||
return MPages(animeList, nextPage.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"En cours": 0, "Terminé": 1},
|
|
||||||
];
|
|
||||||
String res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl${getUrlWithoutDomain(url)}"),
|
|
||||||
)).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final originalUrl = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="breadcrumb"]/li[@class="breadcrumb-item"][2]/a/@href',
|
|
||||||
);
|
|
||||||
if (originalUrl.isNotEmpty) {
|
|
||||||
res = (await client.get(Uri.parse(originalUrl.first))).body;
|
|
||||||
}
|
|
||||||
final description = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="episode fz-sm synop"]/p/text()',
|
|
||||||
);
|
|
||||||
if (description.isNotEmpty) {
|
|
||||||
anime.description = description.first.replaceAll("Synopsis:", "");
|
|
||||||
}
|
|
||||||
final status = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list-unstyled"]/li[contains(text(),"Statut")]/text()',
|
|
||||||
);
|
|
||||||
if (status.isNotEmpty) {
|
|
||||||
anime.status = parseStatus(
|
|
||||||
status.first.replaceAll("Statut: ", ""),
|
|
||||||
statusList,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.genre = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list-unstyled"]/li[contains(text(),"Genre")]/ul/li/a/text()',
|
|
||||||
);
|
|
||||||
|
|
||||||
final epUrls = xpath(res, '//*[@class="list-episodes list-group"]/a/@href');
|
|
||||||
final dates = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list-episodes list-group"]/a/span/text()',
|
|
||||||
);
|
|
||||||
final names = xpath(res, '//*[@class="list-episodes list-group"]/a/text()');
|
|
||||||
List<String> episodes = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < names.length; i++) {
|
|
||||||
final date = dates[i];
|
|
||||||
final name = names[i];
|
|
||||||
episodes.add(
|
|
||||||
"Episode ${regExp(name.replaceAll(date, ""), r".* (\d*) [VvfF]{1,1}", '', 1, 1)}",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final dateUploads = parseDates(dates, "dd MMMM yyyy", "fr");
|
|
||||||
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
for (var i = 0; i < episodes.length; i++) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = episodes[i];
|
|
||||||
episode.url = epUrls[i];
|
|
||||||
episode.dateUpload = dateUploads[i];
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.chapters = episodesList;
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl${getUrlWithoutDomain(url)}"),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
final servers = parseHtml(res).select("div.tab-content iframe[src]");
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final hosterSelection = preferenceHosterSelection(source.id);
|
|
||||||
for (var url in servers) {
|
|
||||||
final urll = url.getSrc != "about:blank" ? url.getSrc : url.getDataSrc;
|
|
||||||
final resServer =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(fixUrl(urll)),
|
|
||||||
headers: {"X-Requested-With": "XMLHttpRequest"},
|
|
||||||
)).body;
|
|
||||||
final serverUrl = fixUrl(
|
|
||||||
regExp(resServer, r"data-url='([^']+)'", '', 1, 1),
|
|
||||||
);
|
|
||||||
List<MVideo> a = [];
|
|
||||||
if (serverUrl.contains("https://streamwish") &&
|
|
||||||
hosterSelection.contains("Streamwish")) {
|
|
||||||
a = await streamWishExtractor(serverUrl, "StreamWish");
|
|
||||||
} else if (serverUrl.contains("https://doo") &&
|
|
||||||
hosterSelection.contains("Doodstream")) {
|
|
||||||
a = await doodExtractor(serverUrl);
|
|
||||||
} else if (containsVidBom(serverUrl) &&
|
|
||||||
hosterSelection.contains("VidBom")) {
|
|
||||||
a = await vidBomExtractor(serverUrl);
|
|
||||||
} else if (serverUrl.contains("https://ok.ru") &&
|
|
||||||
hosterSelection.contains("Okru")) {
|
|
||||||
a = await okruExtractor(serverUrl);
|
|
||||||
} else if (serverUrl.contains("upstream") &&
|
|
||||||
hosterSelection.contains("Upstream")) {
|
|
||||||
a = await upstreamExtractor(serverUrl);
|
|
||||||
} else if (serverUrl.contains("sendvid") &&
|
|
||||||
hosterSelection.contains("Sendvid")) {
|
|
||||||
a = await sendVidExtractorr(serverUrl, "");
|
|
||||||
} else if (serverUrl.contains("voe.sx") &&
|
|
||||||
hosterSelection.contains("Voe")) {
|
|
||||||
a = await voeExtractor(serverUrl, "");
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
String fixUrl(String url) {
|
|
||||||
return regExp(url, r"^(?:(?:https?:)?//|www\.)", 'https://', 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool containsVidBom(String url) {
|
|
||||||
url = url;
|
|
||||||
final list = ["vidbam", "vadbam", "vidbom", "vidbm"];
|
|
||||||
for (var n in list) {
|
|
||||||
if (url.contains(n)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
HeaderFilter("La recherche de texte ignore les filtres"),
|
|
||||||
SelectFilter("GenreFilter", "Genre", 0, [
|
|
||||||
SelectFilterOption("<Selectionner>", ""),
|
|
||||||
SelectFilterOption("Action", "/genre/action/"),
|
|
||||||
SelectFilterOption("Aventure", "/genre/aventure/"),
|
|
||||||
SelectFilterOption("Comedie", "/genre/comedie/"),
|
|
||||||
SelectFilterOption("Crime", "/genre/crime/"),
|
|
||||||
SelectFilterOption("Démons", "/genre/demons/"),
|
|
||||||
SelectFilterOption("Drame", "/genre/drame/"),
|
|
||||||
SelectFilterOption("Ecchi", "/genre/ecchi/"),
|
|
||||||
SelectFilterOption("Espace", "/genre/espace/"),
|
|
||||||
SelectFilterOption("Fantastique", "/genre/fantastique/"),
|
|
||||||
SelectFilterOption("Gore", "/genre/gore/"),
|
|
||||||
SelectFilterOption("Harem", "/genre/harem/"),
|
|
||||||
SelectFilterOption("Historique", "/genre/historique/"),
|
|
||||||
SelectFilterOption("Horreur", "/genre/horreur/"),
|
|
||||||
SelectFilterOption("Isekai", "/genre/isekai/"),
|
|
||||||
SelectFilterOption("Jeux", "/genre/jeu/"),
|
|
||||||
SelectFilterOption("L'école", "/genre/lecole/"),
|
|
||||||
SelectFilterOption("Magical girls", "/genre/magical-girls/"),
|
|
||||||
SelectFilterOption("Magie", "/genre/magie/"),
|
|
||||||
SelectFilterOption("Martial Arts", "/genre/martial-arts/"),
|
|
||||||
SelectFilterOption("Mecha", "/genre/mecha/"),
|
|
||||||
SelectFilterOption("Militaire", "/genre/militaire/"),
|
|
||||||
SelectFilterOption("Musique", "/genre/musique/"),
|
|
||||||
SelectFilterOption("Mysterieux", "/genre/mysterieux/"),
|
|
||||||
SelectFilterOption("Parodie", "/genre/Parodie/"),
|
|
||||||
SelectFilterOption("Police", "/genre/police/"),
|
|
||||||
SelectFilterOption("Psychologique", "/genre/psychologique/"),
|
|
||||||
SelectFilterOption("Romance", "/genre/romance/"),
|
|
||||||
SelectFilterOption("Samurai", "/genre/samurai/"),
|
|
||||||
SelectFilterOption("Sci-Fi", "/genre/sci-fi/"),
|
|
||||||
SelectFilterOption("Seinen", "/genre/seinen/"),
|
|
||||||
SelectFilterOption("Shoujo", "/genre/shoujo/"),
|
|
||||||
SelectFilterOption("Shoujo Ai", "/genre/shoujo-ai/"),
|
|
||||||
SelectFilterOption("Shounen", "/genre/shounen/"),
|
|
||||||
SelectFilterOption("Shounen Ai", "/genre/shounen-ai/"),
|
|
||||||
SelectFilterOption("Sport", "/genre/sport/"),
|
|
||||||
SelectFilterOption("Super Power", "/genre/super-power/"),
|
|
||||||
SelectFilterOption("Surnaturel", "/genre/surnaturel/"),
|
|
||||||
SelectFilterOption("Suspense", "/genre/suspense/"),
|
|
||||||
SelectFilterOption("Thriller", "/genre/thriller/"),
|
|
||||||
SelectFilterOption("Tranche de vie", "/genre/tranche-de-vie/"),
|
|
||||||
SelectFilterOption("Vampire", "/genre/vampire/"),
|
|
||||||
]),
|
|
||||||
SelectFilter("SubPageFilter", "Sous page", 0, [
|
|
||||||
SelectFilterOption("<Selectionner>", ""),
|
|
||||||
SelectFilterOption("Terminé", "/termine/"),
|
|
||||||
SelectFilterOption("Film", "/film/"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
EditTextPreference(
|
|
||||||
key: "overrideBaseUrl",
|
|
||||||
title: "Changer l'url de base",
|
|
||||||
summary: "",
|
|
||||||
value: "https://otakufr.cc",
|
|
||||||
dialogTitle: "Changer l'url de base",
|
|
||||||
dialogMessage: "",
|
|
||||||
text: "https://otakufr.cc",
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Qualité préférée",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 1,
|
|
||||||
entries: ["1080p", "720p", "480p", "360p"],
|
|
||||||
entryValues: ["1080", "720", "480", "360"],
|
|
||||||
),
|
|
||||||
MultiSelectListPreference(
|
|
||||||
key: "hoster_selection_",
|
|
||||||
title: "Enable/Disable Hosts",
|
|
||||||
summary: "",
|
|
||||||
entries: [
|
|
||||||
"Streamwish",
|
|
||||||
"Doodstream",
|
|
||||||
"Sendvid",
|
|
||||||
"VidBom",
|
|
||||||
"Okru",
|
|
||||||
"Voe",
|
|
||||||
"Sibnet",
|
|
||||||
"Upstream",
|
|
||||||
],
|
|
||||||
entryValues: [
|
|
||||||
"Streamwish",
|
|
||||||
"Doodstream",
|
|
||||||
"Sendvid",
|
|
||||||
"VidBom",
|
|
||||||
"Okru",
|
|
||||||
"Voe",
|
|
||||||
"Sibnet",
|
|
||||||
"Upstream",
|
|
||||||
],
|
|
||||||
values: [
|
|
||||||
"Streamwish",
|
|
||||||
"Doodstream",
|
|
||||||
"Sendvid",
|
|
||||||
"Vidbm",
|
|
||||||
"Okru",
|
|
||||||
"Voe",
|
|
||||||
"Sibnet",
|
|
||||||
"Upstream",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
|
||||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
|
||||||
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
if (a.quality.contains(quality)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> preferenceHosterSelection(int sourceId) {
|
|
||||||
return getPreferenceValue(sourceId, "hoster_selection_");
|
|
||||||
}
|
|
||||||
|
|
||||||
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");
|
|
||||||
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>> upstreamExtractor(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final js = xpath(res, '//script[contains(text(), "m3u8")]/text()');
|
|
||||||
if (js.isEmpty) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
final masterUrl = substringBefore(
|
|
||||||
substringAfter(unpackJs(js.first), "{file:\""),
|
|
||||||
"\"}",
|
|
||||||
);
|
|
||||||
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 = "Upstream - $quality";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OtakuFr main(MSource source) {
|
|
||||||
return OtakuFr(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get otakufr => _otakufr;
|
|
||||||
const otakufrVersion = "0.1.0";
|
|
||||||
const otakufrCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/otakufr/otakufr.dart";
|
|
||||||
Source _otakufr = Source(
|
|
||||||
name: "OtakuFr",
|
|
||||||
baseUrl: "https://otakufr.cc",
|
|
||||||
lang: "fr",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/fr/otakufr/icon.png",
|
|
||||||
sourceCodeUrl: otakufrCodeUrl,
|
|
||||||
version: otakufrVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
isFullData: false,
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get yomoviesSource => _yomoviesSource;
|
|
||||||
const _yomoviesVersion = "0.0.3";
|
|
||||||
const _yomoviesSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/hi/yomovies/yomovies.dart";
|
|
||||||
Source _yomoviesSource = Source(
|
|
||||||
name: "YoMovies",
|
|
||||||
baseUrl: "https://yomovies.boo",
|
|
||||||
lang: "hi",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/hi/yomovies/icon.png",
|
|
||||||
sourceCodeUrl: _yomoviesSourceCodeUrl,
|
|
||||||
version: _yomoviesVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class YoMovies extends MProvider {
|
|
||||||
YoMovies({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => getPreferenceValue(source.id, "overrideBaseUrl");
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get supportsLatest => false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
String pageNu = page == 1 ? "" : "page/$page/";
|
|
||||||
|
|
||||||
final res =
|
|
||||||
(await client.get(Uri.parse("$baseUrl/most-favorites/$pageNu"))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
return animeFromElement(
|
|
||||||
document.select("div.movies-list > div.ml-item"),
|
|
||||||
document.selectFirst("ul.pagination > li.active + li")?.getHref != null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
return MPages([], false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String url = "";
|
|
||||||
String pageNu = page == 1 ? "" : "/page/$page";
|
|
||||||
if (query.isNotEmpty) {
|
|
||||||
url = "$baseUrl$pageNu/?s=$query";
|
|
||||||
} else {
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type.isNotEmpty) {
|
|
||||||
final first = filter.values[filter.state].value;
|
|
||||||
if (first.isNotEmpty) {
|
|
||||||
url = first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url = "$baseUrl$url$pageNu";
|
|
||||||
}
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
return animeFromElement(
|
|
||||||
document.select("div.movies-list > div.ml-item"),
|
|
||||||
document.selectFirst("ul.pagination > li.active + li")?.getHref != null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
url = getUrlWithoutDomain(url);
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
MManga anime = MManga();
|
|
||||||
var infoElement = document.selectFirst("div.mvi-content");
|
|
||||||
anime.description = infoElement.selectFirst("p.f-desc")?.text ?? "";
|
|
||||||
|
|
||||||
anime.genre = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="mvici-left" and contains(text(),"Genre:")]/p/a/text()',
|
|
||||||
);
|
|
||||||
|
|
||||||
List<MChapter> episodeList = [];
|
|
||||||
final seasonListElements = document.select("div#seasons > div.tvseason");
|
|
||||||
if (seasonListElements.isEmpty) {
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = "Movie";
|
|
||||||
ep.url = url;
|
|
||||||
episodeList.add(ep);
|
|
||||||
} else {
|
|
||||||
for (var season in seasonListElements) {
|
|
||||||
var seasonText = season.selectFirst("div.les-title").text.trim();
|
|
||||||
for (var episode in season.select("div.les-content > a")) {
|
|
||||||
var epNumber = substringAfter(episode.text.trim(), "pisode ");
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = "$seasonText Ep. $epNumber";
|
|
||||||
ep.url = episode.getHref;
|
|
||||||
|
|
||||||
episodeList.add(ep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.chapters = episodeList.reversed.toList();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
url = getUrlWithoutDomain(url);
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final serverElements = document.select("div.movieplay > iframe");
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (var serverElement in serverElements) {
|
|
||||||
var url = serverElement.getSrc;
|
|
||||||
List<MVideo> a = [];
|
|
||||||
if (url.contains("minoplres")) {
|
|
||||||
a = await minoplresExtractor(url);
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
return sortVideos(videos, source.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
EditTextPreference(
|
|
||||||
key: "overrideBaseUrl",
|
|
||||||
title: "Override BaseUrl",
|
|
||||||
summary: "",
|
|
||||||
value: "https://yomovies.boo",
|
|
||||||
dialogTitle: "Override BaseUrl",
|
|
||||||
dialogMessage: "",
|
|
||||||
text: "https://yomovies.boo",
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Preferred quality",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["1080p", "720p", "480p", "360p"],
|
|
||||||
entryValues: ["1080", "720", "480", "360"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> minoplresExtractor(String url) async {
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
|
|
||||||
final res =
|
|
||||||
(await client.get(Uri.parse(url), headers: {"Referer": url})).body;
|
|
||||||
final script = xpath(res, '//script[contains(text(),"sources:")]/text()');
|
|
||||||
if (script.isEmpty) return [];
|
|
||||||
final masterUrl = substringBefore(
|
|
||||||
substringAfter(script.first, "file:\""),
|
|
||||||
'"',
|
|
||||||
);
|
|
||||||
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");
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "Minoplres - $quality";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages animeFromElement(List<MElement> elements, bool hasNextPage) {
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var element in elements) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = element.selectFirst("div.qtip-title").text;
|
|
||||||
anime.imageUrl =
|
|
||||||
element.selectFirst("img[data-original]")?.attr("data-original") ??
|
|
||||||
"";
|
|
||||||
anime.link = element.selectFirst("a[href]").getHref;
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, hasNextPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
|
||||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
|
||||||
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
if (a.quality.contains(quality)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
HeaderFilter(
|
|
||||||
"Note: Only one selection at a time works, and it ignores text search",
|
|
||||||
),
|
|
||||||
SeparatorFilter(),
|
|
||||||
SelectFilter("BollywoodFilter", "Bollywood", 0, [
|
|
||||||
SelectFilterOption("<select>", ""),
|
|
||||||
SelectFilterOption("Bollywood", "/genre/bollywood"),
|
|
||||||
SelectFilterOption("Trending", "/genre/top-rated"),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Bollywood (2024)",
|
|
||||||
"/account/?ptype=post&tax_category%5B%5D=bollywood&tax_release-year=2024&wpas=1",
|
|
||||||
),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Bollywood (2023)",
|
|
||||||
"/account/?ptype=post&tax_category%5B%5D=bollywood&tax_release-year=2023&wpas=1",
|
|
||||||
),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Bollywood (2022)",
|
|
||||||
"/account/?ptype=post&tax_category%5B%5D=bollywood&tax_release-year=2022&wpas=1",
|
|
||||||
),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Bollywood (2021)",
|
|
||||||
"/account/?ptype=post&tax_category%5B%5D=bollywood&tax_release-year=2021&wpas=1",
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
SelectFilter("DualAudioFilter", "Dual Audio", 0, [
|
|
||||||
SelectFilterOption("<select>", ""),
|
|
||||||
SelectFilterOption("Dual Audio", "/genre/dual-audio"),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Hollywood Dubbed",
|
|
||||||
"/account/?ptype=post&tax_category%5B%5D=dual-audio&wpas=1",
|
|
||||||
),
|
|
||||||
SelectFilterOption(
|
|
||||||
"South Dubbed",
|
|
||||||
"/account/?ptype=post&tax_category%5B%5D=dual-audio&tax_category%5B%5D=south-special&wpas=1",
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
SelectFilter("HollywoodFilter", "Hollywood", 0, [
|
|
||||||
SelectFilterOption("<select>", ""),
|
|
||||||
SelectFilterOption("Hollywood", "/genre/hollywood"),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Hollywood (2023)",
|
|
||||||
"/account/?ptype=post&tax_category%5B%5D=hollywood&tax_release-year=2023&wpas=1",
|
|
||||||
),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Hollywood (2022)",
|
|
||||||
"/account/?ptype=post&tax_category%5B%5D=hollywood&tax_release-year=2022&wpas=1",
|
|
||||||
),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Hollywood (2021)",
|
|
||||||
"/account/?ptype=post&tax_category%5B%5D=hollywood&tax_release-year=2021&wpas=1",
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
SelectFilter("EnglishSeriesFilter", "Hindi Series", 0, [
|
|
||||||
SelectFilterOption("<select>", ""),
|
|
||||||
SelectFilterOption("English Series", "/series"),
|
|
||||||
]),
|
|
||||||
SelectFilter("HindiSeriesFilter", "English Series", 0, [
|
|
||||||
SelectFilterOption("<select>", ""),
|
|
||||||
SelectFilterOption("Hindi Series", "/genre/web-series"),
|
|
||||||
SelectFilterOption("Netflix", "/director/netflix"),
|
|
||||||
SelectFilterOption("Amazon", "/director/amazon-prime"),
|
|
||||||
SelectFilterOption("Altbalaji", "/director/altbalaji"),
|
|
||||||
SelectFilterOption("Zee5", "/director/zee5"),
|
|
||||||
SelectFilterOption("Voot", "/director/voot-originals"),
|
|
||||||
SelectFilterOption("Mx Player", "/director/mx-player"),
|
|
||||||
SelectFilterOption("Hotstar", "/director/hotstar"),
|
|
||||||
SelectFilterOption("Viu", "/director/viu-originals"),
|
|
||||||
SelectFilterOption("Sony Liv", "/director/sonyliv-original"),
|
|
||||||
]),
|
|
||||||
SelectFilter("GenreFilter", "Genre", 0, [
|
|
||||||
SelectFilterOption("<select>", ""),
|
|
||||||
SelectFilterOption("Action", "/genre/action"),
|
|
||||||
SelectFilterOption("Adventure", "/genre/adventure"),
|
|
||||||
SelectFilterOption("Animation", "/genre/animation"),
|
|
||||||
SelectFilterOption("Biography", "/genre/biography"),
|
|
||||||
SelectFilterOption("Comedy", "/genre/comedy"),
|
|
||||||
SelectFilterOption("Crime", "/genre/crime"),
|
|
||||||
SelectFilterOption("Drama", "/genre/drama"),
|
|
||||||
SelectFilterOption("Music", "/genre/music"),
|
|
||||||
SelectFilterOption("Mystery", "/genre/mystery"),
|
|
||||||
SelectFilterOption("Family", "/genre/family"),
|
|
||||||
SelectFilterOption("Fantasy", "/genre/fantasy"),
|
|
||||||
SelectFilterOption("Horror", "/genre/horror"),
|
|
||||||
SelectFilterOption("History", "/genre/history"),
|
|
||||||
SelectFilterOption("Romance", "/genre/romantic"),
|
|
||||||
SelectFilterOption("Science Fiction", "/genre/science-fiction"),
|
|
||||||
SelectFilterOption("Thriller", "/genre/thriller"),
|
|
||||||
SelectFilterOption("War", "/genre/war"),
|
|
||||||
]),
|
|
||||||
SelectFilter("ExtraMoviesFilter", "ExtraMovies", 0, [
|
|
||||||
SelectFilterOption("<select>", ""),
|
|
||||||
SelectFilterOption("ExtraMovies", "/genre/south-special"),
|
|
||||||
SelectFilterOption("Bengali", "/genre/bengali"),
|
|
||||||
SelectFilterOption("Marathi", "/genre/marathi"),
|
|
||||||
SelectFilterOption("Gujarati", "/genre/gujarati"),
|
|
||||||
SelectFilterOption("Punjabi", "/genre/punjabi"),
|
|
||||||
SelectFilterOption("Tamil", "/genre/tamil"),
|
|
||||||
SelectFilterOption("Telugu", "/genre/telugu"),
|
|
||||||
SelectFilterOption("Malayalam", "/genre/malayalam"),
|
|
||||||
SelectFilterOption("Kannada", "/genre/kannada"),
|
|
||||||
SelectFilterOption("Pakistani", "/genre/pakistani"),
|
|
||||||
]),
|
|
||||||
SelectFilter("EroticFilter", "Erotic", 0, [
|
|
||||||
SelectFilterOption("<select>", ""),
|
|
||||||
SelectFilterOption("Erotic", "/genre/erotic-movies"),
|
|
||||||
]),
|
|
||||||
SelectFilter("HotSeriesFilter", "Hot Series", 0, [
|
|
||||||
SelectFilterOption("<select>", ""),
|
|
||||||
SelectFilterOption("Hot Series", "/genre/tv-shows"),
|
|
||||||
SelectFilterOption("Uncut", "/?s=uncut"),
|
|
||||||
SelectFilterOption("Fliz Movies", "/director/fliz-movies"),
|
|
||||||
SelectFilterOption("Nuefliks", "/director/nuefliks-exclusive"),
|
|
||||||
SelectFilterOption("Hotshots", "/director/hotshots"),
|
|
||||||
SelectFilterOption("Ullu Originals", "/?s=ullu"),
|
|
||||||
SelectFilterOption("Kooku", "/director/kooku-originals"),
|
|
||||||
SelectFilterOption("Gupchup", "/director/gupchup-exclusive"),
|
|
||||||
SelectFilterOption("Feneomovies", "/director/feneomovies"),
|
|
||||||
SelectFilterOption("Cinemadosti", "/director/cinemadosti"),
|
|
||||||
SelectFilterOption("Primeflix", "/director/primeflix"),
|
|
||||||
SelectFilterOption("Gemplex", "/director/gemplex"),
|
|
||||||
SelectFilterOption("Rabbit", "/director/rabbit-original"),
|
|
||||||
SelectFilterOption("HotMasti", "/director/hotmasti-originals"),
|
|
||||||
SelectFilterOption("BoomMovies", "/director/boommovies-original"),
|
|
||||||
SelectFilterOption("CliffMovies", "/director/cliff-movies"),
|
|
||||||
SelectFilterOption("MastiPrime", "/director/masti-prime-originals"),
|
|
||||||
SelectFilterOption("Ek Night Show", "/director/ek-night-show"),
|
|
||||||
SelectFilterOption("Flixsksmovies", "/director/flixsksmovies"),
|
|
||||||
SelectFilterOption("Lootlo", "/director/lootlo-original"),
|
|
||||||
SelectFilterOption("Hootzy", "/director/hootzy-channel"),
|
|
||||||
SelectFilterOption("Balloons", "/director/balloons-originals"),
|
|
||||||
SelectFilterOption(
|
|
||||||
"Big Movie Zoo",
|
|
||||||
"/director/big-movie-zoo-originals",
|
|
||||||
),
|
|
||||||
SelectFilterOption("Bambooflix", "/director/bambooflix"),
|
|
||||||
SelectFilterOption("Piliflix", "/director/piliflix-originals"),
|
|
||||||
SelectFilterOption("11upmovies", "/director/11upmovies-originals"),
|
|
||||||
SelectFilterOption("Eightshots", "/director/eightshots-originals"),
|
|
||||||
SelectFilterOption(
|
|
||||||
"I-Entertainment",
|
|
||||||
"/director/i-entertainment-exclusive",
|
|
||||||
),
|
|
||||||
SelectFilterOption("Hotprime", "/director/hotprime-originals"),
|
|
||||||
SelectFilterOption("BananaPrime", "/director/banana-prime"),
|
|
||||||
SelectFilterOption("HotHitFilms", "/director/hothitfilms"),
|
|
||||||
SelectFilterOption("Chikooflix", "/director/chikooflix-originals"),
|
|
||||||
SelectFilterOption("Glamheart", "/?s=glamheart"),
|
|
||||||
SelectFilterOption("Worldprime", "/director/worldprime-originals"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
YoMovies main(MSource source) {
|
|
||||||
return YoMovies(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,200 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class NimeGami extends MProvider {
|
|
||||||
NimeGami({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(Uri.parse("${source.baseUrl}/page/$page"))).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//div[@class="wrapper-2-a"]/article/a/@href');
|
|
||||||
final names = xpath(res, '//div[@class="wrapper-2-a"]/article/a/@title');
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="wrapper-2-a"]/article/a/div/img/@src',
|
|
||||||
);
|
|
||||||
|
|
||||||
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, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(Uri.parse("${source.baseUrl}/page/$page"))).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//div[@class="post-article"]/article/div/a/@href');
|
|
||||||
final names = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="post-article"]/article/div/a/@title',
|
|
||||||
);
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="post-article"]/article/div/a/img/@src',
|
|
||||||
);
|
|
||||||
|
|
||||||
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, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/page/$page/?s=$query&post_type=post"),
|
|
||||||
)).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//div[@class="archive-a"]/article/div/a/@href');
|
|
||||||
final names = xpath(res, '//div[@class="archive-a"]/article/h2/a/@title');
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="archive-a"]/article/div/a/img/@src',
|
|
||||||
);
|
|
||||||
|
|
||||||
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, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final description = xpath(res, '//*[@id="Sinopsis"]/p/text()');
|
|
||||||
if (description.isNotEmpty) {
|
|
||||||
anime.description = description.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
final author = xpath(res, '//tbody/tr[5]/td[2]/text()');
|
|
||||||
if (author.isNotEmpty) {
|
|
||||||
anime.author = author.first;
|
|
||||||
}
|
|
||||||
anime.genre = xpath(res, '//tr/td[@class="info_a"]/a/text()');
|
|
||||||
final epUrls =
|
|
||||||
xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="list_eps_stream"]/li/@data',
|
|
||||||
).reversed.toList();
|
|
||||||
final epNums =
|
|
||||||
xpath(res, '//div[@class="list_eps_stream"]/li/@id').reversed.toList();
|
|
||||||
final names =
|
|
||||||
xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="list_eps_stream"]/li/text()',
|
|
||||||
).reversed.toList();
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
for (var i = 0; i < epUrls.length; i++) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = names[i];
|
|
||||||
episode.url = json.encode({
|
|
||||||
"episodeIndex": int.parse(substringAfterLast(epNums[i], '_')),
|
|
||||||
'urls': json.decode(utf8.decode(base64Url.decode(epUrls[i]))),
|
|
||||||
});
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
anime.chapters = episodesList;
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final resJson = json.decode(url);
|
|
||||||
final urls = resJson["urls"];
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
List<MVideo> a = [];
|
|
||||||
for (var data in urls) {
|
|
||||||
final quality = data["format"];
|
|
||||||
for (var url in data["url"]) {
|
|
||||||
a = await extractVideos(quality, url);
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> extractVideos(String quality, String url) async {
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
if (url.contains("video.nimegami.id")) {
|
|
||||||
final realUrl = utf8.decode(
|
|
||||||
base64Url.decode(substringBefore(substringAfter(url, "url="), "&")),
|
|
||||||
);
|
|
||||||
final a = await extractHXFileVideos(realUrl, quality);
|
|
||||||
videos.addAll(a);
|
|
||||||
} else if (url.contains("berkasdrive") || url.contains("drive.nimegami")) {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final source = xpath(res, '//source/@src');
|
|
||||||
if (source.isNotEmpty) {
|
|
||||||
videos.add(toVideo(source.first, "Berkasdrive - $quality"));
|
|
||||||
}
|
|
||||||
} else if (url.contains("hxfile.co")) {
|
|
||||||
final a = await extractHXFileVideos(url, quality);
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> extractHXFileVideos(String url, String quality) async {
|
|
||||||
if (!url.contains("embed-")) {
|
|
||||||
url = url.replaceAll(".co/", ".co/embed-") + ".html";
|
|
||||||
}
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final script = xpath(
|
|
||||||
res,
|
|
||||||
'//script[contains(text(), "eval") and contains(text(), "p,a,c,k,e,d")]/text()',
|
|
||||||
);
|
|
||||||
if (script.isNotEmpty) {
|
|
||||||
final videoUrl = substringBefore(
|
|
||||||
substringAfter(
|
|
||||||
substringAfter(unpackJs(script.first), "sources:[", ""),
|
|
||||||
"file\":\"",
|
|
||||||
"",
|
|
||||||
),
|
|
||||||
'"',
|
|
||||||
);
|
|
||||||
if (videoUrl.isNotEmpty) {
|
|
||||||
return [toVideo(videoUrl, "HXFile - $quality")];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
MVideo toVideo(String videoUrl, String quality) {
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = quality
|
|
||||||
..subtitles = [];
|
|
||||||
|
|
||||||
return video;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NimeGami main(MSource source) {
|
|
||||||
return NimeGami(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get nimegami => _nimegami;
|
|
||||||
const _nimegamiVersion = "0.0.6";
|
|
||||||
const _nimegamiCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/id/nimegami/nimegami.dart";
|
|
||||||
Source _nimegami = Source(
|
|
||||||
name: "NimeGami",
|
|
||||||
baseUrl: "https://nimegami.id",
|
|
||||||
lang: "id",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/id/nimegami/icon.png",
|
|
||||||
sourceCodeUrl: _nimegamiCodeUrl,
|
|
||||||
version: _nimegamiVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 14 KiB |
@@ -1,172 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class OploVerz extends MProvider {
|
|
||||||
OploVerz({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/anime-list/page/$page/?order=popular"),
|
|
||||||
)).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/anime-list/page/$page/?order=latest"),
|
|
||||||
)).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/anime-list/page/$page/?title=$query"),
|
|
||||||
)).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"ongoing": 0, "completed": 1},
|
|
||||||
];
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final status = xpath(res, '//*[@class="alternati"]/span[2]/text()');
|
|
||||||
if (status.isNotEmpty) {
|
|
||||||
anime.status = parseStatus(status.first, statusList);
|
|
||||||
}
|
|
||||||
anime.description = xpath(res, '//*[@class="desc"]/div/text()').first;
|
|
||||||
|
|
||||||
anime.genre = xpath(res, '//*[@class="genre-info"]/a/text()');
|
|
||||||
final epUrls = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="epsleft")]/span[@class="lchx"]/a/@href',
|
|
||||||
);
|
|
||||||
final names = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="epsleft")]/span[@class="lchx"]/a/text()',
|
|
||||||
);
|
|
||||||
final dates = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="epsleft")]/span[@class="date"]/text()',
|
|
||||||
);
|
|
||||||
final dateUploads = parseDates(dates, "dd/MM/yyyy", "id");
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
for (var i = 0; i < epUrls.length; i++) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = names[i];
|
|
||||||
episode.dateUpload = dateUploads[i];
|
|
||||||
episode.url = epUrls[i];
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.chapters = episodesList;
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final dataPost =
|
|
||||||
xpath(
|
|
||||||
res,
|
|
||||||
'//*[@id="server"]/ul/li/div[contains(@id,"player-option")]/@data-post',
|
|
||||||
).first;
|
|
||||||
final dataNume =
|
|
||||||
xpath(
|
|
||||||
res,
|
|
||||||
'//*[@id="server"]/ul/li/div[contains(@id,"player-option")]/@data-nume',
|
|
||||||
).first;
|
|
||||||
final dataType =
|
|
||||||
xpath(
|
|
||||||
res,
|
|
||||||
'//*[@id="server"]/ul/li/div[contains(@id,"player-option")]/@data-type',
|
|
||||||
).first;
|
|
||||||
|
|
||||||
final ress =
|
|
||||||
(await client.post(
|
|
||||||
Uri.parse("${source.baseUrl}/wp-admin/admin-ajax.php"),
|
|
||||||
headers: {"_": "_"},
|
|
||||||
body: {
|
|
||||||
"action": "player_ajax",
|
|
||||||
"post": dataPost,
|
|
||||||
"nume": dataNume,
|
|
||||||
"type": dataType,
|
|
||||||
},
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
final playerLink =
|
|
||||||
xpath(ress, '//iframe[@class="playeriframe"]/@src').first;
|
|
||||||
final resPlayer = (await client.get(Uri.parse(playerLink))).body;
|
|
||||||
var resJson = substringBefore(substringAfter(resPlayer, "= "), "<");
|
|
||||||
var streams = json.decode(resJson)["streams"] as List<Map<String, dynamic>>;
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (var stream in streams) {
|
|
||||||
String videoUrl = stream["play_url"];
|
|
||||||
final quality = getQuality(stream["format_id"]);
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = quality;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getQuality(int formatId) {
|
|
||||||
if (formatId == 18) {
|
|
||||||
return "Google - 360p";
|
|
||||||
} else if (formatId == 22) {
|
|
||||||
return "Google - 720p";
|
|
||||||
}
|
|
||||||
return "Unknown Resolution";
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages parseAnimeList(String res) {
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(res, '//div[@class="relat"]/article/div/div/a/@href');
|
|
||||||
final names = xpath(res, '//div[@class="relat"]/article/div/div/a/@title');
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="relat"]/article/div/div/a/div/img/@src',
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final pages = xpath(res, '//div[@class="pagination"]/a/@href');
|
|
||||||
final pageNumberCurrent = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="pagination"]/span[@class="page-numbers current"]/text()',
|
|
||||||
);
|
|
||||||
|
|
||||||
bool hasNextPage = true;
|
|
||||||
if (pageNumberCurrent.isNotEmpty && pages.isNotEmpty) {
|
|
||||||
hasNextPage = !(pages.length == int.parse(pageNumberCurrent.first));
|
|
||||||
}
|
|
||||||
return MPages(animeList, hasNextPage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OploVerz main(MSource source) {
|
|
||||||
return OploVerz(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get oploverz => _oploverz;
|
|
||||||
const _oploverzVersion = "0.0.55";
|
|
||||||
const _oploverzCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/id/oploverz/oploverz.dart";
|
|
||||||
Source _oploverz = Source(
|
|
||||||
name: "Oploverz",
|
|
||||||
baseUrl: "https://oploverz.gold",
|
|
||||||
lang: "id",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/id/oploverz/icon.png",
|
|
||||||
sourceCodeUrl: _oploverzCodeUrl,
|
|
||||||
version: _oploverzVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
|
Before Width: | Height: | Size: 6.3 KiB |
@@ -1,261 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class OtakuDesu extends MProvider {
|
|
||||||
OtakuDesu({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => getPreferenceValue(source.id, "overrideBaseUrl");
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl/complete-anime/page/$page"),
|
|
||||||
)).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(Uri.parse("$baseUrl/ongoing-anime/page/$page"))).body;
|
|
||||||
return parseAnimeList(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl/?s=$query&post_type=anime"),
|
|
||||||
)).body;
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final images = xpath(res, '//ul[@class="chivsrc"]/li/img/@src');
|
|
||||||
final names = xpath(res, '//ul[@class="chivsrc"]/li/h2/a/text()');
|
|
||||||
final urls = xpath(res, '//ul[@class="chivsrc"]/li/h2/a/@href');
|
|
||||||
|
|
||||||
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, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"Ongoing": 0, "Completed": 1},
|
|
||||||
];
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final status = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="infozingle"]/p[contains(text(), "Status")]/text()',
|
|
||||||
);
|
|
||||||
if (status.isNotEmpty) {
|
|
||||||
anime.status = parseStatus(status.first.split(':').last, statusList);
|
|
||||||
}
|
|
||||||
final description = xpath(res, '//*[@class="sinopc"]/text()');
|
|
||||||
if (description.isNotEmpty) {
|
|
||||||
anime.description = description.first;
|
|
||||||
}
|
|
||||||
|
|
||||||
final genre = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="infozingle"]/p[contains(text(), "Genre")]/text()',
|
|
||||||
);
|
|
||||||
if (genre.isNotEmpty) {
|
|
||||||
anime.genre = genre.first.split(':').last.split(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
final epUrls = xpath(res, '//div[@class="episodelist"]/ul/li/span/a/@href');
|
|
||||||
final names = xpath(res, '//div[@class="episodelist"]/ul/li/span/a/text()');
|
|
||||||
|
|
||||||
final dates = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="episodelist"]/ul/li/span[@class="zeebr"]/text()',
|
|
||||||
);
|
|
||||||
final dateUploads = parseDates(dates, "d MMMM,yyyy", "id");
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
for (var i = 1; i < epUrls.length; i++) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = names[i];
|
|
||||||
episode.dateUpload = dateUploads[i];
|
|
||||||
episode.url = epUrls[i];
|
|
||||||
episodesList.add(episode);
|
|
||||||
}
|
|
||||||
anime.chapters = episodesList;
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final script =
|
|
||||||
xpath(res, '//script[contains(text(), "{action:")]/text()').first;
|
|
||||||
final nonceAction = substringBefore(
|
|
||||||
substringAfter(script, "{action:\""),
|
|
||||||
'"',
|
|
||||||
);
|
|
||||||
final action = substringBefore(substringAfter(script, "action:\""), '"');
|
|
||||||
|
|
||||||
final resNonceAction =
|
|
||||||
(await client.post(
|
|
||||||
Uri.parse("$baseUrl/wp-admin/admin-ajax.php"),
|
|
||||||
headers: {"_": "_"},
|
|
||||||
body: {"action": nonceAction},
|
|
||||||
)).body;
|
|
||||||
final nonce = substringBefore(substringAfter(resNonceAction, ":\""), '"');
|
|
||||||
final mirrorstream = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="mirrorstream"]/ul/li/a/@data-content',
|
|
||||||
);
|
|
||||||
for (var stream in mirrorstream) {
|
|
||||||
List<MVideo> a = [];
|
|
||||||
final decodedData = json.decode(utf8.decode(base64Url.decode(stream)));
|
|
||||||
final q = decodedData["q"];
|
|
||||||
final id = decodedData["id"];
|
|
||||||
final i = decodedData["i"];
|
|
||||||
|
|
||||||
final res =
|
|
||||||
(await client.post(
|
|
||||||
Uri.parse("$baseUrl/wp-admin/admin-ajax.php"),
|
|
||||||
headers: {"_": "_"},
|
|
||||||
body: {"i": i, "id": id, "q": q, "nonce": nonce, "action": action},
|
|
||||||
)).body;
|
|
||||||
final resJson = json.decode(res);
|
|
||||||
final html = utf8.decode(base64Url.decode(resJson["data"]));
|
|
||||||
String url = xpath(html, '//iframe/@src').first;
|
|
||||||
if (url.contains("yourupload")) {
|
|
||||||
final id = substringBefore(substringAfter(url, "id="), "&");
|
|
||||||
url = "https://yourupload.com/embed/$id";
|
|
||||||
a = await yourUploadExtractor(url, null, "YourUpload - $q", null);
|
|
||||||
} else if (url.contains("filelions")) {
|
|
||||||
a = await streamWishExtractor(url, "FileLions");
|
|
||||||
} else if (url.contains("desustream")) {
|
|
||||||
final response = (await Client().get(Uri.parse(url)));
|
|
||||||
final res = response.body;
|
|
||||||
final script =
|
|
||||||
xpath(res, '//script[contains(text(), "file")]/text()').first;
|
|
||||||
final regex = RegExp(r'file:"(https?://[^"]+)"');
|
|
||||||
final match = regex.firstMatch(script);
|
|
||||||
final videoUrl = match.group(1);
|
|
||||||
if (videoUrl.endsWith(".mp4")) {
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "DesuStream - $q";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
} else if (url.contains("mp4upload")) {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final script =
|
|
||||||
xpath(res, '//script[contains(text(), "player.src")]/text()').first;
|
|
||||||
final videoUrl = substringBefore(
|
|
||||||
substringAfter(script, "src: \""),
|
|
||||||
'"',
|
|
||||||
);
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "Mp4upload - $q";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortVideos(videos);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos) {
|
|
||||||
String quality = getPreferenceValue(source.id, "preferred_quality");
|
|
||||||
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
if (a.quality.contains(quality)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages parseAnimeList(String res) {
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
final urls = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="detpost"]/div[@class="thumb"]/a/@href',
|
|
||||||
);
|
|
||||||
final names = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="detpost"]/div[@class="thumb"]/a/div[@class="thumbz"]/h2/text()',
|
|
||||||
);
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="detpost"]/div[@class="thumb"]/a/div[@class="thumbz"]/img/@src',
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
final pages = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="pagenavix"]/a[@class="next page-numbers"]/@href',
|
|
||||||
);
|
|
||||||
return MPages(animeList, pages.isNotEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
EditTextPreference(
|
|
||||||
key: "overrideBaseUrl",
|
|
||||||
title: "Override BaseUrl",
|
|
||||||
summary: "",
|
|
||||||
value: "https://otakudesu.cloud",
|
|
||||||
dialogTitle: "Override BaseUrl",
|
|
||||||
dialogMessage: "",
|
|
||||||
text: "https://otakudesu.cloud",
|
|
||||||
),
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Preferred quality",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 1,
|
|
||||||
entries: ["1080p", "720p", "480p", "360p"],
|
|
||||||
entryValues: ["1080", "720", "480", "360"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OtakuDesu main(MSource source) {
|
|
||||||
return OtakuDesu(source: source);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get otakudesu => _otakudesu;
|
|
||||||
const _otakudesuVersion = "0.0.6";
|
|
||||||
const _otakudesuCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/id/otakudesu/otakudesu.dart";
|
|
||||||
Source _otakudesu = Source(
|
|
||||||
name: "OtakuDesu",
|
|
||||||
baseUrl: "https://otakudesu.cloud",
|
|
||||||
lang: "id",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/id/otakudesu/icon.png",
|
|
||||||
sourceCodeUrl: _otakudesuCodeUrl,
|
|
||||||
version: _otakudesuVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,394 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class AnimeSaturn extends MProvider {
|
|
||||||
AnimeSaturn({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/animeincorso?page=$page"),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
|
|
||||||
final urls = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="sebox"]/div[@class="msebox"]/div[@class="headsebox"]/div[@class="tisebox"]/h2/a/@href',
|
|
||||||
);
|
|
||||||
|
|
||||||
final names = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="sebox"]/div[@class="msebox"]/div[@class="headsebox"]/div[@class="tisebox"]/h2/a/text()',
|
|
||||||
);
|
|
||||||
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="sebox"]/div[@class="msebox"]/div[@class="bigsebox"]/div/img[@class="attachment-post-thumbnail size-post-thumbnail wp-post-image"]/@src',
|
|
||||||
);
|
|
||||||
|
|
||||||
for (var i = 0; i < names.length; i++) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = formatTitle(names[i]);
|
|
||||||
anime.imageUrl = images[i];
|
|
||||||
anime.link = urls[i];
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("${source.baseUrl}/newest?page=$page"),
|
|
||||||
)).body;
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
|
|
||||||
final urls = xpath(res, '//*[@class="card mb-4 shadow-sm"]/a/@href');
|
|
||||||
|
|
||||||
final names = xpath(res, '//*[@class="card mb-4 shadow-sm"]/a/@title');
|
|
||||||
|
|
||||||
final images = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="card mb-4 shadow-sm"]/a/img[@class="new-anime"]/@src',
|
|
||||||
);
|
|
||||||
|
|
||||||
for (var i = 0; i < names.length; i++) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = formatTitle(names[i]);
|
|
||||||
anime.imageUrl = images[i];
|
|
||||||
anime.link = urls[i];
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String url = "";
|
|
||||||
|
|
||||||
if (query.isNotEmpty) {
|
|
||||||
url = "${source.baseUrl}/animelist?search=$query";
|
|
||||||
} else {
|
|
||||||
url = "${source.baseUrl}/filter?";
|
|
||||||
int variantgenre = 0;
|
|
||||||
int variantstate = 0;
|
|
||||||
int variantyear = 0;
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "GenreFilter") {
|
|
||||||
final genre = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
if (genre.isNotEmpty) {
|
|
||||||
for (var st in genre) {
|
|
||||||
url += "&categories%5B${variantgenre}%5D=${st.value}";
|
|
||||||
variantgenre++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filter.type == "YearList") {
|
|
||||||
final years = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
if (years.isNotEmpty) {
|
|
||||||
for (var st in years) {
|
|
||||||
url += "&years%5B${variantyear}%5D=${st.value}";
|
|
||||||
variantyear++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filter.type == "StateList") {
|
|
||||||
final states = (filter.state as List).where((e) => e.state).toList();
|
|
||||||
if (states.isNotEmpty) {
|
|
||||||
for (var st in states) {
|
|
||||||
url += "&states%5B${variantstate}%5D=${st.value}";
|
|
||||||
variantstate++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (filter.type == "LangList") {
|
|
||||||
final lang = filter.values[filter.state].value;
|
|
||||||
if (lang.isNotEmpty) {
|
|
||||||
url += "&language%5B0%5D=$lang";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url += "&page=$page";
|
|
||||||
}
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
List<String> urls = [];
|
|
||||||
List<String> names = [];
|
|
||||||
List<String> images = [];
|
|
||||||
if (query.isNotEmpty) {
|
|
||||||
urls = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list-group"]/li[@class="list-group-item bg-dark-as-box-shadow"]/div[@class="item-archivio"]/div[@class="info-archivio"]/h3/a[@class="badge badge-archivio badge-light"]/@href',
|
|
||||||
);
|
|
||||||
|
|
||||||
names = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list-group"]/li[@class="list-group-item bg-dark-as-box-shadow"]/div[@class="item-archivio"]/div[@class="info-archivio"]/h3/a[@class="badge badge-archivio badge-light"]/text()',
|
|
||||||
);
|
|
||||||
|
|
||||||
images = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="list-group"]/li[@class="list-group-item bg-dark-as-box-shadow"]/div[@class="item-archivio"]/a/img/@src',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
urls = xpath(res, '//*[@class="card mb-4 shadow-sm"]/a/@href');
|
|
||||||
|
|
||||||
names = xpath(res, '//*[@class="card mb-4 shadow-sm"]/a/text()');
|
|
||||||
|
|
||||||
images = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="card mb-4 shadow-sm"]/a/img[@class="new-anime"]/@src',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < names.length; i++) {
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.name = formatTitle(names[i]);
|
|
||||||
anime.imageUrl = images[i];
|
|
||||||
anime.link = urls[i];
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, query.isEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"In corso": 0, "Finito": 1},
|
|
||||||
];
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
MManga anime = MManga();
|
|
||||||
final detailsList = xpath(
|
|
||||||
res,
|
|
||||||
'//div[@class="container shadow rounded bg-dark-as-box mb-3 p-3 w-100 text-white"]/text()',
|
|
||||||
);
|
|
||||||
if (detailsList.isNotEmpty) {
|
|
||||||
final details = detailsList.first;
|
|
||||||
|
|
||||||
anime.status = parseStatus(
|
|
||||||
details.substring(
|
|
||||||
details.indexOf("Stato:") + 6,
|
|
||||||
details.indexOf("Data di uscita:"),
|
|
||||||
),
|
|
||||||
statusList,
|
|
||||||
);
|
|
||||||
anime.author = details.substring(7, details.indexOf("Stato:"));
|
|
||||||
}
|
|
||||||
|
|
||||||
final description = xpath(res, '//*[@id="shown-trama"]/text()');
|
|
||||||
final descriptionFull = xpath(res, '//*[@id="full-trama"]/text()');
|
|
||||||
if (description.isNotEmpty) {
|
|
||||||
anime.description = description.first;
|
|
||||||
} else {
|
|
||||||
anime.description = "";
|
|
||||||
}
|
|
||||||
if (descriptionFull.isNotEmpty) {
|
|
||||||
if (descriptionFull.first.length > anime.description.length) {
|
|
||||||
anime.description = descriptionFull.first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anime.genre = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="container shadow rounded bg-dark-as-box mb-3 p-3 w-100"]/a/text()',
|
|
||||||
);
|
|
||||||
|
|
||||||
final epUrls = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="btn-group episodes-button episodi-link-button"]/a/@href',
|
|
||||||
);
|
|
||||||
|
|
||||||
final titles = xpath(
|
|
||||||
res,
|
|
||||||
'//*[@class="btn-group episodes-button episodi-link-button"]/a/text()',
|
|
||||||
);
|
|
||||||
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
for (var i = 0; i < epUrls.length; i++) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = titles[i];
|
|
||||||
episode.url = epUrls[i];
|
|
||||||
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(url))).body;
|
|
||||||
|
|
||||||
final urlVid = xpath(res, '//a[contains(@href,"/watch")]/@href').first;
|
|
||||||
final resVid = (await client.get(Uri.parse(urlVid))).body;
|
|
||||||
String masterUrl = "";
|
|
||||||
if (resVid.contains("jwplayer(")) {
|
|
||||||
masterUrl = substringBefore(substringAfter(resVid, "file: \""), "\"");
|
|
||||||
} else {
|
|
||||||
masterUrl = parseHtml(resVid).selectFirst("source").attr("src");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
if (masterUrl.endsWith("playlist.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";
|
|
||||||
}
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = quality;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = masterUrl
|
|
||||||
..originalUrl = masterUrl
|
|
||||||
..quality = "Qualità predefinita";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return sortVideos(videos, source.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
String formatTitle(String titlestring) {
|
|
||||||
return titlestring
|
|
||||||
.replaceAll("(ITA) ITA", "Dub ITA")
|
|
||||||
.replaceAll("(ITA)", "Dub ITA")
|
|
||||||
.replaceAll("Sub ITA", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
HeaderFilter("Ricerca per titolo ignora i filtri e viceversa"),
|
|
||||||
GroupFilter("GenreFilter", "Generi", [
|
|
||||||
CheckBoxFilter("Arti Marziali", "Arti Marziali"),
|
|
||||||
CheckBoxFilter("Avventura", "Avventura"),
|
|
||||||
CheckBoxFilter("Azione", "Azione"),
|
|
||||||
CheckBoxFilter("Bambini", "Bambini"),
|
|
||||||
CheckBoxFilter("Commedia", "Commedia"),
|
|
||||||
CheckBoxFilter("Demenziale", "Demenziale"),
|
|
||||||
CheckBoxFilter("Demoni", "Demoni"),
|
|
||||||
CheckBoxFilter("Drammatico", "Drammatico"),
|
|
||||||
CheckBoxFilter("Ecchi", "Ecchi"),
|
|
||||||
CheckBoxFilter("Fantasy", "Fantasy"),
|
|
||||||
CheckBoxFilter("Gioco", "Gioco"),
|
|
||||||
CheckBoxFilter("Harem", "Harem"),
|
|
||||||
CheckBoxFilter("Hentai", "Hentai"),
|
|
||||||
CheckBoxFilter("Horror", "Horror"),
|
|
||||||
CheckBoxFilter("Josei", "Josei"),
|
|
||||||
CheckBoxFilter("Magia", "Magia"),
|
|
||||||
CheckBoxFilter("Mecha", "Mecha"),
|
|
||||||
CheckBoxFilter("Militari", "Militari"),
|
|
||||||
CheckBoxFilter("Mistero", "Mistero"),
|
|
||||||
CheckBoxFilter("Musicale", "Musicale"),
|
|
||||||
CheckBoxFilter("Parodia", "Parodia"),
|
|
||||||
CheckBoxFilter("Polizia", "Polizia"),
|
|
||||||
CheckBoxFilter("Psicologico", "Psicologico"),
|
|
||||||
CheckBoxFilter("Romantico", "Romantico"),
|
|
||||||
CheckBoxFilter("Samurai", "Samurai"),
|
|
||||||
CheckBoxFilter("Sci-Fi", "Sci-Fi"),
|
|
||||||
CheckBoxFilter("Scolastico", "Scolastico"),
|
|
||||||
CheckBoxFilter("Seinen", "Seinen"),
|
|
||||||
CheckBoxFilter("Sentimentale", "Sentimentale"),
|
|
||||||
CheckBoxFilter("Shoujo Ai", "Shoujo Ai"),
|
|
||||||
CheckBoxFilter("Shoujo", "Shoujo"),
|
|
||||||
CheckBoxFilter("Shounen Ai", "Shounen Ai"),
|
|
||||||
CheckBoxFilter("Shounen", "Shounen"),
|
|
||||||
CheckBoxFilter("Slice of Life", "Slice of Life"),
|
|
||||||
CheckBoxFilter("Soprannaturale", "Soprannaturale"),
|
|
||||||
CheckBoxFilter("Spazio", "Spazio"),
|
|
||||||
CheckBoxFilter("Sport", "Sport"),
|
|
||||||
CheckBoxFilter("Storico", "Storico"),
|
|
||||||
CheckBoxFilter("Superpoteri", "Superpoteri"),
|
|
||||||
CheckBoxFilter("Thriller", "Thriller"),
|
|
||||||
CheckBoxFilter("Vampiri", "Vampiri"),
|
|
||||||
CheckBoxFilter("Veicoli", "Veicoli"),
|
|
||||||
CheckBoxFilter("Yaoi", "Yaoi"),
|
|
||||||
CheckBoxFilter("Yuri", "Yuri"),
|
|
||||||
]),
|
|
||||||
GroupFilter("YearList", "Anno di Uscita", [
|
|
||||||
for (var i = 1969; i < 2022; i++)
|
|
||||||
CheckBoxFilter(i.toString(), i.toString()),
|
|
||||||
]),
|
|
||||||
GroupFilter("StateList", "Stato", [
|
|
||||||
CheckBoxFilter("In corso", "0"),
|
|
||||||
CheckBoxFilter("Finito", "1"),
|
|
||||||
CheckBoxFilter("Non rilasciato", "2"),
|
|
||||||
CheckBoxFilter("Droppato", "3"),
|
|
||||||
]),
|
|
||||||
SelectFilter("LangList", "Lingua", 0, [
|
|
||||||
SelectFilterOption("", ""),
|
|
||||||
SelectFilterOption("Subbato", "0"),
|
|
||||||
SelectFilterOption("Doppiato", "1"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Qualità preferita",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["1080p", "720p", "480p", "360p", "240p", "144p"],
|
|
||||||
entryValues: ["1080", "720", "480", "360", "240", "144"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
|
||||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
|
||||||
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
if (a.quality.contains(quality)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimeSaturn main(MSource source) {
|
|
||||||
return AnimeSaturn(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 6.5 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get animesaturn => _animesaturn;
|
|
||||||
const _animesaturnVersion = "0.0.55";
|
|
||||||
const _animesaturnCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/it/animesaturn/animesaturn.dart";
|
|
||||||
Source _animesaturn = Source(
|
|
||||||
name: "AnimeSaturn",
|
|
||||||
baseUrl: "https://www.animesaturn.cx",
|
|
||||||
lang: "it",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/it/animesaturn/icon.png",
|
|
||||||
sourceCodeUrl: _animesaturnCodeUrl,
|
|
||||||
version: _animesaturnVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
class AnimesVision extends MProvider {
|
|
||||||
AnimesVision({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => source.baseUrl;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> get headers => {
|
|
||||||
"Referer": baseUrl,
|
|
||||||
"Accept-Language": "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7",
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final res = (await client.get(Uri.parse(baseUrl), headers: headers)).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final elements = document.select(
|
|
||||||
"div#anime-trending div.item > a.film-poster",
|
|
||||||
);
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var element in elements) {
|
|
||||||
var anime = MManga();
|
|
||||||
var img = element.selectFirst("img");
|
|
||||||
anime.name = img.attr("title");
|
|
||||||
anime.link = getUrlWithoutDomain(element.attr("href"));
|
|
||||||
anime.imageUrl = img.attr("src");
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, hasNextPage(document));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl/lancamentos?page=$page"),
|
|
||||||
headers: headers,
|
|
||||||
)).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final elements = document.select(
|
|
||||||
"div.container div.screen-items > div.item",
|
|
||||||
);
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var element in elements) {
|
|
||||||
var anime = MManga();
|
|
||||||
anime.name = substringAfter(element.selectFirst("h3").text, "-").trim();
|
|
||||||
anime.link = getUrlWithoutDomain(element.selectFirst("a").attr("href"));
|
|
||||||
anime.imageUrl = element.selectFirst("img")?.attr("src") ?? "";
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, hasNextPage(document));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final res =
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse("$baseUrl/search-anime?nome=$query&page=$page"),
|
|
||||||
)).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final elements = document.select("div.film_list-wrap div.film-poster");
|
|
||||||
List<MManga> animeList = [];
|
|
||||||
for (var element in elements) {
|
|
||||||
var anime = MManga();
|
|
||||||
final elementA = element.selectFirst("a");
|
|
||||||
anime.name = elementA.attr("title");
|
|
||||||
anime.link = getUrlWithoutDomain(elementA.attr("href"));
|
|
||||||
anime.imageUrl = element.selectFirst("img").attr("data-src");
|
|
||||||
animeList.add(anime);
|
|
||||||
}
|
|
||||||
return MPages(animeList, hasNextPage(document));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final statusList = [
|
|
||||||
{"Atualmente sendo exibido": 0, "Fim da exibição": 1},
|
|
||||||
];
|
|
||||||
MManga anime = MManga();
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
|
||||||
var document = await getRealDoc(parseHtml(res), "$baseUrl$url");
|
|
||||||
final content = document.selectFirst("div#ani_detail div.anis-content");
|
|
||||||
final detail = content.selectFirst("div.anisc-detail");
|
|
||||||
final infos = content.selectFirst("div.anisc-info");
|
|
||||||
anime.imageUrl = content.selectFirst("img")?.attr("src");
|
|
||||||
anime.name = detail.selectFirst("h2.film-name").text;
|
|
||||||
anime.genre = getInfo(infos, "Gêneros").split(",");
|
|
||||||
anime.author = getInfo(infos, "Produtores");
|
|
||||||
anime.artist = getInfo(infos, "Estúdios");
|
|
||||||
anime.status = parseStatus(getInfo(infos, "Status"), statusList);
|
|
||||||
String description = getInfo(infos, "Sinopse");
|
|
||||||
if (getInfo(infos, "Inglês").isNotEmpty)
|
|
||||||
description += '\n\nTítulo em inglês: ${getInfo(infos, "Inglês")}';
|
|
||||||
anime.description = description;
|
|
||||||
if (getInfo(infos, "Japonês").isNotEmpty)
|
|
||||||
description += '\nTítulo em Japonês: ${getInfo(infos, "Japonês")}';
|
|
||||||
if (getInfo(infos, "Foi ao ar em").isNotEmpty)
|
|
||||||
description += '\nFoi ao ar em: ${getInfo(infos, "Foi ao ar em")}';
|
|
||||||
if (getInfo(infos, "Temporada").isNotEmpty)
|
|
||||||
description += '\nTemporada: ${getInfo(infos, "Temporada")}';
|
|
||||||
if (getInfo(infos, "Duração").isNotEmpty)
|
|
||||||
description += '\nDuração: ${getInfo(infos, "Duração")}';
|
|
||||||
if (getInfo(infos, "Fansub").isNotEmpty)
|
|
||||||
description += '\nFansub: ${getInfo(infos, "Fansub")}';
|
|
||||||
anime.description = description;
|
|
||||||
List<MChapter> episodeList = [];
|
|
||||||
for (var element
|
|
||||||
in document.select("div.container div.screen-items > div.item") ?? []) {
|
|
||||||
episodeList.add(episodeFromElement(element));
|
|
||||||
}
|
|
||||||
|
|
||||||
while (hasNextPage(document)) {
|
|
||||||
if (episodeList.isNotEmpty) {
|
|
||||||
final nextUrl = nextPageElements(
|
|
||||||
document,
|
|
||||||
)[0].selectFirst("a").attr("href");
|
|
||||||
document = parseHtml((await client.get(Uri.parse(nextUrl))).body);
|
|
||||||
}
|
|
||||||
for (var element
|
|
||||||
in document.select("div.container div.screen-items > div.item") ??
|
|
||||||
[]) {
|
|
||||||
episodeList.add(episodeFromElement(element));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anime.chapters = episodeList.reversed.toList();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final encodedScript =
|
|
||||||
document
|
|
||||||
.selectFirst("div.player-frame div#playerglobalapi ~ script")
|
|
||||||
.text;
|
|
||||||
final decodedScript = decodeScriptFromString(encodedScript);
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (RegExpMatch match in RegExp(
|
|
||||||
r'"file":"(\S+?)",.*?"label":"(.*?)"',
|
|
||||||
).allMatches(decodedScript)) {
|
|
||||||
final videoUrl = match.group(1)!.replaceAll('\\', '');
|
|
||||||
final qualityName = match.group(2);
|
|
||||||
var video = MVideo();
|
|
||||||
video.url = videoUrl;
|
|
||||||
video.headers = headers;
|
|
||||||
video.quality = 'PlayerVision $qualityName';
|
|
||||||
video.originalUrl = videoUrl;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasNextPage(MDocument document) {
|
|
||||||
return nextPageElements(document).isNotEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MElement> nextPageElements(MDocument document) {
|
|
||||||
final elements =
|
|
||||||
document
|
|
||||||
.select("ul.pagination li.page-item")
|
|
||||||
.where(
|
|
||||||
(MElement e) =>
|
|
||||||
e.outerHtml.contains("›") &&
|
|
||||||
!e.outerHtml.contains("disabled"),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<MDocument> getRealDoc(MDocument document, String originalUrl) async {
|
|
||||||
if (["/episodio-", "/filme-"].any((e) => originalUrl.contains(e))) {
|
|
||||||
final url = document.selectFirst("h2.film-name > a").attr("href");
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
return parseHtml(res);
|
|
||||||
}
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getInfo(MElement element, String key) {
|
|
||||||
final divs =
|
|
||||||
element
|
|
||||||
.select("div.item")
|
|
||||||
.where((MElement e) => e.outerHtml.contains(key))
|
|
||||||
.toList();
|
|
||||||
String text = "";
|
|
||||||
if (divs.isNotEmpty) {
|
|
||||||
MElement div = divs[0];
|
|
||||||
var elementsA = div.select("a[href]");
|
|
||||||
if (elementsA.isEmpty) {
|
|
||||||
String selector =
|
|
||||||
div.outerHtml.contains("w-hide") ? "div.text" : "span.name";
|
|
||||||
text = div.selectFirst(selector).text.trim();
|
|
||||||
} else {
|
|
||||||
text = elementsA.map((MElement e) => e.text.trim()).toList().join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
MChapter episodeFromElement(MElement element) {
|
|
||||||
var anime = MChapter();
|
|
||||||
anime.url = getUrlWithoutDomain(element.selectFirst("a").attr("href"));
|
|
||||||
anime.name = element.selectFirst("h3").text.trim();
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
|
||||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
|
||||||
|
|
||||||
videos.sort((MVideo a, MVideo b) {
|
|
||||||
int qualityMatchA = 0;
|
|
||||||
|
|
||||||
if (a.quality.contains(quality)) {
|
|
||||||
qualityMatchA = 1;
|
|
||||||
}
|
|
||||||
int qualityMatchB = 0;
|
|
||||||
if (b.quality.contains(quality)) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Qualidade preferida",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 1,
|
|
||||||
entries: ["480p", "720p", "1080p", "4K"],
|
|
||||||
entryValues: ["1080", "720", "480", "4K"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
int convertToNum(String thing, int limit) {
|
|
||||||
int result = 0;
|
|
||||||
int i = 0;
|
|
||||||
for (var n in thing.split('').reversed.toList()) {
|
|
||||||
final a = int.tryParse(n) ?? 0;
|
|
||||||
result += a * pow(limit, i).toInt();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
String decodeScript(
|
|
||||||
String encodedString,
|
|
||||||
String magicStr,
|
|
||||||
int offset,
|
|
||||||
int limit,
|
|
||||||
) {
|
|
||||||
RegExp regex = RegExp('\\w');
|
|
||||||
List<String> parts = encodedString.split(magicStr[limit]);
|
|
||||||
List<String> decodedParts = [];
|
|
||||||
for (String part in parts.sublist(0, parts.length - 1)) {
|
|
||||||
String replaced = part;
|
|
||||||
for (Match match in regex.allMatches(part)) {
|
|
||||||
replaced = replaced.replaceFirst(
|
|
||||||
match.group(0)!,
|
|
||||||
magicStr.indexOf(match.group(0)!).toString(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
int charInt = convertToNum(replaced, limit) - offset;
|
|
||||||
decodedParts.add(String.fromCharCode(charInt));
|
|
||||||
}
|
|
||||||
return decodedParts.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
String decodeScriptFromString(String script) {
|
|
||||||
RegExp regex = RegExp(r'\}\("(\w+)",.*?"(\w+)",(\d+),(\d+),.*?\)');
|
|
||||||
Match? match = regex.firstMatch(script);
|
|
||||||
if (match != null) {
|
|
||||||
return decodeScript(
|
|
||||||
match.group(1)!,
|
|
||||||
match.group(2)!,
|
|
||||||
int.tryParse(match.group(3)!) ?? 0,
|
|
||||||
int.tryParse(match.group(4)!) ?? 0,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return script;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimesVision main(MSource source) {
|
|
||||||
return AnimesVision(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get animesvision => _animesvision;
|
|
||||||
const _animesvisionVersion = "0.0.2";
|
|
||||||
const _animesvisionCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/pt/animesvision/animesvision.dart";
|
|
||||||
Source _animesvision = Source(
|
|
||||||
name: "AnimesVision",
|
|
||||||
baseUrl: "https://animes.vision",
|
|
||||||
lang: "pt-br",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/pt/animesvision/icon.png",
|
|
||||||
sourceCodeUrl: _animesvisionCodeUrl,
|
|
||||||
version: _animesvisionVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class Filma24 extends MProvider {
|
|
||||||
Filma24({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get baseUrl => getPreferenceValue(source.id, "pref_domain_new");
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
final client = Client(source, json.encode({"useDartHttpClient": true}));
|
|
||||||
String pageNu = page == 1 ? "" : "/page/$page/";
|
|
||||||
final res = (await client.get(Uri.parse("$baseUrl$pageNu"))).body;
|
|
||||||
return animeFromRes(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
final client = Client(source, json.encode({"useDartHttpClient": true}));
|
|
||||||
String pageNu = page == 1 ? "" : "page/$page/";
|
|
||||||
final res =
|
|
||||||
(await client.get(Uri.parse("$baseUrl/$pageNu?sort=trendy"))).body;
|
|
||||||
return animeFromRes(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
final client = Client(source, json.encode({"useDartHttpClient": true}));
|
|
||||||
final filters = filterList.filters;
|
|
||||||
String url = "";
|
|
||||||
String pageNu = page == 1 ? "" : "page/$page/";
|
|
||||||
if (query.isNotEmpty) {
|
|
||||||
url += "$baseUrl/search/$query/";
|
|
||||||
} else {
|
|
||||||
for (var filter in filters) {
|
|
||||||
if (filter.type == "ReleaseFilter") {
|
|
||||||
final year = filter.values[filter.state].value;
|
|
||||||
if (year.isNotEmpty) {
|
|
||||||
url = "/released-year/?release=$year/";
|
|
||||||
}
|
|
||||||
} else if (filter.type == "GenreFilter") {
|
|
||||||
final genre = filter.values[filter.state].value;
|
|
||||||
if (genre.isNotEmpty) {
|
|
||||||
url = genre;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
url = "$baseUrl$url";
|
|
||||||
}
|
|
||||||
|
|
||||||
url += pageNu;
|
|
||||||
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
return animeFromRes(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
final client = Client(source, json.encode({"useDartHttpClient": true}));
|
|
||||||
List<MChapter>? episodesList = [];
|
|
||||||
if (!url.contains("seriale")) {
|
|
||||||
MChapter episode = MChapter();
|
|
||||||
episode.name = "Filma";
|
|
||||||
episode.url = url;
|
|
||||||
episodesList.add(episode);
|
|
||||||
} else {
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final resultElements = document.select("div.row");
|
|
||||||
|
|
||||||
for (var result in resultElements) {
|
|
||||||
final elements = result?.select("div.movie-thumb") ?? [];
|
|
||||||
|
|
||||||
for (var i = 0; i < elements.length; i++) {
|
|
||||||
MChapter ep = MChapter();
|
|
||||||
ep.name = elements[i].selectFirst("div > span").text;
|
|
||||||
ep.url = elements[i].selectFirst("a").getHref;
|
|
||||||
episodesList.add(ep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MManga anime = MManga();
|
|
||||||
anime.chapters = episodesList;
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
final client = Client(source, json.encode({"useDartHttpClient": true}));
|
|
||||||
final res = (await client.get(Uri.parse(url))).body;
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final serverUrls = xpath(res, '//*[@class="player"]/div[1]/a/@href');
|
|
||||||
for (var serverUrl in serverUrls) {
|
|
||||||
List<MVideo> a = [];
|
|
||||||
final serVres = (await client.get(Uri.parse("$url/$serverUrl"))).body;
|
|
||||||
List<String> iframe = xpath(serVres, '//*[@id="plx"]/p/iframe/@src');
|
|
||||||
if (iframe.isNotEmpty) {
|
|
||||||
String i = iframe.first;
|
|
||||||
if (i.startsWith("//")) {
|
|
||||||
i = "https:$i";
|
|
||||||
}
|
|
||||||
if (i.contains("vidmoly")) {
|
|
||||||
a = await vidmolyExtractor(i);
|
|
||||||
} else if (i.contains("dood")) {
|
|
||||||
a = await doodExtractor(i, "DoodStream");
|
|
||||||
} else if (i.contains("oneupload")) {
|
|
||||||
a = await oneuploadExtractor(i);
|
|
||||||
} else if (i.contains("uqload")) {
|
|
||||||
a = await uqloadExtractor(i);
|
|
||||||
} else if (i.contains("voe.sx")) {
|
|
||||||
a = await voeExtractor(i, "Voe");
|
|
||||||
}
|
|
||||||
videos.addAll(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
EditTextPreference(
|
|
||||||
key: "pref_domain_new",
|
|
||||||
title: "Domeni i përdorur aktualisht",
|
|
||||||
summary: "",
|
|
||||||
value: "https://www.filma24.band",
|
|
||||||
dialogTitle: "Domeni i përdorur aktualisht",
|
|
||||||
dialogMessage: "",
|
|
||||||
text: "https://www.filma24.band",
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
SelectFilter("ReleaseFilter", "Viti", 0, [
|
|
||||||
SelectFilterOption("<Select>", ""),
|
|
||||||
SelectFilterOption("2024", "2024"),
|
|
||||||
SelectFilterOption("2023", "2023"),
|
|
||||||
SelectFilterOption("2022", "2022"),
|
|
||||||
SelectFilterOption("2021", "2021"),
|
|
||||||
SelectFilterOption("2020", "2020"),
|
|
||||||
SelectFilterOption("2019", "2019"),
|
|
||||||
SelectFilterOption("2018", "2018"),
|
|
||||||
SelectFilterOption("2017", "2017"),
|
|
||||||
SelectFilterOption("2016", "2016"),
|
|
||||||
SelectFilterOption("2011-2015", "2011-2015"),
|
|
||||||
SelectFilterOption("2006-2010", "2006-2010"),
|
|
||||||
SelectFilterOption("2001-2005", "2001-2005"),
|
|
||||||
SelectFilterOption("1991-2000", "1991-2000"),
|
|
||||||
SelectFilterOption("1900-1990", "1900-1990"),
|
|
||||||
]),
|
|
||||||
SelectFilter("GenreFilter", "Zhanri", 0, [
|
|
||||||
SelectFilterOption("<Select>", ""),
|
|
||||||
SelectFilterOption("SË SHPEJTI", "/se-shpejti/"),
|
|
||||||
SelectFilterOption("Aksion", "/aksion/"),
|
|
||||||
SelectFilterOption("Animuar", "/animuar/"),
|
|
||||||
SelectFilterOption("Aventurë", "/aventure/"),
|
|
||||||
SelectFilterOption("Aziatik", "/aziatik/"),
|
|
||||||
SelectFilterOption("Biografi", "/biografi/"),
|
|
||||||
SelectFilterOption("Nordik", "/nordik/"),
|
|
||||||
SelectFilterOption("Dokumentar", "/dokumentar/"),
|
|
||||||
SelectFilterOption("Dramë", "/drame/"),
|
|
||||||
SelectFilterOption("Erotik +18", "/erotik/"),
|
|
||||||
SelectFilterOption("Familjar", "/familjar/"),
|
|
||||||
SelectFilterOption("Fantashkencë", "/fantashkence/"),
|
|
||||||
SelectFilterOption("Fantazi", "/fantazi/"),
|
|
||||||
SelectFilterOption("Francez", "/francez/"),
|
|
||||||
SelectFilterOption("Gjerman", "/gjerman/"),
|
|
||||||
SelectFilterOption("Hindi", "/hindi/"),
|
|
||||||
SelectFilterOption("Histori", "/histori/"),
|
|
||||||
SelectFilterOption("Horror", "/horror/"),
|
|
||||||
SelectFilterOption("Italian", "/italian/"),
|
|
||||||
SelectFilterOption("Komedi", "/komedi/"),
|
|
||||||
SelectFilterOption("Krim", "/krim/"),
|
|
||||||
SelectFilterOption("Luftë", "/lufte/"),
|
|
||||||
SelectFilterOption("Mister", "/mister/"),
|
|
||||||
SelectFilterOption("Muzikë", "/muzik/"),
|
|
||||||
SelectFilterOption("NETFLIX", "/netflix/"),
|
|
||||||
SelectFilterOption("Romancë", "/romance/"),
|
|
||||||
SelectFilterOption("Rus", "/rus/"),
|
|
||||||
SelectFilterOption("Shqiptar", "/shqiptar/"),
|
|
||||||
SelectFilterOption("Spanjoll", "/spanjoll/"),
|
|
||||||
SelectFilterOption("Sport", "/sport/"),
|
|
||||||
SelectFilterOption("Thriller", "/thriller/"),
|
|
||||||
SelectFilterOption("Turk", "/turk/"),
|
|
||||||
SelectFilterOption("Western", "/western/"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> vidmolyExtractor(String url) async {
|
|
||||||
final client = Client(source, json.encode({"useDartHttpClient": true}));
|
|
||||||
final headers = {'Referer': 'https://vidmoly.to'};
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final playListUrlResponse = (await client.get(Uri.parse(url))).body;
|
|
||||||
final playlistUrl =
|
|
||||||
RegExp(r'file:"(\S+?)"').firstMatch(playListUrlResponse)?.group(1) ??
|
|
||||||
"";
|
|
||||||
if (playlistUrl.isEmpty) return [];
|
|
||||||
final masterPlaylistRes = await client.get(
|
|
||||||
Uri.parse(playlistUrl),
|
|
||||||
headers: headers,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (masterPlaylistRes.statusCode == 200) {
|
|
||||||
for (var it in substringAfter(
|
|
||||||
masterPlaylistRes.body,
|
|
||||||
"#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");
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "Vidmoly $quality"
|
|
||||||
..headers = headers;
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> oneuploadExtractor(String url) async {
|
|
||||||
final client = Client(source, json.encode({"useDartHttpClient": true}));
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
final playListUrlResponse = (await client.get(Uri.parse(url))).body;
|
|
||||||
final playlistUrl =
|
|
||||||
RegExp(r'file:"(\S+?)"').firstMatch(playListUrlResponse)?.group(1) ??
|
|
||||||
"";
|
|
||||||
if (playlistUrl.isEmpty) return [];
|
|
||||||
final masterPlaylistRes = (await client.get(Uri.parse(playlistUrl))).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");
|
|
||||||
|
|
||||||
MVideo video = MVideo();
|
|
||||||
video
|
|
||||||
..url = videoUrl
|
|
||||||
..originalUrl = videoUrl
|
|
||||||
..quality = "OneUploader $quality";
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<MVideo>> uqloadExtractor(String url) async {
|
|
||||||
final client = Client(source, json.encode({"useDartHttpClient": true}));
|
|
||||||
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 = "Uqload"
|
|
||||||
..headers = {"Referer": "${Uri.parse(url).origin}/"};
|
|
||||||
return [video];
|
|
||||||
}
|
|
||||||
|
|
||||||
MPages animeFromRes(String res) {
|
|
||||||
final document = parseHtml(res);
|
|
||||||
final result = document.selectFirst("div.row");
|
|
||||||
final elements = result?.select("div.movie-thumb") ?? [];
|
|
||||||
List<MManga> mangaList = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < elements.length; i++) {
|
|
||||||
MManga manga = MManga();
|
|
||||||
manga.name = elements[i].selectFirst("div > a > h4").text;
|
|
||||||
manga.imageUrl = elements[i].selectFirst("a").getSrc;
|
|
||||||
manga.link = elements[i].selectFirst("a").getHref;
|
|
||||||
mangaList.add(manga);
|
|
||||||
}
|
|
||||||
|
|
||||||
return MPages(
|
|
||||||
mangaList,
|
|
||||||
document.selectFirst("div > a.nextpostslink")?.attr("href") != null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Filma24 main(MSource source) {
|
|
||||||
return Filma24(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 8.8 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get filma24 => _filma24;
|
|
||||||
const _filma24Version = "0.0.5";
|
|
||||||
const _filma24CodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/sq/filma24/filma24.dart";
|
|
||||||
Source _filma24 = Source(
|
|
||||||
name: "Filma24",
|
|
||||||
baseUrl: "https://www.filma24.band",
|
|
||||||
lang: "sq",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/sq/filma24/icon.png",
|
|
||||||
sourceCodeUrl: _filma24CodeUrl,
|
|
||||||
version: _filma24Version,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
import 'package:mangayomi/bridge_lib.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class DiziWatch extends MProvider {
|
|
||||||
DiziWatch({required this.source});
|
|
||||||
|
|
||||||
MSource source;
|
|
||||||
|
|
||||||
final Client client = Client();
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get supportsLatest => true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, String> get headers => {};
|
|
||||||
|
|
||||||
Future<MPages> parseMainList(int index) async {
|
|
||||||
MDocument dom = parseHtml(
|
|
||||||
(await client.get(Uri.parse(source.baseUrl))).body,
|
|
||||||
);
|
|
||||||
List<MManga> list = [];
|
|
||||||
MElement containingElement = dom.select("#list-series-hizala2")[index];
|
|
||||||
List<MElement> results = containingElement.select("#list-series-main");
|
|
||||||
for (MElement result in results) {
|
|
||||||
MElement a = result.selectFirst("a");
|
|
||||||
MElement img = a.selectFirst("img");
|
|
||||||
MManga anime = new MManga();
|
|
||||||
anime.name = img.attr("alt");
|
|
||||||
anime.link = a.getHref;
|
|
||||||
anime.imageUrl = img.getSrc;
|
|
||||||
list.add(anime);
|
|
||||||
}
|
|
||||||
|
|
||||||
return MPages(list, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getPopular(int page) async {
|
|
||||||
return parseMainList(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> getLatestUpdates(int page) async {
|
|
||||||
return parseMainList(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
|
||||||
String orderby = "";
|
|
||||||
String year = "";
|
|
||||||
String imdb = "";
|
|
||||||
String genre = "";
|
|
||||||
for (var filter in filterList.filters) {
|
|
||||||
if (filter.type == "Sort") {
|
|
||||||
orderby = filter.values[filter.state].value;
|
|
||||||
} else if (filter.type == "Year") {
|
|
||||||
year = filter.state;
|
|
||||||
} else if (filter.type == "MinIMDBRating") {
|
|
||||||
imdb = filter.values[filter.state].value;
|
|
||||||
} else if (filter.type == "Genre") {
|
|
||||||
genre = filter.values[filter.state].value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MDocument dom = parseHtml(
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${source.baseUrl}/anime-arsivi/page/${page}/?orderby=${orderby}&yil=${year}&imdb=${imdb}&isim=${query}&tur=${genre}",
|
|
||||||
),
|
|
||||||
)).body,
|
|
||||||
);
|
|
||||||
List<MElement> results = dom.select("#list-series");
|
|
||||||
List<MManga> list = [];
|
|
||||||
for (MElement result in results) {
|
|
||||||
MElement a = result.select("a")[1];
|
|
||||||
MElement img = a.selectFirst("img");
|
|
||||||
MManga anime = new MManga();
|
|
||||||
anime.name = result.selectFirst("div.cat-title a").text;
|
|
||||||
anime.link = a.getHref;
|
|
||||||
anime.imageUrl = img.getSrc;
|
|
||||||
list.add(anime);
|
|
||||||
}
|
|
||||||
|
|
||||||
MElement paginateLinksDiv = dom.selectFirst("div.paginate-links");
|
|
||||||
int lastPage = int.parse(
|
|
||||||
paginateLinksDiv.selectFirst("a.next").previousElementSibling.text ?? "1",
|
|
||||||
);
|
|
||||||
|
|
||||||
return MPages(list, lastPage > page);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<MManga> getDetail(String url) async {
|
|
||||||
MDocument dom = parseHtml((await client.get(Uri.parse(url))).body);
|
|
||||||
|
|
||||||
var anime = new MManga();
|
|
||||||
|
|
||||||
anime.name = dom.selectFirst("h1.title-border").text;
|
|
||||||
anime.link = url;
|
|
||||||
anime.imageUrl = dom.selectFirst("div.category_image img").getSrc;
|
|
||||||
anime.description = dom.selectFirst("div#series-info").text;
|
|
||||||
|
|
||||||
List<String> genres = dom.selectFirst("span.dizi-tur").text.split(", ");
|
|
||||||
genres.remove("Anime"); // not needed
|
|
||||||
anime.genre = genres;
|
|
||||||
|
|
||||||
List<MElement> results = dom.select("div.bolumust");
|
|
||||||
List<MChapter> chapters = [];
|
|
||||||
for (MElement result in results) {
|
|
||||||
MElement a = result.select("a")[1];
|
|
||||||
MChapter chapter = new MChapter();
|
|
||||||
chapter.name =
|
|
||||||
result.selectFirst(".baslik").text +
|
|
||||||
" | " +
|
|
||||||
result.selectFirst("#bolum-ismi").text;
|
|
||||||
chapter.url = a.getHref;
|
|
||||||
chapters.add(chapter);
|
|
||||||
}
|
|
||||||
anime.chapters = chapters.reversed.toList();
|
|
||||||
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<MVideo>> getVideoList(String url) async {
|
|
||||||
MDocument dom = parseHtml((await client.get(Uri.parse(url))).body);
|
|
||||||
String id = dom.selectFirst("#takip_et_izledim_Calis").attr("data-ilanid");
|
|
||||||
var json =
|
|
||||||
json.decode(
|
|
||||||
(await client.get(
|
|
||||||
Uri.parse(
|
|
||||||
"${source.baseUrl}/wp-admin/admin-ajax.php?action=playlist&pid=${id}",
|
|
||||||
),
|
|
||||||
)).body,
|
|
||||||
)[0];
|
|
||||||
var sources = json["sources"];
|
|
||||||
List<MVideo> videos = [];
|
|
||||||
for (var source in sources) {
|
|
||||||
MVideo video = new MVideo();
|
|
||||||
video.url = source["file"];
|
|
||||||
video.originalUrl = source["file"];
|
|
||||||
video.quality = source["label"];
|
|
||||||
video.headers = {"Referer": url};
|
|
||||||
videos.add(video);
|
|
||||||
}
|
|
||||||
|
|
||||||
String quality = getPreferenceValue(source.id, "preferred_quality");
|
|
||||||
videos.sort(
|
|
||||||
(MVideo a, MVideo b) =>
|
|
||||||
(b.quality.contains(quality) ? 1 : 0) -
|
|
||||||
(a.quality.contains(quality) ? 1 : 0),
|
|
||||||
);
|
|
||||||
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getFilterList() {
|
|
||||||
return [
|
|
||||||
HeaderFilter("Filtrele"),
|
|
||||||
SelectFilter("Sort", "Sırala", 0, [
|
|
||||||
SelectFilterOption("IMDb Puanına Göre", "meta_value"),
|
|
||||||
SelectFilterOption("Alfabetik", "name"),
|
|
||||||
SelectFilterOption("Eklenme Tarihine Göre", "ID"),
|
|
||||||
]),
|
|
||||||
TextFilter("Year", "Yapım Yılı"),
|
|
||||||
SelectFilter("Genre", "Tür", 0, [
|
|
||||||
SelectFilterOption("Kategori Seçin", ""),
|
|
||||||
SelectFilterOption("Aksiyon", "aksiyon"),
|
|
||||||
SelectFilterOption("Arabalar", "araba"),
|
|
||||||
SelectFilterOption("Askeri", "askeri"),
|
|
||||||
SelectFilterOption("Bilim Kurgu", "bilim"),
|
|
||||||
SelectFilterOption("Büyü", "buyu"),
|
|
||||||
SelectFilterOption("Doğaüstü Güçler", "doga"),
|
|
||||||
SelectFilterOption("Dövüş Sanatları", "dovus"),
|
|
||||||
SelectFilterOption("Dram", "dram"),
|
|
||||||
SelectFilterOption("Ecchi", "ecchi"),
|
|
||||||
SelectFilterOption("Fantastik", "fantastik"),
|
|
||||||
SelectFilterOption("Gerilim", "gerilim"),
|
|
||||||
SelectFilterOption("Gizem", "gizem"),
|
|
||||||
SelectFilterOption("Harem", "harem"),
|
|
||||||
SelectFilterOption("Isekai", "isekai"),
|
|
||||||
SelectFilterOption("Komedi", "komedi"),
|
|
||||||
SelectFilterOption("Korku", "korku"),
|
|
||||||
SelectFilterOption("Macera", "macera"),
|
|
||||||
SelectFilterOption("Mecha", "mecha"),
|
|
||||||
SelectFilterOption("Müzik", "muzik"),
|
|
||||||
SelectFilterOption("Okul", "okul"),
|
|
||||||
SelectFilterOption("Oyun", "oyun"),
|
|
||||||
SelectFilterOption("Parodi", "parodi"),
|
|
||||||
SelectFilterOption("Polisiye", "polisiye"),
|
|
||||||
SelectFilterOption("Psikolojik", "psikolojik"),
|
|
||||||
SelectFilterOption("Romantizm", "romantizm"),
|
|
||||||
SelectFilterOption("Samuray", "samuray"),
|
|
||||||
SelectFilterOption("Seinen", "seinen"),
|
|
||||||
SelectFilterOption("Shoujo", "shoujo"),
|
|
||||||
SelectFilterOption("Shounen", "shounen"),
|
|
||||||
SelectFilterOption("Spor", "spor"),
|
|
||||||
SelectFilterOption("Suç", "suc"),
|
|
||||||
SelectFilterOption("Süper Güçler", "super"),
|
|
||||||
SelectFilterOption("Şeytanlar", "seytan"),
|
|
||||||
SelectFilterOption("Şizofreni", "sizofreni"),
|
|
||||||
SelectFilterOption("Tarihi", "tarihi"),
|
|
||||||
SelectFilterOption("Uzay", "uzay"),
|
|
||||||
SelectFilterOption("Vampir", "vampir"),
|
|
||||||
SelectFilterOption("Yaşamdan Kesitler", "yasam"),
|
|
||||||
]),
|
|
||||||
SelectFilter("MinIMDBRating", "Min. IMBD Puanı", 0, [
|
|
||||||
SelectFilterOption(
|
|
||||||
"1",
|
|
||||||
"0",
|
|
||||||
), // value 1 looks like buggy so use 0 it wont make any difference.
|
|
||||||
SelectFilterOption("2", "2"),
|
|
||||||
SelectFilterOption("3", "3"),
|
|
||||||
SelectFilterOption("4", "4"),
|
|
||||||
SelectFilterOption("5", "5"),
|
|
||||||
SelectFilterOption("6", "6"),
|
|
||||||
SelectFilterOption("7", "7"),
|
|
||||||
SelectFilterOption("8", "8"),
|
|
||||||
SelectFilterOption("9", "9"),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<dynamic> getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
ListPreference(
|
|
||||||
key: "preferred_quality",
|
|
||||||
title: "Tercih edilen kalite",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["1080p", "480p"], // I only saw 1080p and 480p in diziWatch.
|
|
||||||
entryValues: ["1080", "480"],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DiziWatch main(MSource source) {
|
|
||||||
return DiziWatch(source: source);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,17 +0,0 @@
|
|||||||
import '../../../../../model/source.dart';
|
|
||||||
|
|
||||||
Source get diziwatchSource => _diziwatchSource;
|
|
||||||
const _diziwatchVersion = "0.0.15";
|
|
||||||
const _diziwatchSourceCodeUrl =
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/tr/diziwatch/diziwatch.dart";
|
|
||||||
Source _diziwatchSource = Source(
|
|
||||||
name: "diziWatch",
|
|
||||||
baseUrl: "https://diziwatch.net",
|
|
||||||
lang: "tr",
|
|
||||||
typeSource: "single",
|
|
||||||
iconUrl:
|
|
||||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/tr/diziwatch/icon.png",
|
|
||||||
sourceCodeUrl: _diziwatchSourceCodeUrl,
|
|
||||||
version: _diziwatchVersion,
|
|
||||||
itemType: ItemType.anime,
|
|
||||||
);
|
|
||||||
@@ -1,714 +0,0 @@
|
|||||||
const mangayomiSources = [
|
|
||||||
{
|
|
||||||
"name": "Autoembed",
|
|
||||||
"lang": "all",
|
|
||||||
"baseUrl": "https://watch.autoembed.cc",
|
|
||||||
"apiUrl": "https://tom.autoembed.cc",
|
|
||||||
"iconUrl":
|
|
||||||
"https://www.google.com/s2/favicons?sz=64&domain=https://autoembed.cc/",
|
|
||||||
"typeSource": "multi",
|
|
||||||
"isManga": false,
|
|
||||||
"itemType": 1,
|
|
||||||
"version": "1.2.7",
|
|
||||||
"dateFormat": "",
|
|
||||||
"dateFormatLocale": "",
|
|
||||||
"pkgPath": "anime/src/all/autoembed.js"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
class DefaultExtension extends MProvider {
|
|
||||||
decodeBase64 = function (f) {
|
|
||||||
var g = {},
|
|
||||||
b = 65,
|
|
||||||
d = 0,
|
|
||||||
a,
|
|
||||||
c = 0,
|
|
||||||
h,
|
|
||||||
e = "",
|
|
||||||
k = String.fromCharCode,
|
|
||||||
l = f.length;
|
|
||||||
for (a = ""; 91 > b; ) a += k(b++);
|
|
||||||
a += a.toLowerCase() + "0123456789+/";
|
|
||||||
for (b = 0; 64 > b; b++) g[a.charAt(b)] = b;
|
|
||||||
for (a = 0; a < l; a++)
|
|
||||||
for (b = g[f.charAt(a)], d = (d << 6) + b, c += 6; 8 <= c; )
|
|
||||||
((h = (d >>> (c -= 8)) & 255) || a < l - 2) && (e += k(h));
|
|
||||||
return e;
|
|
||||||
};
|
|
||||||
getHeaders(url) {
|
|
||||||
return {
|
|
||||||
Referer: url,
|
|
||||||
Origin: url,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getPreference(key) {
|
|
||||||
const preferences = new SharedPreferences();
|
|
||||||
return preferences.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
async tmdbRequest(slug) {
|
|
||||||
var api = `https://94c8cb9f702d-tmdb-addon.baby-beamup.club/${slug}`;
|
|
||||||
var response = await new Client().get(api);
|
|
||||||
var body = JSON.parse(response.body);
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSearchItems(body) {
|
|
||||||
var items = [];
|
|
||||||
var results = body.metas;
|
|
||||||
for (let i in results) {
|
|
||||||
var result = results[i];
|
|
||||||
var id = result.id;
|
|
||||||
var media_type = result.type;
|
|
||||||
items.push({
|
|
||||||
name: result.name,
|
|
||||||
imageUrl: result.poster,
|
|
||||||
link: `${media_type}||${id}`,
|
|
||||||
description: result.description,
|
|
||||||
genre: result.genre,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
async getSearchInfo(slug) {
|
|
||||||
var body = await this.tmdbRequest(`catalog/movie/${slug}`);
|
|
||||||
var popMovie = await this.getSearchItems(body);
|
|
||||||
|
|
||||||
body = await this.tmdbRequest(`catalog/series/${slug}`);
|
|
||||||
var popSeries = await this.getSearchItems(body);
|
|
||||||
|
|
||||||
var fullList = [];
|
|
||||||
|
|
||||||
var priority = this.getPreference("pref_content_priority");
|
|
||||||
if (priority === "series") {
|
|
||||||
fullList = [...popSeries, ...popMovie];
|
|
||||||
} else {
|
|
||||||
fullList = [...popMovie, ...popSeries];
|
|
||||||
}
|
|
||||||
var hasNextPage = slug.indexOf("search=") > -1 ? false : true;
|
|
||||||
return {
|
|
||||||
list: fullList,
|
|
||||||
hasNextPage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPopular(page) {
|
|
||||||
var skip = (page - 1) * 20;
|
|
||||||
return await this.getSearchInfo(`tmdb.popular/skip=${skip}.json`);
|
|
||||||
}
|
|
||||||
get supportsLatest() {
|
|
||||||
throw new Error("supportsLatest not implemented");
|
|
||||||
}
|
|
||||||
async getLatestUpdates(page) {
|
|
||||||
var trend_window = this.getPreference("pref_latest_time_window");
|
|
||||||
var skip = (page - 1) * 20;
|
|
||||||
return await this.getSearchInfo(
|
|
||||||
`tmdb.trending/genre=${trend_window}&skip=${skip}.json`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
async search(query, page, filters) {
|
|
||||||
return await this.getSearchInfo(`tmdb.popular/search=${query}.json`);
|
|
||||||
}
|
|
||||||
async getDetail(url) {
|
|
||||||
var baseUrl = this.source.baseUrl;
|
|
||||||
var linkSlug = `${baseUrl}/title/`;
|
|
||||||
|
|
||||||
if (url.includes(linkSlug)) {
|
|
||||||
url = url.replace(linkSlug, "");
|
|
||||||
var id = url.replace("t", "");
|
|
||||||
if (url.includes("t")) {
|
|
||||||
url = `series||tmdb:${id}`;
|
|
||||||
} else {
|
|
||||||
url = `movie||tmdb:${id}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts = url.split("||");
|
|
||||||
var media_type = parts[0];
|
|
||||||
var id = parts[1];
|
|
||||||
var body = await this.tmdbRequest(`meta/${media_type}/${id}.json`);
|
|
||||||
var result = body.meta;
|
|
||||||
|
|
||||||
var tmdb_id = id.substring(5);
|
|
||||||
media_type = media_type == "series" ? "tv" : media_type;
|
|
||||||
|
|
||||||
var dateNow = Date.now().valueOf();
|
|
||||||
var release = result.released
|
|
||||||
? new Date(result.released).valueOf()
|
|
||||||
: dateNow;
|
|
||||||
var chaps = [];
|
|
||||||
|
|
||||||
var item = {
|
|
||||||
name: result.name,
|
|
||||||
imageUrl: result.poster,
|
|
||||||
link: `${linkSlug}${linkCode}`,
|
|
||||||
description: result.description,
|
|
||||||
genre: result.genre,
|
|
||||||
};
|
|
||||||
|
|
||||||
var link = `${media_type}||${tmdb_id}`;
|
|
||||||
|
|
||||||
if (media_type == "tv") {
|
|
||||||
var videos = result.videos;
|
|
||||||
for (var i in videos) {
|
|
||||||
var video = videos[i];
|
|
||||||
var seasonNum = video.season;
|
|
||||||
|
|
||||||
if (!seasonNum) continue;
|
|
||||||
|
|
||||||
release = video.released ? new Date(video.released).valueOf() : dateNow;
|
|
||||||
|
|
||||||
if (release < dateNow) {
|
|
||||||
var episodeNum = video.episode;
|
|
||||||
var name = `S${seasonNum}:E${episodeNum} - ${video.name}`;
|
|
||||||
var eplink = `${link}||${seasonNum}||${episodeNum}`;
|
|
||||||
|
|
||||||
chaps.push({
|
|
||||||
name: name,
|
|
||||||
url: eplink,
|
|
||||||
dateUpload: release.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (release < dateNow) {
|
|
||||||
chaps.push({
|
|
||||||
name: "Movie",
|
|
||||||
url: link,
|
|
||||||
dateUpload: release.toString(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item.chapters = chaps;
|
|
||||||
chaps.reverse();
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extracts the streams url for different resolutions from a hls stream.
|
|
||||||
async extractStreams(url, lang = "", hdr = {}, host = "") {
|
|
||||||
var streams = [
|
|
||||||
{
|
|
||||||
url: url,
|
|
||||||
originalUrl: url,
|
|
||||||
quality: `${lang} Auto`,
|
|
||||||
headers: hdr,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
var pref = this.getPreference("autoembed_split_stream_quality");
|
|
||||||
if (!pref) return streams;
|
|
||||||
|
|
||||||
const response = await new Client().get(url, hdr);
|
|
||||||
const body = response.body;
|
|
||||||
const lines = body.split("\n");
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].startsWith("#EXT-X-STREAM-INF:")) {
|
|
||||||
var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1];
|
|
||||||
resolution = `${lang} ${resolution}`;
|
|
||||||
var m3u8Url = lines[i + 1].trim();
|
|
||||||
m3u8Url = m3u8Url.replace("./", `${url}/`);
|
|
||||||
if (host.length > 0) {
|
|
||||||
m3u8Url = `${host}${m3u8Url}`;
|
|
||||||
}
|
|
||||||
streams.push({
|
|
||||||
url: m3u8Url,
|
|
||||||
originalUrl: m3u8Url,
|
|
||||||
quality: resolution,
|
|
||||||
headers: hdr,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return streams;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For some streams, we can form stream url using a default template.
|
|
||||||
async splitStreams(url, lang = "", hdr = {}) {
|
|
||||||
var streams = [
|
|
||||||
{
|
|
||||||
url: url,
|
|
||||||
originalUrl: url,
|
|
||||||
quality: `${lang} - Auto`,
|
|
||||||
headers: hdr,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
var pref = this.getPreference("autoembed_split_stream_quality");
|
|
||||||
if (!pref) return streams;
|
|
||||||
|
|
||||||
var quality = ["360", "480", "720", "1080"];
|
|
||||||
for (var q of quality) {
|
|
||||||
var link = url;
|
|
||||||
if (q != "auto") {
|
|
||||||
link = link.replace("index.m3u8", `${q}/index.m3u8`);
|
|
||||||
q = `${q}p`;
|
|
||||||
}
|
|
||||||
streams.push({
|
|
||||||
url: link,
|
|
||||||
originalUrl: link,
|
|
||||||
quality: `${lang} - ${q}`,
|
|
||||||
headers: hdr,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return streams;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sorts streams based on user preference.
|
|
||||||
async sortStreams(streams) {
|
|
||||||
var sortedStreams = [];
|
|
||||||
|
|
||||||
var copyStreams = streams.slice();
|
|
||||||
var pref = this.getPreference("pref_video_resolution");
|
|
||||||
for (var i in streams) {
|
|
||||||
var stream = streams[i];
|
|
||||||
if (stream.quality.indexOf(pref) > -1) {
|
|
||||||
sortedStreams.push(stream);
|
|
||||||
var index = copyStreams.indexOf(stream);
|
|
||||||
if (index > -1) {
|
|
||||||
copyStreams.splice(index, 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [...sortedStreams, ...copyStreams];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets subtitles based on TMDB id.
|
|
||||||
async getSubtitleList(id, s, e) {
|
|
||||||
var subPref = parseInt(
|
|
||||||
this.getPreference("autoembed_pref_subtitle_source")
|
|
||||||
);
|
|
||||||
|
|
||||||
var api = `https://sub.wyzie.ru/search?id=${id}`;
|
|
||||||
var hdr = {};
|
|
||||||
|
|
||||||
if (subPref === 2) {
|
|
||||||
api = `https://sources.hexa.watch/subs/${id}`;
|
|
||||||
hdr = { "Origin": "https://api.hexa.watch" };
|
|
||||||
if (s != "0") api = `${api}/${s}/${e}`;
|
|
||||||
} else {
|
|
||||||
if (s != "0") api = `${api}&season=${s}&episode=${e}`;
|
|
||||||
}
|
|
||||||
var response = await new Client().get(api, hdr);
|
|
||||||
var body = JSON.parse(response.body);
|
|
||||||
|
|
||||||
var subs = [];
|
|
||||||
for (var sub of body) {
|
|
||||||
subs.push({
|
|
||||||
file: sub.url,
|
|
||||||
label: sub.display,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return subs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For anime episode video list
|
|
||||||
async getVideoList(url) {
|
|
||||||
var streamAPI = parseInt(this.getPreference("autoembed_stream_source_3"));
|
|
||||||
var nativeSubs = this.getPreference("autoembed_pref_navtive_subtitle");
|
|
||||||
|
|
||||||
var parts = url.split("||");
|
|
||||||
var media_type = parts[0];
|
|
||||||
var id = parts[1];
|
|
||||||
|
|
||||||
var s = "0";
|
|
||||||
var e = "0";
|
|
||||||
if (media_type == "tv") {
|
|
||||||
s = parts[2];
|
|
||||||
e = parts[3];
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmdb = id;
|
|
||||||
var streams = [];
|
|
||||||
var subtitles = [];
|
|
||||||
switch (streamAPI) {
|
|
||||||
case 2: {
|
|
||||||
if (media_type == "tv") {
|
|
||||||
id = `${id}/${s}/${e}`;
|
|
||||||
}
|
|
||||||
var api = `https://play2.123embed.net/server/3?path=/${media_type}/${id}`;
|
|
||||||
var response = await new Client().get(api);
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw new Error(
|
|
||||||
"play2.123embed.net unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = JSON.parse(response.body);
|
|
||||||
var link = body.playlist[0].file;
|
|
||||||
streams.push({
|
|
||||||
url: link,
|
|
||||||
originalUrl: link,
|
|
||||||
quality: "auto",
|
|
||||||
headers: { "Origin": "https://play2.123embed.net" },
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 3: {
|
|
||||||
if (media_type == "tv") {
|
|
||||||
id = `${id}&s=${s}&e=${e}`;
|
|
||||||
}
|
|
||||||
var api = `https://autoembed.cc/embed/player.php?id=${id}`;
|
|
||||||
|
|
||||||
var response = await new Client().get(api);
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw new Error(
|
|
||||||
"autoembed.cc unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
var body = response.body;
|
|
||||||
var sKey = '"file": ';
|
|
||||||
var eKey = "]});";
|
|
||||||
var start = body.indexOf(sKey);
|
|
||||||
if (start < 0) {
|
|
||||||
throw new Error(
|
|
||||||
"autoembed.cc videos unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
start += sKey.length;
|
|
||||||
|
|
||||||
var end = body.substring(start).indexOf(eKey) + start - 1;
|
|
||||||
var strms = JSON.parse(body.substring(start, end) + "]");
|
|
||||||
for (var strm of strms) {
|
|
||||||
var link = strm.file;
|
|
||||||
var lang = strm.title;
|
|
||||||
var streamSplit = await this.splitStreams(link, lang);
|
|
||||||
streams = [...streams, ...streamSplit];
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 4: {
|
|
||||||
if (media_type == "tv") {
|
|
||||||
id = `${id}&season=${s}&episode=${e}`;
|
|
||||||
}
|
|
||||||
var api = `https://flicky.host/player/desi.php?id=${id}`;
|
|
||||||
var response = await new Client().get(api, {
|
|
||||||
"Referer": "https://flicky.host/",
|
|
||||||
"sec-fetch-dest": "iframe",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw new Error(
|
|
||||||
"flicky.host unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
var body = response.body;
|
|
||||||
var sKey = "streams = ";
|
|
||||||
var eKey = "];";
|
|
||||||
var start = body.indexOf(sKey);
|
|
||||||
if (start < 0) {
|
|
||||||
throw new Error(
|
|
||||||
"flicky.host videos unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
start += sKey.length;
|
|
||||||
|
|
||||||
var end = body.substring(start).indexOf(eKey) + start + 1;
|
|
||||||
var strms = JSON.parse(body.substring(start, end));
|
|
||||||
|
|
||||||
for (var strm of strms) {
|
|
||||||
var link = strm.url;
|
|
||||||
var lang = strm.language;
|
|
||||||
var streamSplit = await this.splitStreams(link, lang);
|
|
||||||
streams = [...streams, ...streamSplit];
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 5: {
|
|
||||||
if (media_type == "tv") {
|
|
||||||
id = `${id}/${s}/${e}`;
|
|
||||||
}
|
|
||||||
var api = `https://vidapi.click/api/video/${media_type}/${id}`;
|
|
||||||
var response = await new Client().get(api);
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw new Error(
|
|
||||||
"vidapi.click unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = JSON.parse(response.body);
|
|
||||||
var link = body.sources[0].file;
|
|
||||||
if (nativeSubs) subtitles = body.tracks;
|
|
||||||
streams = await this.extractStreams(link);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 6: {
|
|
||||||
if (media_type == "tv") {
|
|
||||||
id = `${id}/${s}/${e}`;
|
|
||||||
}
|
|
||||||
var api = `https://sources.hexa.watch/plsdontscrapemeuwu/${id}`;
|
|
||||||
var hdr = { "Origin": "https://api.hexa.watch" };
|
|
||||||
var response = await new Client().get(api, hdr);
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw new Error(
|
|
||||||
"hexa.watch unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = JSON.parse(response.body);
|
|
||||||
var strms = body.streams;
|
|
||||||
for (var strm of strms) {
|
|
||||||
var streamLink = strm.url;
|
|
||||||
if (streamLink.length > 0) {
|
|
||||||
streams.push({
|
|
||||||
url: strm.url,
|
|
||||||
originalUrl: strm.url,
|
|
||||||
quality: `${strm.label} - Auto`,
|
|
||||||
headers: strm.headers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 7: {
|
|
||||||
if (media_type == "tv") {
|
|
||||||
id = `${id}/${s}/${e}`;
|
|
||||||
}
|
|
||||||
var api = `https://vidsrc.su/embed/${media_type}/${id}`;
|
|
||||||
var response = await new Client().get(api);
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw new Error(
|
|
||||||
"vidsrc.su unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
var body = response.body;
|
|
||||||
var sKey = "fixedServers = ";
|
|
||||||
var eKey = "];";
|
|
||||||
var start = body.indexOf(sKey);
|
|
||||||
if (start < 0) {
|
|
||||||
throw new Error(
|
|
||||||
"vidsrc.su videos unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
start += sKey.length;
|
|
||||||
|
|
||||||
var end = body.substring(start).indexOf(eKey) + start + 1;
|
|
||||||
var strms = body.substring(start, end);
|
|
||||||
|
|
||||||
// Split the data into lines
|
|
||||||
var lines = strms.split("\n");
|
|
||||||
|
|
||||||
// Regex to match URLs in quotes that start with https://
|
|
||||||
var regex = /url:\s*'(https:\/\/[^']+)'/;
|
|
||||||
var availableStreams = [];
|
|
||||||
|
|
||||||
// Process each line
|
|
||||||
lines.forEach((line) => {
|
|
||||||
var match = line.match(regex);
|
|
||||||
if (match && match[1]) {
|
|
||||||
// Extract the label from the line
|
|
||||||
var labelMatch = line.match(/label:\s*'([^']+)'/);
|
|
||||||
var label = labelMatch ? labelMatch[1] : "Unknown";
|
|
||||||
// Add to our results
|
|
||||||
availableStreams.push({
|
|
||||||
url: match[1],
|
|
||||||
label: label,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (var stream of availableStreams) {
|
|
||||||
var streamSplit = await this.extractStreams(stream.url, stream.label);
|
|
||||||
streams = [...streams, ...streamSplit];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nativeSubs) {
|
|
||||||
// subtitles
|
|
||||||
sKey = "const subtitles = ";
|
|
||||||
eKey = "];";
|
|
||||||
start = body.indexOf(sKey);
|
|
||||||
if (start < 0) {
|
|
||||||
break; // no need for native subtitle if not found.
|
|
||||||
}
|
|
||||||
start += sKey.length;
|
|
||||||
|
|
||||||
end = body.substring(start).indexOf(eKey) + start + 1;
|
|
||||||
var natSubs = JSON.parse(body.substring(start, end));
|
|
||||||
natSubs.forEach((sub) => {
|
|
||||||
subtitles.push({
|
|
||||||
file: sub.url,
|
|
||||||
label: sub.display,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 8: {
|
|
||||||
function reverse(str) {
|
|
||||||
return str.split("").reverse().join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (media_type == "tv") {
|
|
||||||
id = `${id}/${s}/${e}`;
|
|
||||||
}
|
|
||||||
var baseUrl = "https://embed.su";
|
|
||||||
var embedUrl = `${baseUrl}/embed/${media_type}/${id}`;
|
|
||||||
var response = await new Client().get(
|
|
||||||
embedUrl,
|
|
||||||
this.getHeaders(baseUrl)
|
|
||||||
);
|
|
||||||
|
|
||||||
var body = response.body;
|
|
||||||
var sKey = "JSON.parse(atob(`";
|
|
||||||
var start = body.indexOf(sKey) + sKey.length;
|
|
||||||
var end = body.substring(start).indexOf("`") + start;
|
|
||||||
var configHash = body.substring(start, end);
|
|
||||||
|
|
||||||
var config = JSON.parse(this.decodeBase64(configHash));
|
|
||||||
var encodedHash = this.decodeBase64(config.hash);
|
|
||||||
var decodeHash = reverse(
|
|
||||||
encodedHash
|
|
||||||
.split(".")
|
|
||||||
.map((item) => reverse(item))
|
|
||||||
.join("")
|
|
||||||
);
|
|
||||||
encodedHash = JSON.parse(this.decodeBase64(decodeHash));
|
|
||||||
var serverHash = encodedHash[0].hash;
|
|
||||||
|
|
||||||
var api = `${baseUrl}/api/e/${serverHash}`;
|
|
||||||
response = await new Client().get(api, this.getHeaders(baseUrl));
|
|
||||||
var jsonRes = JSON.parse(response.body);
|
|
||||||
|
|
||||||
streams = await this.extractStreams(
|
|
||||||
jsonRes.source,
|
|
||||||
"",
|
|
||||||
this.getHeaders(baseUrl),
|
|
||||||
baseUrl
|
|
||||||
);
|
|
||||||
if (nativeSubs) subtitles = jsonRes.subtitles;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
if (media_type == "tv") {
|
|
||||||
id = `${id}/${s}/${e}`;
|
|
||||||
}
|
|
||||||
var api = `${this.source.apiUrl}/api/getVideoSource?type=${media_type}&id=${id}`;
|
|
||||||
var response = await new Client().get(
|
|
||||||
api,
|
|
||||||
this.getHeaders(this.source.apiUrl)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
throw new Error(
|
|
||||||
"tom.autoembed.cc unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = JSON.parse(response.body);
|
|
||||||
var link = body.videoSource;
|
|
||||||
if (nativeSubs) subtitles = body.subtitles;
|
|
||||||
streams = await this.extractStreams(link);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (streams.length < 1) {
|
|
||||||
throw new Error(
|
|
||||||
"No streams unavailable\nPlease choose a different server"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var apiSubs = await this.getSubtitleList(tmdb, s, e);
|
|
||||||
streams[0].subtitles = [...subtitles, ...apiSubs];
|
|
||||||
|
|
||||||
return await this.sortStreams(streams);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For manga chapter pages
|
|
||||||
async getPageList() {
|
|
||||||
throw new Error("getPageList not implemented");
|
|
||||||
}
|
|
||||||
getFilterList() {
|
|
||||||
throw new Error("getFilterList not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: "pref_latest_time_window",
|
|
||||||
listPreference: {
|
|
||||||
title: "Preferred latest trend time window",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Day", "Week"],
|
|
||||||
entryValues: ["day", "week"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "pref_video_resolution",
|
|
||||||
listPreference: {
|
|
||||||
title: "Preferred video resolution",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Auto", "1080p", "720p", "360p"],
|
|
||||||
entryValues: ["auto", "1080", "720", "360"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "pref_content_priority",
|
|
||||||
listPreference: {
|
|
||||||
title: "Preferred content priority",
|
|
||||||
summary: "Choose which type of content to show first",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Movies", "Series"],
|
|
||||||
entryValues: ["movies", "series"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "autoembed_split_stream_quality",
|
|
||||||
"switchPreferenceCompat": {
|
|
||||||
"title": "Split stream into different quality streams",
|
|
||||||
"summary": "Split stream Auto into 360p/720p/1080p",
|
|
||||||
"value": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "autoembed_stream_source_3",
|
|
||||||
listPreference: {
|
|
||||||
title: "Preferred stream source",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: [
|
|
||||||
"tom.autoembed.cc",
|
|
||||||
"123embed.net",
|
|
||||||
"autoembed.cc - Indian languages",
|
|
||||||
"flicky.host - Indian languages",
|
|
||||||
"vidapi.click",
|
|
||||||
"hexa.watch",
|
|
||||||
"vidsrc.su",
|
|
||||||
"embed.su",
|
|
||||||
],
|
|
||||||
entryValues: ["1", "2", "3", "4", "5", "6", "7", "8"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "autoembed_pref_navtive_subtitle",
|
|
||||||
"switchPreferenceCompat": {
|
|
||||||
"title": "Use native subtitles as well",
|
|
||||||
"summary":
|
|
||||||
"Use subtitles provided by the source along with subtitle API",
|
|
||||||
"value": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "autoembed_pref_subtitle_source",
|
|
||||||
listPreference: {
|
|
||||||
title: "Preferred subtitle source",
|
|
||||||
summary: "",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["sub.wyzie.ru", "hexa.watch"],
|
|
||||||
entryValues: ["1", "2"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
const mangayomiSources = [{
|
|
||||||
"name": "Dramacool",
|
|
||||||
"lang": "all",
|
|
||||||
"baseUrl": "https://dramacool.com.tr",
|
|
||||||
"apiUrl": "",
|
|
||||||
"iconUrl": "https://www.google.com/s2/favicons?sz=128&domain=https://dramacool.com.tr",
|
|
||||||
"typeSource": "multi",
|
|
||||||
"itemType": 1,
|
|
||||||
"version": "1.0.0",
|
|
||||||
"pkgPath": "anime/src/all/dramacool.js"
|
|
||||||
}];
|
|
||||||
|
|
||||||
class DefaultExtension extends MProvider {
|
|
||||||
|
|
||||||
getHeaders(url) {
|
|
||||||
return {
|
|
||||||
'Referer': url,
|
|
||||||
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; WOW64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6788.76 Safari/537.36"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPreference(key) {
|
|
||||||
return new SharedPreferences().get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBaseUrl() {
|
|
||||||
return this.source.baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
async request(slug) {
|
|
||||||
const baseUrl = this.getBaseUrl()
|
|
||||||
var url = `${baseUrl}${slug}`
|
|
||||||
var res = await new Client().get(url, this.getHeaders(baseUrl));
|
|
||||||
var doc = new Document(res.body);
|
|
||||||
return doc
|
|
||||||
}
|
|
||||||
|
|
||||||
async getList(slug) {
|
|
||||||
var body = await this.request(slug);
|
|
||||||
var list = []
|
|
||||||
var hasNextPage = body.selectFirst("a.next.page-numbers").text.length > 0 ? true : false;
|
|
||||||
var items = body.select(".switch-block.list-episode-item > li")
|
|
||||||
items.forEach(item => {
|
|
||||||
var a = item.selectFirst("a")
|
|
||||||
var link = a.getHref.replace(this.getBaseUrl(), "")
|
|
||||||
var imageUrl = a.selectFirst("img").getSrc
|
|
||||||
var name = a.selectFirst("h3").text
|
|
||||||
|
|
||||||
list.push({ name, link, imageUrl })
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
return { list, hasNextPage };
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPopular(page) {
|
|
||||||
var slug = "/most-popular-drama"
|
|
||||||
return await this.getList(`${slug}/page/${page}/`)
|
|
||||||
}
|
|
||||||
|
|
||||||
get supportsLatest() {
|
|
||||||
throw new Error("supportsLatest not implemented");
|
|
||||||
}
|
|
||||||
async getLatestUpdates(page) {
|
|
||||||
var slug = this.getPreference("dramacool_latest_list")
|
|
||||||
return await this.getList(`/${slug}/page/${page}/`)
|
|
||||||
}
|
|
||||||
|
|
||||||
statusFromString(status) {
|
|
||||||
return {
|
|
||||||
"Ongoing": 0,
|
|
||||||
"Completed": 1,
|
|
||||||
}[status] ?? 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
async search(query, page, filters) {
|
|
||||||
var slug = `/page/${page}/?type=movies&s=${query}`
|
|
||||||
return await this.getList(slug)
|
|
||||||
}
|
|
||||||
|
|
||||||
formatReleaseDate(str) {
|
|
||||||
var timeSplit = str.split(" ")
|
|
||||||
var t = parseInt(timeSplit[0])
|
|
||||||
var unit = timeSplit[1]
|
|
||||||
|
|
||||||
var mins = 0
|
|
||||||
var mons = 0
|
|
||||||
if (unit.includes('minute')) {
|
|
||||||
mins = t;
|
|
||||||
} else if (unit.includes('hour')) {
|
|
||||||
mins = t * 60;
|
|
||||||
} else if (unit.includes('day')) {
|
|
||||||
mins = t * 60 * 24;
|
|
||||||
} else if (unit.includes('week')) {
|
|
||||||
mins = t * 60 * 24 * 7;
|
|
||||||
} else if (unit.includes('month')) {
|
|
||||||
mons = t;
|
|
||||||
}
|
|
||||||
var now = new Date();
|
|
||||||
now.setMinutes(now.getMinutes() - mins)
|
|
||||||
now.setMinutes(now.getMonth() - mons)
|
|
||||||
var pastDate = new Date(now);
|
|
||||||
return "" + pastDate.valueOf();
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDetail(url) {
|
|
||||||
if (url.includes("-episode")) {
|
|
||||||
url = '/series' + url.split("-episode")[0] + "/"
|
|
||||||
} else if (url.includes("-full-movie")) {
|
|
||||||
url = '/series' + url.split("-full-movie")[0] + "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
var body = await this.request(url);
|
|
||||||
var infos = body.select(".info > p")
|
|
||||||
|
|
||||||
var name = body.selectFirst("h1").text.trim()
|
|
||||||
var imageUrl = body.selectFirst(".img").selectFirst("img").getSrc
|
|
||||||
var isDescription = infos[1].text.includes("Description")
|
|
||||||
var description = isDescription ? infos[2].text.trim() : ""
|
|
||||||
var link = `${this.getBaseUrl()}${url}`
|
|
||||||
var statusIndex = infos.at(-3).text.includes("Status:") ? -3 : -2
|
|
||||||
var status = this.statusFromString(infos.at(statusIndex).selectFirst("a").text)
|
|
||||||
var genre = []
|
|
||||||
infos.at(-1).select("a").forEach(a => genre.push(a.text.trim()))
|
|
||||||
|
|
||||||
var chapters = []
|
|
||||||
var epLists = body.select("ul.list-episode-item-2.all-episode > li")
|
|
||||||
for (var ep of epLists) {
|
|
||||||
var a = ep.selectFirst('a')
|
|
||||||
var epLink = a.getHref.replace(this.getBaseUrl(), "")
|
|
||||||
var epName = a.selectFirst("h3").text.replace(name + " ", "")
|
|
||||||
var scanlator = a.selectFirst("span.type").text
|
|
||||||
var dateUpload = this.formatReleaseDate(a.selectFirst("span.time").text)
|
|
||||||
chapters.push({
|
|
||||||
name: epName,
|
|
||||||
url: epLink,
|
|
||||||
scanlator,
|
|
||||||
dateUpload
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return { name, imageUrl, description, link, status, genre, chapters }
|
|
||||||
}
|
|
||||||
|
|
||||||
async splitStreams(streams, server) {
|
|
||||||
var pref = this.getPreference("dramacool_split_stream_quality");
|
|
||||||
if (!pref) return streams
|
|
||||||
var autoStream = streams[0]
|
|
||||||
var autoStreamUrl = autoStream.url
|
|
||||||
var hdr = autoStream.headers
|
|
||||||
var hostUrl = ""
|
|
||||||
if (server == "Asianload") {
|
|
||||||
hostUrl = autoStreamUrl.substring(0, autoStreamUrl.indexOf("/media"))
|
|
||||||
} else {
|
|
||||||
hostUrl = autoStreamUrl.substring(0, autoStreamUrl.indexOf("master.m3u8"))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var response = await new Client().get(autoStreamUrl, hdr)
|
|
||||||
var body = response.body;
|
|
||||||
var lines = body.split('\n');
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].startsWith('#EXT-X-STREAM-INF:')) {
|
|
||||||
var resolution = lines[i].match(/RESOLUTION=(\d+x\d+)/)[1];
|
|
||||||
resolution = `${server} - ${resolution}`
|
|
||||||
var m3u8Url = lines[i + 1].trim();
|
|
||||||
m3u8Url = hostUrl + m3u8Url
|
|
||||||
streams.push({
|
|
||||||
url: m3u8Url,
|
|
||||||
originalUrl: m3u8Url,
|
|
||||||
quality: resolution,
|
|
||||||
headers: hdr
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return streams
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeBase64(f) {
|
|
||||||
var g = {},
|
|
||||||
b = 65,
|
|
||||||
d = 0,
|
|
||||||
a, c = 0,
|
|
||||||
h, e = "",
|
|
||||||
k = String.fromCharCode,
|
|
||||||
l = f.length;
|
|
||||||
for (a = ""; 91 > b;) a += k(b++);
|
|
||||||
a += a.toLowerCase() + "0123456789+/";
|
|
||||||
for (b = 0; 64 > b; b++) g[a.charAt(b)] = b;
|
|
||||||
for (a = 0; a < l; a++)
|
|
||||||
for (b = g[f.charAt(a)], d = (d << 6) + b, c += 6; 8 <= c;)((h = d >>> (c -= 8) & 255) || a < l - 2) && (e += k(h));
|
|
||||||
return e
|
|
||||||
};
|
|
||||||
|
|
||||||
async extractDramacoolEmbed(doc) {
|
|
||||||
var streams = []
|
|
||||||
var script = doc.select('script').at(-2)
|
|
||||||
var unpack = unpackJs(script.text)
|
|
||||||
|
|
||||||
var skey = 'hls2":"'
|
|
||||||
var eKey = '"};jwplayer'
|
|
||||||
var start = unpack.indexOf(skey) + skey.length
|
|
||||||
var end = unpack.indexOf(eKey, start)
|
|
||||||
var track = unpack.substring(start, end)
|
|
||||||
|
|
||||||
streams.push({
|
|
||||||
url: track,
|
|
||||||
originalUrl: track,
|
|
||||||
quality: "Dramacool - Auto",
|
|
||||||
headers: this.getHeaders("https://dramacool.men/")
|
|
||||||
});
|
|
||||||
|
|
||||||
streams = await this.splitStreams(streams, "Dramacool")
|
|
||||||
|
|
||||||
return streams
|
|
||||||
}
|
|
||||||
|
|
||||||
async extractAsianLoadEmbed(doc) {
|
|
||||||
var streams = []
|
|
||||||
var script = doc.select('script').at(-2)
|
|
||||||
var unpack = script.text
|
|
||||||
|
|
||||||
// tracks
|
|
||||||
var skey = '|image|'
|
|
||||||
var eKey = '|'
|
|
||||||
var start = unpack.indexOf(skey) + skey.length
|
|
||||||
var end = unpack.indexOf(eKey, start)
|
|
||||||
var track = unpack.substring(start, end)
|
|
||||||
var streamUrl = this.decodeBase64(track)
|
|
||||||
|
|
||||||
// subs
|
|
||||||
eKey = "|default|"
|
|
||||||
var end = unpack.indexOf(eKey)
|
|
||||||
var subs = []
|
|
||||||
if (end != -1) {
|
|
||||||
skey = "|type|"
|
|
||||||
var start = unpack.indexOf(skey) + skey.length
|
|
||||||
var subTracks = unpack.substring(start, end).split("|")
|
|
||||||
subs.push({
|
|
||||||
file: this.decodeBase64(subTracks[1]),
|
|
||||||
label: subTracks[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
streams.push({
|
|
||||||
url: streamUrl,
|
|
||||||
originalUrl: streamUrl,
|
|
||||||
quality: "Asianload - Auto",
|
|
||||||
subtitles: subs,
|
|
||||||
headers: this.getHeaders("https://asianload.cfd/")
|
|
||||||
});
|
|
||||||
|
|
||||||
// Download url
|
|
||||||
skey = '|_blank|'
|
|
||||||
eKey = '|'
|
|
||||||
start = unpack.indexOf(skey) + skey.length
|
|
||||||
end = unpack.indexOf(eKey, start)
|
|
||||||
track = unpack.substring(start, end)
|
|
||||||
var downUrl = this.decodeBase64(track)
|
|
||||||
|
|
||||||
streams.push({
|
|
||||||
url: downUrl,
|
|
||||||
originalUrl: downUrl,
|
|
||||||
quality: "Asianload - Direct download",
|
|
||||||
headers: this.getHeaders("https://asianload.cfd/")
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
streams = await this.splitStreams(streams, "Asianload")
|
|
||||||
|
|
||||||
return streams
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sorts streams based on user preference.
|
|
||||||
async sortStreams(streams) {
|
|
||||||
var sortedStreams = [];
|
|
||||||
|
|
||||||
var copyStreams = streams.slice()
|
|
||||||
var pref = this.getPreference("dramacool_video_resolution");
|
|
||||||
for (var i in streams) {
|
|
||||||
var stream = streams[i];
|
|
||||||
if (stream.quality.indexOf(pref) > -1) {
|
|
||||||
sortedStreams.push(stream);
|
|
||||||
var index = copyStreams.indexOf(stream);
|
|
||||||
if (index > -1) {
|
|
||||||
copyStreams.splice(index, 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [...sortedStreams, ...copyStreams]
|
|
||||||
}
|
|
||||||
|
|
||||||
// For anime episode video list
|
|
||||||
async getVideoList(url) {
|
|
||||||
var res = await this.request(url)
|
|
||||||
var iframe = res.selectFirst("iframe").attr("src").trim()
|
|
||||||
if (iframe == "") {
|
|
||||||
throw new Error("No iframe found")
|
|
||||||
}
|
|
||||||
|
|
||||||
var streams = []
|
|
||||||
|
|
||||||
res = await new Client().get(iframe)
|
|
||||||
var doc = new Document(res.body);
|
|
||||||
|
|
||||||
if (iframe.includes("//dramacool")) {
|
|
||||||
streams = await this.extractDramacoolEmbed(doc)
|
|
||||||
} else if (iframe.includes("//asianload")) {
|
|
||||||
streams = await this.extractAsianLoadEmbed(doc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.sortStreams(streams)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: 'dramacool_latest_list',
|
|
||||||
listPreference: {
|
|
||||||
title: 'Preferred latest list',
|
|
||||||
summary: 'Choose which type of content to be shown "Lastest"',
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Drama", "Movie", "KShow"],
|
|
||||||
entryValues: ["recently-added-drama", "recently-added-movie", "recently-added-kshow"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'dramacool_split_stream_quality',
|
|
||||||
switchPreferenceCompat: {
|
|
||||||
title: 'Split stream into different quality streams',
|
|
||||||
summary: "Split stream Auto into 360p/720p/1080p",
|
|
||||||
value: true
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
key: 'dramacool_video_resolution',
|
|
||||||
listPreference: {
|
|
||||||
title: 'Preferred video resolution',
|
|
||||||
summary: '',
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Auto", "Direct download", "720p", "480", "360p"],
|
|
||||||
entryValues: ["Auto", "download", "720", "480", "360"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,333 +0,0 @@
|
|||||||
const mangayomiSources = [{
|
|
||||||
"name": "NetMirror",
|
|
||||||
"id": 446414301,
|
|
||||||
"lang": "all",
|
|
||||||
"baseUrl": "https://netfree2.cc",
|
|
||||||
"apiUrl": "https://netfree2.cc",
|
|
||||||
"iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/all.netflixmirror.png",
|
|
||||||
"typeSource": "single",
|
|
||||||
"itemType": 1,
|
|
||||||
"version": "0.3.4",
|
|
||||||
"pkgPath": "anime/src/all/netflixmirror.js"
|
|
||||||
}];
|
|
||||||
|
|
||||||
class DefaultExtension extends MProvider {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.client = new Client();
|
|
||||||
}
|
|
||||||
|
|
||||||
getPreference(key) {
|
|
||||||
const preferences = new SharedPreferences();
|
|
||||||
return preferences.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
getTVBaseUrl() {
|
|
||||||
return this.getPreference("netmirror_override_tv_base_url");
|
|
||||||
}
|
|
||||||
|
|
||||||
getServiceDetails() {
|
|
||||||
return this.getPreference("netmirror_pref_service");
|
|
||||||
}
|
|
||||||
|
|
||||||
getPoster(id, service) {
|
|
||||||
if (service === "nf")
|
|
||||||
return `https://imgcdn.media/poster/v/${id}.jpg`
|
|
||||||
if (service === "pv")
|
|
||||||
return `https://imgcdn.media/pv/480/${id}.jpg`
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCookie(service) {
|
|
||||||
const preferences = new SharedPreferences();
|
|
||||||
let cookie = preferences.getString("cookie", "");
|
|
||||||
var cookie_ts = parseInt(preferences.getString("cookie_ts", "0"));
|
|
||||||
var now_ts = parseInt(new Date().getTime() / 1000);
|
|
||||||
|
|
||||||
// Cookie lasts for 24hrs but still checking for 12hrs
|
|
||||||
if (now_ts - cookie_ts > 60 * 60 * 12) {
|
|
||||||
var baseUrl = this.getTVBaseUrl()
|
|
||||||
const check = await this.client.get(baseUrl + `/mobile/home`, { "cookie": cookie });
|
|
||||||
const hDocBody = new Document(check.body).selectFirst("body")
|
|
||||||
|
|
||||||
const addhash = hDocBody.attr("data-addhash");
|
|
||||||
const data_time = hDocBody.attr("data-time");
|
|
||||||
|
|
||||||
var res = await this.client.post(`${baseUrl}/tv/p.php`, { "cookie": "" }, { "hash": addhash });
|
|
||||||
cookie = res.headers["set-cookie"];
|
|
||||||
preferences.setString("cookie", cookie);
|
|
||||||
preferences.setString("cookie_ts", data_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
service = service ?? this.getServiceDetails();
|
|
||||||
|
|
||||||
return `ott=${service}; ${cookie}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async request(slug, service = null, cookie = null) {
|
|
||||||
var service = service ?? this.getServiceDetails();
|
|
||||||
var cookie = cookie ?? await this.getCookie();
|
|
||||||
|
|
||||||
var srv = ""
|
|
||||||
if (service === "pv") srv = "/" + service
|
|
||||||
var url = this.getTVBaseUrl() + "/tv" + srv + slug
|
|
||||||
return (await this.client.get(url, { "cookie": cookie })).body;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async getHome(body) {
|
|
||||||
var service = this.getServiceDetails();
|
|
||||||
var list = []
|
|
||||||
if (service === "nf") {
|
|
||||||
var body = await this.request("/home", service)
|
|
||||||
var elements = new Document(body).select("a.slider-item.boxart-container.open-modal.focusme");
|
|
||||||
|
|
||||||
elements.forEach(item => {
|
|
||||||
var id = item.attr("data-post")
|
|
||||||
if (id.length > 0) {
|
|
||||||
var imageUrl = this.getPoster(id, service)
|
|
||||||
// Having no name breaks the script so having "id" as name
|
|
||||||
var name = `\n${id}`
|
|
||||||
list.push({ name, imageUrl, link: id })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
var body = await this.request("/homepage.php", service)
|
|
||||||
var elements = JSON.parse(body).post
|
|
||||||
|
|
||||||
elements.forEach(item => {
|
|
||||||
var ids = item.ids
|
|
||||||
ids.split(",").forEach(id => {
|
|
||||||
var imageUrl = this.getPoster(id, service)
|
|
||||||
// Having no name breaks the script so having "id" as name
|
|
||||||
var name = `\n${id}`
|
|
||||||
list.push({ name, imageUrl, link: id })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
list: list,
|
|
||||||
hasNextPage: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPopular(page) {
|
|
||||||
return await this.getHome()
|
|
||||||
}
|
|
||||||
async getLatestUpdates(page) {
|
|
||||||
return await this.getHome()
|
|
||||||
}
|
|
||||||
|
|
||||||
async search(query, page, filters) {
|
|
||||||
var service = this.getServiceDetails();
|
|
||||||
const data = JSON.parse(await this.request(`/search.php?s=${query}`, service));
|
|
||||||
const list = [];
|
|
||||||
data.searchResult.map(async (res) => {
|
|
||||||
const id = res.id;
|
|
||||||
list.push({ name: res.t, imageUrl: this.getPoster(id, service), link: id });
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
list: list,
|
|
||||||
hasNextPage: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDetail(url) {
|
|
||||||
var service = this.getServiceDetails();
|
|
||||||
var cookie = await this.getCookie(service);
|
|
||||||
var linkSlug = "https://netflix.com/title/"
|
|
||||||
if (service === "pv") linkSlug = `https://www.primevideo.com/detail/`
|
|
||||||
|
|
||||||
// Check needed while refreshing existing data
|
|
||||||
var vidId = url
|
|
||||||
if (url.includes(linkSlug)) vidId = url.replaceAll(linkSlug, '')
|
|
||||||
|
|
||||||
const data = JSON.parse(await this.request(`/post.php?id=${vidId}`));
|
|
||||||
const name = data.title;
|
|
||||||
const genre = [data.ua, ...(data.genre || '').split(',').map(g => g.trim())];
|
|
||||||
const description = data.desc;
|
|
||||||
let episodes = [];
|
|
||||||
|
|
||||||
var seasons = data.season
|
|
||||||
if (seasons) {
|
|
||||||
let newEpisodes = [];
|
|
||||||
await Promise.all(seasons.map(async (season) => {
|
|
||||||
const eps = await this.getEpisodes(name, vidId, season.id, 1, service, cookie);
|
|
||||||
newEpisodes.push(...eps);
|
|
||||||
}));
|
|
||||||
episodes.push(...newEpisodes);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// For movies aka if there are no seasons and episodes
|
|
||||||
episodes.push({
|
|
||||||
name: `Movie`,
|
|
||||||
url: vidId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var link = `${linkSlug}${vidId}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
name, imageUrl: this.getPoster(vidId, service), link, description, status: 1, genre, episodes
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEpisodes(name, eid, sid, page, service, cookie) {
|
|
||||||
const episodes = [];
|
|
||||||
let pg = page;
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(await this.request(`/episodes.php?s=${sid}&series=${eid}&page=${pg}`, service, cookie));
|
|
||||||
|
|
||||||
data.episodes?.forEach(ep => {
|
|
||||||
var season = ep.s.replace('S', 'Season ')
|
|
||||||
var epNum = ep.ep.replace("E", "")
|
|
||||||
var epText = `Episode ${epNum}`
|
|
||||||
var title = ep.t
|
|
||||||
title = title == epText ? title : `${epText}: ${title}`
|
|
||||||
|
|
||||||
episodes.push({
|
|
||||||
name: `${season} ${title}`,
|
|
||||||
url: ep.id
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.nextPageShow === 0) break;
|
|
||||||
pg++;
|
|
||||||
} catch (_) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return episodes.reverse();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sorts streams based on user preference.
|
|
||||||
async sortStreams(streams) {
|
|
||||||
var sortedStreams = [];
|
|
||||||
|
|
||||||
var copyStreams = streams.slice()
|
|
||||||
var pref = this.getPreference("netmirror_pref_video_resolution");
|
|
||||||
for (var i in streams) {
|
|
||||||
var stream = streams[i];
|
|
||||||
if (stream.quality.indexOf(pref) > -1) {
|
|
||||||
sortedStreams.push(stream);
|
|
||||||
var index = copyStreams.indexOf(stream);
|
|
||||||
if (index > -1) {
|
|
||||||
copyStreams.splice(index, 1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [...sortedStreams, ...copyStreams]
|
|
||||||
}
|
|
||||||
|
|
||||||
async getVideoList(url) {
|
|
||||||
|
|
||||||
var baseUrl = this.getTVBaseUrl()
|
|
||||||
var url = `/playlist.php?id=${url}`
|
|
||||||
const data = JSON.parse(await this.request(url));
|
|
||||||
|
|
||||||
|
|
||||||
let videoList = [];
|
|
||||||
let subtitles = [];
|
|
||||||
let audios = [];
|
|
||||||
var playlist = data[0]
|
|
||||||
var source = playlist.sources[0]
|
|
||||||
|
|
||||||
var link = baseUrl + source.file;
|
|
||||||
var headers =
|
|
||||||
{
|
|
||||||
'Origin': baseUrl,
|
|
||||||
'Referer': `${baseUrl}/`
|
|
||||||
};
|
|
||||||
|
|
||||||
// Auto
|
|
||||||
videoList.push({ url: link, quality: "Auto", "originalUrl": link, headers });
|
|
||||||
|
|
||||||
var resp = await this.client.get(link, headers);
|
|
||||||
|
|
||||||
if (resp.statusCode === 200) {
|
|
||||||
const masterPlaylist = resp.body;
|
|
||||||
|
|
||||||
if (masterPlaylist.indexOf("#EXT-X-STREAM-INF:") > 1) {
|
|
||||||
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
masterPlaylist.substringAfter('#EXT-X-STREAM-INF:').split('#EXT-X-STREAM-INF:').forEach(it => {
|
|
||||||
var quality = `${it.substringAfter('RESOLUTION=').substringAfter('x').substringBefore(',')}p`;
|
|
||||||
let videoUrl = it.substringAfter('\n').substringBefore('\n');
|
|
||||||
|
|
||||||
if (!videoUrl.startsWith('http')) {
|
|
||||||
videoUrl = resp.request.url.substringBeforeLast('/') + `/${videoUrl}`;
|
|
||||||
}
|
|
||||||
headers['Host'] = videoUrl.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/)[1]
|
|
||||||
videoList.push({ url: videoUrl, quality, originalUrl: videoUrl, headers });
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ("tracks" in playlist) {
|
|
||||||
await Promise.all(playlist.tracks.map(async (track) => {
|
|
||||||
if (track.kind == 'captions') {
|
|
||||||
var subUrl = track.file
|
|
||||||
subUrl = subUrl.startsWith("//") ? `https:${subUrl}` : subUrl;
|
|
||||||
var subText = await this.client.get(subUrl)
|
|
||||||
subtitles.push({
|
|
||||||
label: track.label,
|
|
||||||
file: subText.body
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
videoList[0].audios = audios;
|
|
||||||
videoList[0].subtitles = subtitles;
|
|
||||||
return this.sortStreams(videoList);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSourcePreferences() {
|
|
||||||
return [{
|
|
||||||
key: "netmirror_override_tv_base_url",
|
|
||||||
editTextPreference: {
|
|
||||||
title: "Override tv base url",
|
|
||||||
summary: "",
|
|
||||||
value: "https://netfree2.cc",
|
|
||||||
dialogTitle: "Override base url",
|
|
||||||
dialogMessage: "",
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
key: 'netmirror_pref_service',
|
|
||||||
listPreference: {
|
|
||||||
title: 'Preferred OTT service',
|
|
||||||
summary: '',
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Net mirror", "Prime mirror"],
|
|
||||||
entryValues: ["nf", "pv",]
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
key: 'netmirror_pref_video_resolution',
|
|
||||||
listPreference: {
|
|
||||||
title: 'Preferred video resolution',
|
|
||||||
summary: '',
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["1080p", "720p", "480p"],
|
|
||||||
entryValues: ["1080", "720", "480"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
const mangayomiSources = [
|
|
||||||
{
|
|
||||||
"name": "Soaper",
|
|
||||||
"id": 764093578,
|
|
||||||
"lang": "all",
|
|
||||||
"baseUrl": "https://soaper.cc",
|
|
||||||
"apiUrl": "",
|
|
||||||
"iconUrl":
|
|
||||||
"https://www.google.com/s2/favicons?sz=128&domain=https://soaper.cc/",
|
|
||||||
"typeSource": "multi",
|
|
||||||
"version": "1.0.5",
|
|
||||||
"itemType": 1,
|
|
||||||
"dateFormat": "",
|
|
||||||
"dateFormatLocale": "",
|
|
||||||
"pkgPath": "anime/src/all/soaper.js"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Authors: - Swakshan, kodjodevf
|
|
||||||
|
|
||||||
class DefaultExtension extends MProvider {
|
|
||||||
getHeaders(url) {
|
|
||||||
return {
|
|
||||||
Referer: url,
|
|
||||||
Origin: url,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getPreference(key) {
|
|
||||||
return new SharedPreferences().get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
getBasueUrl() {
|
|
||||||
return this.getPreference("soaper_override_base_url");
|
|
||||||
}
|
|
||||||
|
|
||||||
async request(slug) {
|
|
||||||
const baseUrl = this.getBasueUrl();
|
|
||||||
var url = `${baseUrl}/${slug}`;
|
|
||||||
var res = await new Client().get(url, this.getHeaders(baseUrl));
|
|
||||||
var doc = new Document(res.body);
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
async requestJSON(slug, data) {
|
|
||||||
const baseUrl = this.getBasueUrl();
|
|
||||||
var url = `${baseUrl}/${slug}`;
|
|
||||||
var res = await new Client().post(url, this.getHeaders(baseUrl), data);
|
|
||||||
return JSON.parse(res.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
async formatList(slug, page) {
|
|
||||||
const baseUrl = this.getPreference("soaper_override_base_url");
|
|
||||||
slug = parseInt(page) > 1 ? `${slug}?page=${page}` : slug;
|
|
||||||
var doc = await this.request(slug);
|
|
||||||
var list = [];
|
|
||||||
var movies = doc.select(".thumbnail.text-center");
|
|
||||||
|
|
||||||
for (var movie of movies) {
|
|
||||||
var linkSection = movie.selectFirst("div.img-group > a");
|
|
||||||
var link = linkSection.getHref.substring(1);
|
|
||||||
var poster = linkSection.selectFirst("img").getSrc;
|
|
||||||
var imageUrl = `${baseUrl}${poster}`;
|
|
||||||
var name = movie.selectFirst("h5").selectFirst("a").text;
|
|
||||||
|
|
||||||
list.push({ name, imageUrl, link });
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasNextPage = false;
|
|
||||||
if (slug.indexOf("search.html?") == -1) {
|
|
||||||
var pagination = doc.select("ul.pagination > li");
|
|
||||||
var last_page_num = parseInt(pagination[pagination.length - 2].text);
|
|
||||||
hasNextPage = page < last_page_num ? true : false;
|
|
||||||
}
|
|
||||||
return { list, hasNextPage };
|
|
||||||
}
|
|
||||||
|
|
||||||
async filterList(year = "all", genre = "all", sort = "new", page = 1) {
|
|
||||||
year = year == "all" ? "" : `/year/${year}`;
|
|
||||||
genre = genre == "all" ? "" : `/cat/${genre}`;
|
|
||||||
sort = sort == "new" ? "" : `/sort/${sort}`;
|
|
||||||
|
|
||||||
var slug = `${sort}${year}${genre}`;
|
|
||||||
var movieList = await this.formatList(`movielist${slug}`, page);
|
|
||||||
var seriesList = await this.formatList(`tvlist${slug}`, page);
|
|
||||||
|
|
||||||
var list = [];
|
|
||||||
var priority = this.getPreference("soaper_content_priority");
|
|
||||||
if (priority === "series") {
|
|
||||||
list = [...seriesList.list, ...movieList.list];
|
|
||||||
} else {
|
|
||||||
list = [...movieList.list, ...seriesList.list];
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasNextPage = seriesList.hasNextPage || movieList.hasNextPage;
|
|
||||||
|
|
||||||
return { list, hasNextPage };
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPopular(page) {
|
|
||||||
return await this.filterList("all", "all", "hot", page);
|
|
||||||
}
|
|
||||||
get supportsLatest() {
|
|
||||||
throw new Error("supportsLatest not implemented");
|
|
||||||
}
|
|
||||||
async getLatestUpdates(page) {
|
|
||||||
return await this.filterList("all", "all", "new", page);
|
|
||||||
}
|
|
||||||
|
|
||||||
async search(query, page, filters) {
|
|
||||||
var seriesList = [];
|
|
||||||
var movieList = [];
|
|
||||||
var list = [];
|
|
||||||
|
|
||||||
var res = await this.formatList(`search.html?keyword=${query}`, 1);
|
|
||||||
var movies = res["list"];
|
|
||||||
|
|
||||||
for (var movie of movies) {
|
|
||||||
var link = movie.link;
|
|
||||||
if (link.indexOf("tv_") != -1) {
|
|
||||||
seriesList.push(movie);
|
|
||||||
} else {
|
|
||||||
movieList.push(movie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var priority = this.getPreference("soaper_content_priority");
|
|
||||||
if (priority === "series") {
|
|
||||||
list = [...seriesList, ...movieList];
|
|
||||||
} else {
|
|
||||||
list = [...movieList, ...seriesList];
|
|
||||||
}
|
|
||||||
|
|
||||||
return { list, hasNextPage: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDetail(url) {
|
|
||||||
const baseUrl = this.getPreference("soaper_override_base_url");
|
|
||||||
var slug = url.replace(`${baseUrl}/`,'')
|
|
||||||
var doc = await this.request(slug);
|
|
||||||
var name = doc
|
|
||||||
.selectFirst(".col-sm-12.col-lg-12.text-center")
|
|
||||||
.selectFirst("h4")
|
|
||||||
.text.trim();
|
|
||||||
var poster = doc
|
|
||||||
.selectFirst(".thumbnail.text-center")
|
|
||||||
.selectFirst("img").getSrc;
|
|
||||||
var imageUrl = `${baseUrl}${poster}`;
|
|
||||||
|
|
||||||
var description = doc.selectFirst("p#wrap").text.trim();
|
|
||||||
var link = `${baseUrl}/${slug}`;
|
|
||||||
|
|
||||||
var chapters = [];
|
|
||||||
if (slug.indexOf("tv_") != -1) {
|
|
||||||
var seasonList = doc.select(".alert.alert-info-ex.col-sm-12");
|
|
||||||
var seasonCount = seasonList.length;
|
|
||||||
for (var season of seasonList) {
|
|
||||||
var eps = season.select(".col-sm-12.col-md-6.col-lg-4.myp1");
|
|
||||||
for (var ep of eps) {
|
|
||||||
var epLinkSection = ep.selectFirst("a");
|
|
||||||
var epLink = epLinkSection.getHref.substring(1);
|
|
||||||
var epName = epLinkSection.text;
|
|
||||||
|
|
||||||
chapters.push({
|
|
||||||
name: `S${seasonCount}E${epName}`,
|
|
||||||
url: epLink,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
seasonCount--;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
chapters.push({
|
|
||||||
name: "Movie",
|
|
||||||
url: slug,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { name, imageUrl, description, link, chapters };
|
|
||||||
}
|
|
||||||
// For anime episode video list
|
|
||||||
async getVideoList(url) {
|
|
||||||
var body = await this.request(url);
|
|
||||||
var baseUrl = this.getBasueUrl();
|
|
||||||
var streams = [];
|
|
||||||
|
|
||||||
// Traditional servers
|
|
||||||
var eId = body.selectFirst("#hId").attr("value");
|
|
||||||
var hIsW = body.selectFirst("#hIsW").attr("value");
|
|
||||||
var apiType = url[0].toUpperCase();
|
|
||||||
|
|
||||||
var servers = [0, 1];
|
|
||||||
for (var serverNum of servers) {
|
|
||||||
var serverName = body.selectFirst(`#server_button_${serverNum}`).text;
|
|
||||||
if (serverName.length < 1) continue;
|
|
||||||
var data = {
|
|
||||||
pass: eId,
|
|
||||||
param: "",
|
|
||||||
extra: "1",
|
|
||||||
e2: hIsW,
|
|
||||||
server: "" + serverNum,
|
|
||||||
};
|
|
||||||
var res = await this.requestJSON(
|
|
||||||
`home/index/Get${apiType}InfoAjax`,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
|
|
||||||
var streamUrl = baseUrl + res.val;
|
|
||||||
var subs = [];
|
|
||||||
var vidSubs = res.subs;
|
|
||||||
if (vidSubs != null && vidSubs.length > 0) {
|
|
||||||
for (var sub of vidSubs) {
|
|
||||||
subs.push({
|
|
||||||
file: baseUrl + sub.path,
|
|
||||||
label: sub.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
streams.push({
|
|
||||||
url: streamUrl,
|
|
||||||
originalUrl: streamUrl,
|
|
||||||
quality: serverName,
|
|
||||||
subtitles: subs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download servers
|
|
||||||
var modal_footer = body.select(".modal-footer > a");
|
|
||||||
if (modal_footer.length > 0) {
|
|
||||||
modal_footer.reverse();
|
|
||||||
for (var item of modal_footer) {
|
|
||||||
var dSlug = item.getHref;
|
|
||||||
var dBody = await this.request(dSlug);
|
|
||||||
|
|
||||||
var res = dBody.selectFirst("#res").attr("value");
|
|
||||||
var mb = dBody.selectFirst("#mb").attr("value");
|
|
||||||
var streamLink = dBody.selectFirst("#link").attr("value");
|
|
||||||
|
|
||||||
streams.push({
|
|
||||||
url: streamLink,
|
|
||||||
originalUrl: streamLink,
|
|
||||||
quality: `Download Server: ${res} [${mb}]`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return streams;
|
|
||||||
}
|
|
||||||
// For manga chapter pages
|
|
||||||
async getPageList() {
|
|
||||||
throw new Error("getPageList not implemented");
|
|
||||||
}
|
|
||||||
getFilterList() {
|
|
||||||
throw new Error("getFilterList not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: "soaper_override_base_url",
|
|
||||||
editTextPreference: {
|
|
||||||
title: "Override base url",
|
|
||||||
summary: "Default: https://soaper.cc",
|
|
||||||
value: "https://soaper.cc",
|
|
||||||
dialogTitle: "Override base url",
|
|
||||||
dialogMessage: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "soaper_content_priority",
|
|
||||||
listPreference: {
|
|
||||||
title: "Preferred content priority",
|
|
||||||
summary: "Choose which type of content to show first",
|
|
||||||
valueIndex: 0,
|
|
||||||
entries: ["Movies", "Series"],
|
|
||||||
entryValues: ["movies", "series"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,660 +0,0 @@
|
|||||||
const mangayomiSources = [{
|
|
||||||
"name": "Torrentio (Torrent)",
|
|
||||||
"lang": "all",
|
|
||||||
"baseUrl": "https://torrentio.strem.fun",
|
|
||||||
"apiUrl": "",
|
|
||||||
"iconUrl": "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/javascript/icon/all.torrentio.png",
|
|
||||||
"typeSource": "torrent",
|
|
||||||
"isManga": false,
|
|
||||||
"itemType": 1,
|
|
||||||
"version": "0.0.25",
|
|
||||||
"pkgPath": "anime/src/all/torrentio.js"
|
|
||||||
}];
|
|
||||||
|
|
||||||
class DefaultExtension extends MProvider {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.client = new Client();
|
|
||||||
}
|
|
||||||
|
|
||||||
justWatchQuery() {
|
|
||||||
return `
|
|
||||||
query GetPopularTitles(
|
|
||||||
$country: Country!,
|
|
||||||
$first: Int!,
|
|
||||||
$language: Language!,
|
|
||||||
$offset: Int,
|
|
||||||
$searchQuery: String,
|
|
||||||
$packages: [String!]!,
|
|
||||||
$objectTypes: [ObjectType!]!,
|
|
||||||
$popularTitlesSortBy: PopularTitlesSorting!,
|
|
||||||
$releaseYear: IntFilter
|
|
||||||
) {
|
|
||||||
popularTitles(
|
|
||||||
country: $country
|
|
||||||
first: $first
|
|
||||||
offset: $offset
|
|
||||||
sortBy: $popularTitlesSortBy
|
|
||||||
filter: {
|
|
||||||
objectTypes: $objectTypes,
|
|
||||||
searchQuery: $searchQuery,
|
|
||||||
packages: $packages,
|
|
||||||
genres: [],
|
|
||||||
excludeGenres: [],
|
|
||||||
releaseYear: $releaseYear
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
edges {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
objectType
|
|
||||||
content(country: $country, language: $language) {
|
|
||||||
fullPath
|
|
||||||
title
|
|
||||||
shortDescription
|
|
||||||
externalIds {
|
|
||||||
imdbId
|
|
||||||
}
|
|
||||||
posterUrl
|
|
||||||
genres {
|
|
||||||
translation(language: $language)
|
|
||||||
}
|
|
||||||
credits {
|
|
||||||
name
|
|
||||||
role
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pageInfo {
|
|
||||||
hasPreviousPage
|
|
||||||
hasNextPage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`.trim();
|
|
||||||
}
|
|
||||||
async makeGraphQLRequest(query, variables) {
|
|
||||||
const res = await this.client.post("https://apis.justwatch.com/graphql", { "Content-Type": "application/json" },
|
|
||||||
{
|
|
||||||
query: query,
|
|
||||||
variables
|
|
||||||
});
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
async searchAnimeRequest(page, query) {
|
|
||||||
const preferences = new SharedPreferences();
|
|
||||||
const country = preferences.get("jw_region1");
|
|
||||||
const language = preferences.get("jw_lang");
|
|
||||||
const perPage = 40;
|
|
||||||
const year = 0;
|
|
||||||
|
|
||||||
const searchQueryRegex = /[^a-zA-Z0-9 ]/g;
|
|
||||||
const sanitizedQuery = query.replace(searchQueryRegex, "").trim();
|
|
||||||
|
|
||||||
const variables = {
|
|
||||||
first: perPage,
|
|
||||||
offset: (page - 1) * perPage,
|
|
||||||
platform: "WEB",
|
|
||||||
country: country,
|
|
||||||
language: language,
|
|
||||||
searchQuery: sanitizedQuery,
|
|
||||||
packages: [],
|
|
||||||
objectTypes: [],
|
|
||||||
popularTitlesSortBy: "TRENDING",
|
|
||||||
releaseYear: {
|
|
||||||
min: year,
|
|
||||||
max: year
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return await this.makeGraphQLRequest(this.justWatchQuery(), variables);
|
|
||||||
}
|
|
||||||
parseSearchJson(jsonLine) {
|
|
||||||
|
|
||||||
const popularTitlesResponse = JSON.parse(jsonLine);
|
|
||||||
|
|
||||||
const edges = popularTitlesResponse?.data?.popularTitles?.edges || [];
|
|
||||||
const hasNextPage = popularTitlesResponse?.data?.popularTitles?.pageInfo?.hasNextPage || false;
|
|
||||||
|
|
||||||
const animeList = edges
|
|
||||||
.map(edge => {
|
|
||||||
const node = edge?.node;
|
|
||||||
const content = node?.content;
|
|
||||||
if (!node || !content) return null;
|
|
||||||
return {
|
|
||||||
link: `${content.externalIds?.imdbId || ""},${node.objectType || ""},${content.fullPath || ""}`,
|
|
||||||
name: content.title || "",
|
|
||||||
imageUrl: `https://images.justwatch.com${content.posterUrl?.replace("{profile}", "s276")?.replace("{format}", "webp")}`,
|
|
||||||
description: content.shortDescription || "",
|
|
||||||
genre: content.genres?.map(genre => genre.translation).filter(Boolean) || [],
|
|
||||||
author: (content.credits?.filter(credit => credit.role === "DIRECTOR").map(credit => credit.name) || []).join(", "),
|
|
||||||
artist: (content.credits?.filter(credit => credit.role === "ACTOR").slice(0, 4).map(credit => credit.name) || []).join(", "),
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
return { "list": animeList, hasNextPage };
|
|
||||||
}
|
|
||||||
get supportsLatest() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
async getPopular(page) {
|
|
||||||
return this.parseSearchJson((await this.searchAnimeRequest(page, "")).body);
|
|
||||||
}
|
|
||||||
async getLatestUpdates(page) {
|
|
||||||
|
|
||||||
}
|
|
||||||
async search(query, page, filters) {
|
|
||||||
return this.parseSearchJson((await this.searchAnimeRequest(page, query)).body);
|
|
||||||
}
|
|
||||||
async getDetail(url) {
|
|
||||||
const anime = {};
|
|
||||||
const parts = url.split(",");
|
|
||||||
const type = parts[1].toLowerCase();
|
|
||||||
const imdbId = parts[0];
|
|
||||||
const response = await this.client.get(`https://cinemeta-live.strem.io/meta/${type}/${imdbId}.json`);
|
|
||||||
const meta = JSON.parse(response.body).meta;
|
|
||||||
if (!meta) return anime;
|
|
||||||
anime.episodes = (() => {
|
|
||||||
switch (meta.type) {
|
|
||||||
case "show":
|
|
||||||
const videos = meta.videos || [];
|
|
||||||
return videos
|
|
||||||
.filter(video => (video.firstAired ? new Date(video.firstAired) : Date.now()) < Date.now())
|
|
||||||
.map(video => {
|
|
||||||
const firstAired = video.firstAired ? new Date(video.firstAired) : Date.now();
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: `/stream/series/${video.id}.json`,
|
|
||||||
dateUpload: firstAired.valueOf().toString(),
|
|
||||||
name: `S${(video.season || "").toString().trim()}:E${(video.number || "").toString()} - ${video.name || ""}`,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
const seasonA = parseInt(a.name.substringAfter("S").substringBefore(":"), 10);
|
|
||||||
const seasonB = parseInt(b.name.substringAfter("S").substringBefore(":"), 10);
|
|
||||||
const episodeA = parseInt(a.name.substringAfter("E").substringBefore(" -"), 10);
|
|
||||||
const episodeB = parseInt(b.name.substringAfter("E").substringBefore(" -"), 10);
|
|
||||||
|
|
||||||
return seasonA - seasonB || episodeA - episodeB;
|
|
||||||
})
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
case "movie":
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
url: `/stream/movie/${meta.id}.json`,
|
|
||||||
name: "Movie"
|
|
||||||
}
|
|
||||||
].reverse();
|
|
||||||
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
return anime;
|
|
||||||
}
|
|
||||||
|
|
||||||
appendQueryParam(key, values) {
|
|
||||||
let url = "";
|
|
||||||
if (values && values.length > 0) {
|
|
||||||
const filteredValues = Array.from(values).filter(value => value.trim() !== "").join(",");
|
|
||||||
url += `${key}=${filteredValues}|`;
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
async getVideoList(url) {
|
|
||||||
const preferences = new SharedPreferences();
|
|
||||||
|
|
||||||
let mainURL = `${this.source.baseUrl}/`;
|
|
||||||
mainURL += this.appendQueryParam("providers", preferences.get("provider_selection1"));
|
|
||||||
mainURL += this.appendQueryParam("language", preferences.get("lang_selection"));
|
|
||||||
mainURL += this.appendQueryParam("qualityfilter", preferences.get("quality_selection"));
|
|
||||||
mainURL += this.appendQueryParam("sort", new Set([preferences.get("sorting_link")]));
|
|
||||||
mainURL += url;
|
|
||||||
mainURL = mainURL.replace(/\|$/, "");
|
|
||||||
const responseEpisodes = await this.client.get(mainURL);
|
|
||||||
const streamList = JSON.parse(responseEpisodes.body);
|
|
||||||
const animeTrackers = `
|
|
||||||
http://nyaa.tracker.wf:7777/announce,
|
|
||||||
http://anidex.moe:6969/announce,http://tracker.anirena.com:80/announce,
|
|
||||||
udp://tracker.uw0.xyz:6969/announce,
|
|
||||||
http://share.camoe.cn:8080/announce,
|
|
||||||
http://t.nyaatracker.com:80/announce,
|
|
||||||
udp://47.ip-51-68-199.eu:6969/announce,
|
|
||||||
udp://9.rarbg.me:2940,
|
|
||||||
udp://9.rarbg.to:2820,
|
|
||||||
udp://exodus.desync.com:6969/announce,
|
|
||||||
udp://explodie.org:6969/announce,
|
|
||||||
udp://ipv4.tracker.harry.lu:80/announce,
|
|
||||||
udp://open.stealth.si:80/announce,
|
|
||||||
udp://opentor.org:2710/announce,
|
|
||||||
udp://opentracker.i2p.rocks:6969/announce,
|
|
||||||
udp://retracker.lanta-net.ru:2710/announce,
|
|
||||||
udp://tracker.cyberia.is:6969/announce,
|
|
||||||
udp://tracker.dler.org:6969/announce,
|
|
||||||
udp://tracker.ds.is:6969/announce,
|
|
||||||
udp://tracker.internetwarriors.net:1337,
|
|
||||||
udp://tracker.openbittorrent.com:6969/announce,
|
|
||||||
udp://tracker.opentrackr.org:1337/announce,
|
|
||||||
udp://tracker.tiny-vps.com:6969/announce,
|
|
||||||
udp://tracker.torrent.eu.org:451/announce,
|
|
||||||
udp://valakas.rollo.dnsabr.com:2710/announce,
|
|
||||||
udp://www.torrent.eu.org:451/announce
|
|
||||||
`.split(",").map(tracker => tracker.trim()).filter(tracker => tracker);
|
|
||||||
|
|
||||||
const videos = this.sortVideos((streamList.streams || []).map(stream => {
|
|
||||||
const hash = `magnet:?xt=urn:btih:${stream.infoHash}&dn=${stream.infoHash}&tr=${animeTrackers.join("&tr=")}&index=${stream.fileIdx}`;
|
|
||||||
const videoTitle = `${(stream.name || "").replace("Torrentio\n", "")}\n${stream.title || ""}`.trim();
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: hash,
|
|
||||||
originalUrl: hash,
|
|
||||||
quality: videoTitle,
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
const numberOfLinks = preferences.get("number_of_links");
|
|
||||||
if (numberOfLinks == "all") {
|
|
||||||
return videos;
|
|
||||||
}
|
|
||||||
|
|
||||||
return videos.slice(0, parseInt(numberOfLinks))
|
|
||||||
}
|
|
||||||
|
|
||||||
sortVideos(videos) {
|
|
||||||
const preferences = new SharedPreferences();
|
|
||||||
|
|
||||||
const isDub = preferences.get("dubbed");
|
|
||||||
const isEfficient = preferences.get("efficient");
|
|
||||||
|
|
||||||
return videos.sort((a, b) => {
|
|
||||||
const regexMatchA = /\[(.+?) download\]/.test(a.quality);
|
|
||||||
const regexMatchB = /\[(.+?) download\]/.test(b.quality);
|
|
||||||
|
|
||||||
const isDubA = isDub && !a.quality.toLowerCase().includes("dubbed");
|
|
||||||
const isDubB = isDub && !b.quality.toLowerCase().includes("dubbed");
|
|
||||||
|
|
||||||
const isEfficientA = isEfficient && !["hevc", "265", "av1"].some(q => a.quality.toLowerCase().includes(q));
|
|
||||||
const isEfficientB = isEfficient && !["hevc", "265", "av1"].some(q => b.quality.toLowerCase().includes(q));
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
regexMatchA - regexMatchB ||
|
|
||||||
isDubA - isDubB ||
|
|
||||||
isEfficientA - isEfficientB
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getSourcePreferences() {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"key": "number_of_links",
|
|
||||||
"listPreference": {
|
|
||||||
"title": "Number of links to load for video list",
|
|
||||||
"summary": "⚠️ Increasing the number of links will increase the loading time of the video list",
|
|
||||||
"valueIndex": 1,
|
|
||||||
"entries": [
|
|
||||||
"2",
|
|
||||||
"4",
|
|
||||||
"8",
|
|
||||||
"12",
|
|
||||||
"all"],
|
|
||||||
"entryValues": [
|
|
||||||
"2",
|
|
||||||
"4",
|
|
||||||
"8",
|
|
||||||
"12",
|
|
||||||
"all"],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "provider_selection1",
|
|
||||||
"multiSelectListPreference": {
|
|
||||||
"title": "Enable/Disable Providers",
|
|
||||||
"summary": "",
|
|
||||||
"entries": [
|
|
||||||
"YTS",
|
|
||||||
"EZTV",
|
|
||||||
"RARBG",
|
|
||||||
"1337x",
|
|
||||||
"ThePirateBay",
|
|
||||||
"KickassTorrents",
|
|
||||||
"TorrentGalaxy",
|
|
||||||
"MagnetDL",
|
|
||||||
"HorribleSubs",
|
|
||||||
"NyaaSi",
|
|
||||||
"TokyoTosho",
|
|
||||||
"AniDex",
|
|
||||||
"🇷🇺 Rutor",
|
|
||||||
"🇷🇺 Rutracker",
|
|
||||||
"🇵🇹 Comando",
|
|
||||||
"🇵🇹 BluDV",
|
|
||||||
"🇫🇷 Torrent9",
|
|
||||||
"🇪🇸 MejorTorrent",
|
|
||||||
"🇲🇽 Cinecalidad"],
|
|
||||||
"entryValues": [
|
|
||||||
"yts",
|
|
||||||
"eztv",
|
|
||||||
"rarbg",
|
|
||||||
"1337x",
|
|
||||||
"thepiratebay",
|
|
||||||
"kickasstorrents",
|
|
||||||
"torrentgalaxy",
|
|
||||||
"magnetdl",
|
|
||||||
"horriblesubs",
|
|
||||||
"nyaasi",
|
|
||||||
"tokyotosho",
|
|
||||||
"anidex",
|
|
||||||
"rutor",
|
|
||||||
"rutracker",
|
|
||||||
"comando",
|
|
||||||
"bludv",
|
|
||||||
"torrent9",
|
|
||||||
"mejortorrent",
|
|
||||||
"cinecalidad"],
|
|
||||||
"values": [
|
|
||||||
"yts",
|
|
||||||
"eztv",
|
|
||||||
"rarbg",
|
|
||||||
"1337x",
|
|
||||||
"thepiratebay",
|
|
||||||
"kickasstorrents",
|
|
||||||
"torrentgalaxy",
|
|
||||||
"magnetdl",
|
|
||||||
"horriblesubs",
|
|
||||||
"nyaasi",
|
|
||||||
"tokyotosho",
|
|
||||||
"anidex",
|
|
||||||
"rutor",
|
|
||||||
"rutracker",
|
|
||||||
"comando",
|
|
||||||
"bludv",
|
|
||||||
"torrent9",
|
|
||||||
"mejortorrent",
|
|
||||||
"cinecalidad"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "quality_selection",
|
|
||||||
"multiSelectListPreference": {
|
|
||||||
"title": "Exclude Qualities/Resolutions",
|
|
||||||
"summary": "",
|
|
||||||
"entries": [
|
|
||||||
"BluRay REMUX",
|
|
||||||
"HDR/HDR10+/Dolby Vision",
|
|
||||||
"Dolby Vision",
|
|
||||||
"4k",
|
|
||||||
"1080p",
|
|
||||||
"720p",
|
|
||||||
"480p",
|
|
||||||
"Other (DVDRip/HDRip/BDRip...)",
|
|
||||||
"Screener",
|
|
||||||
"Cam",
|
|
||||||
"Unknown"],
|
|
||||||
"entryValues": [
|
|
||||||
"brremux",
|
|
||||||
"hdrall",
|
|
||||||
"dolbyvision",
|
|
||||||
"4k",
|
|
||||||
"1080p",
|
|
||||||
"720p",
|
|
||||||
"480p",
|
|
||||||
"other",
|
|
||||||
"scr",
|
|
||||||
"cam",
|
|
||||||
"unknown"],
|
|
||||||
"values": [
|
|
||||||
"720p",
|
|
||||||
"480p",
|
|
||||||
"other",
|
|
||||||
"scr",
|
|
||||||
"cam",
|
|
||||||
"unknown"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "lang_selection",
|
|
||||||
"multiSelectListPreference": {
|
|
||||||
"title": "Priority foreign language",
|
|
||||||
"summary": "",
|
|
||||||
"entries": [
|
|
||||||
"🇯🇵 Japanese",
|
|
||||||
"🇷🇺 Russian",
|
|
||||||
"🇮🇹 Italian",
|
|
||||||
"🇵🇹 Portuguese",
|
|
||||||
"🇪🇸 Spanish",
|
|
||||||
"🇲🇽 Latino",
|
|
||||||
"🇰🇷 Korean",
|
|
||||||
"🇨🇳 Chinese",
|
|
||||||
"🇹🇼 Taiwanese",
|
|
||||||
"🇫🇷 French",
|
|
||||||
"🇩🇪 German",
|
|
||||||
"🇳🇱 Dutch",
|
|
||||||
"🇮🇳 Hindi",
|
|
||||||
"🇮🇳 Telugu",
|
|
||||||
"🇮🇳 Tamil",
|
|
||||||
"🇵🇱 Polish",
|
|
||||||
"🇱🇹 Lithuanian",
|
|
||||||
"🇱🇻 Latvian",
|
|
||||||
"🇪🇪 Estonian",
|
|
||||||
"🇨🇿 Czech",
|
|
||||||
"🇸🇰 Slovakian",
|
|
||||||
"🇸🇮 Slovenian",
|
|
||||||
"🇭🇺 Hungarian",
|
|
||||||
"🇷🇴 Romanian",
|
|
||||||
"🇧🇬 Bulgarian",
|
|
||||||
"🇷🇸 Serbian",
|
|
||||||
"🇭🇷 Croatian",
|
|
||||||
"🇺🇦 Ukrainian",
|
|
||||||
"🇬🇷 Greek",
|
|
||||||
"🇩🇰 Danish",
|
|
||||||
"🇫🇮 Finnish",
|
|
||||||
"🇸🇪 Swedish",
|
|
||||||
"🇳🇴 Norwegian",
|
|
||||||
"🇹🇷 Turkish",
|
|
||||||
"🇸🇦 Arabic",
|
|
||||||
"🇮🇷 Persian",
|
|
||||||
"🇮🇱 Hebrew",
|
|
||||||
"🇻🇳 Vietnamese",
|
|
||||||
"🇮🇩 Indonesian",
|
|
||||||
"🇲🇾 Malay",
|
|
||||||
"🇹🇭 Thai",],
|
|
||||||
"entryValues": [
|
|
||||||
"japanese",
|
|
||||||
"russian",
|
|
||||||
"italian",
|
|
||||||
"portuguese",
|
|
||||||
"spanish",
|
|
||||||
"latino",
|
|
||||||
"korean",
|
|
||||||
"chinese",
|
|
||||||
"taiwanese",
|
|
||||||
"french",
|
|
||||||
"german",
|
|
||||||
"dutch",
|
|
||||||
"hindi",
|
|
||||||
"telugu",
|
|
||||||
"tamil",
|
|
||||||
"polish",
|
|
||||||
"lithuanian",
|
|
||||||
"latvian",
|
|
||||||
"estonian",
|
|
||||||
"czech",
|
|
||||||
"slovakian",
|
|
||||||
"slovenian",
|
|
||||||
"hungarian",
|
|
||||||
"romanian",
|
|
||||||
"bulgarian",
|
|
||||||
"serbian",
|
|
||||||
"croatian",
|
|
||||||
"ukrainian",
|
|
||||||
"greek",
|
|
||||||
"danish",
|
|
||||||
"finnish",
|
|
||||||
"swedish",
|
|
||||||
"norwegian",
|
|
||||||
"turkish",
|
|
||||||
"arabic",
|
|
||||||
"persian",
|
|
||||||
"hebrew",
|
|
||||||
"vietnamese",
|
|
||||||
"indonesian",
|
|
||||||
"malay",
|
|
||||||
"thai"],
|
|
||||||
"values": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "sorting_link",
|
|
||||||
"listPreference": {
|
|
||||||
"title": "Sorting",
|
|
||||||
"summary": "",
|
|
||||||
"valueIndex": 0,
|
|
||||||
"entries": [
|
|
||||||
"By quality then seeders",
|
|
||||||
"By quality then size",
|
|
||||||
"By seeders",
|
|
||||||
"By size"],
|
|
||||||
"entryValues": [
|
|
||||||
"quality",
|
|
||||||
"qualitysize",
|
|
||||||
"seeders",
|
|
||||||
"size"],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "dubbed",
|
|
||||||
"switchPreferenceCompat": {
|
|
||||||
"title": "Dubbed Video Priority",
|
|
||||||
"summary": "",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "efficient",
|
|
||||||
"switchPreferenceCompat": {
|
|
||||||
"title": "Efficient Video Priority",
|
|
||||||
"summary": "Codec: (HEVC / x265) & AV1. High-quality video with less data usage.",
|
|
||||||
"value": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "jw_region1",
|
|
||||||
"listPreference": {
|
|
||||||
"title": "Catalogue Region",
|
|
||||||
"summary": "Region based catalogue recommendation.",
|
|
||||||
"valueIndex": 132,
|
|
||||||
"entries": [
|
|
||||||
"Albania", "Algeria", "Androrra", "Angola", "Antigua and Barbuda", "Argentina", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Barbados", "Belarus", "Belgium", "Belize", "Bermuda", "Bolivia", "Bosnia and Herzegovina", "Brazil", "Bulgaria", "Burkina Faso", "Cameroon", "Canada", "Cape Verde", "Chad", "Chile", "Colombia", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic", "DR Congo", "Denmark", "Dominican Republic", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Estonia", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", "Germany", "Ghana", "Gibraltar", "Greece", "Guatemala", "Guernsey", "Guyana", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iraq", "Ireland", "Israel", "Italy", "Ivory Coast", "Jamaica", "Japan", "Jordan", "Kenya", "Kosovo", "Kuwait", "Latvia", "Lebanon", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Mali", "Malta", "Mauritius", "Mexico", "Moldova", "Monaco", "Montenegro", "Morocco", "Mozambique", "Netherlands", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Norway", "Oman", "Pakistan", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Qatar", "Romania", "Russia", "Saint Lucia", "San Marino", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Singapore", "Slovakia", "Slovenia", "South Africa", "South Korea", "Spain", "Sweden", "Switzerland", "Taiwan", "Tanzania", "Thailand", "Trinidad and Tobago", "Tunisia", "Turkey", "Turks and Caicos Islands", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "Vatican City", "Venezuela", "Yemen", "Zambia", "Zimbabwe"],
|
|
||||||
"entryValues": [
|
|
||||||
"AL", "DZ", "AD", "AO", "AG", "AR", "AU", "AT", "AZ", "BS", "BH", "BB", "BY", "BE", "BZ", "BM", "BO", "BA", "BR", "BG", "BF", "CM", "CA", "CV", "TD", "CL", "CO", "CR", "HR", "CU", "CY", "CZ", "CD", "DK", "DO", "EC", "EG", "SV", "GQ", "EE", "FJ", "FI", "FR", "GF", "PF", "DE", "GH", "GI", "GR", "GT", "GG", "GY", "HN", "HK", "HU", "IS", "IN", "ID", "IQ", "IE", "IL", "IT", "CI", "JM", "JP", "JO", "KE", "XK", "KW", "LV", "LB", "LY", "LI", "LT", "LU", "MK", "MG", "MW", "MY", "ML", "MT", "MU", "MX", "MD", "MC", "ME", "MA", "MZ", "NL", "NZ", "NI", "NE", "NG", "NO", "OM", "PK", "PS", "PA", "PG", "PY", "PE", "PH", "PL", "PT", "QA", "RO", "RU", "LC", "SM", "SA", "SN", "RS", "SC", "SG", "SK", "SI", "ZA", "KR", "ES", "SE", "CH", "TW", "TZ", "TH", "TT", "TN", "TR", "TC", "UG", "UA", "AE", "UK", "US", "UY", "VA", "VE", "YE", "ZM", "ZW"],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "jw_lang",
|
|
||||||
"listPreference": {
|
|
||||||
"title": "Poster and Titles Language",
|
|
||||||
"summary": "",
|
|
||||||
"valueIndex": 9,
|
|
||||||
"entries": [
|
|
||||||
"Arabic",
|
|
||||||
"Azerbaijani",
|
|
||||||
"Belarusian",
|
|
||||||
"Bulgarian",
|
|
||||||
"Bosnian",
|
|
||||||
"Catalan",
|
|
||||||
"Czech",
|
|
||||||
"German",
|
|
||||||
"Greek",
|
|
||||||
"English",
|
|
||||||
"English (U.S.A.)",
|
|
||||||
"Spanish",
|
|
||||||
"Spanish (Spain)",
|
|
||||||
"Spanish (Latinamerican)",
|
|
||||||
"Estonian",
|
|
||||||
"Finnish",
|
|
||||||
"French",
|
|
||||||
"French (Canada)",
|
|
||||||
"Hebrew",
|
|
||||||
"Croatian",
|
|
||||||
"Hungarian",
|
|
||||||
"Icelandic",
|
|
||||||
"Italian",
|
|
||||||
"Japanese",
|
|
||||||
"Korean",
|
|
||||||
"Lithuanian",
|
|
||||||
"Latvian",
|
|
||||||
"Macedonian",
|
|
||||||
"Maltese",
|
|
||||||
"Polish",
|
|
||||||
"Portuguese",
|
|
||||||
"Portuguese (Portugal)",
|
|
||||||
"Portuguese (Brazil)",
|
|
||||||
"Romanian",
|
|
||||||
"Russian",
|
|
||||||
"Slovakian",
|
|
||||||
"Slovenian",
|
|
||||||
"Albanian",
|
|
||||||
"Serbian",
|
|
||||||
"Swedish",
|
|
||||||
"Swahili",
|
|
||||||
"Turkish",
|
|
||||||
"Ukrainian",
|
|
||||||
"Urdu",
|
|
||||||
"Chinese"],
|
|
||||||
"entryValues": [
|
|
||||||
"ar",
|
|
||||||
"az",
|
|
||||||
"be",
|
|
||||||
"bg",
|
|
||||||
"bs",
|
|
||||||
"ca",
|
|
||||||
"cs",
|
|
||||||
"de",
|
|
||||||
"el",
|
|
||||||
"en",
|
|
||||||
"en-US",
|
|
||||||
"es",
|
|
||||||
"es-ES",
|
|
||||||
"es-LA",
|
|
||||||
"et",
|
|
||||||
"fi",
|
|
||||||
"fr",
|
|
||||||
"fr-CA",
|
|
||||||
"he",
|
|
||||||
"hr",
|
|
||||||
"hu",
|
|
||||||
"is",
|
|
||||||
"it",
|
|
||||||
"ja",
|
|
||||||
"ko",
|
|
||||||
"lt",
|
|
||||||
"lv",
|
|
||||||
"mk",
|
|
||||||
"mt",
|
|
||||||
"pl",
|
|
||||||
"pt",
|
|
||||||
"pt-PT",
|
|
||||||
"pt-BR",
|
|
||||||
"ro",
|
|
||||||
"ru",
|
|
||||||
"sk",
|
|
||||||
"sl",
|
|
||||||
"sq",
|
|
||||||
"sr",
|
|
||||||
"sv",
|
|
||||||
"sw",
|
|
||||||
"tr",
|
|
||||||
"uk",
|
|
||||||
"ur",
|
|
||||||
"zh"],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||