added new anime servers extractors

This commit is contained in:
kodjomoustapha 2023-07-30 21:10:44 +01:00
parent 6a80f194ae
commit b21a9409af
19 changed files with 479 additions and 84 deletions

View file

@ -14,18 +14,24 @@ import 'package:json_path/json_path.dart';
import 'package:mangayomi/eval/bridge_class/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/gogo_cdn_extractor.dart';
import 'package:mangayomi/services/anime_extractors/mp4_upload_extractor.dart';
import 'package:mangayomi/services/anime_extractors/my_tv_extractor.dart';
import 'package:mangayomi/services/anime_extractors/send_vid_extractor.dart';
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/sendvid_extractor.dart';
import 'package:mangayomi/services/anime_extractors/sibnet_extractor.dart';
import 'package:mangayomi/services/anime_extractors/stream_tape_extractor.dart';
import 'package:mangayomi/services/anime_extractors/streamlare_extractor.dart';
import 'package:mangayomi/services/anime_extractors/streamtape_extractor.dart';
import 'package:mangayomi/main.dart';
import 'package:mangayomi/models/source.dart';
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/modules/webview/webview.dart';
import 'package:mangayomi/services/anime_extractors/vidbom_extractor.dart';
import 'package:mangayomi/services/anime_extractors/voe_extractor.dart';
import 'package:mangayomi/services/anime_extractors/your_upload_extractor.dart';
import 'package:mangayomi/services/http_service/cloudflare/cloudflare_bypass.dart';
import 'package:mangayomi/utils/constant.dart';
import 'package:mangayomi/utils/extensions.dart';
import 'package:mangayomi/utils/reg_exp_matcher.dart';
import 'package:mangayomi/utils/xpath_selector.dart';
import 'package:xpath_selector_html_parser/xpath_selector_html_parser.dart';
@ -220,7 +226,7 @@ class MBridge {
static Future<String> getHtmlViaWebview(String url, String rule) async {
bool isOk = false;
String? html;
if (Platform.isWindows || Platform.isLinux) {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
final webview = await WebviewWindow.create(
configuration: CreateConfiguration(
windowHeight: 500,
@ -551,15 +557,12 @@ class MBridge {
static String subString(String text, String pattern, int type) {
String result = "";
//substring before
if (type == 0) {
result = text.split(pattern).first;
} else
// substring after last
if (type == 1) {
result = text.substringBefore(pattern);
} else if (type == 1) {
result = text.split(pattern).last;
} else if (type == 2) {
result = text.substring(text.lastIndexOf(pattern) + 1);
result = text.substringAfter(pattern);
}
return result;
}
@ -725,6 +728,39 @@ class MBridge {
);
}
static Future<List<Video>> okruExtractor(String url) async {
return await OkruExtractor().videosFromUrl(
url,
);
}
static Future<List<Video>> yourUploadExtractor(
String url, String? headers, String? name, String prefix) async {
Map<String, String> newHeaders = {};
if (headers != null) {
newHeaders = (jsonDecode(headers) as Map)
.map((key, value) => MapEntry(key.toString(), value.toString()));
}
return await YourUploadExtractor().videosFromUrl(url, newHeaders,
prefix: prefix, name: name ?? "YourUpload");
}
static Future<List<Video>> voeExtractor(String url, String? quality) async {
return await VoeExtractor().videosFromUrl(url, quality);
}
static Future<List<Video>> vidBomExtractor(String url) async {
return await VidBomExtractor().videosFromUrl(
url,
);
}
static Future<List<Video>> streamlareExtractor(
String url, String prefix, String suffix) async {
return await StreamlareExtractor()
.videosFromUrl(url, prefix: prefix, suffix: suffix);
}
static List<Video> toVideos(
String url, String quality, String originalUrl, String? headers) {
Map<String, String> newHeaders = {};
@ -840,6 +876,74 @@ class $MBridge extends MBridge with $Bridge {
],
namedParams: []),
isStatic: true),
'okruExtractor': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)])),
params: [
BridgeParameter(
'url',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
],
namedParams: []),
isStatic: true),
'voeExtractor': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)])),
params: [
BridgeParameter(
'url',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'quality',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType),
nullable: true),
false),
],
namedParams: []),
isStatic: true),
'vidBomExtractor': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)])),
params: [
BridgeParameter(
'url',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
],
namedParams: []),
isStatic: true),
'streamlareExtractor': 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),
BridgeParameter(
'suffix',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
],
namedParams: []),
isStatic: true),
'sendVidExtractor': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
@ -864,6 +968,36 @@ class $MBridge extends MBridge with $Bridge {
],
namedParams: []),
isStatic: true),
'yourUploadExtractor': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.future,
[BridgeTypeRef.type(RuntimeTypes.dynamicType)])),
params: [
BridgeParameter(
'url',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
BridgeParameter(
'headers',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType),
nullable: true),
false),
BridgeParameter(
'name',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType),
nullable: true),
false),
BridgeParameter(
'prefix',
BridgeTypeAnnotation(
BridgeTypeRef.type(RuntimeTypes.stringType)),
false),
],
namedParams: []),
isStatic: true),
'subString': BridgeMethodDef(
BridgeFunctionDef(
returns: BridgeTypeAnnotation(
@ -1560,77 +1694,62 @@ class $MBridge extends MBridge with $Bridge {
static $Future $gogoCdnExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.gogoCdnExtractor(args[0]!.$value)
.then((value) => $List.wrap(value
.map((e) => $VideoModel.wrap(VideoModel()
..headers = e.headers
..originalUrl = e.originalUrl
..quality = e.quality
..url = e.url))
.toList())));
$Future.wrap(MBridge.gogoCdnExtractor(args[0]!.$value).then(
(value) => $List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $doodExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.doodExtractor(args[0]!.$value)
.then((value) => $List.wrap(value
.map((e) => $VideoModel.wrap(VideoModel()
..headers = e.headers
..originalUrl = e.originalUrl
..quality = e.quality
..url = e.url))
.toList())));
$Future.wrap(MBridge.doodExtractor(args[0]!.$value).then(
(value) => $List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $streamTapeExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.streamTapeExtractor(args[0]!.$value)
.then((value) => $List.wrap(value
.map((e) => $VideoModel.wrap(VideoModel()
..headers = e.headers
..originalUrl = e.originalUrl
..quality = e.quality
..url = e.url))
.toList())));
$Future.wrap(MBridge.streamTapeExtractor(args[0]!.$value).then(
(value) => $List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $mp4UploadExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.mp4UploadExtractor(args[0]!.$value, args[1]!.$value,
args[2]!.$value, args[3]!.$value)
.then((value) => $List.wrap(value
.map((e) => $VideoModel.wrap(VideoModel()
..headers = e.headers
..originalUrl = e.originalUrl
..quality = e.quality
..url = e.url))
.toList())));
.then((value) =>
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $sendVidExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.sendVidExtractor(
args[0]!.$value, args[1]!.$value, args[2]!.$value)
.then((value) => $List.wrap(value
.map((e) => $VideoModel.wrap(VideoModel()
..headers = e.headers
..originalUrl = e.originalUrl
..quality = e.quality
..url = e.url))
.toList())));
.then((value) =>
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $yourUploadExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.yourUploadExtractor(args[0]!.$value, args[1]!.$value,
args[2]!.$value, args[3]!.$value)
.then((value) =>
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $sibnetExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.sibnetExtractor(args[0]!.$value)
.then((value) => $List.wrap(value
.map((e) => $VideoModel.wrap(VideoModel()
..headers = e.headers
..originalUrl = e.originalUrl
..quality = e.quality
..url = e.url))
.toList())));
$Future.wrap(MBridge.sibnetExtractor(args[0]!.$value).then(
(value) => $List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $myTvExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.myTvExtractor(args[0]!.$value)
.then((value) => $List.wrap(value
.map((e) => $VideoModel.wrap(VideoModel()
..headers = e.headers
..originalUrl = e.originalUrl
..quality = e.quality
..url = e.url))
.toList())));
$Future.wrap(MBridge.myTvExtractor(args[0]!.$value).then(
(value) => $List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $okruExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.okruExtractor(args[0]!.$value).then(
(value) => $List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $voeExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.voeExtractor(args[0]!.$value, args[1]!.$value).then(
(value) => $List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $vidBomExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.vidBomExtractor(args[0]!.$value).then(
(value) => $List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $Future $streamlareExtractor(
Runtime runtime, $Value? target, List<$Value?> args) =>
$Future.wrap(MBridge.streamlareExtractor(
args[0]!.$value, args[1]!.$value, args[2]!.$value)
.then((value) =>
$List.wrap(value.map((e) => _toVideoModel(e)).toList())));
static $bool $isEmptyOrIsNotEmpty(
Runtime runtime, $Value? target, List<$Value?> args) {
return $bool(MBridge.isEmptyOrIsNotEmpty(
@ -1656,3 +1775,9 @@ void _botToast(String title) {
duration: const Duration(seconds: 5),
title: title);
}
$VideoModel _toVideoModel(Video e) => $VideoModel.wrap(VideoModel()
..headers = e.headers
..originalUrl = e.originalUrl
..quality = e.quality
..url = e.url);

View file

@ -32,6 +32,16 @@ Runtime runtimeEval(Uint8List bytecode) {
'MBridge.sendVidExtractor', $MBridge.$sendVidExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.sibnetExtractor', $MBridge.$sibnetExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.okruExtractor', $MBridge.$okruExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.yourUploadExtractor', $MBridge.$yourUploadExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.voeExtractor', $MBridge.$voeExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.vidBomExtractor', $MBridge.$vidBomExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.streamlareExtractor', $MBridge.$streamlareExtractor);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',
'MBridge.jsonPathToString', $MBridge.$jsonPathToString);
runtime.registerBridgeFunc('package:bridge_lib/bridge_lib.dart',

View file

@ -87,5 +87,6 @@ class Source {
apiUrl = json['apiUrl'];
version = json['version'];
isManga = json['isManga'] ?? true;
isFullData = json['isFullData'] ?? false;
}
}

View file

@ -384,7 +384,7 @@ class _MangaHomeScreenState extends ConsumerState<MangaHomeScreen> {
Flexible(
child: GridViewWidget(
controller: _scrollController,
itemCount: _length,
itemCount: data.length < _length ? data.length : _length,
itemBuilder: (context, index) {
if (index == _length - 1 && _isLoading) {
return buildProgressIndicator();

View file

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:mangayomi/modules/more/widgets/incognito_mode_widget.dart';
@ -59,16 +61,53 @@ class MoreScreen extends StatelessWidget {
icon: Icons.history_outlined,
title: l10n.history,
),
// ListTile(
// onTap: () {},
// leading: const SizedBox(
// height: 40,
// child: Icon(Icons.settings_backup_restore_sharp)),
// title: const Text('Backup and restore'),
// ),
// const Divider(
// color: Colors.grey,
// ),
ListTile(
onTap: () {
final data = {
'variables': {
'type': 'anime',
'size': 26,
'dateRange': 7,
'page': 1,
},
'query': """
query(
%type: VaildPopularTypeEnumType!
%size: Int!
%page: Int
%dateRange: Int
) {
queryPopular(
type: %type
size: %size
dateRange: %dateRange
page: %page
) {
total
recommendations {
anyCard {
_id
name
thumbnail
englishName
nativeName
slugTime
}
}
}
}
""",
};
print(jsonEncode(data));
print(Uri.parse("https://api.allanime.day").host);
},
leading: const SizedBox(
height: 40, child: Icon(Icons.settings_backup_restore_sharp)),
title: const Text('Backup and restore'),
),
const Divider(
color: Colors.grey,
),
ListTileWidget(
onTap: () {
context.push('/settings');

View file

@ -37,7 +37,7 @@ class _MangaWebViewState extends ConsumerState<MangaWebView> {
double progress = 0;
@override
void initState() {
if (Platform.isWindows || Platform.isLinux) {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
_runWebViewDesktop();
} else {
setState(() {

View file

@ -0,0 +1,50 @@
import 'package:http/http.dart' as http;
import 'package:html/parser.dart' show parse;
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/utils/extensions.dart';
class OkruExtractor {
final http.Client client = http.Client();
String fixQuality(String quality) {
final qualities = {
'ultra': '2160p',
'quad': '1440p',
'full': '1080p',
'hd': '720p',
'sd': '480p',
'low': '360p',
'lowest': '240p',
'mobile': '144p',
};
return qualities[quality] ?? quality;
}
Future<List<Video>> videosFromUrl(String url,
{String prefix = '', bool fixQualities = true}) async {
final response = await client.get(Uri.parse(url));
final document = parse(response.body);
final videosString = document
.querySelector('div[data-options]')
?.attributes['data-options']!
.substringAfter(
'''\\"videos\\":[{\\\\"name\\\\":\\\\"''').substringBefore(']') ??
'';
List<Video> videoList = [];
List<String> values =
videosString.split('''{\\\\"name\\\\":\\\\"''').reversed.toList();
for (var value in values) {
final videoUrl = value
.substringAfter('''url\\\\":\\\\"''').substringBefore(
'''\\"''').replaceAll(r'\\u0026', '&');
final quality = value.substringBefore('''\\"''');
final fixedQuality = fixQualities ? fixQuality(quality) : quality;
final videoQuality =
'${prefix.isNotEmpty ? '$prefix ' : ''}Okru:$fixedQuality';
if (videoUrl.startsWith('https://')) {
videoList.add(Video(videoUrl, videoQuality, videoUrl));
}
}
return videoList;
}
}

View file

@ -0,0 +1,75 @@
import 'package:http/http.dart' as http;
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/utils/extensions.dart';
class StreamlareExtractor {
final http.Client client = http.Client();
Future<List<Video>> videosFromUrl(String url,
{String prefix = "", String suffix = ""}) async {
final id = url.split('/').last;
final playlistResponse = await client.post(
Uri.parse('https://slwatch.co/api/video/stream/get'),
headers: {'Content-Type': 'application/json'},
body: '{"id":"$id"}',
);
final playlist = playlistResponse.body;
final type = playlist.substringAfter('"type":"').substringBefore('"');
if (type == 'hls') {
final masterPlaylistUrl = playlist
.substringAfter('"file":"')
.substringBefore('"')
.replaceAll(r'\/', '/');
final masterPlaylistResponse =
await client.get(Uri.parse(masterPlaylistUrl));
final masterPlaylist = masterPlaylistResponse.body;
const separator = '#EXT-X-STREAM-INF';
return masterPlaylist
.substringAfter(separator)
.split(separator)
.map((value) {
final quality =
'${value.substringAfter('RESOLUTION=').substringAfter('x').substringBefore(',')}p';
final videoUrl =
value.substringAfter('\n').substringBefore('\n').let((urlPart) {
return !urlPart.startsWith('http')
? masterPlaylistUrl.substringBefore('master.m3u8') + urlPart
: urlPart;
});
return Video(
videoUrl, _buildQuality(quality, prefix, suffix), videoUrl);
}).toList();
} else {
const separator = '"label":"';
List<Video> videoList = [];
List<String> values = playlist.substringAfter(separator).split(separator);
for (var value in values) {
final quality = value.substringAfter(separator).substringBefore('",');
final apiUrl = value
.substringAfter('"file":"')
.substringBefore('",')
.replaceAll('\\', '');
final response = await client.post(Uri.parse(apiUrl));
final videoUrl = response.request!.url.toString();
videoList.add(
Video(videoUrl, _buildQuality(quality, prefix, suffix), videoUrl));
}
return videoList;
}
}
String _buildQuality(String resolution,
[String prefix = '', String suffix = '']) {
final buffer = StringBuffer();
if (prefix.isNotEmpty) buffer.write('$prefix ');
buffer.write('Streamlare:$resolution');
if (suffix.isNotEmpty) buffer.write(' $suffix');
return buffer.toString();
}
}

View file

@ -0,0 +1,28 @@
import 'package:http/http.dart' as http;
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/utils/extensions.dart';
import 'package:mangayomi/utils/xpath_selector.dart';
class VidBomExtractor {
final http.Client client = http.Client();
Future<List<Video>> videosFromUrl(String url) async {
final response = await client.get(Uri.parse(url));
final script = xpathSelector(response.body)
.queryXPath('//script[contains(text(), "sources")]/text()')
.attrs;
final data =
script.first!.substringAfter('sources: [').substringBefore('],');
return data.split('file:"').skip(1).map((source) {
final src = source.substringBefore('"');
var quality =
'Vidbom: ${source.substringAfter('label:"').substringBefore('"')}';
if (quality.length > 15) {
quality = 'Vidshare: 480p';
}
return Video(src, quality, src);
}).toList();
}
}

View file

@ -0,0 +1,30 @@
import 'package:http/http.dart' as http;
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/utils/extensions.dart';
import 'package:mangayomi/utils/xpath_selector.dart';
class VoeExtractor {
http.Client client = http.Client();
Future<List<Video>> videosFromUrl(String url, String? quality) async {
try {
final response = await client.get(Uri.parse(url));
final script = xpathSelector(response.body)
.queryXPath(
'//script[contains(text(), "const sources") or contains(text(), "var sources")]/text()')
.attrs;
if (script.isEmpty) {
return [];
}
final videoUrl =
script.first!.substringAfter("hls': '").substringBefore("'");
final resolution =
script.first!.substringAfter("video_height': ").substringBefore(",");
final qualityStr = quality ?? "VoeCDN(${resolution}p)";
return [Video(url, qualityStr, videoUrl)];
} catch (_) {
return [];
}
}
}

View file

@ -0,0 +1,31 @@
import 'package:http/http.dart' as http;
import 'package:mangayomi/models/video.dart';
import 'package:mangayomi/utils/extensions.dart';
import 'package:mangayomi/utils/xpath_selector.dart';
class YourUploadExtractor {
http.Client client = http.Client();
Future<List<Video>> videosFromUrl(String url, Map<String, String> headers,
{String name = "YourUpload", String prefix = ""}) async {
final newHeaders = Map<String, String>.from(headers);
newHeaders["referer"] = "https://www.yourupload.com/";
try {
final response = await client.get(Uri.parse(url), headers: newHeaders);
final baseData = xpathSelector(response.body)
.queryXPath('//script[contains(text(), "jwplayerOptions")]/text()')
.attrs;
if (baseData.isNotEmpty) {
final basicUrl =
baseData.first!.substringAfter("file: '").substringBefore("',");
final quality = prefix + name;
return [Video(basicUrl, quality, basicUrl, headers: newHeaders)];
} else {
return [];
}
} catch (_) {
return [];
}
}
}

View file

@ -17,7 +17,7 @@ Future<String> cloudflareBypass(
final ua = isar.settings.getSync(227)!.userAgent!;
bool isOk = false;
String? html;
if (Platform.isWindows || Platform.isLinux) {
if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
final webview = await WebviewWindow.create(
configuration: CreateConfiguration(
windowHeight: 500,

View file

@ -13,4 +13,10 @@ extension StringExtensions on String {
return substring(0, endIndex);
}
}
}
extension LetExtension<T> on T {
R let<R>(R Function(T) block) {
return block(this);
}
}

View file

@ -527,10 +527,10 @@ packages:
dependency: "direct main"
description:
name: flutter_meedu_videoplayer
sha256: f84e647f1f52aa96b6e3367c8db15b44174dd1144b3ebd91d41693605dea13f7
sha256: "6849b2b05c8413f2efbb70cf5be759b2e6a978e373e58c8d8bb87414ba141e6c"
url: "https://pub.dev"
source: hosted
version: "4.2.20"
version: "4.2.21"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -873,10 +873,10 @@ packages:
dependency: transitive
description:
name: media_kit_libs_windows_video
sha256: "4aa12f61c9989c4d7159ed0c15640d645dbe59026ac9057a3651d026a409dcb9"
sha256: b343e644927982a2ef3db63877b36d84bdda8173d8318ca0d1c68c1ea8a35982
url: "https://pub.dev"
source: hosted
version: "1.0.4"
version: "1.0.5"
media_kit_native_event_loop:
dependency: transitive
description: