diff --git a/anime/source_generator.dart b/anime/source_generator.dart index 8d3537e5..e1e64861 100644 --- a/anime/source_generator.dart +++ b/anime/source_generator.dart @@ -4,6 +4,7 @@ import 'dart:io'; import '../model/source.dart'; import 'multisrc/zorotheme/sources.dart'; import 'src/ar/okanime/source.dart'; +import 'src/en/aniwave/source.dart'; import 'src/en/gogoanime/source.dart'; import 'src/en/kisskh/source.dart'; import 'src/fr/animesultra/source.dart'; @@ -24,7 +25,8 @@ void main() { okanimeSource, otakudesu, nimegami, - oploverz + oploverz, + aniwave ]; final List> jsonList = _sourcesList.map((source) => source.toJson()).toList(); diff --git a/anime/src/en/aniwave/aniwave-v0.0.1.dart b/anime/src/en/aniwave/aniwave-v0.0.1.dart new file mode 100644 index 00000000..55029295 --- /dev/null +++ b/anime/src/en/aniwave/aniwave-v0.0.1.dart @@ -0,0 +1,242 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class Aniwave extends MProvider { + Aniwave(); + + @override + Future getPopular(MSource source, int page) async { + final data = {"url": "${source.baseUrl}/filter?sort=trending&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}/filter?sort=recently_updated&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}/filter?keyword=$query"}; + final res = await http('GET', json.encode(data)); + return parseAnimeList(res); + } + + @override + Future getDetail(MSource source, String url) async { + final statusList = [ + {"Releasing": 0, "Completed": 1} + ]; + final data = {"url": "${source.baseUrl}${url}"}; + final res = await http('GET', json.encode(data)); + 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 = querySelectorAll(res, + selector: "div[data-id]", + typeElement: 3, + attributes: "data-id", + typeRegExp: 0) + .first; + final encrypt = vrfEncrypt(id); + final vrf = "vrf=${Uri.encodeComponent(encrypt)}"; + final dataEp = {"url": "${source.baseUrl}/ajax/episode/list/$id?$vrf"}; + final resEp = await http('GET', json.encode(dataEp)); + final html = json.decode(resEp)["result"]; + List? episodesList = []; + final epsHtml = querySelectorAll(html, + selector: "div.episodes ul > li", + typeElement: 2, + attributes: "", + typeRegExp: 0); + for (var epHtml in epsHtml) { + 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> getVideoList(MSource source, String url) async { + final ids = substringBefore(url, "&"); + final encrypt = vrfEncrypt(ids); + final vrf = "vrf=${Uri.encodeComponent(encrypt)}"; + final res = await http('GET', + json.encode({"url": "${source.baseUrl}/ajax/server/list/$ids?$vrf"})); + final html = json.decode(res)["result"]; + final vidsHtml = querySelectorAll(html, + selector: "div.servers > div", + typeElement: 2, + attributes: "", + typeRegExp: 0); + List videos = []; + for (var vidHtml in vidsHtml) { + 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 http( + 'GET', + json.encode( + {"url": "${source.baseUrl}/ajax/server/$serverId?$vrf"})); + final status = json.decode(res)["status"]; + if (status == 200) { + List a = []; + final url = vrfDecrypt(json.decode(res)["result"]["url"]); + if (url.contains("mp4upload")) { + a = await mp4UploadExtractor(url, null, "", type); + } else if (url.contains("streamtape")) { + a = await streamTapeExtractor(url, "StreamTape - $type"); + } else if (url.contains("filemoon")) { + a = await filemoonExtractor(url, "", type); + } + videos.addAll(a); + } + } + } + + return videos; + } + + MPages parseAnimeList(String res) { + List 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 rc4Encrypt(String key, List message) { + List _key = utf8.encode(key); + int _i = 0, _j = 0; + List _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 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 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 vrfShift(List 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 rot13(List 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; + } +} + +Map getMirrorPref() { + return { + "aniwave.to": "https://aniwave.to", + "aniwave.bz": "https://aniwave.bz", + "aniwave.ws": "https://aniwave.ws", + }; +} + +Aniwave main() { + return Aniwave(); +} diff --git a/anime/src/en/aniwave/source.dart b/anime/src/en/aniwave/source.dart new file mode 100644 index 00000000..7ae0c8e6 --- /dev/null +++ b/anime/src/en/aniwave/source.dart @@ -0,0 +1,17 @@ +import '../../../../model/source.dart'; +import '../../../../utils/utils.dart'; + +Source get aniwave => _aniwave; +const aniwaveVersion = "0.0.1"; +const aniwaveCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/src/en/aniwave/aniwave-v$aniwaveVersion.dart"; +Source _aniwave = Source( + name: "Aniwave", + baseUrl: "https://aniwave.to", + lang: "en", + typeSource: "single", + iconUrl: getIconUrl("aniwave", "en"), + sourceCodeUrl: aniwaveCodeUrl, + version: aniwaveVersion, + appMinVerReq: "0.0.8", + isManga: false); diff --git a/anime/src/id/nimegami/nimegami-v0.0.1.dart b/anime/src/id/nimegami/nimegami-v0.0.1.dart index bf778444..ebf36fbf 100644 --- a/anime/src/id/nimegami/nimegami-v0.0.1.dart +++ b/anime/src/id/nimegami/nimegami-v0.0.1.dart @@ -96,7 +96,7 @@ class NimeGami extends MProvider { episode.name = names[i]; episode.url = json.encode({ "episodeIndex": int.parse(substringAfterLast(epNums[i], '_')), - 'urls': json.decode(base64(epUrls[i], 0)) + 'urls': json.decode(utf8.decode(base64Url.decode(epUrls[i]))) }); episodesList.add(episode); } @@ -124,8 +124,8 @@ class NimeGami extends MProvider { List videos = []; List a = []; if (url.contains("video.nimegami.id")) { - final realUrl = - base64(substringBefore(substringAfter(url, "url="), "&"), 0); + final realUrl = utf8.decode( + base64Url.decode(substringBefore(substringAfter(url, "url="), "&"))); final a = await extractHXFileVideos(realUrl, quality); videos.addAll(a); } else if (url.contains("berkasdrive") || url.contains("drive.nimegami")) { diff --git a/anime/src/id/nimegami/source.dart b/anime/src/id/nimegami/source.dart index 24e0d64e..02224d21 100644 --- a/anime/src/id/nimegami/source.dart +++ b/anime/src/id/nimegami/source.dart @@ -2,7 +2,7 @@ import '../../../../model/source.dart'; import '../../../../utils/utils.dart'; Source get nimegami => _nimegami; -const nimegamiVersion = "0.0.1"; +const nimegamiVersion = "0.0.2"; const nimegamiCodeUrl = "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/src/id/nimegami/nimegami-v$nimegamiVersion.dart"; Source _nimegami = Source( @@ -13,5 +13,5 @@ Source _nimegami = Source( iconUrl: getIconUrl("nimegami", "id"), sourceCodeUrl: nimegamiCodeUrl, version: nimegamiVersion, - appMinVerReq: "0.0.7", + appMinVerReq: "0.0.8", isManga: false); diff --git a/anime/src/id/otakudesu/otakudesu-v0.0.1.dart b/anime/src/id/otakudesu/otakudesu-v0.0.1.dart index fdbed306..3e811601 100644 --- a/anime/src/id/otakudesu/otakudesu-v0.0.1.dart +++ b/anime/src/id/otakudesu/otakudesu-v0.0.1.dart @@ -101,7 +101,7 @@ class OtakuDesu extends MProvider { xpath(res, '//*[@class="mirrorstream"]/ul/li/a/@data-content'); for (var stream in mirrorstream) { List a = []; - final decodedData = json.decode(base64(stream, 0)); + final decodedData = json.decode(utf8.decode(base64Url.decode(stream))); final q = decodedData["q"]; final id = decodedData["id"]; final i = decodedData["i"]; @@ -114,7 +114,8 @@ class OtakuDesu extends MProvider { "body": body, "url": "${source.baseUrl}/wp-admin/admin-ajax.php" })); - final html = base64(substringBefore(substringAfter(res, ":\""), '"'), 0); + final html = utf8.decode( + base64Url.decode(substringBefore(substringAfter(res, ":\""), '"'))); final url = xpath(html, '//iframe/@src').first; if (url.contains("yourupload")) { diff --git a/anime/src/id/otakudesu/source.dart b/anime/src/id/otakudesu/source.dart index eb05a87a..7c47291a 100644 --- a/anime/src/id/otakudesu/source.dart +++ b/anime/src/id/otakudesu/source.dart @@ -2,7 +2,7 @@ import '../../../../model/source.dart'; import '../../../../utils/utils.dart'; Source get otakudesu => _otakudesu; -const otakudesuVersion = "0.0.1"; +const otakudesuVersion = "0.0.2"; const otakudesuCodeUrl = "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/main/anime/src/id/otakudesu/otakudesu-v$otakudesuVersion.dart"; Source _otakudesu = Source( @@ -13,5 +13,5 @@ Source _otakudesu = Source( iconUrl: getIconUrl("otakudesu", "id"), sourceCodeUrl: otakudesuCodeUrl, version: otakudesuVersion, - appMinVerReq: "0.0.7", + appMinVerReq: "0.0.8", isManga: false); diff --git a/icons/mangayomi-en-aniwave.png b/icons/mangayomi-en-aniwave.png new file mode 100644 index 00000000..516f974e Binary files /dev/null and b/icons/mangayomi-en-aniwave.png differ