diff --git a/.kiro/steering/username-comment-detection.md b/.kiro/steering/username-comment-detection.md new file mode 100644 index 0000000..867d123 --- /dev/null +++ b/.kiro/steering/username-comment-detection.md @@ -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 响应格式 diff --git a/ai-summary-query.php b/ai-summary-query.php index 8b73879..c023fbd 100644 --- a/ai-summary-query.php +++ b/ai-summary-query.php @@ -698,10 +698,7 @@ if (current_user_can('manage_options')):

- - - () - +

diff --git a/argontheme.js b/argontheme.js index d771482..33466ab 100644 --- a/argontheme.js +++ b/argontheme.js @@ -1159,6 +1159,9 @@ if (argonConfig.waterflow_columns != "1") { $(document).on("click", ".comment-pin, .comment-unpin", function(){ 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(){ $("#comment-" + this.getAttribute("data-parent-id")).addClass("highlight"); }); @@ -1242,6 +1245,75 @@ if (argonConfig.waterflow_columns != "1") { }); $("#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 + }); + } + }); + } //显示/隐藏额外输入框(评论者网址) diff --git a/functions.php b/functions.php index 19ce684..4ac7cc7 100644 --- a/functions.php +++ b/functions.php @@ -2087,6 +2087,9 @@ function argon_comment_format($comment, $args, $depth){ + + + user_id)) && (get_option("argon_comment_allow_editing") != "false")) { ?> @@ -2123,6 +2126,25 @@ function comment_author_link_filter($html){ return str_replace('href=', 'target="_blank" href=', $html); } 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 . ' (原用户名: ' . esc_html($original_username) . ')'; + } + + return $author; +} +add_filter('get_comment_author', 'argon_display_original_username', 10, 2); //评论验证码生成 & 验证 function get_comment_captcha_seed($refresh = false){ if (isset($_SESSION['captchaSeed']) && !$refresh){ @@ -3072,6 +3094,53 @@ function pin_comment(){ } add_action('wp_ajax_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 = ''){ $args = array( @@ -7558,8 +7627,8 @@ function argon_auto_detect_spam_on_comment($comment_id, $comment_approved) { } 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);