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

     1  package coolq
     2  
     3  import (
     4  	"crypto/md5"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"math"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"runtime"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/Mrs4s/MiraiGo/binary"
    18  	"github.com/Mrs4s/MiraiGo/client"
    19  	"github.com/Mrs4s/MiraiGo/message"
    20  	"github.com/Mrs4s/MiraiGo/utils"
    21  	"github.com/segmentio/asm/base64"
    22  	log "github.com/sirupsen/logrus"
    23  	"github.com/tidwall/gjson"
    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/cache"
    29  	"github.com/Mrs4s/go-cqhttp/internal/download"
    30  	"github.com/Mrs4s/go-cqhttp/internal/msg"
    31  	"github.com/Mrs4s/go-cqhttp/internal/param"
    32  	"github.com/Mrs4s/go-cqhttp/modules/filter"
    33  	"github.com/Mrs4s/go-cqhttp/pkg/onebot"
    34  )
    35  
    36  type guildMemberPageToken struct {
    37  	guildID        uint64
    38  	nextIndex      uint32
    39  	nextRoleID     uint64
    40  	nextQueryParam string
    41  }
    42  
    43  var defaultPageToken = guildMemberPageToken{
    44  	guildID:    0,
    45  	nextIndex:  0,
    46  	nextRoleID: 2,
    47  }
    48  
    49  // CQGetLoginInfo 获取登录号信息
    50  //
    51  // https://git.io/Jtz1I
    52  // @route11(get_login_info)
    53  // @route12(get_self_info)
    54  func (bot *CQBot) CQGetLoginInfo() global.MSG {
    55  	return OK(global.MSG{"user_id": bot.Client.Uin, "nickname": bot.Client.Nickname})
    56  }
    57  
    58  // CQGetQiDianAccountInfo 获取企点账号信息
    59  // @route(qidian_get_account_info)
    60  func (bot *CQBot) CQGetQiDianAccountInfo() global.MSG {
    61  	if bot.Client.QiDian == nil {
    62  		return Failed(100, "QIDIAN_PROTOCOL_REQUEST", "请使用企点协议")
    63  	}
    64  	return OK(global.MSG{
    65  		"master_id":   bot.Client.QiDian.MasterUin,
    66  		"ext_name":    bot.Client.QiDian.ExtName,
    67  		"create_time": bot.Client.QiDian.CreateTime,
    68  	})
    69  }
    70  
    71  // CQGetGuildServiceProfile 获取频道系统个人资料
    72  // @route(get_guild_service_profile)
    73  func (bot *CQBot) CQGetGuildServiceProfile() global.MSG {
    74  	return OK(global.MSG{
    75  		"nickname":   bot.Client.GuildService.Nickname,
    76  		"tiny_id":    fU64(bot.Client.GuildService.TinyId),
    77  		"avatar_url": bot.Client.GuildService.AvatarUrl,
    78  	})
    79  }
    80  
    81  // CQGetGuildList 获取已加入的频道列表
    82  // @route(get_guild_list)
    83  func (bot *CQBot) CQGetGuildList() global.MSG {
    84  	fs := make([]global.MSG, 0, len(bot.Client.GuildService.Guilds))
    85  	for _, info := range bot.Client.GuildService.Guilds {
    86  		/* 做成单独的 api 可能会好些?
    87  		channels := make([]global.MSG, 0, len(info.Channels))
    88  		for _, channel := range info.Channels {
    89  			channels = append(channels, global.MSG{
    90  				"channel_id":   channel.ChannelId,
    91  				"channel_name": channel.ChannelName,
    92  				"channel_type": channel.ChannelType,
    93  			})
    94  		}
    95  		*/
    96  		fs = append(fs, global.MSG{
    97  			"guild_id":         fU64(info.GuildId),
    98  			"guild_name":       info.GuildName,
    99  			"guild_display_id": fU64(info.GuildCode),
   100  			// "channels":         channels,
   101  		})
   102  	}
   103  	return OK(fs)
   104  }
   105  
   106  // CQGetGuildMetaByGuest 通过访客权限获取频道元数据
   107  // @route(get_guild_meta_by_guest)
   108  func (bot *CQBot) CQGetGuildMetaByGuest(guildID uint64) global.MSG {
   109  	meta, err := bot.Client.GuildService.FetchGuestGuild(guildID)
   110  	if err != nil {
   111  		log.Errorf("获取频道元数据时出现错误: %v", err)
   112  		return Failed(100, "API_ERROR", err.Error())
   113  	}
   114  	return OK(global.MSG{
   115  		"guild_id":         fU64(meta.GuildId),
   116  		"guild_name":       meta.GuildName,
   117  		"guild_profile":    meta.GuildProfile,
   118  		"create_time":      meta.CreateTime,
   119  		"max_member_count": meta.MaxMemberCount,
   120  		"max_robot_count":  meta.MaxRobotCount,
   121  		"max_admin_count":  meta.MaxAdminCount,
   122  		"member_count":     meta.MemberCount,
   123  		"owner_id":         fU64(meta.OwnerId),
   124  	})
   125  }
   126  
   127  // CQGetGuildChannelList 获取频道列表
   128  // @route(get_guild_channel_list)
   129  func (bot *CQBot) CQGetGuildChannelList(guildID uint64, noCache bool) global.MSG {
   130  	guild := bot.Client.GuildService.FindGuild(guildID)
   131  	if guild == nil {
   132  		return Failed(100, "GUILD_NOT_FOUND")
   133  	}
   134  	if noCache {
   135  		channels, err := bot.Client.GuildService.FetchChannelList(guildID)
   136  		if err != nil {
   137  			log.Warnf("获取频道 %v 子频道列表时出现错误: %v", guildID, err)
   138  			return Failed(100, "API_ERROR", err.Error())
   139  		}
   140  		guild.Channels = channels
   141  	}
   142  	channels := make([]global.MSG, 0, len(guild.Channels))
   143  	for _, c := range guild.Channels {
   144  		channels = append(channels, convertChannelInfo(c))
   145  	}
   146  	return OK(channels)
   147  }
   148  
   149  // CQGetGuildMembers 获取频道成员列表
   150  // @route(get_guild_member_list)
   151  func (bot *CQBot) CQGetGuildMembers(guildID uint64, nextToken string) global.MSG {
   152  	guild := bot.Client.GuildService.FindGuild(guildID)
   153  	if guild == nil {
   154  		return Failed(100, "GUILD_NOT_FOUND")
   155  	}
   156  	token := &defaultPageToken
   157  	if nextToken != "" {
   158  		i, exists := bot.nextTokenCache.Get(nextToken)
   159  		if !exists {
   160  			return Failed(100, "NEXT_TOKEN_NOT_EXISTS")
   161  		}
   162  		token = i
   163  		if token.guildID != guildID {
   164  			return Failed(100, "GUILD_NOT_MATCH")
   165  		}
   166  	}
   167  	ret, err := bot.Client.GuildService.FetchGuildMemberListWithRole(guildID, 0, token.nextIndex, token.nextRoleID, token.nextQueryParam)
   168  	if err != nil {
   169  		return Failed(100, "API_ERROR", err.Error())
   170  	}
   171  	res := global.MSG{
   172  		"members":    convertGuildMemberInfo(ret.Members),
   173  		"finished":   ret.Finished,
   174  		"next_token": nil,
   175  	}
   176  	if !ret.Finished {
   177  		next := &guildMemberPageToken{
   178  			guildID:        guildID,
   179  			nextIndex:      ret.NextIndex,
   180  			nextRoleID:     ret.NextRoleId,
   181  			nextQueryParam: ret.NextQueryParam,
   182  		}
   183  		id := base64.StdEncoding.EncodeToString(binary.NewWriterF(func(w *binary.Writer) {
   184  			w.WriteUInt64(uint64(time.Now().UnixNano()))
   185  			w.WriteString(utils.RandomString(5))
   186  		}))
   187  		bot.nextTokenCache.Add(id, next, time.Minute*10)
   188  		res["next_token"] = id
   189  	}
   190  	return OK(res)
   191  }
   192  
   193  // CQGetGuildMemberProfile 获取频道成员资料
   194  // @route(get_guild_member_profile)
   195  func (bot *CQBot) CQGetGuildMemberProfile(guildID, userID uint64) global.MSG {
   196  	if bot.Client.GuildService.FindGuild(guildID) == nil {
   197  		return Failed(100, "GUILD_NOT_FOUND")
   198  	}
   199  	profile, err := bot.Client.GuildService.FetchGuildMemberProfileInfo(guildID, userID)
   200  	if err != nil {
   201  		log.Warnf("获取频道 %v 成员 %v 资料时出现错误: %v", guildID, userID, err)
   202  		return Failed(100, "API_ERROR", err.Error())
   203  	}
   204  	roles := make([]global.MSG, 0, len(profile.Roles))
   205  	for _, role := range profile.Roles {
   206  		roles = append(roles, global.MSG{
   207  			"role_id":   fU64(role.RoleId),
   208  			"role_name": role.RoleName,
   209  		})
   210  	}
   211  	return OK(global.MSG{
   212  		"tiny_id":    fU64(profile.TinyId),
   213  		"nickname":   profile.Nickname,
   214  		"avatar_url": profile.AvatarUrl,
   215  		"join_time":  profile.JoinTime,
   216  		"roles":      roles,
   217  	})
   218  }
   219  
   220  // CQGetGuildRoles 获取频道角色列表
   221  // @route(get_guild_roles)
   222  func (bot *CQBot) CQGetGuildRoles(guildID uint64) global.MSG {
   223  	r, err := bot.Client.GuildService.GetGuildRoles(guildID)
   224  	if err != nil {
   225  		log.Warnf("获取频道 %v 角色列表时出现错误: %v", guildID, err)
   226  		return Failed(100, "API_ERROR", err.Error())
   227  	}
   228  	roles := make([]global.MSG, len(r))
   229  	for i, role := range r {
   230  		roles[i] = global.MSG{
   231  			"role_id":      fU64(role.RoleId),
   232  			"role_name":    role.RoleName,
   233  			"argb_color":   role.ArgbColor,
   234  			"independent":  role.Independent,
   235  			"member_count": role.Num,
   236  			"max_count":    role.MaxNum,
   237  			"owned":        role.Owned,
   238  			"disabled":     role.Disabled,
   239  		}
   240  	}
   241  	return OK(roles)
   242  }
   243  
   244  // CQCreateGuildRole 创建频道角色
   245  // @route(create_guild_role)
   246  func (bot *CQBot) CQCreateGuildRole(guildID uint64, name string, color uint32, independent bool, initialUsers gjson.Result) global.MSG {
   247  	userSlice := []uint64{}
   248  	if initialUsers.IsArray() {
   249  		for _, user := range initialUsers.Array() {
   250  			userSlice = append(userSlice, user.Uint())
   251  		}
   252  	}
   253  	role, err := bot.Client.GuildService.CreateGuildRole(guildID, name, color, independent, userSlice)
   254  	if err != nil {
   255  		log.Warnf("创建频道 %v 角色时出现错误: %v", guildID, err)
   256  		return Failed(100, "API_ERROR", err.Error())
   257  	}
   258  	return OK(global.MSG{
   259  		"role_id": fU64(role),
   260  	})
   261  }
   262  
   263  // CQDeleteGuildRole 删除频道角色
   264  // @route(delete_guild_role)
   265  func (bot *CQBot) CQDeleteGuildRole(guildID uint64, roleID uint64) global.MSG {
   266  	err := bot.Client.GuildService.DeleteGuildRole(guildID, roleID)
   267  	if err != nil {
   268  		log.Warnf("删除频道 %v 角色时出现错误: %v", guildID, err)
   269  		return Failed(100, "API_ERROR", err.Error())
   270  	}
   271  	return OK(nil)
   272  }
   273  
   274  // CQSetGuildMemberRole 设置用户在频道中的角色
   275  // @route(set_guild_member_role)
   276  func (bot *CQBot) CQSetGuildMemberRole(guildID uint64, set bool, roleID uint64, users gjson.Result) global.MSG {
   277  	userSlice := []uint64{}
   278  	if users.IsArray() {
   279  		for _, user := range users.Array() {
   280  			userSlice = append(userSlice, user.Uint())
   281  		}
   282  	}
   283  	err := bot.Client.GuildService.SetUserRoleInGuild(guildID, set, roleID, userSlice)
   284  	if err != nil {
   285  		log.Warnf("设置用户在频道 %v 中的角色时出现错误: %v", guildID, err)
   286  		return Failed(100, "API_ERROR", err.Error())
   287  	}
   288  	return OK(nil)
   289  }
   290  
   291  // CQModifyRoleInGuild 修改频道角色
   292  // @route(update_guild_role)
   293  func (bot *CQBot) CQModifyRoleInGuild(guildID uint64, roleID uint64, name string, color uint32, indepedent bool) global.MSG {
   294  	err := bot.Client.GuildService.ModifyRoleInGuild(guildID, roleID, name, color, indepedent)
   295  	if err != nil {
   296  		log.Warnf("修改频道 %v 角色时出现错误: %v", guildID, err)
   297  		return Failed(100, "API_ERROR", err.Error())
   298  	}
   299  	return OK(nil)
   300  }
   301  
   302  // CQGetTopicChannelFeeds 获取话题频道帖子列表
   303  // @route(get_topic_channel_feeds)
   304  func (bot *CQBot) CQGetTopicChannelFeeds(guildID, channelID uint64) global.MSG {
   305  	guild := bot.Client.GuildService.FindGuild(guildID)
   306  	if guild == nil {
   307  		return Failed(100, "GUILD_NOT_FOUND")
   308  	}
   309  	channel := guild.FindChannel(channelID)
   310  	if channel == nil {
   311  		return Failed(100, "CHANNEL_NOT_FOUND")
   312  	}
   313  	if channel.ChannelType != client.ChannelTypeTopic {
   314  		return Failed(100, "CHANNEL_TYPE_ERROR")
   315  	}
   316  	feeds, err := bot.Client.GuildService.GetTopicChannelFeeds(guildID, channelID)
   317  	if err != nil {
   318  		log.Warnf("获取频道 %v 帖子时出现错误: %v", channelID, err)
   319  		return Failed(100, "API_ERROR", err.Error())
   320  	}
   321  	c := make([]global.MSG, 0, len(feeds))
   322  	for _, feed := range feeds {
   323  		c = append(c, convertChannelFeedInfo(feed))
   324  	}
   325  	return OK(c)
   326  }
   327  
   328  // CQGetFriendList 获取好友列表
   329  //
   330  // https://git.io/Jtz1L
   331  // @route(get_friend_list)
   332  func (bot *CQBot) CQGetFriendList(spec *onebot.Spec) global.MSG {
   333  	fs := make([]global.MSG, 0, len(bot.Client.FriendList))
   334  	for _, f := range bot.Client.FriendList {
   335  		fs = append(fs, global.MSG{
   336  			"nickname": f.Nickname,
   337  			"remark":   f.Remark,
   338  			"user_id":  spec.ConvertID(f.Uin),
   339  		})
   340  	}
   341  	return OK(fs)
   342  }
   343  
   344  // CQGetUnidirectionalFriendList 获取单向好友列表
   345  //
   346  // @route(get_unidirectional_friend_list)
   347  func (bot *CQBot) CQGetUnidirectionalFriendList() global.MSG {
   348  	list, err := bot.Client.GetUnidirectionalFriendList()
   349  	if err != nil {
   350  		log.Warnf("获取单向好友列表时出现错误: %v", err)
   351  		return Failed(100, "API_ERROR", err.Error())
   352  	}
   353  	fs := make([]global.MSG, 0, len(list))
   354  	for _, f := range list {
   355  		fs = append(fs, global.MSG{
   356  			"nickname": f.Nickname,
   357  			"user_id":  f.Uin,
   358  			"source":   f.Source,
   359  		})
   360  	}
   361  	return OK(fs)
   362  }
   363  
   364  // CQDeleteUnidirectionalFriend 删除单向好友
   365  //
   366  // @route(delete_unidirectional_friend)
   367  // @rename(uin->user_id)
   368  func (bot *CQBot) CQDeleteUnidirectionalFriend(uin int64) global.MSG {
   369  	list, err := bot.Client.GetUnidirectionalFriendList()
   370  	if err != nil {
   371  		log.Warnf("获取单向好友列表时出现错误: %v", err)
   372  		return Failed(100, "API_ERROR", err.Error())
   373  	}
   374  	for _, f := range list {
   375  		if f.Uin == uin {
   376  			if err = bot.Client.DeleteUnidirectionalFriend(uin); err != nil {
   377  				log.Warnf("删除单向好友时出现错误: %v", err)
   378  				return Failed(100, "API_ERROR", err.Error())
   379  			}
   380  			return OK(nil)
   381  		}
   382  	}
   383  	return Failed(100, "FRIEND_NOT_FOUND", "好友不存在")
   384  }
   385  
   386  // CQDeleteFriend 删除好友
   387  // @route(delete_friend)
   388  // @rename(uin->"[user_id\x2Cid].0")
   389  func (bot *CQBot) CQDeleteFriend(uin int64) global.MSG {
   390  	if bot.Client.FindFriend(uin) == nil {
   391  		return Failed(100, "FRIEND_NOT_FOUND", "好友不存在")
   392  	}
   393  	if err := bot.Client.DeleteFriend(uin); err != nil {
   394  		log.Warnf("删除好友时出现错误: %v", err)
   395  		return Failed(100, "DELETE_API_ERROR", err.Error())
   396  	}
   397  	return OK(nil)
   398  }
   399  
   400  // CQGetGroupList 获取群列表
   401  //
   402  // https://git.io/Jtz1t
   403  // @route(get_group_list)
   404  func (bot *CQBot) CQGetGroupList(noCache bool, spec *onebot.Spec) global.MSG {
   405  	gs := make([]global.MSG, 0, len(bot.Client.GroupList))
   406  	if noCache {
   407  		_ = bot.Client.ReloadGroupList()
   408  	}
   409  	for _, g := range bot.Client.GroupList {
   410  		gs = append(gs, global.MSG{
   411  			"group_id":          spec.ConvertID(g.Code),
   412  			"group_name":        g.Name,
   413  			"group_create_time": g.GroupCreateTime,
   414  			"group_level":       g.GroupLevel,
   415  			"max_member_count":  g.MaxMemberCount,
   416  			"member_count":      g.MemberCount,
   417  		})
   418  	}
   419  	return OK(gs)
   420  }
   421  
   422  // CQGetGroupInfo 获取群信息
   423  //
   424  // https://git.io/Jtz1O
   425  // @route(get_group_info)
   426  func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool, spec *onebot.Spec) global.MSG {
   427  	group := bot.Client.FindGroup(groupID)
   428  	if group == nil || noCache {
   429  		group, _ = bot.Client.GetGroupInfo(groupID)
   430  	}
   431  	if group == nil {
   432  		gid := strconv.FormatInt(groupID, 10)
   433  		info, err := bot.Client.SearchGroupByKeyword(gid)
   434  		if err != nil {
   435  			return Failed(100, "GROUP_SEARCH_ERROR", "群聊搜索失败")
   436  		}
   437  		for _, g := range info {
   438  			if g.Code == groupID {
   439  				return OK(global.MSG{
   440  					"group_id":          spec.ConvertID(g.Code),
   441  					"group_name":        g.Name,
   442  					"group_memo":        g.Memo,
   443  					"group_create_time": 0,
   444  					"group_level":       0,
   445  					"max_member_count":  0,
   446  					"member_count":      0,
   447  				})
   448  			}
   449  		}
   450  	} else {
   451  		return OK(global.MSG{
   452  			"group_id":          spec.ConvertID(group.Code),
   453  			"group_name":        group.Name,
   454  			"group_create_time": group.GroupCreateTime,
   455  			"group_level":       group.GroupLevel,
   456  			"max_member_count":  group.MaxMemberCount,
   457  			"member_count":      group.MemberCount,
   458  		})
   459  	}
   460  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
   461  }
   462  
   463  // CQGetGroupMemberList 获取群成员列表
   464  //
   465  // https://git.io/Jtz13
   466  // @route(get_group_member_list)
   467  func (bot *CQBot) CQGetGroupMemberList(groupID int64, noCache bool) global.MSG {
   468  	group := bot.Client.FindGroup(groupID)
   469  	if group == nil {
   470  		return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
   471  	}
   472  	if noCache {
   473  		t, err := bot.Client.GetGroupMembers(group)
   474  		if err != nil {
   475  			log.Warnf("刷新群 %v 成员列表失败: %v", groupID, err)
   476  			return Failed(100, "GET_MEMBERS_API_ERROR", err.Error())
   477  		}
   478  		group.Members = t
   479  	}
   480  	members := make([]global.MSG, 0, len(group.Members))
   481  	for _, m := range group.Members {
   482  		members = append(members, convertGroupMemberInfo(groupID, m))
   483  	}
   484  	return OK(members)
   485  }
   486  
   487  // CQGetGroupMemberInfo 获取群成员信息
   488  //
   489  // https://git.io/Jtz1s
   490  // @route(get_group_member_info)
   491  func (bot *CQBot) CQGetGroupMemberInfo(groupID, userID int64, noCache bool) global.MSG {
   492  	group := bot.Client.FindGroup(groupID)
   493  	if group == nil {
   494  		return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
   495  	}
   496  	var member *client.GroupMemberInfo
   497  	if noCache {
   498  		var err error
   499  		member, err = bot.Client.GetMemberInfo(groupID, userID)
   500  		if err != nil {
   501  			log.Warnf("刷新群 %v 中成员 %v 失败: %v", groupID, userID, err)
   502  			return Failed(100, "GET_MEMBER_INFO_API_ERROR", err.Error())
   503  		}
   504  	} else {
   505  		member = group.FindMember(userID)
   506  	}
   507  	if member == nil {
   508  		return Failed(100, "MEMBER_NOT_FOUND", "群员不存在")
   509  	}
   510  	return OK(convertGroupMemberInfo(groupID, member))
   511  }
   512  
   513  // CQGetGroupFileSystemInfo 扩展API-获取群文件系统信息
   514  //
   515  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E4%BF%A1%E6%81%AF
   516  // @route(get_group_file_system_info)
   517  func (bot *CQBot) CQGetGroupFileSystemInfo(groupID int64) global.MSG {
   518  	fs, err := bot.Client.GetGroupFileSystem(groupID)
   519  	if err != nil {
   520  		log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err)
   521  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   522  	}
   523  	return OK(fs)
   524  }
   525  
   526  // CQGetGroupRootFiles 扩展API-获取群根目录文件列表
   527  //
   528  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%A0%B9%E7%9B%AE%E5%BD%95%E6%96%87%E4%BB%B6%E5%88%97%E8%A1%A8
   529  // @route(get_group_root_files)
   530  func (bot *CQBot) CQGetGroupRootFiles(groupID int64) global.MSG {
   531  	fs, err := bot.Client.GetGroupFileSystem(groupID)
   532  	if err != nil {
   533  		log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err)
   534  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   535  	}
   536  	files, folders, err := fs.Root()
   537  	if err != nil {
   538  		log.Warnf("获取群 %v 根目录文件失败: %v", groupID, err)
   539  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   540  	}
   541  	return OK(global.MSG{
   542  		"files":   files,
   543  		"folders": folders,
   544  	})
   545  }
   546  
   547  // CQGetGroupFilesByFolderID 扩展API-获取群子目录文件列表
   548  //
   549  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%AD%90%E7%9B%AE%E5%BD%95%E6%96%87%E4%BB%B6%E5%88%97%E8%A1%A8
   550  // @route(get_group_files_by_folder)
   551  func (bot *CQBot) CQGetGroupFilesByFolderID(groupID int64, folderID string) global.MSG {
   552  	fs, err := bot.Client.GetGroupFileSystem(groupID)
   553  	if err != nil {
   554  		log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err)
   555  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   556  	}
   557  	files, folders, err := fs.GetFilesByFolder(folderID)
   558  	if err != nil {
   559  		log.Warnf("获取群 %v 根目录 %v 子文件失败: %v", groupID, folderID, err)
   560  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   561  	}
   562  	return OK(global.MSG{
   563  		"files":   files,
   564  		"folders": folders,
   565  	})
   566  }
   567  
   568  // CQGetGroupFileURL 扩展API-获取群文件资源链接
   569  //
   570  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%96%87%E4%BB%B6%E8%B5%84%E6%BA%90%E9%93%BE%E6%8E%A5
   571  // @route(get_group_file_url)
   572  // @rename(bus_id->"[busid\x2Cbus_id].0")
   573  func (bot *CQBot) CQGetGroupFileURL(groupID int64, fileID string, busID int32) global.MSG {
   574  	url := bot.Client.GetGroupFileUrl(groupID, fileID, busID)
   575  	if url == "" {
   576  		return Failed(100, "FILE_SYSTEM_API_ERROR")
   577  	}
   578  	return OK(global.MSG{
   579  		"url": url,
   580  	})
   581  }
   582  
   583  // CQUploadGroupFile 扩展API-上传群文件
   584  //
   585  // https://docs.go-cqhttp.org/api/#%E4%B8%8A%E4%BC%A0%E7%BE%A4%E6%96%87%E4%BB%B6
   586  // @route(upload_group_file)
   587  func (bot *CQBot) CQUploadGroupFile(groupID int64, file, name, folder string) global.MSG {
   588  	if !global.PathExists(file) {
   589  		log.Warnf("上传群文件 %v 失败: 文件不存在", file)
   590  		return Failed(100, "FILE_NOT_FOUND", "文件不存在")
   591  	}
   592  	fs, err := bot.Client.GetGroupFileSystem(groupID)
   593  	if err != nil {
   594  		log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err)
   595  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   596  	}
   597  	if folder == "" {
   598  		folder = "/"
   599  	}
   600  	if err = fs.UploadFile(file, name, folder); err != nil {
   601  		log.Warnf("上传群 %v 文件 %v 失败: %v", groupID, file, err)
   602  		return Failed(100, "FILE_SYSTEM_UPLOAD_API_ERROR", err.Error())
   603  	}
   604  	return OK(nil)
   605  }
   606  
   607  // CQUploadPrivateFile 扩展API-上传私聊文件
   608  //
   609  // @route(upload_private_file)
   610  func (bot *CQBot) CQUploadPrivateFile(userID int64, file, name string) global.MSG {
   611  	target := message.Source{
   612  		SourceType: message.SourcePrivate,
   613  		PrimaryID:  userID,
   614  	}
   615  	fileBody, err := os.Open(file)
   616  	if err != nil {
   617  		log.Warnf("上传私聊文件 %v 失败: %+v", file, err)
   618  		return Failed(100, "OPEN_FILE_ERROR", "打开文件失败")
   619  	}
   620  	defer func() { _ = fileBody.Close() }()
   621  	localFile := &client.LocalFile{
   622  		FileName: name,
   623  		Body:     fileBody,
   624  	}
   625  	if err := bot.Client.UploadFile(target, localFile); err != nil {
   626  		log.Warnf("上传私聊 %v 文件 %v 失败: %+v", userID, file, err)
   627  		return Failed(100, "FILE_SYSTEM_UPLOAD_API_ERROR", err.Error())
   628  	}
   629  	return OK(nil)
   630  }
   631  
   632  // CQGroupFileCreateFolder 拓展API-创建群文件文件夹
   633  //
   634  // @route(create_group_file_folder)
   635  func (bot *CQBot) CQGroupFileCreateFolder(groupID int64, parentID, name string) global.MSG {
   636  	fs, err := bot.Client.GetGroupFileSystem(groupID)
   637  	if err != nil {
   638  		log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err)
   639  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   640  	}
   641  	if err = fs.CreateFolder(parentID, name); err != nil {
   642  		log.Warnf("创建群 %v 文件夹失败: %v", groupID, err)
   643  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   644  	}
   645  	return OK(nil)
   646  }
   647  
   648  // CQGroupFileDeleteFolder 拓展API-删除群文件文件夹
   649  //
   650  // @route(delete_group_folder)
   651  // @rename(id->folder_id)
   652  func (bot *CQBot) CQGroupFileDeleteFolder(groupID int64, id string) global.MSG {
   653  	fs, err := bot.Client.GetGroupFileSystem(groupID)
   654  	if err != nil {
   655  		log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err)
   656  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   657  	}
   658  	if err = fs.DeleteFolder(id); err != nil {
   659  		log.Warnf("删除群 %v 文件夹 %v 时出现文件: %v", groupID, id, err)
   660  		return Failed(200, "FILE_SYSTEM_API_ERROR", err.Error())
   661  	}
   662  	return OK(nil)
   663  }
   664  
   665  // CQGroupFileDeleteFile 拓展API-删除群文件
   666  //
   667  // @route(delete_group_file)
   668  // @rename(id->file_id, bus_id->"[busid\x2Cbus_id].0")
   669  func (bot *CQBot) CQGroupFileDeleteFile(groupID int64, id string, busID int32) global.MSG {
   670  	fs, err := bot.Client.GetGroupFileSystem(groupID)
   671  	if err != nil {
   672  		log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err)
   673  		return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error())
   674  	}
   675  	if res := fs.DeleteFile("", id, busID); res != "" {
   676  		log.Warnf("删除群 %v 文件 %v 时出现文件: %v", groupID, id, res)
   677  		return Failed(200, "FILE_SYSTEM_API_ERROR", res)
   678  	}
   679  	return OK(nil)
   680  }
   681  
   682  // CQGetWordSlices 隐藏API-获取中文分词
   683  //
   684  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E4%B8%AD%E6%96%87%E5%88%86%E8%AF%8D-%E9%9A%90%E8%97%8F-api
   685  // @route(.get_word_slices)
   686  func (bot *CQBot) CQGetWordSlices(content string) global.MSG {
   687  	slices, err := bot.Client.GetWordSegmentation(content)
   688  	if err != nil {
   689  		return Failed(100, "WORD_SEGMENTATION_API_ERROR", err.Error())
   690  	}
   691  	for i := 0; i < len(slices); i++ {
   692  		slices[i] = strings.ReplaceAll(slices[i], "\u0000", "")
   693  	}
   694  	return OK(global.MSG{"slices": slices})
   695  }
   696  
   697  // CQSendMessage 发送消息
   698  //
   699  // @route11(send_msg)
   700  // @rename(m->message)
   701  func (bot *CQBot) CQSendMessage(groupID, userID int64, m gjson.Result, messageType string, autoEscape bool) global.MSG {
   702  	switch {
   703  	case messageType == "group":
   704  		return bot.CQSendGroupMessage(groupID, m, autoEscape)
   705  	case messageType == "private":
   706  		fallthrough
   707  	case userID != 0:
   708  		return bot.CQSendPrivateMessage(userID, groupID, m, autoEscape)
   709  	case groupID != 0:
   710  		return bot.CQSendGroupMessage(groupID, m, autoEscape)
   711  	}
   712  	return global.MSG{}
   713  }
   714  
   715  // CQSendForwardMessage 发送合并转发消息
   716  //
   717  // @route11(send_forward_msg)
   718  // @rename(m->messages)
   719  func (bot *CQBot) CQSendForwardMessage(groupID, userID int64, m gjson.Result, messageType string) global.MSG {
   720  	switch {
   721  	case messageType == "group":
   722  		return bot.CQSendGroupForwardMessage(groupID, m)
   723  	case messageType == "private":
   724  		fallthrough
   725  	case userID != 0:
   726  		return bot.CQSendPrivateForwardMessage(userID, m)
   727  	case groupID != 0:
   728  		return bot.CQSendGroupForwardMessage(groupID, m)
   729  	}
   730  	return global.MSG{}
   731  }
   732  
   733  // CQSendGroupMessage 发送群消息
   734  //
   735  // https://git.io/Jtz1c
   736  // @route11(send_group_msg)
   737  // @rename(m->message)
   738  func (bot *CQBot) CQSendGroupMessage(groupID int64, m gjson.Result, autoEscape bool) global.MSG {
   739  	group := bot.Client.FindGroup(groupID)
   740  	if group == nil {
   741  		return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
   742  	}
   743  	fixAt := func(elem []message.IMessageElement) {
   744  		for _, e := range elem {
   745  			if at, ok := e.(*message.AtElement); ok && at.Target != 0 && at.Display == "" {
   746  				mem := group.FindMember(at.Target)
   747  				if mem != nil {
   748  					at.Display = "@" + mem.DisplayName()
   749  				} else {
   750  					at.Display = "@" + strconv.FormatInt(at.Target, 10)
   751  				}
   752  			}
   753  		}
   754  	}
   755  
   756  	var elem []message.IMessageElement
   757  	if m.Type == gjson.JSON {
   758  		elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGroup)
   759  	} else {
   760  		str := m.String()
   761  		if str == "" {
   762  			log.Warnf("群 %v 消息发送失败: 信息为空.", groupID)
   763  			return Failed(100, "EMPTY_MSG_ERROR", "消息为空")
   764  		}
   765  		if autoEscape {
   766  			elem = []message.IMessageElement{message.NewText(str)}
   767  		} else {
   768  			elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGroup)
   769  		}
   770  	}
   771  	fixAt(elem)
   772  	mid, err := bot.SendGroupMessage(groupID, &message.SendingMessage{Elements: elem})
   773  	if err != nil {
   774  		return Failed(100, "SEND_MSG_API_ERROR", err.Error())
   775  	}
   776  	log.Infof("发送群 %v(%v) 的消息: %v (%v)", group.Name, groupID, limitedString(m.String()), mid)
   777  	return OK(global.MSG{"message_id": mid})
   778  }
   779  
   780  // CQSendGuildChannelMessage 发送频道消息
   781  //
   782  // @route(send_guild_channel_msg)
   783  // @rename(m->message)
   784  func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.Result, autoEscape bool) global.MSG {
   785  	guild := bot.Client.GuildService.FindGuild(guildID)
   786  	if guild == nil {
   787  		return Failed(100, "GUILD_NOT_FOUND", "频道不存在")
   788  	}
   789  	channel := guild.FindChannel(channelID)
   790  	if channel == nil {
   791  		return Failed(100, "CHANNEL_NOT_FOUND", "子频道不存在")
   792  	}
   793  	if channel.ChannelType != client.ChannelTypeText {
   794  		log.Warnf("无法发送频道信息: 频道类型错误, 不接受文本信息")
   795  		return Failed(100, "CHANNEL_NOT_SUPPORTED_TEXT_MSG", "子频道类型错误, 无法发送文本信息")
   796  	}
   797  	fixAt := func(elem []message.IMessageElement) {
   798  		for _, e := range elem {
   799  			if at, ok := e.(*message.AtElement); ok && at.Target != 0 && at.Display == "" {
   800  				mem, _ := bot.Client.GuildService.FetchGuildMemberProfileInfo(guildID, uint64(at.Target))
   801  				if mem != nil {
   802  					at.Display = "@" + mem.Nickname
   803  				} else {
   804  					at.Display = "@" + strconv.FormatInt(at.Target, 10)
   805  				}
   806  			}
   807  		}
   808  	}
   809  
   810  	var elem []message.IMessageElement
   811  	if m.Type == gjson.JSON {
   812  		elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourceGuildChannel)
   813  	} else {
   814  		str := m.String()
   815  		if str == "" {
   816  			log.Warn("频道发送失败: 信息为空.")
   817  			return Failed(100, "EMPTY_MSG_ERROR", "消息为空")
   818  		}
   819  		if autoEscape {
   820  			elem = []message.IMessageElement{message.NewText(str)}
   821  		} else {
   822  			elem = bot.ConvertStringMessage(onebot.V11, str, message.SourceGuildChannel)
   823  		}
   824  	}
   825  	fixAt(elem)
   826  	mid := bot.SendGuildChannelMessage(guildID, channelID, &message.SendingMessage{Elements: elem})
   827  	if mid == "" {
   828  		return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
   829  	}
   830  	log.Infof("发送频道 %v(%v) 子频道 %v(%v) 的消息: %v (%v)", guild.GuildName, guild.GuildId, channel.ChannelName, channel.ChannelId, limitedString(m.String()), mid)
   831  	return OK(global.MSG{"message_id": mid})
   832  }
   833  
   834  func (bot *CQBot) uploadForwardElement(m gjson.Result, target int64, sourceType message.SourceType) *message.ForwardElement {
   835  	ts := time.Now().Add(-time.Minute * 5)
   836  	groupID := target
   837  	source := message.Source{SourceType: sourceType, PrimaryID: target}
   838  	if sourceType == message.SourcePrivate {
   839  		// ios 设备的合并转发来源群号不能为 0
   840  		if len(bot.Client.GroupList) == 0 {
   841  			groupID = 1
   842  		} else {
   843  			groupID = bot.Client.GroupList[0].Uin
   844  		}
   845  	}
   846  	builder := bot.Client.NewForwardMessageBuilder(groupID)
   847  
   848  	var convertMessage func(m gjson.Result) *message.ForwardMessage
   849  	convertMessage = func(m gjson.Result) *message.ForwardMessage {
   850  		fm := message.NewForwardMessage()
   851  		var w worker
   852  		resolveElement := func(elems []message.IMessageElement) []message.IMessageElement {
   853  			for i, elem := range elems {
   854  				p := &elems[i]
   855  				switch o := elem.(type) {
   856  				case *msg.LocalVideo:
   857  					w.do(func() {
   858  						gm, err := bot.uploadLocalVideo(source, o)
   859  						if err != nil {
   860  							log.Warnf(uploadFailedTemplate, "合并转发", target, "视频", err)
   861  						} else {
   862  							*p = gm
   863  						}
   864  					})
   865  				case *msg.LocalImage:
   866  					w.do(func() {
   867  						gm, err := bot.uploadLocalImage(source, o)
   868  						if err != nil {
   869  							log.Warnf(uploadFailedTemplate, "合并转发", target, "图片", err)
   870  						} else {
   871  							*p = gm
   872  						}
   873  					})
   874  				}
   875  			}
   876  			return elems
   877  		}
   878  
   879  		convert := func(e gjson.Result) *message.ForwardNode {
   880  			if e.Get("type").Str != "node" {
   881  				return nil
   882  			}
   883  			if e.Get("data.id").Exists() {
   884  				i := e.Get("data.id").Int()
   885  				m, _ := db.GetMessageByGlobalID(int32(i))
   886  				if m != nil {
   887  					mSource := message.SourcePrivate
   888  					if m.GetType() == "group" {
   889  						mSource = message.SourceGroup
   890  					}
   891  					msgTime := m.GetAttribute().Timestamp
   892  					if msgTime == 0 {
   893  						msgTime = ts.Unix()
   894  					}
   895  					return &message.ForwardNode{
   896  						SenderId:   m.GetAttribute().SenderUin,
   897  						SenderName: m.GetAttribute().SenderName,
   898  						Time:       int32(msgTime),
   899  						Message:    resolveElement(bot.ConvertContentMessage(m.GetContent(), mSource, false)),
   900  					}
   901  				}
   902  				log.Warnf("警告: 引用消息 %v 错误或数据库未开启.", e.Get("data.id").Str)
   903  				return nil
   904  			}
   905  			uin := e.Get("data.[user_id,uin].0").Int()
   906  			msgTime := e.Get("data.time").Int()
   907  			if msgTime == 0 {
   908  				msgTime = ts.Unix()
   909  			}
   910  			name := e.Get("data.[name,nickname].0").Str
   911  			c := e.Get("data.content")
   912  			if c.IsArray() {
   913  				nested := false
   914  				c.ForEach(func(_, value gjson.Result) bool {
   915  					if value.Get("type").Str == "node" {
   916  						nested = true
   917  						return false
   918  					}
   919  					return true
   920  				})
   921  				if nested { // 处理嵌套
   922  					nestedNode := builder.NestedNode()
   923  					builder.Link(nestedNode, convertMessage(c))
   924  					return &message.ForwardNode{
   925  						SenderId:   uin,
   926  						SenderName: name,
   927  						Time:       int32(msgTime),
   928  						Message:    []message.IMessageElement{nestedNode},
   929  					}
   930  				}
   931  			}
   932  			content := bot.ConvertObjectMessage(onebot.V11, c, sourceType)
   933  			if uin != 0 && name != "" && len(content) > 0 {
   934  				return &message.ForwardNode{
   935  					SenderId:   uin,
   936  					SenderName: name,
   937  					Time:       int32(msgTime),
   938  					Message:    resolveElement(content),
   939  				}
   940  			}
   941  			log.Warnf("警告: 非法 Forward node 将跳过. uin: %v name: %v content count: %v", uin, name, len(content))
   942  			return nil
   943  		}
   944  
   945  		if m.IsArray() {
   946  			for _, item := range m.Array() {
   947  				node := convert(item)
   948  				if node != nil {
   949  					fm.AddNode(node)
   950  				}
   951  			}
   952  		} else {
   953  			node := convert(m)
   954  			if node != nil {
   955  				fm.AddNode(node)
   956  			}
   957  		}
   958  
   959  		w.wait()
   960  		return fm
   961  	}
   962  	return builder.Main(convertMessage(m))
   963  }
   964  
   965  // CQSendGroupForwardMessage 扩展API-发送合并转发(群)
   966  //
   967  // https://docs.go-cqhttp.org/api/#%E5%8F%91%E9%80%81%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91-%E7%BE%A4
   968  // @route11(send_group_forward_msg)
   969  // @rename(m->messages)
   970  func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) global.MSG {
   971  	if m.Type != gjson.JSON {
   972  		return Failed(100)
   973  	}
   974  	source := message.Source{
   975  		SourceType: message.SourcePrivate,
   976  		PrimaryID:  0,
   977  	}
   978  	fe := bot.uploadForwardElement(m, groupID, message.SourceGroup)
   979  	if fe == nil {
   980  		return Failed(100, "EMPTY_NODES", "未找到任何可发送的合并转发信息")
   981  	}
   982  	ret := bot.Client.SendGroupForwardMessage(groupID, fe)
   983  	if ret == nil || ret.Id == -1 {
   984  		log.Warnf("合并转发(群 %v)消息发送失败: 账号可能被风控.", groupID)
   985  		return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
   986  	}
   987  	mid := bot.InsertGroupMessage(ret, source)
   988  	log.Infof("发送群 %v(%v)  的合并转发消息: %v (%v)", groupID, groupID, limitedString(m.String()), mid)
   989  	return OK(global.MSG{
   990  		"message_id": mid,
   991  		"forward_id": fe.ResId,
   992  	})
   993  }
   994  
   995  // CQSendPrivateForwardMessage 扩展API-发送合并转发(好友)
   996  //
   997  // https://docs.go-cqhttp.org/api/#%E5%8F%91%E9%80%81%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91-%E7%BE%A4
   998  // @route11(send_private_forward_msg)
   999  // @rename(m->messages)
  1000  func (bot *CQBot) CQSendPrivateForwardMessage(userID int64, m gjson.Result) global.MSG {
  1001  	if m.Type != gjson.JSON {
  1002  		return Failed(100)
  1003  	}
  1004  	fe := bot.uploadForwardElement(m, userID, message.SourcePrivate)
  1005  	if fe == nil {
  1006  		return Failed(100, "EMPTY_NODES", "未找到任何可发送的合并转发信息")
  1007  	}
  1008  	mid := bot.SendPrivateMessage(userID, 0, &message.SendingMessage{Elements: []message.IMessageElement{fe}})
  1009  	if mid == -1 {
  1010  		log.Warnf("合并转发(好友 %v)消息发送失败: 账号可能被风控.", userID)
  1011  		return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
  1012  	}
  1013  	log.Infof("发送好友 %v(%v)  的合并转发消息: %v (%v)", userID, userID, limitedString(m.String()), mid)
  1014  	return OK(global.MSG{
  1015  		"message_id": mid,
  1016  		"forward_id": fe.ResId,
  1017  	})
  1018  }
  1019  
  1020  // CQSendPrivateMessage 发送私聊消息
  1021  //
  1022  // https://git.io/Jtz1l
  1023  // @route11(send_private_msg)
  1024  // @rename(m->message)
  1025  func (bot *CQBot) CQSendPrivateMessage(userID int64, groupID int64, m gjson.Result, autoEscape bool) global.MSG {
  1026  	var elem []message.IMessageElement
  1027  	if m.Type == gjson.JSON {
  1028  		elem = bot.ConvertObjectMessage(onebot.V11, m, message.SourcePrivate)
  1029  	} else {
  1030  		str := m.String()
  1031  		if str == "" {
  1032  			return Failed(100, "EMPTY_MSG_ERROR", "消息为空")
  1033  		}
  1034  		if autoEscape {
  1035  			elem = []message.IMessageElement{message.NewText(str)}
  1036  		} else {
  1037  			elem = bot.ConvertStringMessage(onebot.V11, str, message.SourcePrivate)
  1038  		}
  1039  	}
  1040  	mid := bot.SendPrivateMessage(userID, groupID, &message.SendingMessage{Elements: elem})
  1041  	if mid == -1 {
  1042  		return Failed(100, "SEND_MSG_API_ERROR", "请参考 go-cqhttp 端输出")
  1043  	}
  1044  	log.Infof("发送好友 %v(%v)  的消息: %v (%v)", userID, userID, limitedString(m.String()), mid)
  1045  	return OK(global.MSG{"message_id": mid})
  1046  }
  1047  
  1048  // CQSetGroupCard 设置群名片(群备注)
  1049  //
  1050  // https://git.io/Jtz1B
  1051  // @route(set_group_card)
  1052  func (bot *CQBot) CQSetGroupCard(groupID, userID int64, card string) global.MSG {
  1053  	if g := bot.Client.FindGroup(groupID); g != nil {
  1054  		if m := g.FindMember(userID); m != nil {
  1055  			m.EditCard(card)
  1056  			return OK(nil)
  1057  		}
  1058  	}
  1059  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1060  }
  1061  
  1062  // CQSetGroupSpecialTitle 设置群组专属头衔
  1063  //
  1064  // https://git.io/Jtz10
  1065  // @route(set_group_special_title)
  1066  // @rename(title->special_title)
  1067  func (bot *CQBot) CQSetGroupSpecialTitle(groupID, userID int64, title string) global.MSG {
  1068  	if g := bot.Client.FindGroup(groupID); g != nil {
  1069  		if m := g.FindMember(userID); m != nil {
  1070  			m.EditSpecialTitle(title)
  1071  			return OK(nil)
  1072  		}
  1073  	}
  1074  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1075  }
  1076  
  1077  // CQSetGroupName 设置群名
  1078  //
  1079  // https://git.io/Jtz12
  1080  // @route(set_group_name)
  1081  // @rename(name->group_name)
  1082  func (bot *CQBot) CQSetGroupName(groupID int64, name string) global.MSG {
  1083  	if g := bot.Client.FindGroup(groupID); g != nil {
  1084  		g.UpdateName(name)
  1085  		return OK(nil)
  1086  	}
  1087  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1088  }
  1089  
  1090  // CQGetGroupMemo 扩展API-获取群公告
  1091  // @route(_get_group_notice)
  1092  func (bot *CQBot) CQGetGroupMemo(groupID int64) global.MSG {
  1093  	r, err := bot.Client.GetGroupNotice(groupID)
  1094  	if err != nil {
  1095  		return Failed(100, "获取群公告失败", err.Error())
  1096  	}
  1097  
  1098  	return OK(r)
  1099  }
  1100  
  1101  // CQSetGroupMemo 扩展API-发送群公告
  1102  //
  1103  // https://docs.go-cqhttp.org/api/#%E5%8F%91%E9%80%81%E7%BE%A4%E5%85%AC%E5%91%8A
  1104  // @route(_send_group_notice)
  1105  // @rename(msg->content, img->image)
  1106  func (bot *CQBot) CQSetGroupMemo(groupID int64, msg, img string) global.MSG {
  1107  	if g := bot.Client.FindGroup(groupID); g != nil {
  1108  		if g.SelfPermission() == client.Member {
  1109  			return Failed(100, "PERMISSION_DENIED", "权限不足")
  1110  		}
  1111  		if img != "" {
  1112  			data, err := global.FindFile(img, "", global.ImagePath)
  1113  			if err != nil {
  1114  				return Failed(100, "IMAGE_NOT_FOUND", "图片未找到")
  1115  			}
  1116  			noticeID, err := bot.Client.AddGroupNoticeWithPic(groupID, msg, data)
  1117  			if err != nil {
  1118  				return Failed(100, "SEND_NOTICE_ERROR", err.Error())
  1119  			}
  1120  			return OK(global.MSG{"notice_id": noticeID})
  1121  		}
  1122  		noticeID, err := bot.Client.AddGroupNoticeSimple(groupID, msg)
  1123  		if err != nil {
  1124  			return Failed(100, "SEND_NOTICE_ERROR", err.Error())
  1125  		}
  1126  		return OK(global.MSG{"notice_id": noticeID})
  1127  	}
  1128  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1129  }
  1130  
  1131  // CQDelGroupMemo 扩展API-删除群公告
  1132  // @route(_del_group_notice)
  1133  // @rename(fid->notice_id)
  1134  func (bot *CQBot) CQDelGroupMemo(groupID int64, fid string) global.MSG {
  1135  	if g := bot.Client.FindGroup(groupID); g != nil {
  1136  		if g.SelfPermission() == client.Member {
  1137  			return Failed(100, "PERMISSION_DENIED", "权限不足")
  1138  		}
  1139  		err := bot.Client.DelGroupNotice(groupID, fid)
  1140  		if err != nil {
  1141  			return Failed(100, "DELETE_NOTICE_ERROR", err.Error())
  1142  		}
  1143  		return OK(nil)
  1144  	}
  1145  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1146  }
  1147  
  1148  // CQSetGroupKick 群组踢人
  1149  //
  1150  // https://git.io/Jtz1V
  1151  // @route(set_group_kick)
  1152  // @rename(msg->message, block->reject_add_request)
  1153  func (bot *CQBot) CQSetGroupKick(groupID int64, userID int64, msg string, block bool) global.MSG {
  1154  	if g := bot.Client.FindGroup(groupID); g != nil {
  1155  		m := g.FindMember(userID)
  1156  		if m == nil {
  1157  			return Failed(100, "MEMBER_NOT_FOUND", "人员不存在")
  1158  		}
  1159  		err := m.Kick(msg, block)
  1160  		if err != nil {
  1161  			return Failed(100, "NOT_MANAGEABLE", "机器人权限不足")
  1162  		}
  1163  		return OK(nil)
  1164  	}
  1165  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1166  }
  1167  
  1168  // CQSetGroupBan 群组单人禁言
  1169  //
  1170  // https://git.io/Jtz1w
  1171  // @route(set_group_ban)
  1172  // @default(duration=1800)
  1173  func (bot *CQBot) CQSetGroupBan(groupID, userID int64, duration uint32) global.MSG {
  1174  	if g := bot.Client.FindGroup(groupID); g != nil {
  1175  		if m := g.FindMember(userID); m != nil {
  1176  			err := m.Mute(duration)
  1177  			if err != nil {
  1178  				if duration >= 2592000 {
  1179  					return Failed(100, "DURATION_IS_NOT_IN_RANGE", "非法的禁言时长")
  1180  				}
  1181  				return Failed(100, "NOT_MANAGEABLE", "机器人权限不足")
  1182  			}
  1183  			return OK(nil)
  1184  		}
  1185  	}
  1186  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1187  }
  1188  
  1189  // CQSetGroupWholeBan 群组全员禁言
  1190  //
  1191  // https://git.io/Jtz1o
  1192  // @route(set_group_whole_ban)
  1193  // @default(enable=true)
  1194  func (bot *CQBot) CQSetGroupWholeBan(groupID int64, enable bool) global.MSG {
  1195  	if g := bot.Client.FindGroup(groupID); g != nil {
  1196  		g.MuteAll(enable)
  1197  		return OK(nil)
  1198  	}
  1199  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1200  }
  1201  
  1202  // CQSetGroupLeave 退出群组
  1203  //
  1204  // https://git.io/Jtz1K
  1205  // @route(set_group_leave)
  1206  func (bot *CQBot) CQSetGroupLeave(groupID int64) global.MSG {
  1207  	if g := bot.Client.FindGroup(groupID); g != nil {
  1208  		g.Quit()
  1209  		return OK(nil)
  1210  	}
  1211  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1212  }
  1213  
  1214  // CQGetAtAllRemain 扩展API-获取群 @全体成员 剩余次数
  1215  //
  1216  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E7%BE%A4-%E5%85%A8%E4%BD%93%E6%88%90%E5%91%98-%E5%89%A9%E4%BD%99%E6%AC%A1%E6%95%B0
  1217  // @route(get_group_at_all_remain)
  1218  func (bot *CQBot) CQGetAtAllRemain(groupID int64) global.MSG {
  1219  	if g := bot.Client.FindGroup(groupID); g != nil {
  1220  		i, err := bot.Client.GetAtAllRemain(groupID)
  1221  		if err != nil {
  1222  			return Failed(100, "GROUP_REMAIN_API_ERROR", err.Error())
  1223  		}
  1224  		return OK(i)
  1225  	}
  1226  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1227  }
  1228  
  1229  // CQProcessFriendRequest 处理加好友请求
  1230  //
  1231  // https://git.io/Jtz11
  1232  // @route(set_friend_add_request)
  1233  // @default(approve=true)
  1234  func (bot *CQBot) CQProcessFriendRequest(flag string, approve bool) global.MSG {
  1235  	req, ok := bot.friendReqCache.Load(flag)
  1236  	if !ok {
  1237  		return Failed(100, "FLAG_NOT_FOUND", "FLAG不存在")
  1238  	}
  1239  	if approve {
  1240  		req.Accept()
  1241  	} else {
  1242  		req.Reject()
  1243  	}
  1244  	return OK(nil)
  1245  }
  1246  
  1247  // CQProcessGroupRequest 处理加群请求/邀请
  1248  //
  1249  // https://git.io/Jtz1D
  1250  // @route(set_group_add_request)
  1251  // @rename(sub_type->"[sub_type\x2Ctype].0")
  1252  // @default(approve=true)
  1253  func (bot *CQBot) CQProcessGroupRequest(flag, subType, reason string, approve bool) global.MSG {
  1254  	msgs, err := bot.Client.GetGroupSystemMessages()
  1255  	if err != nil {
  1256  		log.Warnf("获取群系统消息失败: %v", err)
  1257  		return Failed(100, "SYSTEM_MSG_API_ERROR", err.Error())
  1258  	}
  1259  	if subType == "add" {
  1260  		for _, req := range msgs.JoinRequests {
  1261  			if strconv.FormatInt(req.RequestId, 10) == flag {
  1262  				if req.Checked {
  1263  					log.Warnf("处理群系统消息失败: 无法操作已处理的消息.")
  1264  					return Failed(100, "FLAG_HAS_BEEN_CHECKED", "消息已被处理")
  1265  				}
  1266  				if approve {
  1267  					req.Accept()
  1268  				} else {
  1269  					req.Reject(false, reason)
  1270  				}
  1271  				return OK(nil)
  1272  			}
  1273  		}
  1274  	} else {
  1275  		for _, req := range msgs.InvitedRequests {
  1276  			if strconv.FormatInt(req.RequestId, 10) == flag {
  1277  				if req.Checked {
  1278  					log.Warnf("处理群系统消息失败: 无法操作已处理的消息.")
  1279  					return Failed(100, "FLAG_HAS_BEEN_CHECKED", "消息已被处理")
  1280  				}
  1281  				if approve {
  1282  					req.Accept()
  1283  				} else {
  1284  					req.Reject(false, reason)
  1285  				}
  1286  				return OK(nil)
  1287  			}
  1288  		}
  1289  	}
  1290  	log.Warnf("处理群系统消息失败: 消息 %v 不存在.", flag)
  1291  	return Failed(100, "FLAG_NOT_FOUND", "FLAG不存在")
  1292  }
  1293  
  1294  // CQDeleteMessage 撤回消息
  1295  //
  1296  // https:// git.io/Jtz1y
  1297  // @route(delete_msg)
  1298  func (bot *CQBot) CQDeleteMessage(messageID int32) global.MSG {
  1299  	msg, err := db.GetMessageByGlobalID(messageID)
  1300  	if err != nil {
  1301  		log.Warnf("撤回消息时出现错误: %v", err)
  1302  		return Failed(100, "MESSAGE_NOT_FOUND", "消息不存在")
  1303  	}
  1304  	switch o := msg.(type) {
  1305  	case *db.StoredGroupMessage:
  1306  		if err = bot.Client.RecallGroupMessage(o.GroupCode, o.Attribute.MessageSeq, o.Attribute.InternalID); err != nil {
  1307  			log.Warnf("撤回 %v 失败: %v", messageID, err)
  1308  			return Failed(100, "RECALL_API_ERROR", err.Error())
  1309  		}
  1310  	case *db.StoredPrivateMessage:
  1311  		if o.Attribute.SenderUin != bot.Client.Uin {
  1312  			log.Warnf("撤回 %v 失败: 好友会话无法撤回对方消息.", messageID)
  1313  			return Failed(100, "CANNOT_RECALL_FRIEND_MSG", "无法撤回对方消息")
  1314  		}
  1315  		if err = bot.Client.RecallPrivateMessage(o.TargetUin, o.Attribute.Timestamp, o.Attribute.MessageSeq, o.Attribute.InternalID); err != nil {
  1316  			log.Warnf("撤回 %v 失败: %v", messageID, err)
  1317  			return Failed(100, "RECALL_API_ERROR", err.Error())
  1318  		}
  1319  	default:
  1320  		return Failed(100, "UNKNOWN_ERROR")
  1321  	}
  1322  	return OK(nil)
  1323  }
  1324  
  1325  // CQSetGroupAdmin 群组设置管理员
  1326  //
  1327  // https://git.io/Jtz1S
  1328  // @route(set_group_admin)
  1329  // @default(enable=true)
  1330  func (bot *CQBot) CQSetGroupAdmin(groupID, userID int64, enable bool) global.MSG {
  1331  	group := bot.Client.FindGroup(groupID)
  1332  	if group == nil || group.OwnerUin != bot.Client.Uin {
  1333  		return Failed(100, "PERMISSION_DENIED", "群不存在或权限不足")
  1334  	}
  1335  	mem := group.FindMember(userID)
  1336  	if mem == nil {
  1337  		return Failed(100, "GROUP_MEMBER_NOT_FOUND", "群成员不存在")
  1338  	}
  1339  	mem.SetAdmin(enable)
  1340  	t, err := bot.Client.GetGroupMembers(group)
  1341  	if err != nil {
  1342  		log.Warnf("刷新群 %v 成员列表失败: %v", groupID, err)
  1343  		return Failed(100, "GET_MEMBERS_API_ERROR", err.Error())
  1344  	}
  1345  	group.Members = t
  1346  	return OK(nil)
  1347  }
  1348  
  1349  // CQSetGroupAnonymous 群组匿名
  1350  //
  1351  // https://beautyyu.one
  1352  // @route(set_group_anonymous)
  1353  // @default(enable=true)
  1354  func (bot *CQBot) CQSetGroupAnonymous(groupID int64, enable bool) global.MSG {
  1355  	if g := bot.Client.FindGroup(groupID); g != nil {
  1356  		g.SetAnonymous(enable)
  1357  		return OK(nil)
  1358  	}
  1359  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1360  }
  1361  
  1362  // CQGetGroupHonorInfo 获取群荣誉信息
  1363  //
  1364  // https://git.io/Jtz1H
  1365  // @route(get_group_honor_info)
  1366  // @rename(t->type)
  1367  func (bot *CQBot) CQGetGroupHonorInfo(groupID int64, t string) global.MSG {
  1368  	msg := global.MSG{"group_id": groupID}
  1369  	convertMem := func(memList []client.HonorMemberInfo) (ret []global.MSG) {
  1370  		for _, mem := range memList {
  1371  			ret = append(ret, global.MSG{
  1372  				"user_id":     mem.Uin,
  1373  				"nickname":    mem.Name,
  1374  				"avatar":      mem.Avatar,
  1375  				"description": mem.Desc,
  1376  			})
  1377  		}
  1378  		return
  1379  	}
  1380  	if t == "talkative" || t == "all" {
  1381  		if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Talkative); err == nil {
  1382  			if honor.CurrentTalkative.Uin != 0 {
  1383  				msg["current_talkative"] = global.MSG{
  1384  					"user_id":   honor.CurrentTalkative.Uin,
  1385  					"nickname":  honor.CurrentTalkative.Name,
  1386  					"avatar":    honor.CurrentTalkative.Avatar,
  1387  					"day_count": honor.CurrentTalkative.DayCount,
  1388  				}
  1389  			}
  1390  			msg["talkative_list"] = convertMem(honor.TalkativeList)
  1391  		} else {
  1392  			log.Infof("获取群龙王出错:%v", err)
  1393  		}
  1394  	}
  1395  
  1396  	if t == "performer" || t == "all" {
  1397  		if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Performer); err == nil {
  1398  			msg["performer_list"] = convertMem(honor.ActorList)
  1399  		} else {
  1400  			log.Infof("获取群聊之火出错:%v", err)
  1401  		}
  1402  	}
  1403  
  1404  	if t == "legend" || t == "all" {
  1405  		if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Legend); err == nil {
  1406  			msg["legend_list"] = convertMem(honor.LegendList)
  1407  		} else {
  1408  			log.Infof("获取群聊炽焰出错:%v", err)
  1409  		}
  1410  	}
  1411  
  1412  	if t == "strong_newbie" || t == "all" {
  1413  		if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.StrongNewbie); err == nil {
  1414  			msg["strong_newbie_list"] = convertMem(honor.StrongNewbieList)
  1415  		} else {
  1416  			log.Infof("获取冒尖小春笋出错:%v", err)
  1417  		}
  1418  	}
  1419  
  1420  	if t == "emotion" || t == "all" {
  1421  		if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Emotion); err == nil {
  1422  			msg["emotion_list"] = convertMem(honor.EmotionList)
  1423  		} else {
  1424  			log.Infof("获取快乐之源出错:%v", err)
  1425  		}
  1426  	}
  1427  	return OK(msg)
  1428  }
  1429  
  1430  // CQGetStrangerInfo 获取陌生人信息
  1431  //
  1432  // https://git.io/Jtz17
  1433  // @route11(get_stranger_info)
  1434  // @route12(get_user_info)
  1435  func (bot *CQBot) CQGetStrangerInfo(userID int64) global.MSG {
  1436  	info, err := bot.Client.GetSummaryInfo(userID)
  1437  	if err != nil {
  1438  		return Failed(100, "SUMMARY_API_ERROR", err.Error())
  1439  	}
  1440  	return OK(global.MSG{
  1441  		"user_id":  info.Uin,
  1442  		"nickname": info.Nickname,
  1443  		"qid":      info.Qid,
  1444  		"sex": func() string {
  1445  			if info.Sex == 1 {
  1446  				return "female"
  1447  			} else if info.Sex == 0 {
  1448  				return "male"
  1449  			}
  1450  			// unknown = 0x2
  1451  			return "unknown"
  1452  		}(),
  1453  		"sign":       info.Sign,
  1454  		"age":        info.Age,
  1455  		"level":      info.Level,
  1456  		"login_days": info.LoginDays,
  1457  		"vip_level":  info.VipLevel,
  1458  	})
  1459  }
  1460  
  1461  // CQHandleQuickOperation 隐藏API-对事件执行快速操作
  1462  //
  1463  // https://git.io/Jtz15
  1464  // @route11(".handle_quick_operation")
  1465  func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) global.MSG {
  1466  	postType := context.Get("post_type").Str
  1467  
  1468  	switch postType {
  1469  	case "message":
  1470  		anonymous := context.Get("anonymous")
  1471  		isAnonymous := anonymous.Type != gjson.Null
  1472  		msgType := context.Get("message_type").Str
  1473  		reply := operation.Get("reply")
  1474  
  1475  		if reply.Exists() {
  1476  			autoEscape := param.EnsureBool(operation.Get("auto_escape"), false)
  1477  			at := !isAnonymous && operation.Get("at_sender").Bool() && msgType == "group"
  1478  			if at && reply.IsArray() {
  1479  				// 在 reply 数组头部插入CQ码
  1480  				replySegments := make([]global.MSG, 0)
  1481  				segments := make([]global.MSG, 0)
  1482  				segments = append(segments, global.MSG{
  1483  					"type": "at",
  1484  					"data": global.MSG{
  1485  						"qq": context.Get("sender.user_id").Int(),
  1486  					},
  1487  				})
  1488  
  1489  				err := json.Unmarshal(utils.S2B(reply.Raw), &replySegments)
  1490  				if err != nil {
  1491  					log.WithError(err).Warnf("处理 at_sender 过程中发生错误")
  1492  					return Failed(-1, "处理 at_sender 过程中发生错误", err.Error())
  1493  				}
  1494  
  1495  				segments = append(segments, replySegments...)
  1496  
  1497  				modified, err := json.Marshal(segments)
  1498  				if err != nil {
  1499  					log.WithError(err).Warnf("处理 at_sender 过程中发生错误")
  1500  					return Failed(-1, "处理 at_sender 过程中发生错误", err.Error())
  1501  				}
  1502  
  1503  				reply = gjson.Parse(utils.B2S(modified))
  1504  			} else if at && reply.Type == gjson.String {
  1505  				reply = gjson.Parse(fmt.Sprintf(
  1506  					"\"[CQ:at,qq=%d]%s\"",
  1507  					context.Get("sender.user_id").Int(),
  1508  					reply.String(),
  1509  				))
  1510  			}
  1511  
  1512  			if msgType == "group" {
  1513  				bot.CQSendGroupMessage(context.Get("group_id").Int(), reply, autoEscape)
  1514  			}
  1515  			if msgType == "private" {
  1516  				bot.CQSendPrivateMessage(context.Get("user_id").Int(), context.Get("group_id").Int(), reply, autoEscape)
  1517  			}
  1518  		}
  1519  		if msgType == "group" {
  1520  			if operation.Get("delete").Bool() {
  1521  				bot.CQDeleteMessage(int32(context.Get("message_id").Int()))
  1522  			}
  1523  			if !isAnonymous && operation.Get("kick").Bool() {
  1524  				bot.CQSetGroupKick(context.Get("group_id").Int(), context.Get("user_id").Int(), "", operation.Get("reject_add_request").Bool())
  1525  			}
  1526  			if operation.Get("ban").Bool() {
  1527  				var duration uint32 = 30 * 60
  1528  				if operation.Get("ban_duration").Exists() {
  1529  					duration = uint32(operation.Get("ban_duration").Uint())
  1530  				}
  1531  				// unsupported anonymous ban yet
  1532  				if !isAnonymous {
  1533  					bot.CQSetGroupBan(context.Get("group_id").Int(), context.Get("user_id").Int(), duration)
  1534  				}
  1535  			}
  1536  		}
  1537  	case "request":
  1538  		reqType := context.Get("request_type").Str
  1539  		if operation.Get("approve").Exists() {
  1540  			if reqType == "friend" {
  1541  				bot.CQProcessFriendRequest(context.Get("flag").String(), operation.Get("approve").Bool())
  1542  			}
  1543  			if reqType == "group" {
  1544  				bot.CQProcessGroupRequest(context.Get("flag").String(), context.Get("sub_type").Str, operation.Get("reason").Str, operation.Get("approve").Bool())
  1545  			}
  1546  		}
  1547  	}
  1548  	return OK(nil)
  1549  }
  1550  
  1551  // CQGetImage 获取图片(修改自OneBot)
  1552  //
  1553  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87%E4%BF%A1%E6%81%AF
  1554  // @route(get_image)
  1555  func (bot *CQBot) CQGetImage(file string) global.MSG {
  1556  	var b []byte
  1557  	var err error
  1558  	if strings.HasSuffix(file, ".image") {
  1559  		var f []byte
  1560  		f, err = hex.DecodeString(strings.TrimSuffix(file, ".image"))
  1561  		b = cache.Image.Get(f)
  1562  	}
  1563  
  1564  	if b == nil {
  1565  		if !global.PathExists(path.Join(global.ImagePath, file)) {
  1566  			return Failed(100)
  1567  		}
  1568  		b, err = os.ReadFile(path.Join(global.ImagePath, file))
  1569  	}
  1570  
  1571  	if err == nil {
  1572  		r := binary.NewReader(b)
  1573  		r.ReadBytes(16)
  1574  		msg := global.MSG{
  1575  			"size":     r.ReadInt32(),
  1576  			"filename": r.ReadString(),
  1577  			"url":      r.ReadString(),
  1578  		}
  1579  		local := path.Join(global.CachePath, file+path.Ext(msg["filename"].(string)))
  1580  		if !global.PathExists(local) {
  1581  			r := download.Request{URL: msg["url"].(string)}
  1582  			if err := r.WriteToFile(local); err != nil {
  1583  				log.Warnf("下载图片 %v 时出现错误: %v", msg["url"], err)
  1584  				return Failed(100, "DOWNLOAD_IMAGE_ERROR", err.Error())
  1585  			}
  1586  		}
  1587  		msg["file"] = local
  1588  		return OK(msg)
  1589  	}
  1590  	return Failed(100, "LOAD_FILE_ERROR", err.Error())
  1591  }
  1592  
  1593  // CQDownloadFile 扩展API-下载文件到缓存目录
  1594  //
  1595  // https://docs.go-cqhttp.org/api/#%E4%B8%8B%E8%BD%BD%E6%96%87%E4%BB%B6%E5%88%B0%E7%BC%93%E5%AD%98%E7%9B%AE%E5%BD%95
  1596  // @route(download_file)
  1597  func (bot *CQBot) CQDownloadFile(url string, headers gjson.Result, threadCount int) global.MSG {
  1598  	h := map[string]string{}
  1599  	if headers.IsArray() {
  1600  		for _, sub := range headers.Array() {
  1601  			first, second, ok := strings.Cut(sub.String(), "=")
  1602  			if ok {
  1603  				h[first] = second
  1604  			}
  1605  		}
  1606  	}
  1607  	if headers.Type == gjson.String {
  1608  		lines := strings.Split(headers.String(), "\r\n")
  1609  		for _, sub := range lines {
  1610  			first, second, ok := strings.Cut(sub, "=")
  1611  			if ok {
  1612  				h[first] = second
  1613  			}
  1614  		}
  1615  	}
  1616  
  1617  	hash := md5.Sum([]byte(url))
  1618  	file := path.Join(global.CachePath, hex.EncodeToString(hash[:])+".cache")
  1619  	if global.PathExists(file) {
  1620  		if err := os.Remove(file); err != nil {
  1621  			log.Warnf("删除缓存文件 %v 时出现错误: %v", file, err)
  1622  			return Failed(100, "DELETE_FILE_ERROR", err.Error())
  1623  		}
  1624  	}
  1625  	r := download.Request{URL: url, Header: h}
  1626  	if err := r.WriteToFileMultiThreading(file, threadCount); err != nil {
  1627  		log.Warnf("下载链接 %v 时出现错误: %v", url, err)
  1628  		return Failed(100, "DOWNLOAD_FILE_ERROR", err.Error())
  1629  	}
  1630  	abs, _ := filepath.Abs(file)
  1631  	return OK(global.MSG{
  1632  		"file": abs,
  1633  	})
  1634  }
  1635  
  1636  // CQGetForwardMessage 获取合并转发消息
  1637  //
  1638  // https://git.io/Jtz1F
  1639  // @route(get_forward_msg)
  1640  // @rename(res_id->"[message_id\x2Cid].0")
  1641  func (bot *CQBot) CQGetForwardMessage(resID string) global.MSG {
  1642  	m := bot.Client.GetForwardMessage(resID)
  1643  	if m == nil {
  1644  		return Failed(100, "MSG_NOT_FOUND", "消息不存在")
  1645  	}
  1646  
  1647  	var transformNodes func(nodes []*message.ForwardNode) []global.MSG
  1648  	transformNodes = func(nodes []*message.ForwardNode) []global.MSG {
  1649  		r := make([]global.MSG, len(nodes))
  1650  		for i, n := range nodes {
  1651  			bot.checkMedia(n.Message, 0)
  1652  			content := ToFormattedMessage(n.Message, message.Source{SourceType: message.SourceGroup})
  1653  			if len(n.Message) == 1 {
  1654  				if forward, ok := n.Message[0].(*message.ForwardMessage); ok {
  1655  					content = transformNodes(forward.Nodes)
  1656  				}
  1657  			}
  1658  			r[i] = global.MSG{
  1659  				"sender": global.MSG{
  1660  					"user_id":  n.SenderId,
  1661  					"nickname": n.SenderName,
  1662  				},
  1663  				"time":     n.Time,
  1664  				"content":  content,
  1665  				"group_id": n.GroupId,
  1666  			}
  1667  		}
  1668  		return r
  1669  	}
  1670  	return OK(global.MSG{
  1671  		"messages": transformNodes(m.Nodes),
  1672  	})
  1673  }
  1674  
  1675  // CQGetMessage 获取消息
  1676  //
  1677  // https://git.io/Jtz1b
  1678  // @route(get_msg)
  1679  func (bot *CQBot) CQGetMessage(messageID int32) global.MSG {
  1680  	msg, err := db.GetMessageByGlobalID(messageID)
  1681  	if err != nil {
  1682  		log.Warnf("获取消息时出现错误: %v", err)
  1683  		return Failed(100, "MSG_NOT_FOUND", "消息不存在")
  1684  	}
  1685  	m := global.MSG{
  1686  		"message_id":    msg.GetGlobalID(),
  1687  		"message_id_v2": msg.GetID(),
  1688  		"message_type":  msg.GetType(),
  1689  		"real_id":       msg.GetAttribute().MessageSeq,
  1690  		"message_seq":   msg.GetAttribute().MessageSeq,
  1691  		"group":         msg.GetType() == "group",
  1692  		"sender": global.MSG{
  1693  			"user_id":  msg.GetAttribute().SenderUin,
  1694  			"nickname": msg.GetAttribute().SenderName,
  1695  		},
  1696  		"time": msg.GetAttribute().Timestamp,
  1697  	}
  1698  	switch o := msg.(type) {
  1699  	case *db.StoredGroupMessage:
  1700  		m["group_id"] = o.GroupCode
  1701  		m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourceGroup, false), message.Source{SourceType: message.SourceGroup, PrimaryID: o.GroupCode})
  1702  	case *db.StoredPrivateMessage:
  1703  		m["message"] = ToFormattedMessage(bot.ConvertContentMessage(o.Content, message.SourcePrivate, false), message.Source{SourceType: message.SourcePrivate})
  1704  	}
  1705  	return OK(m)
  1706  }
  1707  
  1708  // CQGetGuildMessage 获取频道消息
  1709  // @route(get_guild_msg)
  1710  func (bot *CQBot) CQGetGuildMessage(messageID string, noCache bool) global.MSG {
  1711  	source, seq := decodeGuildMessageID(messageID)
  1712  	if source.SourceType == 0 {
  1713  		log.Warnf("获取消息时出现错误: 无效消息ID")
  1714  		return Failed(100, "INVALID_MESSAGE_ID", "无效消息ID")
  1715  	}
  1716  	m := global.MSG{
  1717  		"message_id": messageID,
  1718  		"message_source": func() string {
  1719  			if source.SourceType == message.SourceGuildDirect {
  1720  				return "direct"
  1721  			}
  1722  			return "channel"
  1723  		}(),
  1724  		"message_seq": seq,
  1725  		"guild_id":    fU64(uint64(source.PrimaryID)),
  1726  		"reactions":   []int{},
  1727  	}
  1728  	// nolint: exhaustive
  1729  	switch source.SourceType {
  1730  	case message.SourceGuildChannel:
  1731  		m["channel_id"] = fU64(uint64(source.SecondaryID))
  1732  		if noCache {
  1733  			pull, err := bot.Client.GuildService.PullGuildChannelMessage(uint64(source.PrimaryID), uint64(source.SecondaryID), seq, seq)
  1734  			if err != nil {
  1735  				log.Warnf("获取消息时出现错误: %v", err)
  1736  				return Failed(100, "API_ERROR", err.Error())
  1737  			}
  1738  			if len(m) == 0 {
  1739  				log.Warnf("获取消息时出现错误: 消息不存在")
  1740  				return Failed(100, "MSG_NOT_FOUND", "消息不存在")
  1741  			}
  1742  			m["time"] = pull[0].Time
  1743  			m["sender"] = global.MSG{
  1744  				"user_id":  pull[0].Sender.TinyId,
  1745  				"tiny_id":  fU64(pull[0].Sender.TinyId),
  1746  				"nickname": pull[0].Sender.Nickname,
  1747  			}
  1748  			m["message"] = ToFormattedMessage(pull[0].Elements, source)
  1749  			m["reactions"] = convertReactions(pull[0].Reactions)
  1750  			bot.InsertGuildChannelMessage(pull[0])
  1751  		} else {
  1752  			channelMsgByDB, err := db.GetGuildChannelMessageByID(messageID)
  1753  			if err != nil {
  1754  				log.Warnf("获取消息时出现错误: %v", err)
  1755  				return Failed(100, "MSG_NOT_FOUND", "消息不存在")
  1756  			}
  1757  			m["time"] = channelMsgByDB.Attribute.Timestamp
  1758  			m["sender"] = global.MSG{
  1759  				"user_id":  channelMsgByDB.Attribute.SenderTinyID,
  1760  				"tiny_id":  fU64(channelMsgByDB.Attribute.SenderTinyID),
  1761  				"nickname": channelMsgByDB.Attribute.SenderName,
  1762  			}
  1763  			m["message"] = ToFormattedMessage(bot.ConvertContentMessage(channelMsgByDB.Content, message.SourceGuildChannel, false), source)
  1764  		}
  1765  	case message.SourceGuildDirect:
  1766  		// todo(mrs4s): 支持 direct 消息
  1767  		m["tiny_id"] = fU64(uint64(source.SecondaryID))
  1768  	}
  1769  	return OK(m)
  1770  }
  1771  
  1772  // CQGetGroupSystemMessages 扩展API-获取群文件系统消息
  1773  //
  1774  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E7%BE%A4%E7%B3%BB%E7%BB%9F%E6%B6%88%E6%81%AF
  1775  // @route(get_group_system_msg)
  1776  func (bot *CQBot) CQGetGroupSystemMessages() global.MSG {
  1777  	msg, err := bot.Client.GetGroupSystemMessages()
  1778  	if err != nil {
  1779  		log.Warnf("获取群系统消息失败: %v", err)
  1780  		return Failed(100, "SYSTEM_MSG_API_ERROR", err.Error())
  1781  	}
  1782  	return OK(msg)
  1783  }
  1784  
  1785  // CQGetGroupMessageHistory 获取群消息历史记录
  1786  //
  1787  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%B6%88%E6%81%AF%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95
  1788  // @route(get_group_msg_history)
  1789  // @rename(seq->message_seq)
  1790  func (bot *CQBot) CQGetGroupMessageHistory(groupID int64, seq int64) global.MSG {
  1791  	if g := bot.Client.FindGroup(groupID); g == nil {
  1792  		return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1793  	}
  1794  	if seq == 0 {
  1795  		g, err := bot.Client.GetGroupInfo(groupID)
  1796  		if err != nil {
  1797  			return Failed(100, "GROUP_INFO_API_ERROR", err.Error())
  1798  		}
  1799  		seq = g.LastMsgSeq
  1800  	}
  1801  	msg, err := bot.Client.GetGroupMessages(groupID, int64(math.Max(float64(seq-19), 1)), seq)
  1802  	if err != nil {
  1803  		log.Warnf("获取群历史消息失败: %v", err)
  1804  		return Failed(100, "MESSAGES_API_ERROR", err.Error())
  1805  	}
  1806  	source := message.Source{
  1807  		SourceType: message.SourcePrivate,
  1808  		PrimaryID:  0,
  1809  	}
  1810  	ms := make([]*event, 0, len(msg))
  1811  	for _, m := range msg {
  1812  		bot.checkMedia(m.Elements, groupID)
  1813  		id := bot.InsertGroupMessage(m, source)
  1814  		t := bot.formatGroupMessage(m)
  1815  		t.Others["message_id"] = id
  1816  		ms = append(ms, t)
  1817  	}
  1818  	return OK(global.MSG{
  1819  		"messages": ms,
  1820  	})
  1821  }
  1822  
  1823  // CQGetOnlineClients 扩展API-获取当前账号在线客户端列表
  1824  //
  1825  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E5%BD%93%E5%89%8D%E8%B4%A6%E5%8F%B7%E5%9C%A8%E7%BA%BF%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%88%97%E8%A1%A8
  1826  // @route(get_online_clients)
  1827  func (bot *CQBot) CQGetOnlineClients(noCache bool) global.MSG {
  1828  	if noCache {
  1829  		if err := bot.Client.RefreshStatus(); err != nil {
  1830  			log.Warnf("刷新客户端状态时出现问题 %v", err)
  1831  			return Failed(100, "REFRESH_STATUS_ERROR", err.Error())
  1832  		}
  1833  	}
  1834  	d := make([]global.MSG, 0, len(bot.Client.OnlineClients))
  1835  	for _, oc := range bot.Client.OnlineClients {
  1836  		d = append(d, global.MSG{
  1837  			"app_id":      oc.AppId,
  1838  			"device_name": oc.DeviceName,
  1839  			"device_kind": oc.DeviceKind,
  1840  		})
  1841  	}
  1842  	return OK(global.MSG{
  1843  		"clients": d,
  1844  	})
  1845  }
  1846  
  1847  // CQCanSendImage 检查是否可以发送图片(此处永远返回true)
  1848  //
  1849  // https://git.io/Jtz1N
  1850  // @route11(can_send_image)
  1851  func (bot *CQBot) CQCanSendImage() global.MSG {
  1852  	return OK(global.MSG{"yes": true})
  1853  }
  1854  
  1855  // CQCanSendRecord 检查是否可以发送语音(此处永远返回true)
  1856  //
  1857  // https://git.io/Jtz1x
  1858  // @route11(can_send_record)
  1859  func (bot *CQBot) CQCanSendRecord() global.MSG {
  1860  	return OK(global.MSG{"yes": true})
  1861  }
  1862  
  1863  // CQOcrImage 扩展API-图片OCR
  1864  //
  1865  // https://docs.go-cqhttp.org/api/#%E5%9B%BE%E7%89%87-ocr
  1866  // @route(ocr_image,".ocr_image")
  1867  // @rename(image_id->image)
  1868  func (bot *CQBot) CQOcrImage(imageID string) global.MSG {
  1869  	// TODO: fix this
  1870  	var elem msg.Element
  1871  	elem.Type = "image"
  1872  	elem.Data = []msg.Pair{{K: "file", V: imageID}}
  1873  	img, err := bot.makeImageOrVideoElem(elem, false, message.SourceGroup)
  1874  	if err != nil {
  1875  		log.Warnf("load image error: %v", err)
  1876  		return Failed(100, "LOAD_FILE_ERROR", err.Error())
  1877  	}
  1878  	rsp, err := bot.Client.ImageOcr(img)
  1879  	if err != nil {
  1880  		log.Warnf("ocr image error: %v", err)
  1881  		return Failed(100, "OCR_API_ERROR", err.Error())
  1882  	}
  1883  	return OK(rsp)
  1884  }
  1885  
  1886  // CQSetGroupPortrait 扩展API-设置群头像
  1887  //
  1888  // https://docs.go-cqhttp.org/api/#%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%A4%B4%E5%83%8F
  1889  // @route(set_group_portrait)
  1890  func (bot *CQBot) CQSetGroupPortrait(groupID int64, file, cache string) global.MSG {
  1891  	if g := bot.Client.FindGroup(groupID); g != nil {
  1892  		img, err := global.FindFile(file, cache, global.ImagePath)
  1893  		if err != nil {
  1894  			log.Warnf("set group portrait error: %v", err)
  1895  			return Failed(100, "LOAD_FILE_ERROR", err.Error())
  1896  		}
  1897  		g.UpdateGroupHeadPortrait(img)
  1898  		return OK(nil)
  1899  	}
  1900  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1901  }
  1902  
  1903  // CQSetGroupAnonymousBan 群组匿名用户禁言
  1904  //
  1905  // https://git.io/Jtz1p
  1906  // @route(set_group_anonymous_ban)
  1907  // @rename(flag->"[anonymous_flag\x2Canonymous.flag].0")
  1908  func (bot *CQBot) CQSetGroupAnonymousBan(groupID int64, flag string, duration int32) global.MSG {
  1909  	if flag == "" {
  1910  		return Failed(100, "INVALID_FLAG", "无效的flag")
  1911  	}
  1912  	if g := bot.Client.FindGroup(groupID); g != nil {
  1913  		id, nick, ok := strings.Cut(flag, "|")
  1914  		if !ok {
  1915  			return Failed(100, "INVALID_FLAG", "无效的flag")
  1916  		}
  1917  		if err := g.MuteAnonymous(id, nick, duration); err != nil {
  1918  			log.Warnf("anonymous ban error: %v", err)
  1919  			return Failed(100, "CALL_API_ERROR", err.Error())
  1920  		}
  1921  		return OK(nil)
  1922  	}
  1923  	return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1924  }
  1925  
  1926  // CQGetStatus 获取运行状态
  1927  //
  1928  // https://git.io/JtzMe
  1929  // @route(get_status)
  1930  func (bot *CQBot) CQGetStatus(spec *onebot.Spec) global.MSG {
  1931  	if spec.Version == 11 {
  1932  		return OK(global.MSG{
  1933  			"app_initialized": true,
  1934  			"app_enabled":     true,
  1935  			"plugins_good":    nil,
  1936  			"app_good":        true,
  1937  			"online":          bot.Client.Online.Load(),
  1938  			"good":            bot.Client.Online.Load(),
  1939  			"stat":            bot.Client.GetStatistics(),
  1940  		})
  1941  	}
  1942  	return OK(global.MSG{
  1943  		"online": bot.Client.Online.Load(),
  1944  		"good":   bot.Client.Online.Load(),
  1945  		"stat":   bot.Client.GetStatistics(),
  1946  	})
  1947  }
  1948  
  1949  // CQSetEssenceMessage 扩展API-设置精华消息
  1950  //
  1951  // https://docs.go-cqhttp.org/api/#%E8%AE%BE%E7%BD%AE%E7%B2%BE%E5%8D%8E%E6%B6%88%E6%81%AF
  1952  // @route(set_essence_msg)
  1953  func (bot *CQBot) CQSetEssenceMessage(messageID int32) global.MSG {
  1954  	msg, err := db.GetGroupMessageByGlobalID(messageID)
  1955  	if err != nil {
  1956  		return Failed(100, "MESSAGE_NOT_FOUND", "消息不存在")
  1957  	}
  1958  	if err := bot.Client.SetEssenceMessage(msg.GroupCode, msg.Attribute.MessageSeq, msg.Attribute.InternalID); err != nil {
  1959  		log.Warnf("设置精华消息 %v 失败: %v", messageID, err)
  1960  		return Failed(100, "SET_ESSENCE_MSG_ERROR", err.Error())
  1961  	}
  1962  	return OK(nil)
  1963  }
  1964  
  1965  // CQDeleteEssenceMessage 扩展API-移出精华消息
  1966  //
  1967  // https://docs.go-cqhttp.org/api/#%E7%A7%BB%E5%87%BA%E7%B2%BE%E5%8D%8E%E6%B6%88%E6%81%AF
  1968  // @route(delete_essence_msg)
  1969  func (bot *CQBot) CQDeleteEssenceMessage(messageID int32) global.MSG {
  1970  	msg, err := db.GetGroupMessageByGlobalID(messageID)
  1971  	if err != nil {
  1972  		return Failed(100, "MESSAGE_NOT_FOUND", "消息不存在")
  1973  	}
  1974  	if err := bot.Client.DeleteEssenceMessage(msg.GroupCode, msg.Attribute.MessageSeq, msg.Attribute.InternalID); err != nil {
  1975  		log.Warnf("删除精华消息 %v 失败: %v", messageID, err)
  1976  		return Failed(100, "SET_ESSENCE_MSG_ERROR", err.Error())
  1977  	}
  1978  	return OK(nil)
  1979  }
  1980  
  1981  // CQGetEssenceMessageList 扩展API-获取精华消息列表
  1982  //
  1983  // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E7%B2%BE%E5%8D%8E%E6%B6%88%E6%81%AF%E5%88%97%E8%A1%A8
  1984  // @route(get_essence_msg_list)
  1985  func (bot *CQBot) CQGetEssenceMessageList(groupID int64) global.MSG {
  1986  	g := bot.Client.FindGroup(groupID)
  1987  	if g == nil {
  1988  		return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
  1989  	}
  1990  	msgList, err := bot.Client.GetGroupEssenceMsgList(groupID)
  1991  	if err != nil {
  1992  		return Failed(100, "GET_ESSENCE_LIST_FOUND", err.Error())
  1993  	}
  1994  	list := make([]global.MSG, 0, len(msgList))
  1995  	for _, m := range msgList {
  1996  		msg := global.MSG{
  1997  			"sender_nick":   m.SenderNick,
  1998  			"sender_time":   m.SenderTime,
  1999  			"operator_time": m.AddDigestTime,
  2000  			"operator_nick": m.AddDigestNick,
  2001  			"sender_id":     m.SenderUin,
  2002  			"operator_id":   m.AddDigestUin,
  2003  		}
  2004  		msg["message_id"] = db.ToGlobalID(groupID, int32(m.MessageID))
  2005  		list = append(list, msg)
  2006  	}
  2007  	return OK(list)
  2008  }
  2009  
  2010  // CQCheckURLSafely 扩展API-检查链接安全性
  2011  //
  2012  // https://docs.go-cqhttp.org/api/#%E6%A3%80%E6%9F%A5%E9%93%BE%E6%8E%A5%E5%AE%89%E5%85%A8%E6%80%A7
  2013  // @route(check_url_safely)
  2014  func (bot *CQBot) CQCheckURLSafely(url string) global.MSG {
  2015  	return OK(global.MSG{
  2016  		"level": bot.Client.CheckUrlSafely(url),
  2017  	})
  2018  }
  2019  
  2020  // CQGetVersionInfo 获取版本信息
  2021  //
  2022  // https://git.io/JtwUs
  2023  // @route11(get_version_info)
  2024  func (bot *CQBot) CQGetVersionInfo() global.MSG {
  2025  	wd, _ := os.Getwd()
  2026  	return OK(global.MSG{
  2027  		"app_name":                   "go-cqhttp",
  2028  		"app_version":                base.Version,
  2029  		"app_full_name":              fmt.Sprintf("go-cqhttp-%s_%s_%s-%s", base.Version, runtime.GOOS, runtime.GOARCH, runtime.Version()),
  2030  		"protocol_version":           "v11",
  2031  		"coolq_directory":            wd,
  2032  		"coolq_edition":              "pro",
  2033  		"go_cqhttp":                  true,
  2034  		"plugin_version":             "4.15.0",
  2035  		"plugin_build_number":        99,
  2036  		"plugin_build_configuration": "release",
  2037  		"runtime_version":            runtime.Version(),
  2038  		"runtime_os":                 runtime.GOOS,
  2039  		"version":                    base.Version,
  2040  		"protocol_name":              bot.Client.Device().Protocol,
  2041  	})
  2042  }
  2043  
  2044  // CQGetModelShow 获取在线机型
  2045  //
  2046  // https://club.vip.qq.com/onlinestatus/set
  2047  // @route(_get_model_show)
  2048  func (bot *CQBot) CQGetModelShow(model string) global.MSG {
  2049  	variants, err := bot.Client.GetModelShow(model)
  2050  	if err != nil {
  2051  		return Failed(100, "GET_MODEL_SHOW_API_ERROR", "无法获取在线机型")
  2052  	}
  2053  	a := make([]global.MSG, 0, len(variants))
  2054  	for _, v := range variants {
  2055  		a = append(a, global.MSG{
  2056  			"model_show": v.ModelShow,
  2057  			"need_pay":   v.NeedPay,
  2058  		})
  2059  	}
  2060  	return OK(global.MSG{
  2061  		"variants": a,
  2062  	})
  2063  }
  2064  
  2065  // CQSendGroupSign 群打卡
  2066  //
  2067  // https://club.vip.qq.com/onlinestatus/set
  2068  // @route(send_group_sign)
  2069  func (bot *CQBot) CQSendGroupSign(groupID int64) global.MSG {
  2070  	bot.Client.SendGroupSign(groupID)
  2071  	return OK(nil)
  2072  }
  2073  
  2074  // CQSetModelShow 设置在线机型
  2075  //
  2076  // https://club.vip.qq.com/onlinestatus/set
  2077  // @route(_set_model_show)
  2078  func (bot *CQBot) CQSetModelShow(model, modelShow string) global.MSG {
  2079  	err := bot.Client.SetModelShow(model, modelShow)
  2080  	if err != nil {
  2081  		return Failed(100, "SET_MODEL_SHOW_API_ERROR", "无法设置在线机型")
  2082  	}
  2083  	return OK(nil)
  2084  }
  2085  
  2086  // CQMarkMessageAsRead 标记消息已读
  2087  // @route(mark_msg_as_read)
  2088  // @rename(msg_id->message_id)
  2089  func (bot *CQBot) CQMarkMessageAsRead(msgID int32) global.MSG {
  2090  	m, err := db.GetMessageByGlobalID(msgID)
  2091  	if err != nil {
  2092  		return Failed(100, "MSG_NOT_FOUND", "消息不存在")
  2093  	}
  2094  	switch o := m.(type) {
  2095  	case *db.StoredGroupMessage:
  2096  		bot.Client.MarkGroupMessageReaded(o.GroupCode, int64(o.Attribute.MessageSeq))
  2097  		return OK(nil)
  2098  	case *db.StoredPrivateMessage:
  2099  		bot.Client.MarkPrivateMessageReaded(o.SessionUin, o.Attribute.Timestamp)
  2100  	}
  2101  	return OK(nil)
  2102  }
  2103  
  2104  // CQSetQQProfile 设置 QQ 资料
  2105  //
  2106  // @route(set_qq_profile)
  2107  func (bot *CQBot) CQSetQQProfile(nickname, company, email, college, personalNote gjson.Result) global.MSG {
  2108  	u := client.NewProfileDetailUpdate()
  2109  
  2110  	fi := func(f gjson.Result, do func(value string) client.ProfileDetailUpdate) {
  2111  		if f.Exists() {
  2112  			do(f.String())
  2113  		}
  2114  	}
  2115  
  2116  	fi(nickname, u.Nick)
  2117  	fi(company, u.Company)
  2118  	fi(email, u.Email)
  2119  	fi(college, u.College)
  2120  	fi(personalNote, u.PersonalNote)
  2121  	bot.Client.UpdateProfile(u)
  2122  	return OK(nil)
  2123  }
  2124  
  2125  // CQReloadEventFilter 重载事件过滤器
  2126  //
  2127  // @route(reload_event_filter)
  2128  func (bot *CQBot) CQReloadEventFilter(file string) global.MSG {
  2129  	filter.Add(file)
  2130  	return OK(nil)
  2131  }
  2132  
  2133  // CQGetSupportedActions 获取支持的动作列表
  2134  //
  2135  // @route(get_supported_actions)
  2136  func (bot *CQBot) CQGetSupportedActions(spec *onebot.Spec) global.MSG {
  2137  	return OK(spec.SupportedActions)
  2138  }
  2139  
  2140  // OK 生成成功返回值
  2141  func OK(data any) global.MSG {
  2142  	return global.MSG{"data": data, "retcode": 0, "status": "ok", "message": ""}
  2143  }
  2144  
  2145  // Failed 生成失败返回值
  2146  func Failed(code int, msg ...string) global.MSG {
  2147  	m, w := "", ""
  2148  	if len(msg) > 0 {
  2149  		m = msg[0]
  2150  	}
  2151  	if len(msg) > 1 {
  2152  		w = msg[1]
  2153  	}
  2154  	return global.MSG{"data": nil, "retcode": code, "msg": m, "wording": w, "message": w, "status": "failed"}
  2155  }
  2156  
  2157  func limitedString(str string) string {
  2158  	limited := [14]rune{10: ' ', 11: '.', 12: '.', 13: '.'}
  2159  	i := 0
  2160  	for _, r := range str {
  2161  		if i >= 10 {
  2162  			break
  2163  		}
  2164  		limited[i] = r
  2165  		i++
  2166  	}
  2167  	if i != 10 {
  2168  		return str
  2169  	}
  2170  	return string(limited[:])
  2171  }