diff --git a/anime/multisrc/datalifeengine/datalifeengine.dart b/anime/multisrc/datalifeengine/datalifeengine.dart new file mode 100644 index 00000000..a10a1814 --- /dev/null +++ b/anime/multisrc/datalifeengine/datalifeengine.dart @@ -0,0 +1,336 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class DataLifeEngine extends MProvider { + DataLifeEngine(); + + @override + bool get supportsLatest => false; + + @override + Future getPopular(MSource source, int page) async { + final data = {"url": "${source.baseUrl}${getPath(source)}page/$page"}; + final res = await http('GET', json.encode(data)); + return animeFromElement(res, source.baseUrl); + } + + @override + Future getLatestUpdates(MSource source, int page) async { + return MPages([], false); + } + + @override + Future search( + MSource source, String query, int page, FilterList filterList) async { + final filters = filterList.filters; + final baseUrl = source.baseUrl; + String res = ""; + if (query.isNotEmpty) { + if (query.length < 4) return MPages([], false); + final headers = { + "Host": Uri.parse(baseUrl).host, + "Origin": baseUrl, + "Referer": "$baseUrl/" + }; + final cleanQuery = query.replaceAll(" ", "+"); + if (page == 1) { + res = await http( + 'POST', + json.encode({ + "url": "$baseUrl?do=search&subaction=search&story=$cleanQuery", + "headers": headers + })); + } else { + res = await http( + 'POST', + json.encode({ + "url": + "$baseUrl?do=search&subaction=search&search_start=$page&full_search=0&result_from=11&story=$cleanQuery", + "headers": headers + })); + } + } else { + String url = ""; + for (var filter in filters) { + if (filter.type == "CategoriesFilter") { + if (filter.state != 0) { + url = "$baseUrl${filter.values[filter.state].value}page/$page/"; + } + } else if (filter.type == "GenresFilter") { + if (filter.state != 0) { + url = "$baseUrl${filter.values[filter.state].value}page/$page/"; + } + } + } + res = await http('GET', json.encode({"url": url})); + } + + return animeFromElement(res, baseUrl); + } + + @override + Future getDetail(MSource source, String url) async { + final data = {"url": url}; + String res = await http('GET', json.encode(data)); + MManga anime = MManga(); + final description = xpath(res, '//span[@itemprop="description"]/text()'); + anime.description = description.isNotEmpty ? description.first : ""; + anime.genre = xpath(res, '//span[@itemprop="genre"]/a/text()'); + + List? episodesList = []; + + if (source.name == "French Anime") { + final epsData = xpath(res, '//div[@class="eps"]/text()'); + for (var epData in epsData.first.split('\n')) { + final data = epData.split('!'); + MChapter ep = MChapter(); + ep.name = "Episode ${data.first}"; + ep.url = data.last; + episodesList.add(ep); + } + } else { + final eps = xpath(res, + '//*[@class="hostsblock"]/div/a[contains(@href,"https")]/parent::div/@class'); + if (eps.isNotEmpty) { + for (var i = 0; i < eps.length; i++) { + final epUrls = xpath(res, + '//*[@class="hostsblock"]/div[@class="${eps[i]}"]/a[contains(@href,"https")]/@href'); + MChapter ep = MChapter(); + ep.name = "Episode ${i + 1}"; + ep.url = epUrls.join(",").replaceAll("/vd.php?u=", ""); + ep.scanlator = eps[i].contains('vf') ? 'VF' : 'VOSTFR'; + episodesList.add(ep); + } + } else { + anime.status = MStatus.completed; + final epUrls = xpath(res, + '//*[contains(@class,"filmlinks")]/div/a[contains(@href,"https")]/@href'); + MChapter ep = MChapter(); + ep.name = "Film"; + ep.url = epUrls.join(",").replaceAll("/vd.php?u=", ""); + episodesList.add(ep); + } + } + + anime.chapters = episodesList.reversed.toList(); + return anime; + } + + @override + Future> getVideoList(MSource source, String url) async { + List videos = []; + final sUrls = url.split(','); + for (var sUrl in sUrls) { + List a = []; + if (sUrl.contains("dood")) { + a = await doodExtractor(sUrl, "DoodStream"); + } else if (sUrl.contains("voe.sx")) { + a = await voeExtractor(sUrl, "Voe"); + } else if (sUrl.contains("streamvid") || + sUrl.contains("guccihide") || + sUrl.contains("streamhide")) { + a = await streamHideExtractor(sUrl); + } else if (sUrl.contains("uqload")) { + a = await uqloadExtractor(sUrl); + } else if (sUrl.contains("upstream")) { + a = await upstreamExtractor(sUrl); + } else if (sUrl.contains("sibnet")) { + a = await sibnetExtractor(sUrl); + } else if (sUrl.contains("ok.ru")) { + a = await okruExtractor(sUrl); + } + videos.addAll(a); + } + return videos; + } + + MPages animeFromElement(String res, String baseUrl) { + final htmls = querySelectorAll(res, + selector: "div#dle-content > div.mov", + typeElement: 1, + attributes: "", + typeRegExp: 0); + List animeList = []; + for (var html in htmls) { + final url = xpath(html, '//a/@href').first; + final name = xpath(html, '//a/text()').first; + final image = xpath(html, '//div[contains(@class,"mov")]/img/@src').first; + final season = xpath(html, '//div/span[@class="block-sai"]/text()'); + MManga anime = MManga(); + anime.name = + "$name ${season.isNotEmpty ? season.first.replaceAll("\n", " ") : ""}"; + anime.imageUrl = "$baseUrl$image"; + anime.link = url; + animeList.add(anime); + } + final hasNextPage = xpath(res, '//span[@class="pnext"]/a/@href').isNotEmpty; + return MPages(animeList, hasNextPage); + } + + Future> streamHideExtractor(String url) async { + final res = await http('GET', json.encode({"url": url})); + final masterUrl = substringBefore( + substringAfter( + substringAfter(substringAfter(evalJs(res), "sources:"), "file:\""), + "src:\""), + '"'); + final masterPlaylistRes = + await http('GET', json.encode({"url": masterUrl})); + List videos = []; + for (var it in substringAfter(masterPlaylistRes, "#EXT-X-STREAM-INF:") + .split("#EXT-X-STREAM-INF:")) { + final quality = + "${substringBefore(substringBefore(substringAfter(substringAfter(it, "RESOLUTION="), "x"), ","), "\n")}p"; + + String videoUrl = substringBefore(substringAfter(it, "\n"), "\n"); + + if (!videoUrl.startsWith("http")) { + videoUrl = + "${masterUrl.split("/").sublist(0, masterUrl.split("/").length - 1).join("/")}/$videoUrl"; + } + + MVideo video = MVideo(); + video + ..url = videoUrl + ..originalUrl = videoUrl + ..quality = "StreamHideVid - $quality"; + videos.add(video); + } + return videos; + } + + Future> upstreamExtractor(String url) async { + final res = await http('GET', json.encode({"url": url})); + final js = xpath(res, '//script[contains(text(), "m3u8")]/text()'); + if (js.isEmpty) { + return []; + } + final masterUrl = + substringBefore(substringAfter(evalJs(js.first), "{file:\""), "\"}"); + final masterPlaylistRes = + await http('GET', json.encode({"url": masterUrl})); + List videos = []; + for (var it in substringAfter(masterPlaylistRes, "#EXT-X-STREAM-INF:") + .split("#EXT-X-STREAM-INF:")) { + final quality = + "${substringBefore(substringBefore(substringAfter(substringAfter(it, "RESOLUTION="), "x"), ","), "\n")}p"; + + String videoUrl = substringBefore(substringAfter(it, "\n"), "\n"); + + if (!videoUrl.startsWith("http")) { + videoUrl = + "${masterUrl.split("/").sublist(0, masterUrl.split("/").length - 1).join("/")}/$videoUrl"; + } + + MVideo video = MVideo(); + video + ..url = videoUrl + ..originalUrl = videoUrl + ..quality = "Upstream - $quality"; + videos.add(video); + } + return videos; + } + + Future> uqloadExtractor(String url) async { + final res = await http('GET', json.encode({"url": url})); + final js = xpath(res, '//script[contains(text(), "sources:")]/text()'); + if (js.isEmpty) { + return []; + } + + final videoUrl = + substringBefore(substringAfter(js.first, "sources: [\""), '"'); + MVideo video = MVideo(); + video + ..url = videoUrl + ..originalUrl = videoUrl + ..quality = "Uqload" + ..headers = {"Referer": "${Uri.parse(url).origin}/"}; + return [video]; + } + + String getPath(MSource source) { + if (source.name == "French Anime") return "/animes-vostfr/"; + return "/serie-en-streaming/"; + } + + @override + List getFilterList(MSource source) { + return [ + HeaderFilter("La recherche de texte ignore les filtres"), + if (source.name == "French Anime") + SelectFilter("CategoriesFilter", "Catégories", 0, [ + SelectFilterOption("", ""), + SelectFilterOption("Action", "/genre/action/"), + SelectFilterOption("Aventure", "/genre/aventure/"), + SelectFilterOption("Arts martiaux", "/genre/arts-martiaux/"), + SelectFilterOption("Combat", "/genre/combat/"), + SelectFilterOption("Comédie", "/genre/comedie/"), + SelectFilterOption("Drame", "/genre/drame/"), + SelectFilterOption("Epouvante", "/genre/epouvante/"), + SelectFilterOption("Fantastique", "/genre/fantastique/"), + SelectFilterOption("Fantasy", "/genre/fantasy/"), + SelectFilterOption("Mystère", "/genre/mystere/"), + SelectFilterOption("Romance", "/genre/romance/"), + SelectFilterOption("Shonen", "/genre/shonen/"), + SelectFilterOption("Surnaturel", "/genre/surnaturel/"), + SelectFilterOption("Sci-Fi", "/genre/sci-fi/"), + SelectFilterOption("School life", "/genre/school-life/"), + SelectFilterOption("Ninja", "/genre/ninja/"), + SelectFilterOption("Seinen", "/genre/seinen/"), + SelectFilterOption("Horreur", "/genre/horreur/"), + SelectFilterOption("Tranche de vie", "/genre/tranchedevie/"), + SelectFilterOption("Psychologique", "/genre/psychologique/") + ]), + if (source.name == "French Anime") + SelectFilter("GenresFilter", "Genres", 0, [ + SelectFilterOption("", ""), + SelectFilterOption("Animes VF", "/animes-vf/"), + SelectFilterOption("Animes VOSTFR", "/animes-vostfr/"), + SelectFilterOption("Films VF et VOSTFR", "/films-vf-vostfr/") + ]), + if (source.name == "Wiflix") + SelectFilter("CategoriesFilter", "Catégories", 0, [ + SelectFilterOption("", ""), + SelectFilterOption("Séries", "/serie-en-streaming/"), + SelectFilterOption("Films", "/film-en-streaming/") + ]), + if (source.name == "Wiflix") + SelectFilter("GenresFilter", "Genres", 0, [ + SelectFilterOption("", ""), + SelectFilterOption("Action", "/film-en-streaming/action/"), + SelectFilterOption("Animation", "/film-en-streaming/animation/"), + SelectFilterOption( + "Arts Martiaux", "/film-en-streaming/arts-martiaux/"), + SelectFilterOption("Aventure", "/film-en-streaming/aventure/"), + SelectFilterOption("Biopic", "/film-en-streaming/biopic/"), + SelectFilterOption("Comédie", "/film-en-streaming/comedie/"), + SelectFilterOption( + "Comédie Dramatique", "/film-en-streaming/comedie-dramatique/"), + SelectFilterOption( + "Épouvante Horreur", "/film-en-streaming/horreur/"), + SelectFilterOption("Drame", "/film-en-streaming/drame/"), + SelectFilterOption( + "Documentaire", "/film-en-streaming/documentaire/"), + SelectFilterOption("Espionnage", "/film-en-streaming/espionnage/"), + SelectFilterOption("Famille", "/film-en-streaming/famille/"), + SelectFilterOption("Fantastique", "/film-en-streaming/fantastique/"), + SelectFilterOption("Guerre", "/film-en-streaming/guerre/"), + SelectFilterOption("Historique", "/film-en-streaming/historique/"), + SelectFilterOption("Musical", "/film-en-streaming/musical/"), + SelectFilterOption("Policier", "/film-en-streaming/policier/"), + SelectFilterOption("Romance", "/film-en-streaming/romance/"), + SelectFilterOption( + "Science-Fiction", "/film-en-streaming/science-fiction/"), + SelectFilterOption("Spectacles", "/film-en-streaming/spectacles/"), + SelectFilterOption("Thriller", "/film-en-streaming/thriller/"), + SelectFilterOption("Western", "/film-en-streaming/western/"), + ]), + ]; + } +} + +DataLifeEngine main() { + return DataLifeEngine(); +} diff --git a/anime/multisrc/datalifeengine/sources.dart b/anime/multisrc/datalifeengine/sources.dart new file mode 100644 index 00000000..59f23442 --- /dev/null +++ b/anime/multisrc/datalifeengine/sources.dart @@ -0,0 +1,19 @@ +import '../../../model/source.dart'; +import '../../src/fr/franime/source.dart'; +import 'src/wiflix/wiflix.dart'; + +const _datalifeengineVersion = "0.0.1"; +const _datalifeengineSourceCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/multisrc/datalifeengine/datalifeengine-v$_datalifeengineVersion.dart"; + +List get datalifeengineSourcesList => _datalifeengineSourcesList; +List _datalifeengineSourcesList = [ +//French Anime (FR) + franimeSource, +//Wiflix (FR) + wiflixSource, +] + .map((e) => e + ..sourceCodeUrl = _datalifeengineSourceCodeUrl + ..version = _datalifeengineVersion) + .toList(); diff --git a/anime/multisrc/datalifeengine/src/frenchanime/frenchanime-v0.0.1.dart b/anime/multisrc/datalifeengine/src/frenchanime/frenchanime-v0.0.1.dart new file mode 100644 index 00000000..bf4a8613 --- /dev/null +++ b/anime/multisrc/datalifeengine/src/frenchanime/frenchanime-v0.0.1.dart @@ -0,0 +1,13 @@ +import '../../../../../model/source.dart'; + +Source get frenchanimeSource => _frenchanimeSource; + +Source _frenchanimeSource = Source( + name: "French Anime", + baseUrl: "https://french-anime.com", + lang: "fr", + typeSource: "datalifeengine", + isManga: false, + iconUrl: + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/multisrc/datalifeengine/src/frenchanime/icon.png", +); diff --git a/anime/multisrc/datalifeengine/src/frenchanime/icon.png b/anime/multisrc/datalifeengine/src/frenchanime/icon.png new file mode 100644 index 00000000..62591a8e Binary files /dev/null and b/anime/multisrc/datalifeengine/src/frenchanime/icon.png differ diff --git a/anime/multisrc/datalifeengine/src/wiflix/icon.png b/anime/multisrc/datalifeengine/src/wiflix/icon.png new file mode 100644 index 00000000..3317e744 Binary files /dev/null and b/anime/multisrc/datalifeengine/src/wiflix/icon.png differ diff --git a/anime/multisrc/datalifeengine/src/wiflix/wiflix.dart b/anime/multisrc/datalifeengine/src/wiflix/wiflix.dart new file mode 100644 index 00000000..7dac63a0 --- /dev/null +++ b/anime/multisrc/datalifeengine/src/wiflix/wiflix.dart @@ -0,0 +1,13 @@ +import '../../../../../model/source.dart'; + +Source get wiflixSource => _wiflixSource; + +Source _wiflixSource = Source( + name: "Wiflix", + baseUrl: "https://wiflix.voto", + lang: "fr", + typeSource: "datalifeengine", + isManga: false, + iconUrl: + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/multisrc/datalifeengine/src/wiflix/icon.png", +); diff --git a/anime/source_generator.dart b/anime/source_generator.dart index 3e4a2b3a..8ef1722a 100644 --- a/anime/source_generator.dart +++ b/anime/source_generator.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import '../model/source.dart'; +import 'multisrc/datalifeengine/sources.dart'; import 'multisrc/dopeflix/sources.dart'; import 'multisrc/zorotheme/sources.dart'; import 'src/ar/okanime/source.dart'; @@ -32,7 +33,8 @@ void main() { aniwave, ...dopeflixSourcesList, animesaturn, - uhdmoviesSource + uhdmoviesSource, + ...datalifeengineSourcesList ]; final List> jsonList = _sourcesList.map((source) => source.toJson()).toList();