package coolq import ( "crypto/md5" "encoding/hex" "fmt" "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/MiraiGo/message" "github.com/tidwall/gjson" "io/ioutil" "math" "os" "path" "path/filepath" "runtime" "strconv" "strings" "time" "github.com/Mrs4s/go-cqhttp/global" log "github.com/sirupsen/logrus" ) // Version go-cqhttp的版本信息,在编译时使用ldfalgs进行覆盖 var Version = "unknown" // CQGetLoginInfo 获取登录号信息 // // https://git.io/Jtz1I func (bot *CQBot) CQGetLoginInfo() MSG { return OK(MSG{"user_id": bot.Client.Uin, "nickname": bot.Client.Nickname}) } // CQGetFriendList 获取好友列表 // // https://git.io/Jtz1L func (bot *CQBot) CQGetFriendList() MSG { fs := make([]MSG, 0) for _, f := range bot.Client.FriendList { fs = append(fs, MSG{ "nickname": f.Nickname, "remark": f.Remark, "user_id": f.Uin, }) } return OK(fs) } // CQGetGroupList 获取群列表 // // https://git.io/Jtz1t func (bot *CQBot) CQGetGroupList(noCache bool) MSG { gs := make([]MSG, 0) if noCache { _ = bot.Client.ReloadGroupList() } for _, g := range bot.Client.GroupList { gs = append(gs, MSG{ "group_id": g.Code, "group_name": g.Name, "max_member_count": g.MaxMemberCount, "member_count": g.MemberCount, }) } return OK(gs) } // CQGetGroupInfo 获取群信息 // // https://git.io/Jtz1O func (bot *CQBot) CQGetGroupInfo(groupID int64, noCache bool) MSG { group := bot.Client.FindGroup(groupID) if group == nil { gid := strconv.FormatInt(groupID, 10) info, err := bot.Client.SearchGroupByKeyword(gid) if err != nil { return Failed(100, "GROUP_SEARCH_ERROR", "群聊搜索失败") } for _, g := range info { if g.Code == groupID { return OK(MSG{ "group_id": g.Code, "group_name": g.Name, "max_member_count": 0, "member_count": 0, }) } } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在失败") } if noCache { var err error group, err = bot.Client.GetGroupInfo(groupID) if err != nil { return Failed(100, "GET_GROUP_INFO_API_ERROR", err.Error()) } } return OK(MSG{ "group_id": group.Code, "group_name": group.Name, "max_member_count": group.MaxMemberCount, "member_count": group.MemberCount, }) } // CQGetGroupMemberList 获取群成员列表 // // https://git.io/Jtz13 func (bot *CQBot) CQGetGroupMemberList(groupID int64, noCache bool) MSG { group := bot.Client.FindGroup(groupID) if group == nil { return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } if noCache { t, err := bot.Client.GetGroupMembers(group) if err != nil { log.Warnf("刷新群 %v 成员列表失败: %v", groupID, err) return Failed(100, "GET_MEMBERS_API_ERROR", err.Error()) } group.Members = t } members := make([]MSG, 0) for _, m := range group.Members { members = append(members, convertGroupMemberInfo(groupID, m)) } return OK(members) } // CQGetGroupMemberInfo 获取群成员信息 // // https://git.io/Jtz1s func (bot *CQBot) CQGetGroupMemberInfo(groupID, userID int64) MSG { group := bot.Client.FindGroup(groupID) if group == nil { return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } member := group.FindMember(userID) if member == nil { return Failed(100, "MEMBER_NOT_FOUND", "群员不存在") } return OK(convertGroupMemberInfo(groupID, member)) } // CQGetGroupFileSystemInfo 扩展API-获取群文件系统信息 // // 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 func (bot *CQBot) CQGetGroupFileSystemInfo(groupID int64) MSG { fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } return OK(fs) } // CQGetGroupRootFiles 扩展API-获取群根目录文件列表 // // 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 func (bot *CQBot) CQGetGroupRootFiles(groupID int64) MSG { fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } files, folders, err := fs.Root() if err != nil { log.Errorf("获取群 %v 根目录文件失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } return OK(MSG{ "files": files, "folders": folders, }) } // CQGetGroupFilesByFolderID 扩展API-获取群子目录文件列表 // // 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 func (bot *CQBot) CQGetGroupFilesByFolderID(groupID int64, folderID string) MSG { fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } files, folders, err := fs.GetFilesByFolder(folderID) if err != nil { log.Errorf("获取群 %v 根目录 %v 子文件失败: %v", groupID, folderID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } return OK(MSG{ "files": files, "folders": folders, }) } // CQGetGroupFileURL 扩展API-获取群文件资源链接 // // 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 func (bot *CQBot) CQGetGroupFileURL(groupID int64, fileID string, busID int32) MSG { url := bot.Client.GetGroupFileUrl(groupID, fileID, busID) if url == "" { return Failed(100, "FILE_SYSTEM_API_ERROR") } return OK(MSG{ "url": url, }) } // CQUploadGroupFile 扩展API-上传群文件 // // https://docs.go-cqhttp.org/api/#%E4%B8%8A%E4%BC%A0%E7%BE%A4%E6%96%87%E4%BB%B6 func (bot *CQBot) CQUploadGroupFile(groupID int64, file, name, folder string) MSG { if !global.PathExists(file) { log.Errorf("上传群文件 %v 失败: 文件不存在", file) return Failed(100, "FILE_NOT_FOUND", "文件不存在") } fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } if folder == "" { folder = "/" } if err = fs.UploadFile(file, name, folder); err != nil { log.Errorf("上传群 %v 文件 %v 失败: %v", groupID, file, err) return Failed(100, "FILE_SYSTEM_UPLOAD_API_ERROR", err.Error()) } return OK(nil) } // CQGetWordSlices 隐藏API-获取中文分词 // // 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 func (bot *CQBot) CQGetWordSlices(content string) MSG { slices, err := bot.Client.GetWordSegmentation(content) if err != nil { return Failed(100, "WORD_SEGMENTATION_API_ERROR", err.Error()) } for i := 0; i < len(slices); i++ { slices[i] = strings.ReplaceAll(slices[i], "\u0000", "") } return OK(MSG{"slices": slices}) } // CQSendGroupMessage 发送群消息 // // https://git.io/Jtz1c func (bot *CQBot) CQSendGroupMessage(groupID int64, i interface{}, autoEscape bool) MSG { var str string fixAt := func(elem []message.IMessageElement) { for _, e := range elem { if at, ok := e.(*message.AtElement); ok && at.Target != 0 { at.Display = "@" + func() string { mem := bot.Client.FindGroup(groupID).FindMember(at.Target) if mem != nil { return mem.DisplayName() } return strconv.FormatInt(at.Target, 10) }() } } } if m, ok := i.(gjson.Result); ok { if m.Type == gjson.JSON { elem := bot.ConvertObjectMessage(m, true) fixAt(elem) mid := bot.SendGroupMessage(groupID, &message.SendingMessage{Elements: elem}) if mid == -1 { return Failed(100, "SEND_MSG_API_ERROR", "请参考输出") } log.Infof("发送群 %v(%v) 的消息: %v (%v)", groupID, groupID, limitedString(m.String()), mid) return OK(MSG{"message_id": mid}) } str = func() string { if m.Str != "" { return m.Str } return m.Raw }() } else if s, ok := i.(string); ok { str = s } if str == "" { log.Warnf("群消息发送失败: 信息为空. MSG: %v", i) return Failed(100, "EMPTY_MSG_ERROR", "消息为空") } var elem []message.IMessageElement if autoEscape { elem = append(elem, message.NewText(str)) } else { elem = bot.ConvertStringMessage(str, true) } fixAt(elem) mid := bot.SendGroupMessage(groupID, &message.SendingMessage{Elements: elem}) if mid == -1 { return Failed(100, "SEND_MSG_API_ERROR", "请参考输出") } log.Infof("发送群 %v(%v) 的消息: %v (%v)", groupID, groupID, limitedString(str), mid) return OK(MSG{"message_id": mid}) } // CQSendGroupForwardMessage 扩展API-发送合并转发(群) // // 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 func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) MSG { if m.Type != gjson.JSON { return Failed(100) } var sendNodes []*message.ForwardNode ts := time.Now().Add(-time.Minute * 5) hasCustom := func() bool { for _, item := range m.Array() { if item.Get("data.uin").Exists() { return true } } return false }() var convert func(e gjson.Result) []*message.ForwardNode convert = func(e gjson.Result) (nodes []*message.ForwardNode) { if e.Get("type").Str != "node" { return nil } ts.Add(time.Second) if e.Get("data.id").Exists() { i, _ := strconv.Atoi(e.Get("data.id").String()) m := bot.GetMessage(int32(i)) if m != nil { sender := m["sender"].(message.Sender) nodes = append(nodes, &message.ForwardNode{ SenderId: sender.Uin, SenderName: (&sender).DisplayName(), Time: func() int32 { msgTime := m["time"].(int32) if hasCustom && msgTime == 0 { return int32(ts.Unix()) } return msgTime }(), Message: bot.ConvertStringMessage(m["message"].(string), true), }) return } log.Warnf("警告: 引用消息 %v 错误或数据库未开启.", e.Get("data.id").Str) return } uin, _ := strconv.ParseInt(e.Get("data.uin").Str, 10, 64) msgTime, err := strconv.ParseInt(e.Get("data.time").Str, 10, 64) if err != nil { msgTime = ts.Unix() } name := e.Get("data.name").Str c := e.Get("data.content") if c.IsArray() { flag := false c.ForEach(func(_, value gjson.Result) bool { if value.Get("type").String() == "node" { flag = true return false } return true }) if flag { var taowa []*message.ForwardNode for _, item := range c.Array() { taowa = append(taowa, convert(item)...) } nodes = append(nodes, &message.ForwardNode{ SenderId: uin, SenderName: name, Time: int32(msgTime), Message: []message.IMessageElement{bot.Client.UploadGroupForwardMessage(groupID, &message.ForwardMessage{Nodes: taowa})}, }) return } } content := bot.ConvertObjectMessage(e.Get("data.content"), true) if uin != 0 && name != "" && len(content) > 0 { var newElem []message.IMessageElement for _, elem := range content { if img, ok := elem.(*LocalImageElement); ok { gm, err := bot.UploadLocalImageAsGroup(groupID, img) if err != nil { log.Warnf("警告:群 %v 图片上传失败: %v", groupID, err) continue } newElem = append(newElem, gm) continue } if video, ok := elem.(*LocalVideoElement); ok { gm, err := bot.UploadLocalVideo(groupID, video) if err != nil { log.Warnf("警告:群 %v 视频上传失败: %v", groupID, err) continue } newElem = append(newElem, gm) continue } newElem = append(newElem, elem) } nodes = append(nodes, &message.ForwardNode{ SenderId: uin, SenderName: name, Time: int32(msgTime), Message: newElem, }) return } log.Warnf("警告: 非法 Forward node 将跳过") return } if m.IsArray() { for _, item := range m.Array() { sendNodes = append(sendNodes, convert(item)...) } } else { sendNodes = convert(m) } if len(sendNodes) > 0 { gm := bot.Client.SendGroupForwardMessage(groupID, &message.ForwardMessage{Nodes: sendNodes}) return OK(MSG{ "message_id": bot.InsertGroupMessage(gm), }) } return Failed(100) } // CQSendPrivateMessage 发送私聊消息 // // https://git.io/Jtz1l func (bot *CQBot) CQSendPrivateMessage(userID int64, i interface{}, autoEscape bool) MSG { var str string if m, ok := i.(gjson.Result); ok { if m.Type == gjson.JSON { elem := bot.ConvertObjectMessage(m, false) mid := bot.SendPrivateMessage(userID, &message.SendingMessage{Elements: elem}) if mid == -1 { return Failed(100, "SEND_MSG_API_ERROR", "请参考输出") } log.Infof("发送好友 %v(%v) 的消息: %v (%v)", userID, userID, limitedString(m.String()), mid) return OK(MSG{"message_id": mid}) } str = func() string { if m.Str != "" { return m.Str } return m.Raw }() } else if s, ok := i.(string); ok { str = s } if str == "" { return Failed(100, "EMPTY_MSG_ERROR", "消息为空") } var elem []message.IMessageElement if autoEscape { elem = append(elem, message.NewText(str)) } else { elem = bot.ConvertStringMessage(str, false) } mid := bot.SendPrivateMessage(userID, &message.SendingMessage{Elements: elem}) if mid == -1 { return Failed(100, "SEND_MSG_API_ERROR", "请参考输出") } log.Infof("发送好友 %v(%v) 的消息: %v (%v)", userID, userID, limitedString(str), mid) return OK(MSG{"message_id": mid}) } // CQSetGroupCard 设置群名片(群备注) // // https://git.io/Jtz1B func (bot *CQBot) CQSetGroupCard(groupID, userID int64, card string) MSG { if g := bot.Client.FindGroup(groupID); g != nil { if m := g.FindMember(userID); m != nil { m.EditCard(card) return OK(nil) } } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQSetGroupSpecialTitle 设置群组专属头衔 // // https://git.io/Jtz10 func (bot *CQBot) CQSetGroupSpecialTitle(groupID, userID int64, title string) MSG { if g := bot.Client.FindGroup(groupID); g != nil { if m := g.FindMember(userID); m != nil { m.EditSpecialTitle(title) return OK(nil) } } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQSetGroupName 设置群名 // // https://git.io/Jtz12 func (bot *CQBot) CQSetGroupName(groupID int64, name string) MSG { if g := bot.Client.FindGroup(groupID); g != nil { g.UpdateName(name) return OK(nil) } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQSetGroupMemo 扩展API-发送群公告 // // https://docs.go-cqhttp.org/api/#%E5%8F%91%E9%80%81%E7%BE%A4%E5%85%AC%E5%91%8A func (bot *CQBot) CQSetGroupMemo(groupID int64, msg string) MSG { if g := bot.Client.FindGroup(groupID); g != nil { g.UpdateMemo(msg) return OK(nil) } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQSetGroupKick 群组踢人 // // https://git.io/Jtz1V func (bot *CQBot) CQSetGroupKick(groupID, userID int64, msg string, block bool) MSG { if g := bot.Client.FindGroup(groupID); g != nil { if m := g.FindMember(userID); m != nil { err := m.Kick(msg, block) if err != nil { return Failed(100, "NOT_MANAGEABLE", "机器人权限不足") } return OK(nil) } } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQSetGroupBan 群组单人禁言 // // https://git.io/Jtz1w func (bot *CQBot) CQSetGroupBan(groupID, userID int64, duration uint32) MSG { if g := bot.Client.FindGroup(groupID); g != nil { if m := g.FindMember(userID); m != nil { err := m.Mute(duration) if err != nil { if duration >= 2592000 { return Failed(100, "DURATION_IS_NOT_IN_RANGE", "非法的禁言时长") } return Failed(100, "NOT_MANAGEABLE", "机器人权限不足") } return OK(nil) } } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQSetGroupWholeBan 群组全员禁言 // // https://git.io/Jtz1o func (bot *CQBot) CQSetGroupWholeBan(groupID int64, enable bool) MSG { if g := bot.Client.FindGroup(groupID); g != nil { g.MuteAll(enable) return OK(nil) } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQSetGroupLeave 退出群组 // // https://git.io/Jtz1K func (bot *CQBot) CQSetGroupLeave(groupID int64) MSG { if g := bot.Client.FindGroup(groupID); g != nil { g.Quit() return OK(nil) } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQGetAtAllRemain 扩展API-获取群 @全体成员 剩余次数 // // 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 func (bot *CQBot) CQGetAtAllRemain(groupID int64) MSG { if g := bot.Client.FindGroup(groupID); g != nil { i, err := bot.Client.GetAtAllRemain(groupID) if err != nil { return Failed(100, "GROUP_REMAIN_API_ERROR", err.Error()) } return OK(i) } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQProcessFriendRequest 处理加好友请求 // // https://git.io/Jtz11 func (bot *CQBot) CQProcessFriendRequest(flag string, approve bool) MSG { req, ok := bot.friendReqCache.Load(flag) if !ok { return Failed(100, "FLAG_NOT_FOUND", "FLAG不存在") } if approve { req.(*client.NewFriendRequest).Accept() } else { req.(*client.NewFriendRequest).Reject() } return OK(nil) } // CQProcessGroupRequest 处理加群请求/邀请 // // https://git.io/Jtz1D func (bot *CQBot) CQProcessGroupRequest(flag, subType, reason string, approve bool) MSG { msgs, err := bot.Client.GetGroupSystemMessages() if err != nil { log.Errorf("获取群系统消息失败: %v", err) return Failed(100, "SYSTEM_MSG_API_ERROR", err.Error()) } if subType == "add" { for _, req := range msgs.JoinRequests { if strconv.FormatInt(req.RequestId, 10) == flag { if req.Checked { log.Errorf("处理群系统消息失败: 无法操作已处理的消息.") return Failed(100, "FLAG_HAS_BEEN_CHECKED", "消息已被处理") } if approve { req.Accept() } else { req.Reject(false, reason) } return OK(nil) } } } else { for _, req := range msgs.InvitedRequests { if strconv.FormatInt(req.RequestId, 10) == flag { if req.Checked { log.Errorf("处理群系统消息失败: 无法操作已处理的消息.") return Failed(100, "FLAG_HAS_BEEN_CHECKED", "消息已被处理") } if approve { req.Accept() } else { req.Reject(false, reason) } return OK(nil) } } } log.Errorf("处理群系统消息失败: 消息 %v 不存在.", flag) return Failed(100, "FLAG_NOT_FOUND", "FLAG不存在") } // CQDeleteMessage 撤回消息 // // https:// git.io/Jtz1y func (bot *CQBot) CQDeleteMessage(messageID int32) MSG { msg := bot.GetMessage(messageID) if msg == nil { return Failed(100, "MESSAGE_NOT_FOUND", "消息不存在") } if _, ok := msg["group"]; ok { if err := bot.Client.RecallGroupMessage(msg["group"].(int64), msg["message-id"].(int32), msg["internal-id"].(int32)); err != nil { log.Warnf("撤回 %v 失败: %v", messageID, err) return Failed(100, "RECALL_API_ERROR", err.Error()) } } else { if msg["sender"].(message.Sender).Uin != bot.Client.Uin { log.Warnf("撤回 %v 失败: 好友会话无法撤回对方消息.", messageID) return Failed(100, "CANNOT_RECALL_FRIEND_MSG", "无法撤回对方消息") } if err := bot.Client.RecallPrivateMessage(msg["target"].(int64), int64(msg["time"].(int32)), msg["message-id"].(int32), msg["internal-id"].(int32)); err != nil { log.Warnf("撤回 %v 失败: %v", messageID, err) return Failed(100, "RECALL_API_ERROR", err.Error()) } } return OK(nil) } // CQSetGroupAdmin 群组设置管理员 // // https://git.io/Jtz1S func (bot *CQBot) CQSetGroupAdmin(groupID, userID int64, enable bool) MSG { group := bot.Client.FindGroup(groupID) if group == nil || group.OwnerUin != bot.Client.Uin { return Failed(100, "PERMISSION_DENIED", "群不存在或权限不足") } mem := group.FindMember(userID) if mem == nil { return Failed(100, "GROUP_MEMBER_NOT_FOUND", "群成员不存在") } mem.SetAdmin(enable) t, err := bot.Client.GetGroupMembers(group) if err != nil { log.Warnf("刷新群 %v 成员列表失败: %v", groupID, err) return Failed(100, "GET_MEMBERS_API_ERROR", err.Error()) } group.Members = t return OK(nil) } // CQGetVipInfo 扩展API-获取VIP信息 // // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96vip%E4%BF%A1%E6%81%AF func (bot *CQBot) CQGetVipInfo(userID int64) MSG { vip, err := bot.Client.GetVipInfo(userID) if err != nil { return Failed(100, "VIP_API_ERROR", err.Error()) } msg := MSG{ "user_id": vip.Uin, "nickname": vip.Name, "level": vip.Level, "level_speed": vip.LevelSpeed, "vip_level": vip.VipLevel, "vip_growth_speed": vip.VipGrowthSpeed, "vip_growth_total": vip.VipGrowthTotal, } return OK(msg) } // CQGetGroupHonorInfo 获取群荣誉信息 // // https://git.io/Jtz1H func (bot *CQBot) CQGetGroupHonorInfo(groupID int64, t string) MSG { msg := MSG{"group_id": groupID} convertMem := func(memList []client.HonorMemberInfo) (ret []MSG) { for _, mem := range memList { ret = append(ret, MSG{ "user_id": mem.Uin, "nickname": mem.Name, "avatar": mem.Avatar, "description": mem.Desc, }) } return } if t == "talkative" || t == "all" { if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Talkative); err == nil { if honor.CurrentTalkative.Uin != 0 { msg["current_talkative"] = MSG{ "user_id": honor.CurrentTalkative.Uin, "nickname": honor.CurrentTalkative.Name, "avatar": honor.CurrentTalkative.Avatar, "day_count": honor.CurrentTalkative.DayCount, } } msg["talkative_list"] = convertMem(honor.TalkativeList) } } if t == "performer" || t == "all" { if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Performer); err == nil { msg["performer_lis"] = convertMem(honor.ActorList) } } if t == "legend" || t == "all" { if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Legend); err == nil { msg["legend_list"] = convertMem(honor.LegendList) } } if t == "strong_newbie" || t == "all" { if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.StrongNewbie); err == nil { msg["strong_newbie_list"] = convertMem(honor.StrongNewbieList) } } if t == "emotion" || t == "all" { if honor, err := bot.Client.GetGroupHonorInfo(groupID, client.Emotion); err == nil { msg["emotion_list"] = convertMem(honor.EmotionList) } } return OK(msg) } // CQGetStrangerInfo 获取陌生人信息 // // https://git.io/Jtz17 func (bot *CQBot) CQGetStrangerInfo(userID int64) MSG { info, err := bot.Client.GetSummaryInfo(userID) if err != nil { return Failed(100, "SUMMARY_API_ERROR", err.Error()) } return OK(MSG{ "user_id": info.Uin, "nickname": info.Nickname, "qid": info.Qid, "sex": func() string { if info.Sex == 1 { return "female" } else if info.Sex == 0 { return "male" } // unknown = 0x2 return "unknown" }(), "age": info.Age, "level": info.Level, "login_days": info.LoginDays, }) } // CQHandleQuickOperation 隐藏API-对事件执行快速操作 // // https://git.io/Jtz15 func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) MSG { postType := context.Get("post_type").Str switch postType { case "message": anonymous := context.Get("anonymous") isAnonymous := anonymous.Type != gjson.Null msgType := context.Get("message_type").Str reply := operation.Get("reply") if reply.Exists() { autoEscape := global.EnsureBool(operation.Get("auto_escape"), false) at := !isAnonymous // 除匿名消息场合外默认 true if operation.Get("at_sender").Exists() { at = operation.Get("at_sender").Bool() && !isAnonymous } if at && reply.IsArray() { // 在 reply 数组头部插入CQ码 replySegments := make([]MSG, 0) segments := make([]MSG, 0) segments = append(segments, MSG{ "type": "at", "data": MSG{ "qq": context.Get("sender.user_id").Int(), }, }) err := json.UnmarshalFromString(reply.Raw, &replySegments) if err != nil { log.WithError(err).Warnf("处理 at_sender 过程中发生错误") return Failed(-1, "处理 at_sender 过程中发生错误", err.Error()) } segments = append(segments, replySegments...) modified, err := json.MarshalToString(segments) if err != nil { log.WithError(err).Warnf("处理 at_sender 过程中发生错误") return Failed(-1, "处理 at_sender 过程中发生错误", err.Error()) } reply = gjson.Parse(modified) } else if at && reply.Type == gjson.String { reply = gjson.Parse(fmt.Sprintf( "\"[CQ:at,qq=%d]%s\"", context.Get("sender.user_id").Int(), reply.String(), )) } if msgType == "group" { bot.CQSendGroupMessage(context.Get("group_id").Int(), reply, autoEscape) } if msgType == "private" { bot.CQSendPrivateMessage(context.Get("user_id").Int(), reply, autoEscape) } } if msgType == "group" { if operation.Get("delete").Bool() { bot.CQDeleteMessage(int32(context.Get("message_id").Int())) } if operation.Get("kick").Bool() && !isAnonymous { bot.CQSetGroupKick(context.Get("group_id").Int(), context.Get("user_id").Int(), "", operation.Get("reject_add_request").Bool()) } if operation.Get("ban").Bool() { var duration uint32 = 30 * 60 if operation.Get("ban_duration").Exists() { duration = uint32(operation.Get("ban_duration").Uint()) } // unsupported anonymous ban yet if !isAnonymous { bot.CQSetGroupBan(context.Get("group_id").Int(), context.Get("user_id").Int(), duration) } } } case "request": reqType := context.Get("request_type").Str if operation.Get("approve").Exists() { if reqType == "friend" { bot.CQProcessFriendRequest(context.Get("flag").Str, operation.Get("approve").Bool()) } if reqType == "group" { bot.CQProcessGroupRequest(context.Get("flag").Str, context.Get("sub_type").Str, operation.Get("reason").Str, operation.Get("approve").Bool()) } } } return OK(nil) } // CQGetImage 获取图片(修改自OneBot) // // https://docs.go-cqhttp.org/api/#%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87%E4%BF%A1%E6%81%AF func (bot *CQBot) CQGetImage(file string) MSG { if !global.PathExists(path.Join(global.ImagePath, file)) { return Failed(100) } b, err := ioutil.ReadFile(path.Join(global.ImagePath, file)) if err == nil { r := binary.NewReader(b) r.ReadBytes(16) msg := MSG{ "size": r.ReadInt32(), "filename": r.ReadString(), "url": r.ReadString(), } local := path.Join(global.CachePath, file+"."+path.Ext(msg["filename"].(string))) if !global.PathExists(local) { if data, err := global.GetBytes(msg["url"].(string)); err == nil { _ = ioutil.WriteFile(local, data, 0644) } } msg["file"] = local return OK(msg) } return Failed(100, "LOAD_FILE_ERROR", err.Error()) } // CQDownloadFile 扩展API-下载文件到缓存目录 // // 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 func (bot *CQBot) CQDownloadFile(url string, headers map[string]string, threadCount int) MSG { hash := md5.Sum([]byte(url)) file := path.Join(global.CachePath, hex.EncodeToString(hash[:])+".cache") if global.PathExists(file) { if err := os.Remove(file); err != nil { log.Warnf("删除缓存文件 %v 时出现错误: %v", file, err) return Failed(100, "DELETE_FILE_ERROR", err.Error()) } } if err := global.DownloadFileMultiThreading(url, file, 0, threadCount, headers); err != nil { log.Warnf("下载链接 %v 时出现错误: %v", url, err) return Failed(100, "DOWNLOAD_FILE_ERROR", err.Error()) } abs, _ := filepath.Abs(file) return OK(MSG{ "file": abs, }) } // CQGetForwardMessage 获取合并转发消息 // // https://git.io/Jtz1F func (bot *CQBot) CQGetForwardMessage(resID string) MSG { m := bot.Client.GetForwardMessage(resID) if m == nil { return Failed(100, "MSG_NOT_FOUND", "消息不存在") } r := make([]MSG, 0) for _, n := range m.Nodes { bot.checkMedia(n.Message) r = append(r, MSG{ "sender": MSG{ "user_id": n.SenderId, "nickname": n.SenderName, }, "time": n.Time, "content": ToFormattedMessage(n.Message, 0, false), }) } return OK(MSG{ "messages": r, }) } // CQGetMessage 获取消息 // // https://git.io/Jtz1b func (bot *CQBot) CQGetMessage(messageID int32) MSG { msg := bot.GetMessage(messageID) if msg == nil { return Failed(100, "MSG_NOT_FOUND", "消息不存在") } sender := msg["sender"].(message.Sender) gid, isGroup := msg["group"] raw := msg["message"].(string) return OK(MSG{ "message_id": messageID, "real_id": msg["message-id"], "message_seq": msg["message-id"], "group": isGroup, "group_id": gid, "message_type": func() string { if isGroup { return "group" } return "private" }(), "sender": MSG{ "user_id": sender.Uin, "nickname": sender.Nickname, }, "time": msg["time"], "raw_message": raw, "message": ToFormattedMessage(bot.ConvertStringMessage(raw, isGroup), func() int64 { if isGroup { return gid.(int64) } return sender.Uin }(), false), }) } // CQGetGroupSystemMessages 扩展API-获取群文件系统消息 // // 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 func (bot *CQBot) CQGetGroupSystemMessages() MSG { msg, err := bot.Client.GetGroupSystemMessages() if err != nil { log.Warnf("获取群系统消息失败: %v", err) return Failed(100, "SYSTEM_MSG_API_ERROR", err.Error()) } return OK(msg) } // CQGetGroupMessageHistory 获取群消息历史记录 // // 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 func (bot *CQBot) CQGetGroupMessageHistory(groupID int64, seq int64) MSG { if g := bot.Client.FindGroup(groupID); g == nil { return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } if seq == 0 { g, err := bot.Client.GetGroupInfo(groupID) if err != nil { return Failed(100, "GROUP_INFO_API_ERROR", err.Error()) } seq = g.LastMsgSeq } msg, err := bot.Client.GetGroupMessages(groupID, int64(math.Max(float64(seq-19), 1)), seq) if err != nil { log.Warnf("获取群历史消息失败: %v", err) return Failed(100, "MESSAGES_API_ERROR", err.Error()) } var ms []MSG for _, m := range msg { id := m.Id bot.checkMedia(m.Elements) if bot.db != nil { id = bot.InsertGroupMessage(m) } t := bot.formatGroupMessage(m) t["message_id"] = id ms = append(ms, t) } return OK(MSG{ "messages": ms, }) } // CQGetOnlineClients 扩展API-获取当前账号在线客户端列表 // // 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 func (bot *CQBot) CQGetOnlineClients(noCache bool) MSG { if noCache { if err := bot.Client.RefreshStatus(); err != nil { log.Warnf("刷新客户端状态时出现问题 %v", err) return Failed(100, "REFRESH_STATUS_ERROR", err.Error()) } } var d []MSG for _, oc := range bot.Client.OnlineClients { d = append(d, MSG{ "app_id": oc.AppId, "device_name": oc.DeviceName, "device_kind": oc.DeviceKind, }) } return OK(MSG{ "clients": d, }) } // CQCanSendImage 检查是否可以发送图片(此处永远返回true) // // https://git.io/Jtz1N func (bot *CQBot) CQCanSendImage() MSG { return OK(MSG{"yes": true}) } // CQCanSendRecord 检查是否可以发送语音(此处永远返回true) // // https://git.io/Jtz1x func (bot *CQBot) CQCanSendRecord() MSG { return OK(MSG{"yes": true}) } // CQOcrImage 扩展API-图片OCR // // https://docs.go-cqhttp.org/api/#%E5%9B%BE%E7%89%87-ocr func (bot *CQBot) CQOcrImage(imageID string) MSG { img, err := bot.makeImageOrVideoElem(map[string]string{"file": imageID}, false, true) if err != nil { log.Warnf("load image error: %v", err) return Failed(100, "LOAD_FILE_ERROR", err.Error()) } rsp, err := bot.Client.ImageOcr(img) if err != nil { log.Warnf("ocr image error: %v", err) return Failed(100, "OCR_API_ERROR", err.Error()) } return OK(rsp) } // CQReloadEventFilter 扩展API-重载事件过滤器 // // https://docs.go-cqhttp.org/api/#%E9%87%8D%E8%BD%BD%E4%BA%8B%E4%BB%B6%E8%BF%87%E6%BB%A4%E5%99%A8 func (bot *CQBot) CQReloadEventFilter() MSG { global.BootFilter() return OK(nil) } // CQSetGroupPortrait 扩展API-设置群头像 // // https://docs.go-cqhttp.org/api/#%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%A4%B4%E5%83%8F func (bot *CQBot) CQSetGroupPortrait(groupID int64, file, cache string) MSG { if g := bot.Client.FindGroup(groupID); g != nil { img, err := global.FindFile(file, cache, global.ImagePath) if err != nil { log.Warnf("set group portrait error: %v", err) return Failed(100, "LOAD_FILE_ERROR", err.Error()) } g.UpdateGroupHeadPortrait(img) return OK(nil) } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQSetGroupAnonymousBan 群组匿名用户禁言 // // https://git.io/Jtz1p func (bot *CQBot) CQSetGroupAnonymousBan(groupID int64, flag string, duration int32) MSG { if flag == "" { return Failed(100, "INVALID_FLAG", "无效的flag") } if g := bot.Client.FindGroup(groupID); g != nil { s := strings.SplitN(flag, "|", 2) if len(s) != 2 { return Failed(100, "INVALID_FLAG", "无效的flag") } id := s[0] nick := s[1] if err := g.MuteAnonymous(id, nick, duration); err != nil { log.Warnf("anonymous ban error: %v", err) return Failed(100, "CALL_API_ERROR", err.Error()) } return OK(nil) } return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } // CQGetStatus 获取运行状态 // // https://git.io/JtzMe func (bot *CQBot) CQGetStatus() MSG { return OK(MSG{ "app_initialized": true, "app_enabled": true, "plugins_good": nil, "app_good": true, "online": bot.Client.Online, "good": bot.Client.Online, "stat": bot.Client.GetStatistics(), }) } // CQSetEssenceMessage 扩展API-设置精华消息 // // https://docs.go-cqhttp.org/api/#%E8%AE%BE%E7%BD%AE%E7%B2%BE%E5%8D%8E%E6%B6%88%E6%81%AF func (bot *CQBot) CQSetEssenceMessage(messageID int32) MSG { msg := bot.GetMessage(messageID) if msg == nil { return Failed(100, "MESSAGE_NOT_FOUND", "消息不存在") } if _, ok := msg["group"]; ok { if err := bot.Client.SetEssenceMessage(msg["group"].(int64), msg["message-id"].(int32), msg["internal-id"].(int32)); err != nil { log.Warnf("设置精华消息 %v 失败: %v", messageID, err) return Failed(100, "SET_ESSENCE_MSG_ERROR", err.Error()) } } else { log.Warnf("设置精华消息 %v 失败: 非群聊", messageID) return Failed(100, "SET_ESSENCE_MSG_ERROR", "非群聊") } return OK(nil) } // CQDeleteEssenceMessage 扩展API-移出精华消息 // // https://docs.go-cqhttp.org/api/#%E7%A7%BB%E5%87%BA%E7%B2%BE%E5%8D%8E%E6%B6%88%E6%81%AF func (bot *CQBot) CQDeleteEssenceMessage(messageID int32) MSG { msg := bot.GetMessage(messageID) if msg == nil { return Failed(100, "MESSAGE_NOT_FOUND", "消息不存在") } if _, ok := msg["group"]; ok { if err := bot.Client.DeleteEssenceMessage(msg["group"].(int64), msg["message-id"].(int32), msg["internal-id"].(int32)); err != nil { log.Warnf("移出精华消息 %v 失败: %v", messageID, err) return Failed(100, "DEL_ESSENCE_MSG_ERROR", err.Error()) } } else { log.Warnf("移出精华消息 %v 失败: 非群聊", messageID) return Failed(100, "DEL_ESSENCE_MSG_ERROR", "非群聊") } return OK(nil) } // CQGetEssenceMessageList 扩展API-获取精华消息列表 // // 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 func (bot *CQBot) CQGetEssenceMessageList(groupCode int64) MSG { g := bot.Client.FindGroup(groupCode) if g == nil { return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") } msgList, err := bot.Client.GetGroupEssenceMsgList(groupCode) if err != nil { return Failed(100, "GET_ESSENCE_LIST_FOUND", err.Error()) } list := make([]MSG, 0) for _, m := range msgList { var msg = MSG{ "sender_nick": m.SenderNick, "sender_time": m.SenderTime, "operator_time": m.AddDigestTime, "operator_nick": m.AddDigestNick, } msg["sender_id"], _ = strconv.ParseUint(m.SenderUin, 10, 64) msg["operator_id"], _ = strconv.ParseUint(m.AddDigestUin, 10, 64) msg["message_id"] = toGlobalID(groupCode, int32(m.MessageID)) list = append(list, msg) } return OK(list) } // CQCheckURLSafely 扩展API-检查链接安全性 // // 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 func (bot *CQBot) CQCheckURLSafely(url string) MSG { return OK(MSG{ "level": bot.Client.CheckUrlSafely(url), }) } // CQGetVersionInfo 获取版本信息 // // https://git.io/JtwUs func (bot *CQBot) CQGetVersionInfo() MSG { wd, _ := os.Getwd() return OK(MSG{ "coolq_directory": wd, "coolq_edition": "pro", "go-cqhttp": true, "plugin_version": "4.15.0", "plugin_build_number": 99, "plugin_build_configuration": "release", "runtime_version": runtime.Version(), "runtime_os": runtime.GOOS, "version": Version, "protocol": func() int { switch client.SystemDeviceInfo.Protocol { case client.IPad: return 0 case client.AndroidPhone: return 1 case client.AndroidWatch: return 2 case client.MacOS: return 3 default: return -1 } }(), }) } // OK 生成成功返回值 func OK(data interface{}) MSG { return MSG{"data": data, "retcode": 0, "status": "ok"} } // Failed 生成失败返回值 func Failed(code int, msg ...string) MSG { m := "" w := "" if len(msg) > 0 { m = msg[0] } if len(msg) > 1 { w = msg[1] } return MSG{"data": nil, "retcode": code, "msg": m, "wording": w, "status": "failed"} } func convertGroupMemberInfo(groupID int64, m *client.GroupMemberInfo) MSG { return MSG{ "group_id": groupID, "user_id": m.Uin, "nickname": m.Nickname, "card": m.CardName, "sex": func() string { if m.Gender == 1 { return "female" } else if m.Gender == 0 { return "male" } // unknown = 0xff return "unknown" }(), "age": 0, "area": "", "join_time": m.JoinTime, "last_sent_time": m.LastSpeakTime, "level": strconv.FormatInt(int64(m.Level), 10), "role": func() string { switch m.Permission { case client.Owner: return "owner" case client.Administrator: return "admin" default: return "member" } }(), "unfriendly": false, "title": m.SpecialTitle, "title_expire_time": m.SpecialTitleExpireTime, "card_changeable": false, } } func limitedString(str string) string { if strings.Count(str, "") <= 10 { return str } limited := []rune(str) limited = limited[:10] return string(limited) + " ..." }