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  }