mod: check reply manually

Closes #407

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-03-08 12:27:53 +08:00
parent 2949adbbfd
commit 0408b27ca5
9 changed files with 238 additions and 170 deletions

View File

@@ -313,6 +313,49 @@ abstract class ReplyController extends CommonController {
}
}
void onCheckReply(context, item) {
try {
if (item is ReplyInfo) {
checkReply(
context: context,
oid: item.oid.toInt(),
rpid: item.hasRoot() ? item.root.toInt() : null,
replyType: item.type.toInt(),
replyId: item.id.toInt(),
message: item.content.message,
//
root: item.root.toInt(),
parent: item.parent.toInt(),
ctime: item.ctime.toInt(),
pictures:
item.content.pictures.map((item) => item.toProto3Json()).toList(),
mid: item.mid.toInt(),
//
isManual: true,
);
} else if (item is ReplyItemModel) {
checkReply(
context: context,
oid: item.oid,
rpid: item.root == 0 ? null : item.root,
replyType: item.type!,
replyId: item.rpid!,
message: item.content!.message!,
//
root: item.root,
parent: item.parent,
ctime: item.ctime,
pictures: item.content?.pictures,
mid: item.mid,
//
isManual: true,
);
}
} catch (e) {
SmartDialog.showToast(e.toString());
}
}
// ref https://github.com/freedom-introvert/biliSendCommAntifraud
void checkReply({
required BuildContext context,
@@ -324,8 +367,9 @@ abstract class ReplyController extends CommonController {
dynamic root,
dynamic parent,
dynamic ctime,
dynamic pictures,
List? pictures,
dynamic mid,
bool isManual = false,
}) async {
// biliSendCommAntifraud
if (Platform.isAndroid && _biliSendCommAntifraud) {
@@ -346,7 +390,7 @@ abstract class ReplyController extends CommonController {
'parent': parent,
'ctime': ctime,
'comment_text': message,
if (pictures.isNotEmpty == true) 'pictures': jsonEncode(pictures),
if (pictures?.isNotEmpty == true) 'pictures': jsonEncode(pictures),
'source_id': '$sourceId',
'uid': mid,
'cookies': [cookieString],
@@ -359,7 +403,9 @@ abstract class ReplyController extends CommonController {
}
// CommAntifraud
await Future.delayed(const Duration(seconds: 5));
if (isManual.not) {
await Future.delayed(const Duration(seconds: 5));
}
void showReplyCheckResult(String message) {
showDialog(
context: context,
@@ -447,7 +493,9 @@ abstract class ReplyController extends CommonController {
} else if (res2 is Success) {
// found
if (context.mounted) {
showReplyCheckResult('''
showReplyCheckResult(isManual
? '无账号状态下找到了你的评论,评论正常!\n\n你的评论:$message'
: '''
你评论状态有点可疑,虽然无账号翻找评论区获取不到你的评论,但是无账号可通过
https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=${rpid ?? replyId}&type=$replyType
获取你的评论,疑似评论区被戒严或者这是你的视频。

View File

@@ -772,6 +772,8 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
_dynamicDetailController.hasUpTop && index == 0,
upMid: loadingState.response.subjectControl.upMid,
callback: _getImageCallback,
onCheckReply: (item) => _dynamicDetailController
.onCheckReply(context, item),
)
: ReplyItem(
replyItem: loadingState.response.replies[index],
@@ -789,6 +791,8 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
},
onDelete: _dynamicDetailController.onMDelete,
callback: _getImageCallback,
onCheckReply: (item) => _dynamicDetailController
.onCheckReply(context, item),
);
}
},

View File

@@ -755,6 +755,8 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
isTop: _htmlRenderCtr.hasUpTop && index == 0,
upMid: loadingState.response.subjectControl.upMid,
callback: _getImageCallback,
onCheckReply: (item) =>
_htmlRenderCtr.onCheckReply(context, item),
)
: ReplyItem(
replyItem: loadingState.response.replies[index],
@@ -772,6 +774,8 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
},
onDelete: _htmlRenderCtr.onMDelete,
callback: _getImageCallback,
onCheckReply: (item) =>
_htmlRenderCtr.onCheckReply(context, item),
);
}
},

View File

@@ -2037,10 +2037,7 @@ List<SettingsModel> get extraSettings => [
alignment: Alignment.center,
children: [
const Icon(Icons.shield_outlined),
Icon(
Icons.reply,
size: 14,
),
const Icon(Icons.reply, size: 14),
],
),
setKey: SettingBoxKey.enableCommAntifraud,

View File

@@ -261,6 +261,8 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed,
callback: widget.callback,
onCheckReply: (item) => _videoReplyController
.onCheckReply(context, item),
)
: ReplyItem(
replyItem: loadingState.response.replies[index],
@@ -280,6 +282,8 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
onDismissed: widget.onDismissed,
getTag: () => heroTag,
callback: widget.callback,
onCheckReply: (item) => _videoReplyController
.onCheckReply(context, item),
);
}
},

View File

@@ -41,6 +41,7 @@ class ReplyItem extends StatelessWidget {
this.onDismissed,
this.getTag,
this.callback,
required this.onCheckReply,
});
final ReplyItemModel replyItem;
final String? replyLevel;
@@ -54,6 +55,7 @@ class ReplyItem extends StatelessWidget {
final ValueChanged<int>? onDismissed;
final Function? getTag;
final Function(List<String>, int)? callback;
final ValueChanged<ReplyItemModel> onCheckReply;
@override
Widget build(BuildContext context) {
@@ -71,7 +73,8 @@ class ReplyItem extends StatelessWidget {
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(
return morePanel(
context: context,
item: replyItem,
onDelete: (rpid) {
onDelete?.call(rpid, null);
@@ -496,7 +499,8 @@ class ReplyItem extends StatelessWidget {
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(
return morePanel(
context: context,
item: replies[i],
onDelete: onDelete,
);
@@ -1024,119 +1028,115 @@ class ReplyItem extends StatelessWidget {
// spanChildren.add(TextSpan(text: matchMember));
return TextSpan(children: spanChildren);
}
}
class MorePanel extends StatelessWidget {
final ReplyItemModel item;
final Function(dynamic rpid)? onDelete;
const MorePanel({super.key, required this.item, this.onDelete});
Future<dynamic> menuActionHandler(String type) async {
String message = item.content?.message ?? '';
switch (type) {
case 'report':
Get.back();
autoWrapReportDialog(
Get.context!,
ReportOptions.commentReport,
(reasonType, reasonDesc, banUid) async {
final res = await Request().post(
'/x/v2/reply/report',
data: {
'add_blacklist': banUid,
'csrf': await Request.getCsrf(),
'gaia_source': 'main_h5',
'oid': item.oid,
'platform': 'android',
'reason': reasonType,
'rpid': item.rpid,
'scene': 'main',
'type': 1,
if (reasonType == 0) 'content': reasonDesc!
},
options: Options(contentType: Headers.formUrlEncodedContentType),
);
if (res.data['code'] == 0) {
onDelete?.call(item.rpid);
}
return res.data as Map;
},
);
break;
case 'copyAll':
Get.back();
Utils.copyText(message);
break;
case 'copyFreedom':
Get.back();
showDialog(
context: Get.context!,
builder: (context) {
return Dialog(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: SelectableText(message),
),
);
},
);
break;
// case 'block':
// SmartDialog.showToast('加入黑名单');
// break;
// case 'report':
// SmartDialog.showToast('举报');
// break;
case 'delete':
//弹出确认提示:
Get.back();
bool? isDelete = await showDialog<bool>(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('删除评论(测试)'),
content:
Text('确定尝试删除这条评论吗?\n\n$message\n\n注:只能删除自己的评论,或自己管理的评论区下的评论'),
actions: <Widget>[
TextButton(
onPressed: () {
Get.back(result: false);
},
child: const Text('取消'),
Widget morePanel({
required BuildContext context,
required ReplyItemModel item,
required onDelete,
}) {
Future<dynamic> menuActionHandler(String type) async {
late String message = item.content?.message ?? '';
switch (type) {
case 'report':
Get.back();
autoWrapReportDialog(
context,
ReportOptions.commentReport,
(reasonType, reasonDesc, banUid) async {
final res = await Request().post(
'/x/v2/reply/report',
data: {
'add_blacklist': banUid,
'csrf': await Request.getCsrf(),
'gaia_source': 'main_h5',
'oid': item.oid,
'platform': 'android',
'reason': reasonType,
'rpid': item.rpid,
'scene': 'main',
'type': 1,
if (reasonType == 0) 'content': reasonDesc!
},
options:
Options(contentType: Headers.formUrlEncodedContentType),
);
if (res.data['code'] == 0) {
onDelete?.call(item.rpid);
}
return res.data as Map;
},
);
break;
case 'copyAll':
Get.back();
Utils.copyText(message);
break;
case 'copyFreedom':
Get.back();
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: SelectableText(message),
),
TextButton(
onPressed: () {
Get.back(result: true);
},
child: const Text('确定'),
),
],
);
},
);
if (isDelete == null || !isDelete) {
return;
}
SmartDialog.showLoading(msg: '删除中...');
var result = await VideoHttp.replyDel(
type: item.type!, oid: item.oid!, rpid: item.rpid!);
SmartDialog.dismiss();
if (result['status']) {
SmartDialog.showToast('删除成功');
onDelete?.call(item.rpid!);
} else {
SmartDialog.showToast('删除失败, ${result["msg"]}');
}
break;
default:
);
},
);
break;
case 'delete':
Get.back();
bool? isDelete = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('删除评论(测试)'),
content: Text(
'确定尝试删除这条评论吗?\n\n$message\n\n注:只能删除自己的评论,或自己管理的评论区下的评论'),
actions: <Widget>[
TextButton(
onPressed: () {
Get.back(result: false);
},
child: const Text('取消'),
),
TextButton(
onPressed: () {
Get.back(result: true);
},
child: const Text('确定'),
),
],
);
},
);
if (isDelete == null || !isDelete) {
return;
}
SmartDialog.showLoading(msg: '删除中...');
var result = await VideoHttp.replyDel(
type: item.type!, oid: item.oid!, rpid: item.rpid!);
SmartDialog.dismiss();
if (result['status']) {
SmartDialog.showToast('删除成功');
onDelete?.call(item.rpid!);
} else {
SmartDialog.showToast('删除失败, ${result["msg"]}');
}
break;
case 'checkReply':
Get.back();
onCheckReply(item);
break;
default:
}
}
}
@override
Widget build(BuildContext context) {
dynamic ownerMid = GStorage.ownerMid;
Color errorColor = Theme.of(context).colorScheme.error;
return Padding(
padding: EdgeInsets.only(
bottom: MediaQueryData.fromView(
@@ -1148,7 +1148,7 @@ class MorePanel extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () => Get.back(),
onTap: Get.back,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28),
@@ -1167,10 +1167,9 @@ class MorePanel extends StatelessWidget {
),
),
),
// 已登录用户才显示删除
if (GStorage.userInfo.get('userInfoCache') != null) ...[
if (ownerMid != null) ...[
ListTile(
onTap: () async => await menuActionHandler('delete'),
onTap: () => menuActionHandler('delete'),
minLeadingWidth: 0,
leading: Icon(Icons.delete_outlined, color: errorColor, size: 19),
title: Text('删除',
@@ -1180,7 +1179,7 @@ class MorePanel extends StatelessWidget {
.copyWith(color: errorColor)),
),
ListTile(
onTap: () async => await menuActionHandler('report'),
onTap: () => menuActionHandler('report'),
minLeadingWidth: 0,
leading: Icon(Icons.error_outline, color: errorColor, size: 19),
title: Text('举报',
@@ -1191,30 +1190,31 @@ class MorePanel extends StatelessWidget {
),
],
ListTile(
onTap: () async => await menuActionHandler('copyAll'),
onTap: () => menuActionHandler('copyAll'),
minLeadingWidth: 0,
leading: const Icon(Icons.copy_all_outlined, size: 19),
title: Text('复制全部', style: Theme.of(context).textTheme.titleSmall),
),
ListTile(
onTap: () async => await menuActionHandler('copyFreedom'),
onTap: () => menuActionHandler('copyFreedom'),
minLeadingWidth: 0,
leading: const Icon(Icons.copy_outlined, size: 19),
title: Text('自由复制', style: Theme.of(context).textTheme.titleSmall),
),
// ListTile(
// onTap: () async => await menuActionHandler('block'),
// minLeadingWidth: 0,
// leading: Icon(Icons.block_outlined, color: errorColor),
// title: Text('加入黑名单', style: TextStyle(color: errorColor)),
// ),
// ListTile(
// onTap: () async => await menuActionHandler('report'),
// minLeadingWidth: 0,
// leading: Icon(Icons.report_outlined, color: errorColor),
// title: Text('举报', style: TextStyle(color: errorColor)),
// ),
if (item.mid == ownerMid)
ListTile(
onTap: () => menuActionHandler('checkReply'),
minLeadingWidth: 0,
leading: Stack(
alignment: Alignment.center,
children: [
const Icon(Icons.shield_outlined, size: 19),
const Icon(Icons.reply, size: 12),
],
),
title:
Text('检查评论', style: Theme.of(context).textTheme.titleSmall),
),
],
),
);

View File

@@ -44,6 +44,7 @@ class ReplyItemGrpc extends StatelessWidget {
this.onViewImage,
this.onDismissed,
this.callback,
required this.onCheckReply,
});
final ReplyInfo replyItem;
final String? replyLevel;
@@ -60,6 +61,7 @@ class ReplyItemGrpc extends StatelessWidget {
final VoidCallback? onViewImage;
final ValueChanged<int>? onDismissed;
final Function(List<String>, int)? callback;
final ValueChanged<ReplyInfo> onCheckReply;
@override
Widget build(BuildContext context) {
@@ -74,7 +76,7 @@ class ReplyItemGrpc extends StatelessWidget {
onLongPress: () {
feedBack();
// showDialog(
// context: Get.context!,
// context: context,
// builder: (context) => AlertDialog(
// content: SelectableText(jsonEncode(replyItem.toProto3Json())),
// ),
@@ -1073,7 +1075,7 @@ class ReplyItemGrpc extends StatelessWidget {
required onDelete,
}) {
Future<dynamic> menuActionHandler(String type) async {
String message = item.content.message;
late String message = item.content.message;
switch (type) {
case 'report':
Get.back();
@@ -1112,7 +1114,7 @@ class ReplyItemGrpc extends StatelessWidget {
case 'copyFreedom':
Get.back();
showDialog(
context: Get.context!,
context: context,
builder: (context) {
return Dialog(
child: Padding(
@@ -1124,17 +1126,10 @@ class ReplyItemGrpc extends StatelessWidget {
},
);
break;
// case 'block':
// SmartDialog.showToast('加入黑名单');
// break;
// case 'report':
// SmartDialog.showToast('举报');
// break;
case 'delete':
//弹出确认提示:
Get.back();
bool? isDelete = await showDialog<bool>(
context: Get.context!,
context: context,
builder: (context) {
return AlertDialog(
title: const Text('删除评论(测试)'),
@@ -1174,11 +1169,17 @@ class ReplyItemGrpc extends StatelessWidget {
SmartDialog.showToast('删除失败, ${result["msg"]}');
}
break;
case 'checkReply':
Get.back();
onCheckReply(item);
break;
default:
}
}
dynamic ownerMid = GStorage.ownerMid;
Color errorColor = Theme.of(context).colorScheme.error;
return Padding(
padding: EdgeInsets.only(
bottom: MediaQueryData.fromView(
@@ -1190,7 +1191,7 @@ class ReplyItemGrpc extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () => Get.back(),
onTap: Get.back,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(28),
topRight: Radius.circular(28),
@@ -1209,10 +1210,9 @@ class ReplyItemGrpc extends StatelessWidget {
),
),
),
// 已登录用户才显示删除
if (GStorage.userInfo.get('userInfoCache') != null) ...[
if (ownerMid != null) ...[
ListTile(
onTap: () async => await menuActionHandler('delete'),
onTap: () => menuActionHandler('delete'),
minLeadingWidth: 0,
leading: Icon(Icons.delete_outlined, color: errorColor, size: 19),
title: Text('删除',
@@ -1222,7 +1222,7 @@ class ReplyItemGrpc extends StatelessWidget {
.copyWith(color: errorColor)),
),
ListTile(
onTap: () async => await menuActionHandler('report'),
onTap: () => menuActionHandler('report'),
minLeadingWidth: 0,
leading: Icon(Icons.error_outline, color: errorColor, size: 19),
title: Text('举报',
@@ -1233,30 +1233,31 @@ class ReplyItemGrpc extends StatelessWidget {
),
],
ListTile(
onTap: () async => await menuActionHandler('copyAll'),
onTap: () => menuActionHandler('copyAll'),
minLeadingWidth: 0,
leading: const Icon(Icons.copy_all_outlined, size: 19),
title: Text('复制全部', style: Theme.of(context).textTheme.titleSmall),
),
ListTile(
onTap: () async => await menuActionHandler('copyFreedom'),
onTap: () => menuActionHandler('copyFreedom'),
minLeadingWidth: 0,
leading: const Icon(Icons.copy_outlined, size: 19),
title: Text('自由复制', style: Theme.of(context).textTheme.titleSmall),
),
// ListTile(
// onTap: () async => await menuActionHandler('block'),
// minLeadingWidth: 0,
// leading: Icon(Icons.block_outlined, color: errorColor),
// title: Text('加入黑名单', style: TextStyle(color: errorColor)),
// ),
// ListTile(
// onTap: () async => await menuActionHandler('report'),
// minLeadingWidth: 0,
// leading: Icon(Icons.report_outlined, color: errorColor),
// title: Text('举报', style: TextStyle(color: errorColor)),
// ),
if (item.mid.toInt() == ownerMid)
ListTile(
onTap: () => menuActionHandler('checkReply'),
minLeadingWidth: 0,
leading: Stack(
alignment: Alignment.center,
children: [
const Icon(Icons.shield_outlined, size: 19),
const Icon(Icons.reply, size: 12),
],
),
title:
Text('检查评论', style: Theme.of(context).textTheme.titleSmall),
),
],
),
);

View File

@@ -197,6 +197,9 @@ class _VideoReplyReplyPanelState
onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed,
callback: _getImageCallback,
onCheckReply: (item) =>
_videoReplyReplyController.onCheckReply(
context, item),
)
: ReplyItem(
replyItem: firstFloor,
@@ -210,6 +213,9 @@ class _VideoReplyReplyPanelState
onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed,
callback: _getImageCallback,
onCheckReply: (item) =>
_videoReplyReplyController.onCheckReply(
context, item),
);
} else if (index == 1) {
return Divider(
@@ -523,6 +529,8 @@ class _VideoReplyReplyPanelState
onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed,
callback: _getImageCallback,
onCheckReply: (item) =>
_videoReplyReplyController.onCheckReply(context, item),
)
: ReplyItem(
replyItem: replyItem,
@@ -544,6 +552,8 @@ class _VideoReplyReplyPanelState
onViewImage: widget.onViewImage,
onDismissed: widget.onDismissed,
callback: _getImageCallback,
onCheckReply: (item) =>
_videoReplyReplyController.onCheckReply(context, item),
);
}

View File

@@ -35,7 +35,7 @@ class GStorage {
static bool get isLogin => userInfo.get('userInfoCache') != null;
static get ownerMid => GStorage.userInfo.get('userInfoCache')?.mid;
static get ownerMid => userInfo.get('userInfoCache')?.mid;
static List<double> get speedList => List<double>.from(
video.get(