diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index 6e4ffc81..b3d8deaf 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -27,6 +27,10 @@ func (p *Processors) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { mylog.Printf("信息被自定义黑白名单拦截") return nil } + //群没有at,但用户可以选择加一个 + if config.GetAddAtGroup() { + messageText = "[CQ:at,qq=" + config.GetAppIDStr() + "] " + messageText + } //框架内指令 p.HandleFrameworkCommand(messageText, data, "group") // 转换appid diff --git a/config/config.go b/config/config.go index 840064ee..d655c568 100644 --- a/config/config.go +++ b/config/config.go @@ -78,6 +78,9 @@ type Settings struct { RecordBitRate int `yaml:"record_bitRate"` NoWhiteResponse string `yaml:"No_White_Response"` SendError bool `yaml:"send_error"` + AddAtGroup bool `yaml:"add_at_group"` + SendErrorPicAsUrl bool `yaml:"send_error_pic_as_url"` + UrlPicTransfer bool `yaml:"url_pic_transfer"` } // LoadConfig 从文件中加载配置并初始化单例配置 @@ -925,3 +928,38 @@ func GetSendError() bool { return instance.Settings.SendError } +// 获取GetAddAtGroup的值 +func GetAddAtGroup() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to GetAddGroupAt value.") + return true + } + return instance.Settings.AddAtGroup +} + +// 获取GetSendErrorPicAsUrl的值 +func GetSendErrorPicAsUrl() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to GetErrorPicAsUrl value.") + return true + } + return instance.Settings.SendErrorPicAsUrl +} + +// 获取GetUrlPicTransfer的值 +func GetUrlPicTransfer() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to GetUrlPicTransfer value.") + return true + } + return instance.Settings.UrlPicTransfer +} diff --git a/echo/echo.go b/echo/echo.go index e0711514..5c936b32 100644 --- a/echo/echo.go +++ b/echo/echo.go @@ -29,6 +29,17 @@ type StringToInt64MappingSeq struct { mapping map[string]int64 } +// Int64Stack 用于存储 int64 的栈 +type Int64Stack struct { + mu sync.Mutex + stack []int64 +} + +// 定义一个全局的 Int64Stack 实例 +var globalInt64Stack = &Int64Stack{ + stack: make([]int64, 0), +} + var globalEchoMapping = &EchoMapping{ msgTypeMapping: make(map[string]string), msgIDMapping: make(map[string]string), @@ -120,3 +131,28 @@ func GetMappingFileTimeLimit(key string) int64 { defer globalStringToInt64MappingSeq.mu.Unlock() return globalStringToInt64MappingSeq.mapping[key] } + +// Add 添加一个新的 int64 到全局栈中 +func AddFileTimeLimit(value int64) { + globalInt64Stack.mu.Lock() + defer globalInt64Stack.mu.Unlock() + + // 添加新元素到栈顶 + globalInt64Stack.stack = append(globalInt64Stack.stack, value) + + // 如果栈的大小超过 10,移除最早添加的元素 + if len(globalInt64Stack.stack) > 10 { + globalInt64Stack.stack = globalInt64Stack.stack[1:] + } +} + +// Get 获取全局栈顶的元素 +func GetFileTimeLimit() int64 { + globalInt64Stack.mu.Lock() + defer globalInt64Stack.mu.Unlock() + + if len(globalInt64Stack.stack) == 0 { + return 0 // 当栈为空时返回 0 + } + return globalInt64Stack.stack[len(globalInt64Stack.stack)-1] +} diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index d0e6250c..d367110f 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "encoding/base64" + "io" + "net/http" "os" "strconv" "time" @@ -15,6 +17,7 @@ import ( "github.com/hoshinonyaruko/gensokyo/images" "github.com/hoshinonyaruko/gensokyo/mylog" "github.com/hoshinonyaruko/gensokyo/silk" + "github.com/hoshinonyaruko/gensokyo/url" "github.com/tencent-connect/botgo/dto" "github.com/tencent-connect/botgo/openapi" ) @@ -133,6 +136,9 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap echo.AddMappingSeq(messageID, msgseq+1) //时间限制 lastSendTimestamp := echo.GetMappingFileTimeLimit(messageID) + if lastSendTimestamp == 0 { + lastSendTimestamp = echo.GetFileTimeLimit() + } now := time.Now() millis := now.UnixMilli() diff := millis - lastSendTimestamp @@ -153,6 +159,7 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap mylog.Println("延迟完成") _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessageCopy) echo.AddMappingFileTimeLimit(messageID, millis) + echo.AddFileTimeLimit(millis) if err != nil { mylog.Printf("发送 %s 信息失败_send_group_msg: %v", key, err) if config.GetSendError() { //把报错当作文本发出去 @@ -172,13 +179,65 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap mylog.Printf("发送文本报错信息失败: %v", err) } } + if config.GetSendErrorPicAsUrl() { + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupReply := generateGroupMessage(messageID, nil, richMediaMessageCopy.URL, msgseq+1) + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + mylog.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + //重新为err赋值 + _, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + if err != nil { + mylog.Printf("发送图片报错后转url发送失败: %v", err) + } + } } }) } else { _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) echo.AddMappingFileTimeLimit(messageID, millis) + echo.AddFileTimeLimit(millis) if err != nil { mylog.Printf("发送 %s 信息失败_send_group_msg: %v", key, err) + if config.GetSendError() { //把报错当作文本发出去 + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupReply := generateGroupMessage(messageID, nil, err.Error(), msgseq+1) + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + mylog.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + //重新为err赋值 + _, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + if err != nil { + mylog.Printf("发送文本报错信息失败: %v", err) + } + } + if config.GetSendErrorPicAsUrl() { + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupReply := generateGroupMessage(messageID, nil, richMediaMessageCopy.URL, msgseq+1) + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + mylog.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + //重新为err赋值 + _, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + if err != nil { + mylog.Printf("发送图片报错后转url发送失败: %v", err) + } + } } } //发送成功回执 @@ -329,12 +388,68 @@ func generateGroupMessage(id string, foundItems map[string][]string, messageText SrvSendMsg: true, } } else if imageURLs, ok := foundItems["url_image"]; ok && len(imageURLs) > 0 { + var newpiclink string + if config.GetUrlPicTransfer() { + // 从URL下载图片 + resp, err := http.Get("http://" + imageURLs[0]) + if err != nil { + mylog.Printf("Error downloading the image: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 下载图片失败", + MsgID: id, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + defer resp.Body.Close() + + // 读取图片数据 + imageData, err := io.ReadAll(resp.Body) + if err != nil { + mylog.Printf("Error reading the image data: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 读取图片数据失败", + MsgID: id, + MsgSeq: msgseq, + MsgType: 0, + } + } + + // 转换为base64 + base64Encoded := base64.StdEncoding.EncodeToString(imageData) + + // 上传图片并获取新的URL + newURL, err := images.UploadBase64ImageToServer(base64Encoded) + if err != nil { + mylog.Printf("Error uploading base64 encoded image: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传图片失败", + MsgID: id, + MsgSeq: msgseq, + MsgType: 0, + } + } + // 将图片链接缩短 避免 url not allow + if config.GetLotusValue() { + // 连接到另一个gensokyo + newURL = url.GenerateShortURL(newURL) + } else { + // 自己是主节点 + newURL = url.GenerateShortURL(newURL) + // 使用getBaseURL函数来获取baseUrl并与newURL组合 + newURL = url.GetBaseURL() + "/url/" + newURL + } + newpiclink = newURL + } else { + newpiclink = "http://" + imageURLs[0] + } + // 发链接图片 return &dto.RichMediaMessage{ EventID: id, - FileType: 1, // 1代表图片 - URL: "http://" + imageURLs[0], //url在base64时候被截断了,在这里补全 - Content: "", // 这个字段文档没有了 + FileType: 1, // 1代表图片 + URL: newpiclink, // 新图片链接 + Content: "", // 这个字段文档没有了 SrvSendMsg: true, } } else if voiceURLs, ok := foundItems["base64_record"]; ok && len(voiceURLs) > 0 { diff --git a/handlers/send_msg.go b/handlers/send_msg.go index 0736b553..78931368 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -131,6 +131,9 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope echo.AddMappingSeq(messageID, msgseq+1) //时间限制 lastSendTimestamp := echo.GetMappingFileTimeLimit(messageID) + if lastSendTimestamp == 0 { + lastSendTimestamp = echo.GetFileTimeLimit() + } now := time.Now() millis := now.UnixMilli() diff := millis - lastSendTimestamp @@ -153,6 +156,7 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope mylog.Println("延迟完成") _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessageCopy) echo.AddMappingFileTimeLimit(messageID, millis) + echo.AddFileTimeLimit(millis) if err != nil { mylog.Printf("发送 %s 信息失败_send_msg: %v", key, err) if config.GetSendError() { //把报错当作文本发出去 @@ -172,14 +176,67 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope mylog.Printf("发送文本报错信息失败: %v", err) } } + if config.GetSendErrorPicAsUrl() { + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupReply := generateGroupMessage(messageID, nil, richMediaMessageCopy.URL, msgseq+1) + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + mylog.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + //重新为err赋值 + _, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + if err != nil { + mylog.Printf("发送图片报错后转url发送失败: %v", err) + } + } } }) } else { _, err := apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), richMediaMessage) echo.AddMappingFileTimeLimit(messageID, millis) + echo.AddFileTimeLimit(millis) if err != nil { - mylog.Printf("发送 %s 信息失败_send_group_msg: %v", key, err) + mylog.Printf("发送 %s 信息失败_send_msg: %v", key, err) + if config.GetSendError() { //把报错当作文本发出去 + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupReply := generateGroupMessage(messageID, nil, err.Error(), msgseq+1) + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + mylog.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + //重新为err赋值 + _, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + if err != nil { + mylog.Printf("发送文本报错信息失败: %v", err) + } + } + if config.GetSendErrorPicAsUrl() { + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupReply := generateGroupMessage(messageID, nil, richMediaMessageCopy.URL, msgseq+1) + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + mylog.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + //重新为err赋值 + _, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) + if err != nil { + mylog.Printf("发送图片报错后转url发送失败: %v", err) + } + } } + } //发送成功回执 SendResponse(client, err, &message) diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index 5ffecac4..cd74034a 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -138,6 +138,9 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open echo.AddMappingSeq(messageID, msgseq+1) //时间限制 lastSendTimestamp := echo.GetMappingFileTimeLimit(messageID) + if lastSendTimestamp == 0 { + lastSendTimestamp = echo.GetFileTimeLimit() + } now := time.Now() millis := now.UnixMilli() diff := millis - lastSendTimestamp @@ -158,6 +161,7 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open mylog.Println("延迟完成") _, err := apiv2.PostC2CMessage(context.TODO(), UserID, richMediaMessageCopy) echo.AddMappingFileTimeLimit(messageID, millis) + echo.AddFileTimeLimit(millis) if err != nil { mylog.Printf("发送 %s 私聊信息失败: %v", key, err) } @@ -165,6 +169,7 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open } else { // 发送消息 _, err := apiv2.PostC2CMessage(context.TODO(), UserID, richMediaMessage) echo.AddMappingFileTimeLimit(messageID, millis) + echo.AddFileTimeLimit(millis) if err != nil { mylog.Printf("发送 %s 私聊信息失败: %v", key, err) } diff --git a/sys/restart_unix.go b/sys/restart_unix.go index b198f48e..00ebf1aa 100644 --- a/sys/restart_unix.go +++ b/sys/restart_unix.go @@ -21,7 +21,7 @@ func NewRestarter() *UnixRestarter { func (r *UnixRestarter) Restart(executableName string) error { scriptContent := "#!/bin/sh\n" + "sleep 1\n" + // Sleep for a bit to allow the main application to exit - "exec " + executableName + "\n" + "." + executableName + "\n" scriptName := "restart.sh" if err := os.WriteFile(scriptName, []byte(scriptContent), 0755); err != nil { diff --git a/template/config_template.go b/template/config_template.go index a9ebfe6b..1f518785 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -73,6 +73,7 @@ settings: remove_prefix : false #是否忽略公域机器人指令前第一个/ remove_at : false #是否忽略公域机器人指令前第一个[CQ:aq,qq=机器人] 场景(公域机器人,但插件未适配at开头) remove_bot_at_group : true #因为群聊机器人不支持发at,开启本开关会自动隐藏群机器人发出的at(不影响频道场景) + add_at_group : false #自动在群聊指令前加上at,某些机器人写法特别,必须有at才反应时,请打开,默认请关闭(如果需要at,不需要at指令混杂,请优化代码适配群场景,群场景目前没有at概念) white_prefix_mode : false #公域 过审用 指令白名单模式开关 如果审核严格 请开启并设置白名单指令 以白名单开头的指令会被通过,反之被拦截 white_prefixs : [""] #可设置多个 比如设置 机器人 测试 则只有信息以机器人 测试开头会相应 remove_prefix remove_at 需为true时生效 @@ -90,6 +91,8 @@ settings: sandbox_mode : false #默认false 如果你只希望沙箱频道使用,请改为true dev_message_id : false #在沙盒和测试环境使用无限制msg_id 仅沙盒有效,正式环境请关闭,内测结束后,tx侧未来会移除 send_error : true #将报错用文本发出,避免机器人被审核报无响应 + send_error_pic_as_url : false #临时解决22009报错 + url_pic_transfer : false #可与url_pic_transfer配合使用 也可单独使用 把图片url(任意来源图链)变成你备案的白名单url 需要较高上下行+ssl+自备案域名+设置白名单域名(是目前gsk门槛最高的配置) title : "Gensokyo © 2023 - Hoshinonyaruko" #程序的标题 如果多个机器人 可根据标题区分 diff --git a/template/config_template.yml b/template/config_template.yml index 9018489f..0c9e0b96 100644 --- a/template/config_template.yml +++ b/template/config_template.yml @@ -69,4 +69,7 @@ settings: record_sampleRate : 24000 #语音文件的采样率 最高48000 默认24000 单位Khz record_bitRate : 24000 #语音文件的比特率 默认24000 代表 24 kbps 最高无限 请根据带宽 您发送的实际码率调整 No_White_Response : "" #默认不兜底,强烈建议设置一个友善的兜底回复,告知审核机器人已无隐藏指令,如:你输入的指令不对哦,@机器人来获取可用指令 - send_error : true #将报错用文本发出,避免机器人被审核报无响应 \ No newline at end of file + send_error : true #将报错用文本发出,避免机器人被审核报无响应 + add_at_group : false #自动在群聊指令前加上at,某些机器人写法特别,必须有at才反应时,请打开,默认请关闭(如果需要at,不需要at指令混杂,请优化代码适配群场景,群场景目前没有at概念 + send_error_pic_as_url : false #临时解决22009报错 + url_pic_transfer : false #可与url_pic_transfer配合使用 也可单独使用 把图片url(任意来源图链)变成你备案的白名单url 需要较高上下行+ssl+自备案域名+设置白名单域名(是目前gsk门槛最高的配置) \ No newline at end of file