diff --git a/anime/source_generator.dart b/anime/source_generator.dart index 8e4388f1..8d3537e5 100644 --- a/anime/source_generator.dart +++ b/anime/source_generator.dart @@ -9,6 +9,9 @@ import 'src/en/kisskh/source.dart'; import 'src/fr/animesultra/source.dart'; import 'src/fr/franime/source.dart'; import 'src/fr/otakufr/source.dart'; +import 'src/id/nimegami/source.dart'; +import 'src/id/oploverz/source.dart'; +import 'src/id/otakudesu/source.dart'; void main() { List _sourcesList = [ @@ -18,7 +21,10 @@ void main() { animesultraSource, ...zorothemeSourcesList, kisskhSource, - okanimeSource + okanimeSource, + otakudesu, + nimegami, + oploverz ]; final List> jsonList = _sourcesList.map((source) => source.toJson()).toList(); diff --git a/anime/src/id/nimegami/nimegami-v0.0.1.dart b/anime/src/id/nimegami/nimegami-v0.0.1.dart new file mode 100644 index 00000000..bf778444 --- /dev/null +++ b/anime/src/id/nimegami/nimegami-v0.0.1.dart @@ -0,0 +1,179 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class NimeGami extends MProvider { + NimeGami(); + + @override + Future getPopular(MSource source, int page) async { + final data = {"url": "${source.baseUrl}/page/$page"}; + final res = await http('GET', json.encode(data)); + List animeList = []; + final urls = xpath(res, '//div[@class="wrapper-2-a"]/article/a/@href'); + final names = xpath(res, '//div[@class="wrapper-2-a"]/article/a/@title'); + final images = + xpath(res, '//div[@class="wrapper-2-a"]/article/a/div/img/@src'); + + for (var i = 0; i < names.length; i++) { + MManga anime = MManga(); + anime.name = names[i]; + anime.imageUrl = images[i]; + anime.link = urls[i]; + animeList.add(anime); + } + return MPages(animeList, true); + } + + @override + Future getLatestUpdates(MSource source, int page) async { + final data = {"url": "${source.baseUrl}/page/$page"}; + final res = await http('GET', json.encode(data)); + List animeList = []; + final urls = xpath(res, '//div[@class="post-article"]/article/div/a/@href'); + final names = + xpath(res, '//div[@class="post-article"]/article/div/a/@title'); + final images = + xpath(res, '//div[@class="post-article"]/article/div/a/img/@src'); + + for (var i = 0; i < names.length; i++) { + MManga anime = MManga(); + anime.name = names[i]; + anime.imageUrl = images[i]; + anime.link = urls[i]; + animeList.add(anime); + } + return MPages(animeList, true); + } + + @override + Future search(MSource source, String query, int page) async { + final data = { + "url": "${source.baseUrl}/page/$page/?s=$query&post_type=post" + }; + final res = await http('GET', json.encode(data)); + List animeList = []; + final urls = xpath(res, '//div[@class="archive-a"]/article/div/a/@href'); + final names = xpath(res, '//div[@class="archive-a"]/article/h2/a/@title'); + final images = + xpath(res, '//div[@class="archive-a"]/article/div/a/img/@src'); + + for (var i = 0; i < names.length; i++) { + MManga anime = MManga(); + anime.name = names[i]; + anime.imageUrl = images[i]; + anime.link = urls[i]; + animeList.add(anime); + } + return MPages(animeList, true); + } + + @override + Future getDetail(MSource source, String url) async { + final data = {"url": url}; + final res = await http('GET', json.encode(data)); + MManga anime = MManga(); + final description = xpath(res, '//*[@id="Sinopsis"]/p/text()'); + if (description.isNotEmpty) { + anime.description = description.first; + } + + final author = xpath(res, '//tbody/tr[5]/td[2]/text()'); + if (author.isNotEmpty) { + anime.author = author.first; + } + anime.genre = xpath(res, '//tr/td[@class="info_a"]/a/text()'); + final epUrls = xpath(res, '//div[@class="list_eps_stream"]/li/@data') + .reversed + .toList(); + final epNums = + xpath(res, '//div[@class="list_eps_stream"]/li/@id').reversed.toList(); + final names = xpath(res, '//div[@class="list_eps_stream"]/li/text()') + .reversed + .toList(); + List? episodesList = []; + for (var i = 0; i < epUrls.length; i++) { + MChapter episode = MChapter(); + episode.name = names[i]; + episode.url = json.encode({ + "episodeIndex": int.parse(substringAfterLast(epNums[i], '_')), + 'urls': json.decode(base64(epUrls[i], 0)) + }); + episodesList.add(episode); + } + anime.chapters = episodesList; + return anime; + } + + @override + Future> getVideoList(MSource source, String url) async { + final resJson = json.decode(url); + final urls = resJson["urls"]; + List videos = []; + List a = []; + for (var data in urls) { + final quality = data["format"]; + for (var url in data["url"]) { + a = await extractVideos(quality, url); + videos.addAll(a); + } + } + return videos; + } + + Future> extractVideos(String quality, String url) async { + List videos = []; + List a = []; + if (url.contains("video.nimegami.id")) { + final realUrl = + base64(substringBefore(substringAfter(url, "url="), "&"), 0); + final a = await extractHXFileVideos(realUrl, quality); + videos.addAll(a); + } else if (url.contains("berkasdrive") || url.contains("drive.nimegami")) { + final res = await http('GET', json.encode({"url": url})); + final source = xpath(res, '//source/@src'); + if (source.isNotEmpty) { + videos.add(toVideo(source.first, "Berkasdrive - $quality")); + } + } else if (url.contains("hxfile.co")) { + final a = await extractHXFileVideos(url, quality); + videos.addAll(a); + } + + return videos; + } + + Future> extractHXFileVideos(String url, String quality) async { + if (!url.contains("embed-")) { + url = url.replaceAll(".co/", ".co/embed-") + ".html"; + } + final res = await http('GET', json.encode({"url": url})); + final script = xpath(res, + '//script[contains(text(), "eval") and contains(text(), "p,a,c,k,e,d")]/text()'); + if (script.isNotEmpty) { + final videoUrl = substringBefore( + substringAfter(substringAfter(evalJs(script.first), "sources:[", ""), + "file\":\"", ""), + '"'); + if (videoUrl.isNotEmpty) { + return [toVideo(videoUrl, "HXFile - $quality")]; + } + } + + return []; + } + + MVideo toVideo(String videoUrl, String quality) { + MVideo video = MVideo(); + video + ..url = videoUrl + ..originalUrl = videoUrl + ..quality = quality + ..subtitles = []; + + return video; + } +} + +NimeGami main() { + return NimeGami(); +} diff --git a/anime/src/id/nimegami/source.dart b/anime/src/id/nimegami/source.dart new file mode 100644 index 00000000..24e0d64e --- /dev/null +++ b/anime/src/id/nimegami/source.dart @@ -0,0 +1,17 @@ +import '../../../../model/source.dart'; +import '../../../../utils/utils.dart'; + +Source get nimegami => _nimegami; +const nimegamiVersion = "0.0.1"; +const nimegamiCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/src/id/nimegami/nimegami-v$nimegamiVersion.dart"; +Source _nimegami = Source( + name: "NimeGami", + baseUrl: "https://nimegami.id", + lang: "id", + typeSource: "single", + iconUrl: getIconUrl("nimegami", "id"), + sourceCodeUrl: nimegamiCodeUrl, + version: nimegamiVersion, + appMinVerReq: "0.0.7", + isManga: false); diff --git a/anime/src/id/oploverz/oploverz-v0.0.1.dart b/anime/src/id/oploverz/oploverz-v0.0.1.dart new file mode 100644 index 00000000..4884749b --- /dev/null +++ b/anime/src/id/oploverz/oploverz-v0.0.1.dart @@ -0,0 +1,157 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class OploVerz extends MProvider { + OploVerz(); + + @override + Future getPopular(MSource source, int page) async { + final data = { + "url": "${source.baseUrl}/anime-list/page/$page/?order=popular" + }; + final res = await http('GET', json.encode(data)); + return parseAnimeList(res); + } + + @override + Future getLatestUpdates(MSource source, int page) async { + final data = { + "url": "${source.baseUrl}/anime-list/page/$page/?order=latest" + }; + final res = await http('GET', json.encode(data)); + return parseAnimeList(res); + } + + @override + Future search(MSource source, String query, int page) async { + final data = { + "url": "${source.baseUrl}/anime-list/page/$page/?title=$query" + }; + final res = await http('GET', json.encode(data)); + return parseAnimeList(res); + } + + @override + Future getDetail(MSource source, String url) async { + final statusList = [ + {"ongoing": 0, "completed": 1} + ]; + final data = {"url": url}; + final res = await http('GET', json.encode(data)); + MManga anime = MManga(); + final status = xpath(res, '//*[@class="alternati"]/span[2]/text()'); + print(status); + if (status.isNotEmpty) { + anime.status = parseStatus(status.first, statusList); + } + anime.description = xpath(res, '//*[@class="desc"]/div/text()').first; + + anime.genre = xpath(res, '//*[@class="genre-info"]/a/text()'); + final epUrls = + xpath(res, '//div[@class="epsleft")]/span[@class="lchx"]/a/@href'); + final names = + xpath(res, '//div[@class="epsleft")]/span[@class="lchx"]/a/text()'); + final dates = + xpath(res, '//div[@class="epsleft")]/span[@class="date"]/text()'); + final dateUploads = parseDates(dates, "dd/MM/yyyy", "id"); + List? episodesList = []; + for (var i = 0; i < epUrls.length; i++) { + MChapter episode = MChapter(); + episode.name = names[i]; + episode.dateUpload = dateUploads[i]; + episode.url = epUrls[i]; + episodesList.add(episode); + } + + anime.chapters = episodesList; + return anime; + } + + @override + Future> getVideoList(MSource source, String url) async { + final res = await http('GET', json.encode({"url": url})); + final dataPost = xpath(res, + '//*[@id="server"]/ul/li/div[contains(@id,"player-option")]/@data-post') + .first; + final dataNume = xpath(res, + '//*[@id="server"]/ul/li/div[contains(@id,"player-option")]/@data-nume') + .first; + final dataType = xpath(res, + '//*[@id="server"]/ul/li/div[contains(@id,"player-option")]/@data-type') + .first; + final body = { + "action": "player_ajax", + "post": dataPost, + "nume": dataNume, + "type": dataType + }; + + final ress = await http( + 'POST', + json.encode({ + "useFormBuilder": true, + "body": body, + "url": "${source.baseUrl}/wp-admin/admin-ajax.php" + })); + final playerLink = + xpath(ress, '//iframe[@class="playeriframe"]/@src').first; + print(playerLink); + final resPlayer = await http('GET', json.encode({"url": playerLink})); + var resJson = substringBefore(substringAfter(resPlayer, "= "), "<"); + var streams = json.decode(resJson)["streams"] as List; + List videos = []; + for (var stream in streams) { + print(stream["play_url"]); + final videoUrl = stream["play_url"]; + final quality = getQuality(stream["format_id"]); + + MVideo video = MVideo(); + video + ..url = videoUrl + ..originalUrl = videoUrl + ..quality = quality + ..subtitles = []; + videos.add(video); + } + + return videos; + } + + String getQuality(int formatId) { + if (formatId == 18) { + return "Google - 360p"; + } else if (formatId == 22) { + return "Google - 720p"; + } + return "Unknown Resolution"; + } + + MPages parseAnimeList(String res) { + List animeList = []; + final urls = xpath(res, '//div[@class="relat"]/article/div/div/a/@href'); + final names = xpath(res, '//div[@class="relat"]/article/div/div/a/@title'); + final images = + xpath(res, '//div[@class="relat"]/article/div/div/a/div/img/@src'); + + for (var i = 0; i < names.length; i++) { + MManga anime = MManga(); + anime.name = names[i]; + anime.imageUrl = images[i]; + anime.link = urls[i]; + animeList.add(anime); + } + final pages = xpath(res, '//div[@class="pagination"]/a/@href'); + final pageNumberCurrent = xpath(res, + '//div[@class="pagination"]/span[@class="page-numbers current"]/text()'); + + bool hasNextPage = true; + if (pageNumberCurrent.isNotEmpty && pages.isNotEmpty) { + hasNextPage = !(pages.length == int.parse(pageNumberCurrent.first)); + } + return MPages(animeList, hasNextPage); + } +} + +OploVerz main() { + return OploVerz(); +} diff --git a/anime/src/id/oploverz/source.dart b/anime/src/id/oploverz/source.dart new file mode 100644 index 00000000..8a43fbd3 --- /dev/null +++ b/anime/src/id/oploverz/source.dart @@ -0,0 +1,17 @@ +import '../../../../model/source.dart'; +import '../../../../utils/utils.dart'; + +Source get oploverz => _oploverz; +const oploverzVersion = "0.0.1"; +const oploverzCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/src/id/oploverz/oploverz-v$oploverzVersion.dart"; +Source _oploverz = Source( + name: "Oploverz", + baseUrl: "https://oploverz.red", + lang: "id", + typeSource: "single", + iconUrl: getIconUrl("oploverz", "id"), + sourceCodeUrl: oploverzCodeUrl, + version: oploverzVersion, + appMinVerReq: "0.0.7", + isManga: false); diff --git a/anime/src/id/otakudesu/otakudesu-v0.0.1.dart b/anime/src/id/otakudesu/otakudesu-v0.0.1.dart new file mode 100644 index 00000000..fdbed306 --- /dev/null +++ b/anime/src/id/otakudesu/otakudesu-v0.0.1.dart @@ -0,0 +1,184 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class OtakuDesu extends MProvider { + OtakuDesu(); + + @override + Future getPopular(MSource source, int page) async { + final data = {"url": "${source.baseUrl}/complete-anime/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}/ongoing-anime/page/$page"}; + final res = await http('GET', json.encode(data)); + return parseAnimeList(res); + } + + @override + Future search(MSource source, String query, int page) async { + final data = {"url": "${source.baseUrl}/?s=$query&post_type=anime"}; + final res = await http('GET', json.encode(data)); + List animeList = []; + final images = xpath(res, '//ul[@class="chivsrc"]/li/img/@src'); + final names = xpath(res, '//ul[@class="chivsrc"]/li/h2/a/text()'); + final urls = xpath(res, '//ul[@class="chivsrc"]/li/h2/a/@href'); + + for (var i = 0; i < names.length; i++) { + MManga anime = MManga(); + anime.name = names[i]; + anime.imageUrl = images[i]; + anime.link = urls[i]; + animeList.add(anime); + } + return MPages(animeList, false); + } + + @override + Future getDetail(MSource source, String url) async { + final statusList = [ + {"Ongoing": 0, "Completed": 1} + ]; + final data = {"url": url}; + final res = await http('GET', json.encode(data)); + MManga anime = MManga(); + final status = xpath( + res, '//*[@class="infozingle"]/p[contains(text(), "Status")]/text()'); + if (status.isNotEmpty) { + anime.status = parseStatus(status.first.split(':').last, statusList); + } + final description = xpath(res, '//*[@class="sinopc"]/text()'); + if (description.isNotEmpty) { + anime.description = description.first; + } + + final genre = xpath( + res, '//*[@class="infozingle"]/p[contains(text(), "Genre")]/text()'); + if (genre.isNotEmpty) { + anime.genre = genre.first.split(':').last.split(','); + } + + final epUrls = xpath(res, '//div[@class="episodelist"]/ul/li/span/a/@href'); + final names = xpath(res, '//div[@class="episodelist"]/ul/li/span/a/text()'); + + final dates = xpath( + res, '//div[@class="episodelist"]/ul/li/span[@class="zeebr"]/text()'); + final dateUploads = parseDates(dates, "d MMMM,yyyy", "id"); + List? episodesList = []; + for (var i = 1; i < epUrls.length; i++) { + MChapter episode = MChapter(); + episode.name = names[i]; + episode.dateUpload = dateUploads[i]; + episode.url = epUrls[i]; + episodesList.add(episode); + } + anime.chapters = episodesList; + return anime; + } + + @override + Future> getVideoList(MSource source, String url) async { + List videos = []; + final res = await http('GET', json.encode({"url": url})); + final script = + xpath(res, '//script[contains(text(), "{action:")]/text()').first; + final nonceAction = + substringBefore(substringAfter(script, "{action:\""), '"'); + final action = substringBefore(substringAfter(script, "action:\""), '"'); + + final resNonceAction = await http( + 'POST', + json.encode({ + "useFormBuilder": true, + "body": {"action": nonceAction}, + "url": "${source.baseUrl}/wp-admin/admin-ajax.php" + })); + final nonce = substringBefore(substringAfter(resNonceAction, ":\""), '"'); + final mirrorstream = + xpath(res, '//*[@class="mirrorstream"]/ul/li/a/@data-content'); + for (var stream in mirrorstream) { + List a = []; + final decodedData = json.decode(base64(stream, 0)); + final q = decodedData["q"]; + final id = decodedData["id"]; + final i = decodedData["i"]; + final body = {"i": i, "id": id, "q": q, "nonce": nonce, "action": action}; + + final res = await http( + 'POST', + json.encode({ + "useFormBuilder": true, + "body": body, + "url": "${source.baseUrl}/wp-admin/admin-ajax.php" + })); + final html = base64(substringBefore(substringAfter(res, ":\""), '"'), 0); + final url = xpath(html, '//iframe/@src').first; + + if (url.contains("yourupload")) { + final id = substringBefore(substringAfter(url, "id="), "&"); + url = "https://yourupload.com/embed/$id"; + a = await yourUploadExtractor(url, null, "YourUpload - $q", null); + } else if (url.contains("filelions")) { + a = await streamWishExtractor(url, "FileLions"); + } else if (url.contains("desustream")) { + final res = await http('GET', json.encode({"url": url})); + final script = + xpath(res, '//script[contains(text(), "sources")]/text()').first; + final videoUrl = substringBefore( + substringAfter(substringAfter(script, "sources:[{"), "file':'"), + "'"); + MVideo video = MVideo(); + video + ..url = videoUrl + ..originalUrl = videoUrl + ..quality = "DesuStream - $q" + ..subtitles = []; + videos.add(video); + } else if (url.contains("mp4upload")) { + final res = await http('GET', json.encode({"url": url})); + final script = + xpath(res, '//script[contains(text(), "player.src")]/text()').first; + final videoUrl = + substringBefore(substringAfter(script, "src: \""), '"'); + MVideo video = MVideo(); + video + ..url = videoUrl + ..originalUrl = videoUrl + ..quality = "Mp4upload - $q" + ..subtitles = []; + videos.add(video); + } + videos.addAll(a); + } + + return videos; + } + + MPages parseAnimeList(String res) { + List animeList = []; + final urls = + xpath(res, '//div[@class="detpost"]/div[@class="thumb"]/a/@href'); + final names = xpath(res, + '//div[@class="detpost"]/div[@class="thumb"]/a/div[@class="thumbz"]/h2/text()'); + final images = xpath(res, + '//div[@class="detpost"]/div[@class="thumb"]/a/div[@class="thumbz"]/img/@src'); + + for (var i = 0; i < names.length; i++) { + MManga anime = MManga(); + anime.name = names[i]; + anime.imageUrl = images[i]; + anime.link = urls[i]; + animeList.add(anime); + } + final pages = xpath( + res, '//div[@class="pagenavix"]/a[@class="next page-numbers"]/@href'); + return MPages(animeList, pages.isNotEmpty); + } +} + +OtakuDesu main() { + return OtakuDesu(); +} diff --git a/anime/src/id/otakudesu/source.dart b/anime/src/id/otakudesu/source.dart new file mode 100644 index 00000000..eb05a87a --- /dev/null +++ b/anime/src/id/otakudesu/source.dart @@ -0,0 +1,17 @@ +import '../../../../model/source.dart'; +import '../../../../utils/utils.dart'; + +Source get otakudesu => _otakudesu; +const otakudesuVersion = "0.0.1"; +const otakudesuCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/src/id/otakudesu/otakudesu-v$otakudesuVersion.dart"; +Source _otakudesu = Source( + name: "OtakuDesu", + baseUrl: "https://otakudesu.cam", + lang: "id", + typeSource: "single", + iconUrl: getIconUrl("otakudesu", "id"), + sourceCodeUrl: otakudesuCodeUrl, + version: otakudesuVersion, + appMinVerReq: "0.0.7", + isManga: false); diff --git a/icons/mangayomi-id-nimegami.png b/icons/mangayomi-id-nimegami.png new file mode 100644 index 00000000..4a870925 Binary files /dev/null and b/icons/mangayomi-id-nimegami.png differ diff --git a/icons/mangayomi-id.oploverz.png b/icons/mangayomi-id.oploverz.png new file mode 100644 index 00000000..f9bd63f6 Binary files /dev/null and b/icons/mangayomi-id.oploverz.png differ diff --git a/icons/mangayomi-id.otakudesu.png b/icons/mangayomi-id.otakudesu.png new file mode 100644 index 00000000..abca3f89 Binary files /dev/null and b/icons/mangayomi-id.otakudesu.png differ