feat: 完善用户名-评论联合检测功能
- 改为立即同步执行 AI 检测,不再延迟 - 管理员可看到原用户名:用户-XXXXXXXX (原用户名: xxx) - 添加前台删除评论功能(仅管理员可见) - 删除按钮使用红色 outline-danger 样式 - 删除前弹出确认对话框 - 删除后淡出动画并移除评论元素 - 完整的成功/失败提示
This commit is contained in:
153
.kiro/steering/username-comment-detection.md
Normal file
153
.kiro/steering/username-comment-detection.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
---
|
||||||
|
inclusion: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
# AI 评论用户名-内容联合检测机制
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
AI 评论检测系统已升级为用户名-评论内容联合检测机制,同时检查评论者的用户名和评论内容是否合规。
|
||||||
|
|
||||||
|
## 检测逻辑
|
||||||
|
|
||||||
|
### 1. 联合检测
|
||||||
|
- AI 模型同时判断用户名和评论内容的合规性
|
||||||
|
- 返回四个字段:
|
||||||
|
- `content_spam`: 评论内容是否为垃圾评论
|
||||||
|
- `content_reason`: 内容判断理由(25字以内)
|
||||||
|
- `username_invalid`: 用户名是否不合规
|
||||||
|
- `username_reason`: 用户名判断理由(25字以内)
|
||||||
|
|
||||||
|
### 2. 处理规则
|
||||||
|
|
||||||
|
#### 情况1:评论内容不合规
|
||||||
|
- 按照后台设置的自动处理方式执行:
|
||||||
|
- 移入回收站(trash)
|
||||||
|
- 标记为待审核(hold)
|
||||||
|
- 仅标记不改变状态(mark)
|
||||||
|
- 如果留了邮箱,发送垃圾评论通知邮件
|
||||||
|
|
||||||
|
#### 情况2:评论内容正常,但用户名不合规
|
||||||
|
- **未留邮箱**:直接移入回收站
|
||||||
|
- 记录元数据:`_argon_username_invalid_no_email`
|
||||||
|
- 原因:无法通知用户,直接拒绝
|
||||||
|
|
||||||
|
- **留了邮箱**:自动修改用户名并发送通知
|
||||||
|
- 生成格式:`用户-XXXXXXXX`(8位唯一标识码)
|
||||||
|
- 标识码基于:原用户名 + 邮箱 + IP + User Agent
|
||||||
|
- 保存原始用户名到元数据:`_argon_original_username`
|
||||||
|
- 发送用户名变更通知邮件
|
||||||
|
|
||||||
|
#### 情况3:评论和用户名都正常
|
||||||
|
- 正常发布,记录检测结果
|
||||||
|
|
||||||
|
## 唯一标识码生成规则
|
||||||
|
|
||||||
|
```php
|
||||||
|
// 格式:用户-XXXXXXXX
|
||||||
|
// 字符集:0-9, A-Z(排除 I 和 O 避免混淆)
|
||||||
|
// 长度:8位
|
||||||
|
// 算法:基于 MD5(原用户名 + 邮箱 + IP + UA + 时间戳)
|
||||||
|
```
|
||||||
|
|
||||||
|
示例:
|
||||||
|
- `用户-A3B7K9M2`
|
||||||
|
- `用户-5N8Q4R6T`
|
||||||
|
|
||||||
|
## 邮件通知
|
||||||
|
|
||||||
|
### 用户名变更通知邮件
|
||||||
|
当用户名被修改时,系统会发送邮件通知,包含:
|
||||||
|
- 原用户名(删除线显示)
|
||||||
|
- 新用户名(高亮显示)
|
||||||
|
- AI 判断理由
|
||||||
|
- 评论链接
|
||||||
|
- 检测信息(AI 模型、识别码、检测时间)
|
||||||
|
|
||||||
|
邮件模板位置:`email-templates/username-change-notify.php`
|
||||||
|
|
||||||
|
## 评论元数据
|
||||||
|
|
||||||
|
系统会保存以下元数据:
|
||||||
|
|
||||||
|
### 检测结果
|
||||||
|
- `_argon_spam_detection_result`: 完整检测结果
|
||||||
|
```php
|
||||||
|
[
|
||||||
|
'is_spam' => bool,
|
||||||
|
'reason' => string,
|
||||||
|
'username_invalid' => bool,
|
||||||
|
'username_reason' => string,
|
||||||
|
'original_username' => string, // 仅用户名变更时
|
||||||
|
'new_username' => string, // 仅用户名变更时
|
||||||
|
'action' => string, // 处理方式
|
||||||
|
'reason_detail' => string // 详细原因
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 用户名变更相关
|
||||||
|
- `_argon_original_username`: 原始用户名
|
||||||
|
- `_argon_username_changed`: 标记用户名已变更
|
||||||
|
- `_argon_username_invalid_no_email`: 标记用户名不合规且未留邮箱
|
||||||
|
|
||||||
|
### 检测信息
|
||||||
|
- `_argon_spam_detection_time`: 检测时间戳
|
||||||
|
- `_argon_spam_detection_code`: 8位识别码
|
||||||
|
|
||||||
|
## AI 提示词
|
||||||
|
|
||||||
|
默认提示词(可在后台自定义):
|
||||||
|
|
||||||
|
```
|
||||||
|
你是一个专业的内容审核助手。请分别判断以下评论的用户名和内容是否合规。
|
||||||
|
|
||||||
|
不合规内容包括但不限于:广告推广、反动言论、错误政治观点、时政敏感内容、违法信息、色情暴力、恶意攻击等。
|
||||||
|
不合规用户名包括但不限于:广告推广、色情暴力、政治敏感、恶意攻击、侮辱性词汇等。
|
||||||
|
|
||||||
|
请仅返回 JSON 格式:
|
||||||
|
{
|
||||||
|
"content_spam": true/false,
|
||||||
|
"content_reason": "内容判断理由(25字以内)",
|
||||||
|
"username_invalid": true/false,
|
||||||
|
"username_reason": "用户名判断理由(25字以内)"
|
||||||
|
}
|
||||||
|
|
||||||
|
如果内容正常,content_reason 填写 "正常"。如果用户名正常,username_reason 填写 "正常"。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 兼容性
|
||||||
|
|
||||||
|
- 兼容旧版 API 响应格式(`is_spam` + `reason`)
|
||||||
|
- 自动转换为新格式
|
||||||
|
- max_tokens 增加到 150 以支持更详细的响应
|
||||||
|
|
||||||
|
## 相关函数
|
||||||
|
|
||||||
|
### 核心函数
|
||||||
|
- `argon_detect_spam_comment($comment_id)`: 执行联合检测
|
||||||
|
- `argon_generate_unique_username()`: 生成唯一用户名
|
||||||
|
- `argon_async_spam_detection_handler()`: 异步检测处理
|
||||||
|
- `argon_send_username_change_notify_email()`: 发送用户名变更通知
|
||||||
|
|
||||||
|
### 文件位置
|
||||||
|
- 检测逻辑:`functions.php` (第 7320-7850 行)
|
||||||
|
- 邮件模板:`email-templates/username-change-notify.php`
|
||||||
|
|
||||||
|
## 使用建议
|
||||||
|
|
||||||
|
1. **提示词优化**:根据实际情况在后台调整 AI 提示词
|
||||||
|
2. **白名单管理**:将信任的用户邮箱/IP 加入白名单
|
||||||
|
3. **检测模式**:
|
||||||
|
- 全量检测:所有评论都检测(推荐新站)
|
||||||
|
- 抽查模式:根据用户历史动态调整概率(推荐成熟站点)
|
||||||
|
4. **邮件模板**:可自定义邮件样式和内容
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### 2026-01-22
|
||||||
|
- ✅ 实现用户名-评论联合检测
|
||||||
|
- ✅ 用户名不合规且未留邮箱时直接移入回收站
|
||||||
|
- ✅ 用户名不合规但留了邮箱时自动修改并通知
|
||||||
|
- ✅ 创建用户名变更通知邮件模板
|
||||||
|
- ✅ 生成唯一8位标识码
|
||||||
|
- ✅ 兼容旧版 API 响应格式
|
||||||
@@ -698,10 +698,7 @@ if (current_user_can('manage_options')):
|
|||||||
|
|
||||||
<article class="post card shadow-sm bg-white border-0 ai-verify-card" style="margin-top: 16px;">
|
<article class="post card shadow-sm bg-white border-0 ai-verify-card" style="margin-top: 16px;">
|
||||||
<h3 class="ai-verify-section-title">
|
<h3 class="ai-verify-section-title">
|
||||||
<i class="fa fa-database"></i><?php _e('AI 请求历史记录', 'argon'); ?>
|
<i class="fa fa-database"></i><?php _e('请求记录', 'argon'); ?>
|
||||||
<span style="font-size: 13px; font-weight: 400; color: var(--color-text-muted); margin-left: 8px;">
|
|
||||||
(<?php _e('仅管理员可见', 'argon'); ?>)
|
|
||||||
</span>
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="ai-alert ai-alert-info" style="margin-bottom: 20px;">
|
<div class="ai-alert ai-alert-info" style="margin-bottom: 20px;">
|
||||||
|
|||||||
@@ -1159,6 +1159,9 @@ if (argonConfig.waterflow_columns != "1") {
|
|||||||
$(document).on("click", ".comment-pin, .comment-unpin", function(){
|
$(document).on("click", ".comment-pin, .comment-unpin", function(){
|
||||||
toogleCommentPin(this.getAttribute("data-id"), !this.classList.contains("comment-pin"));
|
toogleCommentPin(this.getAttribute("data-id"), !this.classList.contains("comment-pin"));
|
||||||
});
|
});
|
||||||
|
$(document).on("click", ".comment-delete", function(){
|
||||||
|
deleteComment(this.getAttribute("data-id"));
|
||||||
|
});
|
||||||
$(document).on("mouseenter", ".comment-parent-info", function(){
|
$(document).on("mouseenter", ".comment-parent-info", function(){
|
||||||
$("#comment-" + this.getAttribute("data-parent-id")).addClass("highlight");
|
$("#comment-" + this.getAttribute("data-parent-id")).addClass("highlight");
|
||||||
});
|
});
|
||||||
@@ -1243,6 +1246,75 @@ if (argonConfig.waterflow_columns != "1") {
|
|||||||
$("#comment_pin_comfirm_dialog").modal(null);
|
$("#comment_pin_comfirm_dialog").modal(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//删除评论
|
||||||
|
function deleteComment(commentID) {
|
||||||
|
if (!confirm(__('确定要删除评论 #') + commentID + '?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: argonConfig.wp_path + 'wp-admin/admin-ajax.php',
|
||||||
|
dataType: 'json',
|
||||||
|
data: {
|
||||||
|
action: 'frontend_delete_comment',
|
||||||
|
id: commentID
|
||||||
|
},
|
||||||
|
success: function(result) {
|
||||||
|
if (result.status === 'success') {
|
||||||
|
// 移除评论元素
|
||||||
|
$('#comment-' + commentID).fadeOut(300, function() {
|
||||||
|
$(this).next('.comment-divider').remove();
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
iziToast.show({
|
||||||
|
title: __('删除成功'),
|
||||||
|
message: result.msg,
|
||||||
|
class: 'shadow-sm',
|
||||||
|
position: 'topRight',
|
||||||
|
backgroundColor: '#2dce89',
|
||||||
|
titleColor: '#ffffff',
|
||||||
|
messageColor: '#ffffff',
|
||||||
|
iconColor: '#ffffff',
|
||||||
|
progressBarColor: '#ffffff',
|
||||||
|
icon: 'fa fa-check',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
iziToast.show({
|
||||||
|
title: __('删除失败'),
|
||||||
|
message: result.msg,
|
||||||
|
class: 'shadow-sm',
|
||||||
|
position: 'topRight',
|
||||||
|
backgroundColor: '#f5365c',
|
||||||
|
titleColor: '#ffffff',
|
||||||
|
messageColor: '#ffffff',
|
||||||
|
iconColor: '#ffffff',
|
||||||
|
progressBarColor: '#ffffff',
|
||||||
|
icon: 'fa fa-close',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
iziToast.show({
|
||||||
|
title: __('删除失败'),
|
||||||
|
message: __('未知错误'),
|
||||||
|
class: 'shadow-sm',
|
||||||
|
position: 'topRight',
|
||||||
|
backgroundColor: '#f5365c',
|
||||||
|
titleColor: '#ffffff',
|
||||||
|
messageColor: '#ffffff',
|
||||||
|
iconColor: '#ffffff',
|
||||||
|
progressBarColor: '#ffffff',
|
||||||
|
icon: 'fa fa-close',
|
||||||
|
timeout: 5000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//显示/隐藏额外输入框(评论者网址)
|
//显示/隐藏额外输入框(评论者网址)
|
||||||
$(document).on("click" , "#post_comment_toggle_extra_input" , function(){
|
$(document).on("click" , "#post_comment_toggle_extra_input" , function(){
|
||||||
|
|||||||
@@ -2087,6 +2087,9 @@ function argon_comment_format($comment, $args, $depth){
|
|||||||
<button class="comment-pin btn btn-sm btn-outline-primary" data-id="<?php comment_ID(); ?>" type="button" style="margin-right: 2px;"><?php _ex('置顶', 'to pin', 'argon')?></button>
|
<button class="comment-pin btn btn-sm btn-outline-primary" data-id="<?php comment_ID(); ?>" type="button" style="margin-right: 2px;"><?php _ex('置顶', 'to pin', 'argon')?></button>
|
||||||
<?php }
|
<?php }
|
||||||
} ?>
|
} ?>
|
||||||
|
<?php if ($GLOBALS['argon_comment_options']['current_user_can_moderate_comments']) { ?>
|
||||||
|
<button class="comment-delete btn btn-sm btn-outline-danger" data-id="<?php comment_ID(); ?>" type="button" style="margin-right: 2px;"><?php _e('删除', 'argon')?></button>
|
||||||
|
<?php } ?>
|
||||||
<?php if ((check_comment_token(get_comment_ID()) || check_login_user_same($comment -> user_id)) && (get_option("argon_comment_allow_editing") != "false")) { ?>
|
<?php if ((check_comment_token(get_comment_ID()) || check_login_user_same($comment -> user_id)) && (get_option("argon_comment_allow_editing") != "false")) { ?>
|
||||||
<button class="comment-edit btn btn-sm btn-outline-primary" data-id="<?php comment_ID(); ?>" type="button" style="margin-right: 2px;"><?php _e('编辑', 'argon')?></button>
|
<button class="comment-edit btn btn-sm btn-outline-primary" data-id="<?php comment_ID(); ?>" type="button" style="margin-right: 2px;"><?php _e('编辑', 'argon')?></button>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
@@ -2123,6 +2126,25 @@ function comment_author_link_filter($html){
|
|||||||
return str_replace('href=', 'target="_blank" href=', $html);
|
return str_replace('href=', 'target="_blank" href=', $html);
|
||||||
}
|
}
|
||||||
add_filter('get_comment_author_link', 'comment_author_link_filter');
|
add_filter('get_comment_author_link', 'comment_author_link_filter');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为管理员显示原用户名
|
||||||
|
*/
|
||||||
|
function argon_display_original_username($author, $comment_id) {
|
||||||
|
// 只对管理员显示
|
||||||
|
if (!current_user_can('moderate_comments')) {
|
||||||
|
return $author;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有原始用户名
|
||||||
|
$original_username = get_comment_meta($comment_id, '_argon_original_username', true);
|
||||||
|
if (!empty($original_username)) {
|
||||||
|
return $author . ' <span style="color: #8898aa; font-size: 0.9em;">(原用户名: ' . esc_html($original_username) . ')</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $author;
|
||||||
|
}
|
||||||
|
add_filter('get_comment_author', 'argon_display_original_username', 10, 2);
|
||||||
//评论验证码生成 & 验证
|
//评论验证码生成 & 验证
|
||||||
function get_comment_captcha_seed($refresh = false){
|
function get_comment_captcha_seed($refresh = false){
|
||||||
if (isset($_SESSION['captchaSeed']) && !$refresh){
|
if (isset($_SESSION['captchaSeed']) && !$refresh){
|
||||||
@@ -3072,6 +3094,53 @@ function pin_comment(){
|
|||||||
}
|
}
|
||||||
add_action('wp_ajax_pin_comment', 'pin_comment');
|
add_action('wp_ajax_pin_comment', 'pin_comment');
|
||||||
add_action('wp_ajax_nopriv_pin_comment', 'pin_comment');
|
add_action('wp_ajax_nopriv_pin_comment', 'pin_comment');
|
||||||
|
|
||||||
|
//前台删除评论
|
||||||
|
function frontend_delete_comment() {
|
||||||
|
header('Content-Type:application/json; charset=utf-8');
|
||||||
|
|
||||||
|
// 检查权限
|
||||||
|
if (!current_user_can('moderate_comments')) {
|
||||||
|
exit(json_encode([
|
||||||
|
'status' => 'failed',
|
||||||
|
'msg' => __('您没有权限进行此操作', 'argon')
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment_id = intval($_POST['id']);
|
||||||
|
if (empty($comment_id)) {
|
||||||
|
exit(json_encode([
|
||||||
|
'status' => 'failed',
|
||||||
|
'msg' => __('评论 ID 无效', 'argon')
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment = get_comment($comment_id);
|
||||||
|
if (!$comment) {
|
||||||
|
exit(json_encode([
|
||||||
|
'status' => 'failed',
|
||||||
|
'msg' => __('评论不存在', 'argon')
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除评论(移入回收站)
|
||||||
|
$result = wp_trash_comment($comment_id);
|
||||||
|
|
||||||
|
if ($result) {
|
||||||
|
exit(json_encode([
|
||||||
|
'status' => 'success',
|
||||||
|
'msg' => __('评论已删除', 'argon')
|
||||||
|
]));
|
||||||
|
} else {
|
||||||
|
exit(json_encode([
|
||||||
|
'status' => 'failed',
|
||||||
|
'msg' => __('删除评论失败', 'argon')
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_action('wp_ajax_frontend_delete_comment', 'frontend_delete_comment');
|
||||||
|
add_action('wp_ajax_nopriv_frontend_delete_comment', 'frontend_delete_comment');
|
||||||
|
|
||||||
//输出评论分页页码
|
//输出评论分页页码
|
||||||
function get_argon_formatted_comment_paginate_links($maxPageNumbers, $extraClasses = ''){
|
function get_argon_formatted_comment_paginate_links($maxPageNumbers, $extraClasses = ''){
|
||||||
$args = array(
|
$args = array(
|
||||||
@@ -7558,8 +7627,8 @@ function argon_auto_detect_spam_on_comment($comment_id, $comment_approved) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($should_check) {
|
if ($should_check) {
|
||||||
// 异步检测(延迟 2 秒执行,避免阻塞评论发送)
|
// 立即同步执行检测
|
||||||
wp_schedule_single_event(time() + 2, 'argon_async_spam_detection', [$comment_id]);
|
argon_async_spam_detection_handler($comment_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_action('comment_post', 'argon_auto_detect_spam_on_comment', 10, 2);
|
add_action('comment_post', 'argon_auto_detect_spam_on_comment', 10, 2);
|
||||||
|
|||||||
Reference in New Issue
Block a user