Repository: sealdice/sealdice-core (language: go)

Issue:
[Bug]: 敏感词检测拦截输入在私聊无效
### 在提问之前...

- [X] 我理解 Issue 是用于反馈和解决问题的，而非吐槽评论区，将尽可能提供更多信息帮助问题解决
- [X] 我填写了简短且清晰明确的标题，以便开发者在翻阅 issue 列表时能快速确定大致问题。而不是“一个建议”、“卡住了”等
- [X] 我已查看master branch或最新测试版的更新内容，并未提及该 bug 已被修复的情况
- [X] 已有issue中并没有看见其他人与我反馈相同的问题

### 问题描述

[Bug]: 敏感词检测拦截输入在私聊无效

### 如何复现

[Bug]: 敏感词检测拦截输入在私聊无效

### 你期望发生的

和群聊一样

### 实际上发生的

[Bug]: 敏感词检测拦截输入在私聊无效

### 日志文件

[Bug]: 敏感词检测拦截输入在私聊无效

### 截图

_No response_

### 海豹核心版本

1.4.0

### 操作系统

windows

### 帐号类型

qq

### 使用协议

_No response_

### 附加内容

_No response_
因为敏感词拦截误触发产生的一些需求
### 在提问之前...

- [X] 我填写了简短且清晰明确的标题，以便开发者在翻阅 issue 列表时能快速确定大致问题。而不是“一个建议”、“卡住了”等
- [X] 我基本确定这是一个新功能/建议，而不是遇到了 bug（不确定的话请附上日志）

### 说说你遇到的问题？

敏感词拦截误触发时，骰主难以确实具体是触发了哪一个违禁词

### 有什么好的想法？

1.在后台显示触发违禁词
2.拦截无视CQ码
3.导入的时候忽略空行

### 其他内容

_No response_


Discussion / hints:

在后台新增显示触发的违禁词，而不只是一整句话

Fix patch:
diff --git a/dice/dice_censor.go b/dice/dice_censor.go
index 959272da..ea8114ce 100644
--- a/dice/dice_censor.go
+++ b/dice/dice_censor.go
@@ -8,6 +8,7 @@ import (
 	"sealdice-core/dice/censor"
 	"sealdice-core/dice/model"
 	"sort"
+	"strings"
 
 	"github.com/jmoiron/sqlx"
 )
@@ -74,11 +75,11 @@ func (cm *CensorManager) Load(d *Dice) {
 	cm.IsLoading = false
 }
 
-func (cm *CensorManager) Check(ctx *MsgContext, msg *Message) (*MsgCheckResult, error) {
+func (cm *CensorManager) Check(ctx *MsgContext, msg *Message, checkContent string) (*MsgCheckResult, error) {
 	if cm.IsLoading {
 		return nil, fmt.Errorf("censor is loading")
 	}
-	res := cm.Censor.Check(msg.Message)
+	res := cm.Censor.Check(checkContent)
 	if !ctx.Censored && res.HighestLevel > censor.Ignore {
 		// 敏感词命中记录保存
 		model.CensorAppend(cm.DB, ctx.MessageType, msg.Sender.UserId, msg.GroupId, msg.Message, res.SensitiveWords, int(res.HighestLevel))
@@ -104,9 +105,9 @@ type MsgCheckResult struct {
 	CurSensitiveWords []string
 }
 
-func (d *Dice) CensorMsg(mctx *MsgContext, msg *Message, sendContent string) (hit bool, needToTerminate bool, newContent string) {
+func (d *Dice) CensorMsg(mctx *MsgContext, msg *Message, checkContent string, sendContent string) (hit bool, needToTerminate bool, newContent string) {
 	log := d.Logger
-	checkResult, err := d.CensorManager.Check(mctx, msg)
+	checkResult, err := d.CensorManager.Check(mctx, msg, checkContent)
 	if err != nil {
 		// FIXME: 尽管这种情况比较少，但是是否要提供一个配置项，用来控制默认是跳过还是拦截吗？
 		log.Warnf("拦截系统出错(%s)，来自<%s>(%s)的消息跳过了检查", err.Error(), msg.Sender.Nickname, msg.Sender.UserId)
@@ -147,7 +148,7 @@ func (d *Dice) CensorMsg(mctx *MsgContext, msg *Message, sendContent string) (hi
 					levelText := censor.LevelText[level]
 					if handler&(1<<SendWarning) != 0 {
 						tmplText := fmt.Sprintf("核心:拦截_警告内容_%s级", censor.LevelText[level])
-						ReplyToSender(mctx, msg, DiceFormatTmpl(mctx, tmplText))
+						ReplyToSenderNoCheck(mctx, msg, DiceFormatTmpl(mctx, tmplText))
 					}
 					if handler&(1<<SendNotice) != 0 {
 						// 向通知列表/邮件发送通知
@@ -221,7 +222,14 @@ func (d *Dice) CensorMsg(mctx *MsgContext, msg *Message, sendContent string) (hi
 						}
 					}
 					// 只处理一次
-					d.Logger.Infof("<%s>(%s)发送的「%s」触发<%s>级敏感词，触发次数已经超过阈值，进行处理", msg.Sender.Nickname, msg.Sender.UserId, msg.Message, censor.LevelText[level])
+					d.Logger.Infof(
+						"<%s>(%s)发送的「%s」触发最高<%s>级敏感词（%s），触发次数已经超过阈值，进行处理",
+						msg.Sender.Nickname,
+						msg.Sender.UserId,
+						msg.Message,
+						censor.LevelText[level],
+						strings.Join(checkResult.CurSensitiveWords, "|"),
+					)
 					break
 				}
 			}
diff --git a/dice/im_helpers.go b/dice/im_helpers.go
index bdd1c685..d88eb27e 100644
--- a/dice/im_helpers.go
+++ b/dice/im_helpers.go
@@ -10,6 +10,9 @@ import (
 	"github.com/fy0/lockfree"
 )
 
+var sealCodeRe = regexp.MustCompile(`\[(img|图|文本|text|语音|voice|视频|video):(.+?)]`)
+var cqCodeRe = regexp.MustCompile(`\[CQ:.+?]`)
+
 func IsCurGroupBotOnById(session *IMSession, ep *EndPointInfo, messageType string, groupId string) bool {
 	a := messageType == "group" &&
 		session.ServiceAtNew[groupId] != nil
@@ -138,10 +141,23 @@ func ReplyToSenderRaw(ctx *MsgContext, msg *Message, text string, flag string) {
 	}
 }
 
+func replyToSenderRawNoCheck(ctx *MsgContext, msg *Message, text string, flag string) {
+	inGroup := msg.MessageType == "group"
+	if inGroup {
+		replyGroupRawNoCheck(ctx, msg, text, flag)
+	} else {
+		replyPersonRawNoCheck(ctx, msg, text, flag)
+	}
+}
+
 func ReplyToSender(ctx *MsgContext, msg *Message, text string) {
 	go ReplyToSenderRaw(ctx, msg, text, "")
 }
 
+func ReplyToSenderNoCheck(ctx *MsgContext, msg *Message, text string) {
+	go replyToSenderRawNoCheck(ctx, msg, text, "")
+}
+
 func ReplyGroupRaw(ctx *MsgContext, msg *Message, text string, flag string) {
 	if ctx.DelegateText != "" {
 		text = ctx.DelegateText + text
@@ -152,7 +168,11 @@ func ReplyGroupRaw(ctx *MsgContext, msg *Message, text string, flag string) {
 		d.Logger.Infof("发给(群%s): %s", msg.GroupId, text)
 		// 敏感词拦截：回复（群）
 		if d.EnableCensor && d.CensorMode == OnlyOutputReply {
-			hit, needToTerminate, _ := d.CensorMsg(ctx, msg, text)
+			// 先拿掉海豹码和CQ码再检查敏感词
+			checkText := sealCodeRe.ReplaceAllString(text, "")
+			checkText = cqCodeRe.ReplaceAllString(checkText, "")
+
+			hit, needToTerminate, _ := d.CensorMsg(ctx, msg, checkText, text)
 			if needToTerminate {
 				return
 			}
@@ -162,6 +182,14 @@ func ReplyGroupRaw(ctx *MsgContext, msg *Message, text string, flag string) {
 			}
 		}
 	}
+	replyGroupRawNoCheck(ctx, msg, text, flag)
+}
+
+func replyGroupRawNoCheck(ctx *MsgContext, msg *Message, text string, flag string) {
+	if ctx.DelegateText != "" {
+		text = ctx.DelegateText + text
+		ctx.DelegateText = ""
+	}
 	if lenWithoutBase64(text) > 15000 {
 		text = "要发送的文本过长"
 	}
@@ -194,7 +222,11 @@ func ReplyPersonRaw(ctx *MsgContext, msg *Message, text string, flag string) {
 		d.Logger.Infof("发给(帐号%s): %s", msg.Sender.UserId, text)
 		// 敏感词拦截：回复（个人）
 		if d.EnableCensor && d.CensorMode == OnlyOutputReply {
-			hit, needToTerminate, _ := d.CensorMsg(ctx, msg, text)
+			// 先拿掉海豹码和CQ码再检查敏感词
+			checkText := sealCodeRe.ReplaceAllString(text, "")
+			checkText = cqCodeRe.ReplaceAllString(checkText, "")
+
+			hit, needToTerminate, _ := d.CensorMsg(ctx, msg, checkText, text)
 			if needToTerminate {
 				return
 			}
@@ -204,7 +236,14 @@ func ReplyPersonRaw(ctx *MsgContext, msg *Message, text string, flag string) {
 			}
 		}
 	}
+	replyPersonRawNoCheck(ctx, msg, text, flag)
+}
 
+func replyPersonRawNoCheck(ctx *MsgContext, msg *Message, text string, flag string) {
+	if ctx.DelegateText != "" {
+		text = ctx.DelegateText + text
+		ctx.DelegateText = ""
+	}
 	if lenWithoutBase64(text) > 15000 {
 		text = "要发送的文本过长"
 	}
diff --git a/dice/im_session.go b/dice/im_session.go
index 66243248..c73e5507 100644
--- a/dice/im_session.go
+++ b/dice/im_session.go
@@ -664,7 +664,7 @@ func (s *IMSession) Execute(ep *EndPointInfo, msg *Message, runInSync bool) {
 
 		// 敏感词拦截：全部输入
 		if mctx.IsCurGroupBotOn && d.EnableCensor && d.CensorMode == AllInput {
-			hit, needToTerminate, _ := d.CensorMsg(mctx, msg, "")
+			hit, needToTerminate, _ := d.CensorMsg(mctx, msg, msg.Message, "")
 			if needToTerminate {
 				return
 			}
@@ -704,8 +704,8 @@ func (s *IMSession) Execute(ep *EndPointInfo, msg *Message, runInSync bool) {
 				}()
 
 				// 敏感词拦截：命令输入
-				if mctx.IsCurGroupBotOn && d.EnableCensor && d.CensorMode == OnlyInputCommand {
-					hit, needToTerminate, _ := d.CensorMsg(mctx, msg, "")
+				if (msg.MessageType == "private" || mctx.IsCurGroupBotOn) && d.EnableCensor && d.CensorMode == OnlyInputCommand {
+					hit, needToTerminate, _ := d.CensorMsg(mctx, msg, msg.Message, "")
 					if needToTerminate {
 						return
 					}


Test patch: