Reorganize folders
317
dart/anime/src/en/animepahe/animepahe.dart
Normal file
@@ -0,0 +1,317 @@
|
||||
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(source);
|
||||
|
||||
@override
|
||||
String get baseUrl => getPreferenceValue(source.id, "preferred_domain");
|
||||
|
||||
@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"))).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")))
|
||||
.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")))
|
||||
.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))).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))).body;
|
||||
animeList.addAll(await recursivePages(newUrl, newRes, session));
|
||||
}
|
||||
return animeList;
|
||||
}
|
||||
|
||||
Future<String> getSession(String title, String animeId) async {
|
||||
final res =
|
||||
(await client.get(Uri.parse("$baseUrl/api?m=search&q=$title"))).body;
|
||||
return substringBefore(
|
||||
substringAfter(
|
||||
substringAfter(res, "\"id\":$animeId"), "\"session\":\""),
|
||||
"\"");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<MVideo>> getVideoList(String url) async {
|
||||
final res = (await client.get(Uri.parse("$baseUrl$url")));
|
||||
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 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}));
|
||||
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}))
|
||||
.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");
|
||||
|
||||
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_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"]),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
AnimePahe main(MSource source) {
|
||||
return AnimePahe(source);
|
||||
}
|
||||
BIN
dart/anime/src/en/animepahe/icon.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
16
dart/anime/src/en/animepahe/source.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
Source get animepaheSource => _animepaheSource;
|
||||
const _animepaheVersion = "0.0.35";
|
||||
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,
|
||||
isManga: false);
|
||||
688
dart/anime/src/en/aniwave/aniwave.dart
Normal file
@@ -0,0 +1,688 @@
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class Aniwave extends MProvider {
|
||||
Aniwave({required this.source});
|
||||
|
||||
MSource source;
|
||||
|
||||
final Client client = Client(source);
|
||||
|
||||
@override
|
||||
String get baseUrl => getPreferenceValue(source.id, "preferred_domain1");
|
||||
|
||||
@override
|
||||
Future<MPages> getPopular(int page) async {
|
||||
final res = (await client
|
||||
.get(Uri.parse("$baseUrl/filter?sort=trending&page=$page")))
|
||||
.body;
|
||||
return parseAnimeList(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> getLatestUpdates(int page) async {
|
||||
final res = (await client
|
||||
.get(Uri.parse("$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 = "$baseUrl/filter?keyword=$query";
|
||||
|
||||
for (var filter in filters) {
|
||||
if (filter.type == "OrderFilter") {
|
||||
final order = filter.values[filter.state].value;
|
||||
url += "${ll(url)}sort=$order";
|
||||
} else if (filter.type == "GenreFilter") {
|
||||
final genre = (filter.state as List).where((e) => e.state).toList();
|
||||
if (genre.isNotEmpty) {
|
||||
for (var st in genre) {
|
||||
url += "${ll(url)}genre[]=${st.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "CountryFilter") {
|
||||
final country = (filter.state as List).where((e) => e.state).toList();
|
||||
if (country.isNotEmpty) {
|
||||
for (var st in country) {
|
||||
url += "${ll(url)}country[]=${st.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "SeasonFilter") {
|
||||
final season = (filter.state as List).where((e) => e.state).toList();
|
||||
if (season.isNotEmpty) {
|
||||
for (var st in season) {
|
||||
url += "${ll(url)}season[]=${st.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "YearFilter") {
|
||||
final year = (filter.state as List).where((e) => e.state).toList();
|
||||
if (year.isNotEmpty) {
|
||||
for (var st in year) {
|
||||
url += "${ll(url)}year[]=${st.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "TypeFilter") {
|
||||
final type = (filter.state as List).where((e) => e.state).toList();
|
||||
if (type.isNotEmpty) {
|
||||
for (var st in type) {
|
||||
url += "${ll(url)}type[]=${st.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "StatusFilter") {
|
||||
final status = (filter.state as List).where((e) => e.state).toList();
|
||||
if (status.isNotEmpty) {
|
||||
for (var st in status) {
|
||||
url += "${ll(url)}status[]=${st.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "LanguageFilter") {
|
||||
final language = (filter.state as List).where((e) => e.state).toList();
|
||||
if (language.isNotEmpty) {
|
||||
for (var st in language) {
|
||||
url += "${ll(url)}language[]=${st.value}";
|
||||
}
|
||||
}
|
||||
} else if (filter.type == "RatingFilter") {
|
||||
final rating = (filter.state as List).where((e) => e.state).toList();
|
||||
if (rating.isNotEmpty) {
|
||||
for (var st in rating) {
|
||||
url += "${ll(url)}rating[]=${st.value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final res = (await client.get(Uri.parse("$url&page=$page"))).body;
|
||||
return parseAnimeList(res);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MManga> getDetail(String url) async {
|
||||
final statusList = [
|
||||
{"Releasing": 0, "Completed": 1}
|
||||
];
|
||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
||||
MManga anime = MManga();
|
||||
final status = xpath(res, '//div[contains(text(),"Status")]/span/text()');
|
||||
if (status.isNotEmpty) {
|
||||
anime.status = parseStatus(status.first, statusList);
|
||||
}
|
||||
final description = xpath(res,
|
||||
'//*[contains(@class,"synopsis")]/div[@class="shorting"]/div[@class="content"]/text()');
|
||||
if (description.isNotEmpty) {
|
||||
anime.description = description.first;
|
||||
}
|
||||
final author = xpath(res, '//div[contains(text(),"Studio")]/span/text()');
|
||||
if (author.isNotEmpty) {
|
||||
anime.author = author.first;
|
||||
}
|
||||
|
||||
anime.genre = xpath(res, '//div[contains(text(),"Genre")]/span/a/text()');
|
||||
final id = parseHtml(res).selectFirst("div[data-id]").attr("data-id");
|
||||
final encrypt = vrfEncrypt(id);
|
||||
final vrf = "vrf=${Uri.encodeComponent(encrypt)}";
|
||||
|
||||
final resEp =
|
||||
(await client.get(Uri.parse("$baseUrl/ajax/episode/list/$id?$vrf")))
|
||||
.body;
|
||||
|
||||
final html = json.decode(resEp)["result"];
|
||||
List<MChapter>? episodesList = [];
|
||||
|
||||
final epsHtmls = parseHtml(html).select("div.episodes ul > li");
|
||||
|
||||
for (var epH in epsHtmls) {
|
||||
final epHtml = epH.outerHtml;
|
||||
final title = xpath(epHtml, '//li/@title').isNotEmpty
|
||||
? xpath(epHtml, '//li/@title').first
|
||||
: "";
|
||||
final ids = xpath(epHtml, '//a/@data-ids').first;
|
||||
final sub = xpath(epHtml, '//a/@data-sub').first;
|
||||
final dub = xpath(epHtml, '//a/@data-dub').first;
|
||||
final softsub = title.toLowerCase().contains("softsub") ? "1" : "";
|
||||
final fillerEp = title.toLowerCase().contains("filler") ? "1" : "";
|
||||
final epNum = xpath(epHtml, '//a/@data-num').first;
|
||||
String scanlator = "";
|
||||
if (sub == "1") {
|
||||
scanlator += "Sub";
|
||||
}
|
||||
if (softsub == "1") {
|
||||
scanlator += ", Softsub";
|
||||
}
|
||||
if (dub == "1") {
|
||||
scanlator += ", Dub";
|
||||
}
|
||||
if (fillerEp == "1") {
|
||||
scanlator += ", • Filler Episode";
|
||||
}
|
||||
MChapter episode = MChapter();
|
||||
episode.name = "Episode $epNum";
|
||||
episode.scanlator = scanlator;
|
||||
episode.url = "$ids&epurl=$url/ep-$epNum";
|
||||
episodesList.add(episode);
|
||||
}
|
||||
|
||||
anime.chapters = episodesList.reversed.toList();
|
||||
return anime;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<MVideo>> getVideoList(String url) async {
|
||||
final ids = substringBefore(url, "&");
|
||||
final encrypt = vrfEncrypt(ids);
|
||||
final vrf = "vrf=${Uri.encodeComponent(encrypt)}";
|
||||
final res =
|
||||
(await client.get(Uri.parse("$baseUrl/ajax/server/list/$ids?$vrf")))
|
||||
.body;
|
||||
final html = json.decode(res)["result"];
|
||||
|
||||
final vidsHtmls = parseHtml(html).select("div.servers > div");
|
||||
|
||||
List<MVideo> videos = [];
|
||||
for (var vidH in vidsHtmls) {
|
||||
final vidHtml = vidH.outerHtml;
|
||||
final type = xpath(vidHtml, '//div/@data-type').first;
|
||||
final serversIds = xpath(vidHtml, '//li/@data-link-id');
|
||||
for (int i = 0; i < serversIds.length; i++) {
|
||||
final serverId = serversIds[i];
|
||||
|
||||
final encrypt = vrfEncrypt(serverId);
|
||||
final vrf = "vrf=${Uri.encodeComponent(encrypt)}";
|
||||
final res =
|
||||
(await client.get(Uri.parse("$baseUrl/ajax/server/$serverId?$vrf")))
|
||||
.body;
|
||||
final status = json.decode(res)["status"];
|
||||
if (status == 200) {
|
||||
List<MVideo> a = [];
|
||||
final url = vrfDecrypt(json.decode(res)["result"]["url"]);
|
||||
final hosterSelection = preferenceHosterSelection(source.id);
|
||||
final typeSelection = preferenceTypeSelection(source.id);
|
||||
if (typeSelection.contains(type.toLowerCase())) {
|
||||
if (url.contains("vidplay") || url.contains("mcloud")) {
|
||||
final hosterName =
|
||||
url.contains("vidplay") ? "VidPlay" : "MyCloud";
|
||||
if (hosterSelection.contains(hosterName.toLowerCase())) {
|
||||
a = await vidsrcExtractor(url, hosterName, type);
|
||||
}
|
||||
} else if (url.contains("mp4upload") &&
|
||||
hosterSelection.contains("mp4upload")) {
|
||||
a = await mp4UploadExtractor(url, null, "", type);
|
||||
} else if (url.contains("streamtape") &&
|
||||
hosterSelection.contains("streamtape")) {
|
||||
a = await streamTapeExtractor(url, "StreamTape - $type");
|
||||
} else if (url.contains("filemoon") &&
|
||||
hosterSelection.contains("filemoon")) {
|
||||
a = await filemoonExtractor(url, "", type);
|
||||
}
|
||||
videos.addAll(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sortVideos(videos, source.id);
|
||||
}
|
||||
|
||||
MPages parseAnimeList(String res) {
|
||||
List<MManga> animeList = [];
|
||||
final urls = xpath(res, '//div[@class="item "]/div/div/div/a/@href');
|
||||
final names = xpath(res, '//div[@class="item "]/div/div/div/a/text()');
|
||||
final images = xpath(res, '//div[@class="item "]/div/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);
|
||||
}
|
||||
|
||||
List<int> rc4Encrypt(String key, List<int> message) {
|
||||
List<int> _key = utf8.encode(key);
|
||||
int _i = 0, _j = 0;
|
||||
List<int> _box = List.generate(256, (i) => i);
|
||||
|
||||
int x = 0;
|
||||
for (int i = 0; i < 256; i++) {
|
||||
x = (x + _box[i] + _key[i % _key.length]) % 256;
|
||||
var tmp = _box[i];
|
||||
_box[i] = _box[x];
|
||||
_box[x] = tmp;
|
||||
}
|
||||
|
||||
List<int> out = [];
|
||||
for (var char in message) {
|
||||
_i = (_i + 1) % 256;
|
||||
_j = (_j + _box[_i]) % 256;
|
||||
|
||||
var tmp = _box[_i];
|
||||
_box[_i] = _box[_j];
|
||||
_box[_j] = tmp;
|
||||
|
||||
final c = char ^ (_box[(_box[_i] + _box[_j]) % 256]);
|
||||
out.add(c);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
String vrfEncrypt(String input) {
|
||||
final rc4 = rc4Encrypt("ysJhV6U27FVIjjuk", input.codeUnits);
|
||||
final vrf = base64Url.encode(rc4);
|
||||
final vrf1 = base64.encode(vrf.codeUnits);
|
||||
List<int> vrf2 = vrfShift(vrf1.codeUnits);
|
||||
final vrf3 = base64.encode(vrf2);
|
||||
return utf8.decode(rot13(vrf3.codeUnits));
|
||||
}
|
||||
|
||||
String vrfDecrypt(String input) {
|
||||
final decode = base64Url.decode(input);
|
||||
final rc4 = rc4Encrypt("hlPeNwkncH0fq9so", decode);
|
||||
return Uri.decodeComponent(utf8.decode(rc4));
|
||||
}
|
||||
|
||||
List<int> vrfShift(List<int> vrf) {
|
||||
var shifts = [-3, 3, -4, 2, -2, 5, 4, 5];
|
||||
for (var i = 0; i < vrf.length; i++) {
|
||||
var shift = shifts[i % 8];
|
||||
vrf[i] = (vrf[i] + shift) & 0xFF;
|
||||
}
|
||||
return vrf;
|
||||
}
|
||||
|
||||
List<int> rot13(List<int> vrf) {
|
||||
for (var i = 0; i < vrf.length; i++) {
|
||||
var byte = vrf[i];
|
||||
if (byte >= 'A'.codeUnitAt(0) && byte <= 'Z'.codeUnitAt(0)) {
|
||||
vrf[i] = (byte - 'A'.codeUnitAt(0) + 13) % 26 + 'A'.codeUnitAt(0);
|
||||
} else if (byte >= 'a'.codeUnitAt(0) && byte <= 'z'.codeUnitAt(0)) {
|
||||
vrf[i] = (byte - 'a'.codeUnitAt(0) + 13) % 26 + 'a'.codeUnitAt(0);
|
||||
}
|
||||
}
|
||||
return vrf;
|
||||
}
|
||||
|
||||
Future<List<MVideo>> vidsrcExtractor(
|
||||
String url, String name, String type) async {
|
||||
List<String> keys = json.decode((await client.get(Uri.parse(
|
||||
"https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json")))
|
||||
.body);
|
||||
List<MVideo> videoList = [];
|
||||
final host = Uri.parse(url).host;
|
||||
final apiUrl = await getApiUrl(url, keys);
|
||||
|
||||
final res = await client.get(Uri.parse(apiUrl), headers: {
|
||||
"Host": host,
|
||||
"Referer": Uri.decodeComponent(url),
|
||||
"X-Requested-With": "XMLHttpRequest"
|
||||
});
|
||||
final result = json.decode(res.body)['result'];
|
||||
|
||||
if (result != 404) {
|
||||
String masterUrl =
|
||||
((result['sources'] as List<Map<String, dynamic>>).first)['file'];
|
||||
final tracks = (result['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 (_) {}
|
||||
}
|
||||
|
||||
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 - $type - $quality"
|
||||
..headers = {"Referer": "https://$host/"}
|
||||
..subtitles = subtitles;
|
||||
videoList.add(video);
|
||||
}
|
||||
}
|
||||
|
||||
return videoList;
|
||||
}
|
||||
|
||||
Future<String> getApiUrl(String url, List<String> keyList) async {
|
||||
final host = Uri.parse(url).host;
|
||||
final paramsToString = Uri.parse(url)
|
||||
.queryParameters
|
||||
.entries
|
||||
.map((e) => "${e.key}=${e.value}")
|
||||
.join("&");
|
||||
var vidId = substringBefore(substringAfterLast(url, "/"), "?");
|
||||
var encodedID = encodeID(vidId, keyList);
|
||||
final apiSlug = await callFromFuToken(host, encodedID);
|
||||
String apiUrlString = "";
|
||||
apiUrlString += "https://$host/$apiSlug";
|
||||
if (paramsToString.isNotEmpty) {
|
||||
apiUrlString += "?$paramsToString";
|
||||
}
|
||||
|
||||
return apiUrlString;
|
||||
}
|
||||
|
||||
String encodeID(String vidId, List<String> keyList) {
|
||||
var rc4Key1 = keyList[0];
|
||||
var rc4Key2 = keyList[1];
|
||||
final rc4 = rc4Encrypt(rc4Key1, vidId.codeUnits);
|
||||
final rc41 = rc4Encrypt(rc4Key2, rc4);
|
||||
return base64.encode(rc41).replaceAll("/", "_").trim();
|
||||
}
|
||||
|
||||
Future<String> callFromFuToken(String host, String data) async {
|
||||
final fuTokenScript =
|
||||
(await client.get(Uri.parse("https://$host/futoken"))).body;
|
||||
|
||||
String js = "";
|
||||
js += "(function";
|
||||
js += substringBefore(
|
||||
substringAfter(substringAfter(fuTokenScript, "window"), "function")
|
||||
.replaceAll("jQuery.ajax(", ""),
|
||||
"+location.search");
|
||||
js += "}(\"$data\"))";
|
||||
final jsRes = await evalJs(js);
|
||||
if (jsRes == "error") return "";
|
||||
return jsRes;
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getFilterList() {
|
||||
return [
|
||||
SelectFilter("OrderFilter", "Sort order", 0, [
|
||||
SelectFilterOption("Most relevance", "most_relevance"),
|
||||
SelectFilterOption("Recently updated", "recently_updated"),
|
||||
SelectFilterOption("Recently added", "recently_added"),
|
||||
SelectFilterOption("Release date", "release_date"),
|
||||
SelectFilterOption("Trending", "trending"),
|
||||
SelectFilterOption("Name A-Z", "title_az"),
|
||||
SelectFilterOption("Scores", "scores"),
|
||||
SelectFilterOption("MAL scores", "mal_scores"),
|
||||
SelectFilterOption("Most watched", "most_watched"),
|
||||
SelectFilterOption("Most favourited", "most_favourited"),
|
||||
SelectFilterOption("Number of episodes", "number_of_episodes"),
|
||||
]),
|
||||
SeparatorFilter(),
|
||||
GroupFilter("GenreFilter", "Genre", [
|
||||
CheckBoxFilter("Action", "1"),
|
||||
CheckBoxFilter("Adventure", "2"),
|
||||
CheckBoxFilter("Avant Garde", "2262888"),
|
||||
CheckBoxFilter("Boys Love", "2262603"),
|
||||
CheckBoxFilter("Comedy", "4"),
|
||||
CheckBoxFilter("Demons", "4424081"),
|
||||
CheckBoxFilter("Drama", "7"),
|
||||
CheckBoxFilter("Ecchi", "8"),
|
||||
CheckBoxFilter("Fantasy", "9"),
|
||||
CheckBoxFilter("Girls Love", "2263743"),
|
||||
CheckBoxFilter("Gourmet", "2263289"),
|
||||
CheckBoxFilter("Harem", "11"),
|
||||
CheckBoxFilter("Horror", "14"),
|
||||
CheckBoxFilter("Isekai", "3457284"),
|
||||
CheckBoxFilter("Iyashikei", "4398552"),
|
||||
CheckBoxFilter("Josei", "15"),
|
||||
CheckBoxFilter("Kids", "16"),
|
||||
CheckBoxFilter("Magic", "4424082"),
|
||||
CheckBoxFilter("Mahou Shoujo", "3457321"),
|
||||
CheckBoxFilter("Martial Arts", "18"),
|
||||
CheckBoxFilter("Mecha", "19"),
|
||||
CheckBoxFilter("Military", "20"),
|
||||
CheckBoxFilter("Music", "21"),
|
||||
CheckBoxFilter("Mystery", "22"),
|
||||
CheckBoxFilter("Parody", "23"),
|
||||
CheckBoxFilter("Psychological", "25"),
|
||||
CheckBoxFilter("Reverse Harem", "4398403"),
|
||||
CheckBoxFilter("Romance", "26"),
|
||||
CheckBoxFilter("School", "28"),
|
||||
CheckBoxFilter("Sci-Fi", "29"),
|
||||
CheckBoxFilter("Seinen", "30"),
|
||||
CheckBoxFilter("Shoujo", "31"),
|
||||
CheckBoxFilter("Shounen", "33"),
|
||||
CheckBoxFilter("Slice of Life", "35"),
|
||||
CheckBoxFilter("Space", "36"),
|
||||
CheckBoxFilter("Sports", "37"),
|
||||
CheckBoxFilter("Super Power", "38"),
|
||||
CheckBoxFilter("Supernatural", "39"),
|
||||
CheckBoxFilter("Suspense", "2262590"),
|
||||
CheckBoxFilter("Thriller", "40"),
|
||||
CheckBoxFilter("Vampire", "41")
|
||||
]),
|
||||
GroupFilter("CountryFilter", "Country", [
|
||||
CheckBoxFilter("China", "120823"),
|
||||
CheckBoxFilter("Japan", "120822")
|
||||
]),
|
||||
GroupFilter("SeasonFilter", "Season", [
|
||||
CheckBoxFilter("Fall", "fall"),
|
||||
CheckBoxFilter("Summer", "summer"),
|
||||
CheckBoxFilter("Spring", "spring"),
|
||||
CheckBoxFilter("Winter", "winter"),
|
||||
CheckBoxFilter("Unknown", "unknown")
|
||||
]),
|
||||
GroupFilter("YearFilter", "Year", [
|
||||
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("2000s", "2000s"),
|
||||
CheckBoxFilter("1990s", "1990s"),
|
||||
CheckBoxFilter("1980s", "1980s"),
|
||||
CheckBoxFilter("1970s", "1970s"),
|
||||
CheckBoxFilter("1960s", "1960s"),
|
||||
CheckBoxFilter("1950s", "1950s"),
|
||||
CheckBoxFilter("1940s", "1940s"),
|
||||
CheckBoxFilter("1930s", "1930s"),
|
||||
CheckBoxFilter("1920s", "1920s"),
|
||||
CheckBoxFilter("1910s", "1910s")
|
||||
]),
|
||||
GroupFilter("TypeFilter", "Type", [
|
||||
CheckBoxFilter("Movie", "movie"),
|
||||
CheckBoxFilter("TV", "tv"),
|
||||
CheckBoxFilter("OVA", "ova"),
|
||||
CheckBoxFilter("ONA", "ona"),
|
||||
CheckBoxFilter("Special", "special"),
|
||||
CheckBoxFilter("Music", "music")
|
||||
]),
|
||||
GroupFilter("StatusFilter", "Status", [
|
||||
CheckBoxFilter("Not Yet Aired", "info"),
|
||||
CheckBoxFilter("Releasing", "releasing"),
|
||||
CheckBoxFilter("Completed", "completed")
|
||||
]),
|
||||
GroupFilter("LanguageFilter", "Language", [
|
||||
CheckBoxFilter("Sub and Dub", "subdub"),
|
||||
CheckBoxFilter("Sub", "sub"),
|
||||
CheckBoxFilter("Dub", "dub")
|
||||
]),
|
||||
GroupFilter("RatingFilter", "Rating", [
|
||||
CheckBoxFilter("G - All Ages", "g"),
|
||||
CheckBoxFilter("PG - Children", "pg"),
|
||||
CheckBoxFilter("PG 13 - Teens 13 and Older", "pg_13"),
|
||||
CheckBoxFilter("R - 17+, Violence & Profanity", "r"),
|
||||
CheckBoxFilter("R+ - Profanity & Mild Nudity", "r+"),
|
||||
CheckBoxFilter("Rx - Hentai", "rx")
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getSourcePreferences() {
|
||||
return [
|
||||
ListPreference(
|
||||
key: "preferred_domain1",
|
||||
title: "Preferred domain",
|
||||
summary: "",
|
||||
valueIndex: 0,
|
||||
entries: [
|
||||
"aniwave.to",
|
||||
"aniwave.ws",
|
||||
"aniwave.li",
|
||||
"aniwave.vc"
|
||||
],
|
||||
entryValues: [
|
||||
"https://aniwave.to",
|
||||
"https://aniwave.ws",
|
||||
"https://aniwave.li",
|
||||
"https://aniwave.vc"
|
||||
]),
|
||||
ListPreference(
|
||||
key: "preferred_quality",
|
||||
title: "Preferred Quality",
|
||||
summary: "",
|
||||
valueIndex: 0,
|
||||
entries: ["1080p", "720p", "480p", "360p"],
|
||||
entryValues: ["1080", "720", "480", "360"]),
|
||||
ListPreference(
|
||||
key: "preferred_language",
|
||||
title: "Preferred Type",
|
||||
summary: "",
|
||||
valueIndex: 0,
|
||||
entries: ["Sub", "Softsub", "Dub"],
|
||||
entryValues: ["Sub", "Softsub", "Dub"]),
|
||||
ListPreference(
|
||||
key: "preferred_server",
|
||||
title: "Preferred server",
|
||||
summary: "",
|
||||
valueIndex: 0,
|
||||
entries: [
|
||||
"VidPlay",
|
||||
"MyCloud",
|
||||
"Filemoon",
|
||||
"StreamTape",
|
||||
"Mp4Upload"
|
||||
],
|
||||
entryValues: [
|
||||
"vidplay",
|
||||
"mycloud",
|
||||
"filemoon",
|
||||
"streamtape",
|
||||
"mp4upload"
|
||||
]),
|
||||
MultiSelectListPreference(
|
||||
key: "hoster_selection",
|
||||
title: "Enable/Disable Hosts",
|
||||
summary: "",
|
||||
entries: [
|
||||
"VidPlay",
|
||||
"MyCloud",
|
||||
"Filemoon",
|
||||
"StreamTape",
|
||||
"Mp4Upload"
|
||||
],
|
||||
entryValues: [
|
||||
"vidplay",
|
||||
"mycloud",
|
||||
"filemoon",
|
||||
"streamtape",
|
||||
"mp4upload"
|
||||
],
|
||||
values: [
|
||||
"vidplay",
|
||||
"mycloud",
|
||||
"filemoon",
|
||||
"streamtape",
|
||||
"mp4upload"
|
||||
]),
|
||||
MultiSelectListPreference(
|
||||
key: "type_selection",
|
||||
title: "Enable/Disable Type",
|
||||
summary: "",
|
||||
entries: ["Sub", "Softsub", "Dub"],
|
||||
entryValues: ["sub", "softsub", "dub"],
|
||||
values: ["sub", "softsub", "dub"]),
|
||||
];
|
||||
}
|
||||
|
||||
List<String> preferenceHosterSelection(int sourceId) {
|
||||
return getPreferenceValue(sourceId, "hoster_selection");
|
||||
}
|
||||
|
||||
List<String> preferenceTypeSelection(int sourceId) {
|
||||
return getPreferenceValue(sourceId, "type_selection");
|
||||
}
|
||||
|
||||
List<MVideo> sortVideos(List<MVideo> videos, int sourceId) {
|
||||
String quality = getPreferenceValue(sourceId, "preferred_quality");
|
||||
String server = getPreferenceValue(sourceId, "preferred_server");
|
||||
String lang = getPreferenceValue(sourceId, "preferred_language");
|
||||
videos.sort((MVideo a, MVideo b) {
|
||||
int qualityMatchA = 0;
|
||||
|
||||
if (a.quality.contains(quality) &&
|
||||
a.quality.toLowerCase().contains(lang.toLowerCase()) &&
|
||||
a.quality.toLowerCase().contains(server.toLowerCase())) {
|
||||
qualityMatchA = 1;
|
||||
}
|
||||
int qualityMatchB = 0;
|
||||
if (b.quality.contains(quality) &&
|
||||
b.quality.toLowerCase().contains(lang.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;
|
||||
}
|
||||
|
||||
String ll(String url) {
|
||||
if (url.contains("?")) {
|
||||
return "&";
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
Aniwave main(MSource source) {
|
||||
return Aniwave(source: source);
|
||||
}
|
||||
BIN
dart/anime/src/en/aniwave/icon.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
16
dart/anime/src/en/aniwave/source.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
Source get aniwave => _aniwave;
|
||||
const _aniwaveVersion = "0.0.65";
|
||||
const _aniwaveCodeUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/aniwave/aniwave.dart";
|
||||
Source _aniwave = Source(
|
||||
name: "Aniwave",
|
||||
baseUrl: "https://aniwave.to",
|
||||
lang: "en",
|
||||
typeSource: "single",
|
||||
iconUrl:
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/aniwave/icon.png",
|
||||
sourceCodeUrl: _aniwaveCodeUrl,
|
||||
version: _aniwaveVersion,
|
||||
isManga: false);
|
||||
210
dart/anime/src/en/dramacool/dramacool.dart
Normal file
@@ -0,0 +1,210 @@
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class DramaCool extends MProvider {
|
||||
DramaCool({required this.source});
|
||||
|
||||
MSource source;
|
||||
|
||||
final Client client = Client(source);
|
||||
|
||||
@override
|
||||
String get baseUrl => getPreferenceValue(source.id, "overrideBaseUrl");
|
||||
|
||||
@override
|
||||
Future<MPages> getPopular(int page) async {
|
||||
final res =
|
||||
(await client.get(Uri.parse("$baseUrl/most-popular-drama?page=$page")))
|
||||
.body;
|
||||
final document = parseHtml(res);
|
||||
return animeFromElement(document.select("ul.list-episode-item li a"),
|
||||
document.selectFirst("li.next a")?.attr("href") != null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> getLatestUpdates(int page) async {
|
||||
final res =
|
||||
(await client.get(Uri.parse("$baseUrl/recently-added?page=$page")))
|
||||
.body;
|
||||
final document = parseHtml(res);
|
||||
return animeFromElement(document.select("ul.switch-block a"),
|
||||
document.selectFirst("li.next a")?.attr("href") != null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MPages> search(String query, int page, FilterList filterList) async {
|
||||
final res = (await client
|
||||
.get(Uri.parse("$baseUrl/search?keyword=$query&page=$page")))
|
||||
.body;
|
||||
final document = parseHtml(res);
|
||||
return animeFromElement(document.select("ul.list-episode-item li a"),
|
||||
document.selectFirst("li.next a")?.attr("href") != null);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<MManga> getDetail(String url) async {
|
||||
final statusList = [
|
||||
{"Ongoing": 0, "Completed": 1}
|
||||
];
|
||||
url = getUrlWithoutDomain(url);
|
||||
if (url.contains("-episode-") && url.endsWith(".html")) {
|
||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
||||
url = parseHtml(res).selectFirst("div.category a").attr("href");
|
||||
}
|
||||
url = getUrlWithoutDomain(url);
|
||||
|
||||
final res = (await client.get(Uri.parse("$baseUrl$url"))).body;
|
||||
final document = parseHtml(res);
|
||||
MManga anime = MManga();
|
||||
anime.description = document
|
||||
.selectFirst("div.info")
|
||||
.select("p")
|
||||
.map((MElement e) {
|
||||
if (!e.outerHtml.contains("<span")) {
|
||||
return e.text;
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.toList()
|
||||
.join("\n");
|
||||
final author =
|
||||
xpath(res, '//p[contains(text(),"Original Network:")]/a/text()');
|
||||
if (author.isNotEmpty) {
|
||||
anime.author = author.first;
|
||||
}
|
||||
anime.genre = xpath(res, '//p[contains(text(),"Genre:")]/a/text()');
|
||||
final status = xpath(res, '//p[contains(text(),"Status")]/a/text()');
|
||||
if (status.isNotEmpty) {
|
||||
anime.status = parseStatus(status.first, statusList);
|
||||
}
|
||||
List<MChapter> episodesList = [];
|
||||
final episodeListElements = document.select("ul.all-episode li a");
|
||||
|
||||
for (var element in episodeListElements) {
|
||||
var epNum =
|
||||
substringAfterLast(element.selectFirst("h3").text, "Episode ");
|
||||
var type = element.selectFirst("span.type")?.text ?? "RAW";
|
||||
var date = element.selectFirst("span.time")?.text ?? "";
|
||||
MChapter ep = MChapter();
|
||||
ep.name = "$type: Episode $epNum".trim();
|
||||
ep.url = element.getHref;
|
||||
if (date.isNotEmpty)
|
||||
ep.dateUpload = parseDates([element.selectFirst("span.time")?.text],
|
||||
"yyyy-MM-dd HH:mm:ss", "en")
|
||||
.first;
|
||||
episodesList.add(ep);
|
||||
}
|
||||
|
||||
anime.chapters = episodesList;
|
||||
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);
|
||||
String iframeUrl = document.selectFirst("iframe")?.getSrc ?? "";
|
||||
if (iframeUrl.isEmpty) return [];
|
||||
if (iframeUrl.startsWith("//")) {
|
||||
iframeUrl = "https:$iframeUrl";
|
||||
}
|
||||
var iframeDoc = parseHtml((await client.get(Uri.parse(iframeUrl))).body);
|
||||
final serverElements = iframeDoc.select("ul.list-server-items li");
|
||||
List<MVideo> videos = [];
|
||||
for (var serverElement in serverElements) {
|
||||
var url = serverElement.attr("data-video");
|
||||
List<MVideo> a = [];
|
||||
if (url.contains("dood")) {
|
||||
a = await doodExtractor(url, "DoodStream");
|
||||
} else if (url.contains("dwish")) {
|
||||
a = await streamWishExtractor(url, "StreamWish");
|
||||
} else if (url.contains("streamtape")) {
|
||||
a = await streamTapeExtractor(url, "StreamTape");
|
||||
}
|
||||
videos.addAll(a);
|
||||
}
|
||||
return sortVideos(videos, source.id);
|
||||
}
|
||||
|
||||
@override
|
||||
List<dynamic> getSourcePreferences() {
|
||||
return [
|
||||
EditTextPreference(
|
||||
key: "overrideBaseUrl",
|
||||
title: "Override BaseUrl",
|
||||
summary: "",
|
||||
value: "https://dramacool.pa",
|
||||
dialogTitle: "Override BaseUrl",
|
||||
dialogMessage: "",
|
||||
text: "https://dramacool.pa"),
|
||||
ListPreference(
|
||||
key: "preferred_quality",
|
||||
title: "Preferred quality",
|
||||
summary: "",
|
||||
valueIndex: 0,
|
||||
entries: [
|
||||
"1080p",
|
||||
"720p",
|
||||
"480p",
|
||||
"360p",
|
||||
"Doodstream",
|
||||
"StreamTape"
|
||||
],
|
||||
entryValues: [
|
||||
"1080",
|
||||
"720",
|
||||
"480",
|
||||
"360",
|
||||
"Doodstream",
|
||||
"StreamTape"
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
MPages animeFromElement(List<MElement> elements, bool hasNextPage) {
|
||||
List<MManga> animeList = [];
|
||||
for (var element in elements) {
|
||||
MManga anime = MManga();
|
||||
anime.name = element.selectFirst("h3")?.text ?? "Serie";
|
||||
anime.imageUrl = (element.selectFirst("img")?.attr("data-original") ?? "")
|
||||
.replaceAll(" ", "%20") ??
|
||||
"";
|
||||
anime.link = element.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;
|
||||
}
|
||||
}
|
||||
|
||||
DramaCool main(MSource source) {
|
||||
return DramaCool(source: source);
|
||||
}
|
||||
BIN
dart/anime/src/en/dramacool/icon.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
16
dart/anime/src/en/dramacool/source.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
Source get dramacoolSource => _dramacoolSource;
|
||||
const _dramacoolVersion = "0.0.25";
|
||||
const _dramacoolSourceCodeUrl =
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/dramacool/dramacool.dart";
|
||||
Source _dramacoolSource = Source(
|
||||
name: "DramaCool",
|
||||
baseUrl: "https://dramacool.pa",
|
||||
lang: "en",
|
||||
typeSource: "single",
|
||||
iconUrl:
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/dramacool/icon.png",
|
||||
sourceCodeUrl: _dramacoolSourceCodeUrl,
|
||||
version: _dramacoolVersion,
|
||||
isManga: false);
|
||||
1137
dart/anime/src/en/gogoanime/gogoanime.dart
Normal file
BIN
dart/anime/src/en/gogoanime/icon.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
16
dart/anime/src/en/gogoanime/source.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
Source get gogoanimeSource => _gogoanimeSource;
|
||||
const _gogoanimeVersion = "0.0.9";
|
||||
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,
|
||||
isManga: false);
|
||||
BIN
dart/anime/src/en/kisskh/icon.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
157
dart/anime/src/en/kisskh/kisskh.dart
Normal file
@@ -0,0 +1,157 @@
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class KissKh extends MProvider {
|
||||
KissKh({required this.source});
|
||||
|
||||
MSource source;
|
||||
|
||||
final Client client = Client(source);
|
||||
|
||||
@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) {
|
||||
MManga 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) {
|
||||
MManga 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) {
|
||||
MManga 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;
|
||||
MManga 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) {
|
||||
try {
|
||||
final subUrl = sub["src"];
|
||||
final label = sub["label"];
|
||||
MTrack subtitle = MTrack();
|
||||
subtitle
|
||||
..label = label
|
||||
..file = subUrl;
|
||||
subtitles.add(subtitle);
|
||||
} catch (_) {}
|
||||
}
|
||||
final videoUrl = jsonRes["Video"];
|
||||
MVideo video = MVideo();
|
||||
video
|
||||
..url = videoUrl
|
||||
..originalUrl = videoUrl
|
||||
..quality = "kisskh"
|
||||
..subtitles = subtitles
|
||||
..headers = {
|
||||
"referer": "https://kisskh.me/",
|
||||
"origin": "https://kisskh.me"
|
||||
};
|
||||
return [video];
|
||||
}
|
||||
}
|
||||
|
||||
KissKh main(MSource source) {
|
||||
return KissKh(source: source);
|
||||
}
|
||||
16
dart/anime/src/en/kisskh/source.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
Source get kisskhSource => _kisskhSource;
|
||||
const _kisskhVersion = "0.0.55";
|
||||
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,
|
||||
isManga: false);
|
||||
BIN
dart/anime/src/en/nineanimetv/icon.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
546
dart/anime/src/en/nineanimetv/nineanimetv.dart
Normal file
@@ -0,0 +1,546 @@
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class NineAnimeTv extends MProvider {
|
||||
NineAnimeTv({required this.source});
|
||||
|
||||
MSource source;
|
||||
|
||||
final Client client = Client(source);
|
||||
|
||||
@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') ? 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 = resServer;
|
||||
}
|
||||
|
||||
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("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);
|
||||
}
|
||||
16
dart/anime/src/en/nineanimetv/source.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
Source get nineanimetv => _nineanimetv;
|
||||
const _nineanimetvVersion = "0.0.3";
|
||||
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,
|
||||
isManga: false);
|
||||
BIN
dart/anime/src/en/uhdmovies/icon.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
16
dart/anime/src/en/uhdmovies/source.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import '../../../../../model/source.dart';
|
||||
|
||||
Source get uhdmoviesSource => _uhdmoviesSource;
|
||||
const _uhdmoviesVersion = "0.0.35";
|
||||
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.zip",
|
||||
lang: "en",
|
||||
typeSource: "single",
|
||||
iconUrl:
|
||||
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/uhdmovies/icon.png",
|
||||
sourceCodeUrl: _uhdmoviesSourceCodeUrl,
|
||||
version: _uhdmoviesVersion,
|
||||
isManga: false);
|
||||
234
dart/anime/src/en/uhdmovies/uhdmovies.dart
Normal file
@@ -0,0 +1,234 @@
|
||||
import 'package:mangayomi/bridge_lib.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class UHDMovies extends MProvider {
|
||||
UHDMovies({required this.source});
|
||||
|
||||
MSource source;
|
||||
|
||||
final Client client = Client(source);
|
||||
|
||||
@override
|
||||
bool get supportsLatest => false;
|
||||
|
||||
@override
|
||||
String get baseUrl => getPreferenceValue(source.id, "pref_domain");
|
||||
|
||||
@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",
|
||||
title: "Currently used domain",
|
||||
summary: "",
|
||||
value: "https://uhdmovies.zip",
|
||||
dialogTitle: "Currently used domain",
|
||||
dialogMessage: "",
|
||||
text: "https://uhdmovies.zip"),
|
||||
];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||