feat: filter reply

Closes #118

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-01-08 19:28:12 +08:00
parent 847f42fee3
commit ae16771b5e
5 changed files with 191 additions and 34 deletions

View File

@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart'; import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
@@ -49,7 +50,44 @@ class ReplyHttp {
options: options, options: options,
); );
if (res.data['code'] == 0) { 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 { } else {
return LoadingState.error(res.data['message']); return LoadingState.error(res.data['message']);
} }
@@ -62,7 +100,33 @@ class ReplyHttp {
}) async { }) async {
dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor); dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor);
if (res['status']) { 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 { } else {
return LoadingState.error( return LoadingState.error(
'${res['msg'].startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}${res['msg']}'); '${res['msg'].startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}${res['msg']}');
@@ -93,33 +157,21 @@ class ReplyHttp {
options: options, options: options,
); );
if (res.data['code'] == 0) { 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 { } else {
return LoadingState.error(res.data['message']); return LoadingState.error(res.data['message']);
} }
} }
static Future<LoadingState> 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<LoadingState> replyReplyListGrpc({ static Future<LoadingState> replyReplyListGrpc({
int type = 1, int type = 1,
required int oid, required int oid,
@@ -135,7 +187,46 @@ class ReplyHttp {
cursor: cursor, cursor: cursor,
); );
if (res['status']) { 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<LoadingState> 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 { } else {
return LoadingState.error(res['msg']); return LoadingState.error(res['msg']);
} }

View File

@@ -86,7 +86,8 @@ class PlDanmakuController {
} }
break; break;
case 1: case 1:
if (RegExp(filter['filter']).hasMatch(elem.content)) { if (RegExp(filter['filter'], caseSensitive: false)
.hasMatch(elem.content)) {
return false; return false;
} }
break; break;

View File

@@ -1155,8 +1155,7 @@ List<SettingsModel> get recommendSettings => [
return banWordForRecommend.isEmpty ? "点击添加" : banWordForRecommend; return banWordForRecommend.isEmpty ? "点击添加" : banWordForRecommend;
}, },
onTap: (setState) async { onTap: (setState) async {
final TextEditingController textController = String banWordForRecommend = GStorage.banWordForRecommend;
TextEditingController(text: GStorage.banWordForRecommend);
await showDialog( await showDialog(
context: Get.context!, context: Get.context!,
builder: (context) { builder: (context) {
@@ -1170,16 +1169,17 @@ List<SettingsModel> get recommendSettings => [
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text('使用|隔开,如:尝试|测试'), const Text('使用|隔开,如:尝试|测试'),
TextField( TextFormField(
autofocus: true, autofocus: true,
controller: textController, initialValue: banWordForRecommend,
textInputAction: TextInputAction.newline, textInputAction: TextInputAction.newline,
minLines: 1, minLines: 1,
maxLines: 4, maxLines: 4,
onChanged: (value) => banWordForRecommend = value,
) )
], ],
), ),
actions: <Widget>[ actions: [
TextButton( TextButton(
onPressed: Get.back, onPressed: Get.back,
child: Text( child: Text(
@@ -1192,9 +1192,9 @@ List<SettingsModel> get recommendSettings => [
child: const Text('保存'), child: const Text('保存'),
onPressed: () async { onPressed: () async {
Get.back(); Get.back();
GStorage.setting.put( await GStorage.setting.put(
SettingBoxKey.banWordForRecommend, SettingBoxKey.banWordForRecommend,
textController.text, banWordForRecommend,
); );
setState(); setState();
RecommendFilter.update(); RecommendFilter.update();
@@ -1662,6 +1662,66 @@ List<SettingsModel> get extraSettings => [
setKey: SettingBoxKey.horizontalPreview, setKey: SettingBoxKey.horizontalPreview,
defaultVal: false, 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( SettingsModel(
settingsType: SettingsType.sw1tch, settingsType: SettingsType.sw1tch,
enableFeedback: true, enableFeedback: true,

View File

@@ -67,7 +67,8 @@ class RecommendFilter {
if (exemptFilterForFollowed && isFollowed == true) { if (exemptFilterForFollowed && isFollowed == true) {
return false; return false;
} }
if (banWords.isNotEmpty && RegExp(banWords).hasMatch(title)) { if (banWords.isNotEmpty &&
RegExp(banWords, caseSensitive: false).hasMatch(title)) {
return true; return true;
} }
return false; return false;

View File

@@ -180,6 +180,9 @@ class GStorage {
static String get banWordForRecommend => static String get banWordForRecommend =>
setting.get(SettingBoxKey.banWordForRecommend, defaultValue: ''); setting.get(SettingBoxKey.banWordForRecommend, defaultValue: '');
static String get banWordForReply =>
setting.get(SettingBoxKey.banWordForReply, defaultValue: '');
static int get minLikeRatioForRecommend => static int get minLikeRatioForRecommend =>
setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0); setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0);
@@ -517,6 +520,7 @@ class SettingBoxKey {
continuePlayingPart = 'continuePlayingPart', continuePlayingPart = 'continuePlayingPart',
cdnSpeedTest = 'cdnSpeedTest', cdnSpeedTest = 'cdnSpeedTest',
horizontalPreview = 'horizontalPreview', horizontalPreview = 'horizontalPreview',
banWordForReply = 'banWordForReply',
// Sponsor Block // Sponsor Block
enableSponsorBlock = 'enableSponsorBlock', enableSponsorBlock = 'enableSponsorBlock',