From 27093c0204ad4c9061f4c69868a17d2053d09756 Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 25 Mar 2023 19:30:04 -0400 Subject: [PATCH] added hanime support ;) --- lib/anime/anime.dart | 1 + lib/anime/anime_videos.dart | 180 ++++++++++++++++------------- lib/info_page.dart | 127 ++++++++++---------- lib/providers/anime_providers.dart | 99 +++++++++++++--- pubspec.lock | 26 ++--- pubspec.yaml | 5 +- 6 files changed, 253 insertions(+), 185 deletions(-) diff --git a/lib/anime/anime.dart b/lib/anime/anime.dart index 4bacd61..28adde2 100644 --- a/lib/anime/anime.dart +++ b/lib/anime/anime.dart @@ -151,6 +151,7 @@ class AniPageState extends State with AutomaticKeepAliveClientMixin { selectedGenres = []; search = textController.text; } else { + selectedGenres = []; search = null; } await queryData(); diff --git a/lib/anime/anime_videos.dart b/lib/anime/anime_videos.dart index 02841e1..1e6f7de 100644 --- a/lib/anime/anime_videos.dart +++ b/lib/anime/anime_videos.dart @@ -27,15 +27,22 @@ class AniViewerState extends State { BetterPlayerController? phonePlayer; int currentEpisode = 1; List subtitles = []; - late Future getMediaInfo = mediaInfo( - widget.episode['id'], - ); + Map getMedia = {}; bool isPhone = Platform.isAndroid || Platform.isIOS; @override void initState() { super.initState(); + Future.microtask( + () async { + getMedia = await mediaInfo( + widget.episode['id'], + ) ?? + widget.episode; + setState(() {}); + }, + ); } Future desktopPlayer(String url) async { @@ -45,15 +52,17 @@ class AniViewerState extends State { controller = await VideoController.create( player!.handle, ); - Directory dir = await getTemporaryDirectory(); - String path = ""; - for (final i in subtitles) { - await Dio().download(i['url'], "${dir.path}/${i['lang']}subs.vtt"); - if (i['lang'] == "English") { - path = "${dir.path}/${i['lang']}subs.vtt"; + if (getMedia['subtitles'] != null) { + Directory dir = await getTemporaryDirectory(); + String path = ""; + for (final i in getMedia['subtitles']) { + await Dio().download(i['url'], "${dir.path}/${i['lang']}subs.vtt"); + if (i['lang'] == "English") { + path = "${dir.path}/${i['lang']}subs.vtt"; + } } + await (player!.platform as libmpvPlayer).setProperty("sub-files", path); } - await (player!.platform as libmpvPlayer).setProperty("sub-files", path); await player!.open( Playlist( [ @@ -67,81 +76,88 @@ class AniViewerState extends State { @override Widget build(context) { - return FutureBuilder( - future: getMediaInfo, - builder: (context, snap) { - if (snap.hasData && snap.connectionState == ConnectionState.done) { - subtitles = snap.data!['subtitles']!; - if (isPhone) { - BetterPlayerDataSource source = BetterPlayerDataSource( - BetterPlayerDataSourceType.network, - snap.data!['sources']!.first['url'], - headers: {"User-Agent": "Death"}, - videoFormat: BetterPlayerVideoFormat.hls, - subtitles: List.generate( - subtitles.length - 1, - (index) { - return BetterPlayerSubtitlesSource( - selectedByDefault: - (subtitles[index]['lang'] == "English") ? true : false, - type: BetterPlayerSubtitlesSourceType.network, - name: subtitles[index]['lang'], - urls: [ - subtitles[index]['url'], - ], - ); - }, - ), - ); - phonePlayer = BetterPlayerController( - const BetterPlayerConfiguration( - useRootNavigator: true, - controlsConfiguration: BetterPlayerControlsConfiguration( - playerTheme: BetterPlayerTheme.material, - ), - deviceOrientationsOnFullScreen: [ - DeviceOrientation.landscapeLeft, - DeviceOrientation.landscapeRight, + if (getMedia.isNotEmpty) { + if (isPhone) { + BetterPlayerDataSource source = BetterPlayerDataSource( + BetterPlayerDataSourceType.network, + getMedia['sources']!.first['url'], + headers: {"User-Agent": "Death"}, + videoFormat: BetterPlayerVideoFormat.hls, + subtitles: (subtitles.isNotEmpty) + ? List.generate( + subtitles.length - 1, + (index) { + return BetterPlayerSubtitlesSource( + selectedByDefault: (subtitles[index]['lang'] == "English") + ? true + : false, + type: BetterPlayerSubtitlesSourceType.network, + name: subtitles[index]['lang'], + urls: [ + subtitles[index]['url'], + ], + ); + }, + ) + : null, + ); + phonePlayer = BetterPlayerController( + const BetterPlayerConfiguration( + useRootNavigator: true, + controlsConfiguration: BetterPlayerControlsConfiguration( + playerTheme: BetterPlayerTheme.material, + ), + deviceOrientationsOnFullScreen: [ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ], + fullScreenByDefault: true, + autoPlay: true, + aspectRatio: 16 / 9, + allowedScreenSleep: false, + fit: BoxFit.contain, + ), + betterPlayerDataSource: source, + ); + return BetterPlayer( + controller: phonePlayer!, + ); + } else { + return FutureBuilder( + future: desktopPlayer( + getMedia['sources'].last['url'], + ), + builder: (context, innerSnap) { + if (innerSnap.connectionState == ConnectionState.done) { + return Stack( + children: [ + Video(controller: controller), + VideoControls( + player: player!, + ), ], - fullScreenByDefault: true, - autoPlay: true, - aspectRatio: 16 / 9, - allowedScreenSleep: false, - fit: BoxFit.contain, - ), - betterPlayerDataSource: source, - ); - return BetterPlayer( - controller: phonePlayer!, - ); - } else { - return FutureBuilder( - future: desktopPlayer( - snap.data!['sources']!.last['url'], - ), - builder: (context, innerSnap) { - if (innerSnap.connectionState == ConnectionState.done) { - return Stack( - children: [ - Video(controller: controller), - VideoControls( - player: player!, - ), - ], - ); - } - return const Center( - child: CircularProgressIndicator(), - ); - }, + ); + } + return const Center( + child: CircularProgressIndicator(), ); - } - } - return const Center( - child: CircularProgressIndicator(), + }, ); - }, - ); + } + } else { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton( + onPressed: () => Navigator.maybePop(context), + child: const Text("Escape?"), + ), + const Center( + child: CircularProgressIndicator(), + ), + ], + ); + } } @override diff --git a/lib/info_page.dart b/lib/info_page.dart index 74c9b73..7eb03f6 100644 --- a/lib/info_page.dart +++ b/lib/info_page.dart @@ -24,7 +24,9 @@ class InfoPageState extends State { Future.microtask( () async { content = (widget.data.type == "anime") - ? await mediaList(widget.data.mediaId) + ? await mediaList(widget.data.mediaId) ?? + await haniList(widget.data.title) ?? + [] : await dexReader(widget.data.mediaId); setState(() {}); }, @@ -136,75 +138,70 @@ class InfoPageState extends State { child: expands, ), ), - (content.isNotEmpty) - ? SliverPadding( - padding: const EdgeInsets.all(15), - sliver: SliverGrid( - gridDelegate: - const SliverGridDelegateWithMaxCrossAxisExtent( - mainAxisSpacing: 5, - crossAxisSpacing: 6, - maxCrossAxisExtent: 400, - mainAxisExtent: 100, - //childAspectRatio: 4 / 1, - ), - delegate: SliverChildBuilderDelegate( - childCount: content.length, - (context, index) { - return GestureDetector( - onTap: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return Scaffold( - body: (widget.data.type == "anime") - ? AniViewer( - episodes: content, - episode: content[index], - ) - : MangaReader( - chapter: content[index], - chapters: content, - ), - ); - }, - ), - ), - child: IntrinsicHeight( - child: Card( - elevation: 3, - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: Text( - content[index]['title'], - overflow: TextOverflow.visible, - ), - ), - Flexible( - child: Text( - content[index]['number'], - ), + if (content.isNotEmpty) + SliverPadding( + padding: const EdgeInsets.all(15), + sliver: SliverGrid( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: 5, + crossAxisSpacing: 6, + maxCrossAxisExtent: 400, + mainAxisExtent: 100, + //childAspectRatio: 4 / 1, + ), + delegate: SliverChildBuilderDelegate( + childCount: content.length, + (context, index) { + return GestureDetector( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return Scaffold( + body: (widget.data.type == "anime") + ? AniViewer( + episodes: content, + episode: content[index], + ) + : MangaReader( + chapter: content[index], + chapters: content, ), - ], + ); + }, + ), + ), + child: IntrinsicHeight( + child: Card( + elevation: 3, + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: Text( + content[index]['title'], + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), - ), + Flexible( + child: Text( + content[index]['number'], + ), + ), + ], ), ), - ); - }, - ), - ), - ) - : const SliverToBoxAdapter( - child: Center( - child: CircularProgressIndicator(), - ), + ), + ), + ); + }, ), + ), + ) ], ), ), diff --git a/lib/providers/anime_providers.dart b/lib/providers/anime_providers.dart index 6541687..c93f7b7 100644 --- a/lib/providers/anime_providers.dart +++ b/lib/providers/anime_providers.dart @@ -1,25 +1,88 @@ +import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:string_similarity/string_similarity.dart'; //Consumet -Future mediaList(id) async { - String link = "https://api.consumet.org/meta/anilist/info/$id?provider=zoro"; - var json = await Dio().get(link); - return List.generate( - json.data['episodes'].length, - (index) { - return { - "id": json.data['episodes'][index]['id'], - "title": json.data['episodes'][index]['title'], - "number": "Episode: ${json.data['episodes'][index]['number']}", - "description": json.data['episodes'][index]['description'] - }; - }, +Future mediaList(id) async { + Response json = await Dio().get( + "https://api.consumet.org/meta/anilist/info/$id?provider=zoro", ); + return (json.data['episodes'].isEmpty) + ? null + : List.generate( + json.data['episodes'].length, + (index) { + return { + "id": json.data['episodes'][index]['id'], + "title": json.data['episodes'][index]['title'], + "number": "Episode: ${json.data['episodes'][index]['number']}", + "description": json.data['episodes'][index]['description'] + }; + }, + ); } -Future mediaInfo(id) async { - String link = "https://api.consumet.org/meta/anilist/watch/$id?provider=zoro"; - var json = await Dio().get(link); - return json.data; +Future mediaInfo(id) async { + try { + Response json = await Dio().get( + "https://api.consumet.org/meta/anilist/watch/$id?provider=zoro", + ); + return json.data; + } catch (e) { + return null; + } +} +// End Consumet + +//Begin Hanime +Future haniList(String name) async { + //name.split(pattern).getRange(0, 3); + Response json = await Dio().post( + "https://search.htv-services.com/", + data: jsonEncode( + { + "search_text": (name.split(" ").length > 3) + ? name.split(" ").getRange(0, 3).join(" ").replaceAll(":", "") + : name, + // "${name.split(" ")[0]} ${name.split(" ")[1]} ${name.split(" ")[2]}" + // .replaceAll(":", ""), + "tags": [], + "tags-mode": "AND", + "brands": [], + "blacklist": [], + "order_by": "", + "ordering": "", + "page": 0 + }, + ), + ); + if (json.data['nbHits'] > 0) { + final List results = jsonDecode(json.data['hits']); + + List videos = []; + + for (var i in results) { + Response v = await Dio().get( + "https://hanime.tv/api/v8/video?id=${i['id']}", + ); + videos.add(v.data); + } + + return List.generate( + videos.length, + (index) { + return { + "title": (videos[index])['hentai_video']['name'], + "number": "Episode: ${index + 1}", + "sources": [ + { + "url": videos[index]['videos_manifest']['servers'][0]['streams'] + [1]['url'] + }, + ], + }; + }, + ); + } + return null; } -// End Consumet \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 91f531f..477dd8d 100755 --- a/pubspec.lock +++ b/pubspec.lock @@ -311,14 +311,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0-alpha.6" - flutter_layout_grid: - dependency: "direct main" - description: - name: flutter_layout_grid - sha256: "1df27a2e9cd34faa0c0a33148c8bb9d9259e87cc5b934c989a59a77e1a4c0d6b" - url: "https://pub.dev" - source: hosted - version: "2.0.1" flutter_lints: dependency: "direct dev" description: @@ -841,14 +833,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" - quiver: - dependency: transitive - description: - name: quiver - sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 - url: "https://pub.dev" - source: hosted - version: "3.2.1" rxdart: dependency: transitive description: @@ -958,6 +942,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + string_similarity: + dependency: "direct main" + description: + name: string_similarity + sha256: "5219d2ca048f6d3b8954747644b99b103244b5f84ef62b2577d29eb6bdeb61bb" + url: "https://pub.dev" + source: hosted + version: "2.0.0" synchronized: dependency: transitive description: @@ -1143,5 +1135,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=3.0.0-134.0.dev <4.0.0" + dart: ">=3.0.0-290.1.beta <4.0.0" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 50fa9ed..c8b4abe 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,8 +8,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 0.0.1+1 environment: - sdk: ">=2.18.0 <3.0.0" - #sdk: ">=3.0.0-122.0.dev" + sdk: ">=3.0.0-290.1.beta" dependencies: @@ -60,8 +59,8 @@ dependencies: isar: ^3.0.5 isar_flutter_libs: ^3.0.5 expandable_text: - flutter_layout_grid: material_design_icons_flutter: + string_similarity: dependency_overrides: web_socket_channel: 2.2.0