From a6ebe5722bc90f7c34a253fc479415a91bda3bf2 Mon Sep 17 00:00:00 2001 From: cosmo Date: Sat, 29 Jun 2024 22:15:21 +0800 Subject: [PATCH 01/13] beta447 --- idmap/service.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/idmap/service.go b/idmap/service.go index 090b9382..53167f7d 100644 --- a/idmap/service.go +++ b/idmap/service.go @@ -113,7 +113,7 @@ func CleanBucket(bucketName string) { return fmt.Errorf("bucket %s not found", bucketName) } - // 使用游标遍历bucket + // 使用游标遍历bucket 正向键 k:v 32位openid:大宽int64 64位msgid:大宽int6 c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { // 检查键或值是否包含冒号 @@ -121,7 +121,7 @@ func CleanBucket(bucketName string) { continue // 忽略包含冒号的键值对 } - // 检查值id的长度 + // 检查值id的长度 这里是正向键 id := string(k) if len(id) != 32 { if err := c.Delete(); err != nil { @@ -131,14 +131,14 @@ func CleanBucket(bucketName string) { } } - // 再次遍历处理reverseKey的情况 + // 再次遍历处理reverseKey的情况 反向键 row-整数:string 32位openid/64位msgid for k, v := c.First(); k != nil; k, v = c.Next() { if strings.HasPrefix(string(k), "row-") { if bytes.Contains(k, []byte(":")) || bytes.Contains(v, []byte(":")) { continue // 忽略包含冒号的键值对 } - - id := string(b.Get(k)) + // 这里检查反向键是否是32位 + id := string(v) if len(id) != 32 { if err := b.Delete(k); err != nil { return err From 1d63faee49470d5c3e46efa92d5361ec9280fc28 Mon Sep 17 00:00:00 2001 From: cosmo Date: Mon, 1 Jul 2024 22:02:27 +0800 Subject: [PATCH 02/13] beta448 --- idmap/service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idmap/service.go b/idmap/service.go index 53167f7d..31c1fb48 100644 --- a/idmap/service.go +++ b/idmap/service.go @@ -117,7 +117,7 @@ func CleanBucket(bucketName string) { c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { // 检查键或值是否包含冒号 - if bytes.Contains(k, []byte(":")) || bytes.Contains(v, []byte(":")) { + if bytes.Contains(k, []byte(":")) || bytes.Contains(v, []byte(":")) || bytes.Contains(k, []byte("row-")) { continue // 忽略包含冒号的键值对 } From cd346278749fc6759658b94337e4510816055a57 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 4 Jul 2024 14:47:07 +0800 Subject: [PATCH 03/13] beta449 --- config/config.go | 12 ++++++ handlers/avatar.go | 79 +++++++++++++++++++++++++++++++++++++ handlers/get_avatar.go | 5 +++ handlers/message_parser.go | 32 ++++++++++++--- structs/structs.go | 5 ++- template/config_template.go | 1 + 6 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 handlers/avatar.go diff --git a/config/config.go b/config/config.go index 887598e8..d6739645 100644 --- a/config/config.go +++ b/config/config.go @@ -2340,3 +2340,15 @@ func GetLinkNum() int { return instance.Settings.LinkNum } + +// 获取GetDoNotReplaceAppid的值 +func GetDoNotReplaceAppid() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to DoNotReplaceAppid value.") + return false + } + return instance.Settings.DoNotReplaceAppid +} diff --git a/handlers/avatar.go b/handlers/avatar.go new file mode 100644 index 00000000..508a402d --- /dev/null +++ b/handlers/avatar.go @@ -0,0 +1,79 @@ +package handlers + +import ( + "fmt" + "regexp" + + "github.com/hoshinonyaruko/gensokyo/config" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/hoshinonyaruko/gensokyo/mylog" +) + +func ProcessCQAvatar(groupID string, text string) string { + // 断言并获取 groupID 和 qq 号 + qqRegex := regexp.MustCompile(`\[CQ:avatar,qq=(\d+)\]`) + qqMatches := qqRegex.FindAllStringSubmatch(text, -1) + + for _, match := range qqMatches { + qqStr := match[1] // 提取 qq 号 + + var originalUserID string + var err error + if config.GetIdmapPro() { + // 如果UserID不是nil且配置为使用Pro版本,则调用RetrieveRowByIDv2Pro + _, originalUserID, err = idmap.RetrieveRowByIDv2Pro(groupID, qqStr) + if err != nil { + mylog.Printf("Error1 retrieving original GroupID: %v", err) + _, originalUserID, err = idmap.RetrieveRowByIDv2Pro("690426430", qqStr) + if err != nil { + mylog.Printf("Error reading private originalUserID: %v", err) + } + } + } else { + originalUserID, err = idmap.RetrieveRowByIDv2(qqStr) + if err != nil { + mylog.Printf("Error retrieving original UserID: %v", err) + } + } + + // 生成头像URL + avatarURL, _ := GenerateAvatarURLV2(originalUserID) + + // 替换文本中的 [CQ:avatar,qq=12345678] 为 [CQ:image,file=avatarurl] + replacement := fmt.Sprintf("[CQ:image,file=%s]", avatarURL) + text = qqRegex.ReplaceAllString(text, replacement) + } + + return text +} + +func GetAvatarCQCode(groupID, qqNumber string) (string, error) { + var originalUserID string + var err error + + if config.GetIdmapPro() { + // 如果配置为使用Pro版本,则调用RetrieveRowByIDv2Pro + _, originalUserID, err = idmap.RetrieveRowByIDv2Pro(groupID, qqNumber) + if err != nil { + mylog.Printf("Error retrieving original GroupID: %v", err) + return "", err + } + } else { + // 否则调用RetrieveRowByIDv2 + originalUserID, err = idmap.RetrieveRowByIDv2(qqNumber) + if err != nil { + mylog.Printf("Error retrieving original UserID: %v", err) + return "", err + } + } + + // 生成头像URL + avatarURL, err := GenerateAvatarURLV2(originalUserID) + if err != nil { + mylog.Printf("Error generating avatar URL: %v", err) + return "", err + } + + // 返回格式化后的字符串 + return fmt.Sprintf("[CQ:image,file=%s]", avatarURL), nil +} diff --git a/handlers/get_avatar.go b/handlers/get_avatar.go index edfabfaf..95360515 100644 --- a/handlers/get_avatar.go +++ b/handlers/get_avatar.go @@ -3,6 +3,7 @@ package handlers import ( "encoding/json" "fmt" + "strconv" "github.com/hoshinonyaruko/gensokyo/callapi" "github.com/hoshinonyaruko/gensokyo/config" @@ -15,6 +16,7 @@ type GetAvatarResponse struct { Message string `json:"message"` RetCode int `json:"retcode"` Echo interface{} `json:"echo"` + UserID int64 `json:"user_id"` } func init() { @@ -46,9 +48,12 @@ func GetAvatar(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI avatarurl, _ := GenerateAvatarURLV2(originalUserID) + useridstr := message.Params.UserID.(string) + response.Message = avatarurl response.RetCode = 0 response.Echo = message.Echo + response.UserID, _ = strconv.ParseInt(useridstr, 10, 64) outputMap := structToMap(response) diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 9948a873..3c621dc2 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -416,6 +416,8 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac if config.GetEnableChangeWord() { messageText = acnode.CheckWordOUT(messageText) } + // 解析[CQ:avatar,qq=123456] + messageText = ProcessCQAvatar(paramsMessage.GroupID.(string), messageText) case []interface{}: //多个映射组成的切片 mylog.Printf("params.message is a slice (segment_type_koishi)\n") @@ -450,6 +452,9 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac case "at": qqNumber, _ := segmentMap["data"].(map[string]interface{})["qq"].(string) segmentContent = "[CQ:at,qq=" + qqNumber + "]" + case "avatar": + qqNumber, _ := segmentMap["data"].(map[string]interface{})["qq"].(string) + segmentContent, _ = GetAvatarCQCode(paramsMessage.GroupID.(string), qqNumber) case "markdown": mdContent, ok := segmentMap["data"].(map[string]interface{})["data"] if ok { @@ -518,6 +523,9 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac case "at": qqNumber, _ := message["data"].(map[string]interface{})["qq"].(string) messageText = "[CQ:at,qq=" + qqNumber + "]" + case "avatar": + qqNumber, _ := message["data"].(map[string]interface{})["qq"].(string) + messageText, _ = GetAvatarCQCode(paramsMessage.GroupID.(string), qqNumber) case "markdown": mdContent, ok := message["data"].(map[string]interface{})["data"] if ok { @@ -563,8 +571,9 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac default: mylog.Println("Unsupported message format: params.message field is not a string, map or slice") } + //处理at - messageText = transformMessageTextAt(messageText) + messageText = transformMessageTextAt(messageText, paramsMessage.GroupID.(string)) //mylog.Printf(messageText) @@ -635,9 +644,12 @@ func isIPAddress(address string) bool { } // at处理 -func transformMessageTextAt(messageText string) string { - // 首先,将AppID替换为BotID - messageText = strings.ReplaceAll(messageText, AppID, BotID) +func transformMessageTextAt(messageText string, groupid string) string { + // DoNotReplaceAppid=false(默认频道bot,需要自己at自己时,否则改成true) + if !config.GetDoNotReplaceAppid() { + // 首先,将AppID替换为BotID + messageText = strings.ReplaceAll(messageText, AppID, BotID) + } // 去除所有[CQ:reply,id=数字] todo 更好的处理办法 replyRE := regexp.MustCompile(`\[CQ:reply,id=\d+\]`) @@ -648,7 +660,13 @@ func transformMessageTextAt(messageText string) string { messageText = re.ReplaceAllStringFunc(messageText, func(m string) string { submatches := re.FindStringSubmatch(m) if len(submatches) > 1 { - realUserID, err := idmap.RetrieveRowByIDv2(submatches[1]) + var realUserID string + var err error + if config.GetIdmapPro() { + _, realUserID, err = idmap.RetrieveRowByIDv2Pro(groupid, submatches[1]) + } else { + realUserID, err = idmap.RetrieveRowByIDv2(submatches[1]) + } if err != nil { // 如果出错,也替换成相应的格式,但使用原始QQ号 mylog.Printf("Error retrieving user ID: %v", err) @@ -758,7 +776,7 @@ func RevertTransformedText(data interface{}, msgtype string, api openapi.OpenAPI //处理前 先去前后空 messageText = strings.TrimSpace(msg.Content) } - var originmessageText = messageText + //mylog.Printf("1[%v]", messageText) // 将messageText里的BotID替换成AppID @@ -802,6 +820,8 @@ func RevertTransformedText(data interface{}, msgtype string, api openapi.OpenAPI messageText = strings.TrimSpace(messageText) } } + + var originmessageText = messageText //mylog.Printf("2[%v]", messageText) // 检查是否需要移除前缀 diff --git a/structs/structs.go b/structs/structs.go index c524b209..624961c5 100644 --- a/structs/structs.go +++ b/structs/structs.go @@ -141,8 +141,9 @@ type Settings struct { EnableChangeWord bool `yaml:"enableChangeWord"` DefaultChangeWord string `yaml:"defaultChangeWord"` //错误临时修复类 - Fix11300 bool `yaml:"fix_11300"` - HttpOnlyBot bool `yaml:"http_only_bot"` + Fix11300 bool `yaml:"fix_11300"` + HttpOnlyBot bool `yaml:"http_only_bot"` + DoNotReplaceAppid bool `yaml:"do_not_replace_appid"` //内置指令 BindPrefix string `yaml:"bind_prefix"` MePrefix string `yaml:"me_prefix"` diff --git a/template/config_template.go b/template/config_template.go index a377af81..e1fdd9d0 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -184,6 +184,7 @@ settings: #错误临时修复类 fix_11300: false #修复11300报错,需要在develop_bot_id填入自己机器人的appid. 11300原因暂时未知,临时修复方案. http_only_bot : false #这个配置项会自动配置,请不要修改,保持false. + do_not_replace_appid : false #在频道内机器人尝试at自己回at不到,保持false.群内机器人有发送用户头像url的需求时,true(因为用户头像url包含了appid,如果false就会出错.) #内置指令类 bind_prefix : "/bind" #需设置 #增强配置项 master_id 可触发 From b790b29ec0acb0f1af93ce9f2272356912ee97c7 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 4 Jul 2024 16:51:02 +0800 Subject: [PATCH 04/13] beta450 --- handlers/avatar.go | 71 +++++++++++++++++++++++++++++++++++++- handlers/message_parser.go | 21 ++++++++--- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/handlers/avatar.go b/handlers/avatar.go index 508a402d..dff391ad 100644 --- a/handlers/avatar.go +++ b/handlers/avatar.go @@ -27,6 +27,7 @@ func ProcessCQAvatar(groupID string, text string) string { _, originalUserID, err = idmap.RetrieveRowByIDv2Pro("690426430", qqStr) if err != nil { mylog.Printf("Error reading private originalUserID: %v", err) + return "" } } } else { @@ -47,6 +48,70 @@ func ProcessCQAvatar(groupID string, text string) string { return text } +func ProcessCQAvatarNoGroupID(text string) string { + // 断言并获取 groupID 和 qq 号 + qqRegex := regexp.MustCompile(`\[CQ:avatar,qq=(\d+)\]`) + qqMatches := qqRegex.FindAllStringSubmatch(text, -1) + + for _, match := range qqMatches { + qqStr := match[1] // 提取 qq 号 + + var originalUserID string + var err error + if config.GetIdmapPro() { + _, originalUserID, err = idmap.RetrieveRowByIDv2Pro("690426430", qqStr) + if err != nil { + mylog.Printf("Error reading private originalUserID: %v", err) + } + } else { + originalUserID, err = idmap.RetrieveRowByIDv2(qqStr) + if err != nil { + mylog.Printf("Error retrieving original UserID: %v", err) + } + } + + // 生成头像URL + avatarURL, _ := GenerateAvatarURLV2(originalUserID) + + // 替换文本中的 [CQ:avatar,qq=12345678] 为 [CQ:image,file=avatarurl] + replacement := fmt.Sprintf("[CQ:image,file=%s]", avatarURL) + text = qqRegex.ReplaceAllString(text, replacement) + } + + return text +} + +func GetAvatarCQCodeNoGroupID(qqNumber string) (string, error) { + var originalUserID string + var err error + + if config.GetIdmapPro() { + // 如果配置为使用Pro版本,则调用RetrieveRowByIDv2Pro + _, originalUserID, err = idmap.RetrieveRowByIDv2Pro("690426430", qqNumber) + if err != nil { + mylog.Printf("Error reading private originalUserID: %v", err) + return "", err + } + } else { + // 否则调用RetrieveRowByIDv2 + originalUserID, err = idmap.RetrieveRowByIDv2(qqNumber) + if err != nil { + mylog.Printf("Error retrieving original UserID: %v", err) + return "", err + } + } + + // 生成头像URL + avatarURL, err := GenerateAvatarURLV2(originalUserID) + if err != nil { + mylog.Printf("Error generating avatar URL: %v", err) + return "", err + } + + // 返回格式化后的字符串 + return fmt.Sprintf("[CQ:image,file=%s]", avatarURL), nil +} + func GetAvatarCQCode(groupID, qqNumber string) (string, error) { var originalUserID string var err error @@ -56,7 +121,11 @@ func GetAvatarCQCode(groupID, qqNumber string) (string, error) { _, originalUserID, err = idmap.RetrieveRowByIDv2Pro(groupID, qqNumber) if err != nil { mylog.Printf("Error retrieving original GroupID: %v", err) - return "", err + _, originalUserID, err = idmap.RetrieveRowByIDv2Pro("690426430", qqNumber) + if err != nil { + mylog.Printf("Error reading private originalUserID: %v", err) + return "", err + } } } else { // 否则调用RetrieveRowByIDv2 diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 3c621dc2..1254dc8c 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -416,8 +416,13 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac if config.GetEnableChangeWord() { messageText = acnode.CheckWordOUT(messageText) } - // 解析[CQ:avatar,qq=123456] - messageText = ProcessCQAvatar(paramsMessage.GroupID.(string), messageText) + if paramsMessage.GroupID == nil { + // 解析[CQ:avatar,qq=123456] + messageText = ProcessCQAvatarNoGroupID(messageText) + } else { + // 解析[CQ:avatar,qq=123456] + messageText = ProcessCQAvatar(paramsMessage.GroupID.(string), messageText) + } case []interface{}: //多个映射组成的切片 mylog.Printf("params.message is a slice (segment_type_koishi)\n") @@ -454,7 +459,11 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac segmentContent = "[CQ:at,qq=" + qqNumber + "]" case "avatar": qqNumber, _ := segmentMap["data"].(map[string]interface{})["qq"].(string) - segmentContent, _ = GetAvatarCQCode(paramsMessage.GroupID.(string), qqNumber) + if paramsMessage.GroupID == nil { + segmentContent, _ = GetAvatarCQCodeNoGroupID(qqNumber) + } else { + segmentContent, _ = GetAvatarCQCode(paramsMessage.GroupID.(string), qqNumber) + } case "markdown": mdContent, ok := segmentMap["data"].(map[string]interface{})["data"] if ok { @@ -525,7 +534,11 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac messageText = "[CQ:at,qq=" + qqNumber + "]" case "avatar": qqNumber, _ := message["data"].(map[string]interface{})["qq"].(string) - messageText, _ = GetAvatarCQCode(paramsMessage.GroupID.(string), qqNumber) + if paramsMessage.GroupID == nil { + messageText, _ = GetAvatarCQCodeNoGroupID(qqNumber) + } else { + messageText, _ = GetAvatarCQCode(paramsMessage.GroupID.(string), qqNumber) + } case "markdown": mdContent, ok := message["data"].(map[string]interface{})["data"] if ok { From 7a9b56a5bab382951fef7c823e68b77e64d40cbe Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 4 Jul 2024 16:57:20 +0800 Subject: [PATCH 05/13] beta451 --- template/config_template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/config_template.go b/template/config_template.go index e1fdd9d0..4827a82d 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -42,7 +42,7 @@ settings: global_group_msg_rre_to_message : false # 是否将用户开关机器人资料页的机器人推送开关 产生的事件转换为文本信息并发送给应用端.false将使用onebotv11的notice类型上报. global_group_msg_reject_message : "机器人主动消息被关闭" # 当开启 global_group_msg_rre_to_message 时,机器人主动信息被关闭将上报的信息. 自行添加intent - GroupMsgRejectHandler global_group_msg_receive_message : "机器人主动消息被开启" # 建议设置为无规则复杂随机内容,避免用户指令内容碰撞. 自行添加 intent - GroupMsgReceiveHandler - hash_id : false # 使用hash来进行idmaps转换,可以让user_id不是123开始的递增值 + hash_id : true # 使用hash来进行idmaps转换,可以让user_id不是123开始的递增值 idmap_pro : false # 需开启hash_id配合,高级id转换增强,可以多个真实值bind到同一个虚拟值,对于每个用户,每个群\私聊\判断私聊\频道,都会产生新的虚拟值,但可以多次bind,bind到同一个数字.数据库负担会变大. #Gensokyo互联类 From e17d0d4ce838fc8a779309fa01e364618d87f680 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 4 Jul 2024 18:40:56 +0800 Subject: [PATCH 06/13] beta452 --- ...7\260&\345\220\257\345\212\250gensokyo.md" | 0 handlers/message_parser.go | 52 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 "docs/\350\265\267\346\255\245-\346\263\250\345\206\214QQ\345\274\200\346\224\276\345\271\263\345\217\260&\345\220\257\345\212\250gensokyo.md" diff --git "a/docs/\350\265\267\346\255\245-\346\263\250\345\206\214QQ\345\274\200\346\224\276\345\271\263\345\217\260&\345\220\257\345\212\250gensokyo.md" "b/docs/\350\265\267\346\255\245-\346\263\250\345\206\214QQ\345\274\200\346\224\276\345\271\263\345\217\260&\345\220\257\345\212\250gensokyo.md" new file mode 100644 index 00000000..e69de29b diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 1254dc8c..0416a76c 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -585,8 +585,13 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac mylog.Println("Unsupported message format: params.message field is not a string, map or slice") } - //处理at - messageText = transformMessageTextAt(messageText, paramsMessage.GroupID.(string)) + if paramsMessage.GroupID == nil { + //处理at + messageText = transformMessageTextAtNoGroupID(messageText) + } else { + //处理at + messageText = transformMessageTextAt(messageText, paramsMessage.GroupID.(string)) + } //mylog.Printf(messageText) @@ -698,6 +703,49 @@ func transformMessageTextAt(messageText string, groupid string) string { return messageText } +// at处理 +func transformMessageTextAtNoGroupID(messageText string) string { + // DoNotReplaceAppid=false(默认频道bot,需要自己at自己时,否则改成true) + if !config.GetDoNotReplaceAppid() { + // 首先,将AppID替换为BotID + messageText = strings.ReplaceAll(messageText, AppID, BotID) + } + + // 去除所有[CQ:reply,id=数字] todo 更好的处理办法 + replyRE := regexp.MustCompile(`\[CQ:reply,id=\d+\]`) + messageText = replyRE.ReplaceAllString(messageText, "") + + // 使用正则表达式来查找所有[CQ:at,qq=数字]的模式 + re := regexp.MustCompile(`\[CQ:at,qq=(\d+)\]`) + messageText = re.ReplaceAllStringFunc(messageText, func(m string) string { + submatches := re.FindStringSubmatch(m) + if len(submatches) > 1 { + var realUserID string + var err error + if config.GetIdmapPro() { + // 这是个魔法数 代表私聊 + _, realUserID, err = idmap.RetrieveRowByIDv2Pro("690426430", submatches[1]) + } else { + realUserID, err = idmap.RetrieveRowByIDv2(submatches[1]) + } + if err != nil { + // 如果出错,也替换成相应的格式,但使用原始QQ号 + mylog.Printf("Error retrieving user ID: %v", err) + return "<@!" + submatches[1] + ">" + } + + // 在这里检查 GetRemoveBotAtGroup 和 realUserID 的长度 + if config.GetRemoveBotAtGroup() && len(realUserID) == 32 { + return "" + } + + return "<@!" + realUserID + ">" + } + return m + }) + return messageText +} + // 链接处理 func transformMessageTextUrl(messageText string, message callapi.ActionMessage, client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI) string { // 是否处理url From 13181759dbe85cad3f5a6acde0b2698d323aab55 Mon Sep 17 00:00:00 2001 From: cosmo Date: Thu, 4 Jul 2024 19:31:22 +0800 Subject: [PATCH 07/13] beta453 --- "docs/api\344\273\213\347\273\215.md" | 40 +++++ ...api\345\267\256\345\274\202-delete_msg.md" | 17 ++ ...7\346\241\243-markdown message segment.md" | 54 ++++++ ...1\243-markdown\345\256\232\344\271\211.md" | 167 ++++++++++++++++++ ...64\345\244\232\346\226\207\346\241\243.md" | 2 + ...7\260&\345\220\257\345\212\250gensokyo.md" | 56 ++++++ ...\351\242\235\345\244\226api-get_avatar.md" | 27 +++ readme.md | 2 + 8 files changed, 365 insertions(+) create mode 100644 "docs/api\344\273\213\347\273\215.md" create mode 100644 "docs/api\345\267\256\345\274\202-delete_msg.md" create mode 100644 "docs/\346\226\207\346\241\243-markdown message segment.md" create mode 100644 "docs/\346\226\207\346\241\243-markdown\345\256\232\344\271\211.md" create mode 100644 "docs/\346\233\264\345\244\232\346\226\207\346\241\243.md" create mode 100644 "docs/\351\242\235\345\244\226api-get_avatar.md" diff --git "a/docs/api\344\273\213\347\273\215.md" "b/docs/api\344\273\213\347\273\215.md" new file mode 100644 index 00000000..5f278dca --- /dev/null +++ "b/docs/api\344\273\213\347\273\215.md" @@ -0,0 +1,40 @@ +目前支持的API列表 + +具体api的定义请参考[onebot-11](https://github.com/botuniverse/onebot-11) + +### 支持的API列表 + +1. `/avatar` - avatar.go +2. `/delete_msg` - delete_msg.go +3. `/get_avatar` - get_avatar.go +4. `/get_friend_list` - get_friend_list.go +5. `/get_group_info` - get_group_info.go +6. `/get_group_list` - get_group_list.go +7. `/get_group_member_info` - get_group_member_info.go +8. `/get_group_member_list` - get_group_member_list.go +9. `/get_guild_channel_list` - get_guild_channel_list.go +10. `/get_guild_list` - get_guild_list.go +11. `/get_guild_service_profile` - get_guild_service_profile.go +12. `/get_login_info` - get_login_info.go +13. `/get_online_clients` - get_online_clients.go +14. `/get_status` - get_status.go +15. `/get_version_info` - get_version_info.go +16. `/handle_quick_operation` - handle_quick_operation.go +17. `/handle_quick_operation_async` - handle_quick_operation_async.go +18. `/mark_msg_as_read` - mark_msg_as_read.go +19. `/message_parser` - message_parser.go +20. `/put_interaction` - put_interaction.go +21. `/send_group_forward_msg` - send_group_forward_msg.go +22. `/send_group_msg` - send_group_msg.go +23. `/send_group_msg_async` - send_group_msg_async.go +24. `/send_group_msg_raw` - send_group_msg_raw.go +25. `/send_guild_channel_forum` - send_guild_channel_forum.go +26. `/send_guild_channel_msg` - send_guild_channel_msg.go +27. `/send_guild_private_msg` - send_guild_private_msg.go +28. `/send_msg` - send_msg.go +29. `/send_msg_async` - send_msg_async.go +30. `/send_private_msg` - send_private_msg.go +31. `/send_private_msg_async` - send_private_msg_async.go +32. `/send_private_msg_sse` - send_private_msg_sse.go +33. `/set_group_ban` - set_group_ban.go +34. `/set_group_whole_ban` - set_group_whole_ban.go \ No newline at end of file diff --git "a/docs/api\345\267\256\345\274\202-delete_msg.md" "b/docs/api\345\267\256\345\274\202-delete_msg.md" new file mode 100644 index 00000000..2bd6cd81 --- /dev/null +++ "b/docs/api\345\267\256\345\274\202-delete_msg.md" @@ -0,0 +1,17 @@ +# API: delete_msg + +撤回消息。 + +## 参数 + +| 字段名 | 数据类型 | 默认值 | 说明 | +|-------------|----------------|--------|-----------------------------------| +| message_id | number (int32) | - | 消息 ID | +| user_id | number | - | 对方 QQ 号(消息类型为 private 时需要) | +| group_id | number | - | 群号(消息类型为 group 时需要) | +| channel_id | number | - | 频道号(消息类型是 guild 时需要) | +| guild_id | number | - | 子频道号(消息类型是 guild_Private 时需要) | + +## 响应数据 + +无 \ No newline at end of file diff --git "a/docs/\346\226\207\346\241\243-markdown message segment.md" "b/docs/\346\226\207\346\241\243-markdown message segment.md" new file mode 100644 index 00000000..d03a2495 --- /dev/null +++ "b/docs/\346\226\207\346\241\243-markdown message segment.md" @@ -0,0 +1,54 @@ +```markdown +# Gensokyo Markdown Segment + +Gensokyo的Markdown Segment是对现有OneBot v11的扩展。 + +## Markdown卡片(文本形式) + +```json +{ + "type": "markdown", + "data": { + "data": "文本内容" + } +} +``` + +| 参数名 | 收 | 发 | 可能的值 | 说明 | +|----------|----|----|----------|-------------| +| data | ✓ | ✓ | - | md文本 | + +**文本内容为**: +- [链接](https://www.yuque.com/km57bt/hlhnxg/ddkv4a2lgcswitei) 中markdown的json字符串的base64(以base64://开头,文字处理为/u形式的unicode)或按以下规则处理后的,json实体化文本。 + +**转义**: +CQ 码由字符 [ 起始, 以 ] 结束, 并且以 , 分割各个参数。如果你的 CQ 码中, 参数值包括了这些字符, 那么它们应该被使用 HTML 特殊字符的编码方式进行转义。 + +字符 | 对应实体转义序列 +-----|------------------ +& | & +[ | [ +] | ] +, | , + +## Markdown卡片(object形式) + +```json +{ + "type": "markdown", + "data": { + "data": md object + } +} +``` + +| 参数名 | 收 | 发 | 可能的值 | 说明 | +|----------|----|----|----------|-------------| +| data | ✓ | ✓ | - | md object | + +**结构请参考**: +支持MessageSegment [链接](https://www.yuque.com/km57bt/hlhnxg/ddkv4a2lgcswitei) 与文本形式实际包含内容相同,但传参类型不同,不是string,而是你所组合的md卡片object(map)。 + +data下层应包含data(2层data),data.markdown,data.keyboard。 +同时与type同级的data字段是OneBot v11标准固定的,所以json结构会呈现data.data.markdown,data.data.keyboard双层结构。 +``` \ No newline at end of file diff --git "a/docs/\346\226\207\346\241\243-markdown\345\256\232\344\271\211.md" "b/docs/\346\226\207\346\241\243-markdown\345\256\232\344\271\211.md" new file mode 100644 index 00000000..c5eb6909 --- /dev/null +++ "b/docs/\346\226\207\346\241\243-markdown\345\256\232\344\271\211.md" @@ -0,0 +1,167 @@ +```markdown +## md cq码/segment格式 + +### CQ码格式 + +```markdown +[CQ:markdown,data=xxx] +``` + +推荐使用模板:[链接到模板](https://github.com/hoshinonyaruko/gensokyo-qqmd) + +- `data,xxx` 是经过base64编码的JSON数据,支持与其他CQ码拼接,可以组合成message segment形式。 + +官方文档:[开发者文档](https://bot.q.qq.com/wiki/develop) + +新文档:[API v2文档](https://bot.q.qq.com/wiki/develop/api-v2/) + +### 自定义md格式 + +```json +{ + "markdown": { + "content": "你好" + }, + "keyboard": { + "content": { + "rows": [ + { + "buttons": [ + { + "render_data": { + "label": "再来一张", + "visited_label": "正在绘图", + "style": 1 + }, + "action": { + "type": 2, + "permission": { + "type": 2, + "specify_role_ids": [ + "1", + "2", + "3" + ] + }, + "click_limit": 10, + "unsupport_tips": "编辑-兼容文本", + "data": "你好", + "at_bot_show_channel_list": false + } + } + ] + } + ] + } + }, + "msg_id": "123", + "timestamp": "123", + "msg_type": 2 +} +``` + +### 模板md格式 + +```json +{ + "markdown": { + "custom_template_id": "101993071_1658748972", + "params": [ + { + "key": "text", + "values": ["标题"] + }, + { + "key": "image", + "values": [ + "https://resource5-1255303497.cos.ap-guangzhou.myqcloud.com/abcmouse_word_watch/other/mkd_img.png" + ] + } + ] + }, + "keyboard": { + "content": { + "rows": [ + { + "buttons": [ + { + "render_data": { + "label": "再来一次", + "visited_label": "再来一次" + }, + "action": { + "type": 1, + "permission": { + "type": 1, + "specify_role_ids": [ + "1", + "2", + "3" + ] + }, + "click_limit": 10, + "unsupport_tips": "兼容文本", + "data": "data", + "at_bot_show_channel_list": true + } + } + ] + } + ] + } + } +} +``` + +### 按钮格式 + +```json +{ + "keyboard": { + "id": 1, + "rows": [ + { + "buttons": [ + { + "render_data": { + "label": "再来一次", + "visited_label": "再来一次" + }, + "action": { + "type": 1, + "permission": { + "type": 1, + "specify_role_ids": [ + "1", + "2", + "3" + ] + }, + "click_limit": 10, + "unsupport_tips": "兼容文本", + "data": "data", + "at_bot_show_channel_list": true + } + } + ] + } + ] + } +} +``` + +### 图文混排格式 + +```markdown +{{.text}}![{{.image_info}}]({{.image_url}}) +``` + +![{{.image_info}}]({{.image_url}}){{.text}} + +注意:在`{{}}`中不可以使用`![]()`这种Markdown格式的关键字。 + +![text #208px #320px](https://xxxxx.png) +``` + +详细文档请参考:[发消息含有消息按钮组件的消息](https://bot.q.qq.com/wiki/develop/api/openapi/message/post_keyboard_messages.html#%E5%8F%91%E9%80%81%E5%90%AB%E6%9C%89%E6%B6%88%E6%81%AF%E6%8C%89%E9%92%AE%E7%BB%84%E4%BB%B6%E7%9A%84%E6%B6%88%E6%81%AF) +``` \ No newline at end of file diff --git "a/docs/\346\233\264\345\244\232\346\226\207\346\241\243.md" "b/docs/\346\233\264\345\244\232\346\226\207\346\241\243.md" new file mode 100644 index 00000000..e149a183 --- /dev/null +++ "b/docs/\346\233\264\345\244\232\346\226\207\346\241\243.md" @@ -0,0 +1,2 @@ +文档陆续迁移中,更多文档可查看 +[yuque](https://www.yuque.com/km57bt/hlhnxg/mw7gm8dlpccd324e) \ No newline at end of file diff --git "a/docs/\350\265\267\346\255\245-\346\263\250\345\206\214QQ\345\274\200\346\224\276\345\271\263\345\217\260&\345\220\257\345\212\250gensokyo.md" "b/docs/\350\265\267\346\255\245-\346\263\250\345\206\214QQ\345\274\200\346\224\276\345\271\263\345\217\260&\345\220\257\345\212\250gensokyo.md" index e69de29b..edb128de 100644 --- "a/docs/\350\265\267\346\255\245-\346\263\250\345\206\214QQ\345\274\200\346\224\276\345\271\263\345\217\260&\345\220\257\345\212\250gensokyo.md" +++ "b/docs/\350\265\267\346\255\245-\346\263\250\345\206\214QQ\345\274\200\346\224\276\345\271\263\345\217\260&\345\220\257\345\212\250gensokyo.md" @@ -0,0 +1,56 @@ +```markdown +# 创建QQ机器人并配置 + +首先,您需要在 [QQ开放平台](https://q.qq.com/qqbot/) 注册一个开发者账号,确保使用您的大号QQ进行注册,而非小号。 + +## 注册步骤 + +1. 登录 QQ 开放平台,使用大号QQ注册账号。 +2. 注册完成后,进入开发者控制台。 + +## 创建机器人 + +根据 [图文版教程](https://www.yuque.com/km57bt/hlhnxg/hoxlh53gg11h7r3l) 中的指导操作,创建您的机器人,并进行必要的配置。 + +## 设置Intent + +根据您的频道类型选择合适的Intent设置: + +### 私域频道 + +```yaml +text_intent: + - DirectMessageHandler + - CreateMessageHandler + - InteractionHandler + - GroupATMessageEventHandler + - C2CMessageEventHandler + - GroupMsgRejectHandler + - GroupMsgReceiveHandler + - GroupAddRobotEventHandler + - GroupDelRobotEventHandler +``` + +### 公域频道 + +```yaml +text_intent: + - DirectMessageHandler + - ATMessageEventHandler + - InteractionHandler + - GroupATMessageEventHandler + - C2CMessageEventHandler + - GroupMsgRejectHandler + - GroupMsgReceiveHandler + - GroupAddRobotEventHandler + - GroupDelRobotEventHandler +``` + +确保按照上述格式将Intent配置正确,这将确保机器人能够正确地处理消息和事件。 + +## 连接nb2和koishi + +完成上述基础配置后,您可以继续学习如何使用nb2和koishi等应用程序来开发您的自定义插件。 + +现在,您已经完成了基础配置和必要的设置,可以开始进行进一步的开发和集成了。 +``` \ No newline at end of file diff --git "a/docs/\351\242\235\345\244\226api-get_avatar.md" "b/docs/\351\242\235\345\244\226api-get_avatar.md" new file mode 100644 index 00000000..768ee75e --- /dev/null +++ "b/docs/\351\242\235\345\244\226api-get_avatar.md" @@ -0,0 +1,27 @@ +```markdown +# API: get_avatar + +获取用户头像。 + +## 返回值 + +```go +type GetAvatarResponse struct { + Message string `json:"message"` + RetCode int `json:"retcode"` + Echo interface{} `json:"echo"` + UserID int64 `json:"user_id"` +} +``` + +## 所需字段 + +- **group_id**: 群号(当获取群成员头像时需要) +- **user_id**: 用户 QQ 号(当获取私信头像时需要) + +## CQcode + +CQ头像码格式.支持message segment式传参,将at segment类比修改为avatar即可. +[CQ:avatar,qq=123456] + +``` \ No newline at end of file diff --git a/readme.md b/readme.md index b77013a7..b525998d 100644 --- a/readme.md +++ b/readme.md @@ -50,6 +50,8 @@ _✨ 基于 [OneBot](https://github.com/howmanybots/onebot/blob/master/README.md ## 介绍 gensokyo兼容 [OneBot-v11](https://github.com/botuniverse/onebot-11) ,并在其基础上做了一些扩展,详情请看 OneBot 的文档。 +Gensokyo文档(施工中):[起步](/docs/起步-注册QQ开放平台&启动gensokyo.md) + 可将官方的websocket和api转换至onebotv11标准, 支持连接koishi,nonebot2,trss,zerobot,MiraiCQ,hoshino.. From b7f291c0e52431d165ec2a7ab6db647ab6df33fe Mon Sep 17 00:00:00 2001 From: cosmo Date: Fri, 5 Jul 2024 00:35:34 +0800 Subject: [PATCH 08/13] beta454 --- handlers/send_group_msg.go | 588 +++++++++++++++++++++++++++++++++++ handlers/send_private_msg.go | 2 +- 2 files changed, 589 insertions(+), 1 deletion(-) diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index aff3fed9..014a8bb7 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -1263,6 +1263,594 @@ func generateGroupMessage(id string, eventid string, foundItems map[string][]str return nil } +// 上传富媒体信息 +func generatePrivateMessage(id string, eventid string, foundItems map[string][]string, messageText string, msgseq int, apiv2 openapi.OpenAPI, userid string) interface{} { + if imageURLs, ok := foundItems["local_image"]; ok && len(imageURLs) > 0 { + // 从本地路径读取图片 + imageData, err := os.ReadFile(imageURLs[0]) + if err != nil { + // 读入文件失败 + mylog.Printf("Error reading the image from path %s: %v", imageURLs[0], err) + // 返回文本信息,提示图片文件不存在 + return &dto.MessageToCreate{ + Content: "错误: 图片文件不存在", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + // 首先压缩图片 默认不压缩 + compressedData, err := images.CompressSingleImage(imageData) + if err != nil { + mylog.Printf("Error compressing image: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 压缩图片失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + + // base64编码 + base64Encoded := base64.StdEncoding.EncodeToString(compressedData) + + if config.GetUploadPicV2Base64() { + // 直接上传图片返回 MessageToCreate type=7 + messageToCreate, err := images.CreateAndUploadMediaMessagePrivate(context.TODO(), base64Encoded, eventid, 1, false, "", userid, id, msgseq, apiv2) + if err != nil { + mylog.Printf("Error messageToCreate: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传图片失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + return messageToCreate + } + + // 上传base64编码的图片并获取其URL + imageURL, _, _, err := images.UploadBase64ImageToServer(base64Encoded, apiv2) + if err != nil { + mylog.Printf("Error uploading base64 encoded image: %v", err) + // 如果上传失败,也返回文本信息,提示上传失败 + return &dto.MessageToCreate{ + Content: "错误: 上传图片失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + + // 创建RichMediaMessage并返回,当作URL图片处理 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: imageURL, + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } else if RecordURLs, ok := foundItems["local_record"]; ok && len(RecordURLs) > 0 { + // 从本地路径读取语音 + RecordData, err := os.ReadFile(RecordURLs[0]) + if err != nil { + // 读入文件失败 + mylog.Printf("Error reading the record from path %s: %v", RecordURLs[0], err) + // 返回文本信息,提示语音文件不存在 + return &dto.MessageToCreate{ + Content: "错误: 语音文件不存在", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + //判断并转码 + if !silk.IsAMRorSILK(RecordData) { + mt, ok := silk.CheckAudio(bytes.NewReader(RecordData)) + if !ok { + mylog.Errorf("voice type error: " + mt) + return nil + } + RecordData = silk.EncoderSilk(RecordData) + mylog.Printf("音频转码ing") + } + + base64Encoded := base64.StdEncoding.EncodeToString(RecordData) + if config.GetUploadPicV2Base64() { + // 直接上传图片返回 MessageToCreate type=7 + messageToCreate, err := images.CreateAndUploadMediaMessagePrivate(context.TODO(), base64Encoded, eventid, 1, false, "", userid, id, msgseq, apiv2) + if err != nil { + mylog.Printf("Error messageToCreate: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传语音失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + return messageToCreate + } + + // 将解码的语音数据转换回base64格式并上传 + imageURL, err := images.UploadBase64RecordToServer(base64Encoded) + if err != nil { + mylog.Printf("failed to upload base64 record: %v", err) + return nil + } + // 创建RichMediaMessage并返回 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 3, // 3代表语音 + URL: imageURL, + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } 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, + EventID: eventid, + 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, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, + } + } + + // 转换为base64 + base64Encoded := base64.StdEncoding.EncodeToString(imageData) + + if config.GetUploadPicV2Base64() { + // 直接上传图片返回 MessageToCreate type=7 + messageToCreate, err := images.CreateAndUploadMediaMessagePrivate(context.TODO(), base64Encoded, eventid, 1, false, "", userid, id, msgseq, apiv2) + if err != nil { + mylog.Printf("Error messageToCreate: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传图片失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + return messageToCreate + } + + // 上传图片并获取新的URL + newURL, _, _, err := images.UploadBase64ImageToServer(base64Encoded, apiv2) + if err != nil { + mylog.Printf("Error uploading base64 encoded image: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传图片失败", + MsgID: id, + EventID: eventid, + 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: newpiclink, // 新图片链接 + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } else if imageURLs, ok := foundItems["url_images"]; ok && len(imageURLs) > 0 { + var newpiclink string + if config.GetUrlPicTransfer() { + // 从URL下载图片 + resp, err := http.Get("https://" + imageURLs[0]) + if err != nil { + mylog.Printf("Error downloading the image: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 下载图片失败", + MsgID: id, + EventID: eventid, + 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, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, + } + } + + // 转换为base64 + base64Encoded := base64.StdEncoding.EncodeToString(imageData) + + if config.GetUploadPicV2Base64() { + // 直接上传图片返回 MessageToCreate type=7 + messageToCreate, err := images.CreateAndUploadMediaMessagePrivate(context.TODO(), base64Encoded, eventid, 1, false, "", userid, id, msgseq, apiv2) + if err != nil { + mylog.Printf("Error messageToCreate: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传图片失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + return messageToCreate + } + + // 上传图片并获取新的URL + newURL, _, _, err := images.UploadBase64ImageToServer(base64Encoded, apiv2) + if err != nil { + mylog.Printf("Error uploading base64 encoded image: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传图片失败", + MsgID: id, + EventID: eventid, + 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 = "https://" + imageURLs[0] + } + + // 发链接图片 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: newpiclink, // 新图片链接 + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } else if voiceURLs, ok := foundItems["base64_record"]; ok && len(voiceURLs) > 0 { + // 适配base64 slik + if base64_record, ok := foundItems["base64_record"]; ok && len(base64_record) > 0 { + // 解码base64语音数据 + fileRecordData, err := base64.StdEncoding.DecodeString(base64_record[0]) + if err != nil { + mylog.Printf("failed to decode base64 record: %v", err) + return nil + } + //判断并转码 + if !silk.IsAMRorSILK(fileRecordData) { + mt, ok := silk.CheckAudio(bytes.NewReader(fileRecordData)) + if !ok { + mylog.Errorf("voice type error: " + mt) + return nil + } + fileRecordData = silk.EncoderSilk(fileRecordData) + mylog.Printf("音频转码ing") + } + // 将解码的语音数据转换回base64格式并上传 + imageURL, err := images.UploadBase64RecordToServer(base64.StdEncoding.EncodeToString(fileRecordData)) + if err != nil { + mylog.Printf("failed to upload base64 record: %v", err) + return nil + } + // 创建RichMediaMessage并返回 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 3, // 3代表语音 + URL: imageURL, + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } + } else if imageURLs, ok := foundItems["url_record"]; ok && len(imageURLs) > 0 { + // 从URL下载语音 + resp, err := http.Get("http://" + imageURLs[0]) + if err != nil { + mylog.Printf("Error downloading the record: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 下载语音失败", + MsgID: id, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + defer resp.Body.Close() + + // 读取语音数据 + recordData, err := io.ReadAll(resp.Body) + if err != nil { + mylog.Printf("Error reading the record data: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 读取语音数据失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, + } + } + //判断并转码 + if !silk.IsAMRorSILK(recordData) { + mt, ok := silk.CheckAudio(bytes.NewReader(recordData)) + if !ok { + mylog.Errorf("voice type error: " + mt) + return nil + } + recordData = silk.EncoderSilk(recordData) + mylog.Printf("音频转码ing") + } + // 转换为base64 + base64Encoded := base64.StdEncoding.EncodeToString(recordData) + + // 上传语音并获取新的URL + newURL, err := images.UploadBase64RecordToServer(base64Encoded) + if err != nil { + mylog.Printf("Error uploading base64 encoded image: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传语音失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, + } + } + + // 发链接语音 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 3, // 3代表语音 + URL: newURL, // 新语音链接 + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } else if imageURLs, ok := foundItems["url_records"]; ok && len(imageURLs) > 0 { + // 从URL下载语音 + resp, err := http.Get("https://" + imageURLs[0]) + if err != nil { + mylog.Printf("Error downloading the record: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 下载语音失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + defer resp.Body.Close() + + // 读取语音数据 + recordData, err := io.ReadAll(resp.Body) + if err != nil { + mylog.Printf("Error reading the record data: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 读取语音数据失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, + } + } + //判断并转码 + if !silk.IsAMRorSILK(recordData) { + mt, ok := silk.CheckAudio(bytes.NewReader(recordData)) + if !ok { + mylog.Errorf("voice type error: " + mt) + return nil + } + recordData = silk.EncoderSilk(recordData) + mylog.Printf("音频转码ing") + } + // 转换为base64 + base64Encoded := base64.StdEncoding.EncodeToString(recordData) + + // 上传语音并获取新的URL + newURL, err := images.UploadBase64RecordToServer(base64Encoded) + if err != nil { + mylog.Printf("Error uploading base64 encoded image: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传语音失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, + } + } + + // 发链接语音 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 3, // 3代表语音 + URL: newURL, // 新语音链接 + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } else if base64Image, ok := foundItems["base64_image"]; ok && len(base64Image) > 0 { + // todo 适配base64图片 + //因为QQ群没有 form方式上传,所以在gensokyo内置了图床,需公网,或以lotus方式连接位于公网的gensokyo + //要正确的开放对应的端口和设置正确的ip地址在config,这对于一般用户是有一些难度的 + // 解码base64图片数据 + fileImageData, err := base64.StdEncoding.DecodeString(base64Image[0]) + if err != nil { + mylog.Printf("failed to decode base64 image: %v", err) + return nil + } + + // 首先压缩图片 默认不压缩 + compressedData, err := images.CompressSingleImage(fileImageData) + if err != nil { + mylog.Printf("Error compressing image: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 压缩图片失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + + base64Encoded := base64.StdEncoding.EncodeToString(compressedData) + if config.GetUploadPicV2Base64() { + // 直接上传图片返回 MessageToCreate type=7 + messageToCreate, err := images.CreateAndUploadMediaMessagePrivate(context.TODO(), base64Encoded, eventid, 1, false, "", userid, id, msgseq, apiv2) + if err != nil { + mylog.Printf("Error messageToCreate: %v", err) + return &dto.MessageToCreate{ + Content: "错误: 上传图片失败", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + return messageToCreate + } + + // 将解码的图片数据转换回base64格式并上传 + imageURL, _, _, err := images.UploadBase64ImageToServer(base64Encoded, apiv2) + if err != nil { + mylog.Printf("failed to upload base64 image: %v", err) + return nil + } + // 创建RichMediaMessage并返回 + return &dto.RichMediaMessage{ + EventID: id, + FileType: 1, // 1代表图片 + URL: imageURL, + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } else if mdContent, ok := foundItems["markdown"]; ok && len(mdContent) > 0 { + // 解码base64 markdown数据 + mdData, err := base64.StdEncoding.DecodeString(mdContent[0]) + if err != nil { + mylog.Printf("failed to decode base64 md: %v", err) + return nil + } + markdown, keyboard, err := parseMDData(mdData) + if err != nil { + mylog.Printf("failed to parseMDData: %v", err) + return nil + } + return &dto.MessageToCreate{ + Content: "markdown", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + Markdown: markdown, + Keyboard: keyboard, + MsgType: 2, + } + } else if qqmusic, ok := foundItems["qqmusic"]; ok && len(qqmusic) > 0 { + // 转换qq音乐id到一个md + music_id := qqmusic[0] + markdown, keyboard, err := parseQQMuiscMDData(music_id) + if err != nil { + mylog.Printf("failed to parseMDData: %v", err) + return nil + } + if markdown != nil { + return &dto.MessageToCreate{ + Content: "markdown", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + Markdown: markdown, + Keyboard: keyboard, + MsgType: 2, + } + } else { + return &dto.MessageToCreate{ + Content: "markdown", + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + Keyboard: keyboard, + MsgType: 2, + } + } + } else if videoURL, ok := foundItems["url_video"]; ok && len(videoURL) > 0 { + newvideolink := "http://" + videoURL[0] + // 发链接视频 http + return &dto.RichMediaMessage{ + EventID: id, + FileType: 2, // 2代表视频 + URL: newvideolink, // 新图片链接 + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } else if videoURLs, ok := foundItems["url_videos"]; ok && len(videoURLs) > 0 { + newvideolink := "https://" + videoURLs[0] + // 发链接视频 https + return &dto.RichMediaMessage{ + EventID: id, + FileType: 2, // 2代表视频 + URL: newvideolink, // 新图片链接 + Content: "", // 这个字段文档没有了 + SrvSendMsg: false, + } + } else { + // 返回文本信息 + return &dto.MessageToCreate{ + Content: messageText, + MsgID: id, + EventID: eventid, + MsgSeq: msgseq, + MsgType: 0, // 默认文本类型 + } + } + return nil +} + // 通过user_id获取类型 func GetMessageTypeByUserid(appID string, userID interface{}) string { // 从appID和userID生成key diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index 7c0923cc..74cef2cc 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -233,7 +233,7 @@ func HandleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open if messageText != "" { msgseq := echo.GetMappingSeq(messageID) echo.AddMappingSeq(messageID, msgseq+1) - groupReply := generateGroupMessage(messageID, eventID, nil, messageText, msgseq+1, apiv2, message.Params.GroupID.(string)) + groupReply := generatePrivateMessage(messageID, eventID, nil, messageText, msgseq+1, apiv2, message.Params.UserID.(string)) // 进行类型断言 groupMessage, ok := groupReply.(*dto.MessageToCreate) From f921895234f33ccde2f8789dd022e30dd17f6cac Mon Sep 17 00:00:00 2001 From: cosmo Date: Tue, 9 Jul 2024 15:57:34 +0800 Subject: [PATCH 09/13] beta455 --- Processor/ProcessC2CMessage.go | 31 ++++++++--- Processor/ProcessChannelDirectMessage.go | 29 ++++++++--- Processor/ProcessGroupMessage.go | 16 ++++-- Processor/ProcessGuildATMessage.go | 16 ++++-- Processor/ProcessGuildNormalMessage.go | 16 ++++-- Processor/ProcessThreadMessage.go | 16 ++++-- config/config.go | 12 +++++ echo/echo.go | 53 +++++++++++++++++++ handlers/delete_msg.go | 21 ++++++-- handlers/message_parser.go | 65 ++++++++++++++++++------ handlers/send_group_msg_raw.go | 10 +++- structs/structs.go | 1 + template/config_template.go | 1 + 13 files changed, 234 insertions(+), 53 deletions(-) diff --git a/Processor/ProcessC2CMessage.go b/Processor/ProcessC2CMessage.go index bfe31ab5..67c6f40b 100644 --- a/Processor/ProcessC2CMessage.go +++ b/Processor/ProcessC2CMessage.go @@ -67,11 +67,19 @@ func (p *Processors) ProcessC2CMessage(data *dto.WSC2CMessageData) error { //收到私聊信息调用的具体还原步骤 //1,idmap还原真实userid, //发信息使用的是userid - - messageID64, err := idmap.StoreCachev2(data.ID) - if err != nil { - log.Fatalf("Error storing ID: %v", err) + var messageID64 int64 + if config.GetMemoryMsgid() { + messageID64, err = echo.StoreCacheInMemory(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + } else { + messageID64, err = idmap.StoreCachev2(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } } + messageID := int(messageID64) if config.GetAutoBind() { if len(data.Attachments) > 0 && data.Attachments[0].URL != "" { @@ -205,10 +213,17 @@ func (p *Processors) ProcessC2CMessage(data *dto.WSC2CMessageData) error { //框架内指令 p.HandleFrameworkCommand(messageText, data, "group_private") //映射str的messageID到int - messageID64, err := idmap.StoreCachev2(data.ID) - if err != nil { - mylog.Printf("Error storing ID: %v", err) - return nil + var messageID64 int64 + if config.GetMemoryMsgid() { + messageID64, err = echo.StoreCacheInMemory(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + } else { + messageID64, err = idmap.StoreCachev2(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } } messageID := int(messageID64) //todo 判断array模式 然后对Message处理成array格式 diff --git a/Processor/ProcessChannelDirectMessage.go b/Processor/ProcessChannelDirectMessage.go index 5ada9175..6a0845f7 100644 --- a/Processor/ProcessChannelDirectMessage.go +++ b/Processor/ProcessChannelDirectMessage.go @@ -95,9 +95,17 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) //3,通过idmap用channelid获取guildid, //发信息使用的是guildid //todo 优化数据库读写次数 - messageID64, err := idmap.StoreCachev2(data.ID) - if err != nil { - log.Fatalf("Error storing ID: %v", err) + var messageID64 int64 + if config.GetMemoryMsgid() { + messageID64, err = echo.StoreCacheInMemory(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + } else { + messageID64, err = idmap.StoreCachev2(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } } messageID := int(messageID64) //转换at @@ -344,10 +352,17 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) //userid := int(userid64) //映射str的messageID到int - messageID64, err := idmap.StoreCachev2(data.ID) - if err != nil { - mylog.Printf("Error storing ID: %v", err) - return nil + var messageID64 int64 + if config.GetMemoryMsgid() { + messageID64, err = echo.StoreCacheInMemory(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + } else { + messageID64, err = idmap.StoreCachev2(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } } messageID := int(messageID64) // 如果在Array模式下, 则处理Message为Segment格式 diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index 11195ec9..2cda94ca 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -3,6 +3,7 @@ package Processor import ( "fmt" + "log" "strconv" "time" @@ -78,10 +79,17 @@ func (p *Processors) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { //框架内指令 p.HandleFrameworkCommand(messageText, data, "group") //映射str的messageID到int - messageID64, err := idmap.StoreCachev2(data.ID) - if err != nil { - mylog.Printf("Error storing ID: %v", err) - return nil + var messageID64 int64 + if config.GetMemoryMsgid() { + messageID64, err = echo.StoreCacheInMemory(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + } else { + messageID64, err = idmap.StoreCachev2(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } } messageID := int(messageID64) if config.GetAutoBind() { diff --git a/Processor/ProcessGuildATMessage.go b/Processor/ProcessGuildATMessage.go index d9b8f1fc..82163320 100644 --- a/Processor/ProcessGuildATMessage.go +++ b/Processor/ProcessGuildATMessage.go @@ -3,6 +3,7 @@ package Processor import ( "fmt" + "log" "strconv" "time" @@ -195,10 +196,17 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { echostr := fmt.Sprintf("%s_%d_%d", AppIDString, s, currentTimeMillis) //映射str的messageID到int - messageID64, err := idmap.StoreCachev2(data.ID) - if err != nil { - mylog.Printf("Error storing ID: %v", err) - return nil + var messageID64 int64 + if config.GetMemoryMsgid() { + messageID64, err = echo.StoreCacheInMemory(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + } else { + messageID64, err = idmap.StoreCachev2(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } } messageID := int(messageID64) // 如果在Array模式下, 则处理Message为Segment格式 diff --git a/Processor/ProcessGuildNormalMessage.go b/Processor/ProcessGuildNormalMessage.go index fd01d02d..9c1f99fd 100644 --- a/Processor/ProcessGuildNormalMessage.go +++ b/Processor/ProcessGuildNormalMessage.go @@ -3,6 +3,7 @@ package Processor import ( "fmt" + "log" "strconv" "time" @@ -191,10 +192,17 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { // 构造echostr,包括AppID,原始的s变量和当前时间戳 echostr := fmt.Sprintf("%s_%d_%d", AppIDString, s, currentTimeMillis) //映射str的messageID到int - messageID64, err := idmap.StoreCachev2(data.ID) - if err != nil { - mylog.Printf("Error storing ID: %v", err) - return nil + var messageID64 int64 + if config.GetMemoryMsgid() { + messageID64, err = echo.StoreCacheInMemory(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + } else { + messageID64, err = idmap.StoreCachev2(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } } messageID := int(messageID64) // 如果在Array模式下, 则处理Message为Segment格式 diff --git a/Processor/ProcessThreadMessage.go b/Processor/ProcessThreadMessage.go index 29533d04..9bc30546 100644 --- a/Processor/ProcessThreadMessage.go +++ b/Processor/ProcessThreadMessage.go @@ -4,6 +4,7 @@ package Processor import ( "encoding/json" "fmt" + "log" "strconv" "strings" "time" @@ -312,10 +313,17 @@ func (p *Processors) ProcessThreadMessage(data *dto.WSThreadData) error { // 构造echostr,包括AppID,原始的s变量和当前时间戳 echostr := fmt.Sprintf("%s_%d_%d", AppIDString, s, currentTimeMillis) //映射str的messageID到int - messageID64, err := idmap.StoreCachev2(data.ID) - if err != nil { - mylog.Printf("Error storing ID: %v", err) - return nil + var messageID64 int64 + if config.GetMemoryMsgid() { + messageID64, err = echo.StoreCacheInMemory(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } + } else { + messageID64, err = idmap.StoreCachev2(data.ID) + if err != nil { + log.Fatalf("Error storing ID: %v", err) + } } messageID := int(messageID64) // 如果在Array模式下, 则处理Message为Segment格式 diff --git a/config/config.go b/config/config.go index d6739645..029f00f0 100644 --- a/config/config.go +++ b/config/config.go @@ -2352,3 +2352,15 @@ func GetDoNotReplaceAppid() bool { } return instance.Settings.DoNotReplaceAppid } + +// 获取GetMemoryMsgid的值 +func GetMemoryMsgid() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to MemoryMsgid value.") + return false + } + return instance.Settings.MemoryMsgid +} diff --git a/echo/echo.go b/echo/echo.go index 86ffef5a..3fbec3ce 100644 --- a/echo/echo.go +++ b/echo/echo.go @@ -1,12 +1,14 @@ package echo import ( + "fmt" "math/rand" "strconv" "sync" "time" "github.com/hoshinonyaruko/gensokyo/config" + "github.com/hoshinonyaruko/gensokyo/idmap" "github.com/tencent-connect/botgo/dto" ) @@ -53,6 +55,12 @@ type globalMessageGroup struct { stack []MessageGroupPair } +// 使用 sync.Map 作为内存存储 +var ( + globalSyncMapMsgid sync.Map + globalReverseMapMsgid sync.Map // 用于存储反向键值对 +) + // 初始化一个全局栈实例 var globalMessageGroupStack = &globalMessageGroup{ stack: make([]MessageGroupPair, 0), @@ -273,3 +281,48 @@ func RemoveFromGlobalStack(index int) { globalMessageGroupStack.stack = append(globalMessageGroupStack.stack[:index], globalMessageGroupStack.stack[index+1:]...) } + +// StoreCacheInMemory 根据 ID 将映射存储在内存中的 sync.Map 中 +func StoreCacheInMemory(id string) (int64, error) { + var newRow int64 + + // 检查是否已存在映射 + if value, ok := globalSyncMapMsgid.Load(id); ok { + newRow = value.(int64) + return newRow, nil + } + + // 生成新的行号 + var err error + maxDigits := 18 // int64 的位数上限-1 + for digits := 9; digits <= maxDigits; digits++ { + newRow, err = idmap.GenerateRowID(id, digits) + if err != nil { + return 0, err + } + + // 检查新生成的行号是否重复 + if _, exists := globalSyncMapMsgid.LoadOrStore(id, newRow); !exists { + // 存储反向键值对 + globalReverseMapMsgid.Store(newRow, id) + // 找到了一个唯一的行号,可以跳出循环 + break + } + + // 如果到达了最大尝试次数还没有找到唯一的行号,则返回错误 + if digits == maxDigits { + return 0, fmt.Errorf("unable to find a unique row ID after %d attempts", maxDigits-8) + } + } + + return newRow, nil +} + +// GetIDFromRowID 根据行号获取原始 ID +func GetCacheIDFromMemoryByRowID(rowID string) (string, bool) { + introwID, _ := strconv.ParseInt(rowID, 10, 64) + if value, ok := globalReverseMapMsgid.Load(introwID); ok { + return value.(string), true + } + return "", false +} diff --git a/handlers/delete_msg.go b/handlers/delete_msg.go index f2830094..306875ed 100644 --- a/handlers/delete_msg.go +++ b/handlers/delete_msg.go @@ -6,6 +6,8 @@ import ( "fmt" "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" + "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/idmap" "github.com/hoshinonyaruko/gensokyo/mylog" "github.com/tencent-connect/botgo/openapi" @@ -16,12 +18,21 @@ func init() { } func DeleteMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) (string, error) { - - //还原msgid - RealMsgID, err := idmap.RetrieveRowByCachev2(message.Params.MessageID.(string)) - if err != nil { - mylog.Printf("error retrieving real RChannelID: %v", err) + var RealMsgID string + var err error + + // 如果从内存取 + if config.GetMemoryMsgid() { + //还原msgid + RealMsgID, _ = echo.GetCacheIDFromMemoryByRowID(message.Params.MessageID.(string)) + } else { + //还原msgid + RealMsgID, err = idmap.RetrieveRowByCachev2(message.Params.MessageID.(string)) + if err != nil { + mylog.Printf("error retrieving real RChannelID: %v", err) + } } + //重新赋值 message.Params.MessageID = RealMsgID //撤回频道信息 diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 0416a76c..6c7f9f32 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -146,11 +146,20 @@ func SendResponse(client callapi.Client, err error, message *callapi.ActionMessa // 设置响应值 response := ServerResponse{} if resp != nil { - messageID64, mapErr = idmap.StoreCachev2(resp.Message.ID) - if mapErr != nil { - mylog.Printf("Error storing ID: %v", mapErr) - return "", nil + if config.GetMemoryMsgid() { + messageID64, mapErr = echo.StoreCacheInMemory(resp.Message.ID) + if mapErr != nil { + mylog.Printf("Error storing ID: %v", mapErr) + return "", nil + } + } else { + messageID64, mapErr = idmap.StoreCachev2(resp.Message.ID) + if mapErr != nil { + mylog.Printf("Error storing ID: %v", mapErr) + return "", nil + } } + response.Data.MessageID = int(messageID64) // 发送成功 增加今日发信息数 botstats.RecordMessageSent() @@ -246,10 +255,18 @@ func SendGuildResponse(client callapi.Client, err error, message *callapi.Action // 设置响应值 response := ServerResponse{} if resp != nil { - messageID64, mapErr = idmap.StoreCachev2(resp.ID) - if mapErr != nil { - mylog.Printf("Error storing ID: %v", mapErr) - return "", nil + if config.GetMemoryMsgid() { + messageID64, mapErr = echo.StoreCacheInMemory(resp.ID) + if mapErr != nil { + mylog.Printf("Error storing ID: %v", mapErr) + return "", nil + } + } else { + messageID64, mapErr = idmap.StoreCachev2(resp.ID) + if mapErr != nil { + mylog.Printf("Error storing ID: %v", mapErr) + return "", nil + } } response.Data.MessageID = int(messageID64) // 发送成功 增加今日发信息数 @@ -304,10 +321,18 @@ func SendC2CResponse(client callapi.Client, err error, message *callapi.ActionMe // 设置响应值 response := ServerResponse{} if resp != nil { - messageID64, mapErr = idmap.StoreCachev2(resp.Message.ID) - if mapErr != nil { - mylog.Printf("Error storing ID: %v", mapErr) - return "", nil + if config.GetMemoryMsgid() { + messageID64, mapErr = echo.StoreCacheInMemory(resp.Message.ID) + if mapErr != nil { + mylog.Printf("Error storing ID: %v", mapErr) + return "", nil + } + } else { + messageID64, mapErr = idmap.StoreCachev2(resp.Message.ID) + if mapErr != nil { + mylog.Printf("Error storing ID: %v", mapErr) + return "", nil + } } response.Data.MessageID = int(messageID64) // 发送成功 增加今日发信息数 @@ -361,10 +386,18 @@ func SendGuildPrivateResponse(client callapi.Client, err error, message *callapi // 设置响应值 response := ServerResponse{} if resp != nil { - messageID64, mapErr = idmap.StoreCachev2(resp.ID) - if mapErr != nil { - mylog.Printf("Error storing ID: %v", mapErr) - return "", nil + if config.GetMemoryMsgid() { + messageID64, mapErr = echo.StoreCacheInMemory(resp.ID) + if mapErr != nil { + mylog.Printf("Error storing ID: %v", mapErr) + return "", nil + } + } else { + messageID64, mapErr = idmap.StoreCachev2(resp.ID) + if mapErr != nil { + mylog.Printf("Error storing ID: %v", mapErr) + return "", nil + } } response.Data.MessageID = int(messageID64) } else { diff --git a/handlers/send_group_msg_raw.go b/handlers/send_group_msg_raw.go index bd9c0fb6..99713cfe 100644 --- a/handlers/send_group_msg_raw.go +++ b/handlers/send_group_msg_raw.go @@ -160,7 +160,15 @@ func HandleSendGroupMsgRaw(client callapi.Client, api openapi.OpenAPI, apiv2 ope if isNumeric(messageID) && messageID != "0" { // 当messageID是字符串形式的数字时,执行转换 - RealMsgID, err := idmap.RetrieveRowByCachev2(messageID) + var RealMsgID string + var err error + if config.GetMemoryMsgid() { + //从内存取 + RealMsgID, _ = echo.GetCacheIDFromMemoryByRowID(messageID) + } else { + RealMsgID, err = idmap.RetrieveRowByCachev2(messageID) + } + if err != nil { mylog.Printf("error retrieving real MessageID: %v", err) } else { diff --git a/structs/structs.go b/structs/structs.go index 624961c5..d81b4766 100644 --- a/structs/structs.go +++ b/structs/structs.go @@ -102,6 +102,7 @@ type Settings struct { SendError bool `yaml:"send_error"` SaveError bool `yaml:"save_error"` DowntimeMessage string `yaml:"downtime_message"` + MemoryMsgid bool `yaml:"memory_msgid"` //增长营销类 SelfIntroduce []string `yaml:"self_introduce"` //api修改 diff --git a/template/config_template.go b/template/config_template.go index 4827a82d..0ca4c670 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -134,6 +134,7 @@ settings: send_error : true #将报错用文本发出,避免机器人被审核报无响应 save_error : false #将保存保存在log文件夹,方便开发者定位发送错误. downtime_message : "我正在维护中~请不要担心,维护结束就回来~维护时间:(1小时)" + memory_msgid : false #当你的机器人单日信息量超过100万,就需要高性能SSD或者开启这个选项了.部分依赖msgid的功能可能会受影响(如delete_msg) #增长营销类(推荐gensokyo-broadcast项目) self_introduce : ["",""] #自我介绍,可设置多个随机发送,当不为空时,机器人被邀入群会发送自定义自我介绍 需手动添加新textintent - "GroupAddRobotEventHandler" - "GroupDelRobotEventHandler" From 24108c63b7f8ec13874573ed240668d55f4e9e47 Mon Sep 17 00:00:00 2001 From: cosmo Date: Tue, 9 Jul 2024 16:10:44 +0800 Subject: [PATCH 10/13] btea455 --- echo/echo.go | 37 +++++++++++++++++++++++++++++++++++++ main.go | 11 +++++++++++ 2 files changed, 48 insertions(+) diff --git a/echo/echo.go b/echo/echo.go index 3fbec3ce..9d4a91c6 100644 --- a/echo/echo.go +++ b/echo/echo.go @@ -59,6 +59,8 @@ type globalMessageGroup struct { var ( globalSyncMapMsgid sync.Map globalReverseMapMsgid sync.Map // 用于存储反向键值对 + cleanupTicker *time.Ticker + onceMsgid sync.Once ) // 初始化一个全局栈实例 @@ -326,3 +328,38 @@ func GetCacheIDFromMemoryByRowID(rowID string) (string, bool) { } return "", false } + +// StartCleanupRoutine 启动定时清理函数,每5分钟清空 globalSyncMapMsgid 和 globalReverseMapMsgid +func StartCleanupRoutine() { + onceMsgid.Do(func() { + cleanupTicker = time.NewTicker(5 * time.Minute) + + // 启动一个协程执行清理操作 + go func() { + for range cleanupTicker.C { + fmt.Println("Starting cleanup...") + + // 清空 sync.Map + globalSyncMapMsgid.Range(func(key, value interface{}) bool { + globalSyncMapMsgid.Delete(key) + return true + }) + + // 清空反向映射 sync.Map + globalReverseMapMsgid.Range(func(key, value interface{}) bool { + globalReverseMapMsgid.Delete(key) + return true + }) + + fmt.Println("Cleanup completed.") + } + }() + }) +} + +// StopCleanupRoutine 停止定时清理函数 +func StopCleanupRoutine() { + if cleanupTicker != nil { + cleanupTicker.Stop() + } +} diff --git a/main.go b/main.go index 6c3d089f..c534166c 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "github.com/hoshinonyaruko/gensokyo/acnode" "github.com/hoshinonyaruko/gensokyo/botstats" "github.com/hoshinonyaruko/gensokyo/config" + "github.com/hoshinonyaruko/gensokyo/echo" "github.com/hoshinonyaruko/gensokyo/handlers" "github.com/hoshinonyaruko/gensokyo/httpapi" "github.com/hoshinonyaruko/gensokyo/idmap" @@ -532,6 +533,11 @@ func main() { }() } + //杂七杂八的地方 + if conf.Settings.MemoryMsgid { + echo.StartCleanupRoutine() + } + // 使用color库输出天蓝色的文本 cyan := color.New(color.FgCyan) cyan.Printf("欢迎来到Gensokyo, 控制台地址: %s\n", webuiURL) @@ -554,6 +560,11 @@ func main() { } } + // 停止内存清理线程 + if conf.Settings.MemoryMsgid { + echo.StopCleanupRoutine() + } + // 关闭BoltDB数据库 url.CloseDB() idmap.CloseDB() From 7cdc968d4ca4f78019ce71d460b4bbcb65ac1088 Mon Sep 17 00:00:00 2001 From: cosmo Date: Tue, 9 Jul 2024 18:13:15 +0800 Subject: [PATCH 11/13] beta456 --- Processor/ProcessGuildATMessage.go | 2 + handlers/send_guild_channel_msg.go | 74 +++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/Processor/ProcessGuildATMessage.go b/Processor/ProcessGuildATMessage.go index 82163320..266923fb 100644 --- a/Processor/ProcessGuildATMessage.go +++ b/Processor/ProcessGuildATMessage.go @@ -296,6 +296,8 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { echo.AddMsgType(AppIDString, ChannelID64, "guild") //懒message_id池 echo.AddLazyMessageId(strconv.FormatInt(ChannelID64, 10), data.ID, time.Now()) + //测试 + echo.AddLazyMessageId(data.ChannelID, data.ID, time.Now()) //懒message_id池 //echo.AddLazyMessageId(strconv.FormatInt(userid64, 10), data.ID, time.Now()) //echo.AddLazyMessageIdv2(strconv.FormatInt(ChannelID64, 10), strconv.FormatInt(userid64, 10), data.ID, time.Now()) diff --git a/handlers/send_guild_channel_msg.go b/handlers/send_guild_channel_msg.go index 5975b293..4f475595 100644 --- a/handlers/send_guild_channel_msg.go +++ b/handlers/send_guild_channel_msg.go @@ -18,6 +18,7 @@ import ( "github.com/hoshinonyaruko/gensokyo/echo" "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/dto/keyboard" "github.com/tencent-connect/botgo/openapi" ) @@ -163,10 +164,33 @@ func HandleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 MsgType: 0, } newMessage.Timestamp = time.Now().Unix() // 设置时间戳 + var transmd bool + var md *dto.Markdown + var kb *keyboard.MessageKeyboard + //判断是否需要自动转换md + if config.GetTwoWayEcho() { + // 初始化 RichMediaMessage 结构体指针 + var richMediaMessage *dto.RichMediaMessage = &dto.RichMediaMessage{} + richMediaMessage.Content = messageText + richMediaMessage.URL = Reply.Image + md, kb, transmd = auto_md(message, messageText, richMediaMessage) + } - if _, err = api.PostMessage(context.TODO(), channelID.(string), newMessage); err != nil { - mylog.Printf("发送图文混合信息失败: %v", err) + if transmd { + newMessage.Content = "" + newMessage.Image = "" + newMessage.Markdown = md + newMessage.Keyboard = kb + newMessage.MsgType = 2 //md信息 + if _, err = api.PostMessage(context.TODO(), channelID.(string), newMessage); err != nil { + mylog.Printf("发送图文混合信息失败: %v", err) + } + } else { + if _, err = api.PostMessage(context.TODO(), channelID.(string), newMessage); err != nil { + mylog.Printf("发送图文混合信息失败: %v", err) + } } + // 检查是否是 40003 错误 if err != nil && strings.Contains(err.Error(), `"code":40003`) && len(newMessage.Image) > 0 { // 从 newMessage.Image 中提取图片地址 @@ -439,6 +463,52 @@ func GenerateReplyMessage(id string, foundItems map[string][]string, messageText MsgType: 0, // Default type for text } isBase64 = true + } else if qqmusic, ok := foundItems["qqmusic"]; ok && len(qqmusic) > 0 { + // 转换qq音乐id到一个md + music_id := qqmusic[0] + markdown, keyboard, err := parseQQMuiscMDData(music_id) + if err != nil { + mylog.Printf("failed to parseMDData: %v", err) + return nil, false + } + if markdown != nil { + msgtocreate := &dto.MessageToCreate{ + MsgID: id, + MsgSeq: msgseq, + Markdown: markdown, + Keyboard: keyboard, + MsgType: 2, + } + return msgtocreate, false + } else { + msgtocreate := &dto.MessageToCreate{ + MsgID: id, + MsgSeq: msgseq, + Keyboard: keyboard, + MsgType: 2, + } + return msgtocreate, false + } + } else if mdContent, ok := foundItems["markdown"]; ok && len(mdContent) > 0 { + // 解码base64 markdown数据 + mdData, err := base64.StdEncoding.DecodeString(mdContent[0]) + if err != nil { + mylog.Printf("failed to decode base64 md: %v", err) + return nil, false + } + markdown, keyboard, err := parseMDData(mdData) + if err != nil { + mylog.Printf("failed to parseMDData: %v", err) + return nil, false + } + msgtocreate := &dto.MessageToCreate{ + MsgID: id, + MsgSeq: msgseq, + Markdown: markdown, + Keyboard: keyboard, + MsgType: 2, + } + return msgtocreate, false } else { // 发文本信息 reply = dto.MessageToCreate{ From b5d784eae268a5a7a3dd19538a3f647897961236 Mon Sep 17 00:00:00 2001 From: cosmo Date: Tue, 9 Jul 2024 20:30:29 +0800 Subject: [PATCH 12/13] beta457 --- echo/echo.go | 173 ++++++++++++++++----------------------------------- 1 file changed, 53 insertions(+), 120 deletions(-) diff --git a/echo/echo.go b/echo/echo.go index 9d4a91c6..7125cc4f 100644 --- a/echo/echo.go +++ b/echo/echo.go @@ -13,34 +13,19 @@ import ( ) type EchoMapping struct { - mu sync.Mutex - msgTypeMapping map[string]string - msgIDMapping map[string]string - eventIDMapping map[string]string + msgTypeMapping sync.Map + msgIDMapping sync.Map + eventIDMapping sync.Map } // Int64ToIntMapping 用于存储 int64 到 int 的映射(递归计数器) type Int64ToIntMapping struct { - mu sync.Mutex - mapping map[int64]int + mapping sync.Map } -// IntToStringMappingSeq 用于存储 string 到 int 的映射(seq对应) +// StringToIntMappingSeq 用于存储 string 到 int 的映射(seq对应) type StringToIntMappingSeq struct { - mu sync.Mutex - mapping map[string]int -} - -// StringToInt64MappingSeq 用于存储 string 到 int64 的映射(file接口频率限制) -type StringToInt64MappingSeq struct { - mu sync.Mutex - mapping map[string]int64 -} - -// Int64Stack 用于存储 int64 的栈 -type Int64Stack struct { - mu sync.Mutex - stack []int64 + mapping sync.Map } // MessageGroupPair 用于存储 group 和 groupMessage @@ -68,27 +53,18 @@ var globalMessageGroupStack = &globalMessageGroup{ stack: make([]MessageGroupPair, 0), } -// 定义一个全局的 Int64Stack 实例 -var globalInt64Stack = &Int64Stack{ - stack: make([]int64, 0), -} - var globalEchoMapping = &EchoMapping{ - msgTypeMapping: make(map[string]string), - msgIDMapping: make(map[string]string), - eventIDMapping: make(map[string]string), + msgTypeMapping: sync.Map{}, + msgIDMapping: sync.Map{}, + eventIDMapping: sync.Map{}, } var globalInt64ToIntMapping = &Int64ToIntMapping{ - mapping: make(map[int64]int), + mapping: sync.Map{}, } var globalStringToIntMappingSeq = &StringToIntMappingSeq{ - mapping: make(map[string]int), -} - -var globalStringToInt64MappingSeq = &StringToInt64MappingSeq{ - mapping: make(map[string]int64), + mapping: sync.Map{}, } func (e *EchoMapping) GenerateKey(appid string, s int64) string { @@ -107,146 +83,103 @@ func (e *EchoMapping) GenerateKeyv3(appid string, s string) string { return appid + "_" + s } -// 添加echo对应的类型 +// 添加 echo 对应的类型 func AddMsgType(appid string, s int64, msgType string) { key := globalEchoMapping.GenerateKey(appid, s) - globalEchoMapping.mu.Lock() - defer globalEchoMapping.mu.Unlock() - globalEchoMapping.msgTypeMapping[key] = msgType + globalEchoMapping.msgTypeMapping.Store(key, msgType) } // 添加echo对应的messageid func AddMsgIDv3(appid string, s string, msgID string) { key := globalEchoMapping.GenerateKeyv3(appid, s) - globalEchoMapping.mu.Lock() - defer globalEchoMapping.mu.Unlock() - globalEchoMapping.msgIDMapping[key] = msgID + globalEchoMapping.msgIDMapping.Store(key, msgID) } // GetMsgIDv3 返回给定appid和s的msgID func GetMsgIDv3(appid string, s string) string { key := globalEchoMapping.GenerateKeyv3(appid, s) - globalEchoMapping.mu.Lock() - defer globalEchoMapping.mu.Unlock() - - return globalEchoMapping.msgIDMapping[key] + value, ok := globalEchoMapping.msgIDMapping.Load(key) + if !ok { + return "" // 或者根据需要返回默认值或者错误处理 + } + return value.(string) } // 添加group和userid对应的messageid func AddMsgIDv2(appid string, groupid int64, userid int64, msgID string) { key := globalEchoMapping.GenerateKeyv2(appid, groupid, userid) - globalEchoMapping.mu.Lock() - defer globalEchoMapping.mu.Unlock() - globalEchoMapping.msgIDMapping[key] = msgID + globalEchoMapping.msgIDMapping.Store(key, msgID) } // 添加group对应的eventid func AddEvnetID(appid string, groupid int64, eventID string) { key := globalEchoMapping.GenerateKeyEventID(appid, groupid) - globalEchoMapping.mu.Lock() - defer globalEchoMapping.mu.Unlock() - globalEchoMapping.eventIDMapping[key] = eventID + globalEchoMapping.eventIDMapping.Store(key, eventID) } // 添加echo对应的messageid func AddMsgID(appid string, s int64, msgID string) { key := globalEchoMapping.GenerateKey(appid, s) - globalEchoMapping.mu.Lock() - defer globalEchoMapping.mu.Unlock() - globalEchoMapping.msgIDMapping[key] = msgID + globalEchoMapping.msgIDMapping.Store(key, msgID) } // 根据给定的key获取消息类型 func GetMsgTypeByKey(key string) string { - globalEchoMapping.mu.Lock() - defer globalEchoMapping.mu.Unlock() - return globalEchoMapping.msgTypeMapping[key] + value, _ := globalEchoMapping.msgTypeMapping.Load(key) + if value == nil { + return "" // 根据需要返回默认值或者进行错误处理 + } + return value.(string) } // 根据给定的key获取消息ID func GetMsgIDByKey(key string) string { - globalEchoMapping.mu.Lock() - defer globalEchoMapping.mu.Unlock() - return globalEchoMapping.msgIDMapping[key] + value, _ := globalEchoMapping.msgIDMapping.Load(key) + if value == nil { + return "" // 根据需要返回默认值或者进行错误处理 + } + return value.(string) } // 根据给定的key获取EventID func GetEventIDByKey(key string) string { - globalEchoMapping.mu.Lock() - defer globalEchoMapping.mu.Unlock() - return globalEchoMapping.eventIDMapping[key] + value, _ := globalEchoMapping.eventIDMapping.Load(key) + if value == nil { + return "" // 根据需要返回默认值或者进行错误处理 + } + return value.(string) } // AddMapping 添加一个新的映射 func AddMapping(key int64, value int) { - globalInt64ToIntMapping.mu.Lock() - defer globalInt64ToIntMapping.mu.Unlock() - globalInt64ToIntMapping.mapping[key] = value + globalInt64ToIntMapping.mapping.Store(key, value) } // GetMapping 根据给定的 int64 键获取映射值 func GetMapping(key int64) int { - globalInt64ToIntMapping.mu.Lock() - defer globalInt64ToIntMapping.mu.Unlock() - return globalInt64ToIntMapping.mapping[key] + value, _ := globalInt64ToIntMapping.mapping.Load(key) + if value == nil { + return 0 // 根据需要返回默认值或者进行错误处理 + } + return value.(int) } -// AddMapping 添加一个新的映射 +// AddMappingSeq 添加一个新的映射 func AddMappingSeq(key string, value int) { - globalStringToIntMappingSeq.mu.Lock() - defer globalStringToIntMappingSeq.mu.Unlock() - globalStringToIntMappingSeq.mapping[key] = value + globalStringToIntMappingSeq.mapping.Store(key, value) } // GetMappingSeq 根据给定的 string 键获取映射值 func GetMappingSeq(key string) int { - if config.GetRamDomSeq() { - rng := rand.New(rand.NewSource(time.Now().UnixNano())) - return rng.Intn(10000) + 1 // 生成 1 到 10000 的随机数 - } else { - globalStringToIntMappingSeq.mu.Lock() - defer globalStringToIntMappingSeq.mu.Unlock() - return globalStringToIntMappingSeq.mapping[key] - } -} - -// AddMapping 添加一个新的映射 -func AddMappingFileTimeLimit(key string, value int64) { - globalStringToInt64MappingSeq.mu.Lock() - defer globalStringToInt64MappingSeq.mu.Unlock() - globalStringToInt64MappingSeq.mapping[key] = value -} - -// GetMapping 根据给定的 string 键获取映射值 -func GetMappingFileTimeLimit(key string) int64 { - globalStringToInt64MappingSeq.mu.Lock() - 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 + value, ok := globalStringToIntMappingSeq.mapping.Load(key) + if !ok { + if config.GetRamDomSeq() { + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + return rng.Intn(10000) + 1 // 生成 1 到 10000 的随机数 + } + return 0 // 或者根据需要返回默认值或者进行错误处理 } - return globalInt64Stack.stack[len(globalInt64Stack.stack)-1] + return value.(int) } // PushGlobalStack 向全局栈中添加一个新的 MessageGroupPair From 61e4001a9ab8f9fa7007f044eca2856d142e3e3f Mon Sep 17 00:00:00 2001 From: cosmo Date: Tue, 9 Jul 2024 20:44:04 +0800 Subject: [PATCH 13/13] beta458 --- Processor/Processor.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Processor/Processor.go b/Processor/Processor.go index 229d676a..f9c84318 100644 --- a/Processor/Processor.go +++ b/Processor/Processor.go @@ -579,7 +579,7 @@ func (p *Processors) HandleFrameworkCommand(messageText string, data interface{} } //link指令 - if Type == "group" && strings.HasPrefix(cleanedMessage, config.GetLinkPrefix()) { + if strings.HasPrefix(cleanedMessage, config.GetLinkPrefix()) { md, kb := generateMdByConfig() SendMessageMd(md, kb, data, Type, p.Api, p.Apiv2) } @@ -845,7 +845,6 @@ func SendMessageMd(md *dto.Markdown, kb *keyboard.MessageKeyboard, data interfac msgseq := echo.GetMappingSeq(msg.ID) echo.AddMappingSeq(msg.ID, msgseq+1) Message := &dto.MessageToCreate{ - Content: "markdown", MsgID: msg.ID, MsgSeq: msgseq, Markdown: md, @@ -889,7 +888,6 @@ func SendMessageMd(md *dto.Markdown, kb *keyboard.MessageKeyboard, data interfac msgseq := echo.GetMappingSeq(msg.ID) echo.AddMappingSeq(msg.ID, msgseq+1) Message := &dto.MessageToCreate{ - Content: "markdown", MsgID: msg.ID, MsgSeq: msgseq, Markdown: md,