diff --git a/dart/anime/anime_source_list.dart b/dart/anime/anime_source_list.dart index 302c95fc..7ab0bc58 100644 --- a/dart/anime/anime_source_list.dart +++ b/dart/anime/anime_source_list.dart @@ -26,6 +26,7 @@ import 'src/it/animesaturn/source.dart'; import 'src/pt/animesvision/source.dart'; import 'src/sq/filma24/source.dart'; import 'src/tr/diziwatch/source.dart'; +import 'src/en/donghuastream/source.dart'; List dartAnimesourceList = [ gogoanimeSource, @@ -55,4 +56,5 @@ List dartAnimesourceList = [ animeonlineninjaSource, kisskhSource, vumetoSource, + donghuastreamSource, ]; diff --git a/dart/anime/src/en/donghuastream/donghuastream.dart b/dart/anime/src/en/donghuastream/donghuastream.dart new file mode 100644 index 00000000..730fde43 --- /dev/null +++ b/dart/anime/src/en/donghuastream/donghuastream.dart @@ -0,0 +1,173 @@ +import 'package:mangayomi/bridge_lib.dart'; +import 'dart:convert'; + +class DonghuaStream extends MProvider { + DonghuaStream({required this.source}); + + MSource source; + + final Client client = Client(source); + + @override + bool get supportsLatest => true; + + @override + Map get headers => {}; + + @override + Future getPopular(int page) async { + final res = (await client.get(Uri.parse("${source.baseUrl}/anime?page=${page}&sub=&order=popular"))).body; + List animeList = []; + final urls = xpath(res, '//article[@class="bs"]/div/a/@href'); + final names = xpath(res, '//article[@class="bs"]/div/a/@title'); + final images = xpath(res, '//article[@class="bs"]/div/a/div/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 nextPage = xpath(res, '//a[@class="r"]/@href'); + return MPages(animeList, nextPage.isNotEmpty); + } + + @override + Future getLatestUpdates(int page) async { + final res = (await client.get(Uri.parse("${source.baseUrl}/anime?page=${page}&sub=&order=update"))).body; + List animeList = []; + final urls = xpath(res, '//article[@class="bs"]/div/a/@href'); + final names = xpath(res, '//article[@class="bs"]/div/a/@title'); + final images = xpath(res, '//article[@class="bs"]/div/a/div/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 nextPage = xpath(res, '//a[@class="r"]/@href'); + return MPages(animeList, nextPage.isNotEmpty); + } + + @override + Future search(String query, int page, FilterList filterList) async { + final res = (await client.get(Uri.parse("${source.baseUrl}/page/${page}/?s=${query}"))).body; + List animeList = []; + final urls = xpath(res, '//article[@class="bs"]/div/a/@href'); + final names = xpath(res, '//article[@class="bs"]/div/a/@title'); + final images = xpath(res, '//article[@class="bs"]/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 nextPage = xpath(res, '//a[@class="next page-numbers"]/@href'); + return MPages(animeList, nextPage.isNotEmpty); + } + + @override + Future getDetail(String url) async { + final res = (await client.get(Uri.parse(url))).body; + MManga anime = MManga(); + var genre = xpath(res,'//div[@class="genxed"]/a/text()'); + genre.remove('MY FAVOURITE'); + anime.genre = genre; + anime.description = xpath(res,'//div[@class="entry-content"]/p/text()').join("\n"); + + final statusList = [{"Status: Ongoing": 0, "Status: Completed": 1}]; + final infoContent = xpath(res,'//div[@class="info-content"]/div[@class="spe"]/span/text()'); + anime.status = parseStatus(infoContent[0], statusList); + anime.author = infoContent[1].replaceFirst('Network: ','').replaceFirst('Donghua Stream, ',''); + anime.artist = infoContent[2].replaceFirst('Studio: ',''); + final epElements = parseHtml(res).select('div.eplister > ul > li >a'); + List? episodesList = []; + + for (var epElement in epElements) { + final number = epElement.selectFirst("div.epl-num").text; + final title = epElement.selectFirst("div.epl-title").text; + final dateString = epElement.selectFirst("div.epl-date").text; + MChapter episode = MChapter(); + episode.name = "Episode $number"; + episode.url = epElement.getHref; + episode.dateUpload = parseDates([dateString],"MMMM d, yyyy","en",)[0]; + episodesList.add(episode); + } + anime.chapters = episodesList; + return anime; + } + + + // For anime episode video list + @override + Future> getVideoList(String url) async { + final res = (await client.get(Uri.parse(url))).body; + final servers = parseHtml(res).select('select.mirror > option[data-index]'); + List videos = []; + for (var i = 0; i < servers.length; i++) { + String name = '${servers[i].attr("data-index")}: ${servers[i].text}'; + String valueHtml = utf8.decode(base64Url.decode(servers[i].attr('value'))); + String serverUrl = xpath(valueHtml,'//iframe/@src')[0]; + if(serverUrl.startsWith('https://geo.dailymotion.com/player')){ + String videoId = RegExp(r'[?&]video=([a-zA-Z0-9]+)').firstMatch(serverUrl).group(1)!; + return dailymotionUrlFetcher(videoId,name); + } + } + return videos; + } + + Future> dailymotionUrlFetcher(String videoID, String name) async { + String metaDataUrl = 'https://www.dailymotion.com/player/metadata/video/$videoID'; + final res = (await client.get(Uri.parse(metaDataUrl))).body; + final jsonRes = json.decode(res); + String masterUrl = jsonRes["qualities"]["auto"][0]["url"]; + return m3u8extractor(masterUrl, name); + } + + Future> m3u8extractor(String masterUrl, String name) async { + List videos = []; + List subtitles = []; + final masterPlaylistRes = + (await client.get(Uri.parse(masterUrl), headers: headers)).body; + + // Parse Subtitles + final subtitleRegExp = RegExp(r'#EXT-X-MEDIA:TYPE=SUBTITLES.*?NAME="(.*?)".*?URI="(.*?)"', dotAll: true); + for (final match in subtitleRegExp.allMatches(masterPlaylistRes)) { + MTrack subtitle = MTrack(); + subtitle.label = match.group(1) ?? 'Subtitle'; + subtitle.file = match.group(2) ?? ''; + subtitles.add(subtitle); + } + + 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 + ..headers = headers; + videos.add(video); + } + return videos; + } +} + +DonghuaStream main(MSource source) { + return DonghuaStream(source:source); +} \ No newline at end of file diff --git a/dart/anime/src/en/donghuastream/icon.png b/dart/anime/src/en/donghuastream/icon.png new file mode 100644 index 00000000..7340cd62 Binary files /dev/null and b/dart/anime/src/en/donghuastream/icon.png differ diff --git a/dart/anime/src/en/donghuastream/source.dart b/dart/anime/src/en/donghuastream/source.dart new file mode 100644 index 00000000..7a543447 --- /dev/null +++ b/dart/anime/src/en/donghuastream/source.dart @@ -0,0 +1,17 @@ +import '../../../../../model/source.dart'; + +Source get donghuastreamSource => _donghuastreamSource; +const _donghuastreamVersion = "0.0.1"; +const _donghuastreamSourceCodeUrl = + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/donghuastream/donghuastream.dart"; +Source _donghuastreamSource = Source( + name: "DonghuaStream", + baseUrl: "https://donghuastream.org", + lang: "en", + typeSource: "single", + iconUrl: + "https://raw.githubusercontent.com/kodjodevf/mangayomi-extensions/$branchName/dart/anime/src/en/donghuastream/icon.png", + sourceCodeUrl: _donghuastreamSourceCodeUrl, + version: _donghuastreamVersion, + itemType: ItemType.anime, +);