github.com/Mrs4s/go-cqhttp@v1.2.0/coolq/bot.go (about) 1 package coolq 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "encoding/json" 7 "fmt" 8 "image/png" 9 "os" 10 "runtime/debug" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/Mrs4s/MiraiGo/binary" 16 "github.com/Mrs4s/MiraiGo/client" 17 "github.com/Mrs4s/MiraiGo/message" 18 "github.com/Mrs4s/MiraiGo/utils" 19 "github.com/RomiChan/syncx" 20 "github.com/pkg/errors" 21 "github.com/segmentio/asm/base64" 22 log "github.com/sirupsen/logrus" 23 "golang.org/x/image/webp" 24 25 "github.com/Mrs4s/go-cqhttp/db" 26 "github.com/Mrs4s/go-cqhttp/global" 27 "github.com/Mrs4s/go-cqhttp/internal/base" 28 "github.com/Mrs4s/go-cqhttp/internal/mime" 29 "github.com/Mrs4s/go-cqhttp/internal/msg" 30 "github.com/Mrs4s/go-cqhttp/pkg/onebot" 31 ) 32 33 // CQBot CQBot结构体,存储Bot实例相关配置 34 type CQBot struct { 35 Client *client.QQClient 36 37 lock sync.RWMutex 38 events []func(*Event) 39 40 friendReqCache syncx.Map[string, *client.NewFriendRequest] 41 tempSessionCache syncx.Map[int64, *client.TempSessionInfo] 42 nextTokenCache *utils.Cache[*guildMemberPageToken] 43 } 44 45 // Event 事件 46 type Event struct { 47 once sync.Once 48 Raw *event 49 buffer *bytes.Buffer 50 } 51 52 func (e *Event) marshal() { 53 if e.buffer == nil { 54 e.buffer = global.NewBuffer() 55 } 56 _ = json.NewEncoder(e.buffer).Encode(e.Raw) 57 } 58 59 // JSONBytes return byes of json by lazy marshalling. 60 func (e *Event) JSONBytes() []byte { 61 e.once.Do(e.marshal) 62 return e.buffer.Bytes() 63 } 64 65 // JSONString return string of json without extra allocation 66 // by lazy marshalling. 67 func (e *Event) JSONString() string { 68 e.once.Do(e.marshal) 69 return utils.B2S(e.buffer.Bytes()) 70 } 71 72 // NewQQBot 初始化一个QQBot实例 73 func NewQQBot(cli *client.QQClient) *CQBot { 74 bot := &CQBot{ 75 Client: cli, 76 nextTokenCache: utils.NewCache[*guildMemberPageToken](time.Second * 10), 77 } 78 bot.Client.PrivateMessageEvent.Subscribe(bot.privateMessageEvent) 79 bot.Client.GroupMessageEvent.Subscribe(bot.groupMessageEvent) 80 if base.ReportSelfMessage { 81 bot.Client.SelfPrivateMessageEvent.Subscribe(bot.privateMessageEvent) 82 bot.Client.SelfGroupMessageEvent.Subscribe(bot.groupMessageEvent) 83 } 84 bot.Client.TempMessageEvent.Subscribe(bot.tempMessageEvent) 85 bot.Client.GuildService.OnGuildChannelMessage(bot.guildChannelMessageEvent) 86 bot.Client.GuildService.OnGuildMessageReactionsUpdated(bot.guildMessageReactionsUpdatedEvent) 87 bot.Client.GuildService.OnGuildMessageRecalled(bot.guildChannelMessageRecalledEvent) 88 bot.Client.GuildService.OnGuildChannelUpdated(bot.guildChannelUpdatedEvent) 89 bot.Client.GuildService.OnGuildChannelCreated(bot.guildChannelCreatedEvent) 90 bot.Client.GuildService.OnGuildChannelDestroyed(bot.guildChannelDestroyedEvent) 91 bot.Client.GroupMuteEvent.Subscribe(bot.groupMutedEvent) 92 bot.Client.GroupMessageRecalledEvent.Subscribe(bot.groupRecallEvent) 93 bot.Client.GroupNotifyEvent.Subscribe(bot.groupNotifyEvent) 94 bot.Client.FriendNotifyEvent.Subscribe(bot.friendNotifyEvent) 95 bot.Client.MemberSpecialTitleUpdatedEvent.Subscribe(bot.memberTitleUpdatedEvent) 96 bot.Client.FriendMessageRecalledEvent.Subscribe(bot.friendRecallEvent) 97 bot.Client.OfflineFileEvent.Subscribe(bot.offlineFileEvent) 98 bot.Client.GroupJoinEvent.Subscribe(bot.joinGroupEvent) 99 bot.Client.GroupLeaveEvent.Subscribe(bot.leaveGroupEvent) 100 bot.Client.GroupMemberJoinEvent.Subscribe(bot.memberJoinEvent) 101 bot.Client.GroupMemberLeaveEvent.Subscribe(bot.memberLeaveEvent) 102 bot.Client.GroupMemberPermissionChangedEvent.Subscribe(bot.memberPermissionChangedEvent) 103 bot.Client.MemberCardUpdatedEvent.Subscribe(bot.memberCardUpdatedEvent) 104 bot.Client.NewFriendRequestEvent.Subscribe(bot.friendRequestEvent) 105 bot.Client.NewFriendEvent.Subscribe(bot.friendAddedEvent) 106 bot.Client.GroupInvitedEvent.Subscribe(bot.groupInvitedEvent) 107 bot.Client.UserWantJoinGroupEvent.Subscribe(bot.groupJoinReqEvent) 108 bot.Client.OtherClientStatusChangedEvent.Subscribe(bot.otherClientStatusChangedEvent) 109 bot.Client.GroupDigestEvent.Subscribe(bot.groupEssenceMsg) 110 go func() { 111 if base.HeartbeatInterval == 0 { 112 log.Warn("警告: 心跳功能已关闭,若非预期,请检查配置文件。") 113 return 114 } 115 t := time.NewTicker(base.HeartbeatInterval) 116 for { 117 <-t.C 118 bot.dispatchEvent("meta_event/heartbeat", global.MSG{ 119 "status": bot.CQGetStatus(onebot.V11)["data"], 120 "interval": base.HeartbeatInterval.Milliseconds(), 121 }) 122 } 123 }() 124 return bot 125 } 126 127 // OnEventPush 注册事件上报函数 128 func (bot *CQBot) OnEventPush(f func(e *Event)) { 129 bot.lock.Lock() 130 bot.events = append(bot.events, f) 131 bot.lock.Unlock() 132 } 133 134 type worker struct { 135 wg sync.WaitGroup 136 } 137 138 func (w *worker) do(f func()) { 139 w.wg.Add(1) 140 go func() { 141 defer w.wg.Done() 142 f() 143 }() 144 } 145 146 func (w *worker) wait() { 147 w.wg.Wait() 148 } 149 150 // uploadLocalImage 上传本地图片 151 func (bot *CQBot) uploadLocalImage(target message.Source, img *msg.LocalImage) (message.IMessageElement, error) { 152 if img.File != "" { 153 f, err := os.Open(img.File) 154 if err != nil { 155 return nil, errors.Wrap(err, "open image error") 156 } 157 defer func() { _ = f.Close() }() 158 img.Stream = f 159 } 160 mt, ok := mime.CheckImage(img.Stream) 161 if !ok { 162 return nil, errors.New("image type error: " + mt) 163 } 164 if mt == "image/webp" && base.ConvertWebpImage { 165 img0, err := webp.Decode(img.Stream) 166 if err != nil { 167 return nil, errors.Wrap(err, "decode webp error") 168 } 169 stream := bytes.NewBuffer(nil) 170 err = png.Encode(stream, img0) 171 if err != nil { 172 return nil, errors.Wrap(err, "encode png error") 173 } 174 img.Stream = bytes.NewReader(stream.Bytes()) 175 } 176 i, err := bot.Client.UploadImage(target, img.Stream) 177 if err != nil { 178 return nil, err 179 } 180 switch i := i.(type) { 181 case *message.GroupImageElement: 182 i.Flash = img.Flash 183 i.EffectID = img.EffectID 184 case *message.FriendImageElement: 185 i.Flash = img.Flash 186 } 187 return i, err 188 } 189 190 // uploadLocalVideo 上传本地短视频至群聊 191 func (bot *CQBot) uploadLocalVideo(target message.Source, v *msg.LocalVideo) (*message.ShortVideoElement, error) { 192 video, err := os.Open(v.File) 193 if err != nil { 194 return nil, err 195 } 196 defer func() { _ = video.Close() }() 197 return bot.Client.UploadShortVideo(target, video, v.Thumb) 198 } 199 200 func removeLocalElement(elements []message.IMessageElement) []message.IMessageElement { 201 var j int 202 for i, e := range elements { 203 switch e.(type) { 204 case *msg.LocalImage, *msg.LocalVideo: 205 case *message.VoiceElement: // 未上传的语音消息, 也删除 206 case nil: 207 default: 208 if j < i { 209 elements[j] = e 210 } 211 j++ 212 } 213 } 214 return elements[:j] 215 } 216 217 const uploadFailedTemplate = "警告: %s %d %s上传失败: %v" 218 219 func (bot *CQBot) uploadMedia(target message.Source, elements []message.IMessageElement) []message.IMessageElement { 220 var w worker 221 var source string 222 switch target.SourceType { // nolint:exhaustive 223 case message.SourceGroup: 224 source = "群" 225 case message.SourcePrivate: 226 source = "私聊" 227 case message.SourceGuildChannel: 228 source = "频道" 229 } 230 231 for i, m := range elements { 232 p := &elements[i] 233 switch e := m.(type) { 234 case *msg.LocalImage: 235 w.do(func() { 236 m, err := bot.uploadLocalImage(target, e) 237 if err != nil { 238 log.Warnf(uploadFailedTemplate, source, target.PrimaryID, "图片", err) 239 } else { 240 *p = m 241 } 242 }) 243 case *message.VoiceElement: 244 w.do(func() { 245 m, err := bot.Client.UploadVoice(target, bytes.NewReader(e.Data)) 246 if err != nil { 247 log.Warnf(uploadFailedTemplate, source, target.PrimaryID, "语音", err) 248 } else { 249 *p = m 250 } 251 }) 252 case *msg.LocalVideo: 253 w.do(func() { 254 m, err := bot.uploadLocalVideo(target, e) 255 if err != nil { 256 log.Warnf(uploadFailedTemplate, source, target.PrimaryID, "视频", err) 257 } else { 258 *p = m 259 } 260 }) 261 } 262 } 263 w.wait() 264 return removeLocalElement(elements) 265 } 266 267 // SendGroupMessage 发送群消息 268 func (bot *CQBot) SendGroupMessage(groupID int64, m *message.SendingMessage) (int32, error) { 269 newElem := make([]message.IMessageElement, 0, len(m.Elements)) 270 group := bot.Client.FindGroup(groupID) 271 source := message.Source{ 272 SourceType: message.SourceGroup, 273 PrimaryID: groupID, 274 } 275 m.Elements = bot.uploadMedia(source, m.Elements) 276 for _, e := range m.Elements { 277 switch i := e.(type) { 278 case *msg.Poke: 279 if group != nil { 280 if mem := group.FindMember(i.Target); mem != nil { 281 mem.Poke() 282 } 283 } 284 return 0, nil 285 case *message.MusicShareElement: 286 ret, err := bot.Client.SendGroupMusicShare(groupID, i) 287 if err != nil { 288 log.Warnf("警告: 群 %v 富文本消息发送失败: %v", groupID, err) 289 return -1, errors.Wrap(err, "send group music share error") 290 } 291 return bot.InsertGroupMessage(ret, source), nil 292 case *message.AtElement: 293 if i.Target == 0 && group.SelfPermission() == client.Member { 294 e = message.NewText("@全体成员") 295 } 296 } 297 newElem = append(newElem, e) 298 } 299 if len(newElem) == 0 { 300 log.Warnf("群 %v 消息发送失败: 消息为空.", groupID) 301 return -1, errors.New("empty message") 302 } 303 m.Elements = newElem 304 bot.checkMedia(newElem, groupID) 305 ret := bot.Client.SendGroupMessage(groupID, m) 306 if ret == nil || ret.Id == -1 { 307 log.Warnf("群 %v 发送消息失败: 账号可能被风控.", groupID) 308 return -1, errors.New("send group message failed: blocked by server") 309 } 310 return bot.InsertGroupMessage(ret, source), nil 311 } 312 313 // SendPrivateMessage 发送私聊消息 314 func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.SendingMessage) int32 { 315 newElem := make([]message.IMessageElement, 0, len(m.Elements)) 316 source := message.Source{ 317 SourceType: message.SourcePrivate, 318 PrimaryID: target, 319 } 320 m.Elements = bot.uploadMedia(source, m.Elements) 321 for _, e := range m.Elements { 322 switch i := e.(type) { 323 case *msg.Poke: 324 bot.Client.SendFriendPoke(i.Target) 325 return 0 326 case *message.MusicShareElement: 327 bot.Client.SendFriendMusicShare(target, i) 328 return 0 329 } 330 newElem = append(newElem, e) 331 } 332 if len(newElem) == 0 { 333 log.Warnf("好友消息发送失败: 消息为空.") 334 return -1 335 } 336 m.Elements = newElem 337 bot.checkMedia(newElem, bot.Client.Uin) 338 339 // 单向好友是否存在 340 unidirectionalFriendExists := func() bool { 341 list, err := bot.Client.GetUnidirectionalFriendList() 342 if err != nil { 343 return false 344 } 345 for _, f := range list { 346 if f.Uin == target { 347 return true 348 } 349 } 350 return false 351 } 352 353 session, ok := bot.tempSessionCache.Load(target) 354 var id int32 = -1 355 356 switch { 357 case bot.Client.FindFriend(target) != nil: // 双向好友 358 msg := bot.Client.SendPrivateMessage(target, m) 359 if msg != nil { 360 id = bot.InsertPrivateMessage(msg, source) 361 } 362 case ok || groupID != 0: // 临时会话 363 if !base.AllowTempSession { 364 log.Warnf("发送临时会话消息失败: 已关闭临时会话信息发送功能") 365 return -1 366 } 367 switch { 368 case groupID != 0 && bot.Client.FindGroup(groupID) == nil: 369 log.Errorf("错误: 找不到群(%v)", groupID) 370 case groupID != 0 && !bot.Client.FindGroup(groupID).AdministratorOrOwner(): 371 log.Errorf("错误: 机器人在群(%v) 为非管理员或群主, 无法主动发起临时会话", groupID) 372 case groupID != 0 && bot.Client.FindGroup(groupID).FindMember(target) == nil: 373 log.Errorf("错误: 群员(%v) 不在 群(%v), 无法发起临时会话", target, groupID) 374 default: 375 if session == nil && groupID != 0 { 376 msg := bot.Client.SendGroupTempMessage(groupID, target, m) 377 //lint:ignore SA9003 there is a todo 378 if msg != nil { // nolint 379 // todo(Mrs4s) 380 // id = bot.InsertTempMessage(target, msg) 381 } 382 break 383 } 384 msg, err := session.SendMessage(m) 385 if err != nil { 386 log.Errorf("发送临时会话消息失败: %v", err) 387 break 388 } 389 //lint:ignore SA9003 there is a todo 390 if msg != nil { // nolint 391 // todo(Mrs4s) 392 // id = bot.InsertTempMessage(target, msg) 393 } 394 } 395 case unidirectionalFriendExists(): // 单向好友 396 msg := bot.Client.SendPrivateMessage(target, m) 397 if msg != nil { 398 id = bot.InsertPrivateMessage(msg, source) 399 } 400 default: 401 nickname := "Unknown" 402 if summaryInfo, _ := bot.Client.GetSummaryInfo(target); summaryInfo != nil { 403 nickname = summaryInfo.Nickname 404 } 405 log.Errorf("错误: 请先添加 %v(%v) 为好友", nickname, target) 406 } 407 return id 408 } 409 410 // SendGuildChannelMessage 发送频道消息 411 func (bot *CQBot) SendGuildChannelMessage(guildID, channelID uint64, m *message.SendingMessage) string { 412 newElem := make([]message.IMessageElement, 0, len(m.Elements)) 413 source := message.Source{ 414 SourceType: message.SourceGuildChannel, 415 PrimaryID: int64(guildID), 416 SecondaryID: int64(channelID), 417 } 418 m.Elements = bot.uploadMedia(source, m.Elements) 419 for _, e := range m.Elements { 420 switch i := e.(type) { 421 case *message.MusicShareElement: 422 bot.Client.SendGuildMusicShare(guildID, channelID, i) 423 return "-1" // todo: fix this 424 425 case *message.VoiceElement, *msg.Poke: 426 log.Warnf("警告: 频道暂不支持发送 %v 消息", i.Type().String()) 427 continue 428 } 429 newElem = append(newElem, e) 430 } 431 if len(newElem) == 0 { 432 log.Warnf("频道消息发送失败: 消息为空.") 433 return "" 434 } 435 m.Elements = newElem 436 bot.checkMedia(newElem, bot.Client.Uin) 437 ret, err := bot.Client.GuildService.SendGuildChannelMessage(guildID, channelID, m) 438 if err != nil { 439 log.Warnf("频道消息发送失败: %v", err) 440 return "" 441 } 442 // todo: insert db 443 return fmt.Sprintf("%v-%v", ret.Id, ret.InternalId) 444 } 445 446 // InsertGroupMessage 群聊消息入数据库 447 func (bot *CQBot) InsertGroupMessage(m *message.GroupMessage, source message.Source) int32 { 448 t := &message.SendingMessage{Elements: m.Elements} 449 replyElem := t.FirstOrNil(func(e message.IMessageElement) bool { 450 _, ok := e.(*message.ReplyElement) 451 return ok 452 }) 453 msg := &db.StoredGroupMessage{ 454 ID: encodeMessageID(m.GroupCode, m.Id), 455 GlobalID: db.ToGlobalID(m.GroupCode, m.Id), 456 SubType: "normal", 457 Attribute: &db.StoredMessageAttribute{ 458 MessageSeq: m.Id, 459 InternalID: m.InternalId, 460 SenderUin: m.Sender.Uin, 461 SenderName: m.Sender.DisplayName(), 462 Timestamp: int64(m.Time), 463 }, 464 GroupCode: m.GroupCode, 465 AnonymousID: func() string { 466 if m.Sender.IsAnonymous() { 467 return m.Sender.AnonymousInfo.AnonymousId 468 } 469 return "" 470 }(), 471 Content: ToMessageContent(m.Elements, source), 472 } 473 if replyElem != nil { 474 reply := replyElem.(*message.ReplyElement) 475 msg.SubType = "quote" 476 msg.QuotedInfo = &db.QuotedInfo{ 477 PrevID: encodeMessageID(m.GroupCode, reply.ReplySeq), 478 PrevGlobalID: db.ToGlobalID(m.GroupCode, reply.ReplySeq), 479 QuotedContent: ToMessageContent(reply.Elements, source), 480 } 481 } 482 if err := db.InsertGroupMessage(msg); err != nil { 483 log.Warnf("记录聊天数据时出现错误: %v", err) 484 return -1 485 } 486 return msg.GlobalID 487 } 488 489 // InsertPrivateMessage 私聊消息入数据库 490 func (bot *CQBot) InsertPrivateMessage(m *message.PrivateMessage, source message.Source) int32 { 491 t := &message.SendingMessage{Elements: m.Elements} 492 replyElem := t.FirstOrNil(func(e message.IMessageElement) bool { 493 _, ok := e.(*message.ReplyElement) 494 return ok 495 }) 496 msg := &db.StoredPrivateMessage{ 497 ID: encodeMessageID(m.Sender.Uin, m.Id), 498 GlobalID: db.ToGlobalID(m.Sender.Uin, m.Id), 499 SubType: "normal", 500 Attribute: &db.StoredMessageAttribute{ 501 MessageSeq: m.Id, 502 InternalID: m.InternalId, 503 SenderUin: m.Sender.Uin, 504 SenderName: m.Sender.DisplayName(), 505 Timestamp: int64(m.Time), 506 }, 507 SessionUin: func() int64 { 508 if m.Sender.Uin == m.Self { 509 return m.Target 510 } 511 return m.Sender.Uin 512 }(), 513 TargetUin: m.Target, 514 Content: ToMessageContent(m.Elements, source), 515 } 516 if replyElem != nil { 517 reply := replyElem.(*message.ReplyElement) 518 msg.SubType = "quote" 519 msg.QuotedInfo = &db.QuotedInfo{ 520 PrevID: encodeMessageID(reply.Sender, reply.ReplySeq), 521 PrevGlobalID: db.ToGlobalID(reply.Sender, reply.ReplySeq), 522 QuotedContent: ToMessageContent(reply.Elements, source), 523 } 524 } 525 if err := db.InsertPrivateMessage(msg); err != nil { 526 log.Warnf("记录聊天数据时出现错误: %v", err) 527 return -1 528 } 529 return msg.GlobalID 530 } 531 532 /* 533 // InsertTempMessage 临时消息入数据库 534 func (bot *CQBot) InsertTempMessage(target int64, m *message.TempMessage) int32 { 535 val := global.MSG{ 536 "message-id": m.Id, 537 // FIXME(InsertTempMessage) InternalId missing 538 "from-group": m.GroupCode, 539 "group-name": m.GroupName, 540 "target": target, 541 "sender": m.Sender, 542 "time": int32(time.Now().Unix()), 543 "message": ToStringMessage(m.Elements, 0, true), 544 } 545 id := db.ToGlobalID(m.Sender.Uin, m.Id) 546 if bot.db != nil { 547 buf := global.NewBuffer() 548 defer global.PutBuffer(buf) 549 if err := gob.NewEncoder(buf).Encode(val); err != nil { 550 log.Warnf("记录聊天数据时出现错误: %v", err) 551 return -1 552 } 553 if err := bot.db.Put(binary.ToBytes(id), buf.Bytes(), nil); err != nil { 554 log.Warnf("记录聊天数据时出现错误: %v", err) 555 return -1 556 } 557 } 558 return id 559 } 560 */ 561 562 // InsertGuildChannelMessage 频道消息入数据库 563 func (bot *CQBot) InsertGuildChannelMessage(m *message.GuildChannelMessage) string { 564 id := encodeGuildMessageID(m.GuildId, m.ChannelId, m.Id, message.SourceGuildChannel) 565 source := message.Source{ 566 SourceType: message.SourceGuildChannel, 567 PrimaryID: int64(m.Sender.TinyId), 568 } 569 msg := &db.StoredGuildChannelMessage{ 570 ID: id, 571 Attribute: &db.StoredGuildMessageAttribute{ 572 MessageSeq: m.Id, 573 InternalID: m.InternalId, 574 SenderTinyID: m.Sender.TinyId, 575 SenderName: m.Sender.Nickname, 576 Timestamp: m.Time, 577 }, 578 GuildID: m.GuildId, 579 ChannelID: m.ChannelId, 580 Content: ToMessageContent(m.Elements, source), 581 } 582 if err := db.InsertGuildChannelMessage(msg); err != nil { 583 log.Warnf("记录聊天数据时出现错误: %v", err) 584 return "" 585 } 586 return msg.ID 587 } 588 589 func (bot *CQBot) event(typ string, others global.MSG) *event { 590 ev := new(event) 591 post, detail, ok := strings.Cut(typ, "/") 592 ev.PostType = post 593 ev.DetailType = detail 594 if ok { 595 detail, sub, _ := strings.Cut(detail, "/") 596 ev.DetailType = detail 597 ev.SubType = sub 598 } 599 ev.Time = time.Now().Unix() 600 ev.SelfID = bot.Client.Uin 601 ev.Others = others 602 return ev 603 } 604 605 func (bot *CQBot) dispatchEvent(typ string, others global.MSG) { 606 bot.dispatch(bot.event(typ, others)) 607 } 608 609 func (bot *CQBot) dispatch(ev *event) { 610 bot.lock.RLock() 611 defer bot.lock.RUnlock() 612 613 event := &Event{Raw: ev} 614 wg := sync.WaitGroup{} 615 wg.Add(len(bot.events)) 616 for _, f := range bot.events { 617 go func(fn func(*Event)) { 618 defer func() { 619 if pan := recover(); pan != nil { 620 log.Warnf("处理事件 %v 时出现错误: %v \n%s", event.JSONString(), pan, debug.Stack()) 621 } 622 wg.Done() 623 }() 624 625 start := time.Now() 626 fn(event) 627 end := time.Now() 628 if end.Sub(start) > time.Second*5 { 629 log.Debugf("警告: 事件处理耗时超过 5 秒 (%v), 请检查应用是否有堵塞.", end.Sub(start)) 630 } 631 }(f) 632 } 633 wg.Wait() 634 if event.buffer != nil { 635 global.PutBuffer(event.buffer) 636 } 637 } 638 639 func formatGroupName(group *client.GroupInfo) string { 640 return fmt.Sprintf("%s(%d)", group.Name, group.Code) 641 } 642 643 func formatMemberName(mem *client.GroupMemberInfo) string { 644 if mem == nil { 645 return "未知" 646 } 647 return fmt.Sprintf("%s(%d)", mem.DisplayName(), mem.Uin) 648 } 649 650 // encodeMessageID 临时先这样, 暂时用不上 651 func encodeMessageID(target int64, seq int32) string { 652 return hex.EncodeToString(binary.NewWriterF(func(w *binary.Writer) { 653 w.WriteUInt64(uint64(target)) 654 w.WriteUInt32(uint32(seq)) 655 })) 656 } 657 658 // encodeGuildMessageID 将频道信息编码为字符串 659 // 当信息来源为 Channel 时 primaryID 为 guildID , subID 为 channelID 660 // 当信息来源为 Direct 时 primaryID 为 guildID , subID 为 tinyID 661 func encodeGuildMessageID(primaryID, subID, seq uint64, source message.SourceType) string { 662 return base64.StdEncoding.EncodeToString(binary.NewWriterF(func(w *binary.Writer) { 663 w.WriteByte(byte(source)) 664 w.WriteUInt64(primaryID) 665 w.WriteUInt64(subID) 666 w.WriteUInt64(seq) 667 })) 668 } 669 670 func decodeGuildMessageID(id string) (source message.Source, seq uint64) { 671 b, _ := base64.StdEncoding.DecodeString(id) 672 if len(b) < 25 { 673 return 674 } 675 r := binary.NewReader(b) 676 source = message.Source{ 677 SourceType: message.SourceType(r.ReadByte()), 678 PrimaryID: r.ReadInt64(), 679 SecondaryID: r.ReadInt64(), 680 } 681 seq = uint64(r.ReadInt64()) 682 return 683 }