diff --git a/botgo/dto/message.go b/botgo/dto/message.go index 14318ec3..606806b2 100644 --- a/botgo/dto/message.go +++ b/botgo/dto/message.go @@ -49,12 +49,18 @@ type MediaResponse struct { TTL int `json:"ttl"` } -//新增 +//群信息结构 type GroupMessageResponse struct { MediaResponse *MediaResponse Message *Message } +// C2CMessageResponse 用于包装 C2C 消息的响应 +type C2CMessageResponse struct { + Message *Message `json:"message,omitempty"` + MediaResponse *MediaResponse `json:"media_response,omitempty"` +} + // Embed 结构 type Embed struct { Title string `json:"title,omitempty"` diff --git a/botgo/openapi/iface.go b/botgo/openapi/iface.go index f2ae3207..bf832ac1 100644 --- a/botgo/openapi/iface.go +++ b/botgo/openapi/iface.go @@ -78,7 +78,7 @@ type MessageAPI interface { // PostGroupMessage 发送群消息 PostGroupMessage(ctx context.Context, groupID string, msg dto.APIMessage) (*dto.GroupMessageResponse, error) // PostC2CMessage 发送C2C消息 - PostC2CMessage(ctx context.Context, userID string, msg dto.APIMessage) (*dto.Message, error) + PostC2CMessage(ctx context.Context, userID string, msg dto.APIMessage) (*dto.C2CMessageResponse, error) } // GuildAPI guild 相关接口 diff --git a/botgo/openapi/v1/message.go b/botgo/openapi/v1/message.go index 436e939a..c13446f3 100644 --- a/botgo/openapi/v1/message.go +++ b/botgo/openapi/v1/message.go @@ -7,6 +7,7 @@ import ( "fmt" "strconv" + "github.com/go-resty/resty/v2" "github.com/tidwall/gjson" "github.com/tencent-connect/botgo/dto" @@ -221,14 +222,37 @@ func getC2CURLBySendType(msgType dto.SendType) uri { } // PostC2CMessage 回复C2C消息 -func (o *openAPI) PostC2CMessage(ctx context.Context, userID string, msg dto.APIMessage) (*dto.Message, error) { - resp, err := o.request(ctx). - SetResult(dto.Message{}). - SetPathParam("user_id", userID). - SetBody(msg). - Post(o.getURL(getC2CURLBySendType(msg.GetSendType()))) +func (o *openAPI) PostC2CMessage(ctx context.Context, userID string, msg dto.APIMessage) (*dto.C2CMessageResponse, error) { + var resp *resty.Response + var err error + + msgType := msg.GetSendType() + switch msgType { + case dto.RichMedia: + resp, err = o.request(ctx). + SetResult(dto.MediaResponse{}). // 设置为媒体响应类型 + SetPathParam("user_id", userID). + SetBody(msg). + Post(o.getURL(getC2CURLBySendType(msgType))) + default: + resp, err = o.request(ctx). + SetResult(dto.Message{}). // 设置为消息类型 + SetPathParam("user_id", userID). + SetBody(msg). + Post(o.getURL(getC2CURLBySendType(msgType))) + } + if err != nil { return nil, err } - return resp.Result().(*dto.Message), nil + + result := &dto.C2CMessageResponse{} + switch msgType { + case dto.RichMedia: + result.MediaResponse = resp.Result().(*dto.MediaResponse) + default: + result.Message = resp.Result().(*dto.Message) + } + + return result, nil } diff --git a/botgo/openapi/v2/message.go b/botgo/openapi/v2/message.go index 10f9bf2e..e21dc89d 100644 --- a/botgo/openapi/v2/message.go +++ b/botgo/openapi/v2/message.go @@ -237,14 +237,37 @@ func getC2CURLBySendType(msgType dto.SendType) uri { } // PostC2CMessage 回复C2C消息 -func (o *openAPIv2) PostC2CMessage(ctx context.Context, userID string, msg dto.APIMessage) (*dto.Message, error) { - resp, err := o.request(ctx). - SetResult(dto.Message{}). - SetPathParam("user_id", userID). - SetBody(msg). - Post(o.getURL(getC2CURLBySendType(msg.GetSendType()))) +func (o *openAPIv2) PostC2CMessage(ctx context.Context, userID string, msg dto.APIMessage) (*dto.C2CMessageResponse, error) { + var resp *resty.Response + var err error + + msgType := msg.GetSendType() + switch msgType { + case dto.RichMedia: + resp, err = o.request(ctx). + SetResult(dto.MediaResponse{}). // 设置为媒体响应类型 + SetPathParam("user_id", userID). + SetBody(msg). + Post(o.getURL(getC2CURLBySendType(msgType))) + default: + resp, err = o.request(ctx). + SetResult(dto.Message{}). // 设置为消息类型 + SetPathParam("user_id", userID). + SetBody(msg). + Post(o.getURL(getC2CURLBySendType(msgType))) + } + if err != nil { return nil, err } - return resp.Result().(*dto.Message), nil + + result := &dto.C2CMessageResponse{} + switch msgType { + case dto.RichMedia: + result.MediaResponse = resp.Result().(*dto.MediaResponse) + default: + result.Message = resp.Result().(*dto.Message) + } + + return result, nil } diff --git a/config/config.go b/config/config.go index 2d152df3..e29cd7bb 100644 --- a/config/config.go +++ b/config/config.go @@ -86,6 +86,7 @@ type Settings struct { CardAndNick string `yaml:"card_nick"` AutoBind bool `yaml:"auto_bind"` CustomBotName string `yaml:"custom_bot_name"` + SendDelay int `yaml:"send_delay"` } // LoadConfig 从文件中加载配置并初始化单例配置 @@ -1028,3 +1029,15 @@ func GetCustomBotName() string { } return instance.Settings.CustomBotName } + +// 获取send_delay的值 +func GetSendDelay() int { + mu.Lock() + defer mu.Unlock() + + if instance == nil { + mylog.Println("Warning: instance is nil when trying to GetSendDelay value.") + return 300 + } + return instance.Settings.SendDelay +} diff --git a/handlers/send_group_msg.go b/handlers/send_group_msg.go index d09ebcf1..4d34cdbb 100644 --- a/handlers/send_group_msg.go +++ b/handlers/send_group_msg.go @@ -339,7 +339,8 @@ func handleSendGroupMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openap tryMessageTypes := []string{"group", "guild", "guild_private"} messageCopy := message // 创建message的副本 echo.AddMsgType(config.GetAppIDStr(), idInt64, tryMessageTypes[echo.GetMapping(idInt64)-1]) - time.Sleep(300 * time.Millisecond) + delay := config.GetSendDelay() + time.Sleep(time.Duration(delay) * time.Millisecond) handleSendGroupMsg(client, api, apiv2, messageCopy) } } diff --git a/handlers/send_msg.go b/handlers/send_msg.go index 27406ee9..b5380729 100644 --- a/handlers/send_msg.go +++ b/handlers/send_msg.go @@ -108,7 +108,8 @@ func handleSendMsg(client callapi.Client, api openapi.OpenAPI, apiv2 openapi.Ope tryMessageTypes := []string{"group", "guild", "guild_private"} messageCopy := message // 创建message的副本 echo.AddMsgType(config.GetAppIDStr(), idInt64, tryMessageTypes[echo.GetMapping(idInt64)-1]) - time.Sleep(300 * time.Millisecond) + delay := config.GetSendDelay() + time.Sleep(time.Duration(delay) * time.Millisecond) handleSendMsg(client, api, apiv2, messageCopy) } } diff --git a/handlers/send_private_msg.go b/handlers/send_private_msg.go index 5962f553..5ae5f194 100644 --- a/handlers/send_private_msg.go +++ b/handlers/send_private_msg.go @@ -117,6 +117,72 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open mylog.Println("私聊发信息messageText:", messageText) //mylog.Println("foundItems:", foundItems) + var singleItem = make(map[string][]string) + var imageType, imageUrl string + imageCount := 0 + + // 检查不同类型的图片并计算数量 + if imageURLs, ok := foundItems["local_image"]; ok && len(imageURLs) == 1 { + imageType = "local_image" + imageUrl = imageURLs[0] + imageCount++ + } else if imageURLs, ok := foundItems["url_image"]; ok && len(imageURLs) == 1 { + imageType = "url_image" + imageUrl = imageURLs[0] + imageCount++ + } else if base64Images, ok := foundItems["base64_image"]; ok && len(base64Images) == 1 { + imageType = "base64_image" + imageUrl = base64Images[0] + imageCount++ + } + + if imageCount == 1 && messageText != "" { + mylog.Printf("发私聊图文混合信息") + // 创建包含单个图片的 singleItem + singleItem[imageType] = []string{imageUrl} + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupReply := generateGroupMessage(messageID, singleItem, "", msgseq+1) + // 进行类型断言 + richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) + if !ok { + mylog.Printf("Error: Expected RichMediaMessage type for key ") + return + } + // 上传图片并获取FileInfo + fileInfo, err := uploadMediaPrivate(context.TODO(), UserID, richMediaMessage, apiv2) + if err != nil { + mylog.Printf("上传图片失败: %v", err) + return // 或其他错误处理 + } + // 创建包含文本和图像信息的消息 + 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() // 设置时间戳 + + // 发送组合消息 + _, err = apiv2.PostC2CMessage(context.TODO(), UserID, groupMessage) + if err != nil { + mylog.Printf("发送组合消息失败: %v", err) + return // 或其他错误处理 + } + + // 发送成功回执 + SendResponse(client, err, &message) + + delete(foundItems, imageType) // 从foundItems中删除已处理的图片项 + messageText = "" + } + // 优先发送文本信息 if messageText != "" { msgseq := echo.GetMappingSeq(messageID) @@ -144,19 +210,56 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open for _, url := range urls { var singleItem = make(map[string][]string) singleItem[key] = []string{url} // 创建一个只包含一个 URL 的 singleItem + //mylog.Println("singleItem:", singleItem) msgseq := echo.GetMappingSeq(messageID) echo.AddMappingSeq(messageID, msgseq+1) groupReply := generateGroupMessage(messageID, singleItem, "", msgseq+1) - // 进行类型断言 richMediaMessage, ok := groupReply.(*dto.RichMediaMessage) if !ok { mylog.Printf("Error: Expected RichMediaMessage type for key %s.", key) continue // 跳过这个项,继续下一个 } - _, err = apiv2.PostC2CMessage(context.TODO(), UserID, richMediaMessage) + message_return, err := apiv2.PostC2CMessage(context.TODO(), UserID, richMediaMessage) if err != nil { - mylog.Printf("发送 %s 私聊信息失败: %v", key, err) + mylog.Printf("发送 %s 信息失败_send_private_msg: %v", key, err) + if config.GetSendError() { //把报错当作文本发出去 + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + groupReply := generateGroupMessage(messageID, nil, err.Error(), msgseq+1) + // 进行类型断言 + groupMessage, ok := groupReply.(*dto.MessageToCreate) + if !ok { + mylog.Println("Error: Expected MessageToCreate type.") + return // 或其他错误处理 + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + //重新为err赋值 + _, err = apiv2.PostC2CMessage(context.TODO(), UserID, groupMessage) + if err != nil { + mylog.Printf("发送 %s 私聊信息失败: %v", key, err) + } + } + } + if message_return != nil && message_return.MediaResponse != nil && message_return.MediaResponse.FileInfo != "" { + msgseq := echo.GetMappingSeq(messageID) + echo.AddMappingSeq(messageID, msgseq+1) + media := dto.Media{ + FileInfo: message_return.MediaResponse.FileInfo, + } + groupMessage := &dto.MessageToCreate{ + Content: " ", + MsgID: messageID, + MsgSeq: msgseq, + MsgType: 7, // 默认文本类型 + Media: media, + } + groupMessage.Timestamp = time.Now().Unix() // 设置时间戳 + //重新为err赋值 + _, err = apiv2.PostC2CMessage(context.TODO(), UserID, groupMessage) + if err != nil { + mylog.Printf("发送 %s 私聊信息失败: %v", key, err) + } } //发送成功回执 SendResponse(client, err, &message) @@ -179,7 +282,8 @@ func handleSendPrivateMsg(client callapi.Client, api openapi.OpenAPI, apiv2 open tryMessageTypes := []string{"group", "guild", "guild_private"} messageCopy := message // 创建message的副本 echo.AddMsgType(config.GetAppIDStr(), idInt64, tryMessageTypes[echo.GetMapping(idInt64)-1]) - time.Sleep(300 * time.Millisecond) + delay := config.GetSendDelay() + time.Sleep(time.Duration(delay) * time.Millisecond) handleSendGroupMsg(client, api, apiv2, messageCopy) } } @@ -420,3 +524,14 @@ func getGuildIDFromMessage(message callapi.ActionMessage) (string, string, error return guildID, channelID, nil } + +// uploadMedia 上传媒体并返回FileInfo +func uploadMediaPrivate(ctx context.Context, UserID string, richMediaMessage *dto.RichMediaMessage, apiv2 openapi.OpenAPI) (string, error) { + // 调用API来上传媒体 + messageReturn, err := apiv2.PostC2CMessage(ctx, UserID, richMediaMessage) + if err != nil { + return "", err + } + // 返回上传后的FileInfo + return messageReturn.MediaResponse.FileInfo, nil +} diff --git a/template/config_template.go b/template/config_template.go index 5c1be7ad..7d3a9f37 100644 --- a/template/config_template.go +++ b/template/config_template.go @@ -96,6 +96,7 @@ settings: send_error : true #将报错用文本发出,避免机器人被审核报无响应 url_pic_transfer : false #把图片url(任意来源图链)变成你备案的白名单url 需要较高上下行+ssl+自备案域名+设置白名单域名(暂时不需要) idmap_pro : false #需开启hash_id配合,高级id转换增强,可以多个真实值bind到同一个虚拟值,对于每个用户,每个群\私聊\判断私聊\频道,都会产生新的虚拟值,但可以多次bind,bind到同一个数字.数据库负担会变大. + send_delay : 300 #单位 毫秒 默认300ms 可以视情况减少到100或者50 title : "Gensokyo © 2023 - Hoshinonyaruko" #程序的标题 如果多个机器人 可根据标题区分 custom_bot_name : "Gensokyo全域机器人" #自定义机器人名字,会在api调用中返回,默认Gensokyo全域机器人 diff --git a/template/config_template.yml b/template/config_template.yml index cc64b24f..1b6f0aed 100644 --- a/template/config_template.yml +++ b/template/config_template.yml @@ -77,3 +77,4 @@ settings: card_nick : "" #默认为空,连接mirai-overflow时,请设置为非空,这里是机器人对用户称谓,为空为插件获取,mirai不支持 auto_bind : true #测试功能,后期会移除 custom_bot_name : "Gensokyo全域机器人" #自定义机器人名字,会在api调用中返回,默认Gensokyo全域机器人 + send_delay : 300 #单位 毫秒 默认300ms 可以视情况减少到100或者50