diff --git a/Processor/ProcessInlineSearch.go b/Processor/ProcessInlineSearch.go new file mode 100644 index 00000000..fa03d10f --- /dev/null +++ b/Processor/ProcessInlineSearch.go @@ -0,0 +1,214 @@ +// 处理收到的回调事件 +package Processor + +import ( + "context" + "sync" + "time" + + "github.com/hoshinonyaruko/gensokyo/config" + "github.com/hoshinonyaruko/gensokyo/handlers" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/hoshinonyaruko/gensokyo/mylog" + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/openapi" +) + +var ( + // 用户或群组ID到下一次允许执行操作的时间戳的映射 + nextAllowedTime = make(map[string]time.Time) + mu sync.Mutex +) + +// ProcessInlineSearch 处理内联查询 +func (p *Processors) ProcessInlineSearch(data *dto.WSInteractionData) error { + // 转换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" + } + //这里处理自动handle回调回应 + if config.GetAutoPutInteraction() { + DelayedPutInteraction(p.Api, data.ID, fromuid, fromgid) + } + 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 + } + } + if !config.GetGlobalInteractionToMessage() { + notice := &OnebotInteractionNotice{ + GroupID: GroupID64, + NoticeType: "interaction", + PostType: "notice", + SelfID: int64(p.Settings.AppID), + SubType: "create", + Time: time.Now().Unix(), + UserID: userid64, + Data: data, + } + //调试 + PrintStructWithFieldNames(notice) + + // Convert OnebotGroupMessage to map and send + noticeMap := structToMap(notice) + + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(noticeMap) + } else { + if data.GroupOpenID != "" { + //群回调 + newdata := ConvertInteractionToMessage(data) + segmentedMessages := handlers.ConvertToSegmentedMessage(newdata) + //映射str的messageID到int + messageID64, err := idmap.StoreIDv2(data.ID) + if err != nil { + mylog.Printf("Error storing ID: %v", err) + return nil + } + messageID := int(messageID64) + groupMsg := OnebotGroupMessage{ + RawMessage: data.Data.Resolved.ButtonData, + Message: segmentedMessages, + MessageID: messageID, + GroupID: GroupID64, + MessageType: "group", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + Sender: Sender{ + UserID: userid64, + Sex: "0", + Age: 0, + Area: "0", + Level: "0", + }, + SubType: "normal", + Time: time.Now().Unix(), + } + //增强配置 + if !config.GetNativeOb11() { + groupMsg.RealMessageType = "interaction" + } + // 调试 + PrintStructWithFieldNames(groupMsg) + + // Convert OnebotGroupMessage to map and send + groupMsgMap := structToMap(groupMsg) + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(groupMsgMap) + } else { + //频道回调 + // 处理onebot_channel_message逻辑 + //群回调 + newdata := ConvertInteractionToMessage(data) + segmentedMessages := handlers.ConvertToSegmentedMessage(newdata) + + onebotMsg := OnebotChannelMessage{ + ChannelID: data.ChannelID, + GuildID: data.GuildID, + Message: segmentedMessages, + RawMessage: data.Data.Resolved.ButtonData, + MessageID: data.ID, + MessageType: "guild", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + SelfTinyID: "0", + Sender: Sender{ + Nickname: "频道按钮回调", + TinyID: "0", + UserID: userid64, + Card: "频道按钮回调", + Sex: "0", + Age: 0, + Area: "0", + Level: "0", + }, + SubType: "channel", + Time: time.Now().Unix(), + Avatar: "", + } + //增强配置 + if !config.GetNativeOb11() { + onebotMsg.RealMessageType = "interaction" + } + //调试 + PrintStructWithFieldNames(onebotMsg) + + // 将 onebotMsg 结构体转换为 map[string]interface{} + msgMap := structToMap(onebotMsg) + + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(msgMap) + } + } + + return nil +} + +// ConvertInteractionToMessage 转换 Interaction 到 Message +func ConvertInteractionToMessage(interaction *dto.WSInteractionData) dto.Message { + var message dto.Message + + // 直接映射的字段 + message.ID = interaction.ID + message.ChannelID = interaction.ChannelID + message.GuildID = interaction.GuildID + message.GroupID = interaction.GroupOpenID + + // 特殊处理的字段 + message.Content = interaction.Data.Resolved.ButtonData + message.DirectMessage = interaction.ChatType == 2 + + return message +} + +// 延迟执行PutInteraction +func DelayedPutInteraction(o openapi.OpenAPI, interactionID, fromuid, fromgid string) { + key := fromuid + if fromuid == "0" { + key = fromgid + } + + mu.Lock() + delay := config.GetPutInteractionDelay() + nextTime, exists := nextAllowedTime[key] + if !exists || time.Now().After(nextTime) { + nextAllowedTime[key] = time.Now().Add(time.Millisecond * time.Duration(delay)) + mu.Unlock() + o.PutInteraction(context.TODO(), interactionID, `{"code": 0}`) + } else { + mu.Unlock() + time.Sleep(time.Until(nextTime)) + DelayedPutInteraction(o, interactionID, fromuid, fromgid) // 重新尝试 + } +} diff --git a/Processor/ProcessThreadMessage.go b/Processor/ProcessThreadMessage.go new file mode 100644 index 00000000..c72934c0 --- /dev/null +++ b/Processor/ProcessThreadMessage.go @@ -0,0 +1,465 @@ +// 处理收到的帖子信息事件 +package Processor + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/hoshinonyaruko/gensokyo/config" + "github.com/hoshinonyaruko/gensokyo/echo" + "github.com/hoshinonyaruko/gensokyo/handlers" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/hoshinonyaruko/gensokyo/mylog" + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/websocket/client" +) + +// ProcessInlineSearch 处理帖子事件 +func (p *Processors) ProcessThreadMessage(data *dto.WSThreadData) error { + // 过滤,仅当ID以"FORUM_THREAD_CREATE"开头时继续执行 后期再改 + if !strings.HasPrefix(data.ID, "FORUM_THREAD_CREATE") { + return nil + } + if !p.Settings.GlobalForumToChannel { + //原始帖子类型 + // 将时间字符串转换为时间戳 + t, err := time.Parse(time.RFC3339, string(data.ThreadInfo.DateTime)) + if err != nil { + return fmt.Errorf("error parsing time: %v", err) + } + //获取s + s := client.GetGlobalS() + //转换at + //帖子没有at + //框架内指令 + //帖子不需要 + //转换appid + AppIDString := strconv.FormatUint(p.Settings.AppID, 10) + //构造echo + echostr := AppIDString + "_" + strconv.FormatInt(s, 10) + //映射str的userid到int + userid64, err := idmap.StoreIDv2(data.AuthorID) + if err != nil { + mylog.Printf("Error storing ID: %v", err) + return nil + } + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = data.ThreadInfo.Content + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } + messageText, err := parseContent(data.ThreadInfo.Content) + if err != nil { + mylog.Printf("Error parseContent Forum: %v", err) + } + // 处理onebot_channel_message逻辑 + onebotMsg := OnebotChannelMessage{ + ChannelID: data.ChannelID, + GuildID: data.GuildID, + Message: segmentedMessages, + RawMessage: messageText, + MessageID: data.ID, + MessageType: "guild", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + SelfTinyID: "0", + Sender: Sender{ + Nickname: "发帖人", + TinyID: "0", + UserID: userid64, + Card: "发帖人昵称", + Sex: "0", + Age: 0, + Area: "0", + Level: "0", + }, + SubType: "forum", + Time: t.Unix(), + } + // 根据条件判断是否添加Echo字段 + if config.GetTwoWayEcho() { + onebotMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) + } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + onebotMsg.Sender.Role = "owner" + } else { + onebotMsg.Sender.Role = "member" + } + //将当前s和appid和message进行映射 + echo.AddMsgID(AppIDString, s, data.ID) + echo.AddMsgType(AppIDString, s, "forum") + //为不支持双向echo的ob11服务端映射 + echo.AddMsgID(AppIDString, userid64, data.ID) + //映射类型 + echo.AddMsgType(AppIDString, userid64, "forum") + //储存当前群或频道号的类型 + idmap.WriteConfigv2(data.ChannelID, "type", "forum") + //todo 完善频道ob信息 + //懒message_id池 + echo.AddLazyMessageId(data.ChannelID, data.ID, time.Now()) + //懒message_id池 + //echo.AddLazyMessageId(strconv.FormatInt(userid64, 10), data.ID, time.Now()) + //echo.AddLazyMessageIdv2(data.ChannelID, strconv.FormatInt(userid64, 10), data.ID, time.Now()) + + //调试 + PrintStructWithFieldNames(onebotMsg) + + // 将 onebotMsg 结构体转换为 map[string]interface{} + msgMap := structToMap(onebotMsg) + + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(msgMap) + + return nil + } else { + //转换为频道或者群 + if !p.Settings.GlobalChannelToGroup { + //转化为频道信息 + // 将时间字符串转换为时间戳 + t, err := time.Parse(time.RFC3339, string(data.ThreadInfo.DateTime)) + if err != nil { + return fmt.Errorf("error parsing time: %v", err) + } + //获取s + s := client.GetGlobalS() + //转换at + //帖子没有at + //框架内指令 + //帖子不需要 + //转换appid + AppIDString := strconv.FormatUint(p.Settings.AppID, 10) + //构造echo + echostr := AppIDString + "_" + strconv.FormatInt(s, 10) + //映射str的userid到int + userid64, err := idmap.StoreIDv2(data.AuthorID) + if err != nil { + mylog.Printf("Error storing ID: %v", err) + return nil + } + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = data.ThreadInfo.Content + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } + messageText, err := parseContent(data.ThreadInfo.Content) + if err != nil { + mylog.Printf("Error parseContent Forum: %v", err) + } + // 处理onebot_channel_message逻辑 + onebotMsg := OnebotChannelMessage{ + ChannelID: data.ChannelID, + GuildID: data.GuildID, + Message: segmentedMessages, + RawMessage: messageText, + MessageID: data.ID, + MessageType: "guild", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + SelfTinyID: "0", + Sender: Sender{ + Nickname: "发帖人", + TinyID: "0", + UserID: userid64, + Card: "发帖人昵称", + Sex: "0", + Age: 0, + Area: "0", + Level: "0", + }, + SubType: "channel", + Time: t.Unix(), + } + // 根据条件判断是否添加Echo字段 + if config.GetTwoWayEcho() { + onebotMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) + } + // 获取MasterID数组 + masterIDs := config.GetMasterID() + + // 判断userid64是否在masterIDs数组里 + isMaster := false + for _, id := range masterIDs { + if strconv.FormatInt(userid64, 10) == id { + isMaster = true + break + } + } + + // 根据isMaster的值为groupMsg的Sender赋值role字段 + if isMaster { + onebotMsg.Sender.Role = "owner" + } else { + onebotMsg.Sender.Role = "member" + } + //将当前s和appid和message进行映射 + echo.AddMsgID(AppIDString, s, data.ID) + echo.AddMsgType(AppIDString, s, "forum") + //为不支持双向echo的ob11服务端映射 + echo.AddMsgID(AppIDString, userid64, data.ID) + //映射类型 + echo.AddMsgType(AppIDString, userid64, "forum") + //储存当前群或频道号的类型 + idmap.WriteConfigv2(data.ChannelID, "type", "forum") + //todo 完善频道ob信息 + //懒message_id池 + echo.AddLazyMessageId(data.ChannelID, data.ID, time.Now()) + //懒message_id池 + //echo.AddLazyMessageId(strconv.FormatInt(userid64, 10), data.ID, time.Now()) + //echo.AddLazyMessageIdv2(data.ChannelID, strconv.FormatInt(userid64, 10), data.ID, time.Now()) + + //调试 + PrintStructWithFieldNames(onebotMsg) + + // 将 onebotMsg 结构体转换为 map[string]interface{} + msgMap := structToMap(onebotMsg) + + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(msgMap) + } else { + //转化为群信息 + //将频道转化为一个群 + //获取s + AppIDString := strconv.FormatUint(p.Settings.AppID, 10) + s := client.GetGlobalS() + var userid64 int64 + var ChannelID64 int64 + var err error + if config.GetIdmapPro() { + //将真实id转为int userid64 + ChannelID64, userid64, err = idmap.StoreIDv2Pro(data.ChannelID, data.AuthorID) + if err != nil { + mylog.Fatalf("Error storing ID: %v", err) + } + //当参数不全时 + _, _ = idmap.StoreIDv2(data.ChannelID) + _, _ = idmap.StoreIDv2(data.AuthorID) + if !config.GetHashIDValue() { + mylog.Fatalf("避坑日志:你开启了高级id转换,请设置hash_id为true,并且删除idmaps并重启") + } + //补救措施 + idmap.SimplifiedStoreID(data.AuthorID) + //补救措施 + idmap.SimplifiedStoreID(data.ChannelID) + //补救措施 + echo.AddMsgIDv3(AppIDString, data.ChannelID, data.ID) + } else { + //将channelid写入ini,可取出guild_id + ChannelID64, err = idmap.StoreIDv2(data.ChannelID) + if err != nil { + mylog.Printf("Error storing ID: %v", err) + return nil + } + //映射str的userid到int + userid64, err = idmap.StoreIDv2(data.AuthorID) + if err != nil { + mylog.Printf("Error storing ID: %v", err) + return nil + } + } + //转成int再互转 + idmap.WriteConfigv2(fmt.Sprint(ChannelID64), "guild_id", data.GuildID) + //储存原来的(获取群列表需要) + idmap.WriteConfigv2(data.ChannelID, "guild_id", data.GuildID) + + messageText, err := parseContent(data.ThreadInfo.Content) + if err != nil { + mylog.Printf("Error parseContent Forum: %v", err) + } + //构造echo + echostr := AppIDString + "_" + strconv.FormatInt(s, 10) + //映射str的messageID到int + messageID64, err := idmap.StoreIDv2(data.ID) + if err != nil { + mylog.Printf("Error storing ID: %v", err) + return nil + } + messageID := int(messageID64) + // 如果在Array模式下, 则处理Message为Segment格式 + var segmentedMessages interface{} = messageText + if config.GetArrayValue() { + segmentedMessages = handlers.ConvertToSegmentedMessage(data) + } + var IsBindedUserId, IsBindedGroupId bool + if config.GetHashIDValue() { + IsBindedUserId = idmap.CheckValue(data.AuthorID, userid64) + IsBindedGroupId = idmap.CheckValue(data.ChannelID, ChannelID64) + } else { + IsBindedUserId = idmap.CheckValuev2(userid64) + IsBindedGroupId = idmap.CheckValuev2(ChannelID64) + } + groupMsg := OnebotGroupMessage{ + RawMessage: messageText, + Message: segmentedMessages, + MessageID: messageID, + GroupID: ChannelID64, + MessageType: "group", + PostType: "message", + SelfID: int64(p.Settings.AppID), + UserID: userid64, + Sender: Sender{ + Nickname: "发帖人昵称", + UserID: userid64, + Card: "发帖人昵称", + Sex: "0", + Age: 0, + Area: "", + Level: "0", + }, + SubType: "normal", + Time: time.Now().Unix(), + } + //增强配置 + if !config.GetNativeOb11() { + groupMsg.RealMessageType = "forum" + groupMsg.IsBindedUserId = IsBindedUserId + groupMsg.IsBindedGroupId = IsBindedGroupId + } + // 根据条件判断是否添加Echo字段 + if config.GetTwoWayEcho() { + groupMsg.Echo = echostr + //用向应用端(如果支持)发送echo,来确定客户端的send_msg对应的触发词原文 + echo.AddMsgIDv3(AppIDString, echostr, messageText) + } + + //将当前s和appid和message进行映射 + echo.AddMsgID(AppIDString, s, data.ID) + echo.AddMsgType(AppIDString, s, "forum") + //为不支持双向echo的ob服务端映射 + echo.AddMsgID(AppIDString, ChannelID64, data.ID) + //将当前的userid和groupid和msgid进行一个更稳妥的映射 + echo.AddMsgIDv2(AppIDString, ChannelID64, userid64, data.ID) + //储存当前群或频道号的类型 + idmap.WriteConfigv2(fmt.Sprint(ChannelID64), "type", "forum") + echo.AddMsgType(AppIDString, ChannelID64, "forum") + //懒message_id池 + echo.AddLazyMessageId(strconv.FormatInt(ChannelID64, 10), 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()) + + //调试 + PrintStructWithFieldNames(groupMsg) + + // Convert OnebotGroupMessage to map and send + groupMsgMap := structToMap(groupMsg) + + //上报信息到onebotv11应用端(正反ws) + p.BroadcastMessageToAll(groupMsgMap) + + } + } + return nil +} + +// UnmarshalForumContentElements 动态解析元素类型 +func UnmarshalForumContentElements(content string) ([]dto.ForumContentElement, error) { + var contentStructure dto.ForumContentStructure + err := json.Unmarshal([]byte(content), &contentStructure) + if err != nil { + return nil, err + } + + var elements []dto.ForumContentElement + for _, paragraph := range contentStructure.Paragraphs { + for _, rawElem := range paragraph.Elems { + var base struct { + Type int `json:"type"` + } + err := json.Unmarshal(rawElem, &base) + if err != nil { + fmt.Println("Error determining element type:", err) + continue + } + + switch base.Type { + case 1: // 文本元素 + var textElem dto.ForumTextElement + err := json.Unmarshal(rawElem, &textElem) + if err != nil { + fmt.Println("Error unmarshalling text element:", err) + continue + } + elements = append(elements, textElem) + case 3: // URL元素 + var urlElem dto.ForumURLElement + err := json.Unmarshal(rawElem, &urlElem) + if err != nil { + fmt.Println("Error unmarshalling URL element:", err) + continue + } + elements = append(elements, urlElem) + case 5: // 频道元素 + var channelElem dto.ForumChannelElement + err := json.Unmarshal(rawElem, &channelElem) + if err != nil { + fmt.Println("Error unmarshalling channel element:", err) + continue + } + elements = append(elements, channelElem) + default: + fmt.Printf("Unknown type: %d\n", base.Type) + } + } + } + + return elements, nil +} + +// parseContent 解析字符串content,并生成消息文本 +func parseContent(content string) (string, error) { + elements, err := UnmarshalForumContentElements(content) + if err != nil { + return "", err + } + mylog.Printf("测试:%v", elements) + + var messageTextBuilder strings.Builder + + // 计算是不是只有一个ForumTextElement而且没有其他元素 + onlyOneTextElement := len(elements) == 1 + if onlyOneTextElement { + _, onlyOneTextElement = elements[0].(dto.ForumTextElement) + } + + for _, element := range elements { + switch e := element.(type) { + case dto.ForumTextElement: + messageTextBuilder.WriteString(e.TextInfo.Text) + if !onlyOneTextElement { + messageTextBuilder.WriteString("\n") // 如果不是只有一个ForumTextElement,加换行符 + } + case dto.ForumURLElement: + if e.URLInfo.DisplayText != "" { + messageTextBuilder.WriteString(e.URLInfo.DisplayText + ": ") + } + messageTextBuilder.WriteString(e.URLInfo.URL) + messageTextBuilder.WriteString("\n") + case *dto.ForumChannelElement: + // 频道元素被忽略 + } + } + + return messageTextBuilder.String(), nil +} diff --git a/Processor/Processor.go b/Processor/Processor.go index 84552489..44f24d0d 100644 --- a/Processor/Processor.go +++ b/Processor/Processor.go @@ -55,21 +55,22 @@ type Sender struct { // 频道信息事件 type OnebotChannelMessage struct { - ChannelID string `json:"channel_id"` - GuildID string `json:"guild_id"` - Message interface{} `json:"message"` - MessageID string `json:"message_id"` - MessageType string `json:"message_type"` - PostType string `json:"post_type"` - SelfID int64 `json:"self_id"` - SelfTinyID string `json:"self_tiny_id"` - Sender Sender `json:"sender"` - SubType string `json:"sub_type"` - Time int64 `json:"time"` - Avatar string `json:"avatar,omitempty"` - UserID int64 `json:"user_id"` - RawMessage string `json:"raw_message"` - Echo string `json:"echo,omitempty"` + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id"` + Message interface{} `json:"message"` + MessageID string `json:"message_id"` + MessageType string `json:"message_type"` + PostType string `json:"post_type"` + SelfID int64 `json:"self_id"` + SelfTinyID string `json:"self_tiny_id"` + Sender Sender `json:"sender"` + SubType string `json:"sub_type"` + Time int64 `json:"time"` + Avatar string `json:"avatar,omitempty"` + UserID int64 `json:"user_id"` + RawMessage string `json:"raw_message"` + Echo string `json:"echo,omitempty"` + RealMessageType string `json:"real_message_type,omitempty"` //当前信息的真实类型 表情表态 } // 群信息事件 @@ -131,101 +132,6 @@ type PrivateSender struct { UserID int64 `json:"user_id"` // Can be either string or int depending on logic } -func FoxTimestamp() int64 { - return time.Now().Unix() -} - -// ProcessInlineSearch 处理内联查询 -func (p *Processors) ProcessInlineSearch(data *dto.WSInteractionData) error { - // 转换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, - } - - //调试 - PrintStructWithFieldNames(notice) - - // Convert OnebotGroupMessage to map and send - noticeMap := structToMap(notice) - - //上报信息到onebotv11应用端(正反ws) - p.BroadcastMessageToAll(noticeMap) - return nil -} - -//return nil - -//下面是测试时候固定代码 -//发私信给机器人4条机器人不回,就不能继续发了 - -// timestamp := time.Now().Unix() // 获取当前时间的int64类型的Unix时间戳 -// timestampStr := fmt.Sprintf("%d", timestamp) - -// dm := &dto.DirectMessage{ -// GuildID: GuildID, -// ChannelID: ChannelID, -// CreateTime: timestampStr, -// } - -// PrintStructWithFieldNames(dm) - -// // 发送默认回复 -// toCreate := &dto.MessageToCreate{ -// Content: "默认私信回复", -// MsgID: data.ID, -// } -// _, err = p.Api.PostDirectMessage( -// context.Background(), dm, toCreate, -// ) -// if err != nil { -// mylog.Println("Error sending default reply:", err) -// return nil -// } - // 打印结构体的函数 func PrintStructWithFieldNames(v interface{}) { val := reflect.ValueOf(v) diff --git a/botgo/dto/forum.go b/botgo/dto/forum.go index 1e0529d4..a8f5fcf9 100644 --- a/botgo/dto/forum.go +++ b/botgo/dto/forum.go @@ -1,14 +1,45 @@ package dto -// Thread 主题事件主体内容 -type Thread struct { - GuildID string `json:"guild_id"` - ChannelID string `json:"channel_id"` - AuthorID string `json:"author_id"` - ThreadInfo ThreadInfo `json:"thread_info"` +import "encoding/json" + +// ForumContentElement 接口定义了所有内容元素共有的方法,这里我们使用一个空接口,因为不同元素的方法会有所不同 +type ForumContentElement interface{} + +// ForumTextElement 文本元素结构体 +type ForumTextElement struct { + Type int `json:"type"` // 类型标识,例如1代表文本 + TextInfo struct { + Text string `json:"text"` + } `json:"text"` //这里文档与实际情况不一致 +} + +// ForumChannelElement 频道元素结构体 +type ForumChannelElement struct { + Type int `json:"type"` // 类型标识,例如5代表频道信息 + ChannelInfo struct { + ChannelID int `json:"channel_id"` + ChannelName string `json:"channel_name"` + } `json:"channel_info"` +} + +// ForumURLElement 链接元素结构体 +type ForumURLElement struct { + Type int `json:"type"` // 类型标识,例如3代表URL + URLInfo struct { + URL string `json:"url"` + DisplayText string `json:"display_text"` + } `json:"url_info"` +} + +// 定义匹配JSON结构的新结构体 +type ForumContentStructure struct { + Paragraphs []struct { + Elems []json.RawMessage `json:"elems"` // 使用json.RawMessage延迟解析 + Props json.RawMessage `json:"props"` + } `json:"paragraphs"` } -// ThreadInfo 主题信息 +// ThreadInfo 主题信息结构体更新,使用ContentElement接口切片来表示复杂的Content type ThreadInfo struct { ThreadID string `json:"thread_id"` Title string `json:"title"` @@ -16,6 +47,15 @@ type ThreadInfo struct { DateTime string `json:"date_time"` } +// Thread 主题事件主体内容 +type Thread struct { + GuildID string `json:"guild_id"` + ChannelID string `json:"channel_id"` + AuthorID string `json:"author_id"` + ThreadInfo ThreadInfo `json:"thread_info"` + ID string `json:"id,omitempty"` // 新增字段以存储ID +} + // Post 帖子事件主体内容 type Post struct { GuildID string `json:"guild_id"` diff --git a/botgo/dto/message.go b/botgo/dto/message.go index 1308b8c0..1b2647ff 100644 --- a/botgo/dto/message.go +++ b/botgo/dto/message.go @@ -43,6 +43,14 @@ type Message struct { Ret int `json:"ret,omitempty"` } +// Forum 消息结构体定义 +type Forum struct { + // 消息ID + TaskId string `json:"task_id"` + // 发送时间 秒级时间戳 + CreateTime string `json:"create_time"` +} + // GroupAddBotEvent 表示群添加机器人事件的数据结构 type GroupAddBotEvent struct { GroupOpenID string `json:"group_openid"` diff --git a/botgo/dto/message_create.go b/botgo/dto/message_create.go index 10f8c715..0e1be64b 100644 --- a/botgo/dto/message_create.go +++ b/botgo/dto/message_create.go @@ -58,6 +58,13 @@ type MessageToCreate struct { MsgSeq int `json:"msg_seq,omitempty"` //回复消息的序号,与 msg_id 联合使用,避免相同消息id回复重复发送,不填默认是1。相同的 msg_id + msg_seq 重复发送会失败。 } +// FourmToCreate 发送帖子结构体定义 +type FourmToCreate struct { + Title string `json:"title,omitempty"` + Content string `json:"content,omitempty"` + Format uint32 `json:"format,omitempty"` //消息类型: 1:文字消息 2:html信息 3: md消息 4:json信息 +} + // GetEventID 事件ID func (msg MessageToCreate) GetEventID() string { return msg.EventID diff --git a/botgo/event/event.go b/botgo/event/event.go index 0fdbb814..7229f991 100644 --- a/botgo/event/event.go +++ b/botgo/event/event.go @@ -76,6 +76,19 @@ func ParseAndHandle(payload *dto.WSPayload) error { // ParseData 解析数据 func ParseData(message []byte, target interface{}) error { + // 尝试检查target是否为*dto.WSThreadData类型 + if threadData, ok := target.(*dto.WSThreadData); ok { + // 特殊处理dto.WSThreadData + data := gjson.Get(string(message), "d") + if err := json.Unmarshal([]byte(data.String()), &threadData); err != nil { + return err + } + // 设置ID字段 + threadData.ID = gjson.Get(string(message), "id").String() + return nil + } + + // 对于其他类型,继续原有逻辑 data := gjson.Get(string(message), "d") return json.Unmarshal([]byte(data.String()), target) } diff --git a/botgo/openapi/iface.go b/botgo/openapi/iface.go index bf832ac1..d9670d8e 100644 --- a/botgo/openapi/iface.go +++ b/botgo/openapi/iface.go @@ -67,6 +67,8 @@ type MessageAPI interface { Message(ctx context.Context, channelID string, messageID string) (*dto.Message, error) Messages(ctx context.Context, channelID string, pager *dto.MessagesPager) ([]*dto.Message, error) PostMessage(ctx context.Context, channelID string, msg *dto.MessageToCreate) (*dto.Message, error) + // 增加的新的接口,发频道帖子信息 + PostFourm(ctx context.Context, channelID string, Fourm *dto.FourmToCreate) (*dto.Forum, error) // 增加的新的接口,发频道multipart信息 PostMessageMultipart(ctx context.Context, channelID string, msg *dto.MessageToCreate, fileImageData []byte) (*dto.Message, error) PatchMessage(ctx context.Context, diff --git a/botgo/openapi/v1/message.go b/botgo/openapi/v1/message.go index c13446f3..43e11c4c 100644 --- a/botgo/openapi/v1/message.go +++ b/botgo/openapi/v1/message.go @@ -72,6 +72,20 @@ func (o *openAPI) PostMessage(ctx context.Context, channelID string, msg *dto.Me return resp.Result().(*dto.Message), nil } +// PostFourm 发帖子 +func (o *openAPI) PostFourm(ctx context.Context, channelID string, msg *dto.FourmToCreate) (*dto.Forum, error) { + resp, err := o.request(ctx). + SetResult(dto.Forum{}). + SetPathParam("channel_id", channelID). + SetBody(msg). + Put(o.getURL(fourmMessagesURI)) + if err != nil { + return nil, err + } + + return resp.Result().(*dto.Forum), nil +} + // PostMessageMultipart 发送消息使用multipart/form-data func (o *openAPI) PostMessageMultipart(ctx context.Context, channelID string, msg *dto.MessageToCreate, fileImageData []byte) (*dto.Message, error) { request := o.request(ctx).SetResult(dto.Message{}).SetPathParam("channel_id", channelID) diff --git a/botgo/openapi/v1/resource.go b/botgo/openapi/v1/resource.go index 768718dc..85496f83 100644 --- a/botgo/openapi/v1/resource.go +++ b/botgo/openapi/v1/resource.go @@ -27,6 +27,7 @@ const ( channelRolesPermissionsURI uri = "/channels/{channel_id}/roles/{role_id}/permissions" messagesURI uri = "/channels/{channel_id}/messages" + fourmMessagesURI uri = "/channels/{channel_id}/threads" groupMessagesURI uri = "/v2/groups/{group_id}/messages" groupRichMediaURI uri = "/v2/groups/{group_id}/files" diff --git a/botgo/openapi/v2/message.go b/botgo/openapi/v2/message.go index e21dc89d..4627d3d0 100644 --- a/botgo/openapi/v2/message.go +++ b/botgo/openapi/v2/message.go @@ -72,6 +72,20 @@ func (o *openAPIv2) PostMessage(ctx context.Context, channelID string, msg *dto. return resp.Result().(*dto.Message), nil } +// PostFourm 发帖子 +func (o *openAPIv2) PostFourm(ctx context.Context, channelID string, msg *dto.FourmToCreate) (*dto.Forum, error) { + resp, err := o.request(ctx). + SetResult(dto.Forum{}). + SetPathParam("channel_id", channelID). + SetBody(msg). + Put(o.getURL(fourmMessagesURI)) + if err != nil { + return nil, err + } + + return resp.Result().(*dto.Forum), nil +} + // PostMessageMultipart 发送消息使用multipart/form-data func (o *openAPIv2) PostMessageMultipart(ctx context.Context, channelID string, msg *dto.MessageToCreate, fileImageData []byte) (*dto.Message, error) { request := o.request(ctx).SetResult(dto.Message{}).SetPathParam("channel_id", channelID) diff --git a/botgo/openapi/v2/resource.go b/botgo/openapi/v2/resource.go index 8ec71634..b8835c1a 100644 --- a/botgo/openapi/v2/resource.go +++ b/botgo/openapi/v2/resource.go @@ -27,6 +27,7 @@ const ( channelRolesPermissionsURI uri = "/channels/{channel_id}/roles/{role_id}/permissions" messagesURI uri = "/channels/{channel_id}/messages" + fourmMessagesURI uri = "/channels/{channel_id}/threads" groupMessagesURI uri = "/v2/groups/{group_id}/messages" groupRichMediaURI uri = "/v2/groups/{group_id}/files" diff --git a/config/config.go b/config/config.go index 58a16945..2a256828 100644 --- a/config/config.go +++ b/config/config.go @@ -31,119 +31,124 @@ type VisualPrefixConfig struct { NoWhiteResponse string `yaml:"No_White_Response"` } type Settings struct { - WsAddress []string `yaml:"ws_address"` - AppID uint64 `yaml:"app_id"` - Token string `yaml:"token"` - ClientSecret string `yaml:"client_secret"` - TextIntent []string `yaml:"text_intent"` - GlobalChannelToGroup bool `yaml:"global_channel_to_group"` - GlobalPrivateToChannel bool `yaml:"global_private_to_channel"` - Array bool `yaml:"array"` - Server_dir string `yaml:"server_dir"` - Lotus bool `yaml:"lotus"` - Port string `yaml:"port"` - WsToken []string `yaml:"ws_token,omitempty"` // 连接wss时使用,不是wss可留空 一一对应 - MasterID []string `yaml:"master_id,omitempty"` // 如果需要在群权限判断是管理员是,将user_id填入这里,master_id是一个文本数组 - EnableWsServer bool `yaml:"enable_ws_server,omitempty"` //正向ws开关 - WsServerToken string `yaml:"ws_server_token,omitempty"` //正向ws token - IdentifyFile bool `yaml:"identify_file"` // 域名校验文件 - Crt string `yaml:"crt"` - Key string `yaml:"key"` - DeveloperLog bool `yaml:"developer_log"` - Username string `yaml:"server_user_name"` - Password string `yaml:"server_user_password"` - ImageLimit int `yaml:"image_sizelimit"` - RemovePrefix bool `yaml:"remove_prefix"` - BackupPort string `yaml:"backup_port"` - DevlopAcDir string `yaml:"develop_access_token_dir"` - RemoveAt bool `yaml:"remove_at"` - DevBotid string `yaml:"develop_bot_id"` - SandBoxMode bool `yaml:"sandbox_mode"` - Title string `yaml:"title"` - HashID bool `yaml:"hash_id"` - TwoWayEcho bool `yaml:"twoway_echo"` - LazyMessageId bool `yaml:"lazy_message_id"` - WhitePrefixMode bool `yaml:"white_prefix_mode"` - WhitePrefixs []string `yaml:"white_prefixs"` - BlackPrefixMode bool `yaml:"black_prefix_mode"` - BlackPrefixs []string `yaml:"black_prefixs"` - VisualPrefixs []VisualPrefixConfig `yaml:"visual_prefixs"` - VisibleIp bool `yaml:"visible_ip"` - ForwardMsgLimit int `yaml:"forward_msg_limit"` - DevMessgeID bool `yaml:"dev_message_id"` - LogLevel int `yaml:"log_level"` - SaveLogs bool `yaml:"save_logs"` - BindPrefix string `yaml:"bind_prefix"` - MePrefix string `yaml:"me_prefix"` - FrpPort string `yaml:"frp_port"` - RemoveBotAtGroup bool `yaml:"remove_bot_at_group"` - ImageLimitB int `yaml:"image_limit"` - RecordSampleRate int `yaml:"record_sampleRate"` - RecordBitRate int `yaml:"record_bitRate"` - NoWhiteResponse string `yaml:"No_White_Response"` - SendError bool `yaml:"send_error"` - AddAtGroup bool `yaml:"add_at_group"` - UrlPicTransfer bool `yaml:"url_pic_transfer"` - LotusPassword string `yaml:"lotus_password"` - WsServerPath string `yaml:"ws_server_path"` - IdmapPro bool `yaml:"idmap_pro"` - CardAndNick string `yaml:"card_nick"` - AutoBind bool `yaml:"auto_bind"` - CustomBotName string `yaml:"custom_bot_name"` - SendDelay int `yaml:"send_delay"` - AtoPCount int `yaml:"AMsgRetryAsPMsg_Count"` - ReconnecTimes int `yaml:"reconnect_times"` - HeartBeatInterval int `yaml:"heart_beat_interval"` - LaunchReconectTimes int `yaml:"launch_reconnect_times"` - UnlockPrefix string `yaml:"unlock_prefix"` - WhiteBypass []int64 `yaml:"white_bypass"` - TransferUrl bool `yaml:"transfer_url"` - HttpAddress string `yaml:"http_address"` - HttpVersion int `yaml:"http_version"` - HttpTimeOut int `yaml:"http_timeout"` - PostUrl []string `yaml:"post_url"` - PostSecret []string `yaml:"post_secret"` - PostMaxRetries []int `yaml:"post_max_retries"` - PostRetriesInterval []int `yaml:"post_retries_interval"` - NativeOb11 bool `yaml:"native_ob11"` - RamDomSeq bool `yaml:"ramdom_seq"` - UrlToQrimage bool `yaml:"url_to_qrimage"` - QrSize int `yaml:"qr_size"` - WhiteBypassRevers bool `yaml:"white_bypass_reverse"` - GuildUrlImageToBase64 bool `yaml:"guild_url_image_to_base64"` - TencentBucketName string `yaml:"t_COS_BUCKETNAME"` - TencentBucketRegion string `yaml:"t_COS_REGION"` - TencentCosSecretid string `yaml:"t_COS_SECRETID"` - TencentSecretKey string `yaml:"t_COS_SECRETKEY"` - TencentAudit bool `yaml:"t_audit"` - OssType int `yaml:"oss_type"` - BaiduBOSBucketName string `yaml:"b_BOS_BUCKETNAME"` - BaiduBCEAK string `yaml:"b_BCE_AK"` - BaiduBCESK string `yaml:"b_BCE_SK"` - BaiduAudit int `yaml:"b_audit"` - AliyunEndpoint string `yaml:"a_OSS_EndPoint"` - AliyunAccessKeyId string `yaml:"a_OSS_AccessKeyId"` - AliyunAccessKeySecret string `yaml:"a_OSS_AccessKeySecret"` - AliyunBucketName string `yaml:"a_OSS_BucketName"` - AliyunAudit bool `yaml:"a_audit"` - Alias []string `yaml:"alias"` - SelfIntroduce []string `yaml:"self_introduce"` - 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"` - LinkPrefix string `yaml:"link_prefix"` - LinkBots []string `yaml:"link_bots"` - LinkText string `yaml:"link_text"` - LinkPic string `yaml:"link_pic"` - MusicPrefix string `yaml:"music_prefix"` - DisableWebui bool `yaml:"disable_webui"` - ShardCount int `yaml:"shard_count"` - ShardID int `yaml:"shard_id"` + WsAddress []string `yaml:"ws_address"` + AppID uint64 `yaml:"app_id"` + Token string `yaml:"token"` + ClientSecret string `yaml:"client_secret"` + TextIntent []string `yaml:"text_intent"` + GlobalChannelToGroup bool `yaml:"global_channel_to_group"` + GlobalPrivateToChannel bool `yaml:"global_private_to_channel"` + GlobalForumToChannel bool `yaml:"global_forum_to_channel"` + Array bool `yaml:"array"` + Server_dir string `yaml:"server_dir"` + Lotus bool `yaml:"lotus"` + Port string `yaml:"port"` + WsToken []string `yaml:"ws_token,omitempty"` // 连接wss时使用,不是wss可留空 一一对应 + MasterID []string `yaml:"master_id,omitempty"` // 如果需要在群权限判断是管理员是,将user_id填入这里,master_id是一个文本数组 + EnableWsServer bool `yaml:"enable_ws_server,omitempty"` //正向ws开关 + WsServerToken string `yaml:"ws_server_token,omitempty"` //正向ws token + IdentifyFile bool `yaml:"identify_file"` // 域名校验文件 + Crt string `yaml:"crt"` + Key string `yaml:"key"` + DeveloperLog bool `yaml:"developer_log"` + Username string `yaml:"server_user_name"` + Password string `yaml:"server_user_password"` + ImageLimit int `yaml:"image_sizelimit"` + RemovePrefix bool `yaml:"remove_prefix"` + BackupPort string `yaml:"backup_port"` + DevlopAcDir string `yaml:"develop_access_token_dir"` + RemoveAt bool `yaml:"remove_at"` + DevBotid string `yaml:"develop_bot_id"` + SandBoxMode bool `yaml:"sandbox_mode"` + Title string `yaml:"title"` + HashID bool `yaml:"hash_id"` + TwoWayEcho bool `yaml:"twoway_echo"` + LazyMessageId bool `yaml:"lazy_message_id"` + WhitePrefixMode bool `yaml:"white_prefix_mode"` + WhitePrefixs []string `yaml:"white_prefixs"` + BlackPrefixMode bool `yaml:"black_prefix_mode"` + BlackPrefixs []string `yaml:"black_prefixs"` + VisualPrefixs []VisualPrefixConfig `yaml:"visual_prefixs"` + VisibleIp bool `yaml:"visible_ip"` + ForwardMsgLimit int `yaml:"forward_msg_limit"` + DevMessgeID bool `yaml:"dev_message_id"` + LogLevel int `yaml:"log_level"` + SaveLogs bool `yaml:"save_logs"` + BindPrefix string `yaml:"bind_prefix"` + MePrefix string `yaml:"me_prefix"` + FrpPort string `yaml:"frp_port"` + RemoveBotAtGroup bool `yaml:"remove_bot_at_group"` + ImageLimitB int `yaml:"image_limit"` + RecordSampleRate int `yaml:"record_sampleRate"` + RecordBitRate int `yaml:"record_bitRate"` + NoWhiteResponse string `yaml:"No_White_Response"` + SendError bool `yaml:"send_error"` + AddAtGroup bool `yaml:"add_at_group"` + UrlPicTransfer bool `yaml:"url_pic_transfer"` + LotusPassword string `yaml:"lotus_password"` + WsServerPath string `yaml:"ws_server_path"` + IdmapPro bool `yaml:"idmap_pro"` + CardAndNick string `yaml:"card_nick"` + AutoBind bool `yaml:"auto_bind"` + CustomBotName string `yaml:"custom_bot_name"` + SendDelay int `yaml:"send_delay"` + AtoPCount int `yaml:"AMsgRetryAsPMsg_Count"` + ReconnecTimes int `yaml:"reconnect_times"` + HeartBeatInterval int `yaml:"heart_beat_interval"` + LaunchReconectTimes int `yaml:"launch_reconnect_times"` + UnlockPrefix string `yaml:"unlock_prefix"` + WhiteBypass []int64 `yaml:"white_bypass"` + TransferUrl bool `yaml:"transfer_url"` + HttpAddress string `yaml:"http_address"` + HttpVersion int `yaml:"http_version"` + HttpTimeOut int `yaml:"http_timeout"` + PostUrl []string `yaml:"post_url"` + PostSecret []string `yaml:"post_secret"` + PostMaxRetries []int `yaml:"post_max_retries"` + PostRetriesInterval []int `yaml:"post_retries_interval"` + NativeOb11 bool `yaml:"native_ob11"` + RamDomSeq bool `yaml:"ramdom_seq"` + UrlToQrimage bool `yaml:"url_to_qrimage"` + QrSize int `yaml:"qr_size"` + WhiteBypassRevers bool `yaml:"white_bypass_reverse"` + GuildUrlImageToBase64 bool `yaml:"guild_url_image_to_base64"` + TencentBucketName string `yaml:"t_COS_BUCKETNAME"` + TencentBucketRegion string `yaml:"t_COS_REGION"` + TencentCosSecretid string `yaml:"t_COS_SECRETID"` + TencentSecretKey string `yaml:"t_COS_SECRETKEY"` + TencentAudit bool `yaml:"t_audit"` + OssType int `yaml:"oss_type"` + BaiduBOSBucketName string `yaml:"b_BOS_BUCKETNAME"` + BaiduBCEAK string `yaml:"b_BCE_AK"` + BaiduBCESK string `yaml:"b_BCE_SK"` + BaiduAudit int `yaml:"b_audit"` + AliyunEndpoint string `yaml:"a_OSS_EndPoint"` + AliyunAccessKeyId string `yaml:"a_OSS_AccessKeyId"` + AliyunAccessKeySecret string `yaml:"a_OSS_AccessKeySecret"` + AliyunBucketName string `yaml:"a_OSS_BucketName"` + AliyunAudit bool `yaml:"a_audit"` + Alias []string `yaml:"alias"` + SelfIntroduce []string `yaml:"self_introduce"` + 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"` + LinkPrefix string `yaml:"link_prefix"` + LinkBots []string `yaml:"link_bots"` + LinkText string `yaml:"link_text"` + LinkPic string `yaml:"link_pic"` + MusicPrefix string `yaml:"music_prefix"` + DisableWebui bool `yaml:"disable_webui"` + ShardCount int `yaml:"shard_count"` + ShardID int `yaml:"shard_id"` + BotForumTitle string `yaml:"bot_forum_title"` + GlobalInteractionToMessage bool `yaml:"global_interaction_to_message"` + AutoPutInteraction bool `yaml:"auto_put_interaction"` + PutInteractionDelay int `yaml:"put_interaction_delay"` } // LoadConfig 从文件中加载配置并初始化单例配置 @@ -1791,3 +1796,51 @@ func GetDisableWebui() bool { } return instance.Settings.DisableWebui } + +// 获取 GetBotForumTitle +func GetBotForumTitle() string { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to get BotForumTitle.") + return "" + } + return instance.Settings.BotForumTitle +} + +// 获取 GetGlobalInteractionToMessage 的值 +func GetGlobalInteractionToMessage() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to GlobalInteractionToMessage value.") + return false + } + return instance.Settings.GlobalInteractionToMessage +} + +// 获取 AutoPutInteraction 的值 +func GetAutoPutInteraction() bool { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to AutoPutInteraction value.") + return false + } + return instance.Settings.AutoPutInteraction +} + +// 获取 PutInteractionDelay 延迟 +func GetPutInteractionDelay() int { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to get PutInteractionDelay.") + return 0 + } + return instance.Settings.PutInteractionDelay +} diff --git a/handlers/message_parser.go b/handlers/message_parser.go index 160526af..50e584f5 100644 --- a/handlers/message_parser.go +++ b/handlers/message_parser.go @@ -569,8 +569,8 @@ func RevertTransformedText(data interface{}, msgtype string, api openapi.OpenAPI isSpecialType = true originalPrefix = specialPrefixes[i] // 恢复原始前缀 } - // 检查 messageText 的长度是否大于 prefix 的长度 - if len(messageText) > len(vp.Prefix) { + // 检查 messageText 的长度是否大于 prefix 的长度 忽略长度为0的初始 vp.Prefix + if len(vp.Prefix) > 0 && len(messageText) > len(vp.Prefix) { // 移除找到的前缀 且messageText不为空 if messageText != "" { messageText = strings.TrimPrefix(messageText, vp.Prefix) @@ -727,6 +727,8 @@ func ConvertToSegmentedMessage(data interface{}) []map[string]interface{} { var msg *dto.Message var menumsg bool switch v := data.(type) { + case *dto.Message: + msg = v // 直接赋值,因为v已经是*dto.Message类型 case *dto.WSGroupATMessageData: msg = (*dto.Message)(v) case *dto.WSATMessageData: diff --git a/handlers/put_interaction.go b/handlers/put_interaction.go new file mode 100644 index 00000000..7e3706ad --- /dev/null +++ b/handlers/put_interaction.go @@ -0,0 +1,90 @@ +package handlers + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/mylog" + "github.com/tencent-connect/botgo/openapi" +) + +type InteractionResponse struct { + Data string `json:"data"` + Message string `json:"message"` + RetCode int `json:"retcode"` + Status string `json:"status"` + Echo interface{} `json:"echo"` +} + +func init() { + callapi.RegisterHandler("put_interaction", HandlePutInteraction) +} + +func HandlePutInteraction(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) (string, error) { + + // 解析 ActionMessage 中的 Echo 字段获取 interactionID + interactionID, ok := message.Echo.(string) + if !ok { + return "", fmt.Errorf("echo is not a string") + } + + // 根据 PostType 解析出 code 的值 + var code int + switch message.PostType { + case "0": + code = 0 // 成功 + case "1": + code = 1 // 操作失败 + case "2": + code = 2 // 操作频繁 + case "3": + code = 3 // 重复操作 + case "4": + code = 4 // 没有权限 + case "5": + code = 5 // 仅管理员操作 + default: + // 如果 PostType 不在预期范围内,可以设置一个默认值或返回错误 + return "", fmt.Errorf("invalid post type: %s", message.PostType) + } + + // 构造请求体,包括 code + requestBody := fmt.Sprintf(`{"code": %d}`, code) + + // 调用 PutInteraction API + ctx := context.Background() + err := api.PutInteraction(ctx, interactionID, requestBody) + if err != nil { + return "", err + } + + var response InteractionResponse + + response.Data = "" + response.Message = "" + response.RetCode = 0 + response.Status = "ok" + response.Echo = message.Echo + + // Convert the members slice to a map + outputMap := structToMap(response) + + mylog.Printf("put_interaction: %+v\n", outputMap) + + err = client.SendMessage(outputMap) + if err != nil { + mylog.Printf("Error sending message via client: %v", err) + } else { + mylog.Printf("响应put_interaction: %+v", outputMap) + } + //把结果从struct转换为json + result, err := json.Marshal(response) + if err != nil { + mylog.Printf("Error marshaling data: %v", err) + //todo 符合onebotv11 ws返回的错误码 + return "", nil + } + return string(result), nil +} diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index 8f22ed32..7b570955 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -455,6 +455,25 @@ func HandleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap //用userid还原出openid 这是虚拟成群的群聊私聊信息 message.Params.UserID = message.Params.GroupID.(string) retmsg, _ = HandleSendPrivateMsg(client, api, apiv2, message) + case "forum": + //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 + message.Params.ChannelID = message.Params.GroupID.(string) + var RChannelID string + if message.Params.UserID != nil && config.GetIdmapPro() { + RChannelID, _, err = idmap.RetrieveRowByIDv2Pro(message.Params.ChannelID, message.Params.UserID.(string)) + mylog.Printf("测试,通过Proid获取的RChannelID:%v", RChannelID) + } + if RChannelID == "" { + // 使用RetrieveRowByIDv2还原真实的ChannelID + RChannelID, err = idmap.RetrieveRowByIDv2(message.Params.ChannelID) + } + if err != nil { + mylog.Printf("error retrieving real RChannelID: %v", err) + } + message.Params.ChannelID = RChannelID + //这一句是group_private的逻辑,发频道信息用的是channelid + //message.Params.GroupID = value + retmsg, _ = HandleSendGuildChannelForum(client, api, apiv2, message) default: mylog.Printf("Unknown message type: %s", msgType) } diff --git a/handlers/send_guild_channel_forum.go b/handlers/send_guild_channel_forum.go new file mode 100644 index 00000000..d046321d --- /dev/null +++ b/handlers/send_guild_channel_forum.go @@ -0,0 +1,238 @@ +package handlers + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" + "github.com/hoshinonyaruko/gensokyo/images" + "github.com/hoshinonyaruko/gensokyo/mylog" + + "github.com/hoshinonyaruko/gensokyo/echo" + + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/openapi" +) + +func init() { + callapi.RegisterHandler("send_guild_channel_forum", HandleSendGuildChannelForum) +} + +func HandleSendGuildChannelForum(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage) (string, error) { + // 使用 message.Echo 作为key来获取消息类型 + var msgType string + var retmsg string + if echoStr, ok := message.Echo.(string); ok { + // 当 message.Echo 是字符串类型时执行此块 + msgType = echo.GetMsgTypeByKey(echoStr) + } + if msgType == "" { + msgType = GetMessageTypeByGroupid(config.GetAppIDStr(), message.Params.GroupID) + } + if msgType == "" { + msgType = GetMessageTypeByUserid(config.GetAppIDStr(), message.Params.UserID) + } + if msgType == "" { + msgType = GetMessageTypeByGroupidV2(message.Params.GroupID) + } + if msgType == "" { + msgType = GetMessageTypeByUseridV2(message.Params.UserID) + } + //当不转换频道信息时(不支持频道私聊) + if msgType == "" { + msgType = "forum" + } + switch msgType { + //原生guild信息 + case "forum": + params := message.Params + messageText, foundItems := parseMessageContent(params, message, client, api, apiv2) + + channelID := params.ChannelID + // 使用 echo 获取消息ID + + mylog.Println("频道发帖子messageText:", messageText) + + Forum, err := GenerateForumMessage(foundItems, messageText) + if err != nil { + mylog.Printf("组合帖子信息失败: %v", err) + } + if _, err = api.PostFourm(context.TODO(), channelID, Forum); err != nil { + mylog.Printf("发送帖子信息失败: %v", err) + } + + //发送成功回执 + retmsg, _ = SendResponse(client, err, &message) + + default: + mylog.Printf("2Unknown message type: %s", msgType) + } + return retmsg, nil +} + +// GenerateForumMessage 生成帖子消息,类型是2 +// func GenerateForumMessage(foundItems map[string][]string, messageText string) (*dto.FourmToCreate, error) { +// var forum dto.FourmToCreate + +// // 设置标题 +// title := config.GetBotForumTitle() +// forum.Title = title + +// // 使用提供的messageText作为帖子内容 +// forum.Content = messageText +// forum.Format = 1 // 纯文本 + +// mdImages := []string{} + +// // 检查是否有图片链接 +// if imageItems, ok := foundItems["url_image"]; ok && len(imageItems) > 0 { +// for _, url := range imageItems { +// // 获取图片宽高 +// height, width, err := images.GetImageDimensions(url) +// if err != nil { +// mylog.Printf("获取图片宽高出错: %v", err) +// continue // 如果无法获取宽高,则跳过此图片 +// } +// // 将图片URL转换为Markdown图片格式,并添加宽高信息 +// imgDesc := fmt.Sprintf("图片 #%dpx #%dpx", width, height) +// mdImages = append(mdImages, fmt.Sprintf("![%s](%s)", imgDesc, url)) +// } +// } + +// // 处理base64图片 +// if base64Image, ok := foundItems["base64_image"]; ok && len(base64Image) > 0 { +// fileImageData, err := base64.StdEncoding.DecodeString(base64Image[0]) +// if err != nil { +// mylog.Printf("failed to decode base64 image: %v", err) +// return nil, fmt.Errorf("failed to decode base64 image: %v", err) +// } +// compressedData, err := images.CompressSingleImage(fileImageData) +// if err != nil { +// mylog.Printf("Error compressing image: %v", err) +// return nil, fmt.Errorf("error compressing image: %v", err) +// } +// imageURL, err := images.UploadBase64ImageToServer(base64.StdEncoding.EncodeToString(compressedData)) +// if err != nil { +// mylog.Printf("failed to upload base64 image: %v", err) +// return nil, fmt.Errorf("failed to upload base64 image: %v", err) +// } +// // 获取图片宽高 +// height, width, err := images.GetImageDimensions(imageURL) +// if err != nil { +// mylog.Printf("获取图片宽高出错: %v", err) +// // 如果无法获取宽高,则使用默认描述 +// mdImages = append(mdImages, fmt.Sprintf("![(默认图片描述)](%s)", imageURL)) +// } else { +// imgDesc := fmt.Sprintf("图片 #%dpx #%dpx", width, height) +// mdImages = append(mdImages, fmt.Sprintf("![%s](%s)", imgDesc, imageURL)) +// } +// } + +// // 如果有图片,则更新内容和格式 +// if len(mdImages) > 0 { +// // 如果已经有文本内容,则在文本后添加图片 +// if forum.Content != "" { +// forum.Content += "\n\n" + strings.Join(mdImages, "\n\n") +// } else { +// forum.Content = strings.Join(mdImages, "\n\n") +// } +// forum.Format = 3 // Markdown,因为包含文本和/或图片 +// } + +// // 如果没有找到任何内容,返回错误 +// if forum.Content == "" { +// return nil, fmt.Errorf("no valid content found") +// } + +// return &forum, nil +// } + +// GenerateForumMessage 生成帖子消息 +func GenerateForumMessage(foundItems map[string][]string, messageText string) (*dto.FourmToCreate, error) { + var forum dto.FourmToCreate + + // 设置标题 + title := config.GetBotForumTitle() + forum.Title = title + + // 初始化富文本内容结构 + var richText struct { + Paragraphs []struct { + Elems []interface{} `json:"elems"` + } `json:"paragraphs"` + } + + // 处理文本消息 + if messageText != "" { + richText.Paragraphs = append(richText.Paragraphs, struct { + Elems []interface{} `json:"elems"` + }{ + Elems: []interface{}{ + map[string]interface{}{ + "text": map[string]string{ + "text": messageText, + }, + "type": 1, + }, + }, + }) + } + + if len(richText.Paragraphs) == 0 { + // 初始化一个空段落 + richText.Paragraphs = append(richText.Paragraphs, struct { + Elems []interface{} "json:\"elems\"" + }{}) + } + // 处理图片链接 + for _, url := range foundItems["url_image"] { + richText.Paragraphs[0].Elems = append(richText.Paragraphs[0].Elems, map[string]interface{}{ + "image": map[string]interface{}{ + "third_url": url, + "width_percent": 1.0, // 设置图片宽度比例为1.0 + }, + "type": 2, + }) + } + + // 处理base64图片 + for _, base64Image := range foundItems["base64_image"] { + fileImageData, err := base64.StdEncoding.DecodeString(base64Image) + if err != nil { + mylog.Printf("failed to decode base64 image: %v", err) + return nil, fmt.Errorf("failed to decode base64 image: %v", err) + } + compressedData, err := images.CompressSingleImage(fileImageData) + if err != nil { + mylog.Printf("Error compressing image: %v", err) + return nil, fmt.Errorf("error compressing image: %v", err) + } + imageURL, err := images.UploadBase64ImageToServer(base64.StdEncoding.EncodeToString(compressedData)) + if err != nil { + mylog.Printf("failed to upload base64 image: %v", err) + return nil, fmt.Errorf("failed to upload base64 image: %v", err) + } + richText.Paragraphs[0].Elems = append(richText.Paragraphs[0].Elems, map[string]interface{}{ + "image": map[string]interface{}{ + "third_url": imageURL, + "width_percent": 1.0, // 设置图片宽度比例为1.0 + }, + "type": 2, + }) + } + + // 将富文本内容结构转换为JSON字符串 + contentJSON, err := json.Marshal(richText) + if err != nil { + mylog.Printf("Error marshalling rich text content: %v", err) + return nil, fmt.Errorf("error marshalling rich text content: %v", err) + } + + forum.Content = string(contentJSON) + forum.Format = 4 // 设置格式为富文本 + + return &forum, nil +} diff --git a/handlers/send_guild_channel_msg.go b/handlers/send_guild_channel_msg.go index 9022ed09..94bb86fc 100644 --- a/handlers/send_guild_channel_msg.go +++ b/handlers/send_guild_channel_msg.go @@ -150,8 +150,13 @@ func HandleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 if err != nil { mylog.Printf("Base64 解码失败: %v", err) } else { + // 压缩图片 + compressedData, err := images.CompressSingleImage(fileImageData) + if err != nil { + mylog.Printf("Error compressing image: %v", err) + } // 使用 Multipart 方法发送 - if _, err = api.PostMessageMultipart(context.TODO(), channelID, newMessage, fileImageData); err != nil { + if _, err = api.PostMessageMultipart(context.TODO(), channelID, newMessage, compressedData); err != nil { mylog.Printf("40003重试,使用 multipart 发送图文混合信息失败: %v message_id %v", err, messageID) } } @@ -163,6 +168,11 @@ func HandleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 if err != nil { mylog.Printf("Base64 解码失败: %v", err) } + // 压缩图片 + compressedData, err := images.CompressSingleImage(fileImageData) + if err != nil { + mylog.Printf("Error compressing image: %v", err) + } // 创建包含文本和图像信息的消息 msgseq = echo.GetMappingSeq(messageID) echo.AddMappingSeq(messageID, msgseq+1) @@ -174,7 +184,7 @@ func HandleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 } newMessage.Timestamp = time.Now().Unix() // 设置时间戳 // 使用Multipart方法发送 - if _, err = api.PostMessageMultipart(context.TODO(), channelID, newMessage, fileImageData); err != nil { + if _, err = api.PostMessageMultipart(context.TODO(), channelID, newMessage, compressedData); err != nil { mylog.Printf("使用multipart发送图文信息失败: %v message_id %v", err, messageID) } } @@ -215,9 +225,13 @@ func HandleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 // 清除reply的Content reply.Content = "" - + // 压缩图片 + compressedData, err := images.CompressSingleImage(fileImageData) + if err != nil { + mylog.Printf("Error compressing image: %v", err) + } // 使用Multipart方法发送 - if _, err = api.PostMessageMultipart(context.TODO(), channelID, reply, fileImageData); err != nil { + if _, err = api.PostMessageMultipart(context.TODO(), channelID, reply, compressedData); err != nil { mylog.Printf("使用multipart发送 %s 信息失败: %v message_id %v", key, err, messageID) } //发送成功回执 @@ -244,8 +258,13 @@ func HandleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 if err != nil { mylog.Printf("Base64 解码失败: %v", err) } else { + // 压缩图片 + compressedData, err := images.CompressSingleImage(fileImageData) + if err != nil { + mylog.Printf("Error compressing image: %v", err) + } // 使用 Multipart 方法发送 - if _, err = api.PostMessageMultipart(context.TODO(), channelID, reply, fileImageData); err != nil { + if _, err = api.PostMessageMultipart(context.TODO(), channelID, reply, compressedData); err != nil { mylog.Printf("40003重试,使用 multipart 发送 %s 信息失败: %v message_id %v", key, err, messageID) } } @@ -269,6 +288,9 @@ func HandleSendGuildChannelMsg(client callapi.Client, api openapi.OpenAPI, apiv2 mylog.Printf("error retrieving real UserID: %v", err) } retmsg, _ = HandleSendGuildChannelPrivateMsg(client, api, apiv2, message, &guildID, &RChannelID) + case "forum": + //api一样的 直接丢进去试试 + retmsg, _ = HandleSendGuildChannelForum(client, api, apiv2, message) default: mylog.Printf("2Unknown message type: %s", msgType) } diff --git a/handlers/send_guild_private_msg.go b/handlers/send_guild_private_msg.go new file mode 100644 index 00000000..72e7bf30 --- /dev/null +++ b/handlers/send_guild_private_msg.go @@ -0,0 +1,249 @@ +package handlers + +import ( + "context" + "encoding/base64" + "fmt" + "time" + + "github.com/hoshinonyaruko/gensokyo/callapi" + "github.com/hoshinonyaruko/gensokyo/config" + "github.com/hoshinonyaruko/gensokyo/idmap" + "github.com/hoshinonyaruko/gensokyo/images" + "github.com/hoshinonyaruko/gensokyo/mylog" + + "github.com/hoshinonyaruko/gensokyo/echo" + + "github.com/tencent-connect/botgo/dto" + "github.com/tencent-connect/botgo/openapi" +) + +// 调用send_private_msg即可 一样的 + +// func init() { +// callapi.RegisterHandler("send_guild_private_msg", HandleSendGuildChannelPrivateMsg) +// } + +// 处理频道私信 最后2个指针参数可空 代表使用userid倒推 +func HandleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage, optionalGuildID *string, optionalChannelID *string) (string, error) { + params := message.Params + messageText, foundItems := parseMessageContent(params, message, client, api, apiv2) + + var guildID, channelID string + var err error + var UserID string + var GroupID string + var retmsg string + if message.Params.GroupID != nil { + if gid, ok := message.Params.GroupID.(string); ok { + GroupID = gid // GroupID 是 string 类型 + } else { + mylog.Printf(" GroupID 不是 string,304") + } + } else { + mylog.Printf("GroupID 为 nil,信息发送正常可忽略") + } + RawUserID := message.Params.UserID.(string) + + if optionalGuildID != nil && optionalChannelID != nil { + guildID = *optionalGuildID + channelID = *optionalChannelID + } + + // 使用 echo 获取消息ID + var messageID string + if config.GetLazyMessageId() { + //由于实现了Params的自定义unmarshell 所以可以类型安全的断言为string + messageID = echo.GetLazyMessagesId(RawUserID) + mylog.Printf("GetLazyMessagesId: %v", messageID) + } + if messageID == "" { + if echoStr, ok := message.Echo.(string); ok { + messageID = echo.GetMsgIDByKey(echoStr) + mylog.Println("echo取私聊发信息对应的message_id:", messageID) + } + } + mylog.Println("私聊信息messageText:", messageText) + //获取guild和channelid和message id流程(来个大佬简化下) + if RawUserID != "" { + if guildID == "" && channelID == "" { + //频道私信 转 私信 通过userid(author_id)来还原频道私信需要的guildid channelID + guildID, channelID, err = getGuildIDFromMessage(message) + if err != nil { + mylog.Printf("获取 guild_id 和 channel_id 出错,进行重试: %v", err) + guildID, channelID, err = getGuildIDFromMessagev2(message) + if err != nil { + mylog.Printf("获取 guild_id 和 channel_id 出错,重试失败: %v", err) + return "", nil + } + } + //频道私信 转 私信 + if GroupID != "" && config.GetIdmapPro() { + _, UserID, err = idmap.RetrieveRowByIDv2Pro(GroupID, RawUserID) + if err != nil { + mylog.Printf("Error reading config: %v", err) + return "", nil + } + mylog.Printf("测试,通过Proid获取的UserID:%v", UserID) + } else { + UserID, err = idmap.RetrieveRowByIDv2(RawUserID) + if err != nil { + mylog.Printf("Error reading config: %v", err) + return "", nil + } + } + // 如果messageID为空,通过函数获取 + if messageID == "" { + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), UserID) + mylog.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) + } + } else { + //频道私信 转 私信 + if GroupID != "" && config.GetIdmapPro() { + _, UserID, err = idmap.RetrieveRowByIDv2Pro(GroupID, RawUserID) + if err != nil { + mylog.Printf("Error reading config: %v", err) + return "", nil + } + mylog.Printf("测试,通过Proid获取的UserID:%v", UserID) + } else { + UserID, err = idmap.RetrieveRowByIDv2(RawUserID) + if err != nil { + mylog.Printf("Error reading config: %v", err) + return "", nil + } + } + // 如果messageID为空,通过函数获取 + if messageID == "" { + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), UserID) + mylog.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) + } + } + } else { + if guildID == "" && channelID == "" { + //频道私信 转 群聊 通过groupid(author_id)来还原频道私信需要的guildid channelID + guildID, err = idmap.ReadConfigv2(GroupID, "guild_id") + if err != nil { + mylog.Printf("根据GroupID获取guild_id失败: %v", err) + return "", nil + } + channelID, err = idmap.RetrieveRowByIDv2(GroupID) + if err != nil { + mylog.Printf("根据GroupID获取channelID失败: %v", err) + return "", nil + } + //频道私信 转 群聊 获取id + var originalGroupID string + if config.GetIdmapPro() { + _, originalGroupID, err = idmap.RetrieveRowByIDv2Pro(channelID, GroupID) + if err != nil { + mylog.Printf("Error retrieving original GroupID: %v", err) + return "", nil + } + mylog.Printf("测试,通过Proid获取的originalGroupID:%v", originalGroupID) + } else { + originalGroupID, err = idmap.RetrieveRowByIDv2(message.Params.GroupID.(string)) + if err != nil { + mylog.Printf("Error retrieving original GroupID: %v", err) + return "", nil + } + } + mylog.Println("群组(私信虚拟成的)发信息messageText:", messageText) + //mylog.Println("foundItems:", foundItems) + // 如果messageID为空,通过函数获取 + if messageID == "" { + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), originalGroupID) + mylog.Println("通过GetMessageIDByUseridOrGroupid函数获取的message_id:", originalGroupID, messageID) + } + } else { + //频道私信 转 群聊 获取id + var originalGroupID string + if config.GetIdmapPro() { + _, originalGroupID, err = idmap.RetrieveRowByIDv2Pro(GroupID, RawUserID) + if err != nil { + mylog.Printf("Error retrieving original GroupID2: %v", err) + } + mylog.Printf("测试,通过Proid获取的originalGroupID:%v", originalGroupID) + } + //降级重试 + if originalGroupID == "" { + originalGroupID, err = idmap.RetrieveRowByIDv2(message.Params.GroupID.(string)) + if err != nil { + mylog.Printf("Error retrieving original GroupID: %v", err) + } + } + if messageID == "" { + messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), originalGroupID) + mylog.Println("通过GetMessageIDByUseridOrGroupid函数获取的message_id:", originalGroupID, messageID) + } + } + } + if messageID == "2000" { + messageID = "" + mylog.Println("通过lazymsgid发送频道私聊主动信息,若非主动信息请提交issue") + } + //开发环境用 + if config.GetDevMsgID() { + messageID = "1000" + } + + timestamp := time.Now().Unix() + timestampStr := fmt.Sprintf("%d", timestamp) + + // 构造 dm (dms 私信事件) + dm := &dto.DirectMessage{ + GuildID: guildID, + ChannelID: channelID, + CreateTime: timestampStr, + } + + // 优先发送文本信息 + if messageText != "" { + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + textMsg, _ := GenerateReplyMessage(messageID, nil, messageText, msgseq+1) + if _, err = apiv2.PostDirectMessage(context.TODO(), dm, textMsg); err != nil { + mylog.Printf("发送文本信息失败: %v", err) + } + //发送成功回执 + retmsg, _ = SendResponse(client, err, &message) + } + + // 遍历foundItems并发送每种信息 + for key, urls := range foundItems { + for _, url := range urls { + var singleItem = make(map[string][]string) + singleItem[key] = []string{url} // 创建一个只包含单个 URL 的 singleItem + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + reply, isBase64Image := GenerateReplyMessage(messageID, singleItem, "", msgseq+1) + + if isBase64Image { + // 处理 Base64 图片的逻辑 + fileImageData, err := base64.StdEncoding.DecodeString(reply.Content) + if err != nil { + mylog.Printf("Base64 解码失败: %v", err) + continue // 跳过当前项,继续处理下一个 + } + + reply.Content = "" + // 压缩图片 + compressedData, err := images.CompressSingleImage(fileImageData) + if err != nil { + mylog.Printf("Error compressing image: %v", err) + } + if _, err = api.PostDirectMessageMultipart(context.TODO(), dm, reply, compressedData); err != nil { + mylog.Printf("使用multipart发送 %s 信息失败: %v message_id %v", key, err, messageID) + } + retmsg, _ = SendResponse(client, err, &message) + } else { + // 处理非 Base64 图片的逻辑 + if _, err = api.PostDirectMessage(context.TODO(), dm, reply); err != nil { + mylog.Printf("发送 %s 信息失败: %v", key, err) + } + retmsg, _ = SendResponse(client, err, &message) + } + } + } + return retmsg, nil +} diff --git a/handlers/send_msg.go b/handlers/send_msg.go index f5ba50e9..9fe36bda 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -93,6 +93,23 @@ func HandleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope case "group_private": //私聊信息 retmsg, _ = HandleSendPrivateMsg(client, api, apiv2, message) + case "forum": + //用GroupID给ChannelID赋值,因为我们是把频道虚拟成了群 + message.Params.ChannelID = message.Params.GroupID.(string) + var RChannelID string + if message.Params.UserID != nil && config.GetIdmapPro() { + RChannelID, _, err = idmap.RetrieveRowByIDv2Pro(message.Params.ChannelID, message.Params.UserID.(string)) + mylog.Printf("测试,通过Proid获取的RChannelID:%v", RChannelID) + } + if RChannelID == "" { + // 使用RetrieveRowByIDv2还原真实的ChannelID + RChannelID, err = idmap.RetrieveRowByIDv2(message.Params.ChannelID) + } + if err != nil { + mylog.Printf("error retrieving real RChannelID: %v", err) + } + message.Params.ChannelID = RChannelID + retmsg, _ = HandleSendGuildChannelForum(client, api, apiv2, message) default: mylog.Printf("1Unknown message type: %s", msgType) } diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index a4a94793..99f168c8 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -2,7 +2,6 @@ package handlers import ( "context" - "encoding/base64" "fmt" "strconv" "time" @@ -294,226 +293,6 @@ func HandleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open return retmsg, nil } -// 处理频道私信 最后2个指针参数可空 代表使用userid倒推 -func HandleSendGuildChannelPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI, message callapi.ActionMessage, optionalGuildID *string, optionalChannelID *string) (string, error) { - params := message.Params - messageText, foundItems := parseMessageContent(params, message, client, api, apiv2) - - var guildID, channelID string - var err error - var UserID string - var GroupID string - var retmsg string - if message.Params.GroupID != nil { - if gid, ok := message.Params.GroupID.(string); ok { - GroupID = gid // GroupID 是 string 类型 - } else { - mylog.Printf(" GroupID 不是 string,304") - } - } else { - mylog.Printf("GroupID 为 nil,信息发送正常可忽略") - } - RawUserID := message.Params.UserID.(string) - - if optionalGuildID != nil && optionalChannelID != nil { - guildID = *optionalGuildID - channelID = *optionalChannelID - } - - // 使用 echo 获取消息ID - var messageID string - if config.GetLazyMessageId() { - //由于实现了Params的自定义unmarshell 所以可以类型安全的断言为string - messageID = echo.GetLazyMessagesId(RawUserID) - mylog.Printf("GetLazyMessagesId: %v", messageID) - } - if messageID == "" { - if echoStr, ok := message.Echo.(string); ok { - messageID = echo.GetMsgIDByKey(echoStr) - mylog.Println("echo取私聊发信息对应的message_id:", messageID) - } - } - mylog.Println("私聊信息messageText:", messageText) - //获取guild和channelid和message id流程(来个大佬简化下) - if RawUserID != "" { - if guildID == "" && channelID == "" { - //频道私信 转 私信 通过userid(author_id)来还原频道私信需要的guildid channelID - guildID, channelID, err = getGuildIDFromMessage(message) - if err != nil { - mylog.Printf("获取 guild_id 和 channel_id 出错,进行重试: %v", err) - guildID, channelID, err = getGuildIDFromMessagev2(message) - if err != nil { - mylog.Printf("获取 guild_id 和 channel_id 出错,重试失败: %v", err) - return "", nil - } - } - //频道私信 转 私信 - if GroupID != "" && config.GetIdmapPro() { - _, UserID, err = idmap.RetrieveRowByIDv2Pro(GroupID, RawUserID) - if err != nil { - mylog.Printf("Error reading config: %v", err) - return "", nil - } - mylog.Printf("测试,通过Proid获取的UserID:%v", UserID) - } else { - UserID, err = idmap.RetrieveRowByIDv2(RawUserID) - if err != nil { - mylog.Printf("Error reading config: %v", err) - return "", nil - } - } - // 如果messageID为空,通过函数获取 - if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), UserID) - mylog.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) - } - } else { - //频道私信 转 私信 - if GroupID != "" && config.GetIdmapPro() { - _, UserID, err = idmap.RetrieveRowByIDv2Pro(GroupID, RawUserID) - if err != nil { - mylog.Printf("Error reading config: %v", err) - return "", nil - } - mylog.Printf("测试,通过Proid获取的UserID:%v", UserID) - } else { - UserID, err = idmap.RetrieveRowByIDv2(RawUserID) - if err != nil { - mylog.Printf("Error reading config: %v", err) - return "", nil - } - } - // 如果messageID为空,通过函数获取 - if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), UserID) - mylog.Println("通过GetMessageIDByUserid函数获取的message_id:", messageID) - } - } - } else { - if guildID == "" && channelID == "" { - //频道私信 转 群聊 通过groupid(author_id)来还原频道私信需要的guildid channelID - guildID, err = idmap.ReadConfigv2(GroupID, "guild_id") - if err != nil { - mylog.Printf("根据GroupID获取guild_id失败: %v", err) - return "", nil - } - channelID, err = idmap.RetrieveRowByIDv2(GroupID) - if err != nil { - mylog.Printf("根据GroupID获取channelID失败: %v", err) - return "", nil - } - //频道私信 转 群聊 获取id - var originalGroupID string - if config.GetIdmapPro() { - _, originalGroupID, err = idmap.RetrieveRowByIDv2Pro(channelID, GroupID) - if err != nil { - mylog.Printf("Error retrieving original GroupID: %v", err) - return "", nil - } - mylog.Printf("测试,通过Proid获取的originalGroupID:%v", originalGroupID) - } else { - originalGroupID, err = idmap.RetrieveRowByIDv2(message.Params.GroupID.(string)) - if err != nil { - mylog.Printf("Error retrieving original GroupID: %v", err) - return "", nil - } - } - mylog.Println("群组(私信虚拟成的)发信息messageText:", messageText) - //mylog.Println("foundItems:", foundItems) - // 如果messageID为空,通过函数获取 - if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), originalGroupID) - mylog.Println("通过GetMessageIDByUseridOrGroupid函数获取的message_id:", originalGroupID, messageID) - } - } else { - //频道私信 转 群聊 获取id - var originalGroupID string - if config.GetIdmapPro() { - _, originalGroupID, err = idmap.RetrieveRowByIDv2Pro(GroupID, RawUserID) - if err != nil { - mylog.Printf("Error retrieving original GroupID2: %v", err) - } - mylog.Printf("测试,通过Proid获取的originalGroupID:%v", originalGroupID) - } - //降级重试 - if originalGroupID == "" { - originalGroupID, err = idmap.RetrieveRowByIDv2(message.Params.GroupID.(string)) - if err != nil { - mylog.Printf("Error retrieving original GroupID: %v", err) - } - } - if messageID == "" { - messageID = GetMessageIDByUseridOrGroupid(config.GetAppIDStr(), originalGroupID) - mylog.Println("通过GetMessageIDByUseridOrGroupid函数获取的message_id:", originalGroupID, messageID) - } - } - } - if messageID == "2000" { - messageID = "" - mylog.Println("通过lazymsgid发送频道私聊主动信息,若非主动信息请提交issue") - } - //开发环境用 - if config.GetDevMsgID() { - messageID = "1000" - } - - timestamp := time.Now().Unix() - timestampStr := fmt.Sprintf("%d", timestamp) - - // 构造 dm (dms 私信事件) - dm := &dto.DirectMessage{ - GuildID: guildID, - ChannelID: channelID, - CreateTime: timestampStr, - } - - // 优先发送文本信息 - if messageText != "" { - msgseq := echo.GetMappingSeq(messageID) - echo.AddMappingSeq(messageID, msgseq+1) - textMsg, _ := GenerateReplyMessage(messageID, nil, messageText, msgseq+1) - if _, err = apiv2.PostDirectMessage(context.TODO(), dm, textMsg); err != nil { - mylog.Printf("发送文本信息失败: %v", err) - } - //发送成功回执 - retmsg, _ = SendResponse(client, err, &message) - } - - // 遍历foundItems并发送每种信息 - for key, urls := range foundItems { - for _, url := range urls { - var singleItem = make(map[string][]string) - singleItem[key] = []string{url} // 创建一个只包含单个 URL 的 singleItem - msgseq := echo.GetMappingSeq(messageID) - echo.AddMappingSeq(messageID, msgseq+1) - reply, isBase64Image := GenerateReplyMessage(messageID, singleItem, "", msgseq+1) - - if isBase64Image { - // 处理 Base64 图片的逻辑 - fileImageData, err := base64.StdEncoding.DecodeString(reply.Content) - if err != nil { - mylog.Printf("Base64 解码失败: %v", err) - continue // 跳过当前项,继续处理下一个 - } - - reply.Content = "" - - if _, err = api.PostDirectMessageMultipart(context.TODO(), dm, reply, fileImageData); err != nil { - mylog.Printf("使用multipart发送 %s 信息失败: %v message_id %v", key, err, messageID) - } - retmsg, _ = SendResponse(client, err, &message) - } else { - // 处理非 Base64 图片的逻辑 - if _, err = api.PostDirectMessage(context.TODO(), dm, reply); err != nil { - mylog.Printf("发送 %s 信息失败: %v", key, err) - } - retmsg, _ = SendResponse(client, err, &message) - } - } - } - return retmsg, nil -} - // 这个函数可以通过int类型的虚拟userid反推真实的guild_id和channel_id func getGuildIDFromMessage(message callapi.ActionMessage) (string, string, error) { var userID string diff --git a/httpapi/httpapi.go b/httpapi/httpapi.go index 8f2656f0..7f3fb903 100644 --- a/httpapi/httpapi.go +++ b/httpapi/httpapi.go @@ -30,6 +30,10 @@ func CombinedMiddleware(api openapi.OpenAPI, apiV2 openapi.OpenAPI) gin.HandlerF handleGetGroupList(c, api, apiV2) return } + if c.Request.URL.Path == "/put_interaction" { + handlePutInteraction(c, api, apiV2) + return + } // 调用c.Next()以继续处理请求链 c.Next() @@ -224,3 +228,45 @@ func handleGetGroupList(c *gin.Context, api openapi.OpenAPI, apiV2 openapi.OpenA c.Header("Content-Type", "application/json") c.String(http.StatusOK, retmsg) } + +// handlePutInteraction 处理put_interaction的请求 +func handlePutInteraction(c *gin.Context, api openapi.OpenAPI, apiV2 openapi.OpenAPI) { + var req struct { + Echo string `json:"echo" form:"echo"` // Echo值用于标识interaction + PostType string `json:"post_type" form:"post_type"` // PostType用于设置code参数 + } + + // 根据请求方法解析参数 + if c.Request.Method == http.MethodGet { + // 从URL查询参数解析 + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + } else { + // 从JSON或表单数据解析 + if err := c.ShouldBind(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + } + + // 创建 ActionMessage 实例 + message := callapi.ActionMessage{ + Action: "put_interaction", + Echo: req.Echo, + PostType: req.PostType, + } + + // 调用处理函数 + client := &HttpAPIClient{} // 假设HttpAPIClient实现了callapi.Client接口 + retmsg, err := handlers.HandlePutInteraction(client, api, apiV2, message) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // 返回处理结果 + c.Header("Content-Type", "application/json") + c.String(http.StatusOK, retmsg) +} diff --git a/images/upload_api.go b/images/upload_api.go index 2b2f7636..5018edb1 100644 --- a/images/upload_api.go +++ b/images/upload_api.go @@ -34,6 +34,26 @@ func UploadBase64ImageToServer(base64Image string) (string, error) { } } +// 将base64语音通过lotus转换成url +func UploadBase64RecordToServer(base64Record string) (string, error) { + extraPicAuditingType := config.GetOssType() + + // 根据不同的extraPicAuditingType值来调整函数行为 + switch extraPicAuditingType { + case 0: + // 原有的函数行为 + return originalUploadBehaviorRecord(base64Record) + case 1: + return oss.UploadAndAuditRecord(base64Record) //腾讯 + case 2: + return oss.UploadAndAuditRecord(base64Record) //百度 + case 3: + return oss.UploadAndAuditRecord(base64Record) //阿里 + default: + return "", errors.New("invalid extraPicAuditingType") + } +} + func originalUploadBehavior(base64Image string) (string, error) { // 原有的UploadBase64ImageToServer函数的实现 protocol := "http" @@ -72,7 +92,7 @@ func originalUploadBehavior(base64Image string) (string, error) { } // 将base64语音通过lotus转换成url -func UploadBase64RecordToServer(base64Image string) (string, error) { +func originalUploadBehaviorRecord(base64Image string) (string, error) { // 根据serverPort确定协议 protocol := "http" serverPort := config.GetPortValue() diff --git a/main.go b/main.go index caa7b2e9..86e8e0aa 100644 --- a/main.go +++ b/main.go @@ -559,6 +559,14 @@ func InteractionHandler() event.InteractionEventHandler { } } +// ThreadEventHandler 处理帖子事件 +func ThreadEventHandler() event.ThreadEventHandler { + return func(event *dto.WSPayload, data *dto.WSThreadData) error { + mylog.Printf("收到帖子事件:%v", data) + return p.ProcessThreadMessage(data) + } +} + // GroupATMessageEventHandler 实现处理 群at 消息的回调 func GroupATMessageEventHandler() event.GroupATMessageEventHandler { return func(event *dto.WSPayload, data *dto.WSGroupATMessageData) error { @@ -607,9 +615,8 @@ func getHandlerByName(handlerName string) (interface{}, bool) { return CreateMessageHandler(), true case "InteractionHandler": //添加频道互动回应 return InteractionHandler(), true - case "ThreadEventHandler": //发帖事件 暂不支持 暂不支持 - return nil, false - //return ThreadEventHandler(), true + case "ThreadEventHandler": //发帖事件 + return ThreadEventHandler(), true case "GroupATMessageEventHandler": //群at信息 return GroupATMessageEventHandler(), true case "C2CMessageEventHandler": //群私聊 diff --git a/oss/aliyun.go b/oss/aliyun.go index 848ffb25..f9f91161 100644 --- a/oss/aliyun.go +++ b/oss/aliyun.go @@ -94,6 +94,52 @@ func UploadAndAuditImageA(base64Data string) (string, error) { return imageURL, nil } +// 上传语音 +func UploadAndAuditRecordA(base64Data string) (string, error) { + initaliyunClient() + + // Decode base64 data + decodedData, err := base64.StdEncoding.DecodeString(base64Data) + if err != nil { + return "", err + } + + // Create a temporary file to save decoded data + tmpFile, err := os.CreateTemp("", "upload-*.amr") + if err != nil { + return "", err + } + defer tmpFile.Close() + + if _, err = tmpFile.Write(decodedData); err != nil { + return "", err + } + + // 计算解码数据的 MD5 + h := md5.New() + if _, err := h.Write(decodedData); err != nil { + return "", err + } + md5Hash := fmt.Sprintf("%x", h.Sum(nil)) + + // 使用 MD5 值作为对象键 + objectKey := md5Hash + ".amr" + // 上传文件到 OSS + bucket, err := aliyunclient.Bucket(config.GetAliyunBucketName()) + if err != nil { + return "", err + } + + err = bucket.PutObjectFromFile(objectKey, tmpFile.Name()) + if err != nil { + return "", err + } + + // 图片正常,返回语音 URL + imageURL := "https://" + config.GetAliyunBucketName() + ".oss-" + config.GetRegionID() + ".aliyuncs.com/" + objectKey + return imageURL, nil +} + // auditImage 审核图片 func auditImage(client *green.Client, objectKey string) (bool, error) { // 构造审核请求 diff --git a/oss/baidu.go b/oss/baidu.go index c12ca401..36165d3e 100644 --- a/oss/baidu.go +++ b/oss/baidu.go @@ -115,6 +115,20 @@ func UploadAndAuditImageB(base64Data string) (string, error) { } } +// UploadAndAuditRecordB 上传语音 +func UploadAndAuditRecordB(base64Data string) (string, error) { + initClientB() + + // 解码base64数据 + decodedData, err := base64.StdEncoding.DecodeString(base64Data) + if err != nil { + return "", err + } + + return uploadRecordBOS(decodedData) + +} + func originalUploadBehavior(base64Image string) (string, error) { // 原有的UploadBase64ImageToServer函数的实现 protocol := "http" @@ -239,6 +253,49 @@ func uploadImageBOS(data []byte) (string, error) { return fmt.Sprintf("https://%s/%s/%s", bucketDomain, appid, actualFileName), nil } +// uploadRecordBOS 使用BOS进行语音上传 +func uploadRecordBOS(data []byte) (string, error) { + // 计算MD5以用作文件名 + md5Hash := md5.New() + md5Hash.Write(data) + md5String := hex.EncodeToString(md5Hash.Sum(nil)) + + // 创建临时文件 + picname := fmt.Sprintf("qqbot-upload-%s-*.amr", md5String) + tmpFile, err := os.CreateTemp("", picname) + if err != nil { + return "", fmt.Errorf("failed to create temp file: %v", err) + } + defer func() { + tmpFile.Close() + os.Remove(tmpFile.Name()) // 清理临时文件 + }() + + // 写入数据到临时文件 + if _, err = tmpFile.Write(data); err != nil { + return "", fmt.Errorf("failed to write to temp file: %v", err) + } + + // 确保数据写入磁盘 + if err = tmpFile.Sync(); err != nil { + return "", fmt.Errorf("failed to sync temp file: %v", err) + } + + // 获取临时文件的实际文件名 + actualFileName := filepath.Base(tmpFile.Name()) + + // 上传到BOS + bucketDomain := config.GetBaiduBOSBucketName() //bucket的域名 + appid := config.GetAppIDStr() + _, err = clientBos.PutObjectFromFile(appid, actualFileName, tmpFile.Name(), nil) + if err != nil { + return "", fmt.Errorf("failed to upload to BOS: %v", err) + } + + // 返回图片URL + return fmt.Sprintf("https://%s/%s/%s", bucketDomain, appid, actualFileName), nil +} + // auditUsingBOS 使用BOS进行图片审核 func auditUsingBOS(imageURL string) (string, error) { ok := AuditImageContentAIP(imageURL) diff --git a/oss/tencent.go b/oss/tencent.go index 950938f0..627d74df 100644 --- a/oss/tencent.go +++ b/oss/tencent.go @@ -95,3 +95,46 @@ func UploadAndAuditImage(base64Data string) (string, error) { imageURL := bucketURL.String() + "/" + objectKey return imageURL, nil } + +// 上传语音 +func UploadAndAuditRecord(base64Data string) (string, error) { + initClient() + + // Decode base64 data + decodedData, err := base64.StdEncoding.DecodeString(base64Data) + if err != nil { + return "", err + } + + // Create a temporary file to save decoded data + tmpFile, err := os.CreateTemp("", "upload-*.amr") + if err != nil { + return "", err + } + defer tmpFile.Close() + + if _, err = tmpFile.Write(decodedData); err != nil { + return "", err + } + + // 计算解码数据的 MD5 + h := md5.New() + if _, err := h.Write(decodedData); err != nil { + return "", err + } + md5Hash := fmt.Sprintf("%x", h.Sum(nil)) + + // 使用 MD5 值作为对象键 + objectKey := md5Hash + ".amr" + + // 上传文件到 COS + _, err = client.Object.PutFromFile(context.Background(), objectKey, tmpFile.Name(), nil) + if err != nil { + return "", err + } + + // 语音正常,返回语音 URL + bucketURL, _ := url.Parse(config.GetTencentBucketURL()) // 确保这里的 GetTencentBucketURL 是正确的函数调用 + imageURL := bucketURL.String() + "/" + objectKey + return imageURL, nil +} diff --git a/template/config_template.go b/template/config_template.go index 6c5c1224..c0e9af21 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -23,11 +23,14 @@ settings: # - "InteractionHandler" # 添加频道互动回应 卡片按钮data回调事件 # - "GroupATMessageEventHandler" # 群at信息 仅频道机器人时候需要注释 # - "C2CMessageEventHandler" # 群私聊 仅频道机器人时候需要注释 - # - "ThreadEventHandler" # 发帖事件 (当前版本已禁用) + # - "ThreadEventHandler" # 频道发帖事件 仅频道私域机器人可用 global_channel_to_group: true # 是否将频道转换成群 默认true global_private_to_channel: false # 是否将私聊转换成频道 如果是群场景 会将私聊转为群(方便提审\测试) + global_forum_to_channel: false # 是否将频道帖子信息转化为频道 子频道信息 如果开启global_channel_to_group会进一步转换为群信息 + global_interaction_to_message : false # 是否将按钮和表态回调转化为消息 仅在设置了按钮回调中的message时有效 + bot_forum_title : "机器人帖子" # 机器人发帖子回复默认标题 array: false # 连接trss云崽请开启array hash_id : false # 使用hash来进行idmaps转换,可以让user_id不是123开始的递增值 @@ -127,8 +130,10 @@ settings: idmap_pro : false #需开启hash_id配合,高级id转换增强,可以多个真实值bind到同一个虚拟值,对于每个用户,每个群\私聊\判断私聊\频道,都会产生新的虚拟值,但可以多次bind,bind到同一个数字.数据库负担会变大. send_delay : 300 #单位 毫秒 默认300ms 可以视情况减少到100或者50 disable_webui: false #禁用webui - shard_count: 1 #分片数量 默认1 - shard_id: 0 #当前分片id 默认从0开始,详细请看 https://bot.q.qq.com/wiki/develop/api/gateway/reference.html + shard_count: 1 #分片数量 默认1 + shard_id: 0 #当前分片id 默认从0开始,详细请看 https://bot.q.qq.com/wiki/develop/api/gateway/reference.html + auto_put_interaction : false #自动回应按钮回调的/interactions/{interaction_id} 注本api需要邮件申请,详细方法参考群公告:196173384 + put_interaction_delay : 0 #单位毫秒 表示回应已收到回调类型的按钮的毫秒数 会按用户进行区分 非全局delay title : "Gensokyo © 2023 - Hoshinonyaruko" #程序的标题 如果多个机器人 可根据标题区分 custom_bot_name : "Gensokyo全域机器人" #自定义机器人名字,会在api调用中返回,默认Gensokyo全域机器人