diff --git a/lib/http/reply.dart b/lib/http/reply.dart index 9dc2b63f9..ba0c9b32d 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:dio/dio.dart'; @@ -49,7 +50,44 @@ class ReplyHttp { options: options, ); if (res.data['code'] == 0) { - return LoadingState.success(ReplyData.fromJson(res.data['data'])); + ReplyData replyData = ReplyData.fromJson(res.data['data']); + String banWordForReply = GStorage.banWordForReply; + if (banWordForReply.isNotEmpty) { + // topReplies + if (replyData.topReplies?.isNotEmpty == true) { + replyData.topReplies!.removeWhere((item) { + bool hasMatch = RegExp(banWordForReply, caseSensitive: false) + .hasMatch(item.content?.message ?? ''); + // remove subreplies + if (hasMatch.not) { + if (item.replies?.isNotEmpty == true) { + item.replies!.removeWhere((item) => + RegExp(banWordForReply, caseSensitive: false) + .hasMatch(item.content?.message ?? '')); + } + } + return hasMatch; + }); + } + + // replies + if (replyData.replies?.isNotEmpty == true) { + replyData.replies!.removeWhere((item) { + bool hasMatch = RegExp(banWordForReply, caseSensitive: false) + .hasMatch(item.content?.message ?? ''); + // remove subreplies + if (hasMatch.not) { + if (item.replies?.isNotEmpty == true) { + item.replies!.removeWhere((item) => + RegExp(banWordForReply, caseSensitive: false) + .hasMatch(item.content?.message ?? '')); + } + } + return hasMatch; + }); + } + } + return LoadingState.success(replyData); } else { return LoadingState.error(res.data['message']); } @@ -62,7 +100,33 @@ class ReplyHttp { }) async { dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor); if (res['status']) { - return LoadingState.success(res['data']); + MainListReply mainListReply = res['data']; + String banWordForReply = GStorage.banWordForReply; + if (banWordForReply.isNotEmpty) { + // upTop + if (mainListReply.hasUpTop() && + RegExp(banWordForReply, caseSensitive: false) + .hasMatch(mainListReply.upTop.content.message)) { + mainListReply.clearUpTop(); + } + } + // replies + if (mainListReply.replies.isNotEmpty) { + mainListReply.replies.removeWhere((item) { + bool hasMatch = RegExp(banWordForReply, caseSensitive: false) + .hasMatch(item.content.message); + // remove subreplies + if (hasMatch.not) { + if (item.replies.isNotEmpty) { + item.replies.removeWhere((item) => + RegExp(banWordForReply, caseSensitive: false) + .hasMatch(item.content.message)); + } + } + return hasMatch; + }); + } + return LoadingState.success(mainListReply); } else { return LoadingState.error( '${res['msg'].startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}${res['msg']}'); @@ -93,33 +157,21 @@ class ReplyHttp { options: options, ); if (res.data['code'] == 0) { - return LoadingState.success(ReplyReplyData.fromJson(res.data['data'])); + ReplyReplyData replyData = ReplyReplyData.fromJson(res.data['data']); + String banWordForReply = GStorage.banWordForReply; + if (banWordForReply.isNotEmpty) { + if (replyData.replies?.isNotEmpty == true) { + replyData.replies!.removeWhere((item) => + RegExp(banWordForReply, caseSensitive: false) + .hasMatch(item.content?.message ?? '')); + } + } + return LoadingState.success(replyData); } else { return LoadingState.error(res.data['message']); } } - static Future dialogListGrpc({ - int type = 1, - required int oid, - required int root, - required int rpid, - required CursorReq cursor, - }) async { - dynamic res = await GrpcRepo.dialogList( - type: type, - oid: oid, - root: root, - rpid: rpid, - cursor: cursor, - ); - if (res['status']) { - return LoadingState.success(res['data']); - } else { - return LoadingState.error(res['msg']); - } - } - static Future replyReplyListGrpc({ int type = 1, required int oid, @@ -135,7 +187,46 @@ class ReplyHttp { cursor: cursor, ); if (res['status']) { - return LoadingState.success(res['data']); + DetailListReply detailListReply = res['data']; + String banWordForReply = GStorage.banWordForReply; + if (banWordForReply.isNotEmpty) { + if (detailListReply.root.replies.isNotEmpty) { + detailListReply.root.replies.removeWhere((item) => + RegExp(banWordForReply, caseSensitive: false) + .hasMatch(item.content.message)); + } + } + return LoadingState.success(detailListReply); + } else { + return LoadingState.error(res['msg']); + } + } + + static Future dialogListGrpc({ + int type = 1, + required int oid, + required int root, + required int rpid, + required CursorReq cursor, + }) async { + dynamic res = await GrpcRepo.dialogList( + type: type, + oid: oid, + root: root, + rpid: rpid, + cursor: cursor, + ); + if (res['status']) { + DialogListReply dialogListReply = res['data']; + String banWordForReply = GStorage.banWordForReply; + if (banWordForReply.isNotEmpty) { + if (dialogListReply.replies.isNotEmpty) { + dialogListReply.replies.removeWhere((item) => + RegExp(banWordForReply, caseSensitive: false) + .hasMatch(item.content.message)); + } + } + return LoadingState.success(dialogListReply); } else { return LoadingState.error(res['msg']); } diff --git a/lib/pages/danmaku/controller.dart b/lib/pages/danmaku/controller.dart index 3d489f69e..b70e7ea39 100644 --- a/lib/pages/danmaku/controller.dart +++ b/lib/pages/danmaku/controller.dart @@ -86,7 +86,8 @@ class PlDanmakuController { } break; case 1: - if (RegExp(filter['filter']).hasMatch(elem.content)) { + if (RegExp(filter['filter'], caseSensitive: false) + .hasMatch(elem.content)) { return false; } break; diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart index 720691bdd..ec5374a7d 100644 --- a/lib/pages/setting/widgets/model.dart +++ b/lib/pages/setting/widgets/model.dart @@ -1155,8 +1155,7 @@ List get recommendSettings => [ return banWordForRecommend.isEmpty ? "点击添加" : banWordForRecommend; }, onTap: (setState) async { - final TextEditingController textController = - TextEditingController(text: GStorage.banWordForRecommend); + String banWordForRecommend = GStorage.banWordForRecommend; await showDialog( context: Get.context!, builder: (context) { @@ -1170,16 +1169,17 @@ List get recommendSettings => [ crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('使用|隔开,如:尝试|测试'), - TextField( + TextFormField( autofocus: true, - controller: textController, + initialValue: banWordForRecommend, textInputAction: TextInputAction.newline, minLines: 1, maxLines: 4, + onChanged: (value) => banWordForRecommend = value, ) ], ), - actions: [ + actions: [ TextButton( onPressed: Get.back, child: Text( @@ -1192,9 +1192,9 @@ List get recommendSettings => [ child: const Text('保存'), onPressed: () async { Get.back(); - GStorage.setting.put( + await GStorage.setting.put( SettingBoxKey.banWordForRecommend, - textController.text, + banWordForRecommend, ); setState(); RecommendFilter.update(); @@ -1662,6 +1662,66 @@ List get extraSettings => [ setKey: SettingBoxKey.horizontalPreview, defaultVal: false, ), + SettingsModel( + settingsType: SettingsType.normal, + leading: const Icon(Icons.filter_alt_outlined), + title: '评论关键词过滤', + getSubtitle: () { + String banWordForReply = GStorage.banWordForReply; + return banWordForReply.isEmpty ? "点击添加" : banWordForReply; + }, + onTap: (setState) async { + String banWordForReply = GStorage.banWordForReply; + await showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text( + '评论关键词过滤', + style: TextStyle(fontSize: 18), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('使用|隔开,如:尝试|测试'), + TextFormField( + autofocus: true, + initialValue: banWordForReply, + textInputAction: TextInputAction.newline, + minLines: 1, + maxLines: 4, + onChanged: (value) => banWordForReply = value, + ) + ], + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + child: const Text('保存'), + onPressed: () async { + Get.back(); + await GStorage.setting.put( + SettingBoxKey.banWordForReply, + banWordForReply, + ); + setState(); + SmartDialog.showToast('已保存'); + }, + ), + ], + ); + }, + ); + }, + ), SettingsModel( settingsType: SettingsType.sw1tch, enableFeedback: true, diff --git a/lib/utils/recommend_filter.dart b/lib/utils/recommend_filter.dart index 017be0900..2ce2e5b54 100644 --- a/lib/utils/recommend_filter.dart +++ b/lib/utils/recommend_filter.dart @@ -67,7 +67,8 @@ class RecommendFilter { if (exemptFilterForFollowed && isFollowed == true) { return false; } - if (banWords.isNotEmpty && RegExp(banWords).hasMatch(title)) { + if (banWords.isNotEmpty && + RegExp(banWords, caseSensitive: false).hasMatch(title)) { return true; } return false; diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 9657771f7..3716bc4a4 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -180,6 +180,9 @@ class GStorage { static String get banWordForRecommend => setting.get(SettingBoxKey.banWordForRecommend, defaultValue: ''); + static String get banWordForReply => + setting.get(SettingBoxKey.banWordForReply, defaultValue: ''); + static int get minLikeRatioForRecommend => setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0); @@ -517,6 +520,7 @@ class SettingBoxKey { continuePlayingPart = 'continuePlayingPart', cdnSpeedTest = 'cdnSpeedTest', horizontalPreview = 'horizontalPreview', + banWordForReply = 'banWordForReply', // Sponsor Block enableSponsorBlock = 'enableSponsorBlock',