diff --git a/Processor/ProcessC2CMessage.go b/Processor/ProcessC2CMessage.go index 2b734017..465ae443 100644 --- a/Processor/ProcessC2CMessage.go +++ b/Processor/ProcessC2CMessage.go @@ -118,6 +118,8 @@ func (p *Processors) ProcessC2CMessage(data *dto.WSC2CMessageData) error { // 根据条件判断是否添加Echo字段 if config.GetTwoWayEcho() { privateMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) @@ -220,6 +222,8 @@ func (p *Processors) ProcessC2CMessage(data *dto.WSC2CMessageData) error { // 根据条件判断是否添加Echo字段 if config.GetTwoWayEcho() { groupMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 获取MasterID数组 masterIDs := config.GetMasterID() diff --git a/Processor/ProcessChannelDirectMessage.go b/Processor/ProcessChannelDirectMessage.go index 53eb6ee7..e27faa94 100644 --- a/Processor/ProcessChannelDirectMessage.go +++ b/Processor/ProcessChannelDirectMessage.go @@ -141,6 +141,8 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) // 根据条件判断是否添加Echo字段 if config.GetTwoWayEcho() { privateMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 将当前s和appid和message进行映射 echo.AddMsgID(AppIDString, s, data.ID) @@ -218,6 +220,8 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) // 根据条件判断是否添加Echo字段 if config.GetTwoWayEcho() { onebotMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 获取MasterID数组 masterIDs := config.GetMasterID() @@ -368,6 +372,8 @@ func (p *Processors) ProcessChannelDirectMessage(data *dto.WSDirectMessageData) // 根据条件判断是否添加Echo字段 if config.GetTwoWayEcho() { groupMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 获取MasterID数组 masterIDs := config.GetMasterID() diff --git a/Processor/ProcessGroupMessage.go b/Processor/ProcessGroupMessage.go index 28387413..91ece57a 100644 --- a/Processor/ProcessGroupMessage.go +++ b/Processor/ProcessGroupMessage.go @@ -135,6 +135,8 @@ func (p *Processors) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { // 根据条件判断是否添加Echo字段 if config.GetTwoWayEcho() { groupMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 获取MasterID数组 masterIDs := config.GetMasterID() @@ -167,6 +169,8 @@ func (p *Processors) ProcessGroupMessage(data *dto.WSGroupATMessageData) error { echo.AddMsgType(AppIDString, GroupID64, "group") //懒message_id池 echo.AddLazyMessageId(strconv.FormatInt(GroupID64, 10), data.ID, time.Now()) + //懒message_id池 + echo.AddLazyMessageId(strconv.FormatInt(userid64, 10), data.ID, time.Now()) // 调试 PrintStructWithFieldNames(groupMsg) diff --git a/Processor/ProcessGuildATMessage.go b/Processor/ProcessGuildATMessage.go index e043dfba..6bd646d8 100644 --- a/Processor/ProcessGuildATMessage.go +++ b/Processor/ProcessGuildATMessage.go @@ -75,7 +75,12 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { SubType: "channel", Time: t.Unix(), Avatar: data.Author.Avatar, - Echo: echostr, + } + // 根据条件判断是否添加Echo字段 + if config.GetTwoWayEcho() { + onebotMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 获取MasterID数组 masterIDs := config.GetMasterID() @@ -106,6 +111,8 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { //todo 完善频道转换 //懒message_id池 echo.AddLazyMessageId(data.ChannelID, data.ID, time.Now()) + //懒message_id池 + echo.AddLazyMessageId(strconv.FormatInt(userid64, 10), data.ID, time.Now()) //调试 PrintStructWithFieldNames(onebotMsg) @@ -226,6 +233,8 @@ func (p *Processors) ProcessGuildATMessage(data *dto.WSATMessageData) error { // 根据条件判断是否添加Echo字段 if config.GetTwoWayEcho() { groupMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 获取MasterID数组 masterIDs := config.GetMasterID() @@ -257,6 +266,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()) + //懒message_id池 + echo.AddLazyMessageId(strconv.FormatInt(userid64, 10), data.ID, time.Now()) //调试 PrintStructWithFieldNames(groupMsg) diff --git a/Processor/ProcessGuildNormalMessage.go b/Processor/ProcessGuildNormalMessage.go index 71b66abc..1c280e7e 100644 --- a/Processor/ProcessGuildNormalMessage.go +++ b/Processor/ProcessGuildNormalMessage.go @@ -78,6 +78,8 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { // 根据条件判断是否添加Echo字段 if config.GetTwoWayEcho() { onebotMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 获取MasterID数组 masterIDs := config.GetMasterID() @@ -109,6 +111,8 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { //todo 完善频道ob信息 //懒message_id池 echo.AddLazyMessageId(data.ChannelID, data.ID, time.Now()) + //懒message_id池 + echo.AddLazyMessageId(strconv.FormatInt(userid64, 10), data.ID, time.Now()) //调试 PrintStructWithFieldNames(onebotMsg) @@ -225,6 +229,8 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { // 根据条件判断是否添加Echo字段 if config.GetTwoWayEcho() { groupMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) } // 获取MasterID数组 masterIDs := config.GetMasterID() @@ -268,6 +274,8 @@ func (p *Processors) ProcessGuildNormalMessage(data *dto.WSMessageData) error { echo.AddMsgType(AppIDString, ChannelID64, "guild") //懒message_id池 echo.AddLazyMessageId(strconv.FormatInt(ChannelID64, 10), data.ID, time.Now()) + //懒message_id池 + echo.AddLazyMessageId(strconv.FormatInt(userid64, 10), data.ID, time.Now()) //调试 PrintStructWithFieldNames(groupMsg) diff --git a/Processor/Processor.go b/Processor/Processor.go index b95d58ba..cc104dca 100644 --- a/Processor/Processor.go +++ b/Processor/Processor.go @@ -112,6 +112,18 @@ type OnebotPrivateMessage struct { IsBindedUserId bool `json:"is_binded_user_id,omitempty"` //当前用户号号是否是binded后的 } +// onebotv11标准扩展 +type OnebotInteractionNotice struct { + GroupID int64 `json:"group_id,omitempty"` + NoticeType string `json:"notice_type,omitempty"` + PostType string `json:"post_type,omitempty"` + SelfID int64 `json:"self_id,omitempty"` + SubType string `json:"sub_type,omitempty"` + Time int64 `json:"time,omitempty"` + UserID int64 `json:"user_id,omitempty"` + Data *dto.WSInteractionData `json:"data,omitempty"` +} + type PrivateSender struct { Nickname string `json:"nickname"` UserID int64 `json:"user_id"` // Can be either string or int depending on logic @@ -123,19 +135,63 @@ func FoxTimestamp() int64 { // ProcessInlineSearch 处理内联查询 func (p *Processors) ProcessInlineSearch(data *dto.WSInteractionData) error { - //ctx := context.Background() // 或从更高级别传递一个上下文 + // 转换appid + var userid64 int64 + var GroupID64 int64 + var err error + var fromgid, fromuid string + if data.GroupOpenID != "" { + fromgid = data.GroupOpenID + fromuid = data.GroupMemberOpenID + } else { + fromgid = data.ChannelID + fromuid = "0" + } + if config.GetIdmapPro() { + //将真实id转为int userid64 + GroupID64, userid64, err = idmap.StoreIDv2Pro(fromgid, fromuid) + if err != nil { + mylog.Fatalf("Error storing ID: %v", err) + } + //当参数不全 + _, _ = idmap.StoreIDv2(fromgid) + _, _ = idmap.StoreIDv2(fromuid) + if !config.GetHashIDValue() { + mylog.Fatalf("避坑日志:你开启了高级id转换,请设置hash_id为true,并且删除idmaps并重启") + } + } else { + // 映射str的GroupID到int + GroupID64, err = idmap.StoreIDv2(fromgid) + if err != nil { + mylog.Errorf("failed to convert ChannelID to int: %v", err) + return nil + } + // 映射str的userid到int + userid64, err = idmap.StoreIDv2(fromuid) + if err != nil { + mylog.Printf("Error storing ID: %v", err) + return nil + } + } + notice := &OnebotInteractionNotice{ + GroupID: GroupID64, + NoticeType: "interaction", + PostType: "notice", + SelfID: int64(p.Settings.AppID), + SubType: "create", + Time: time.Now().Unix(), + UserID: userid64, + Data: data, + } - // 在这里处理内联查询 - // 这可能涉及解析查询、调用某些API、获取结果并格式化为响应 - // ... + //调试 + PrintStructWithFieldNames(notice) - // 示例:发送响应 - // response := "Received your interaction!" // 创建响应消息 - // err := p.api.PostInteractionResponse(ctx, response) // 替换为您的OpenAPI方法 - // if err != nil { - // return err - // } + // Convert OnebotGroupMessage to map and send + noticeMap := structToMap(notice) + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(noticeMap) return nil } diff --git a/botgo/dto/interaction.go b/botgo/dto/interaction.go index 9d633c5b..31544046 100644 --- a/botgo/dto/interaction.go +++ b/botgo/dto/interaction.go @@ -1,16 +1,20 @@ package dto -import "encoding/json" - // Interaction 互动行为对象 type Interaction struct { - ID string `json:"id,omitempty"` // 互动行为唯一标识 - ApplicationID string `json:"application_id,omitempty"` // 应用ID - Type InteractionType `json:"type,omitempty"` // 互动类型 - Data *InteractionData `json:"data,omitempty"` // 互动数据 - GuildID string `json:"guild_id,omitempty"` // 频道 ID - ChannelID string `json:"channel_id,omitempty"` // 子频道 ID - Version uint32 `json:"version,omitempty"` // 版本,默认为 1 + ID string `json:"id,omitempty"` // 平台方事件 ID + Type InteractionType `json:"type,omitempty"` // 消息按钮: 11, 单聊快捷菜单: 12 + Scene string `json:"scene,omitempty"` // 事件发生的场景 + ChatType int `json:"chat_type,omitempty"` // 频道场景: 0, 群聊场景: 1, 单聊场景: 2 + Timestamp string `json:"timestamp,omitempty"` // 触发时间 RFC 3339 格式 + GuildID string `json:"guild_id,omitempty"` // 频道的 openid + ChannelID string `json:"channel_id,omitempty"` // 文字子频道的 openid + UserOpenID string `json:"user_openid,omitempty"` // 单聊按钮触发的用户 openid + GroupOpenID string `json:"group_openid,omitempty"` // 群的 openid + GroupMemberOpenID string `json:"group_member_openid,omitempty"` // 群成员 openid + Data *InteractionData `json:"data,omitempty"` // 互动数据 + Version uint32 `json:"version,omitempty"` // 版本,默认为 1 + ApplicationID string `json:"application_id,omitempty"` // 机器人的 appid } // InteractionType 互动类型 @@ -25,9 +29,15 @@ const ( // InteractionData 互动数据 type InteractionData struct { - Name string `json:"name,omitempty"` // 标题 - Type InteractionDataType `json:"type,omitempty"` // 数据类型,不同数据类型对应不同的 resolved 数据 - Resolved json.RawMessage `json:"resolved,omitempty"` // 跟不同的互动类型和数据类型有关系的数据 + Name string `json:"name,omitempty"` // 标题 + Type InteractionDataType `json:"type,omitempty"` // 数据类型 + Resolved struct { + ButtonData string `json:"button_data,omitempty"` // 操作按钮的 data 字段值 + ButtonID string `json:"button_id,omitempty"` // 操作按钮的 id 字段值 + UserID string `json:"user_id,omitempty"` // 操作的用户 userid + FeatureID string `json:"feature_id,omitempty"` // 操作按钮的 feature id + MessageID string `json:"message_id,omitempty"` // 操作的消息 id + } `json:"resolved,omitempty"` } // InteractionDataType 互动数据类型 diff --git a/botgo/dto/keyboard/keyboard.go b/botgo/dto/keyboard/keyboard.go index f53b4a74..c1087fb5 100644 --- a/botgo/dto/keyboard/keyboard.go +++ b/botgo/dto/keyboard/keyboard.go @@ -55,19 +55,23 @@ type RenderData struct { // Action 按纽点击操作 type Action struct { - Type ActionType `json:"type,omitempty"` // 操作类型 + Type ActionType `json:"type,omitempty"` // 操作类型 设置 0 跳转按钮:http 或 小程序 客户端识别 scheme,设置 1 回调按钮:回调后台接口, data 传给后台,设置 2 指令按钮:自动在输入框插入 @bot data Permission *Permission `json:"permission,omitempty"` // 可操作 ClickLimit uint32 `json:"click_limit,omitempty"` // 可点击的次数, 默认不限 Data string `json:"data,omitempty"` // 操作相关数据 AtBotShowChannelList bool `json:"at_bot_show_channel_list,omitempty"` // false:当前 true:弹出展示子频道选择器 + UnsupportTips string `json:"unsupport_tips"` //2024-1-12 新增字段 + AnChor int `json:"anchor"` //本字段仅在指令按钮下有效,设置后后会忽略 action.enter 配置。设置为 1 时 ,点击按钮自动唤起启手Q选图器,其他值暂无效果。(仅支持手机端版本 8983+ 的单聊场景,桌面端不支持) + Enter bool `json:"enter"` //指令按钮可用,点击按钮后直接自动发送 data,默认 false。支持版本 8983 + Reply bool `json:"reply"` //指令按钮可用,指令是否带引用回复本消息,默认 false。支持版本 8983 } // Permission 按纽操作权限 type Permission struct { - // Type 操作权限类型 + // Type 操作权限类型 0 指定用户可操作,1 仅管理者可操作,2 所有人可操作,3 指定身份组可操作(仅频道可用) Type PermissionType `json:"type,omitempty"` - // SpecifyRoleIDs 身份组 + // SpecifyRoleIDs 身份组(仅频道可用) SpecifyRoleIDs []string `json:"specify_role_ids,omitempty"` - // SpecifyUserIDs 指定 UserID + // SpecifyUserIDs 指定 UserID 有权限的用户 id 的列表 SpecifyUserIDs []string `json:"specify_user_ids,omitempty"` } diff --git a/config/config.go b/config/config.go index 021ed06f..78ddf764 100644 --- a/config/config.go +++ b/config/config.go @@ -131,6 +131,11 @@ type Settings struct { WhiteEnable []bool `yaml:"white_enable"` IdentifyAppids []int64 `yaml:"identify_appids"` TransFormApiIds bool `yaml:"transform_api_ids"` + CustomTemplateID string `yaml:"custom_template_id"` + KeyBoardID string `yaml:"keyboard_id"` + Uin int64 `yaml:"uin"` + VwhitePrefixMode bool `yaml:"v_white_prefix_mode"` + Enters []string `yaml:"enters"` } // LoadConfig 从文件中加载配置并初始化单例配置 @@ -1650,3 +1655,59 @@ func GetTransFormApiIds() bool { } return instance.Settings.TransFormApiIds } + +// 获取 CustomTemplateID 的值 +func GetCustomTemplateID() string { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to get CustomTemplateID.") + return "" + } + return instance.Settings.CustomTemplateID +} + +// 获取 KeyBoardIDD 的值 +func GetKeyBoardID() string { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to get KeyBoardID.") + return "" + } + return instance.Settings.KeyBoardID +} + +// 获取Uin String +func GetUinStr() string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return fmt.Sprintf("%d", instance.Settings.Uin) + } + return "0" +} + +// 获取 VV GetVwhitePrefixMode 的值 +func GetVwhitePrefixMode() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to VwhitePrefixMode value.") + return false + } + return instance.Settings.VwhitePrefixMode +} + +// 获取Enters的值 +func GetEnters() []string { + mu.Lock() + defer mu.Unlock() + if instance != nil { + return instance.Settings.Enters + } + return nil // 返回nil,如果instance为nil +} diff --git a/echo/echo.go b/echo/echo.go index c898215a..fc840e4f 100644 --- a/echo/echo.go +++ b/echo/echo.go @@ -106,6 +106,15 @@ func AddMsgIDv3(appid string, s string, msgID string) { globalEchoMapping.msgIDMapping[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] +} + // 添加group和userid对应的messageid func AddMsgIDv2(appid string, groupid int64, userid int64, msgID string) { key := globalEchoMapping.GenerateKeyv2(appid, groupid, userid) diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 4298ce71..e53351d2 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -119,7 +119,8 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac segmentContent = "[CQ:at,qq=" + qqNumber + "]" case "markdown": mdContent, _ := segmentMap["data"].(map[string]interface{})["data"].(string) - segmentContent = "[CQ:markdown,data=" + mdContent + "]" + encoded := "base64://" + base64.StdEncoding.EncodeToString([]byte(mdContent)) + segmentContent = "[CQ:markdown,data=" + encoded + "]" } messageText += segmentContent @@ -145,7 +146,8 @@ func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.Ac messageText = "[CQ:at,qq=" + qqNumber + "]" case "markdown": mdContent, _ := message["data"].(map[string]interface{})["data"].(string) - messageText = "[CQ:markdown,data=" + mdContent + "]" + encoded := "base64://" + base64.StdEncoding.EncodeToString([]byte(mdContent)) + messageText = "[CQ:markdown,data=" + encoded + "]" } default: mylog.Println("Unsupported message format: params.message field is not a string, map or slice") @@ -503,8 +505,8 @@ func RevertTransformedText(data interface{}, msgtype string, api openapi.OpenAPI aliases := config.GetAlias() messageText = processMessageText(messageText, aliases) //mylog.Printf("4[%v]", messageText) - // 检查是否启用白名单模式 - if config.GetWhitePrefixMode() && matchedPrefix != nil { + // 检查是否启用二级白名单模式 + if config.GetVwhitePrefixMode() && matchedPrefix != nil { // 获取白名单反转标志 whiteBypassRevers := config.GetWhiteBypassRevers() diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index 9905aada..651827d6 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -4,10 +4,12 @@ import ( "bytes" "context" "encoding/base64" + "fmt" "io" "net/http" "os" "strconv" + "strings" "time" "github.com/hoshinonyaruko/gensokyo/callapi" @@ -18,6 +20,7 @@ import ( "github.com/hoshinonyaruko/gensokyo/mylog" "github.com/hoshinonyaruko/gensokyo/silk" "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/dto/keyboard" "github.com/tencent-connect/botgo/openapi" ) @@ -33,22 +36,18 @@ func HandleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap // 当 message.Echo 是字符串类型时执行此块 msgType = echo.GetMsgTypeByKey(echoStr) } - //如果获取不到 就用user_id获取信息类型 - if msgType == "" { - msgType = GetMessageTypeByUserid(config.GetAppIDStr(), message.Params.UserID) - } - - //如果获取不到 就用group_id获取信息类型 if msgType == "" { msgType = GetMessageTypeByGroupid(config.GetAppIDStr(), message.Params.GroupID) } - //新增 内存获取不到从数据库获取 if msgType == "" { - msgType = GetMessageTypeByUseridV2(message.Params.UserID) + msgType = GetMessageTypeByUserid(config.GetAppIDStr(), message.Params.UserID) } if msgType == "" { msgType = GetMessageTypeByGroupidV2(message.Params.GroupID) } + if msgType == "" { + msgType = GetMessageTypeByUseridV2(message.Params.UserID) + } mylog.Printf("send_group_msg获取到信息类型:%v", msgType) var idInt64 int64 var err error @@ -86,6 +85,11 @@ func HandleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap //由于实现了Params的自定义unmarshell 所以可以类型安全的断言为string messageID = echo.GetLazyMessagesId(message.Params.GroupID.(string)) mylog.Printf("GetLazyMessagesId: %v", messageID) + //如果应用端传递了user_id 就让at不要顺序乱套 + if message.Params.UserID != nil { + messageID = echo.GetLazyMessagesId(message.Params.UserID.(string)) + mylog.Printf("GetLazyMessagesIdv2: %v", messageID) + } if messageID != "" { //尝试发送栈内信息 SSM = true @@ -202,26 +206,52 @@ func HandleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap mylog.Printf("Error: Expected RichMediaMessage type for key ") return "", nil } - // 上传图片并获取FileInfo - fileInfo, err := uploadMedia(context.TODO(), message.Params.GroupID.(string), richMediaMessage, apiv2) - if err != nil { - mylog.Printf("上传图片失败: %v", err) - return "", nil // 或其他错误处理 + var groupMessage *dto.MessageToCreate + var transmd bool + var md *dto.Markdown + var kb *keyboard.MessageKeyboard + //判断是否需要自动转换md + if config.GetTwoWayEcho() { + md, kb, transmd = auto_md(message, messageText, richMediaMessage) } - // 创建包含文本和图像信息的消息 - msgseq = echo.GetMappingSeq(messageID) - echo.AddMappingSeq(messageID, msgseq+1) - groupMessage := &dto.MessageToCreate{ - Content: messageText, // 添加文本内容 - Media: dto.Media{ - FileInfo: fileInfo, // 添加图像信息 - }, - MsgID: messageID, - MsgSeq: msgseq, - MsgType: 7, // 假设7是组合消息类型 - } - groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + //如果没有转换成md发送 + if !transmd { + // 上传图片并获取FileInfo + fileInfo, err := uploadMedia(context.TODO(), message.Params.GroupID.(string), richMediaMessage, apiv2) + if err != nil { + mylog.Printf("上传图片失败: %v", err) + return "", nil // 或其他错误处理 + } + // 创建包含文本和图像信息的消息 + msgseq = echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupMessage = &dto.MessageToCreate{ + Content: messageText, // 添加文本内容 + Media: dto.Media{ + FileInfo: fileInfo, // 添加图像信息 + }, + MsgID: messageID, + MsgSeq: msgseq, + MsgType: 7, // 假设7是组合消息类型 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + } else { + //将kb和md组合成groupMessage并用MsgType=2发送 + + msgseq = echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupMessage = &dto.MessageToCreate{ + Content: "markdown", // 添加文本内容 + MsgID: messageID, + MsgSeq: msgseq, + Markdown: md, + Keyboard: kb, + MsgType: 2, // 假设7是组合消息类型 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + + } // 发送组合消息 ret, err = apiv2.PostGroupMessage(context.TODO(), message.Params.GroupID.(string), groupMessage) if err != nil { @@ -1020,3 +1050,169 @@ func SendStackMessages(apiv2 openapi.OpenAPI, messageid string, originalGroupID } } + +func auto_md(message callapi.ActionMessage, messageText string, richMediaMessage *dto.RichMediaMessage) (md *dto.Markdown, kb *keyboard.MessageKeyboard, transmd bool) { + if echoStr, ok := message.Echo.(string); ok { + // 当 message.Echo 是字符串类型时才执行此块 + msg_on_touch := echo.GetMsgIDv3(config.GetAppIDStr(), echoStr) + mylog.Printf("msg_on_touch:%v", msg_on_touch) + // 判断是否是 GetVisualkPrefixs 数组开头的文本 + visualkPrefixs := config.GetVisualkPrefixs() + var matchedPrefix *config.VisualPrefixConfig + // 去掉前缀开头的* + for i, vp := range visualkPrefixs { + if strings.HasPrefix(vp.Prefix, "*") { + visualkPrefixs[i].Prefix = strings.TrimPrefix(vp.Prefix, "*") + } + } + + for _, vp := range visualkPrefixs { + if strings.HasPrefix(msg_on_touch, vp.Prefix) { + if len(msg_on_touch) >= len(vp.Prefix) { + if msg_on_touch != " " { + transmd = true + matchedPrefix = &vp + break // 匹配到了 + } + } + } + } + if transmd { + //将messageText和groupReply组合成一个md + // 处理 Markdown + CustomTemplateID := config.GetCustomTemplateID() + imgURL := richMediaMessage.URL + height, width, err := images.GetImageDimensions(imgURL) + if err != nil { + mylog.Printf("获取图片宽高出错") + } + imgDesc := fmt.Sprintf("图片 #%dpx #%dpx", width, height) + // 将所有的\r\n替换为\r + messageText = strings.ReplaceAll(messageText, "\r\n", "\r") + // 将所有的\n替换为\r + messageText = strings.ReplaceAll(messageText, "\n", "\r") + // 检查messageText是否以\r开头 + if !strings.HasPrefix(messageText, "\r") { + messageText = "\r" + messageText + } + // 创建 MarkdownParams 的实例 + mdParams := []*dto.MarkdownParams{ + {Key: "text_start", Values: []string{" "}}, //空着 + {Key: "img_dec", Values: []string{imgDesc}}, + {Key: "img_url", Values: []string{imgURL}}, + {Key: "text_end", Values: []string{messageText}}, + } + // 组合模板 Markdown + md = &dto.Markdown{ + CustomTemplateID: CustomTemplateID, + Params: mdParams, + } + whiteList := matchedPrefix.WhiteList + // 创建 CustomKeyboard + customKeyboard := &keyboard.CustomKeyboard{ + Rows: []*keyboard.Row{}, + } + + var currentRow *keyboard.Row + buttonCount := 0 // 当前行的按钮计数 + + for _, whiteLabel := range whiteList { + // 如果whiteList的成员数大于或等于15,才检查whiteLabel是否为纯数字 + if len(whiteList) >= 15 { + if _, err := strconv.Atoi(whiteLabel); err == nil { + // 如果没有错误,表示 whiteLabel 是一个数字,因此忽略这个元素并继续下一个迭代 + // 避免 因为纯数字按钮太多导致混乱,但是少量的纯数字按钮可以允许 + continue + } + } + // 检查msg_on_touch是否已经以whiteLabel结尾 + //场景 按钮递归时 比如 随机meme 再来一次,同时随机meme是*类型一级指令 + var dataLabel string + if !strings.HasSuffix(msg_on_touch, whiteLabel) { + dataLabel = msg_on_touch + whiteLabel + } else { + dataLabel = msg_on_touch + } + + var actiontype keyboard.ActionType + var permission *keyboard.Permission + var actiondata string + //检查是否设置了enter数组 + enter := checkDataLabelPrefix(dataLabel) + switch whiteLabel { + case "邀请机器人": + botuin := config.GetUinStr() + botappid := config.GetAppIDStr() + boturl := BuildQQBotShareLink(botuin, botappid) + actiontype = 0 + actiondata = boturl + permission = &keyboard.Permission{ + Type: 2, // 所有人可操作 + } + case "测试按钮回调": + actiontype = 1 + actiondata = "收到就代表是管理员哦" + permission = &keyboard.Permission{ + Type: 1, // 仅管理可操作 + } + default: + actiontype = 2 //帮用户输入指令 + actiondata = dataLabel //从虚拟前缀的二级指令组合md按钮 + permission = &keyboard.Permission{ + Type: 2, // 所有人可操作 + } + } + // 创建按钮 + button := &keyboard.Button{ + RenderData: &keyboard.RenderData{ + Label: whiteLabel, + VisitedLabel: whiteLabel, + Style: 1, //蓝色边缘 + }, + Action: &keyboard.Action{ + Type: actiontype, + Permission: permission, + Data: actiondata, + UnsupportTips: "请升级新版手机QQ", + Enter: enter, + }, + } + + // 如果当前行为空或已满(4个按钮),则创建一个新行 + if currentRow == nil || buttonCount == 4 { + currentRow = &keyboard.Row{} + customKeyboard.Rows = append(customKeyboard.Rows, currentRow) + buttonCount = 0 // 重置按钮计数 + } + + // 将按钮添加到当前行 + currentRow.Buttons = append(currentRow.Buttons, button) + buttonCount++ + } + + // 在循环结束后,最后一行可能不满4个按钮,但已经被正确处理 + + // 创建 MessageKeyboard 并设置其 Content + kb = &keyboard.MessageKeyboard{ + Content: customKeyboard, + } + } + } + return md, kb, transmd +} + +// 构建QQ群机器人分享链接的函数 +func BuildQQBotShareLink(uin string, appid string) string { + return fmt.Sprintf("https://qun.qq.com/qunpro/robot/qunshare?robot_uin=%s&robot_appid=%s", uin, appid) +} + +// 检查dataLabel是否以config中getenters返回的任一字符串开头 +func checkDataLabelPrefix(dataLabel string) bool { + enters := config.GetEnters() + for _, enter := range enters { + if enter != "" && strings.HasPrefix(dataLabel, enter) { + return true + } + } + return false +} diff --git a/handlers/send_guild_channel_msg.go b/handlers/send_guild_channel_msg.go index c84ed3e5..c625902b 100644 --- a/handlers/send_guild_channel_msg.go +++ b/handlers/send_guild_channel_msg.go @@ -34,20 +34,17 @@ func HandleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 // 当 message.Echo 是字符串类型时执行此块 msgType = echo.GetMsgTypeByKey(echoStr) } - //如果获取不到 就用group_id获取信息类型 if msgType == "" { msgType = GetMessageTypeByGroupid(config.GetAppIDStr(), message.Params.GroupID) } - //如果获取不到 就用user_id获取信息类型 if msgType == "" { msgType = GetMessageTypeByUserid(config.GetAppIDStr(), message.Params.UserID) } - //新增 内存获取不到从数据库获取 if msgType == "" { - msgType = GetMessageTypeByUseridV2(message.Params.UserID) + msgType = GetMessageTypeByGroupidV2(message.Params.GroupID) } if msgType == "" { - msgType = GetMessageTypeByGroupidV2(message.Params.GroupID) + msgType = GetMessageTypeByUseridV2(message.Params.UserID) } //当不转换频道信息时(不支持频道私聊) if msgType == "" { diff --git a/handlers/send_msg.go b/handlers/send_msg.go index 1982ae06..f5ba50e9 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -25,28 +25,28 @@ func HandleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope // 当 message.Echo 是字符串类型时执行此块 msgType = echo.GetMsgTypeByKey(echoStr) } - //如果获取不到 就用group_id获取信息类型 if msgType == "" { msgType = GetMessageTypeByGroupid(config.GetAppIDStr(), message.Params.GroupID) } - //如果获取不到 就用user_id获取信息类型 if msgType == "" { msgType = GetMessageTypeByUserid(config.GetAppIDStr(), message.Params.UserID) } - //新增 内存获取不到从数据库获取 if msgType == "" { - msgType = GetMessageTypeByUseridV2(message.Params.UserID) + msgType = GetMessageTypeByGroupidV2(message.Params.GroupID) } if msgType == "" { - msgType = GetMessageTypeByGroupidV2(message.Params.GroupID) + msgType = GetMessageTypeByUseridV2(message.Params.UserID) } - var idInt64 int64 + + var idInt64, idInt642 int64 var err error if message.Params.GroupID != "" { - idInt64, err = ConvertToInt64(message.Params.GroupID) + idInt64, _ = ConvertToInt64(message.Params.GroupID) + idInt642, _ = ConvertToInt64(message.Params.UserID) } else if message.Params.UserID != "" { - idInt64, err = ConvertToInt64(message.Params.UserID) + idInt64, _ = ConvertToInt64(message.Params.UserID) + idInt642, _ = ConvertToInt64(message.Params.GroupID) } //设置递归 对直接向gsk发送action时有效果 @@ -99,6 +99,7 @@ func HandleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope //重置递归类型 if echo.GetMapping(idInt64) <= 0 { echo.AddMsgType(config.GetAppIDStr(), idInt64, "") + echo.AddMsgType(config.GetAppIDStr(), idInt642, "") } echo.AddMapping(idInt64, echo.GetMapping(idInt64)-1) diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index 43bcba67..6dcefe7d 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -29,14 +29,14 @@ func HandleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open msgType = echo.GetMsgTypeByKey(echoStr) } - //如果获取不到 就用group_id获取信息类型 - if msgType == "" { - msgType = GetMessageTypeByGroupid(config.GetAppIDStr(), message.Params.GroupID) - } //如果获取不到 就用user_id获取信息类型 if msgType == "" { msgType = GetMessageTypeByUserid(config.GetAppIDStr(), message.Params.UserID) } + //顺序,私聊优先从UserID推断类型会更准确 + if msgType == "" { + msgType = GetMessageTypeByGroupid(config.GetAppIDStr(), message.Params.GroupID) + } //新增 内存获取不到从数据库获取 if msgType == "" { msgType = GetMessageTypeByUseridV2(message.Params.UserID) @@ -68,7 +68,7 @@ func HandleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open } switch msgType { - case "group_private","group": + case "group_private", "group": //私聊信息 var UserID string if config.GetIdmapPro() { @@ -266,7 +266,7 @@ func HandleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open retmsg, _ = SendResponse(client, err, &message) } } - case "guild_private","guild": + case "guild_private", "guild": //当收到发私信调用 并且来源是频道 retmsg, _ = HandleSendGuildChannelPrivateMsg(client, api, apiv2, message, nil, nil) default: diff --git a/images/format.go b/images/format.go new file mode 100644 index 00000000..a5cb61c2 --- /dev/null +++ b/images/format.go @@ -0,0 +1,180 @@ +package images + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +// 宽度 高度 +func GetImageDimensions(url string) (int, int, error) { + if strings.HasSuffix(url, ".png") { + return getPNGDimensions(url) + } else if strings.HasSuffix(url, ".jpg") || strings.HasSuffix(url, ".jpeg") { + return getJpegDimensions(url) + } else if strings.HasSuffix(url, ".gif") { + return getGIFDimensions(url) + } + return 0, 0, fmt.Errorf("unsupported image format") +} + +func getPNGDimensions(url string) (int, int, error) { + resp, err := http.Get(url) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while making request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return 0, 0, fmt.Errorf("failed to download image with status code: %d", resp.StatusCode) + } + + // 只读取前33字节 + buffer := make([]byte, 33) + _, err = io.ReadFull(resp.Body, buffer) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading the header: %w", err) + } + + // 检查PNG文件头 + if bytes.HasPrefix(buffer, []byte("\x89PNG\r\n\x1a\n")) { + var width, height int32 + reader := bytes.NewReader(buffer[16:24]) + err := binary.Read(reader, binary.BigEndian, &width) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading width: %w", err) + } + err = binary.Read(reader, binary.BigEndian, &height) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading height: %w", err) + } + return int(width), int(height), nil + } + + width, height, err := getJpegDimensions(url) + return width, height, err +} + +func getJpegDimensions(url string) (int, int, error) { + response, err := http.Get(url) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while making request: %w", err) + } + defer response.Body.Close() + + if response.StatusCode != 200 { + return 0, 0, fmt.Errorf("failed to download image with status code: %d", response.StatusCode) + } + + bytesRead := 0 // 用于跟踪读取的字节数 + for { + b := make([]byte, 1) + _, err := response.Body.Read(b) + bytesRead++ // 更新读取的字节数 + if err == io.EOF { + return 0, 0, fmt.Errorf("reached end of file before finding dimensions") + } else if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading byte: %w", err) + } + + if b[0] == 0xFF { + nextByte := make([]byte, 1) + _, err := response.Body.Read(nextByte) + if err == io.EOF { + return 0, 0, fmt.Errorf("reached end of file before finding SOI marker") + } else if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading next byte: %w", err) + } + + if nextByte[0] == 0xD8 { // SOI + continue + } else if (nextByte[0] >= 0xC0 && nextByte[0] <= 0xCF) && nextByte[0] != 0xC4 && nextByte[0] != 0xC8 && nextByte[0] != 0xCC { + c := make([]byte, 3) + _, err := response.Body.Read(c) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while skipping bytes: %w", err) + } + heightBytes := make([]byte, 2) + widthBytes := make([]byte, 2) + _, err = response.Body.Read(heightBytes) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading height: %w", err) + } + _, err = response.Body.Read(widthBytes) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading width: %w", err) + } + // 使用binary.BigEndian.Uint16将字节转换为uint16 + height := binary.BigEndian.Uint16(heightBytes) + width := binary.BigEndian.Uint16(widthBytes) + return int(height), int(width), nil + } else { + time.Sleep(5 * time.Millisecond) + lengthBytes := make([]byte, 2) + _, err := response.Body.Read(lengthBytes) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading segment length: %w", err) + } + + length := binary.BigEndian.Uint16(lengthBytes) + bytesToSkip := int(length) - 2 + + // 循环读取并跳过指定的字节数 + for bytesToSkip > 0 { + bufferSize := bytesToSkip + if bufferSize > 512 { // 可以调整这个值以优化性能和内存使用 + bufferSize = 512 //如果成功率低 就继续减少它 + } + buffer := make([]byte, bufferSize) + n, err := response.Body.Read(buffer) + if err != nil { + if err == io.EOF { + break // 数据流结束 + } + return 0, 0, fmt.Errorf("error occurred while skipping segment: %w", err) + } + bytesToSkip -= n + } + } + } + } +} + +func getGIFDimensions(url string) (int, int, error) { + resp, err := http.Get(url) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while making request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return 0, 0, fmt.Errorf("failed to download image with status code: %d", resp.StatusCode) + } + + // 仅读取前10字节 + buffer := make([]byte, 10) + _, err = io.ReadFull(resp.Body, buffer) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading the header: %w", err) + } + + if bytes.HasPrefix(buffer, []byte("GIF")) { + var width, height int16 + reader := bytes.NewReader(buffer[6:10]) + err := binary.Read(reader, binary.LittleEndian, &width) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading width: %w", err) + } + err = binary.Read(reader, binary.LittleEndian, &height) + if err != nil { + return 0, 0, fmt.Errorf("error occurred while reading height: %w", err) + } + return int(width), int(height), nil + } + + return 0, 0, fmt.Errorf("not a valid GIF file") +} diff --git a/main.go b/main.go index e45c8c6a..b2ca9fdd 100644 --- a/main.go +++ b/main.go @@ -523,7 +523,6 @@ func DirectMessageHandler() event.DirectMessageEventHandler { // CreateMessageHandler 处理消息事件 私域的事件 不at信息 func CreateMessageHandler() event.MessageEventHandler { return func(event *dto.WSPayload, data *dto.WSMessageData) error { - log.Println("收到私域信息", data) return p.ProcessGuildNormalMessage(data) } } @@ -531,7 +530,7 @@ func CreateMessageHandler() event.MessageEventHandler { // InteractionHandler 处理内联交互事件 func InteractionHandler() event.InteractionEventHandler { return func(event *dto.WSPayload, data *dto.WSInteractionData) error { - log.Println(data) + mylog.Printf("收到按钮回调:%v", data) return p.ProcessInlineSearch(data) } } diff --git a/readme.md b/readme.md index d8f7a8ee..90341e90 100644 --- a/readme.md +++ b/readme.md @@ -40,6 +40,9 @@ _✨ 基于 [OneBot](https://github.com/howmanybots/onebot/blob/master/README.md · 参与贡献
+ ## 引用 - [`tencent-connect/botgo`](https://github.com/tencent-connect/botgo): 本项目引用了此项目,并做了一点改动. diff --git a/template/config_template.go b/template/config_template.go index f01a81d8..cda74bc4 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -6,7 +6,8 @@ settings: #反向ws设置 ws_address: ["ws://