From 7930408f7f6683ed3a167a99b78c689dac19c5d9 Mon Sep 17 00:00:00 2001 From: SanaeFox <36219542+Hoshinonyaruko@users.noreply.github.com> Date: Thu, 4 Apr 2024 11:06:34 +0800 Subject: [PATCH] Beta45 (#46) * beta1 * beta2 * beta3 * beta4 * beta5 * beta6 * beta7 * beta8 * beta9 * beta10 * beta11 * beta12 * beta13 * beta14 * beta15 * beta16 * beta16 * beta19 * beta20 * beta21 * beta22 * beta23 * beta24 * beta25 * beta27 * beta28 * beta29 * beta30 * beta31 * beta33 * beta34 * beta35 * beta36 * beta37 * beta38 * beta39 * beta40 * beta41 * beta42 * beta43 * beta44 * beta45 * beta45 --- applogic/gensokyo.go | 151 +++++-------------------- applogic/vectorsensitive.go | 2 +- config/config.go | 172 +++++++++++++++++++---------- go.mod | 2 + go.sum | 2 + readme.md | 12 +- template/config_template.go | 5 + utils/utils.go | 215 ++++++++++++++++++++++++++++++++++++ 8 files changed, 374 insertions(+), 187 deletions(-) diff --git a/applogic/gensokyo.go b/applogic/gensokyo.go index 24d2918..64ee051 100644 --- a/applogic/gensokyo.go +++ b/applogic/gensokyo.go @@ -202,7 +202,29 @@ func (app *App) GensokyoHandler(w http.ResponseWriter, r *http.Request) { lastSelectedVectorID int // 用于存储最后选取的相似文本的ID ) - //如果使用向量缓存 或者使用 向量安全词 + // 进行字数拦截 + if config.GetQuestionMaxLenth() != 0 { + if utils.LengthIntercept(newmsg, message) { + fmtf.Printf("字数过长,可在questionMaxLenth配置项修改,Q: %v", newmsg) + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("question too long")) + return + } + } + + // 进行语言判断拦截 + if len(config.GetAllowedLanguages()) > 0 { + if utils.LanguageIntercept(newmsg, message) { + fmtf.Printf("不安全!不支持的语言,可在config.yml设置允许的语言,allowedLanguages配置项,Q: %v", newmsg) + // 发送响应 + w.WriteHeader(http.StatusOK) + w.Write([]byte("language not support")) + return + } + } + + // 如果使用向量缓存 或者使用 向量安全词 if config.GetUseCache() || config.GetVectorSensitiveFilter() { // 计算文本向量 vector, err = app.CalculateTextEmbedding(newmsg) @@ -265,7 +287,7 @@ func (app *App) GensokyoHandler(w http.ResponseWriter, r *http.Request) { if !config.GetUsePrivateSSE() { utils.SendPrivateMessage(message.UserID, responseText) } else { - SendSSEPrivateMessage(message.UserID, responseText) + utils.SendSSEPrivateMessage(message.UserID, responseText) } } else { utils.SendGroupMessage(message.GroupID, responseText) @@ -310,7 +332,7 @@ func (app *App) GensokyoHandler(w http.ResponseWriter, r *http.Request) { if !config.GetUsePrivateSSE() { utils.SendPrivateMessage(message.UserID, saveresponse) } else { - SendSSEPrivateSafeMessage(message.UserID, saveresponse) + utils.SendSSEPrivateSafeMessage(message.UserID, saveresponse) } } else { utils.SendGroupMessage(message.GroupID, saveresponse) @@ -615,126 +637,3 @@ func processMessage(response string, msg structs.OnebotGroupMessage, newmesssage } } } - -// SendSSEPrivateMessage 分割并发送消息的核心逻辑,直接遍历字符串 -func SendSSEPrivateMessage(userID int64, content string) { - punctuations := []rune{'。', '!', '?', ',', ',', '.', '!', '?'} - splitProbability := config.GetSplitByPuntuations() - - var parts []string - var currentPart strings.Builder - - for _, runeValue := range content { - currentPart.WriteRune(runeValue) - if strings.ContainsRune(string(punctuations), runeValue) { - // 根据概率决定是否分割 - if rand.Intn(100) < splitProbability { - parts = append(parts, currentPart.String()) - currentPart.Reset() - } - } - } - // 添加最后一部分(如果有的话) - if currentPart.Len() > 0 { - parts = append(parts, currentPart.String()) - } - - // 根据parts长度处理状态 - for i, part := range parts { - state := 1 - if i == len(parts)-2 { // 倒数第二部分 - state = 11 - } else if i == len(parts)-1 { // 最后一部分 - state = 20 - } - - // 构造消息体并发送 - messageSSE := structs.InterfaceBody{ - Content: part, - State: state, - } - - if state == 20 { // 对最后一部分特殊处理 - RestoreResponses := config.GetRestoreCommand() - promptKeyboard := config.GetPromptkeyboard() - - if len(RestoreResponses) > 0 { - selectedRestoreResponse := RestoreResponses[rand.Intn(len(RestoreResponses))] - if len(promptKeyboard) > 0 { - promptKeyboard[0] = selectedRestoreResponse - } - } - - messageSSE.PromptKeyboard = promptKeyboard - } - - // 发送SSE消息函数 - utils.SendPrivateMessageSSE(userID, messageSSE) - } -} - -// SendSSEPrivateSafeMessage 分割并发送安全消息的核心逻辑,直接遍历字符串 -func SendSSEPrivateSafeMessage(userID int64, saveresponse string) { - // 将字符串转换为rune切片,以正确处理多字节字符 - runes := []rune(saveresponse) - - // 计算每部分应该包含的rune数量 - partLength := len(runes) / 3 - - // 初始化用于存储分割结果的切片 - parts := make([]string, 3) - - // 按字符分割字符串 - for i := 0; i < 3; i++ { - if i < 2 { // 前两部分 - start := i * partLength - end := start + partLength - parts[i] = string(runes[start:end]) - } else { // 最后一部分,包含所有剩余的字符 - start := i * partLength - parts[i] = string(runes[start:]) - } - } - // 开头 - messageSSE := structs.InterfaceBody{ - Content: parts[0], - State: 1, - } - - utils.SendPrivateMessageSSE(userID, messageSSE) - - // 中间 - messageSSE = structs.InterfaceBody{ - Content: parts[1], - State: 11, - } - utils.SendPrivateMessageSSE(userID, messageSSE) - - // 从配置中获取恢复响应数组 - RestoreResponses := config.GetRestoreCommand() - - var selectedRestoreResponse string - // 如果RestoreResponses至少有一个成员,则随机选择一个 - if len(RestoreResponses) > 0 { - selectedRestoreResponse = RestoreResponses[rand.Intn(len(RestoreResponses))] - } - - // 从配置中获取promptkeyboard - promptkeyboard := config.GetPromptkeyboard() - - // 确保promptkeyboard至少有一个成员 - if len(promptkeyboard) > 0 { - // 使用随机选中的RestoreResponse替换promptkeyboard的第一个成员 - promptkeyboard[0] = selectedRestoreResponse - } - - // 创建InterfaceBody结构体实例 - messageSSE = structs.InterfaceBody{ - Content: parts[2], // 假设空格字符串是期望的内容 - State: 20, // 假设的状态码 - PromptKeyboard: promptkeyboard, // 使用更新后的promptkeyboard - } - - // 发送SSE私人消息 - utils.SendPrivateMessageSSE(userID, messageSSE) -} diff --git a/applogic/vectorsensitive.go b/applogic/vectorsensitive.go index efa7bd2..b483f8a 100644 --- a/applogic/vectorsensitive.go +++ b/applogic/vectorsensitive.go @@ -203,7 +203,7 @@ func (app *App) InterceptSensitiveContent(vector []float64, message structs.Oneb if !config.GetUsePrivateSSE() { utils.SendPrivateMessage(message.UserID, saveresponse) } else { - SendSSEPrivateSafeMessage(message.UserID, saveresponse) + utils.SendSSEPrivateSafeMessage(message.UserID, saveresponse) } } else { utils.SendGroupMessage(message.GroupID, saveresponse) diff --git a/config/config.go b/config/config.go index 3b14155..b0a16c2 100644 --- a/config/config.go +++ b/config/config.go @@ -23,64 +23,68 @@ type Config struct { } type Settings struct { - SecretId string `yaml:"secretId"` - SecretKey string `yaml:"secretKey"` - Region string `yaml:"region"` - UseSse bool `yaml:"useSse"` - Port int `yaml:"port"` - HttpPath string `yaml:"path"` - SystemPrompt []string `yaml:"systemPrompt"` - IPWhiteList []string `yaml:"iPWhiteList"` - MaxTokensHunyuan int `yaml:"maxTokensHunyuan"` - ApiType int `yaml:"apiType"` - WenxinAccessToken string `yaml:"wenxinAccessToken"` - WenxinApiPath string `yaml:"wenxinApiPath"` - MaxTokenWenxin int `yaml:"maxTokenWenxin"` - GptModel string `yaml:"gptModel"` - GptApiPath string `yaml:"gptApiPath"` - GptToken string `yaml:"gptToken"` - MaxTokenGpt int `yaml:"maxTokenGpt"` - GptSafeMode bool `yaml:"gptSafeMode"` - GptSseType int `yaml:"gptSseType"` - Groupmessage bool `yaml:"groupMessage"` - SplitByPuntuations int `yaml:"splitByPuntuations"` - HunyuanType int `yaml:"hunyuanType"` - FirstQ []string `yaml:"firstQ"` - FirstA []string `yaml:"firstA"` - SecondQ []string `yaml:"secondQ"` - SecondA []string `yaml:"secondA"` - ThirdQ []string `yaml:"thirdQ"` - ThirdA []string `yaml:"thirdA"` - SensitiveMode bool `yaml:"sensitiveMode"` - SensitiveModeType int `yaml:"sensitiveModeType"` - DefaultChangeWord string `yaml:"defaultChangeWord"` - AntiPromptAttackPath string `yaml:"antiPromptAttackPath"` - ReverseUserPrompt bool `yaml:"reverseUserPrompt"` - IgnoreExtraTips bool `yaml:"ignoreExtraTips"` - SaveResponses []string `yaml:"saveResponses"` - RestoreCommand []string `yaml:"restoreCommand"` - RestoreResponses []string `yaml:"restoreResponses"` - UsePrivateSSE bool `yaml:"usePrivateSSE"` - Promptkeyboard []string `yaml:"promptkeyboard"` - Savelogs bool `yaml:"savelogs"` - AntiPromptLimit float64 `yaml:"antiPromptLimit"` - UseCache bool `yaml:"useCache"` - CacheThreshold int `yaml:"cacheThreshold"` - CacheChance int `yaml:"cacheChance"` - EmbeddingType int `yaml:"embeddingType"` - WenxinEmbeddingUrl string `yaml:"wenxinEmbeddingUrl"` - GptEmbeddingUrl string `yaml:"gptEmbeddingUrl"` - PrintHanming bool `yaml:"printHanming"` - CacheK float64 `yaml:"cacheK"` - CacheN int `yaml:"cacheN"` - PrintVector bool `yaml:"printVector"` - VToBThreshold float64 `yaml:"vToBThreshold"` - GptModeration bool `yaml:"gptModeration"` - WenxinTopp float64 `yaml:"wenxinTopp"` - WnxinPenaltyScore float64 `yaml:"wenxinPenaltyScore"` - WenxinMaxOutputTokens int `yaml:"wenxinMaxOutputTokens"` - VectorSensitiveFilter bool `yaml:"vectorSensitiveFilter"` - VertorSensitiveThreshold int `yaml:"vertorSensitiveThreshold"` + SecretId string `yaml:"secretId"` + SecretKey string `yaml:"secretKey"` + Region string `yaml:"region"` + UseSse bool `yaml:"useSse"` + Port int `yaml:"port"` + HttpPath string `yaml:"path"` + SystemPrompt []string `yaml:"systemPrompt"` + IPWhiteList []string `yaml:"iPWhiteList"` + MaxTokensHunyuan int `yaml:"maxTokensHunyuan"` + ApiType int `yaml:"apiType"` + WenxinAccessToken string `yaml:"wenxinAccessToken"` + WenxinApiPath string `yaml:"wenxinApiPath"` + MaxTokenWenxin int `yaml:"maxTokenWenxin"` + GptModel string `yaml:"gptModel"` + GptApiPath string `yaml:"gptApiPath"` + GptToken string `yaml:"gptToken"` + MaxTokenGpt int `yaml:"maxTokenGpt"` + GptSafeMode bool `yaml:"gptSafeMode"` + GptSseType int `yaml:"gptSseType"` + Groupmessage bool `yaml:"groupMessage"` + SplitByPuntuations int `yaml:"splitByPuntuations"` + HunyuanType int `yaml:"hunyuanType"` + FirstQ []string `yaml:"firstQ"` + FirstA []string `yaml:"firstA"` + SecondQ []string `yaml:"secondQ"` + SecondA []string `yaml:"secondA"` + ThirdQ []string `yaml:"thirdQ"` + ThirdA []string `yaml:"thirdA"` + SensitiveMode bool `yaml:"sensitiveMode"` + SensitiveModeType int `yaml:"sensitiveModeType"` + DefaultChangeWord string `yaml:"defaultChangeWord"` + AntiPromptAttackPath string `yaml:"antiPromptAttackPath"` + ReverseUserPrompt bool `yaml:"reverseUserPrompt"` + IgnoreExtraTips bool `yaml:"ignoreExtraTips"` + SaveResponses []string `yaml:"saveResponses"` + RestoreCommand []string `yaml:"restoreCommand"` + RestoreResponses []string `yaml:"restoreResponses"` + UsePrivateSSE bool `yaml:"usePrivateSSE"` + Promptkeyboard []string `yaml:"promptkeyboard"` + Savelogs bool `yaml:"savelogs"` + AntiPromptLimit float64 `yaml:"antiPromptLimit"` + UseCache bool `yaml:"useCache"` + CacheThreshold int `yaml:"cacheThreshold"` + CacheChance int `yaml:"cacheChance"` + EmbeddingType int `yaml:"embeddingType"` + WenxinEmbeddingUrl string `yaml:"wenxinEmbeddingUrl"` + GptEmbeddingUrl string `yaml:"gptEmbeddingUrl"` + PrintHanming bool `yaml:"printHanming"` + CacheK float64 `yaml:"cacheK"` + CacheN int `yaml:"cacheN"` + PrintVector bool `yaml:"printVector"` + VToBThreshold float64 `yaml:"vToBThreshold"` + GptModeration bool `yaml:"gptModeration"` + WenxinTopp float64 `yaml:"wenxinTopp"` + WnxinPenaltyScore float64 `yaml:"wenxinPenaltyScore"` + WenxinMaxOutputTokens int `yaml:"wenxinMaxOutputTokens"` + VectorSensitiveFilter bool `yaml:"vectorSensitiveFilter"` + VertorSensitiveThreshold int `yaml:"vertorSensitiveThreshold"` + AllowedLanguages []string `yaml:"allowedLanguages"` + LanguagesResponseMessages []string `yaml:"langResponseMessages"` + QuestionMaxLenth int `yaml:"questionMaxLenth"` + QmlResponseMessages []string `yaml:"qmlResponseMessages"` } // LoadConfig 从文件中加载配置并初始化单例配置 @@ -794,3 +798,55 @@ func GetVertorSensitiveThreshold() int { } return 0 } + +// GetAllowedLanguages 返回允许的语言列表 +func GetAllowedLanguages() []string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.AllowedLanguages + } + return nil // 或返回一个默认的语言列表 +} + +// GetLanguagesResponseMessages 返回语言拦截响应消息列表 +func GetLanguagesResponseMessages() string { + mu.Lock() + defer mu.Unlock() + if instance != nil && len(instance.Settings.LanguagesResponseMessages) > 0 { + // 如果列表中只有一个消息,直接返回这个消息 + if len(instance.Settings.LanguagesResponseMessages) == 1 { + return instance.Settings.LanguagesResponseMessages[0] + } + // 如果有多个消息,随机选择一个返回 + index := rand.Intn(len(instance.Settings.LanguagesResponseMessages)) + return instance.Settings.LanguagesResponseMessages[index] + } + return "" // 如果列表为空,返回空字符串 +} + +// 获取QuestionMaxLenth +func GetQuestionMaxLenth() int { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.QuestionMaxLenth + } + return 0 +} + +// GetQmlResponseMessages 返回语言拦截响应消息列表 +func GetQmlResponseMessages() string { + mu.Lock() + defer mu.Unlock() + if instance != nil && len(instance.Settings.QmlResponseMessages) > 0 { + // 如果列表中只有一个消息,直接返回这个消息 + if len(instance.Settings.QmlResponseMessages) == 1 { + return instance.Settings.QmlResponseMessages[0] + } + // 如果有多个消息,随机选择一个返回 + index := rand.Intn(len(instance.Settings.QmlResponseMessages)) + return instance.Settings.QmlResponseMessages[index] + } + return "" // 如果列表为空,返回空字符串 +} diff --git a/go.mod b/go.mod index cc88944..32c4542 100644 --- a/go.mod +++ b/go.mod @@ -8,3 +8,5 @@ require ( github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.839 gopkg.in/yaml.v3 v3.0.1 ) + +require github.com/abadojack/whatlanggo v1.0.1 diff --git a/go.sum b/go.sum index 1454c2d..646cdb2 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4= +github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= diff --git a/readme.md b/readme.md index 16690cc..cc15a7a 100644 --- a/readme.md +++ b/readme.md @@ -28,7 +28,9 @@ _✨ 适用于Gensokyo以及Onebot的大模型数字人一键端 ✨_ 并发环境下的sse内存安全,支持维持多用户同时双向sse传输 -六重完备安全措施,全力以赴保证开发者和应用安全. +## 安全性 + +多重完备安全措施,尽可能保证开发者和llm应用安全. 可设置多轮模拟QA强化角色提示词,可自定义重置回复,安全词回复,第一重安全措施 @@ -36,7 +38,7 @@ _✨ 适用于Gensokyo以及Onebot的大模型数字人一键端 ✨_ 向量安全词列表,基于向量相似度的敏感拦截词列表,先于文本替换进行,第三重安全措施 -AhoCorasick算法实现的超高效文本替换规则,可大量替换n个关键词到各自对应的新关键词,第四重安全措施 +AhoCorasick算法实现的超高效文本IN-Out替换规则,可大量替换n个关键词到各自对应的新关键词,第四重安全措施 结果可再次通过百度-腾讯,文本审核接口,第五重安全措施 @@ -44,6 +46,12 @@ AhoCorasick算法实现的超高效文本替换规则,可大量替换n个关 命令行 -mlog 将当前储存的所有日志进行QA格式化,每日审验,从实际场景提炼新安全规则,不断增加安全性,第六重安全措施 +语言过滤,允许llm只接受所指定的语言,在自己擅长的领域进行防守,第七重安全措施 + +提示词长度限制,用最原始的方式控制安全,阻止恶意用户构造长提示词,第八重安全措施 + +通过这些方法,打造尽可能安全llm对话机器人。 + 文本IN-OUT双层替换,可自行实现内部提示词动态替换,修改,更安全强大 基于sqlite设计的向量数据表结构,可使用缓存来省钱.自定义缓存命中率,精准度. diff --git a/template/config_template.go b/template/config_template.go index d4b6fb2..01b4be0 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -33,6 +33,11 @@ settings: usePrivateSSE : false #不知道是啥的话就不用开 promptkeyboard : [""] #临时的promptkeyboard超过3个则随机,后期会增加一个ai生成的方式,也会是ai-agent savelogs : false #本地落地日志. + #语言过滤 + allowedLanguages : ["Cmn"] #根据自身安全实力,酌情过滤,cmn代表中文,小写字母,[]空数组代表不限制. + langResponseMessages : ["抱歉,我不会**这个语言呢","我不会**这门语言,请使用中文和我对话吧"] #定型文,**会自动替换为检测到的语言 + questionMaxLenth : 100 #最大问题字数. 0代表不限制 + qmlResponseMessages : ["问题太长了,缩短问题试试吧"] #最大问题长度回复. #向量缓存(省钱-酌情调整参数)(进阶!!)需要有一定的调试能力,数据库调优能力,计算和数据测试能力. #不同种类的向量,维度和模型不同,所以请一开始决定好使用的向量,或者自行将数据库备份\对应,不同种类向量没有互相检索的能力。 diff --git a/utils/utils.go b/utils/utils.go index be9b869..f5c66c3 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -12,6 +12,7 @@ import ( "regexp" "strings" + "github.com/abadojack/whatlanggo" "github.com/google/uuid" "github.com/hoshinonyaruko/gensokyo-llm/acnode" "github.com/hoshinonyaruko/gensokyo-llm/config" @@ -305,3 +306,217 @@ func PostSensitiveMessages() error { // 将HTTP响应结果保存到test_result.txt文件中 return os.WriteFile("test_result.txt", []byte(strings.Join(results, "\n")), 0644) } + +// SendSSEPrivateMessage 分割并发送消息的核心逻辑,直接遍历字符串 +func SendSSEPrivateMessage(userID int64, content string) { + punctuations := []rune{'。', '!', '?', ',', ',', '.', '!', '?'} + splitProbability := config.GetSplitByPuntuations() + + var parts []string + var currentPart strings.Builder + + for _, runeValue := range content { + currentPart.WriteRune(runeValue) + if strings.ContainsRune(string(punctuations), runeValue) { + // 根据概率决定是否分割 + if rand.Intn(100) < splitProbability { + parts = append(parts, currentPart.String()) + currentPart.Reset() + } + } + } + // 添加最后一部分(如果有的话) + if currentPart.Len() > 0 { + parts = append(parts, currentPart.String()) + } + + // 根据parts长度处理状态 + for i, part := range parts { + state := 1 + if i == len(parts)-2 { // 倒数第二部分 + state = 11 + } else if i == len(parts)-1 { // 最后一部分 + state = 20 + } + + // 构造消息体并发送 + messageSSE := structs.InterfaceBody{ + Content: part, + State: state, + } + + if state == 20 { // 对最后一部分特殊处理 + RestoreResponses := config.GetRestoreCommand() + promptKeyboard := config.GetPromptkeyboard() + + if len(RestoreResponses) > 0 { + selectedRestoreResponse := RestoreResponses[rand.Intn(len(RestoreResponses))] + if len(promptKeyboard) > 0 { + promptKeyboard[0] = selectedRestoreResponse + } + } + + messageSSE.PromptKeyboard = promptKeyboard + } + + // 发送SSE消息函数 + SendPrivateMessageSSE(userID, messageSSE) + } +} + +// SendSSEPrivateSafeMessage 分割并发送安全消息的核心逻辑,直接遍历字符串 +func SendSSEPrivateSafeMessage(userID int64, saveresponse string) { + // 将字符串转换为rune切片,以正确处理多字节字符 + runes := []rune(saveresponse) + + // 计算每部分应该包含的rune数量 + partLength := len(runes) / 3 + + // 初始化用于存储分割结果的切片 + parts := make([]string, 3) + + // 按字符分割字符串 + for i := 0; i < 3; i++ { + if i < 2 { // 前两部分 + start := i * partLength + end := start + partLength + parts[i] = string(runes[start:end]) + } else { // 最后一部分,包含所有剩余的字符 + start := i * partLength + parts[i] = string(runes[start:]) + } + } + // 开头 + messageSSE := structs.InterfaceBody{ + Content: parts[0], + State: 1, + } + + SendPrivateMessageSSE(userID, messageSSE) + + // 中间 + messageSSE = structs.InterfaceBody{ + Content: parts[1], + State: 11, + } + SendPrivateMessageSSE(userID, messageSSE) + + // 从配置中获取恢复响应数组 + RestoreResponses := config.GetRestoreCommand() + + var selectedRestoreResponse string + // 如果RestoreResponses至少有一个成员,则随机选择一个 + if len(RestoreResponses) > 0 { + selectedRestoreResponse = RestoreResponses[rand.Intn(len(RestoreResponses))] + } + + // 从配置中获取promptkeyboard + promptkeyboard := config.GetPromptkeyboard() + + // 确保promptkeyboard至少有一个成员 + if len(promptkeyboard) > 0 { + // 使用随机选中的RestoreResponse替换promptkeyboard的第一个成员 + promptkeyboard[0] = selectedRestoreResponse + } + + // 创建InterfaceBody结构体实例 + messageSSE = structs.InterfaceBody{ + Content: parts[2], // 假设空格字符串是期望的内容 + State: 20, // 假设的状态码 + PromptKeyboard: promptkeyboard, // 使用更新后的promptkeyboard + } + + // 发送SSE私人消息 + SendPrivateMessageSSE(userID, messageSSE) +} + +// LanguageIntercept 检查文本语言,如果不在允许列表中,则返回 true 并发送消息 +func LanguageIntercept(text string, message structs.OnebotGroupMessage) bool { + info := whatlanggo.Detect(text) + lang := whatlanggo.LangToString(info.Lang) + fmtf.Printf("LanguageIntercept:%v\n", lang) + + allowedLanguages := config.GetAllowedLanguages() + for _, allowed := range allowedLanguages { + if strings.Contains(allowed, lang) { + return false // 语言允许,不拦截 + } + } + + // 语言不允许,进行拦截 + responseMessage := config.GetLanguagesResponseMessages() + friendlyName := FriendlyLanguageNameCN(info.Lang) + responseMessage = strings.Replace(responseMessage, "**", friendlyName, -1) + + // 发送响应消息 + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if !config.GetUsePrivateSSE() { + SendPrivateMessage(message.UserID, responseMessage) + } else { + SendSSEPrivateMessage(message.UserID, responseMessage) + } + } else { + SendGroupMessage(message.GroupID, responseMessage) + } + + return true // 拦截 +} + +// FriendlyLanguageNameCN 将语言代码映射为中文名称 +func FriendlyLanguageNameCN(lang whatlanggo.Lang) string { + langMapCN := map[whatlanggo.Lang]string{ + whatlanggo.Eng: "英文", + whatlanggo.Cmn: "中文", + whatlanggo.Spa: "西班牙文", + whatlanggo.Por: "葡萄牙文", + whatlanggo.Rus: "俄文", + whatlanggo.Jpn: "日文", + whatlanggo.Deu: "德文", + whatlanggo.Kor: "韩文", + whatlanggo.Fra: "法文", + whatlanggo.Ita: "意大利文", + whatlanggo.Tur: "土耳其文", + whatlanggo.Pol: "波兰文", + whatlanggo.Nld: "荷兰文", + whatlanggo.Hin: "印地文", + whatlanggo.Ben: "孟加拉文", + whatlanggo.Vie: "越南文", + whatlanggo.Ukr: "乌克兰文", + whatlanggo.Swe: "瑞典文", + whatlanggo.Fin: "芬兰文", + whatlanggo.Dan: "丹麦文", + whatlanggo.Heb: "希伯来文", + whatlanggo.Tha: "泰文", + // 根据需要添加更多语言 + } + + // 获取中文的语言名称,如果没有找到,则返回"未知语言" + name, ok := langMapCN[lang] + if !ok { + return "未知语言" + } + return name +} + +// LengthIntercept 检查文本长度,如果超过最大长度,则返回 true 并发送消息 +func LengthIntercept(text string, message structs.OnebotGroupMessage) bool { + maxLen := config.GetQuestionMaxLenth() + if len(text) > maxLen { + // 长度超出限制,获取并发送响应消息 + responseMessage := config.GetQmlResponseMessages() + + // 根据消息类型发送响应 + if message.RealMessageType == "group_private" || message.MessageType == "private" { + if !config.GetUsePrivateSSE() { + SendPrivateMessage(message.UserID, responseMessage) + } else { + SendSSEPrivateMessage(message.UserID, responseMessage) + } + } else { + SendGroupMessage(message.GroupID, responseMessage) + } + + return true // 拦截 + } + return false // 长度符合要求,不拦截 +}