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 }