github.com/Mrs4s/go-cqhttp@v1.2.0/coolq/event.go (about)

     1  package coolq
     2  
     3  import (
     4  	"encoding/hex"
     5  	"encoding/json"
     6  	"fmt"
     7  	"path"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/Mrs4s/MiraiGo/binary"
    12  	"github.com/Mrs4s/MiraiGo/client"
    13  	"github.com/Mrs4s/MiraiGo/message"
    14  	log "github.com/sirupsen/logrus"
    15  
    16  	"github.com/Mrs4s/go-cqhttp/db"
    17  	"github.com/Mrs4s/go-cqhttp/global"
    18  	"github.com/Mrs4s/go-cqhttp/internal/base"
    19  	"github.com/Mrs4s/go-cqhttp/internal/cache"
    20  	"github.com/Mrs4s/go-cqhttp/internal/download"
    21  )
    22  
    23  // ToFormattedMessage 将给定[]message.IMessageElement转换为通过coolq.SetMessageFormat所定义的消息上报格式
    24  func ToFormattedMessage(e []message.IMessageElement, source message.Source) (r any) {
    25  	if base.PostFormat == "string" {
    26  		r = toStringMessage(e, source)
    27  	} else if base.PostFormat == "array" {
    28  		r = toElements(e, source)
    29  	}
    30  	return
    31  }
    32  
    33  type event struct {
    34  	PostType   string
    35  	DetailType string
    36  	SubType    string
    37  	Time       int64
    38  	SelfID     int64
    39  	Others     global.MSG
    40  }
    41  
    42  func (ev *event) MarshalJSON() ([]byte, error) {
    43  	buf := global.NewBuffer()
    44  	defer global.PutBuffer(buf)
    45  
    46  	detail := ""
    47  	switch ev.PostType {
    48  	case "message", "message_sent":
    49  		detail = "message_type"
    50  	case "notice":
    51  		detail = "notice_type"
    52  	case "request":
    53  		detail = "request_type"
    54  	case "meta_event":
    55  		detail = "meta_event_type"
    56  	default:
    57  		panic("unknown post type: " + ev.PostType)
    58  	}
    59  	fmt.Fprintf(buf, `{"post_type":"%s","%s":"%s","time":%d,"self_id":%d`, ev.PostType, detail, ev.DetailType, ev.Time, ev.SelfID)
    60  	if ev.SubType != "" {
    61  		fmt.Fprintf(buf, `,"sub_type":"%s"`, ev.SubType)
    62  	}
    63  	for k, v := range ev.Others {
    64  		v, err := json.Marshal(v)
    65  		if err != nil {
    66  			log.Warnf("marshal message payload error: %v", err)
    67  			return nil, err
    68  		}
    69  		fmt.Fprintf(buf, `,"%s":%s`, k, v)
    70  	}
    71  	buf.WriteByte('}')
    72  	return append([]byte(nil), buf.Bytes()...), nil
    73  }
    74  
    75  func (bot *CQBot) privateMessageEvent(_ *client.QQClient, m *message.PrivateMessage) {
    76  	bot.checkMedia(m.Elements, m.Sender.Uin)
    77  	source := message.Source{
    78  		SourceType: message.SourcePrivate,
    79  		PrimaryID:  m.Sender.Uin,
    80  	}
    81  	cqm := toStringMessage(m.Elements, source)
    82  	id := bot.InsertPrivateMessage(m, source)
    83  	log.Infof("收到好友 %v(%v) 的消息: %v (%v)", m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
    84  	typ := "message/private/friend"
    85  	if m.Sender.Uin == bot.Client.Uin {
    86  		typ = "message_sent/private/friend"
    87  	}
    88  	fm := global.MSG{
    89  		"message_id":  id,
    90  		"user_id":     m.Sender.Uin,
    91  		"target_id":   m.Target,
    92  		"message":     ToFormattedMessage(m.Elements, source),
    93  		"raw_message": cqm,
    94  		"font":        0,
    95  		"sender": global.MSG{
    96  			"user_id":  m.Sender.Uin,
    97  			"nickname": m.Sender.Nickname,
    98  			"sex":      "unknown",
    99  			"age":      0,
   100  		},
   101  	}
   102  	bot.dispatchEvent(typ, fm)
   103  }
   104  
   105  func (bot *CQBot) groupMessageEvent(c *client.QQClient, m *message.GroupMessage) {
   106  	bot.checkMedia(m.Elements, m.GroupCode)
   107  	for _, elem := range m.Elements {
   108  		if file, ok := elem.(*message.GroupFileElement); ok {
   109  			log.Infof("群 %v(%v) 内 %v(%v) 上传了文件: %v", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, file.Name)
   110  			bot.dispatchEvent("notice/group_upload", global.MSG{
   111  				"group_id": m.GroupCode,
   112  				"user_id":  m.Sender.Uin,
   113  				"file": global.MSG{
   114  					"id":    file.Path,
   115  					"name":  file.Name,
   116  					"size":  file.Size,
   117  					"busid": file.Busid,
   118  					"url":   c.GetGroupFileUrl(m.GroupCode, file.Path, file.Busid),
   119  				},
   120  			})
   121  			// return
   122  		}
   123  	}
   124  	source := message.Source{
   125  		SourceType: message.SourceGroup,
   126  		PrimaryID:  m.GroupCode,
   127  	}
   128  	cqm := toStringMessage(m.Elements, source)
   129  	id := bot.InsertGroupMessage(m, source)
   130  	log.Infof("收到群 %v(%v) 内 %v(%v) 的消息: %v (%v)", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm, id)
   131  	gm := bot.formatGroupMessage(m)
   132  	if gm == nil {
   133  		return
   134  	}
   135  	gm.Others["message_id"] = id
   136  	bot.dispatch(gm)
   137  }
   138  
   139  func (bot *CQBot) tempMessageEvent(_ *client.QQClient, e *client.TempMessageEvent) {
   140  	m := e.Message
   141  	bot.checkMedia(m.Elements, m.Sender.Uin)
   142  	source := message.Source{
   143  		SourceType: message.SourcePrivate,
   144  		PrimaryID:  e.Session.Sender,
   145  	}
   146  	cqm := toStringMessage(m.Elements, source)
   147  	if base.AllowTempSession {
   148  		bot.tempSessionCache.Store(m.Sender.Uin, e.Session)
   149  	}
   150  
   151  	id := m.Id
   152  	// todo(Mrs4s)
   153  	// if bot.db != nil { // nolint
   154  	// 		id = bot.InsertTempMessage(m.Sender.Uin, m)
   155  	// }
   156  	log.Infof("收到来自群 %v(%v) 内 %v(%v) 的临时会话消息: %v", m.GroupName, m.GroupCode, m.Sender.DisplayName(), m.Sender.Uin, cqm)
   157  	tm := global.MSG{
   158  		"temp_source": e.Session.Source,
   159  		"message_id":  id,
   160  		"user_id":     m.Sender.Uin,
   161  		"message":     ToFormattedMessage(m.Elements, source),
   162  		"raw_message": cqm,
   163  		"font":        0,
   164  		"sender": global.MSG{
   165  			"user_id":  m.Sender.Uin,
   166  			"group_id": m.GroupCode,
   167  			"nickname": m.Sender.Nickname,
   168  			"sex":      "unknown",
   169  			"age":      0,
   170  		},
   171  	}
   172  	bot.dispatchEvent("message/private/group", tm)
   173  }
   174  
   175  func (bot *CQBot) guildChannelMessageEvent(c *client.QQClient, m *message.GuildChannelMessage) {
   176  	bot.checkMedia(m.Elements, int64(m.Sender.TinyId))
   177  	guild := c.GuildService.FindGuild(m.GuildId)
   178  	if guild == nil {
   179  		return
   180  	}
   181  	channel := guild.FindChannel(m.ChannelId)
   182  	source := message.Source{
   183  		SourceType:  message.SourceGuildChannel,
   184  		PrimaryID:   int64(m.GuildId),
   185  		SecondaryID: int64(m.ChannelId),
   186  	}
   187  	log.Infof("收到来自频道 %v(%v) 子频道 %v(%v) 内 %v(%v) 的消息: %v", guild.GuildName, guild.GuildId, channel.ChannelName, m.ChannelId, m.Sender.Nickname, m.Sender.TinyId, toStringMessage(m.Elements, source))
   188  	id := bot.InsertGuildChannelMessage(m)
   189  	ev := bot.event("message/guild/channel", global.MSG{
   190  		"guild_id":     fU64(m.GuildId),
   191  		"channel_id":   fU64(m.ChannelId),
   192  		"message_id":   id,
   193  		"user_id":      fU64(m.Sender.TinyId),
   194  		"message":      ToFormattedMessage(m.Elements, source), // todo: 增加对频道消息 Reply 的支持
   195  		"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
   196  		"sender": global.MSG{
   197  			"user_id":  m.Sender.TinyId,
   198  			"tiny_id":  fU64(m.Sender.TinyId),
   199  			"nickname": m.Sender.Nickname,
   200  		},
   201  	})
   202  	ev.Time = m.Time
   203  	bot.dispatch(ev)
   204  }
   205  
   206  func (bot *CQBot) guildMessageReactionsUpdatedEvent(c *client.QQClient, e *client.GuildMessageReactionsUpdatedEvent) {
   207  	guild := c.GuildService.FindGuild(e.GuildId)
   208  	if guild == nil {
   209  		return
   210  	}
   211  	msgID := encodeGuildMessageID(e.GuildId, e.ChannelId, e.MessageId, message.SourceGuildChannel)
   212  	str := fmt.Sprintf("频道 %v(%v) 消息 %v 表情贴片已更新: ", guild.GuildName, guild.GuildId, msgID)
   213  	currentReactions := make([]global.MSG, len(e.CurrentReactions))
   214  	for i, r := range e.CurrentReactions {
   215  		str += fmt.Sprintf("%v*%v ", r.Face.Name, r.Count)
   216  		currentReactions[i] = global.MSG{
   217  			"emoji_id":    r.EmojiId,
   218  			"emoji_index": r.Face.Index,
   219  			"emoji_type":  r.EmojiType,
   220  			"emoji_name":  r.Face.Name,
   221  			"count":       r.Count,
   222  			"clicked":     r.Clicked,
   223  		}
   224  	}
   225  	if len(e.CurrentReactions) == 0 {
   226  		str += "无任何表情"
   227  	}
   228  	log.Infof(str)
   229  	bot.dispatchEvent("notice/message_reactions_updated", global.MSG{
   230  		"guild_id":          fU64(e.GuildId),
   231  		"channel_id":        fU64(e.ChannelId),
   232  		"message_id":        msgID,
   233  		"operator_id":       fU64(e.OperatorId),
   234  		"current_reactions": currentReactions,
   235  		"self_tiny_id":      fU64(bot.Client.GuildService.TinyId),
   236  		"user_id":           e.OperatorId,
   237  	})
   238  }
   239  
   240  func (bot *CQBot) guildChannelMessageRecalledEvent(c *client.QQClient, e *client.GuildMessageRecalledEvent) {
   241  	guild := c.GuildService.FindGuild(e.GuildId)
   242  	if guild == nil {
   243  		return
   244  	}
   245  	channel := guild.FindChannel(e.ChannelId)
   246  	if channel == nil {
   247  		return
   248  	}
   249  	operator, err := c.GuildService.FetchGuildMemberProfileInfo(e.GuildId, e.OperatorId)
   250  	if err != nil {
   251  		log.Errorf("处理频道撤回事件时出现错误: 获取操作者资料时出现错误 %v", err)
   252  		return
   253  	}
   254  	msgID := encodeGuildMessageID(e.GuildId, e.ChannelId, e.MessageId, message.SourceGuildChannel)
   255  	log.Infof("用户 %v(%v) 撤回了频道 %v(%v) 子频道 %v(%v) 的消息 %v", operator.Nickname, operator.TinyId, guild.GuildName, guild.GuildId, channel.ChannelName, channel.ChannelId, msgID)
   256  	bot.dispatchEvent("notice/guild_channel_recall", global.MSG{
   257  		"guild_id":     fU64(e.GuildId),
   258  		"channel_id":   fU64(e.ChannelId),
   259  		"operator_id":  fU64(e.OperatorId),
   260  		"message_id":   msgID,
   261  		"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
   262  		"user_id":      e.OperatorId,
   263  	})
   264  }
   265  
   266  func (bot *CQBot) guildChannelUpdatedEvent(c *client.QQClient, e *client.GuildChannelUpdatedEvent) {
   267  	guild := c.GuildService.FindGuild(e.GuildId)
   268  	if guild == nil {
   269  		return
   270  	}
   271  	log.Infof("频道 %v(%v) 子频道 %v(%v) 信息已更新", guild.GuildName, guild.GuildId, e.NewChannelInfo.ChannelName, e.NewChannelInfo.ChannelId)
   272  	bot.dispatchEvent("notice/channel_updated", global.MSG{
   273  		"guild_id":     fU64(e.GuildId),
   274  		"channel_id":   fU64(e.ChannelId),
   275  		"operator_id":  fU64(e.OperatorId),
   276  		"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
   277  		"user_id":      e.OperatorId,
   278  		"old_info":     convertChannelInfo(e.OldChannelInfo),
   279  		"new_info":     convertChannelInfo(e.NewChannelInfo),
   280  	})
   281  }
   282  
   283  func (bot *CQBot) guildChannelCreatedEvent(c *client.QQClient, e *client.GuildChannelOperationEvent) {
   284  	guild := c.GuildService.FindGuild(e.GuildId)
   285  	if guild == nil {
   286  		return
   287  	}
   288  	member, _ := c.GuildService.FetchGuildMemberProfileInfo(e.GuildId, e.OperatorId)
   289  	if member == nil {
   290  		member = &client.GuildUserProfile{Nickname: "未知"}
   291  	}
   292  	log.Infof("频道 %v(%v) 内用户 %v(%v) 创建了子频道 %v(%v)", guild.GuildName, guild.GuildId, member.Nickname, member.TinyId, e.ChannelInfo.ChannelName, e.ChannelInfo.ChannelId)
   293  	bot.dispatchEvent("notice/channel_created", global.MSG{
   294  		"guild_id":     fU64(e.GuildId),
   295  		"channel_id":   fU64(e.ChannelInfo.ChannelId),
   296  		"operator_id":  fU64(e.OperatorId),
   297  		"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
   298  		"user_id":      e.OperatorId,
   299  		"channel_info": convertChannelInfo(e.ChannelInfo),
   300  	})
   301  }
   302  
   303  func (bot *CQBot) guildChannelDestroyedEvent(c *client.QQClient, e *client.GuildChannelOperationEvent) {
   304  	guild := c.GuildService.FindGuild(e.GuildId)
   305  	if guild == nil {
   306  		return
   307  	}
   308  	member, _ := c.GuildService.FetchGuildMemberProfileInfo(e.GuildId, e.OperatorId)
   309  	if member == nil {
   310  		member = &client.GuildUserProfile{Nickname: "未知"}
   311  	}
   312  	log.Infof("频道 %v(%v) 内用户 %v(%v) 删除了子频道 %v(%v)", guild.GuildName, guild.GuildId, member.Nickname, member.TinyId, e.ChannelInfo.ChannelName, e.ChannelInfo.ChannelId)
   313  	bot.dispatchEvent("notice/channel_destroyed", global.MSG{
   314  		"guild_id":     fU64(e.GuildId),
   315  		"channel_id":   fU64(e.ChannelInfo.ChannelId),
   316  		"operator_id":  fU64(e.OperatorId),
   317  		"self_tiny_id": fU64(bot.Client.GuildService.TinyId),
   318  		"user_id":      e.OperatorId,
   319  		"channel_info": convertChannelInfo(e.ChannelInfo),
   320  	})
   321  }
   322  
   323  func (bot *CQBot) groupMutedEvent(c *client.QQClient, e *client.GroupMuteEvent) {
   324  	g := c.FindGroup(e.GroupCode)
   325  	if e.TargetUin == 0 {
   326  		if e.Time != 0 {
   327  			log.Infof("群 %v 被 %v 开启全员禁言.",
   328  				formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin)))
   329  		} else {
   330  			log.Infof("群 %v 被 %v 解除全员禁言.",
   331  				formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin)))
   332  		}
   333  	} else {
   334  		if e.Time > 0 {
   335  			log.Infof("群 %v 内 %v 被 %v 禁言了 %v 秒.",
   336  				formatGroupName(g), formatMemberName(g.FindMember(e.TargetUin)), formatMemberName(g.FindMember(e.OperatorUin)), e.Time)
   337  		} else {
   338  			log.Infof("群 %v 内 %v 被 %v 解除禁言.",
   339  				formatGroupName(g), formatMemberName(g.FindMember(e.TargetUin)), formatMemberName(g.FindMember(e.OperatorUin)))
   340  		}
   341  	}
   342  	typ := "notice/group_ban/ban"
   343  	if e.Time == 0 {
   344  		typ = "notice/group_ban/lift_ban"
   345  	}
   346  	bot.dispatchEvent(typ, global.MSG{
   347  		"duration":    e.Time,
   348  		"group_id":    e.GroupCode,
   349  		"operator_id": e.OperatorUin,
   350  		"user_id":     e.TargetUin,
   351  	})
   352  }
   353  
   354  func (bot *CQBot) groupRecallEvent(c *client.QQClient, e *client.GroupMessageRecalledEvent) {
   355  	g := c.FindGroup(e.GroupCode)
   356  	gid := db.ToGlobalID(e.GroupCode, e.MessageId)
   357  	log.Infof("群 %v 内 %v 撤回了 %v 的消息: %v.",
   358  		formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin)), formatMemberName(g.FindMember(e.AuthorUin)), gid)
   359  
   360  	ev := bot.event("notice/group_recall", global.MSG{
   361  		"group_id":    e.GroupCode,
   362  		"user_id":     e.AuthorUin,
   363  		"operator_id": e.OperatorUin,
   364  		"message_id":  gid,
   365  	})
   366  	ev.Time = int64(e.Time)
   367  	bot.dispatch(ev)
   368  }
   369  
   370  func (bot *CQBot) groupNotifyEvent(c *client.QQClient, e client.INotifyEvent) {
   371  	group := c.FindGroup(e.From())
   372  	switch notify := e.(type) {
   373  	case *client.GroupPokeNotifyEvent:
   374  		sender := group.FindMember(notify.Sender)
   375  		receiver := group.FindMember(notify.Receiver)
   376  		log.Infof("群 %v 内 %v 戳了戳 %v", formatGroupName(group), formatMemberName(sender), formatMemberName(receiver))
   377  		bot.dispatchEvent("notice/notify/poke", global.MSG{
   378  			"group_id":  group.Code,
   379  			"user_id":   notify.Sender,
   380  			"sender_id": notify.Sender,
   381  			"target_id": notify.Receiver,
   382  		})
   383  	case *client.GroupRedBagLuckyKingNotifyEvent:
   384  		sender := group.FindMember(notify.Sender)
   385  		luckyKing := group.FindMember(notify.LuckyKing)
   386  		log.Infof("群 %v 内 %v 的红包被抢完, %v 是运气王", formatGroupName(group), formatMemberName(sender), formatMemberName(luckyKing))
   387  		bot.dispatchEvent("notice/notify/lucky_king", global.MSG{
   388  			"group_id":  group.Code,
   389  			"user_id":   notify.Sender,
   390  			"sender_id": notify.Sender,
   391  			"target_id": notify.LuckyKing,
   392  		})
   393  	case *client.MemberHonorChangedNotifyEvent:
   394  		log.Info(notify.Content())
   395  		bot.dispatchEvent("notice/notify/honor", global.MSG{
   396  			"group_id": group.Code,
   397  			"user_id":  notify.Uin,
   398  			"honor_type": func() string {
   399  				switch notify.Honor {
   400  				case client.Talkative:
   401  					return "talkative"
   402  				case client.Performer:
   403  					return "performer"
   404  				case client.Emotion:
   405  					return "emotion"
   406  				case client.Legend:
   407  					return "legend"
   408  				case client.StrongNewbie:
   409  					return "strong_newbie"
   410  				default:
   411  					return "ERROR"
   412  				}
   413  			}(),
   414  		})
   415  	}
   416  }
   417  
   418  func (bot *CQBot) friendNotifyEvent(c *client.QQClient, e client.INotifyEvent) {
   419  	friend := c.FindFriend(e.From())
   420  	if notify, ok := e.(*client.FriendPokeNotifyEvent); ok {
   421  		if notify.Receiver == notify.Sender {
   422  			log.Infof("好友 %v 戳了戳自己.", friend.Nickname)
   423  		} else {
   424  			log.Infof("好友 %v 戳了戳你.", friend.Nickname)
   425  		}
   426  		bot.dispatchEvent("notice/notify/poke", global.MSG{
   427  			"user_id":   notify.Sender,
   428  			"sender_id": notify.Sender,
   429  			"target_id": notify.Receiver,
   430  		})
   431  	}
   432  }
   433  
   434  func (bot *CQBot) memberTitleUpdatedEvent(c *client.QQClient, e *client.MemberSpecialTitleUpdatedEvent) {
   435  	group := c.FindGroup(e.GroupCode)
   436  	mem := group.FindMember(e.Uin)
   437  	log.Infof("群 %v(%v) 内成员 %v(%v) 获得了新的头衔: %v", group.Name, group.Code, mem.DisplayName(), mem.Uin, e.NewTitle)
   438  	bot.dispatchEvent("notice/notify/title", global.MSG{
   439  		"group_id": group.Code,
   440  		"user_id":  e.Uin,
   441  		"title":    e.NewTitle,
   442  	})
   443  }
   444  
   445  func (bot *CQBot) friendRecallEvent(c *client.QQClient, e *client.FriendMessageRecalledEvent) {
   446  	f := c.FindFriend(e.FriendUin)
   447  	gid := db.ToGlobalID(e.FriendUin, e.MessageId)
   448  	if f != nil {
   449  		log.Infof("好友 %v(%v) 撤回了消息: %v", f.Nickname, f.Uin, gid)
   450  	} else {
   451  		log.Infof("好友 %v 撤回了消息: %v", e.FriendUin, gid)
   452  	}
   453  	ev := bot.event("notice/friend_recall", global.MSG{
   454  		"user_id":    e.FriendUin,
   455  		"message_id": gid,
   456  	})
   457  	ev.Time = e.Time
   458  	bot.dispatch(ev)
   459  }
   460  
   461  func (bot *CQBot) offlineFileEvent(c *client.QQClient, e *client.OfflineFileEvent) {
   462  	f := c.FindFriend(e.Sender)
   463  	if f == nil {
   464  		return
   465  	}
   466  	log.Infof("好友 %v(%v) 发送了离线文件 %v", f.Nickname, f.Uin, e.FileName)
   467  	bot.dispatchEvent("notice/offline_file", global.MSG{
   468  		"user_id": e.Sender,
   469  		"file": global.MSG{
   470  			"name": e.FileName,
   471  			"size": e.FileSize,
   472  			"url":  e.DownloadUrl,
   473  		},
   474  	})
   475  }
   476  
   477  func (bot *CQBot) joinGroupEvent(c *client.QQClient, group *client.GroupInfo) {
   478  	if group == nil {
   479  		return
   480  	}
   481  	log.Infof("Bot进入了群 %v.", formatGroupName(group))
   482  	bot.dispatch(bot.groupIncrease(group.Code, 0, c.Uin))
   483  }
   484  
   485  func (bot *CQBot) leaveGroupEvent(c *client.QQClient, e *client.GroupLeaveEvent) {
   486  	if e.Operator != nil {
   487  		log.Infof("Bot被 %v T出了群 %v.", formatMemberName(e.Operator), formatGroupName(e.Group))
   488  	} else {
   489  		log.Infof("Bot退出了群 %v.", formatGroupName(e.Group))
   490  	}
   491  	bot.dispatch(bot.groupDecrease(e.Group.Code, c.Uin, e.Operator))
   492  }
   493  
   494  func (bot *CQBot) memberPermissionChangedEvent(_ *client.QQClient, e *client.MemberPermissionChangedEvent) {
   495  	st := "unset"
   496  	if e.NewPermission == client.Administrator {
   497  		st = "set"
   498  	}
   499  	bot.dispatchEvent("notice/group_admin/"+st, global.MSG{
   500  		"group_id": e.Group.Code,
   501  		"user_id":  e.Member.Uin,
   502  	})
   503  }
   504  
   505  func (bot *CQBot) memberCardUpdatedEvent(_ *client.QQClient, e *client.MemberCardUpdatedEvent) {
   506  	log.Infof("群 %v 的 %v 更新了名片 %v -> %v", formatGroupName(e.Group), formatMemberName(e.Member), e.OldCard, e.Member.CardName)
   507  	bot.dispatchEvent("notice/group_card", global.MSG{
   508  		"group_id": e.Group.Code,
   509  		"user_id":  e.Member.Uin,
   510  		"card_new": e.Member.CardName,
   511  		"card_old": e.OldCard,
   512  	})
   513  }
   514  
   515  func (bot *CQBot) memberJoinEvent(_ *client.QQClient, e *client.MemberJoinGroupEvent) {
   516  	log.Infof("新成员 %v 进入了群 %v.", formatMemberName(e.Member), formatGroupName(e.Group))
   517  	bot.dispatch(bot.groupIncrease(e.Group.Code, 0, e.Member.Uin))
   518  }
   519  
   520  func (bot *CQBot) memberLeaveEvent(_ *client.QQClient, e *client.MemberLeaveGroupEvent) {
   521  	if e.Operator != nil {
   522  		log.Infof("成员 %v 被 %v T出了群 %v.", formatMemberName(e.Member), formatMemberName(e.Operator), formatGroupName(e.Group))
   523  	} else {
   524  		log.Infof("成员 %v 离开了群 %v.", formatMemberName(e.Member), formatGroupName(e.Group))
   525  	}
   526  	bot.dispatch(bot.groupDecrease(e.Group.Code, e.Member.Uin, e.Operator))
   527  }
   528  
   529  func (bot *CQBot) friendRequestEvent(_ *client.QQClient, e *client.NewFriendRequest) {
   530  	log.Infof("收到来自 %v(%v) 的好友请求: %v", e.RequesterNick, e.RequesterUin, e.Message)
   531  	flag := strconv.FormatInt(e.RequestId, 10)
   532  	bot.friendReqCache.Store(flag, e)
   533  	bot.dispatchEvent("request/friend", global.MSG{
   534  		"user_id": e.RequesterUin,
   535  		"comment": e.Message,
   536  		"flag":    flag,
   537  	})
   538  }
   539  
   540  func (bot *CQBot) friendAddedEvent(_ *client.QQClient, e *client.NewFriendEvent) {
   541  	log.Infof("添加了新好友: %v(%v)", e.Friend.Nickname, e.Friend.Uin)
   542  	bot.tempSessionCache.Delete(e.Friend.Uin)
   543  	bot.dispatchEvent("notice/friend_add", global.MSG{
   544  		"user_id": e.Friend.Uin,
   545  	})
   546  }
   547  
   548  func (bot *CQBot) groupInvitedEvent(_ *client.QQClient, e *client.GroupInvitedRequest) {
   549  	log.Infof("收到来自群 %v(%v) 内用户 %v(%v) 的加群邀请.", e.GroupName, e.GroupCode, e.InvitorNick, e.InvitorUin)
   550  	flag := strconv.FormatInt(e.RequestId, 10)
   551  	bot.dispatchEvent("request/group/invite", global.MSG{
   552  		"group_id":   e.GroupCode,
   553  		"user_id":    e.InvitorUin,
   554  		"invitor_id": 0,
   555  		"comment":    "",
   556  		"flag":       flag,
   557  	})
   558  }
   559  
   560  func (bot *CQBot) groupJoinReqEvent(_ *client.QQClient, e *client.UserJoinGroupRequest) {
   561  	log.Infof("群 %v(%v) 收到来自用户 %v(%v) 的加群请求.", e.GroupName, e.GroupCode, e.RequesterNick, e.RequesterUin)
   562  	flag := strconv.FormatInt(e.RequestId, 10)
   563  	bot.dispatchEvent("request/group/add", global.MSG{
   564  		"group_id":   e.GroupCode,
   565  		"user_id":    e.RequesterUin,
   566  		"invitor_id": e.ActionUin,
   567  		"comment":    e.Message,
   568  		"flag":       flag,
   569  	})
   570  }
   571  
   572  func (bot *CQBot) otherClientStatusChangedEvent(_ *client.QQClient, e *client.OtherClientStatusChangedEvent) {
   573  	if e.Online {
   574  		log.Infof("Bot 账号在客户端 %v (%v) 登录.", e.Client.DeviceName, e.Client.DeviceKind)
   575  	} else {
   576  		log.Infof("Bot 账号在客户端 %v (%v) 登出.", e.Client.DeviceName, e.Client.DeviceKind)
   577  	}
   578  	bot.dispatchEvent("notice/client_status", global.MSG{
   579  		"online": e.Online,
   580  		"client": global.MSG{
   581  			"app_id":      e.Client.AppId,
   582  			"device_name": e.Client.DeviceName,
   583  			"device_kind": e.Client.DeviceKind,
   584  		},
   585  	})
   586  }
   587  
   588  func (bot *CQBot) groupEssenceMsg(c *client.QQClient, e *client.GroupDigestEvent) {
   589  	g := c.FindGroup(e.GroupCode)
   590  	gid := db.ToGlobalID(e.GroupCode, e.MessageID)
   591  	if e.OperationType == 1 {
   592  		log.Infof(
   593  			"群 %v 内 %v 将 %v 的消息(%v)设为了精华消息.",
   594  			formatGroupName(g),
   595  			formatMemberName(g.FindMember(e.OperatorUin)),
   596  			formatMemberName(g.FindMember(e.SenderUin)),
   597  			gid,
   598  		)
   599  	} else {
   600  		log.Infof(
   601  			"群 %v 内 %v 将 %v 的消息(%v)移出了精华消息.",
   602  			formatGroupName(g),
   603  			formatMemberName(g.FindMember(e.OperatorUin)),
   604  			formatMemberName(g.FindMember(e.SenderUin)),
   605  			gid,
   606  		)
   607  	}
   608  	if e.OperatorUin == bot.Client.Uin {
   609  		return
   610  	}
   611  	subtype := "delete"
   612  	if e.OperationType == 1 {
   613  		subtype = "add"
   614  	}
   615  	bot.dispatchEvent("notice/essence/"+subtype, global.MSG{
   616  		"group_id":    e.GroupCode,
   617  		"sender_id":   e.SenderUin,
   618  		"operator_id": e.OperatorUin,
   619  		"message_id":  gid,
   620  	})
   621  }
   622  
   623  func (bot *CQBot) groupIncrease(groupCode, operatorUin, userUin int64) *event {
   624  	return bot.event("notice/group_increase/approve", global.MSG{
   625  		"group_id":    groupCode,
   626  		"operator_id": operatorUin,
   627  		"user_id":     userUin,
   628  	})
   629  }
   630  
   631  func (bot *CQBot) groupDecrease(groupCode, userUin int64, operator *client.GroupMemberInfo) *event {
   632  	op := userUin
   633  	if operator != nil {
   634  		op = operator.Uin
   635  	}
   636  	subtype := "leave"
   637  	if operator != nil {
   638  		if userUin == bot.Client.Uin {
   639  			subtype = "kick_me"
   640  		} else {
   641  			subtype = "kick"
   642  		}
   643  	}
   644  	return bot.event("notice/group_decrease/"+subtype, global.MSG{
   645  		"group_id":    groupCode,
   646  		"operator_id": op,
   647  		"user_id":     userUin,
   648  	})
   649  }
   650  
   651  func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) {
   652  	for _, elem := range e {
   653  		switch i := elem.(type) {
   654  		case *message.GroupImageElement:
   655  			if i.Flash && sourceID != 0 {
   656  				u, err := bot.Client.GetGroupImageDownloadUrl(i.FileId, sourceID, i.Md5)
   657  				if err != nil {
   658  					log.Warnf("获取闪照地址时出现错误: %v", err)
   659  				} else {
   660  					i.Url = u
   661  				}
   662  			}
   663  			data := binary.NewWriterF(func(w *binary.Writer) {
   664  				w.Write(i.Md5)
   665  				w.WriteUInt32(uint32(i.Size))
   666  				w.WriteString(i.ImageId)
   667  				w.WriteString(i.Url)
   668  			})
   669  			cache.Image.Insert(i.Md5, data)
   670  
   671  		case *message.GuildImageElement:
   672  			data := binary.NewWriterF(func(w *binary.Writer) {
   673  				w.Write(i.Md5)
   674  				w.WriteUInt32(uint32(i.Size))
   675  				w.WriteString(i.DownloadIndex)
   676  				w.WriteString(i.Url)
   677  			})
   678  			filename := hex.EncodeToString(i.Md5) + ".image"
   679  			cache.Image.Insert(i.Md5, data)
   680  			if i.Url != "" && !global.PathExists(path.Join(global.ImagePath, "guild-images", filename)) {
   681  				r := download.Request{URL: i.Url}
   682  				if err := r.WriteToFile(path.Join(global.ImagePath, "guild-images", filename)); err != nil {
   683  					log.Warnf("下载频道图片时出现错误: %v", err)
   684  				}
   685  			}
   686  		case *message.FriendImageElement:
   687  			data := binary.NewWriterF(func(w *binary.Writer) {
   688  				w.Write(i.Md5)
   689  				w.WriteUInt32(uint32(i.Size))
   690  				w.WriteString(i.ImageId)
   691  				w.WriteString(i.Url)
   692  			})
   693  			cache.Image.Insert(i.Md5, data)
   694  
   695  		case *message.VoiceElement:
   696  			// todo: don't download original file?
   697  			i.Name = strings.ReplaceAll(i.Name, "{", "")
   698  			i.Name = strings.ReplaceAll(i.Name, "}", "")
   699  			if !global.PathExists(path.Join(global.VoicePath, i.Name)) {
   700  				err := download.Request{URL: i.Url}.WriteToFile(path.Join(global.VoicePath, i.Name))
   701  				if err != nil {
   702  					log.Warnf("语音文件 %v 下载失败: %v", i.Name, err)
   703  					continue
   704  				}
   705  			}
   706  		case *message.ShortVideoElement:
   707  			data := binary.NewWriterF(func(w *binary.Writer) {
   708  				w.Write(i.Md5)
   709  				w.Write(i.ThumbMd5)
   710  				w.WriteUInt32(uint32(i.Size))
   711  				w.WriteUInt32(uint32(i.ThumbSize))
   712  				w.WriteString(i.Name)
   713  				w.Write(i.Uuid)
   714  			})
   715  			filename := hex.EncodeToString(i.Md5) + ".video"
   716  			cache.Video.Insert(i.Md5, data)
   717  			i.Name = filename
   718  			i.Url = bot.Client.GetShortVideoUrl(i.Uuid, i.Md5)
   719  		}
   720  	}
   721  }