New source: AnimeWorld India (MULTI)

This commit is contained in:
kodjomoustapha
2024-01-04 17:24:59 +01:00
parent e6c6fb45e2
commit 8d3b79251c
4 changed files with 446 additions and 1 deletions

View File

@@ -0,0 +1,411 @@
import 'package:mangayomi/bridge_lib.dart';
import 'dart:convert';
class AnimeWorldIndia extends MProvider {
AnimeWorldIndia();
@override
Future<MPages> getPopular(MSource source, int page) async {
final data = {
"url":
"${source.baseUrl}/advanced-search/page/$page/?s_lang=${source.lang}&s_orderby=viewed"
};
final res = await http('GET', json.encode(data));
return parseAnimeList(res, source.baseUrl);
}
@override
Future<MPages> getLatestUpdates(MSource source, int page) async {
final data = {
"url":
"${source.baseUrl}/advanced-search/page/$page/?s_lang=${source.lang}&s_orderby=update"
};
final res = await http('GET', json.encode(data));
return parseAnimeList(res, source.baseUrl);
}
@override
Future<MPages> search(
MSource source, 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) {
url += "${st.value}".toLowerCase().replaceAll(" ", "-");
if (genre.length > 1) {
url += "%2C";
}
}
if (genre.length > 1) {
url = substringBeforeLast(url, '%2C');
}
}
}
}
final data = {"url": url};
final res = await http('GET', json.encode(data));
return parseAnimeList(res, source.baseUrl);
}
@override
Future<MManga> getDetail(MSource source, String url) async {
final data = {"url": url};
final res = await http('GET', json.encode(data));
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(MSource source, String url) async {
final res =
await http('GET', json.encode({"url": "${source.baseUrl}$url"}));
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);
}
String getUrlWithoutDomain(String orig) {
final uri = Uri.parse(orig.replaceAll(' ', '%20'));
String out = uri.path;
if (uri.query.isNotEmpty) {
out += '?${uri.query}';
}
if (uri.fragment.isNotEmpty) {
out += '#${uri.fragment}';
}
return out;
}
MPages parseAnimeList(String res, String baseUrl) {
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 =
"$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 http('GET', json.encode({"url": url}));
final streamCode = substringBefore(
substringAfter(substringAfter(res, "sniff("), ", \""), '"');
final streamUrl =
"${substringBefore(url, "/watch")}/m3u8/$streamCode/master.txt?s=1&cache=1";
final masterPlaylistRes =
await http('GET', json.encode({"url": streamUrl}));
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(MSource source) {
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(MSource source) {
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() {
return AnimeWorldIndia();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -0,0 +1,32 @@
import '../../../../model/source.dart';
const _animeworldindiaVersion = "0.0.1";
const _animeworldindiaSourceCodeUrl =
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/anime/src/all/animeworldindia/animeworldindia.dart";
String _iconUrl =
"https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/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,
sourceCodeUrl: _animeworldindiaSourceCodeUrl))
.toList();