diff --git a/lib/common/constants.dart b/lib/common/constants.dart index 4daad0be8..3b510d0e8 100644 --- a/lib/common/constants.dart +++ b/lib/common/constants.dart @@ -40,8 +40,6 @@ class Constants { '{"appId":1,"platform":3,"version":"8.43.0","abtest":""}'; static const baseHeaders = { - 'connection': 'keep-alive', - 'accept-encoding': 'br,gzip', // 'referer': HttpString.baseUrl, 'env': 'prod', 'app-key': 'android64', diff --git a/lib/common/widgets/dialog/dialog.dart b/lib/common/widgets/dialog/dialog.dart index b018a239e..7ad1a0f70 100644 --- a/lib/common/widgets/dialog/dialog.dart +++ b/lib/common/widgets/dialog/dialog.dart @@ -1,42 +1,46 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -Future showConfirmDialog({ +Future showConfirmDialog({ required BuildContext context, required String title, - dynamic content, - required VoidCallback onConfirm, -}) { + Object? content, + @Deprecated('use `bool result = await showConfirmDialog()` instead') + VoidCallback? onConfirm, +}) async { assert(content is String? || content is Widget); - return showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(title), - content: content is String - ? Text(content) - : content is Widget - ? content - : null, - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () { - Get.back(); - onConfirm(); - }, - child: const Text('确认'), - ), - ], - ); - }, - ); + return await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text(title), + content: content is String + ? Text(content) + : content is Widget + ? content + : null, + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + TextButton( + onPressed: () { + Get.back(result: true); + onConfirm?.call(); + }, + child: const Text('确认'), + ), + ], + ); + }, + ) ?? + false; } void showPgcFollowDialog({ diff --git a/lib/http/init.dart b/lib/http/init.dart index 731bb3ab2..cbcced50e 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -103,6 +103,8 @@ class Request { //Http请求头. headers: { 'user-agent': 'Dart/3.6 (dart:io)', // Http2Adapter不会自动添加标头 + if (!Pref.enableHttp2) 'connection': 'keep-alive', + 'accept-encoding': 'br,gzip', }, responseDecoder: _responseDecoder, // Http2Adapter没有自动解压 persistentConnection: true, diff --git a/lib/http/sponsor_block.dart b/lib/http/sponsor_block.dart new file mode 100644 index 000000000..ef151f32d --- /dev/null +++ b/lib/http/sponsor_block.dart @@ -0,0 +1,222 @@ +import 'dart:convert'; + +import 'package:PiliPlus/build_config.dart'; +import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/http/init.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/sponsor_block_api.dart'; +import 'package:PiliPlus/models/common/sponsor_block/post_segment_model.dart'; +import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart'; +import 'package:PiliPlus/models_new/sponsor_block/segment_item.dart'; +import 'package:PiliPlus/models_new/sponsor_block/user_info.dart'; +import 'package:PiliPlus/utils/storage_pref.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:dio/dio.dart'; + +/// https://github.com/hanydd/BilibiliSponsorBlock/wiki/API +abstract final class SponsorBlock { + static String get blockServer => Pref.blockServer; + static final options = Options( + followRedirects: true, + validateStatus: (status) => true, + ); + + static Error getErrMsg(Response res) { + String statusMessage = switch (res.statusCode) { + 200 => '意料之外的响应', + 400 => '参数错误', + 403 => '被自动审核机制拒绝', + 404 => '未找到数据', + 409 => '重复提交', + 429 => '提交太快(触发速率控制)', + 500 => '服务器无法获取信息', + -1 => res.data['message'].toString(), // DioException + _ => res.statusMessage ?? res.statusCode.toString(), + }; + if (res.statusCode != null && res.statusCode != -1) { + final data = res.data; + if (res.statusCode == 200 || + (data is String && data.isNotEmpty && data.length < 200)) { + statusMessage = '$statusMessage:$data'; + } + } + return Error(statusMessage, code: res.statusCode); + } + + static String _api(String url) => '$blockServer/api/$url'; + + static Future>> getSkipSegments({ + required String bvid, + required int cid, + }) async { + final res = await Request().get( + _api(SponsorBlockApi.skipSegments), + queryParameters: { + 'videoID': bvid, + 'cid': cid, + }, + options: options, + ); + + if (res.statusCode == 200) { + if (res.data case List list) { + return Success(list.map((i) => SegmentItemModel.fromJson(i)).toList()); + } + } + return getErrMsg(res); + } + + static Future> voteOnSponsorTime({ + required String uuid, + int? type, + SegmentType? category, + }) async { + assert((type == null) == (category == null)); + final res = await Request().post( + _api(SponsorBlockApi.voteOnSponsorTime), + data: { + 'UUID': uuid, + 'type': ?type, + 'category': ?category?.name, + 'userID': Pref.blockUserID, + }, + options: options, + ); + return res.statusCode == 200 ? const Success(null) : getErrMsg(res); + } + + static Future> viewedVideoSponsorTime(String uuid) async { + final res = await Request().post( + _api(SponsorBlockApi.viewedVideoSponsorTime), + data: {'UUID': uuid}, + options: options, + ); + return res.statusCode == 200 ? const Success(null) : getErrMsg(res); + } + + static Future> uptimeStatus() async { + final res = await Request().get( + _api(SponsorBlockApi.uptimeStatus), + options: options, + ); + if (res.statusCode == 200 && + res.data is String && + Utils.isStringNumeric(res.data)) { + return const Success(null); + } + return getErrMsg(res); + } + + static Future> userInfo( + List query, { + String? userId, + }) async { + final res = await Request().get( + _api(SponsorBlockApi.userInfo), + queryParameters: { + 'userID': userId ?? Pref.blockUserID, + 'values': jsonEncode(query), + }, + options: options, + ); + if (res.statusCode == 200) { + return Success(UserInfo.fromJson(res.data)); + } + return getErrMsg(res); + } + + static Future>> postSkipSegments({ + required String bvid, + required int cid, + required double videoDuration, + required List segments, + }) async { + final res = await Request().post( + _api(SponsorBlockApi.skipSegments), + data: { + 'videoID': bvid, + 'cid': cid.toString(), + 'userID': Pref.blockUserID, + 'userAgent': '${Constants.appName}/${BuildConfig.versionName}', + 'videoDuration': videoDuration, + 'segments': segments + .map( + (item) => { + 'segment': [item.segment.first, item.segment.second], + 'category': item.category.name, + 'actionType': item.actionType.name, + }, + ) + .toList(), + }, + options: options, + ); + + if (res.statusCode == 200) { + if (res.data case List list) { + return Success(list.map((i) => SegmentItemModel.fromJson(i)).toList()); + } + } + return getErrMsg(res); + } + + /// { + /// "bvID": string, // B站视频BVID + /// "cid": string, // 视频CID + /// "ytbID": string, // YouTube视频ID + /// "UUID": string, // 绑定记录的UUID(不是视频中片段的UUID,是绑定记录本身的UUID) + /// "votes": int, // 绑定记录的投票数 + /// "locked": int, // 绑定记录是否锁定 + /// } + /// TODO: show port video info dialog + static Future> getPortVideo({ + required String bvid, + required int cid, + }) async { + final res = await Request().get( + _api(SponsorBlockApi.portVideo), + queryParameters: { + 'videoID': bvid, + 'cid': cid.toString(), + }, + options: options, + ); + + if (res.statusCode == 200) { + if (res.data case Map data) { + if (data['ytbID'] case String ytbId) { + return Success(ytbId); + } + } + } + return getErrMsg(res); + } + + static Future> postPortVideo({ + required String bvid, + required int cid, + required String ytbId, + required int videoDuration, + }) async { + final res = await Request().post( + _api(SponsorBlockApi.portVideo), + data: { + 'bvID': bvid, + 'cid': cid.toString(), + 'ytbID': ytbId, + 'userID': Pref.blockUserID, + 'biliDuration': videoDuration, + }, + options: options, + ); + + if (res.statusCode == 200) { + if (res.data case Map data) { + if (data['UUID'] case String uuid) { + return Success(uuid); + } + } + } + return getErrMsg(res); + } +} diff --git a/lib/http/sponsor_block_api.dart b/lib/http/sponsor_block_api.dart new file mode 100644 index 000000000..82cbf4373 --- /dev/null +++ b/lib/http/sponsor_block_api.dart @@ -0,0 +1,9 @@ +abstract final class SponsorBlockApi { + static const String skipSegments = 'skipSegments'; + static const String voteOnSponsorTime = 'voteOnSponsorTime'; + static const String viewedVideoSponsorTime = 'viewedVideoSponsorTime'; + static const String portVideo = 'portVideo'; + + static const String userInfo = 'userInfo'; + static const String uptimeStatus = 'status/uptime'; +} diff --git a/lib/models_new/sponsor_block/segment_item.dart b/lib/models_new/sponsor_block/segment_item.dart index 359db7931..b3ced0e91 100644 --- a/lib/models_new/sponsor_block/segment_item.dart +++ b/lib/models_new/sponsor_block/segment_item.dart @@ -7,6 +7,7 @@ class SegmentItemModel { List segment; String uuid; num? videoDuration; + int votes; SegmentItemModel({ this.cid, @@ -15,6 +16,7 @@ class SegmentItemModel { required this.segment, required this.uuid, this.videoDuration, + this.votes = 0, }); factory SegmentItemModel.fromJson(Map json) => @@ -29,6 +31,7 @@ class SegmentItemModel { videoDuration: json["videoDuration"] == null ? null : (json["videoDuration"] as num) * 1000, + votes: json["votes"], ); factory SegmentItemModel.fromPgcJson( diff --git a/lib/models_new/sponsor_block/user_info.dart b/lib/models_new/sponsor_block/user_info.dart new file mode 100644 index 000000000..25a2a3c75 --- /dev/null +++ b/lib/models_new/sponsor_block/user_info.dart @@ -0,0 +1,33 @@ +import 'package:PiliPlus/utils/duration_utils.dart'; +import 'package:PiliPlus/utils/num_utils.dart'; + +class UserInfo { + final int viewCount; + final double minutesSaved; + final int segmentCount; + + const UserInfo({ + required this.viewCount, + required this.minutesSaved, + required this.segmentCount, + }); + + factory UserInfo.fromJson(Map json) => UserInfo( + viewCount: json['viewCount'], + minutesSaved: (json['minutesSaved'] as num).toDouble(), + segmentCount: json['segmentCount'], + ); + + @override + String toString() { + String minutes = DurationUtils.formatTimeDuration( + Duration(minutes: minutesSaved.round()), + ); + if (minutes.isEmpty) { + minutes = '0分钟'; + } + return ('您提交了 ${NumUtils.formatPositiveDecimal(segmentCount)} 片段\n' + '您为大家节省了 ${NumUtils.formatPositiveDecimal(viewCount)} 片段\n' + '($minutes 的生命)'); + } +} diff --git a/lib/pages/sponsor_block/view.dart b/lib/pages/sponsor_block/view.dart index 9253b4c5e..7b7404b3e 100644 --- a/lib/pages/sponsor_block/view.dart +++ b/lib/pages/sponsor_block/view.dart @@ -4,16 +4,15 @@ import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/sponsor_block.dart'; import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart'; import 'package:PiliPlus/models/common/sponsor_block/skip_type.dart'; +import 'package:PiliPlus/models_new/sponsor_block/user_info.dart'; import 'package:PiliPlus/pages/setting/slide_color_picker.dart'; -import 'package:PiliPlus/utils/duration_utils.dart'; -import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; -import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show FilteringTextInputFormatter; import 'package:get/get.dart'; @@ -39,7 +38,7 @@ class _SponsorBlockPageState extends State { String _blockServer = Pref.blockServer; bool _blockTrack = Pref.blockTrack; final _serverStatus = Rxn(); - final _userInfo = LoadingState<_UserInfo>.loading().obs; + final _userInfo = LoadingState.loading().obs; Box setting = GStorage.setting; @@ -56,29 +55,16 @@ class _SponsorBlockPageState extends State { super.dispose(); } - void _checkServerStatus() { - Request().get('$_blockServer/api/status/uptime').then((res) { - _serverStatus.value = - res.statusCode == 200 && - res.data is String && - Utils.isStringNumeric(res.data); - }); + Future _checkServerStatus() async { + _serverStatus.value = (await SponsorBlock.uptimeStatus()).isSuccess; } Future _getUserInfo() async { - final params = { - 'userID': _userId, - 'values': '["viewCount","minutesSaved","segmentCount"]', - }; - final res = await Request().get( - '$_blockServer/api/userInfo', - queryParameters: params, - ); - if (res.statusCode == 200) { - _userInfo.value = Success(_UserInfo.fromJson(res.data)); - } else { - _userInfo.value = Error(res.data['message']); - } + _userInfo.value = await SponsorBlock.userInfo(const [ + 'viewCount', + 'minutesSaved', + 'segmentCount', + ], userId: _userId); } Widget _blockLimitItem( @@ -309,7 +295,7 @@ class _SponsorBlockPageState extends State { ), subtitle: switch (_userInfo.value) { Loading() => const SizedBox.shrink(), - Success<_UserInfo>(:final response) => Text( + Success(:final response) => Text( response.toString(), style: subTitleStyle, ), @@ -657,34 +643,3 @@ class _SponsorBlockPageState extends State { ); } } - -class _UserInfo { - final int viewCount; - final double minutesSaved; - final int segmentCount; - - const _UserInfo({ - required this.viewCount, - required this.minutesSaved, - required this.segmentCount, - }); - - factory _UserInfo.fromJson(Map json) => _UserInfo( - viewCount: json['viewCount'], - minutesSaved: (json['minutesSaved'] as num).toDouble(), - segmentCount: json['segmentCount'], - ); - - @override - String toString() { - String minutes = DurationUtils.formatTimeDuration( - Duration(minutes: minutesSaved.round()), - ); - if (minutes.isEmpty) { - minutes = '0分钟'; - } - return ('您提交了 ${NumUtils.formatPositiveDecimal(segmentCount)} 片段\n' - '您为大家节省了 ${NumUtils.formatPositiveDecimal(viewCount)} 片段\n' - '($minutes 的生命)'); - } -} diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index a5a566d3b..c35e3e28e 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -10,6 +10,7 @@ import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/fav.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/sponsor_block.dart'; import 'package:PiliPlus/http/ua_type.dart'; import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/video.dart'; @@ -63,7 +64,6 @@ import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/video_utils.dart'; -import 'package:dio/dio.dart' show Options; import 'package:easy_debounce/easy_throttle.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/foundation.dart' show kDebugMode; @@ -492,25 +492,15 @@ class VideoDetailController extends GetxController plPlayerController.blockColor[segment.index]; late RxString videoLabel = ''.obs; - String get blockServer => plPlayerController.blockServer; - Timer? skipTimer; late final listKey = GlobalKey(); late final List listData = []; void _vote(String uuid, int type) { - Request() - .post( - '$blockServer/api/voteOnSponsorTime', - queryParameters: { - 'UUID': uuid, - 'userID': Pref.blockUserID, - 'type': type, - }, - ) - .then((res) { - SmartDialog.showToast(res.statusCode == 200 ? '投票成功' : '投票失败'); - }); + SponsorBlock.voteOnSponsorTime( + uuid: uuid, + type: type, + ).then((i) => SmartDialog.showToast(i.isSuccess ? '投票成功' : '投票失败: $i')); } void _showCategoryDialog(BuildContext context, SegmentModel segment) { @@ -528,20 +518,14 @@ class VideoDetailController extends GetxController dense: true, onTap: () { Get.back(); - Request() - .post( - '$blockServer/api/voteOnSponsorTime', - queryParameters: { - 'UUID': segment.UUID, - 'userID': Pref.blockUserID, - 'category': item.name, - }, - ) - .then((res) { - SmartDialog.showToast( - '类别更改${res.statusCode == 200 ? '成功' : '失败'}', - ); - }); + SponsorBlock.voteOnSponsorTime( + uuid: segment.UUID, + category: item, + ).then((i) { + SmartDialog.showToast( + '类别更改${i.isSuccess ? '成功' : '失败: $i'}', + ); + }); }, title: Text.rich( TextSpan( @@ -736,18 +720,19 @@ class VideoDetailController extends GetxController videoLabel.value = ''; segmentList.clear(); segmentProgressList.clear(); - final result = await Request().get( - '$blockServer/api/skipSegments', - queryParameters: { - 'videoID': bvid, - 'cid': cid.value, - }, - options: Options(validateStatus: (status) => true), + + final result = await SponsorBlock.getSkipSegments( + bvid: bvid, + cid: cid.value, ); - if (result.statusCode == 200) { - if (result.data case List list) { - handleSBData(list.map((e) => SegmentItemModel.fromJson(e)).toList()); - } + switch (result) { + case Success>(:final response): + handleSBData(response); + case Error(:final code) when code != 404: + if (kDebugMode) { + result.toast(); + } + default: } } @@ -1013,10 +998,7 @@ class VideoDetailController extends GetxController _showBlockToast('已跳过${item.segmentType.shortTitle}片段'); } if (_isBlock && Pref.blockTrack) { - Request().post( - '$blockServer/api/viewedVideoSponsorTime', - queryParameters: {'UUID': item.UUID}, - ); + SponsorBlock.viewedVideoSponsorTime(item.UUID); } } else { _showBlockToast('已跳至${item.segmentType.shortTitle}'); diff --git a/lib/pages/video/introduction/ugc/view.dart b/lib/pages/video/introduction/ugc/view.dart index 7f1901d94..cc8bffb13 100644 --- a/lib/pages/video/introduction/ugc/view.dart +++ b/lib/pages/video/introduction/ugc/view.dart @@ -1,8 +1,10 @@ import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; +import 'package:PiliPlus/http/sponsor_block.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models_new/video/video_detail/data.dart'; @@ -603,6 +605,11 @@ class _UgcIntroPanelState extends State { caseSensitive: false, ); + static final youtubeRegExp = RegExp( + r'(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-z0-9_\-]{11})', + caseSensitive: false, + ); + TextSpan buildContent(ThemeData theme, VideoDetailData content) { if (content.descV2.isNullOrEmpty) { return const TextSpan(); @@ -625,12 +632,57 @@ class _UgcIntroPanelState extends State { text: matchStr, style: TextStyle(color: theme.colorScheme.primary), recognizer: TapGestureRecognizer() - ..onTap = () { - try { - PageUtils.handleWebview(matchStr); - } catch (err) { - SmartDialog.showToast(err.toString()); + ..onTap = () async { + if (videoDetailCtr + .plPlayerController + .enableSponsorBlock) { + final duration = + videoDetailCtr.data.timeLength ?? + videoDetailCtr + .plPlayerController + .duration + .value + .inMilliseconds; + if (duration > 0) { + final ytbId = youtubeRegExp + .firstMatch(matchStr) + ?.group(1); + if (ytbId != null) { + final bvid = videoDetailCtr.bvid; + final cid = videoDetailCtr.cid.value; + + SmartDialog.showLoading(); + final hasPortVideo = + (await SponsorBlock.getPortVideo( + bvid: bvid, + cid: cid, + )).dataOrNull == + ytbId; + SmartDialog.dismiss(); + + if (!mounted) return; + final confirmed = await showConfirmDialog( + context: context, + title: '空降助手:搬运视频同步', + content: + '${hasPortVideo ? "" : "是否将"}该视频${hasPortVideo ? "已" : ""}绑定到此YouTube视频($ytbId)', + ); + if (!hasPortVideo && confirmed) { + final res = await SponsorBlock.postPortVideo( + bvid: bvid, + cid: cid, + ytbId: ytbId, + videoDuration: (duration / 1000).round(), + ); + SmartDialog.showToast( + '提交搬运视频${res.isSuccess ? "成功" : "失败: $res"}', + ); + return; + } + } + } } + PageUtils.handleWebview(matchStr); }, ), ); diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index e240f28ed..77eaebfd7 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -1,22 +1,19 @@ import 'dart:math'; -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; -import 'package:PiliPlus/http/init.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/sponsor_block.dart'; import 'package:PiliPlus/models/common/sponsor_block/action_type.dart'; import 'package:PiliPlus/models/common/sponsor_block/post_segment_model.dart'; import 'package:PiliPlus/models/common/sponsor_block/segment_type.dart'; -import 'package:PiliPlus/models_new/sponsor_block/segment_item.dart'; import 'package:PiliPlus/pages/common/slide/common_slide_page.dart'; import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/pages/video/post_panel/popup_menu_text.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/extension.dart'; -import 'package:PiliPlus/utils/storage_pref.dart'; -import 'package:dio/dio.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; @@ -310,69 +307,26 @@ class _PostPanelState extends State Future _onPost() async { Get.back(); - final res = await Request().post( - '${widget.videoDetailController.blockServer}/api/skipSegments', - data: { - 'videoID': videoDetailController.bvid, - 'cid': videoDetailController.cid.value.toString(), - 'userID': Pref.blockUserID.toString(), - 'userAgent': Constants.userAgent, - 'videoDuration': videoDuration, - 'segments': list - .map( - (item) => { - 'segment': [ - item.segment.first, - item.segment.second, - ], - 'category': item.category.name, - 'actionType': item.actionType.name, - }, - ) - .toList(), - }, - options: Options( - followRedirects: true, // Defaults to true. - validateStatus: (int? status) { - return (status! >= 200 && status < 300) || - const [400, 403, 429, 409] // reduce extra toast - .contains(status); - }, - ), + final res = await SponsorBlock.postSkipSegments( + bvid: videoDetailController.bvid, + cid: videoDetailController.cid.value, + videoDuration: videoDuration, + segments: list, ); - if (res.statusCode == 200) { + if (res case Success(:final response)) { Get.back(); SmartDialog.showToast('提交成功'); list.clear(); - if (res.data case List list) { - videoDetailController.handleSBData( - list.map((e) => SegmentItemModel.fromJson(e)).toList(), - ); - } + videoDetailController.handleSBData(response); if (videoDetailController.positionSubscription == null) { videoDetailController.initSkip(); } } else { - SmartDialog.showToast('提交失败: ${_errMsg(res)}'); + SmartDialog.showToast('提交失败: $res'); } } - String _errMsg(Response res) { - if (res.data case String e) { - if (e.isNotEmpty) { - return e; - } - } - return switch (res.statusCode) { - 400 => '参数错误', - 403 => '被自动审核机制拒绝', - 429 => '重复提交太快', - 409 => '重复提交', - _ => '${res.data}(${res.statusCode})', - }; - } - Widget _buildItem(ThemeData theme, int index, PostSegmentModel item) { return Stack( clipBehavior: Clip.none, diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 5987b13f1..9cd379384 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -407,7 +407,6 @@ class PlPlayerController { .where((item) => item.second != SkipType.disable) .map((item) => item.first.name) .toSet(); - late final blockServer = Pref.blockServer; // settings late final showFSActionItem = Pref.showFSActionItem;