From a1a0c48e6084213d9eb3f782a6e7ad971d35e6f4 Mon Sep 17 00:00:00 2001 From: kodjomoustapha <107993382+kodjodevf@users.noreply.github.com> Date: Thu, 30 Nov 2023 17:09:54 +0100 Subject: [PATCH] New sources Multi: DopeFlix --- anime/multisrc/dopeflix/dopeflix-v0.0.1.dart | 411 +++++++++++++++++++ anime/multisrc/dopeflix/sources.dart | 28 ++ anime/source_generator.dart | 4 +- icons/mangayomi-en-dopebox.png | Bin 0 -> 4126 bytes icons/mangayomi-en-sflix.png | Bin 0 -> 6778 bytes 5 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 anime/multisrc/dopeflix/dopeflix-v0.0.1.dart create mode 100644 anime/multisrc/dopeflix/sources.dart create mode 100644 icons/mangayomi-en-dopebox.png create mode 100644 icons/mangayomi-en-sflix.png diff --git a/anime/multisrc/dopeflix/dopeflix-v0.0.1.dart b/anime/multisrc/dopeflix/dopeflix-v0.0.1.dart new file mode 100644 index 00000000..d69cffe3 --- /dev/null +++ b/anime/multisrc/dopeflix/dopeflix-v0.0.1.dart @@ -0,0 +1,411 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class DopeFlix extends MProvider { + DopeFlix(); + + @override + Future getPopular(MSource source, int page) async { + final data = {"url": "${source.baseUrl}/movie?page=$page"}; + final res = await http('GET', json.encode(data)); + return parseAnimeList(res); + } + + @override + Future getLatestUpdates(MSource source, int page) async { + final data = {"url": "${source.baseUrl}/home"}; + final res = await http('GET', json.encode(data)); + List animeList = []; + final path = + '//section[contains(text(),"Latest Movies")]/div/div[@class="film_list-wrap"]/div[@class="flw-item"]/div[@class="film-poster"]'; + final urls = xpath(res, '$path/a/@href'); + final names = xpath(res, '$path/a/@title'); + final images = xpath(res, '$path/img/@data-src'); + + for (var i = 0; i < names.length; i++) { + MManga anime = MManga(); + anime.name = names[i]; + anime.imageUrl = images[i]; + anime.link = urls[i]; + animeList.add(anime); + } + return MPages(animeList, false); + } + + @override + Future search( + MSource source, String query, int page, FilterList filterList) async { + final filters = filterList.filters; + String url = "${source.baseUrl}"; + + if (query.isNotEmpty) { + url += "/search/${query.replaceAll(" ", "-")}?page=$page"; + } else { + url += "/filter/?page=$page"; + for (var filter in filters) { + if (filter.type == "TypeFilter") { + final type = filter.values[filter.state].value; + url += "${ll(url)}type=$type"; + } else if (filter.type == "QualityFilter") { + final quality = filter.values[filter.state].value; + url += "${ll(url)}quality=$quality"; + } else if (filter.type == "ReleaseYearFilter") { + final year = filter.values[filter.state].value; + url += "${ll(url)}release_year=$year"; + } else if (filter.type == "GenresFilter") { + final genre = (filter.state as List).where((e) => e.state).toList(); + if (genre.isNotEmpty) { + url += "${ll(url)}genre="; + for (var st in genre) { + url += "${st.value}-"; + } + } + } else if (filter.type == "CountriesFilter") { + final country = (filter.state as List).where((e) => e.state).toList(); + if (country.isNotEmpty) { + url += "${ll(url)}country="; + for (var st in country) { + url += "${st.value}-"; + } + } + } + } + } + final data = {"url": url}; + final res = await http('GET', json.encode(data)); + return parseAnimeList(res); + } + + @override + Future getDetail(MSource source, String url) async { + url = Uri.parse(url).path; + final data = {"url": "${source.baseUrl}$url"}; + final res = await http('GET', json.encode(data)); + MManga anime = MManga(); + final description = xpath(res, '//div[@class="description"]/text()'); + if (description.isNotEmpty) { + anime.description = description.first.replaceAll("Overview:", ""); + } + final author = xpath(res, '//div[contains(text(),"Production")]/a/text()'); + if (author.isNotEmpty) { + anime.author = author.first; + } + anime.genre = xpath(res, '//div[contains(text(),"Genre")]/a/text()'); + List episodesList = []; + final id = xpath(res, '//div[@class="detail_page-watch"]/@data-id').first; + final dataType = + xpath(res, '//div[@class="detail_page-watch"]/@data-type').first; + if (dataType == "1") { + MChapter episode = MChapter(); + episode.name = "Movie"; + episode.url = "${source.baseUrl}/ajax/movie/episodes/$id"; + episodesList.add(episode); + } else { + final dataS = {"url": "${source.baseUrl}/ajax/v2/tv/seasons/$id"}; + final resS = await http('GET', json.encode(dataS)); + final seasonId = + xpath(resS, '//a[@class="dropdown-item ss-item"]/@data-id').first; + final seasonName = + xpath(resS, '//a[@class="dropdown-item ss-item"]/text()').first; + final dataE = { + "url": "${source.baseUrl}/ajax/v2/season/episodes/$seasonId" + }; + final html = await http('GET', json.encode(dataE)); + final epsHtml = querySelectorAll(html, + selector: "div.eps-item", + typeElement: 2, + attributes: "", + typeRegExp: 0); + for (var epHtml in epsHtml) { + final episodeId = + xpath(epHtml, '//div[contains(@class,"eps-item")]/@data-id').first; + final epNum = + xpath(epHtml, '//div[@class="episode-number"]/text()').first; + final epName = xpath(epHtml, '//h3[@class="film-name"]/text()').first; + MChapter episode = MChapter(); + episode.name = "$seasonName $epNum $epName"; + episode.url = "${source.baseUrl}/ajax/v2/episode/servers/$episodeId"; + episodesList.add(episode); + } + } + anime.chapters = episodesList.reversed.toList(); + return anime; + } + + @override + Future> getVideoList(MSource source, String url) async { + url = Uri.parse(url).path; + final res = + await http('GET', json.encode({"url": "${source.baseUrl}/$url"})); + final vidsHtml = querySelectorAll(res, + selector: "ul.fss-list a.btn-play", + typeElement: 2, + attributes: "", + typeRegExp: 0); + List videos = []; + for (var vidHtml in vidsHtml) { + final id = xpath(vidHtml, '//a/@data-id').first; + final name = xpath(vidHtml, '//span/text()').first; + final resSource = await http( + 'GET', json.encode({"url": "${source.baseUrl}/ajax/sources/$id"})); + final vidUrl = + substringBefore(substringAfter(resSource, "\"link\":\""), "\""); + List a = []; + if (name.contains("DoodStream")) { + a = await doodExtractor(vidUrl, "DoodStream"); + } else if (["Vidcloud", "UpCloud"].contains(name)) { + final id = substringBefore(substringAfter(vidUrl, "/embed-4/"), "?"); + final serverUrl = substringBefore(vidUrl, "/embed"); + final datasServer = { + "url": "$serverUrl/ajax/embed-4/getSources?id=$id", + "headers": {"X-Requested-With": "XMLHttpRequest"} + }; + + final resServer = await http('GET', json.encode(datasServer)); + final encrypted = getMapValue(resServer, "encrypted"); + String videoResJson = ""; + if (encrypted == "true") { + final ciphered = getMapValue(resServer, "sources"); + + List> indexPairs = await generateIndexPairs(); + + var password = ''; + String ciphertext = ciphered; + int index = 0; + for (List 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>) + .first)['file']; + String type = ((json.decode(videoResJson) as List>) + .first)['type']; + + final tracks = (json.decode(resServer)['tracks'] as List) + .where((e) => e['kind'] == 'captions' ? true : false) + .toList(); + List 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 http('GET', json.encode({"url": masterUrl})); + 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; + a.add(video); + } + } else { + MVideo video = MVideo(); + video + ..url = masterUrl + ..originalUrl = masterUrl + ..quality = "$name - Default" + ..subtitles = subtitles; + a.add(video); + } + } + videos.addAll(a); + } + + return videos; + } + + Future>> generateIndexPairs() async { + final res = await http( + 'GET', + json.encode({ + "url": "https://rabbitstream.net/js/player/prod/e4-player.min.js" + })); + String script = substringBefore(substringAfter(res, "const "), "()"); + script = script.substring(0, script.lastIndexOf(',')); + final list = script + .split(",") + .map((e) { + String value = substringAfter((e as String), "="); + if (value.contains("0x")) { + return int.parse(substringAfter(value, "0x"), radix: 16); + } else { + return int.parse(value); + } + }) + .toList() + .skip(1) + .toList(); + return chunked(list, 2) + .map((list) => (list as List).reversed.toList()) + .toList(); + } + + List> chunked(List list, int size) { + List> chunks = []; + for (int i = 0; i < list.length; i += size) { + int end = list.length; + if (i + size < list.length) { + end = i + size; + } + chunks.add(list.sublist(i, end)); + } + return chunks; + } + + MPages parseAnimeList(String res) { + List animeList = []; + final path = + '//div[@class="film_list-wrap"]/div[@class="flw-item"]/div[@class="film-poster"]'; + final urls = xpath(res, '$path/a/@href'); + final names = xpath(res, '$path/a/@title'); + final images = xpath(res, '$path/img/@data-src'); + + for (var i = 0; i < names.length; i++) { + MManga anime = MManga(); + anime.name = names[i]; + anime.imageUrl = images[i]; + anime.link = urls[i]; + animeList.add(anime); + } + final pages = xpath( + res, '//ul[contains(@class,"pagination")]/li/a[@title="Next"]/@title'); + return MPages(animeList, pages.isNotEmpty); + } + + @override + List getFilterList() { + return [ + SelectFilter("TypeFilter", "Type", 0, [ + SelectFilterOption("All", "all"), + SelectFilterOption("Movies", "movies"), + SelectFilterOption("TV Shows", "tv") + ]), + SelectFilter("QualityFilter", "Quality", 0, [ + SelectFilterOption("All", "all"), + SelectFilterOption("HD", "HD"), + SelectFilterOption("SD", "SD"), + SelectFilterOption("CAM", "CAM") + ]), + SelectFilter("ReleaseYearFilter", "Released at", 0, [ + SelectFilterOption("All", "all"), + SelectFilterOption("2023", "2023"), + SelectFilterOption("2022", "2022"), + SelectFilterOption("2021", "2021"), + SelectFilterOption("2020", "2020"), + SelectFilterOption("2019", "2019"), + SelectFilterOption("2018", "2018"), + SelectFilterOption("Older", "older-2018") + ]), + SeparatorFilter(), + GroupFilter("GenresFilter", "Genre", [ + CheckBoxFilter("Action", "10"), + CheckBoxFilter("Action & Adventure", "24"), + CheckBoxFilter("Adventure", "18"), + CheckBoxFilter("Animation", "3"), + CheckBoxFilter("Biography", "37"), + CheckBoxFilter("Comedy", "7"), + CheckBoxFilter("Crime", "2"), + CheckBoxFilter("Documentary", "11"), + CheckBoxFilter("Drama", "4"), + CheckBoxFilter("Family", "9"), + CheckBoxFilter("Fantasy", "13"), + CheckBoxFilter("History", "19"), + CheckBoxFilter("Horror", "14"), + CheckBoxFilter("Kids", "27"), + CheckBoxFilter("Music", "15"), + CheckBoxFilter("Mystery", "1"), + CheckBoxFilter("News", "34"), + CheckBoxFilter("Reality", "22"), + CheckBoxFilter("Romance", "12"), + CheckBoxFilter("Sci-Fi & Fantasy", "31"), + CheckBoxFilter("Science Fiction", "5"), + CheckBoxFilter("Soap", "35"), + CheckBoxFilter("Talk", "29"), + CheckBoxFilter("Thriller", "16"), + CheckBoxFilter("TV Movie", "8"), + CheckBoxFilter("War", "17"), + CheckBoxFilter("War & Politics", "28"), + CheckBoxFilter("Western", "6") + ]), + GroupFilter("CountriesFilter", "Countries", [ + CheckBoxFilter("Argentina", "11"), + CheckBoxFilter("Australia", "151"), + CheckBoxFilter("Austria", "4"), + CheckBoxFilter("Belgium", "44"), + CheckBoxFilter("Brazil", "190"), + CheckBoxFilter("Canada", "147"), + CheckBoxFilter("China", "101"), + CheckBoxFilter("Czech Republic", "231"), + CheckBoxFilter("Denmark", "222"), + CheckBoxFilter("Finland", "158"), + CheckBoxFilter("France", "3"), + CheckBoxFilter("Germany", "96"), + CheckBoxFilter("Hong Kong", "93"), + CheckBoxFilter("Hungary", "72"), + CheckBoxFilter("India", "105"), + CheckBoxFilter("Ireland", "196"), + CheckBoxFilter("Israel", "24"), + CheckBoxFilter("Italy", "205"), + CheckBoxFilter("Japan", "173"), + CheckBoxFilter("Luxembourg", "91"), + CheckBoxFilter("Mexico", "40"), + CheckBoxFilter("Netherlands", "172"), + CheckBoxFilter("New Zealand", "122"), + CheckBoxFilter("Norway", "219"), + CheckBoxFilter("Poland", "23"), + CheckBoxFilter("Romania", "170"), + CheckBoxFilter("Russia", "109"), + CheckBoxFilter("South Africa", "200"), + CheckBoxFilter("South Korea", "135"), + CheckBoxFilter("Spain", "62"), + CheckBoxFilter("Sweden", "114"), + CheckBoxFilter("Switzerland", "41"), + CheckBoxFilter("Taiwan", "119"), + CheckBoxFilter("Thailand", "57"), + CheckBoxFilter("United Kingdom", "180"), + CheckBoxFilter("United States of America", "129") + ]), + ]; + } + + String ll(String url) { + if (url.contains("?")) { + return "&"; + } + return "?"; + } +} + +DopeFlix main() { + return DopeFlix(); +} diff --git a/anime/multisrc/dopeflix/sources.dart b/anime/multisrc/dopeflix/sources.dart new file mode 100644 index 00000000..0f6c7244 --- /dev/null +++ b/anime/multisrc/dopeflix/sources.dart @@ -0,0 +1,28 @@ +import '../../../model/source.dart'; +import '../../../utils/utils.dart'; + +const dopeflixVersion = "0.0.1"; +const dopeflixSourceCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/multisrc/dopeflix/dopeflix-v$dopeflixVersion.dart"; + +List get dopeflixSourcesList => _dopeflixSourcesList; +List _dopeflixSourcesList = [ + Source( + name: "DopeBox", + baseUrl: "https://dopebox.to", + lang: "en", + typeSource: "dopeflix", + iconUrl: getIconUrl("dopebox", "en"), + version: dopeflixVersion, + isManga: false, + sourceCodeUrl: dopeflixSourceCodeUrl), + Source( + name: "SFlix", + baseUrl: "https://sflix.to", + lang: "en", + typeSource: "dopeflix", + iconUrl: getIconUrl("sflix", "en"), + version: dopeflixVersion, + isManga: false, + sourceCodeUrl: dopeflixSourceCodeUrl), +]; diff --git a/anime/source_generator.dart b/anime/source_generator.dart index e1e64861..9589261a 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/dopeflix/sources.dart'; import 'multisrc/zorotheme/sources.dart'; import 'src/ar/okanime/source.dart'; import 'src/en/aniwave/source.dart'; @@ -26,7 +27,8 @@ void main() { otakudesu, nimegami, oploverz, - aniwave + aniwave, + ...dopeflixSourcesList ]; final List> jsonList = _sourcesList.map((source) => source.toJson()).toList(); diff --git a/icons/mangayomi-en-dopebox.png b/icons/mangayomi-en-dopebox.png new file mode 100644 index 0000000000000000000000000000000000000000..0e467600241cdb6cdb05e90b4d90c53951be8003 GIT binary patch literal 4126 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 literal 0 HcmV?d00001 diff --git a/icons/mangayomi-en-sflix.png b/icons/mangayomi-en-sflix.png new file mode 100644 index 0000000000000000000000000000000000000000..afdbec537024a228fb5883ee21a326621a4f7a74 GIT binary patch literal 6778 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 literal 0 HcmV?d00001