Add more features vor anime player
This commit is contained in:
parent
44c5a54bbf
commit
6fbbbc5bc2
24 changed files with 565 additions and 72 deletions
|
|
@ -74,6 +74,14 @@ class VideoModel {
|
|||
String? quality;
|
||||
String? originalUrl;
|
||||
Map<String, String>? headers;
|
||||
|
||||
VideoModel({this.url, this.quality, this.originalUrl, this.headers});
|
||||
List<TrackModel>? subtitles;
|
||||
VideoModel(
|
||||
{this.url, this.quality, this.originalUrl, this.headers, this.subtitles});
|
||||
}
|
||||
|
||||
class TrackModel {
|
||||
String? file;
|
||||
String? label;
|
||||
|
||||
TrackModel({this.file, this.label});
|
||||
}
|
||||
|
|
|
|||
91
lib/eval/bridge_class/track_model.dart
Normal file
91
lib/eval/bridge_class/track_model.dart
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import 'package:dart_eval/dart_eval.dart';
|
||||
import 'package:dart_eval/dart_eval_bridge.dart';
|
||||
import 'package:dart_eval/stdlib/core.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/model.dart';
|
||||
|
||||
class $TrackModel implements TrackModel, $Instance {
|
||||
$TrackModel.wrap(this.$value) : _superclass = $Object($value);
|
||||
|
||||
static const $type = BridgeTypeRef(
|
||||
BridgeTypeSpec('package:bridge_lib/bridge_lib.dart', 'TrackModel'));
|
||||
|
||||
static const $declaration = BridgeClassDef(BridgeClassType($type),
|
||||
constructors: {
|
||||
'': BridgeConstructorDef(BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation($type),
|
||||
params: [],
|
||||
namedParams: [
|
||||
BridgeParameter(
|
||||
'file',
|
||||
BridgeTypeAnnotation(
|
||||
BridgeTypeRef.type(RuntimeTypes.stringType)),
|
||||
false),
|
||||
BridgeParameter(
|
||||
'label',
|
||||
BridgeTypeAnnotation(
|
||||
BridgeTypeRef.type(RuntimeTypes.stringType)),
|
||||
false),
|
||||
]))
|
||||
},
|
||||
// Specify class fields
|
||||
fields: {
|
||||
'file': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
|
||||
'label': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
|
||||
},
|
||||
wrap: true);
|
||||
|
||||
static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
|
||||
return $TrackModel.wrap(TrackModel());
|
||||
}
|
||||
|
||||
@override
|
||||
final TrackModel $value;
|
||||
|
||||
@override
|
||||
TrackModel get $reified => $value;
|
||||
|
||||
final $Instance _superclass;
|
||||
|
||||
@override
|
||||
$Value? $getProperty(Runtime runtime, String identifier) {
|
||||
switch (identifier) {
|
||||
case 'file':
|
||||
return $String($value.file!);
|
||||
case 'label':
|
||||
return $String($value.label!);
|
||||
|
||||
default:
|
||||
return _superclass.$getProperty(runtime, identifier);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int $getRuntimeType(Runtime runtime) => runtime.lookupType($type.spec!);
|
||||
|
||||
@override
|
||||
void $setProperty(Runtime runtime, String identifier, $Value value) {
|
||||
switch (identifier) {
|
||||
case 'file':
|
||||
$value.file = value.$reified;
|
||||
case 'label':
|
||||
$value.label = value.$reified;
|
||||
|
||||
default:
|
||||
_superclass.$setProperty(runtime, identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String? get file => $value.file;
|
||||
|
||||
@override
|
||||
String? get label => $value.label;
|
||||
|
||||
@override
|
||||
set file(String? file) {}
|
||||
|
||||
@override
|
||||
set label(String? label) {}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ import 'package:dart_eval/dart_eval.dart';
|
|||
import 'package:dart_eval/dart_eval_bridge.dart';
|
||||
import 'package:dart_eval/stdlib/core.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/model.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/track_model.dart';
|
||||
|
||||
class $VideoModel implements VideoModel, $Instance {
|
||||
$VideoModel.wrap(this.$value) : _superclass = $Object($value);
|
||||
|
|
@ -35,6 +36,11 @@ class $VideoModel implements VideoModel, $Instance {
|
|||
BridgeTypeAnnotation(
|
||||
BridgeTypeRef.type(RuntimeTypes.mapType)),
|
||||
false),
|
||||
BridgeParameter(
|
||||
'substitles',
|
||||
BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.list,
|
||||
[BridgeTypeRef.type(RuntimeTypes.dynamicType)])),
|
||||
false),
|
||||
]))
|
||||
},
|
||||
// Specify class fields
|
||||
|
|
@ -47,6 +53,8 @@ class $VideoModel implements VideoModel, $Instance {
|
|||
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType))),
|
||||
'headers': BridgeFieldDef(
|
||||
BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.mapType))),
|
||||
'substitles': BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef(
|
||||
CoreTypes.list, [BridgeTypeRef.type(RuntimeTypes.dynamicType)]))),
|
||||
},
|
||||
wrap: true);
|
||||
|
||||
|
|
@ -75,6 +83,12 @@ class $VideoModel implements VideoModel, $Instance {
|
|||
case 'headers':
|
||||
return $Map.wrap($value.headers!);
|
||||
|
||||
case 'substitles':
|
||||
return $List.wrap($value.subtitles!
|
||||
.map((e) =>
|
||||
$TrackModel.wrap(TrackModel(file: e.file, label: e.label)))
|
||||
.toList());
|
||||
|
||||
default:
|
||||
return _superclass.$getProperty(runtime, identifier);
|
||||
}
|
||||
|
|
@ -94,6 +108,8 @@ class $VideoModel implements VideoModel, $Instance {
|
|||
$value.originalUrl = value.$reified;
|
||||
case 'headers':
|
||||
$value.headers = value.$reified as Map<String, String>;
|
||||
case 'subtitles':
|
||||
$value.subtitles = value.$reified as List<TrackModel>;
|
||||
|
||||
default:
|
||||
_superclass.$setProperty(runtime, identifier, value);
|
||||
|
|
@ -103,6 +119,9 @@ class $VideoModel implements VideoModel, $Instance {
|
|||
@override
|
||||
String? get url => $value.url;
|
||||
|
||||
@override
|
||||
List<TrackModel>? get subtitles => $value.subtitles;
|
||||
|
||||
@override
|
||||
String? get quality => $value.quality;
|
||||
|
||||
|
|
@ -123,4 +142,7 @@ class $VideoModel implements VideoModel, $Instance {
|
|||
|
||||
@override
|
||||
set originalUrl(String? originalUrl) {}
|
||||
|
||||
@override
|
||||
set subtitles(List? subtitles) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:typed_data';
|
||||
import 'package:dart_eval/dart_eval.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/manga_model.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/track_model.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/video_model.dart';
|
||||
import 'package:mangayomi/eval/m_bridge.dart';
|
||||
|
||||
|
|
@ -9,7 +10,8 @@ Uint8List compilerEval(String sourceCode) {
|
|||
compiler.defineBridgeClasses([
|
||||
$MBridge.$declaration,
|
||||
$MangaModel.$declaration,
|
||||
$VideoModel.$declaration
|
||||
$VideoModel.$declaration,
|
||||
$TrackModel.$declaration
|
||||
]);
|
||||
final program = compiler.compile({
|
||||
'mangayomi': {'source_code.dart': sourceCode}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'package:intl/intl.dart';
|
|||
import 'package:js_packer/js_packer.dart';
|
||||
import 'package:json_path/json_path.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/model.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/track_model.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/video_model.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/dood_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/filemoon.dart';
|
||||
|
|
@ -19,6 +20,7 @@ import 'package:mangayomi/services/anime_extractors/gogocdn_extractor.dart';
|
|||
import 'package:mangayomi/services/anime_extractors/mp4upload_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/mytv_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/okru_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/rapidcloud_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/sendvid_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/sibnet_extractor.dart';
|
||||
import 'package:mangayomi/services/anime_extractors/streamlare_extractor.dart';
|
||||
|
|
@ -931,6 +933,11 @@ class MBridge {
|
|||
.videosFromUrl(url, prefix: prefix, suffix: suffix);
|
||||
}
|
||||
|
||||
static Future<List<Video>> rapidCloudExtractor(
|
||||
String url, String prefix) async {
|
||||
return await RapidCloudExtractor().videosFromUrl(url, prefix);
|
||||
}
|
||||
|
||||
//Utility to use base64
|
||||
static String bAse64(String text, int type) {
|
||||
return utf8.decode(base64.decode(text));
|
||||
|
|
@ -1138,6 +1145,24 @@ class $MBridge extends MBridge with $Bridge {
|
|||
],
|
||||
namedParams: []),
|
||||
isStatic: true),
|
||||
'rapidCloudExtractor': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
|
||||
[BridgeTypeRef.type(RuntimeTypes.dynamicType)])),
|
||||
params: [
|
||||
BridgeParameter(
|
||||
'url',
|
||||
BridgeTypeAnnotation(
|
||||
BridgeTypeRef.type(RuntimeTypes.stringType)),
|
||||
false),
|
||||
BridgeParameter(
|
||||
'prefix',
|
||||
BridgeTypeAnnotation(
|
||||
BridgeTypeRef.type(RuntimeTypes.stringType)),
|
||||
false),
|
||||
],
|
||||
namedParams: []),
|
||||
isStatic: true),
|
||||
'sendVidExtractor': BridgeMethodDef(
|
||||
BridgeFunctionDef(
|
||||
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
|
||||
|
|
@ -2044,6 +2069,11 @@ class $MBridge extends MBridge with $Bridge {
|
|||
args[0]!.$value, args[1]!.$value, args[2]!.$value)
|
||||
.then((value) =>
|
||||
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
|
||||
static $Future $rapidCloudExtractor(
|
||||
Runtime runtime, $Value? target, List<$Value?> args) =>
|
||||
$Future.wrap(MBridge.rapidCloudExtractor(args[0]!.$value, args[1]!.$value)
|
||||
.then((value) =>
|
||||
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
|
||||
|
||||
static $bool $isEmptyOrIsNotEmpty(
|
||||
Runtime runtime, $Value? target, List<$Value?> args) {
|
||||
|
|
@ -2075,4 +2105,11 @@ $VideoModel _toVideoModel(Video e) => $VideoModel.wrap(VideoModel()
|
|||
..headers = e.headers
|
||||
..originalUrl = e.originalUrl
|
||||
..quality = e.quality
|
||||
..url = e.url);
|
||||
..url = e.url
|
||||
..subtitles = $List.wrap(e.subtitles == null
|
||||
? []
|
||||
: e.subtitles!
|
||||
.map((t) => $TrackModel.wrap(TrackModel()
|
||||
..file = t.file
|
||||
..label = t.label))
|
||||
.toList()));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dart_eval/dart_eval.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/track_model.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/video_model.dart';
|
||||
import 'package:mangayomi/eval/m_bridge.dart';
|
||||
import 'package:mangayomi/eval/bridge_class/manga_model.dart';
|
||||
|
|
@ -14,6 +15,8 @@ Runtime runtimeEval(Uint8List bytecode) {
|
|||
'package:bridge_lib/bridge_lib.dart', 'MangaModel.', $MangaModel.$new);
|
||||
runtime.registerBridgeFunc(
|
||||
'package:bridge_lib/bridge_lib.dart', 'VideoModel.', $VideoModel.$new);
|
||||
runtime.registerBridgeFunc(
|
||||
'package:bridge_lib/bridge_lib.dart', 'TrackModel.', $TrackModel.$new);
|
||||
runtime.registerBridgeFunc(
|
||||
'package:bridge_lib/bridge_lib.dart', 'MBridge.http', $MBridge.$http);
|
||||
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
|
||||
|
|
@ -26,6 +29,8 @@ Runtime runtimeEval(Uint8List bytecode) {
|
|||
'MBridge.gogoCdnExtractor', $MBridge.$gogoCdnExtractor);
|
||||
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
|
||||
'MBridge.doodExtractor', $MBridge.$doodExtractor);
|
||||
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
|
||||
'MBridge.rapidCloudExtractor', $MBridge.$rapidCloudExtractor);
|
||||
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
|
||||
'MBridge.streamTapeExtractor', $MBridge.$streamTapeExtractor);
|
||||
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ class Video {
|
|||
final String quality;
|
||||
final String originalUrl;
|
||||
final Map<String, String>? headers;
|
||||
final List<Track>? subtitles;
|
||||
|
||||
Video(this.url, this.quality, this.originalUrl, {this.headers});
|
||||
}
|
||||
Video(this.url, this.quality, this.originalUrl,
|
||||
{this.headers, this.subtitles});
|
||||
}
|
||||
|
||||
class Track {
|
||||
final String? file;
|
||||
final String? label;
|
||||
|
||||
Track(this.file, this.label);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ class _AnimePlayerViewState extends riv.ConsumerState<AnimePlayerView> {
|
|||
));
|
||||
return serversData.when(
|
||||
data: (data) {
|
||||
if (data.isEmpty &&
|
||||
if (data.$1.isEmpty &&
|
||||
(widget.episode.manga.value!.isLocalArchive ?? false) == false) {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
|
|
@ -80,12 +80,13 @@ class _AnimePlayerViewState extends riv.ConsumerState<AnimePlayerView> {
|
|||
),
|
||||
);
|
||||
}
|
||||
data.sort(
|
||||
data.$1.sort(
|
||||
(a, b) => a.quality.compareTo(b.quality),
|
||||
);
|
||||
return AnimeStreamPage(
|
||||
episode: widget.episode,
|
||||
videos: data,
|
||||
videos: data.$1,
|
||||
isLocal: data.$2,
|
||||
);
|
||||
},
|
||||
error: (error, stackTrace) => Scaffold(
|
||||
|
|
@ -146,7 +147,12 @@ class _AnimePlayerViewState extends riv.ConsumerState<AnimePlayerView> {
|
|||
class AnimeStreamPage extends riv.ConsumerStatefulWidget {
|
||||
final List<vid.Video> videos;
|
||||
final Chapter episode;
|
||||
const AnimeStreamPage({Key? key, required this.videos, required this.episode})
|
||||
final bool isLocal;
|
||||
const AnimeStreamPage(
|
||||
{Key? key,
|
||||
required this.isLocal,
|
||||
required this.videos,
|
||||
required this.episode})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -160,7 +166,8 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
|
||||
late final _streamController = AnimeStreamController(episode: widget.episode);
|
||||
|
||||
final ValueNotifier<vid.Video?> _video = ValueNotifier(null);
|
||||
final ValueNotifier<VideoPrefs?> _video = ValueNotifier(null);
|
||||
final ValueNotifier<VideoPrefs?> _subtitle = ValueNotifier(null);
|
||||
final ValueNotifier<double> _playbackSpeed = ValueNotifier(1.0);
|
||||
bool _seekToCurrentPosition = true;
|
||||
late Duration _currentPosition = _streamController.geTCurrentPosition();
|
||||
|
|
@ -184,14 +191,24 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
}
|
||||
},
|
||||
);
|
||||
|
||||
late final _firstVid = widget.videos.first;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentPositionSub;
|
||||
_video.value = widget.videos.first;
|
||||
_player.open(
|
||||
Media(_video.value!.originalUrl, httpHeaders: _video.value!.headers));
|
||||
if (_firstVid.subtitles!.isNotEmpty) {
|
||||
final firstSub = _firstVid.subtitles!.first;
|
||||
_subtitle.value = VideoPrefs(
|
||||
subtitle: SubtitleTrack.uri(firstSub.file!,
|
||||
title: firstSub.label, language: firstSub.label));
|
||||
}
|
||||
|
||||
_video.value = VideoPrefs(
|
||||
videoTrack: VideoTrack(
|
||||
_firstVid.originalUrl, _firstVid.quality, _firstVid.quality),
|
||||
headers: _firstVid.headers);
|
||||
_player.open(Media(_video.value!.videoTrack!.id,
|
||||
httpHeaders: _video.value!.headers));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -202,20 +219,44 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
}
|
||||
|
||||
void _onChangeVideoQuality() {
|
||||
List<VideoPrefs> videoQuality = [];
|
||||
List<VideoTrack> videoTracks = _player.state.tracks.video
|
||||
.where((element) => element.w != null && element.h != null)
|
||||
.toList();
|
||||
for (var track in videoTracks) {
|
||||
videoQuality.add(VideoPrefs(videoTrack: track, isLocal: true));
|
||||
}
|
||||
if (widget.videos.isNotEmpty && !widget.isLocal) {
|
||||
for (var video in widget.videos) {
|
||||
videoQuality.add(VideoPrefs(
|
||||
videoTrack: VideoTrack(
|
||||
video.url,
|
||||
video.quality,
|
||||
video.quality,
|
||||
),
|
||||
headers: video.headers,
|
||||
isLocal: false));
|
||||
}
|
||||
}
|
||||
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (_) => CupertinoActionSheet(
|
||||
title: Text(l10n.select_video_quality,
|
||||
style: const TextStyle(fontSize: 30)),
|
||||
actions: widget.videos
|
||||
actions: videoQuality
|
||||
.map((quality) => CupertinoActionSheetAction(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
quality.quality,
|
||||
quality.isLocal && !widget.isLocal
|
||||
? "${_firstVid.quality} ${quality.videoTrack!.h}p"
|
||||
: widget.isLocal
|
||||
? _firstVid.quality
|
||||
: quality.videoTrack!.title!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
@ -225,7 +266,12 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
),
|
||||
Icon(
|
||||
Icons.check,
|
||||
color: _video.value == quality
|
||||
color: quality.isLocal &&
|
||||
!widget.isLocal &&
|
||||
"${quality.videoTrack!.title}${quality.videoTrack!.h}p" ==
|
||||
"${_video.value!.videoTrack!.title}${_video.value!.videoTrack!.h}p" ||
|
||||
"${_video.value!.videoTrack!.id}${_video.value!.videoTrack!.title}" ==
|
||||
"${quality.videoTrack!.id}${quality.videoTrack!.title}"
|
||||
? Theme.of(context).iconTheme.color
|
||||
: Colors.transparent,
|
||||
),
|
||||
|
|
@ -233,8 +279,17 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
),
|
||||
onPressed: () {
|
||||
_video.value = quality; // change the video quality
|
||||
_player.open(Media(quality.originalUrl,
|
||||
httpHeaders: quality.headers));
|
||||
if (quality.isLocal) {
|
||||
if (widget.isLocal) {
|
||||
_player.setVideoTrack(quality.videoTrack!);
|
||||
} else {
|
||||
_player.open(Media(quality.videoTrack!.id,
|
||||
httpHeaders: quality.headers));
|
||||
}
|
||||
} else {
|
||||
_player.open(Media(quality.videoTrack!.id,
|
||||
httpHeaders: quality.headers));
|
||||
}
|
||||
_seekToCurrentPosition = true;
|
||||
_currentPositionSub = _player.stream.position.listen(
|
||||
(position) {
|
||||
|
|
@ -264,21 +319,40 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
}
|
||||
|
||||
void _onChangeVideoSubtitle() {
|
||||
List<VideoPrefs> videoSubtitle = [];
|
||||
List<SubtitleTrack> videoSubs = _player.state.tracks.subtitle
|
||||
.where((element) => element.title != null && element.language != null)
|
||||
.toList();
|
||||
for (var sub in videoSubs) {
|
||||
videoSubtitle.add(VideoPrefs(isLocal: true, subtitle: sub));
|
||||
}
|
||||
|
||||
if (widget.videos.isNotEmpty && !widget.isLocal) {
|
||||
for (var video in widget.videos) {
|
||||
for (var sub in video.subtitles!) {
|
||||
videoSubtitle.add(VideoPrefs(
|
||||
isLocal: false,
|
||||
subtitle: SubtitleTrack.uri(sub.file!,
|
||||
title: sub.label, language: sub.label)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final l10n = l10nLocalizations(context)!;
|
||||
showCupertinoModalPopup(
|
||||
context: context,
|
||||
builder: (_) => CupertinoActionSheet(
|
||||
title: Text(l10n.select_video_subtitle,
|
||||
style: const TextStyle(fontSize: 30)),
|
||||
actions: _player.state.tracks.subtitle
|
||||
actions: videoSubtitle
|
||||
.map((subtitle) => CupertinoActionSheetAction(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
subtitle.title ??
|
||||
subtitle.language ??
|
||||
subtitle.channels ??
|
||||
subtitle.subtitle!.title ??
|
||||
subtitle.subtitle!.language ??
|
||||
subtitle.subtitle!.channels ??
|
||||
"N/A",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
|
@ -288,14 +362,17 @@ class _AnimeStreamPageState extends riv.ConsumerState<AnimeStreamPage> {
|
|||
),
|
||||
Icon(
|
||||
Icons.check,
|
||||
color: _player.state.track.subtitle == subtitle
|
||||
color: _subtitle.value != null &&
|
||||
"${_subtitle.value!.subtitle!.id}${_subtitle.value!.subtitle!.title}${_subtitle.value!.subtitle!.language}" ==
|
||||
"${subtitle.subtitle!.id}${subtitle.subtitle!.title}${subtitle.subtitle!.language}"
|
||||
? Theme.of(context).iconTheme.color
|
||||
: Colors.transparent,
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
_player.setSubtitleTrack(subtitle);
|
||||
_subtitle.value = subtitle;
|
||||
_player.setSubtitleTrack(subtitle.subtitle!);
|
||||
Navigator.maybePop(_);
|
||||
},
|
||||
))
|
||||
|
|
@ -723,3 +800,12 @@ class MaterialPositionIndicatorState extends State<MaterialPositionIndicator> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPrefs {
|
||||
VideoTrack? videoTrack;
|
||||
SubtitleTrack? subtitle;
|
||||
bool isLocal;
|
||||
final Map<String, String>? headers;
|
||||
VideoPrefs(
|
||||
{this.videoTrack, this.isLocal = true, this.headers, this.subtitle});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,17 @@ class ExtensionScreen extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
if (isManga) {
|
||||
ref.watch(fetchMangaSourcesListProvider(id: null));
|
||||
ref.watch(fetchMangaSourcesListProvider(id: null, refresh: false));
|
||||
} else {
|
||||
ref.watch(fetchAnimeSourcesListProvider(id: null));
|
||||
ref.watch(fetchAnimeSourcesListProvider(id: null, refresh: false));
|
||||
}
|
||||
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => isManga
|
||||
? ref.refresh(fetchMangaSourcesListProvider(id: null).future)
|
||||
: ref.refresh(fetchAnimeSourcesListProvider(id: null).future),
|
||||
? ref.refresh(
|
||||
fetchMangaSourcesListProvider(id: null, refresh: true).future)
|
||||
: ref.refresh(
|
||||
fetchAnimeSourcesListProvider(id: null, refresh: true).future),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: StreamBuilder(
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import 'package:http/http.dart' as http;
|
|||
part 'fetch_anime_sources.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future fetchAnimeSourcesList(FetchAnimeSourcesListRef ref, {int? id}) async {
|
||||
if (ref.watch(checkForExtensionsUpdateStateProvider)) {
|
||||
Future fetchAnimeSourcesList(FetchAnimeSourcesListRef ref,
|
||||
{int? id, required bool refresh}) async {
|
||||
if (ref.watch(checkForExtensionsUpdateStateProvider) || refresh) {
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
final req = await http.get(Uri.parse(
|
||||
"https://kodjodevf.github.io/mangayomi-extensions/anime_index.json"));
|
||||
|
|
@ -64,7 +65,8 @@ Future fetchAnimeSourcesList(FetchAnimeSourcesListRef ref, {int? id}) async {
|
|||
final headers = await getHeaders(req.body, source.baseUrl!);
|
||||
isar.writeTxnSync(() {
|
||||
isar.sources.putSync(sourc
|
||||
..headers = headers
|
||||
..headers = headers ?? ""
|
||||
..isAdded = true
|
||||
..sourceCode = req.body
|
||||
..sourceCodeUrl = source.sourceCodeUrl
|
||||
..id = source.id
|
||||
|
|
@ -75,14 +77,13 @@ Future fetchAnimeSourcesList(FetchAnimeSourcesListRef ref, {int? id}) async {
|
|||
..hasCloudflare = source.hasCloudflare
|
||||
..iconUrl = source.iconUrl
|
||||
..typeSource = source.typeSource
|
||||
..isFullData = source.isFullData ?? false
|
||||
..lang = source.lang
|
||||
..isNsfw = source.isNsfw
|
||||
..name = source.name
|
||||
..version = source.version
|
||||
..versionLast = source.version
|
||||
..isManga = source.isManga
|
||||
..isFullData = source.isFullData
|
||||
..isFullData = source.isFullData ?? false
|
||||
..appMinVerReq = source.appMinVerReq);
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'fetch_anime_sources.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$fetchAnimeSourcesListHash() =>
|
||||
r'51594d405256b56228e36f285f095e24333ea273';
|
||||
r'1808961fb57b3d53b12ec898ceb7ecb25886ea9e';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
@ -44,9 +44,11 @@ class FetchAnimeSourcesListFamily extends Family<AsyncValue<dynamic>> {
|
|||
/// See also [fetchAnimeSourcesList].
|
||||
FetchAnimeSourcesListProvider call({
|
||||
int? id,
|
||||
required bool refresh,
|
||||
}) {
|
||||
return FetchAnimeSourcesListProvider(
|
||||
id: id,
|
||||
refresh: refresh,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -56,6 +58,7 @@ class FetchAnimeSourcesListFamily extends Family<AsyncValue<dynamic>> {
|
|||
) {
|
||||
return call(
|
||||
id: provider.id,
|
||||
refresh: provider.refresh,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -79,10 +82,12 @@ class FetchAnimeSourcesListProvider extends AutoDisposeFutureProvider<dynamic> {
|
|||
/// See also [fetchAnimeSourcesList].
|
||||
FetchAnimeSourcesListProvider({
|
||||
this.id,
|
||||
required this.refresh,
|
||||
}) : super.internal(
|
||||
(ref) => fetchAnimeSourcesList(
|
||||
ref,
|
||||
id: id,
|
||||
refresh: refresh,
|
||||
),
|
||||
from: fetchAnimeSourcesListProvider,
|
||||
name: r'fetchAnimeSourcesListProvider',
|
||||
|
|
@ -96,16 +101,20 @@ class FetchAnimeSourcesListProvider extends AutoDisposeFutureProvider<dynamic> {
|
|||
);
|
||||
|
||||
final int? id;
|
||||
final bool refresh;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FetchAnimeSourcesListProvider && other.id == id;
|
||||
return other is FetchAnimeSourcesListProvider &&
|
||||
other.id == id &&
|
||||
other.refresh == refresh;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, id.hashCode);
|
||||
hash = _SystemHash.combine(hash, refresh.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ import 'package:http/http.dart' as http;
|
|||
part 'fetch_manga_sources.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future fetchMangaSourcesList(FetchMangaSourcesListRef ref, {int? id}) async {
|
||||
if (ref.watch(checkForExtensionsUpdateStateProvider)) {
|
||||
Future fetchMangaSourcesList(FetchMangaSourcesListRef ref,
|
||||
{int? id, required refresh}) async {
|
||||
if (ref.watch(checkForExtensionsUpdateStateProvider) || refresh) {
|
||||
final info = await PackageInfo.fromPlatform();
|
||||
final req = await http.get(Uri.parse(
|
||||
"https://kodjodevf.github.io/mangayomi-extensions/index.json"));
|
||||
|
|
@ -66,7 +67,8 @@ Future fetchMangaSourcesList(FetchMangaSourcesListRef ref, {int? id}) async {
|
|||
final headers = await getHeaders(req.body, source.baseUrl!);
|
||||
isar.writeTxnSync(() {
|
||||
isar.sources.putSync(sourc
|
||||
..headers = headers
|
||||
..headers = headers ?? ""
|
||||
..isAdded = true
|
||||
..sourceCode = req.body
|
||||
..sourceCodeUrl = source.sourceCodeUrl
|
||||
..id = source.id
|
||||
|
|
@ -77,14 +79,13 @@ Future fetchMangaSourcesList(FetchMangaSourcesListRef ref, {int? id}) async {
|
|||
..hasCloudflare = source.hasCloudflare
|
||||
..iconUrl = source.iconUrl
|
||||
..typeSource = source.typeSource
|
||||
..isFullData = source.isFullData ?? false
|
||||
..lang = source.lang
|
||||
..isNsfw = source.isNsfw
|
||||
..name = source.name
|
||||
..version = source.version
|
||||
..versionLast = source.version
|
||||
..isManga = source.isManga
|
||||
..isFullData = source.isFullData
|
||||
..isFullData = source.isFullData ?? false
|
||||
..appMinVerReq = source.appMinVerReq);
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'fetch_manga_sources.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$fetchMangaSourcesListHash() =>
|
||||
r'719d582950ddb40cf7367ddca871ec46145ab0eb';
|
||||
r'0cd22cf1bb061a13e86e34ec08061e1c52c2f7cf';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
@ -44,9 +44,11 @@ class FetchMangaSourcesListFamily extends Family<AsyncValue<dynamic>> {
|
|||
/// See also [fetchMangaSourcesList].
|
||||
FetchMangaSourcesListProvider call({
|
||||
int? id,
|
||||
required dynamic refresh,
|
||||
}) {
|
||||
return FetchMangaSourcesListProvider(
|
||||
id: id,
|
||||
refresh: refresh,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -56,6 +58,7 @@ class FetchMangaSourcesListFamily extends Family<AsyncValue<dynamic>> {
|
|||
) {
|
||||
return call(
|
||||
id: provider.id,
|
||||
refresh: provider.refresh,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -79,10 +82,12 @@ class FetchMangaSourcesListProvider extends AutoDisposeFutureProvider<dynamic> {
|
|||
/// See also [fetchMangaSourcesList].
|
||||
FetchMangaSourcesListProvider({
|
||||
this.id,
|
||||
required this.refresh,
|
||||
}) : super.internal(
|
||||
(ref) => fetchMangaSourcesList(
|
||||
ref,
|
||||
id: id,
|
||||
refresh: refresh,
|
||||
),
|
||||
from: fetchMangaSourcesListProvider,
|
||||
name: r'fetchMangaSourcesListProvider',
|
||||
|
|
@ -96,16 +101,20 @@ class FetchMangaSourcesListProvider extends AutoDisposeFutureProvider<dynamic> {
|
|||
);
|
||||
|
||||
final int? id;
|
||||
final dynamic refresh;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is FetchMangaSourcesListProvider && other.id == id;
|
||||
return other is FetchMangaSourcesListProvider &&
|
||||
other.id == id &&
|
||||
other.refresh == refresh;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, id.hashCode);
|
||||
hash = _SystemHash.combine(hash, refresh.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,10 +42,12 @@ class _ExtensionListTileWidgetState
|
|||
_isLoading = true;
|
||||
});
|
||||
widget.source.isManga!
|
||||
? await ref.watch(
|
||||
fetchMangaSourcesListProvider(id: widget.source.id).future)
|
||||
: await ref.watch(
|
||||
fetchAnimeSourcesListProvider(id: widget.source.id).future);
|
||||
? await ref.watch(fetchMangaSourcesListProvider(
|
||||
id: widget.source.id, refresh: true)
|
||||
.future)
|
||||
: await ref.watch(fetchAnimeSourcesListProvider(
|
||||
id: widget.source.id, refresh: true)
|
||||
.future);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
|
|
@ -133,12 +135,12 @@ class _ExtensionListTileWidgetState
|
|||
_isLoading = true;
|
||||
});
|
||||
widget.source.isManga!
|
||||
? await ref.watch(
|
||||
fetchMangaSourcesListProvider(id: widget.source.id)
|
||||
.future)
|
||||
: await ref.watch(
|
||||
fetchAnimeSourcesListProvider(id: widget.source.id)
|
||||
.future);
|
||||
? await ref.watch(fetchMangaSourcesListProvider(
|
||||
id: widget.source.id, refresh: true)
|
||||
.future)
|
||||
: await ref.watch(fetchAnimeSourcesListProvider(
|
||||
id: widget.source.id, refresh: true)
|
||||
.future);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ Future<List<String>> downloadChapter(
|
|||
).future)
|
||||
.then((value) {
|
||||
final videosUrls =
|
||||
value.where((element) => !element.url.contains(".m3u8")).toList();
|
||||
value.$1.where((element) => !element.url.contains(".mp4")).toList();
|
||||
if (videosUrls.isNotEmpty) {
|
||||
pageUrls = [videosUrls.first.url];
|
||||
videoHeader.addAll(videosUrls.first.headers!);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ final showNSFWStateProvider =
|
|||
|
||||
typedef _$ShowNSFWState = AutoDisposeNotifier<bool>;
|
||||
String _$autoUpdateExtensionsStateHash() =>
|
||||
r'dda58b6f78d486f82150e1edee4c1dadb9776df4';
|
||||
r'30ce3c558504e005f9c85e2fc969ab7a581510cd';
|
||||
|
||||
/// See also [AutoUpdateExtensionsState].
|
||||
@ProviderFor(AutoUpdateExtensionsState)
|
||||
|
|
|
|||
107
lib/services/anime_extractors/rapidcloud_extractor.dart
Normal file
107
lib/services/anime_extractors/rapidcloud_extractor.dart
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:mangayomi/models/video.dart';
|
||||
import 'package:mangayomi/utils/cryptoaes/crypto_aes.dart';
|
||||
|
||||
class RapidCloudExtractor {
|
||||
static const serverUrl = ['https://megacloud.tv', 'https://rapid-cloud.co'];
|
||||
static const sourceUrl = [
|
||||
'/embed-2/ajax/e-1/getSources?id=',
|
||||
'/ajax/embed-6-v2/getSources?id='
|
||||
];
|
||||
static const sourceSpliter = ['/e-1/', '/embed-6-v2/'];
|
||||
static const sourceKey = ['1', '6'];
|
||||
|
||||
final http.Client client = http.Client();
|
||||
|
||||
Future<(String, String)> cipherTextCleaner(String data, String type) async {
|
||||
final response = await http.get(Uri.parse(
|
||||
'https://raw.githubusercontent.com/Claudemirovsky/keys/e$type/key'));
|
||||
final List indexPairs = json.decode(response.body);
|
||||
|
||||
String password = '';
|
||||
String ciphertext = data;
|
||||
int currentIndex = 0;
|
||||
|
||||
for (List item in indexPairs) {
|
||||
final start = item.first + currentIndex;
|
||||
final end = start + item.last;
|
||||
final passSubstr = data.substring(start, end);
|
||||
password += passSubstr;
|
||||
ciphertext = ciphertext.replaceFirst(passSubstr, '');
|
||||
currentIndex += item[1] as int;
|
||||
}
|
||||
return (ciphertext, password);
|
||||
}
|
||||
|
||||
Future<String> decrypt(String ciphered, String type) async {
|
||||
final result = await cipherTextCleaner(ciphered, type);
|
||||
|
||||
final res = CryptoAES.decryptAESCryptoJS(result.$1, result.$2);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<List<Video>> videosFromUrl(String url, String name) async {
|
||||
try {
|
||||
final type = url.startsWith('https://megacloud.tv') ? 0 : 1;
|
||||
final keyType = sourceKey[type];
|
||||
|
||||
final id = url.split(sourceSpliter[type]).last.split('?').first;
|
||||
final srcRes =
|
||||
await http.get(Uri.parse('${serverUrl[type]}${sourceUrl[type]}$id'));
|
||||
|
||||
final data = Data.fromJson(json.decode(srcRes.body));
|
||||
|
||||
final decrypted = json.decode(await decrypt(data.sources!, keyType));
|
||||
|
||||
return [
|
||||
Video(decrypted[0]["file"], name, decrypted[0]["file"],
|
||||
subtitles:
|
||||
data.tracks!.map((e) => Track(e.file!, e.label!)).toList())
|
||||
];
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Tracks {
|
||||
String? file;
|
||||
String? label;
|
||||
|
||||
Tracks({
|
||||
this.file,
|
||||
this.label,
|
||||
});
|
||||
|
||||
Tracks.fromJson(Map<String, dynamic> json) {
|
||||
file = json['file'];
|
||||
label = json['label'];
|
||||
}
|
||||
}
|
||||
|
||||
class Data {
|
||||
String? sources;
|
||||
List<Tracks>? tracks;
|
||||
bool? encrypted;
|
||||
|
||||
Data({
|
||||
this.sources,
|
||||
this.tracks,
|
||||
this.encrypted,
|
||||
});
|
||||
|
||||
Data.fromJson(Map<String, dynamic> json) {
|
||||
sources = json['sources'];
|
||||
if (json['tracks'] != null) {
|
||||
tracks = <Tracks>[];
|
||||
json['tracks'].forEach((v) {
|
||||
tracks!.add(Tracks.fromJson(v));
|
||||
});
|
||||
}
|
||||
encrypted = json['encrypted'];
|
||||
}
|
||||
}
|
||||
|
|
@ -11,13 +11,16 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|||
part 'get_anime_servers.g.dart';
|
||||
|
||||
@riverpod
|
||||
Future<List<Video>> getAnimeServers(
|
||||
Future<(List<Video>, bool)> getAnimeServers(
|
||||
GetAnimeServersRef ref, {
|
||||
required Chapter episode,
|
||||
}) async {
|
||||
List<Video> video = [];
|
||||
if (episode.manga.value!.isLocalArchive!) {
|
||||
return [Video(episode.archivePath!, episode.name!, episode.archivePath!)];
|
||||
return (
|
||||
[Video(episode.archivePath!, episode.name!, episode.archivePath!)],
|
||||
true
|
||||
);
|
||||
}
|
||||
final source =
|
||||
getSource(episode.manga.value!.lang!, episode.manga.value!.source!);
|
||||
|
|
@ -37,12 +40,19 @@ Future<List<Video>> getAnimeServers(
|
|||
];
|
||||
var res = await runtime.executeLib(
|
||||
'package:mangayomi/source_code.dart', 'getVideoList');
|
||||
// await Future.delayed(Duration(days: 1));
|
||||
if (res is $List) {
|
||||
video = res.$reified
|
||||
.map(
|
||||
(e) => Video(e.url, e.quality, e.originalUrl, headers: e.headers),
|
||||
)
|
||||
.toList();
|
||||
video = res.$reified.map(
|
||||
(e) {
|
||||
List<Track>? subtitles = [];
|
||||
var subs = e.subtitles;
|
||||
if (subs is $List) {
|
||||
subtitles = subs.map((e) => Track(e.file, e.label)).toList();
|
||||
}
|
||||
return Video(e.url, e.quality, e.originalUrl,
|
||||
headers: e.headers, subtitles: subtitles);
|
||||
},
|
||||
).toList();
|
||||
}
|
||||
return video;
|
||||
return (video, false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'get_anime_servers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getAnimeServersHash() => r'26be3f4055c08947e6dca4d8e62abe07ddb98481';
|
||||
String _$getAnimeServersHash() => r'a458dba029f241dd84237469ce6496526932ca32';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
@ -29,14 +29,14 @@ class _SystemHash {
|
|||
}
|
||||
}
|
||||
|
||||
typedef GetAnimeServersRef = AutoDisposeFutureProviderRef<List<Video>>;
|
||||
typedef GetAnimeServersRef = AutoDisposeFutureProviderRef<(List<Video>, bool)>;
|
||||
|
||||
/// See also [getAnimeServers].
|
||||
@ProviderFor(getAnimeServers)
|
||||
const getAnimeServersProvider = GetAnimeServersFamily();
|
||||
|
||||
/// See also [getAnimeServers].
|
||||
class GetAnimeServersFamily extends Family<AsyncValue<List<Video>>> {
|
||||
class GetAnimeServersFamily extends Family<AsyncValue<(List<Video>, bool)>> {
|
||||
/// See also [getAnimeServers].
|
||||
const GetAnimeServersFamily();
|
||||
|
||||
|
|
@ -74,7 +74,8 @@ class GetAnimeServersFamily extends Family<AsyncValue<List<Video>>> {
|
|||
}
|
||||
|
||||
/// See also [getAnimeServers].
|
||||
class GetAnimeServersProvider extends AutoDisposeFutureProvider<List<Video>> {
|
||||
class GetAnimeServersProvider
|
||||
extends AutoDisposeFutureProvider<(List<Video>, bool)> {
|
||||
/// See also [getAnimeServers].
|
||||
GetAnimeServersProvider({
|
||||
required this.episode,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'get_manga_detail.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getMangaDetailHash() => r'025ccc11f94f9b69bd85d86833bc261f66b74f7f';
|
||||
String _$getMangaDetailHash() => r'97c04f6710cb2c5678c16f1c0e73c7bb73b77693';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'get_popular_manga.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$getPopularMangaHash() => r'f5a1f5a66bad652cb461ef0ed9d30f60ff7a000a';
|
||||
String _$getPopularMangaHash() => r'7f785eaad6696c1181b1ddb3479c9987454397f4';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
|
|||
90
lib/utils/cryptoaes/crypto_aes.dart
Normal file
90
lib/utils/cryptoaes/crypto_aes.dart
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
|
||||
class CryptoAES {
|
||||
static String encryptAESCryptoJS(String plainText, String passphrase) {
|
||||
try {
|
||||
final salt = genRandomWithNonZero(8);
|
||||
var keyndIV = deriveKeyAndIV(passphrase, salt);
|
||||
final key = encrypt.Key(keyndIV.$1);
|
||||
final iv = encrypt.IV(keyndIV.$2);
|
||||
|
||||
final encrypter = encrypt.Encrypter(
|
||||
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
|
||||
final encrypted = encrypter.encrypt(plainText, iv: iv);
|
||||
Uint8List encryptedBytesWithSalt = Uint8List.fromList(
|
||||
createUint8ListFromString("Salted__") + salt + encrypted.bytes);
|
||||
return base64.encode(encryptedBytesWithSalt);
|
||||
} catch (error) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
static String decryptAESCryptoJS(String encrypted, String passphrase) {
|
||||
try {
|
||||
Uint8List encryptedBytesWithSalt = base64.decode(encrypted);
|
||||
|
||||
Uint8List encryptedBytes =
|
||||
encryptedBytesWithSalt.sublist(16, encryptedBytesWithSalt.length);
|
||||
final salt = encryptedBytesWithSalt.sublist(8, 16);
|
||||
var keyndIV = deriveKeyAndIV(passphrase, salt);
|
||||
final key = encrypt.Key(keyndIV.$1);
|
||||
final iv = encrypt.IV(keyndIV.$2);
|
||||
|
||||
final encrypter = encrypt.Encrypter(
|
||||
encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
|
||||
final decrypted =
|
||||
encrypter.decrypt64(base64.encode(encryptedBytes), iv: iv);
|
||||
return decrypted;
|
||||
} catch (error) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
static (Uint8List, Uint8List) deriveKeyAndIV(
|
||||
String passphrase, Uint8List salt) {
|
||||
var password = createUint8ListFromString(passphrase);
|
||||
Uint8List concatenatedHashes = Uint8List(0);
|
||||
Uint8List currentHash = Uint8List(0);
|
||||
bool enoughBytesForKey = false;
|
||||
Uint8List preHash = Uint8List(0);
|
||||
|
||||
while (!enoughBytesForKey) {
|
||||
// int preHashLength = currentHash.length + password.length + salt.length;
|
||||
if (currentHash.isNotEmpty) {
|
||||
preHash = Uint8List.fromList(currentHash + password + salt);
|
||||
} else {
|
||||
preHash = Uint8List.fromList(password + salt);
|
||||
}
|
||||
|
||||
currentHash = md5.convert(preHash).bytes as Uint8List;
|
||||
concatenatedHashes = Uint8List.fromList(concatenatedHashes + currentHash);
|
||||
if (concatenatedHashes.length >= 48) enoughBytesForKey = true;
|
||||
}
|
||||
|
||||
var keyBtyes = concatenatedHashes.sublist(0, 32);
|
||||
var ivBtyes = concatenatedHashes.sublist(32, 48);
|
||||
return (keyBtyes, ivBtyes);
|
||||
}
|
||||
|
||||
static Uint8List createUint8ListFromString(String s) {
|
||||
var ret = Uint8List(s.length);
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
ret[i] = s.codeUnitAt(i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Uint8List genRandomWithNonZero(int seedLength) {
|
||||
final random = Random.secure();
|
||||
const int randomMax = 245;
|
||||
final Uint8List uint8list = Uint8List(seedLength);
|
||||
for (int i = 0; i < seedLength; i++) {
|
||||
uint8list[i] = random.nextInt(randomMax) + 1;
|
||||
}
|
||||
return uint8list;
|
||||
}
|
||||
}
|
||||
|
|
@ -242,7 +242,7 @@ packages:
|
|||
source: hosted
|
||||
version: "0.3.3+4"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: crypto
|
||||
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
name: mangayomi
|
||||
description: Free and open source manga reader multi plateform app inspired by Tachiyomi.
|
||||
|
||||
version: 0.0.35+13
|
||||
version: 0.0.3+13
|
||||
|
||||
environment:
|
||||
sdk: '>=3.1.0 <4.0.0'
|
||||
|
|
@ -58,6 +58,7 @@ dependencies:
|
|||
media_kit_libs_ios_video: ^1.1.3
|
||||
media_kit_libs_macos_video: ^1.1.3
|
||||
media_kit_libs_windows_video: ^1.0.7
|
||||
crypto: ^3.0.3
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue