github.com/fumiama/NanoBot@v0.0.0-20231122134259-c22d8183efca/openapi_message.go (about) 1 package nano 2 3 import ( 4 "encoding/json" 5 "reflect" 6 "strconv" 7 "strings" 8 "time" 9 10 "github.com/pkg/errors" 11 "github.com/sirupsen/logrus" 12 ) 13 14 var ( 15 ErrEmptyMessagePost = errors.New("empty message post") 16 ) 17 18 // Message 消息对象 19 // 20 // https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#%E6%B6%88%E6%81%AF%E5%AF%B9%E8%B1%A1-message 21 type Message struct { 22 ID string `json:"id"` 23 ChannelID string `json:"channel_id"` 24 GuildID string `json:"guild_id"` 25 GroupOpenID string `json:"group_openid"` 26 FileUUID string `json:"file_uuid"` 27 FileInfo string `json:"file_info"` 28 Content string `json:"content"` 29 Timestamp *time.Time `json:"timestamp"` 30 EditedTimestamp *time.Time `json:"edited_timestamp"` 31 FileInfoTTL int `json:"ttl"` 32 MentionEveryone bool `json:"mention_everyone"` 33 Author *User `json:"author"` 34 Attachments []MessageAttachment `json:"attachments"` 35 Embeds []MessageEmbed `json:"embeds"` 36 Member *Member `json:"member"` 37 Ark *MessageArk `json:"ark"` 38 SeqInChannel string `json:"seq_in_channel"` 39 MessageReference *MessageReference `json:"message_reference"` 40 SrcGuildID string `json:"src_guild_id"` 41 Data *struct { 42 MessageAudit *MessageAudited `json:"message_audit,omitempty"` 43 } `json:"data,omitempty"` 44 } 45 46 // "=> ギルド:", ctx.Message.GuildID+", 频道:", ctx.Message.ChannelID+", 用户:", ctx.Message.Author.Username+"("+ctx.Message.Author.ID+"), 内容:", ctx.Message.Content 47 func (m *Message) String() string { 48 sb := strings.Builder{} 49 if m.Timestamp != nil { 50 sb.WriteString(m.Timestamp.Format(time.DateTime)) 51 sb.WriteByte(' ') 52 } 53 if m.FileUUID != "" { 54 sb.WriteString("富媒体: ") 55 sb.WriteString(m.FileUUID) 56 sb.WriteString(", 有效期: ") 57 if m.FileInfoTTL == 0 { 58 sb.WriteString("长期") 59 } else { 60 sb.WriteString(strconv.Itoa(m.FileInfoTTL)) 61 sb.WriteByte('s') 62 } 63 u, err := mediaURL(m.FileInfo) 64 if err == nil { 65 sb.WriteString(", URL: ") 66 sb.WriteString(u) 67 } 68 return sb.String() 69 } 70 if m.SeqInChannel != "" { 71 sb.WriteByte('[') 72 sb.WriteString(m.SeqInChannel) 73 sb.WriteByte(']') 74 } 75 sb.WriteString(m.ID) 76 sb.WriteString(" ギルド: ") 77 sb.WriteString(m.GuildID) 78 if m.SrcGuildID != "" { 79 sb.WriteString(", 元ギルド: ") 80 sb.WriteString(m.SrcGuildID) 81 } 82 sb.WriteString(", 频道: ") 83 sb.WriteString(m.ChannelID) 84 if m.Author != nil { 85 sb.WriteString(", 用户: ") 86 sb.WriteString(m.Author.Username) 87 sb.WriteByte('(') 88 sb.WriteString(m.Author.ID) 89 sb.WriteByte(')') 90 if m.Author.Bot { 91 sb.WriteString("(机器人)") 92 } 93 } else { 94 sb.WriteString(", 用户: 未知") 95 } 96 if m.Content == "" { 97 sb.WriteString(", 无文本") 98 } else { 99 sb.WriteString(", 文本: ") 100 if m.MentionEveryone { 101 sb.WriteString("[@全体]") 102 } 103 sb.WriteString(m.Content) 104 } 105 if len(m.Attachments) > 0 { 106 sb.WriteString(", 附加: ") 107 for _, a := range m.Attachments { 108 sb.WriteString("<ID:") 109 sb.WriteString(a.ID) 110 sb.WriteString(",URL:") 111 sb.WriteString(a.URL) 112 sb.WriteByte('>') 113 } 114 } 115 if len(m.Embeds) > 0 { 116 for _, e := range m.Embeds { 117 sb.WriteString(", 嵌入: <标题:") 118 sb.WriteString(e.Title) 119 sb.WriteString(",提示:") 120 sb.WriteString(e.Prompt) 121 sb.WriteByte('>') 122 } 123 } 124 if m.Ark != nil { 125 sb.WriteString(", 模版: ") 126 sb.WriteString(strconv.Itoa(m.Ark.TemplateID)) 127 } 128 if m.MessageReference != nil { 129 sb.WriteString(", 回复: ") 130 sb.WriteString(m.MessageReference.MessageID) 131 } 132 return sb.String() 133 } 134 135 // MessageEmbed https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembed 136 type MessageEmbed struct { 137 Title string `json:"title"` 138 Prompt string `json:"prompt"` 139 Thumbnail *MessageEmbedThumbnail `json:"thumbnail"` 140 Fields []MessageEmbedField `json:"fields"` 141 } 142 143 // MessageEmbedThumbnail https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembedthumbnail 144 type MessageEmbedThumbnail struct { 145 URL string `json:"url"` 146 } 147 148 // MessageEmbedField https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembedfield 149 type MessageEmbedField struct { 150 Name string `json:"name"` 151 } 152 153 // MessageAttachment https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageattachment 154 type MessageAttachment struct { 155 ContentType string `json:"content_type,omitempty"` 156 Filename string `json:"filename,omitempty"` 157 Height int `json:"height,omitempty"` 158 ID string `json:"id,omitempty"` 159 Size int `json:"size,omitempty"` 160 URL string `json:"url,omitempty"` 161 Width int `json:"width,omitempty"` 162 } 163 164 // MessageArk https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageark 165 type MessageArk struct { 166 TemplateID int `json:"template_id"` 167 KV []MessageArkKV `json:"kv"` 168 } 169 170 // MessageArkKV https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagearkkv 171 type MessageArkKV struct { 172 Key string `json:"key"` 173 Value string `json:"value"` 174 Obj []MessageArkObj `json:"obj"` 175 } 176 177 // MessageArkObj https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagearkobj 178 type MessageArkObj struct { 179 ObjKV []MessageArkObjKV `json:"obj_kv"` 180 } 181 182 // MessageArkObjKV https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagearkobjkv 183 type MessageArkObjKV struct { 184 Key string `json:"key"` 185 Value string `json:"value"` 186 } 187 188 // MessageReference https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagereference 189 type MessageReference struct { 190 MessageID string `json:"message_id"` 191 IgnoreGetMessageError bool `json:"ignore_get_message_error"` 192 } 193 194 // MessageDelete https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagedelete 195 type MessageDelete struct { 196 Message *Message `json:"message"` 197 OpUser *User `json:"op_user"` 198 } 199 200 func (mdl *MessageDelete) String() string { 201 sb := strings.Builder{} 202 sb.WriteString("用户ID ") 203 sb.WriteString(mdl.OpUser.ID) 204 sb.WriteString(" 删除了消息: ") 205 sb.WriteString(mdl.Message.ID) 206 sb.WriteString(" ギルド: ") 207 sb.WriteString(mdl.Message.GuildID) 208 if mdl.Message.SrcGuildID != "" { 209 sb.WriteString(", 元ギルド: ") 210 sb.WriteString(mdl.Message.SrcGuildID) 211 } 212 sb.WriteString(", 频道: ") 213 sb.WriteString(mdl.Message.ChannelID) 214 if mdl.Message.Member.User != nil { 215 sb.WriteString(", 用户: ") 216 sb.WriteString(mdl.Message.Member.User.Username) 217 sb.WriteByte('(') 218 sb.WriteString(mdl.Message.Member.User.ID) 219 sb.WriteByte(')') 220 if mdl.Message.Member.User.Bot { 221 sb.WriteString("(机器人)") 222 } 223 } else { 224 sb.WriteString(", 用户: 未知") 225 } 226 return sb.String() 227 } 228 229 // MessageAudited 消息审核对象 230 // 231 // https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#%E6%B6%88%E6%81%AF%E5%AE%A1%E6%A0%B8%E5%AF%B9%E8%B1%A1-messageaudited 232 type MessageAudited struct { 233 AuditID string `json:"audit_id"` 234 AuditTime time.Time `json:"audit_time"` 235 ChannelID string `json:"channel_id"` 236 CreateTime time.Time `json:"create_time"` 237 GuildID string `json:"guild_id"` 238 MessageID string `json:"message_id"` 239 } 240 241 // GetMessageFromChannel 获取子频道 channel_id 下的消息 message_id 的详情 242 // 243 // https://bot.q.qq.com/wiki/develop/api/openapi/message/get_message_of_id.html 244 func (bot *Bot) GetMessageFromChannel(messageid, channelid string) (*Message, error) { 245 return bot.getOpenAPIofMessage("/channels/" + channelid + "/messages/" + messageid) 246 } 247 248 // MessagePost 发送消息所需参数 249 // 250 // https://bot.q.qq.com/wiki/develop/api/openapi/message/post_messages.html#%E9%80%9A%E7%94%A8%E5%8F%82%E6%95%B0 251 type MessagePost struct { 252 // https://bot.q.qq.com/wiki/develop/api-231017/server-inter/message/send-receive/send.html 253 Type MessageType `json:"msg_type"` 254 Seq int `json:"msg_seq,omitempty"` // 回复消息的序号,与 msg_id 联合使用,避免相同消息id回复重复发送,不填默认是1。相同的 msg_id + msg_seq 重复发送会失败。 255 Content string `json:"content,omitempty"` 256 Embed *MessageEmbed `json:"embed,omitempty"` // https://bot.q.qq.com/wiki/develop/api/openapi/message/template/embed_message.html 257 Ark *MessageArk `json:"ark,omitempty"` // https://bot.q.qq.com/wiki/develop/api/openapi/message/message_template.html 258 MessageReference *MessageReference `json:"message_reference,omitempty"` 259 Image string `json:"image,omitempty"` 260 ImageFile string `json:"-"` // ImageFile 为图片路径 file:/// or base64:// or base16384:// , 与 Image, ImageBytes 参数二选一, 优先 ImageBytes 261 ImageBytes []byte `json:"-"` // ImageBytes 图片数据 262 ReplyMessageID string `json:"msg_id,omitempty"` 263 ReplyEventID string `json:"event_id,omitempty"` 264 Markdown *MessageMarkdown `json:"markdown,omitempty"` 265 KeyBoard *MessageKeyboard `json:"keyboard,omitempty"` 266 Media *MessageMedia `json:"media,omitempty"` 267 } 268 269 func (mp *MessagePost) String() string { 270 sb := strings.Builder{} 271 if mp.Seq > 0 { 272 sb.WriteString("[v2:") 273 sb.WriteString(mp.Type.String()) 274 sb.WriteString("#") 275 sb.WriteString(strconv.Itoa(mp.Seq)) 276 sb.WriteString("]") 277 } 278 if mp.Content == "" { 279 sb.WriteString("无文本") 280 } else { 281 sb.WriteString("文本: ") 282 sb.WriteString(mp.Content) 283 } 284 if mp.ReplyMessageID != "" { 285 sb.WriteString(", 回应消息: ") 286 sb.WriteString(mp.ReplyMessageID) 287 } 288 if mp.ReplyEventID != "" { 289 sb.WriteString(", 回应事件: ") 290 sb.WriteString(mp.ReplyEventID) 291 } 292 if mp.Embed != nil { 293 sb.WriteString(", 嵌入: <标题:") 294 sb.WriteString(mp.Embed.Title) 295 sb.WriteString(",提示:") 296 sb.WriteString(mp.Embed.Prompt) 297 sb.WriteByte('>') 298 } 299 if mp.Ark != nil { 300 sb.WriteString(", 模版: ") 301 sb.WriteString(strconv.Itoa(mp.Ark.TemplateID)) 302 } 303 if mp.MessageReference != nil { 304 sb.WriteString(", 回复: ") 305 sb.WriteString(mp.MessageReference.MessageID) 306 } 307 if mp.Image != "" { 308 sb.WriteString(", 图片URL: ") 309 sb.WriteString(mp.Image) 310 } 311 if mp.ImageFile != "" { 312 sb.WriteString(", 图片内容: ") 313 x := mp.ImageFile 314 if len(x) > 64 { 315 x = x[:64] + "..." 316 } 317 sb.WriteString(x) 318 } 319 if len(mp.ImageBytes) > 0 { 320 sb.WriteString(", 图片大小: ") 321 sb.WriteString(strconv.Itoa(len(mp.ImageBytes))) 322 } 323 if mp.Markdown != nil { 324 sb.WriteString(", MD模版: ") 325 sb.WriteString(strconv.Itoa(mp.Markdown.TemplateID)) 326 } 327 if mp.KeyBoard != nil { 328 sb.WriteString(", KB模版: ") 329 sb.WriteString(mp.KeyBoard.ID) 330 } 331 if mp.Media != nil { 332 sb.WriteString(", 富媒体: ") 333 u, err := mediaURL(mp.Media.FileInfo) 334 if err == nil { 335 sb.WriteString(u) 336 } else { 337 sb.WriteString(mp.Media.FileInfo) 338 } 339 } 340 return sb.String() 341 } 342 343 func (bot *Bot) postMessageTo(ep string, content *MessagePost) (*Message, error) { 344 if len(content.ImageBytes) == 0 && content.ImageFile == "" { 345 return bot.postOpenAPIofMessage(ep, "", WriteBodyFromJSON(content)) 346 } 347 x := reflect.ValueOf(content).Elem() 348 t := x.Type() 349 msg := []any{} 350 for i := 0; i < x.NumField(); i++ { 351 xi := x.Field(i) 352 if xi.IsZero() { 353 continue 354 } 355 tag, _, _ := strings.Cut(t.Field(i).Tag.Get("json"), ",") 356 if tag == "-" { 357 tag = "file_image" 358 } 359 msg = append(msg, tag) 360 if xi.Kind() == reflect.Pointer && xi.Elem().Kind() == reflect.Struct { 361 data, err := json.Marshal(xi.Interface()) 362 if err != nil { 363 return nil, err 364 } 365 msg = append(msg, data) 366 continue 367 } 368 msg = append(msg, xi.Interface()) // []byte or string 369 } 370 if len(msg) < 2 { 371 return nil, ErrEmptyMessagePost 372 } 373 body, contenttype, err := WriteBodyByMultipartFormData(msg...) 374 if err != nil { 375 return nil, errors.Wrap(err, getThisFuncName()) 376 } 377 m, err := bot.postOpenAPIofMessage(ep, contenttype, body) 378 if err != nil { 379 return nil, errors.Wrap(err, getThisFuncName()) 380 } 381 logrus.Infoln(getLogHeader(), "=> 消息结果:", m) 382 return m, nil 383 } 384 385 // PostMessageToChannel 向 channel_id 指定的子频道发送消息 386 // 387 // https://bot.q.qq.com/wiki/develop/api/openapi/message/post_messages.html 388 func (bot *Bot) PostMessageToChannel(id string, content *MessagePost) (*Message, error) { 389 logrus.Infoln(getLogHeader(), "<= [公]频道:", id+",", content) 390 return bot.postMessageTo("/channels/"+id+"/messages", content) 391 } 392 393 // DeleteMessageInChannel 回子频道 channel_id 下的消息 message_id 394 // 395 // https://bot.q.qq.com/wiki/develop/api/openapi/message/delete_message.html 396 func (bot *Bot) DeleteMessageInChannel(channelid, messageid string, hidetip bool) error { 397 logrus.Infoln(getLogHeader(), "<x 频道:", channelid+", 消息:", messageid) 398 return bot.DeleteOpenAPI(WriteHTTPQueryIfNotNil("/channels/"+channelid+"/messages/"+messageid, 399 "hidetip", hidetip, 400 ), "", nil) 401 } 402 403 // MessageSetting 频道消息频率设置对象 404 // 405 // https://bot.q.qq.com/wiki/develop/api/openapi/setting/model.html 406 type MessageSetting struct { 407 DisableCreateDm bool `json:"disable_create_dm"` 408 DisablePushMsg bool `json:"disable_push_msg"` 409 ChannelIDs []string `json:"channel_ids"` 410 ChannelPushMaxNum uint32 `json:"channel_push_max_num"` 411 } 412 413 // GetGuildMessageSetting 获取机器人在频道 guild_id 内的消息频率设置 414 // 415 // https://bot.q.qq.com/wiki/develop/api/openapi/setting/message_setting.html 416 func (bot *Bot) GetGuildMessageSetting(id string) (*MessageSetting, error) { 417 return bot.getOpenAPIofMessageSetting("/guilds/" + id + "/message/setting") 418 }