From 957c326148a3872926e24018b9b6d23b4c9c87a0 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Mon, 17 Feb 2025 18:49:16 +0800 Subject: [PATCH] feat: anti goods reply Closes #276 Signed-off-by: bggRGjQaUbCoE --- lib/common/constants.dart | 2 + lib/http/reply.dart | 107 ++++++++++++++++++ lib/pages/common/reply_controller.dart | 6 + lib/pages/dynamics/detail/controller.dart | 2 + lib/pages/html/controller.dart | 2 + lib/pages/setting/widgets/model.dart | 13 +++ lib/pages/video/detail/reply/controller.dart | 2 + .../video/detail/reply_reply/controller.dart | 3 + lib/utils/storage.dart | 4 + 9 files changed, 141 insertions(+) diff --git a/lib/common/constants.dart b/lib/common/constants.dart index 53e988332..fb0817294 100644 --- a/lib/common/constants.dart +++ b/lib/common/constants.dart @@ -29,6 +29,8 @@ class Constants { static const urlPattern = r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]'; + static const goodsUrlPrefix = "https://gaoneng.bilibili.com/tetris"; + // 超分辨率滤镜 static const List mpvAnime4KShaders = [ 'Anime4K_Clamp_Highlights.glsl', diff --git a/lib/http/reply.dart b/lib/http/reply.dart index 77673cef1..61356147a 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -1,6 +1,8 @@ +import 'package:PiliPlus/common/constants.dart'; 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/models/video/reply/item.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:dio/dio.dart'; @@ -20,6 +22,7 @@ class ReplyHttp { required int page, int sort = 1, required String banWordForReply, + required bool antiGoodsReply, }) async { var res = !isLogin ? await Request().get( @@ -81,6 +84,37 @@ class ReplyHttp { }); } } + + // antiGoodsReply + if (antiGoodsReply) { + // topReplies + if (replyData.topReplies?.isNotEmpty == true) { + replyData.topReplies!.removeWhere((item) { + bool hasMatch = needRemove(item); + // remove subreplies + if (hasMatch.not) { + if (item.replies?.isNotEmpty == true) { + item.replies!.removeWhere(needRemove); + } + } + return hasMatch; + }); + } + + // replies + if (replyData.replies?.isNotEmpty == true) { + replyData.replies!.removeWhere((item) { + bool hasMatch = needRemove(item); + // remove subreplies + if (hasMatch.not) { + if (item.replies?.isNotEmpty == true) { + item.replies!.removeWhere(needRemove); + } + } + return hasMatch; + }); + } + } return LoadingState.success(replyData); } else { return LoadingState.error(res.data['message']); @@ -92,10 +126,12 @@ class ReplyHttp { required int oid, required CursorReq cursor, required String banWordForReply, + required bool antiGoodsReply, }) async { dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor); if (res['status']) { MainListReply mainListReply = res['data']; + // keyword filter if (banWordForReply.isNotEmpty) { // upTop if (mainListReply.hasUpTop() && @@ -121,12 +157,65 @@ class ReplyHttp { }); } } + + // antiGoodsReply + if (antiGoodsReply) { + // upTop + if (mainListReply.hasUpTop() && needRemoveGrpc(mainListReply.upTop)) { + mainListReply.clearUpTop(); + } + + // replies + if (mainListReply.replies.isNotEmpty) { + mainListReply.replies.removeWhere((item) { + bool hasMatch = needRemoveGrpc(item); + // remove subreplies + if (hasMatch.not) { + if (item.replies.isNotEmpty) { + item.replies.removeWhere(needRemoveGrpc); + } + } + return hasMatch; + }); + } + } return LoadingState.success(mainListReply); } else { return LoadingState.error(res['msg']); } } + // ref BiliRoamingX + static bool needRemoveGrpc(ReplyInfo reply) { + if ((reply.content.url.isNotEmpty && + reply.content.url.values.any((url) { + return url.hasExtra() && + (url.extra.goodsCmControl == 1 || + url.extra.goodsItemId != 0 || + url.extra.goodsPrefetchedCache.isNotEmpty); + })) || + reply.content.message.contains(Constants.goodsUrlPrefix)) { + return true; + } + return false; + } + + static bool needRemove(ReplyItemModel reply) { + try { + if ((reply.content?.jumpUrl?.isNotEmpty == true && + reply.content!.jumpUrl!.values.any((url) { + return url['extra'] != null && + (url['extra']['goods_cm_control'] == 1 || + url['extra']['goods_item_id'] != 0 || + url['extra']['goods_prefetched_cache'].isNotEmpty); + })) || + reply.content?.message?.contains(Constants.goodsUrlPrefix) == true) { + return true; + } + } catch (_) {} + return false; + } + static Future replyReplyList({ required bool isLogin, required int oid, @@ -134,6 +223,7 @@ class ReplyHttp { required int pageNum, required int type, required String banWordForReply, + required bool antiGoodsReply, bool? isCheck, }) async { var res = await Request().get( @@ -157,6 +247,11 @@ class ReplyHttp { .hasMatch(item.content?.message ?? '')); } } + if (antiGoodsReply) { + if (replyData.replies?.isNotEmpty == true) { + replyData.replies!.removeWhere(needRemove); + } + } return LoadingState.success(replyData); } else { return LoadingState.error( @@ -174,6 +269,7 @@ class ReplyHttp { required int rpid, required CursorReq cursor, required String banWordForReply, + required bool antiGoodsReply, }) async { dynamic res = await GrpcRepo.detailList( type: type, @@ -191,6 +287,11 @@ class ReplyHttp { .hasMatch(item.content.message)); } } + if (antiGoodsReply) { + if (detailListReply.root.replies.isNotEmpty) { + detailListReply.root.replies.removeWhere(needRemoveGrpc); + } + } return LoadingState.success(detailListReply); } else { return LoadingState.error(res['msg']); @@ -204,6 +305,7 @@ class ReplyHttp { required int rpid, required CursorReq cursor, required String banWordForReply, + required bool antiGoodsReply, }) async { dynamic res = await GrpcRepo.dialogList( type: type, @@ -221,6 +323,11 @@ class ReplyHttp { .hasMatch(item.content.message)); } } + if (antiGoodsReply) { + if (dialogListReply.replies.isNotEmpty) { + dialogListReply.replies.removeWhere(needRemoveGrpc); + } + } return LoadingState.success(dialogListReply); } else { return LoadingState.error(res['msg']); diff --git a/lib/pages/common/reply_controller.dart b/lib/pages/common/reply_controller.dart index a206f031e..df8819a67 100644 --- a/lib/pages/common/reply_controller.dart +++ b/lib/pages/common/reply_controller.dart @@ -34,6 +34,7 @@ abstract class ReplyController extends CommonController { late final banWordForReply = GStorage.banWordForReply; late final enableCommAntifraud = GStorage.enableCommAntifraud; + late final antiGoodsReply = GStorage.antiGoodsReply; @override void onInit() { @@ -321,6 +322,7 @@ abstract class ReplyController extends CommonController { sort: ReplySortType.time.index, page: 1, banWordForReply: '', + antiGoodsReply: false, ); if (context.mounted.not) return; if (res is Error) { @@ -348,6 +350,7 @@ abstract class ReplyController extends CommonController { pageNum: 1, type: replyType, banWordForReply: '', + antiGoodsReply: false, ); if (context.mounted.not) return; if (res1 is Error) { @@ -369,6 +372,7 @@ abstract class ReplyController extends CommonController { type: replyType, banWordForReply: '', isCheck: true, + antiGoodsReply: false, ); if (context.mounted.not) return; if (res2 is Error) { @@ -405,6 +409,7 @@ https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=${rpid ?? rep type: replyType, banWordForReply: '', isCheck: true, + antiGoodsReply: false, ); if (res3 is Error) { break; @@ -439,6 +444,7 @@ https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=${rpid ?? rep type: replyType, banWordForReply: '', isCheck: true, + antiGoodsReply: false, ); if (res4 is Error) { break; diff --git a/lib/pages/dynamics/detail/controller.dart b/lib/pages/dynamics/detail/controller.dart index bbbb5c563..552b553f1 100644 --- a/lib/pages/dynamics/detail/controller.dart +++ b/lib/pages/dynamics/detail/controller.dart @@ -49,6 +49,7 @@ class DynamicDetailController extends ReplyController { mode: mode.value, ), banWordForReply: banWordForReply, + antiGoodsReply: antiGoodsReply, ) : ReplyHttp.replyList( isLogin: isLogin, @@ -58,5 +59,6 @@ class DynamicDetailController extends ReplyController { sort: sortType.value.index, page: currentPage, banWordForReply: banWordForReply, + antiGoodsReply: antiGoodsReply, ); } diff --git a/lib/pages/html/controller.dart b/lib/pages/html/controller.dart index 0d6479a90..2fa24bafa 100644 --- a/lib/pages/html/controller.dart +++ b/lib/pages/html/controller.dart @@ -83,6 +83,7 @@ class HtmlRenderController extends ReplyController { mode: mode.value, ), banWordForReply: banWordForReply, + antiGoodsReply: antiGoodsReply, ) : ReplyHttp.replyList( isLogin: isLogin, @@ -92,5 +93,6 @@ class HtmlRenderController extends ReplyController { sort: sortType.value.index, page: currentPage, banWordForReply: banWordForReply, + antiGoodsReply: antiGoodsReply, ); } diff --git a/lib/pages/setting/widgets/model.dart b/lib/pages/setting/widgets/model.dart index 35dc9aa9c..216753586 100644 --- a/lib/pages/setting/widgets/model.dart +++ b/lib/pages/setting/widgets/model.dart @@ -1980,6 +1980,19 @@ List get extraSettings => [ setKey: SettingBoxKey.antiGoodsDyn, defaultVal: false, ), + SettingsModel( + settingsType: SettingsType.sw1tch, + title: '屏蔽带货评论', + leading: Stack( + alignment: Alignment.center, + children: [ + Icon(Icons.shopping_bag_outlined, size: 14), + Icon(Icons.not_interested), + ], + ), + setKey: SettingBoxKey.antiGoodsReply, + defaultVal: false, + ), SettingsModel( settingsType: SettingsType.sw1tch, enableFeedback: true, diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index eea97bf62..2fa746713 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -28,6 +28,7 @@ class VideoReplyController extends ReplyController { mode: mode.value, ), banWordForReply: banWordForReply, + antiGoodsReply: antiGoodsReply, ) : ReplyHttp.replyList( isLogin: isLogin, @@ -37,5 +38,6 @@ class VideoReplyController extends ReplyController { sort: sortType.value.index, page: currentPage, banWordForReply: banWordForReply, + antiGoodsReply: antiGoodsReply, ); } diff --git a/lib/pages/video/detail/reply_reply/controller.dart b/lib/pages/video/detail/reply_reply/controller.dart index 688c49dbb..d665572cf 100644 --- a/lib/pages/video/detail/reply_reply/controller.dart +++ b/lib/pages/video/detail/reply_reply/controller.dart @@ -185,6 +185,7 @@ class VideoReplyReplyController extends ReplyController mode: mode.value, ), banWordForReply: banWordForReply, + antiGoodsReply: antiGoodsReply, ) : GlobalData().grpcReply ? ReplyHttp.replyReplyListGrpc( @@ -197,6 +198,7 @@ class VideoReplyReplyController extends ReplyController mode: mode.value, ), banWordForReply: banWordForReply, + antiGoodsReply: antiGoodsReply, ) : ReplyHttp.replyReplyList( isLogin: isLogin, @@ -205,6 +207,7 @@ class VideoReplyReplyController extends ReplyController pageNum: currentPage, type: replyType.index, banWordForReply: banWordForReply, + antiGoodsReply: antiGoodsReply, ); @override diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index ee2dc9ea1..09802a92a 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -375,6 +375,9 @@ class GStorage { static bool get antiGoodsDyn => GStorage.setting.get(SettingBoxKey.antiGoodsDyn, defaultValue: false); + static bool get antiGoodsReply => + GStorage.setting.get(SettingBoxKey.antiGoodsReply, defaultValue: false); + static List get dynamicDetailRatio => List.from(setting .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); @@ -609,6 +612,7 @@ class SettingBoxKey { coinWithLike = 'coinWithLike', isPureBlackTheme = 'isPureBlackTheme', antiGoodsDyn = 'antiGoodsDyn', + antiGoodsReply = 'antiGoodsReply', // Sponsor Block enableSponsorBlock = 'enableSponsorBlock',