diff --git a/README.md b/README.md index 3ff33de..d6b5f59 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ | /set_group_leave | [退出群组](https://cqhttp.cc/docs/4.15/#/API?id=set_group_leave-退出群组) | | /set_group_name | 设置群组名(拓展API) | | /get_image | 获取图片信息(拓展API) | -| /get_group_msg | 获取群组消息(拓展API) | +| /get_msg | [获取消息]() | | /can_send_image | [检查是否可以发送图片](https://cqhttp.cc/docs/4.15/#/API?id=can_send_image-检查是否可以发送图片) | | /can_send_record | [检查是否可以发送语音](https://cqhttp.cc/docs/4.15/#/API?id=can_send_record-检查是否可以发送语音) | | /get_status | [获取插件运行状态](https://cqhttp.cc/docs/4.15/#/API?id=get_status-获取插件运行状态) | diff --git a/coolq/api.go b/coolq/api.go index c6b2b55..ee5ba17 100644 --- a/coolq/api.go +++ b/coolq/api.go @@ -102,6 +102,59 @@ func (bot *CQBot) CQGetGroupMemberInfo(groupId, userId int64) MSG { return OK(convertGroupMemberInfo(groupId, member)) } +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) + } + return OK(fs) +} + +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) + } + files, folders, err := fs.Root() + if err != nil { + log.Errorf("获取群 %v 根目录文件失败: %v", groupId, err) + return Failed(100) + } + return OK(MSG{ + "files": files, + "folders": folders, + }) +} + +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) + } + files, folders, err := fs.GetFilesByFolder(folderId) + if err != nil { + log.Errorf("获取群 %v 根目录 %v 子文件失败: %v", groupId, folderId, err) + return Failed(100) + } + return OK(MSG{ + "files": files, + "folders": folders, + }) +} + +func (bot *CQBot) CQGetGroupFileUrl(groupId int64, fileId string, busId int32) MSG { + url := bot.Client.GetGroupFileUrl(groupId, fileId, busId) + if url == "" { + return Failed(100) + } + return OK(MSG{ + "url": url, + }) +} + func (bot *CQBot) CQGetWordSlices(content string) MSG { slices, err := bot.Client.GetWordSegmentation(content) if err != nil { @@ -384,29 +437,43 @@ func (bot *CQBot) CQProcessFriendRequest(flag string, approve bool) MSG { // https://cqhttp.cc/docs/4.15/#/API?id=set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%EF%BC%8F%E9%82%80%E8%AF%B7 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) + } if subType == "add" { - req, ok := bot.joinReqCache.Load(flag) - if !ok { - return Failed(100) + for _, req := range msgs.JoinRequests { + if strconv.FormatInt(req.RequestId, 10) == flag { + if req.Checked { + log.Errorf("处理群系统消息失败: 无法操作已处理的消息.") + return Failed(100) + } + if approve { + req.Accept() + } else { + req.Reject(false, reason) + } + return OK(nil) + } } - bot.joinReqCache.Delete(flag) - if approve { - req.(*client.UserJoinGroupRequest).Accept() - } else { - req.(*client.UserJoinGroupRequest).Reject(false, reason) + } else { + for _, req := range msgs.InvitedRequests { + if strconv.FormatInt(req.RequestId, 10) == flag { + if req.Checked { + log.Errorf("处理群系统消息失败: 无法操作已处理的消息.") + return Failed(100) + } + if approve { + req.Accept() + } else { + req.Reject(false, reason) + } + return OK(nil) + } } - return OK(nil) - } - req, ok := bot.invitedReqCache.Load(flag) - if ok { - bot.invitedReqCache.Delete(flag) - if approve { - req.(*client.GroupInvitedRequest).Accept() - } else { - req.(*client.GroupInvitedRequest).Reject(false, reason) - } - return OK(nil) } + log.Errorf("处理群系统消息失败: 消息 %v 不存在.", flag) return Failed(100) } @@ -666,6 +733,15 @@ func (bot *CQBot) CQGetMessage(messageId int32) MSG { }) } +func (bot *CQBot) CQGetGroupSystemMessages() MSG { + msg, err := bot.Client.GetGroupSystemMessages() + if err != nil { + log.Warnf("获取群系统消息失败: %v", err) + return Failed(100) + } + return OK(msg) +} + func (bot *CQBot) CQCanSendImage() MSG { return OK(MSG{"yes": true}) } diff --git a/coolq/bot.go b/coolq/bot.go index 0483d57..06c02ff 100644 --- a/coolq/bot.go +++ b/coolq/bot.go @@ -23,13 +23,11 @@ import ( type CQBot struct { Client *client.QQClient - events []func(MSG) - db *leveldb.DB - friendReqCache sync.Map - invitedReqCache sync.Map - joinReqCache sync.Map - tempMsgCache sync.Map - oneWayMsgCache sync.Map + events []func(MSG) + db *leveldb.DB + friendReqCache sync.Map + tempMsgCache sync.Map + oneWayMsgCache sync.Map } type MSG map[string]interface{} @@ -211,7 +209,12 @@ func (bot *CQBot) SendGroupMessage(groupId int64, m *message.SendingMessage) int } newElem = append(newElem, elem) } + if len(newElem) == 0 { + log.Warnf("群消息发送失败: 消息为空.") + return -1 + } m.Elements = newElem + bot.checkMedia(newElem) ret := bot.Client.SendGroupMessage(groupId, m, ForceFragmented) if ret == nil || ret.Id == -1 { log.Warnf("群消息发送失败: 账号可能被风控.") @@ -292,7 +295,12 @@ func (bot *CQBot) SendPrivateMessage(target int64, m *message.SendingMessage) in } newElem = append(newElem, elem) } + if len(newElem) == 0 { + log.Warnf("好友消息发送失败: 消息为空.") + return -1 + } m.Elements = newElem + bot.checkMedia(newElem) var id int32 = -1 if bot.Client.FindFriend(target) != nil { // 双向好友 msg := bot.Client.SendPrivateMessage(target, m) diff --git a/coolq/cqcode.go b/coolq/cqcode.go index 2233ad6..f092a5f 100644 --- a/coolq/cqcode.go +++ b/coolq/cqcode.go @@ -243,6 +243,10 @@ func ToStringMessage(e []message.IMessageElement, code int64, raw ...bool) (r st } else { r += fmt.Sprintf(`[CQ:image,file=%s,url=%s]`, o.Filename, CQCodeEscapeValue(o.Url)) } + case *message.GroupImageElement: + r += fmt.Sprintf("[CQ:image,file=%s]", hex.EncodeToString(o.Md5)+".image") + case *message.FriendImageElement: + r += fmt.Sprintf("[CQ:image,file=%s]", hex.EncodeToString(o.Md5)+".image") case *message.ServiceElement: if isOk := strings.Contains(o.Content, " 0 { - log.Infof("群 %v 内 %v 被 %v 禁言了 %v秒.", - formatGroupName(g), formatMemberName(g.FindMember(e.TargetUin)), formatMemberName(g.FindMember(e.OperatorUin)), e.Time) + if e.TargetUin == 0 { + if e.Time != 0 { + log.Infof("群 %v 被 %v 开启全员禁言.", + formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin))) + } else { + log.Infof("群 %v 被 %v 解除全员禁言.", + formatGroupName(g), formatMemberName(g.FindMember(e.OperatorUin))) + } } else { - log.Infof("群 %v 内 %v 被 %v 解除禁言.", - formatGroupName(g), formatMemberName(g.FindMember(e.TargetUin)), formatMemberName(g.FindMember(e.OperatorUin))) + if e.Time > 0 { + log.Infof("群 %v 内 %v 被 %v 禁言了 %v 秒.", + formatGroupName(g), formatMemberName(g.FindMember(e.TargetUin)), formatMemberName(g.FindMember(e.OperatorUin)), e.Time) + } else { + log.Infof("群 %v 内 %v 被 %v 解除禁言.", + formatGroupName(g), formatMemberName(g.FindMember(e.TargetUin)), formatMemberName(g.FindMember(e.OperatorUin))) + } } + bot.dispatchEventMessage(MSG{ "post_type": "notice", "duration": e.Time, @@ -184,10 +195,10 @@ func (bot *CQBot) groupMutedEvent(c *client.QQClient, e *client.GroupMuteEvent) "user_id": e.TargetUin, "time": time.Now().Unix(), "sub_type": func() string { - if e.Time > 0 { - return "ban" + if e.Time == 0 { + return "lift_ban" } - return "lift_ban" + return "ban" }(), }) } @@ -271,12 +282,16 @@ func (bot *CQBot) groupNotifyEvent(c *client.QQClient, e client.IGroupNotifyEven func (bot *CQBot) friendRecallEvent(c *client.QQClient, e *client.FriendMessageRecalledEvent) { f := c.FindFriend(e.FriendUin) gid := ToGlobalId(e.FriendUin, e.MessageId) - log.Infof("好友 %v(%v) 撤回了消息: %v", f.Nickname, f.Uin, gid) + if f != nil { + log.Infof("好友 %v(%v) 撤回了消息: %v", f.Nickname, f.Uin, gid) + } else { + log.Infof("好友 %v 撤回了消息: %v", e.FriendUin, gid) + } bot.dispatchEventMessage(MSG{ "post_type": "notice", "notice_type": "friend_recall", "self_id": c.Uin, - "user_id": f.Uin, + "user_id": e.FriendUin, "time": e.Time, "message_id": gid, }) @@ -392,7 +407,6 @@ func (bot *CQBot) friendAddedEvent(c *client.QQClient, e *client.NewFriendEvent) func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRequest) { log.Infof("收到来自群 %v(%v) 内用户 %v(%v) 的加群邀请.", e.GroupName, e.GroupCode, e.InvitorNick, e.InvitorUin) flag := strconv.FormatInt(e.RequestId, 10) - bot.invitedReqCache.Store(flag, e) bot.dispatchEventMessage(MSG{ "post_type": "request", "request_type": "group", @@ -409,7 +423,6 @@ func (bot *CQBot) groupInvitedEvent(c *client.QQClient, e *client.GroupInvitedRe func (bot *CQBot) groupJoinReqEvent(c *client.QQClient, e *client.UserJoinGroupRequest) { log.Infof("群 %v(%v) 收到来自用户 %v(%v) 的加群请求.", e.GroupName, e.GroupCode, e.RequesterNick, e.RequesterUin) flag := strconv.FormatInt(e.RequestId, 10) - bot.joinReqCache.Store(flag, e) bot.dispatchEventMessage(MSG{ "post_type": "request", "request_type": "group", @@ -476,6 +489,26 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement) { }), 0644) } i.Filename = filename + case *message.GroupImageElement: + filename := hex.EncodeToString(i.Md5) + ".image" + if !global.PathExists(path.Join(global.IMAGE_PATH, filename)) { + _ = ioutil.WriteFile(path.Join(global.IMAGE_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { + w.Write(i.Md5) + w.WriteUInt32(uint32(i.Size)) + w.WriteString(filename) + w.WriteString(i.Url) + }), 0644) + } + case *message.FriendImageElement: + filename := hex.EncodeToString(i.Md5) + ".image" + if !global.PathExists(path.Join(global.IMAGE_PATH, filename)) { + _ = ioutil.WriteFile(path.Join(global.IMAGE_PATH, filename), binary.NewWriterF(func(w *binary.Writer) { + w.Write(i.Md5) + w.WriteUInt32(uint32(0)) // 发送时会调用url, 大概没事 + w.WriteString(filename) + w.WriteString(i.Url) + }), 0644) + } case *message.VoiceElement: i.Name = strings.ReplaceAll(i.Name, "{", "") i.Name = strings.ReplaceAll(i.Name, "}", "") diff --git a/docs/cqhttp.md b/docs/cqhttp.md index 9584ed1..09b2c0d 100644 --- a/docs/cqhttp.md +++ b/docs/cqhttp.md @@ -380,9 +380,9 @@ Type: `tts` | `filename` | string | 图片文件原名 | | `url` | string | 图片下载地址 | -### 获取群消息 +### 获取消息 -终结点: `/get_group_msg` +终结点: `/get_msg` 参数 @@ -398,7 +398,7 @@ Type: `tts` | `real_id` | int32 | 消息真实id | | `sender` | object | 发送者 | | `time` | int32 | 发送时间 | -| `content` | message | 消息内容 | +| `message` | message | 消息内容 | ### 获取合并转发内容 @@ -500,6 +500,147 @@ Type: `tts` | `coordinates` | vector2 | 坐标 | +### 获取群系统消息 + +终结点: `/get_group_system_msg` + +**响应数据** + + | 字段 | 类型 | 说明 | + | ---------- | ----------------- | -------- | + | `invited_requests` | InvitedRequest[] | 邀请消息列表 | + | `join_requests` | JoinRequest[] | 进群消息列表 | + + > 注意: 如果列表不存在任何消息, 将返回 `null` + + **InvitedRequest** + + | 字段 | 类型 | 说明 | + | ---------- | ----------------- | -------- | + | `request_id` | int64 | 请求ID | + | `invitor_uin` | int64 | 邀请者 | + | `invitor_nick` | string | 邀请者昵称 | + | `group_id` | int64 | 群号 | + | `group_name` | string | 群名 | + | `checked` | bool | 是否已被处理| + | `actor` | int64 | 处理者, 未处理为0 | + + **JoinRequest** + + | 字段 | 类型 | 说明 | + | ---------- | ----------------- | -------- | + | `request_id` | int64 | 请求ID | + | `requester_uin` | int64 | 请求者ID | + | `requester_nick` | string | 请求者昵称 | + | `message` | string | 验证消息 | + | `group_id` | int64 | 群号 | + | `group_name` | string | 群名 | + | `checked` | bool | 是否已被处理| + | `actor` | int64 | 处理者, 未处理为0 | + +### 获取群文件系统信息 + +终结点: `/get_group_file_system_info` + +**参数** + + | 字段 | 类型 | 说明 | + | ------------ | ------ | ------ | + | `group_id` | int64 | 群号 | + +**响应数据** + + | 字段 | 类型 | 说明 | + | ---------- | ----------------- | -------- | + | `file_count` | int32 | 文件总数 | + | `limit_count` | int32 | 文件上限 | + | `used_space` | int64 | 已使用空间 | + | `total_space` | int64 | 空间上限 | + +### 获取群根目录文件列表 + +> `File` 和 `Folder` 对象信息请参考最下方 + +终结点: `/get_group_root_files` + +**参数** + + | 字段 | 类型 | 说明 | + | ------------ | ------ | ------ | + | `group_id` | int64 | 群号 | + +**响应数据** + + | 字段 | 类型 | 说明 | + | ---------- | ----------------- | -------- | + | `files` | File[] | 文件列表 | + | `folders` | Folder[] | 文件夹列表 | + +### 获取群子目录文件列表 + +> `File` 和 `Folder` 对象信息请参考最下方 + +终结点: `/get_group_files_by_folder` + +**参数** + + | 字段 | 类型 | 说明 | + | ------------ | ------ | ------ | + | `group_id` | int64 | 群号 | + | `folder_id` | string | 文件夹ID 参考 `Folder` 对象 | + +**响应数据** + + | 字段 | 类型 | 说明 | + | ---------- | ----------------- | -------- | + | `files` | File[] | 文件列表 | + | `folders` | Folder[] | 文件夹列表 | + +### 获取群文件资源链接 + +> `File` 和 `Folder` 对象信息请参考最下方 + +终结点: `/get_group_file_url` + +**参数** + + | 字段 | 类型 | 说明 | + | ------------ | ------ | ------ | + | `group_id` | int64 | 群号 | + | `file_id` | string | 文件ID 参考 `File` 对象 | + | `busid` | int32 | 文件类型 参考 `File` 对象 | + +**响应数据** + + | 字段 | 类型 | 说明 | + | ---------- | ----------------- | -------- | + | `url` | string | 文件下载链接 | + + **File** + + | 字段 | 类型 | 说明 | + | ---------- | ----------------- | -------- | + | `file_id` | string | 文件ID | + | `file_name` | string | 文件名 | + | `busid` | int32 | 文件类型 | + | `file_size` | int64 | 文件大小 | + | `upload_time` | int64 | 上传时间 | + | `dead_time` | int64 | 过期时间,永久文件恒为0 | + | `modify_time` | int64 | 最后修改时间 | + | `download_times` | int32 | 下载次数 | + | `uploader` | int64 | 上传者ID | + | `uploader_name` | string | 上传者名字 | + + **Folder** + + | 字段 | 类型 | 说明 | + | ---------- | ----------------- | -------- | + | `folder_id` | string | 文件夹ID | + | `folder_name` | string | 文件名 | + | `create_time` | int64 | 创建时间 | + | `creator` | int64 | 创建者 | + | `creator_name` | string | 创建者名字 | + | `total_file_count` | int32 | 子文件数量 | ## 事件 diff --git a/global/config.go b/global/config.go index e380e99..15a3759 100644 --- a/global/config.go +++ b/global/config.go @@ -131,7 +131,7 @@ func DefaultConfig() *JsonConfig { }, WebUi: &GoCqWebUi{ Enabled: true, - Host: "0.0.0.0", + Host: "127.0.0.1", WebInput: false, WebUiPort: 9999, }, diff --git a/global/net.go b/global/net.go index 7691e89..d30d796 100644 --- a/global/net.go +++ b/global/net.go @@ -8,15 +8,20 @@ import ( "io/ioutil" "net/http" "strings" + "time" ) +var client = &http.Client{ + Timeout: time.Second * 15, +} + func GetBytes(url string) ([]byte, error) { req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } req.Header["User-Agent"] = []string{"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.61"} - resp, err := http.DefaultClient.Do(req) + resp, err := client.Do(req) if err != nil { return nil, err } diff --git a/global/param.go b/global/param.go index 25e138f..5f01bea 100644 --- a/global/param.go +++ b/global/param.go @@ -2,6 +2,9 @@ package global import ( "github.com/tidwall/gjson" + "math" + "regexp" + "strconv" "strings" ) @@ -48,3 +51,22 @@ func EnsureBool(p interface{}, defaultVal bool) bool { } return defaultVal } + +// VersionNameCompare 检查版本名是否需要更新, 仅适用于 go-cqhttp 的版本命名规则 +// 例: v0.9.29-fix2 == v0.9.29-fix2 -> false +// v0.9.29-fix1 < v0.9.29-fix2 -> true +// v0.9.29-fix2 > v0.9.29-fix1 -> false +// v0.9.29-fix2 < v0.9.30 -> true +func VersionNameCompare(current, remote string) bool { + sp := regexp.MustCompile(`[0-9]\d*`) + cur := sp.FindAllStringSubmatch(current, -1) + re := sp.FindAllStringSubmatch(remote, -1) + for i := 0; i < int(math.Min(float64(len(cur)), float64(len(re)))); i++ { + curSub, _ := strconv.Atoi(cur[i][0]) + reSub, _ := strconv.Atoi(re[i][0]) + if curSub < reSub { + return true + } + } + return len(cur) < len(re) +} diff --git a/go.mod b/go.mod index 3118405..ba3dc3b 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/Mrs4s/go-cqhttp go 1.14 require ( - github.com/Mrs4s/MiraiGo v0.0.0-20201017083749-517ddcd50b8d + github.com/Mrs4s/MiraiGo v0.0.0-20201105120358-ca72d542ca72 github.com/gin-gonic/gin v1.6.3 github.com/gorilla/websocket v1.4.2 - github.com/guonaihong/gout v0.1.2 + github.com/guonaihong/gout v0.1.3 github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible github.com/lestrrat-go/strftime v1.0.3 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 7acd5a6..163260f 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Mrs4s/MiraiGo v0.0.0-20201013050256-7b392cacdb79 h1:t9PK37mnl5tbilG+FMUY1hZujoDjFr8iO8upwYHww5c= -github.com/Mrs4s/MiraiGo v0.0.0-20201013050256-7b392cacdb79/go.mod h1:cwYPI2uq6nxNbx0nA6YuAKF1V5szSs6FPlGVLQvRUlo= -github.com/Mrs4s/MiraiGo v0.0.0-20201016033322-0922b058ff56 h1:vcXOLG+W/c56EWOwA8btrHMEGBMwFgLUZli0Jh1axzg= -github.com/Mrs4s/MiraiGo v0.0.0-20201016033322-0922b058ff56/go.mod h1:cwYPI2uq6nxNbx0nA6YuAKF1V5szSs6FPlGVLQvRUlo= -github.com/Mrs4s/MiraiGo v0.0.0-20201017083749-517ddcd50b8d h1:f59SuqT0RVy6T9nAg6zTNfnzkvdgLdGqkNKu/AfiPU4= -github.com/Mrs4s/MiraiGo v0.0.0-20201017083749-517ddcd50b8d/go.mod h1:cwYPI2uq6nxNbx0nA6YuAKF1V5szSs6FPlGVLQvRUlo= -github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= -github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= +github.com/Mrs4s/MiraiGo v0.0.0-20201105120358-ca72d542ca72 h1:aiKVmrgZHXARnO6AYODwFf1JvTZr6OCl2pohepkkYKc= +github.com/Mrs4s/MiraiGo v0.0.0-20201105120358-ca72d542ca72/go.mod h1:pAsWtMIwqkBXr5DkUpTIHoWQJNduVnX9WSBPmPvkuCs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -40,8 +34,8 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -53,8 +47,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/guonaihong/gout v0.1.2 h1:TR2XCRopGgJdj231IayEoeavgbznFXzzzcZVdT/hG10= -github.com/guonaihong/gout v0.1.2/go.mod h1:vXvv5Kxr70eM5wrp4F0+t9lnLWmq+YPW2GByll2f/EA= +github.com/guonaihong/gout v0.1.3 h1:BIiV6nnsA+R6dIB1P33uhCM8+TVAG3zHrXGZad7hDc8= +github.com/guonaihong/gout v0.1.3/go.mod h1:vXvv5Kxr70eM5wrp4F0+t9lnLWmq+YPW2GByll2f/EA= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -108,13 +102,6 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/wdvxdr1123/go-silk v0.0.0-20201007123416-b982fd3d91d6 h1:lX18MCdNzT2zIi7K02x4C5cPkDXpL+wCb1YTAMXjLWQ= github.com/wdvxdr1123/go-silk v0.0.0-20201007123416-b982fd3d91d6/go.mod h1:5q9LFlBr+yX/J8Jd/9wHdXwkkjFkNyQIS7kX2Lgx/Zs= -github.com/xujiajun/gorouter v1.2.0/go.mod h1:yJrIta+bTNpBM/2UT8hLOaEAFckO+m/qmR3luMIQygM= -github.com/xujiajun/mmap-go v1.0.1 h1:7Se7ss1fLPPRW+ePgqGpCkfGIZzJV6JPq9Wq9iv/WHc= -github.com/xujiajun/mmap-go v1.0.1/go.mod h1:CNN6Sw4SL69Sui00p0zEzcZKbt+5HtEnYUsc6BKKRMg= -github.com/xujiajun/nutsdb v0.5.0 h1:j/jM3Zw7Chg8WK7bAcKR0Xr7Mal47U1oJAMgySfDn9E= -github.com/xujiajun/nutsdb v0.5.0/go.mod h1:owdwN0tW084RxEodABLbO7h4Z2s9WiAjZGZFhRh0/1Q= -github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b h1:jKG9OiL4T4xQN3IUrhUpc1tG+HfDXppkgVcrAiiaI/0= -github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b/go.mod h1:AZd87GYJlUzl82Yab2kTjx1EyXSQCAfZDhpTo1SQC4k= github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 h1:4UJw9if55Fu3HOwbfcaQlJ27p3oeJU2JZqoeT3ITJQk= github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189/go.mod h1:rIrm5geMiBhPQkdfUm8gDFi/WiHneOp1i9KjmJqc+9I= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -135,7 +122,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/main.go b/main.go index 46ce32d..6da4252 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,8 @@ import ( "encoding/json" "fmt" "github.com/Mrs4s/go-cqhttp/server" + "github.com/guonaihong/gout" + "github.com/tidwall/gjson" "io" "io/ioutil" "os" @@ -245,7 +247,7 @@ func main() { conf.WebUi.WebUiPort = 9999 } if conf.WebUi.Host == "" { - conf.WebUi.Host = "0.0.0.0" + conf.WebUi.Host = "127.0.0.1" } confErr := conf.Save("config.json") if confErr != nil { @@ -253,6 +255,7 @@ func main() { } b := server.WebServer.Run(fmt.Sprintf("%s:%d", conf.WebUi.Host, conf.WebUi.WebUiPort), cli) c := server.Console + go checkUpdate() signal.Notify(c, os.Interrupt, os.Kill) <-c b.Release() @@ -282,3 +285,27 @@ func DecryptPwd(ePwd string, key []byte) string { } return string(tea.Decrypt(encrypted)) } + +func checkUpdate() { + log.Infof("正在检查更新.") + if coolq.Version == "unknown" { + log.Warnf("检查更新失败: 使用的 Actions 测试版或自编译版本.") + return + } + var res string + if err := gout.GET("https://api.github.com/repos/Mrs4s/go-cqhttp/releases").BindBody(&res).Do(); err != nil { + log.Warnf("检查更新失败: %v", err) + return + } + detail := gjson.Parse(res) + if len(detail.Array()) < 1 { + return + } + info := detail.Array()[0] + if global.VersionNameCompare(coolq.Version, info.Get("tag_name").Str) { + log.Infof("当前有更新的 go-cqhttp 可供更新, 请前往 https://github.com/Mrs4s/go-cqhttp/releases 下载.") + log.Infof("当前版本: %v 最新版本: %v", coolq.Version, info.Get("tag_name").Str) + return + } + log.Infof("检查更新完成. 当前已运行最新版本.") +} diff --git a/server/apiAdmin.go b/server/apiAdmin.go index 44544f9..637b088 100644 --- a/server/apiAdmin.go +++ b/server/apiAdmin.go @@ -183,7 +183,11 @@ func (s *webServer) Dologin() { os.Exit(0) return case client.OtherLoginError, client.UnknownLoginError: - log.Warnf("登录失败: %v", rsp.ErrorMessage) + msg := rsp.ErrorMessage + if strings.Contains(msg, "版本") { + msg = "密码错误或账号被冻结" + } + log.Warnf("登录失败: %v", msg) log.Infof("按 Enter 继续....") readLine() os.Exit(0) @@ -225,8 +229,7 @@ func (s *webServer) Dologin() { log.Warn("Bot已登录") return } - if conf.ReLogin.MaxReloginTimes == 0 { - } else if times > conf.ReLogin.MaxReloginTimes { + if times > conf.ReLogin.MaxReloginTimes && conf.ReLogin.MaxReloginTimes != 0 { break } log.Warnf("Bot已离线 (%v),将在 %v 秒后尝试重连. 重连次数:%v", diff --git a/server/http.go b/server/http.go index c4348ae..3ad7faf 100644 --- a/server/http.go +++ b/server/http.go @@ -5,6 +5,7 @@ import ( "crypto/hmac" "crypto/sha1" "encoding/hex" + "github.com/guonaihong/gout/dataflow" "net/http" "os" "strconv" @@ -132,11 +133,18 @@ func (c *httpClient) onBotPushEvent(m coolq.MSG) { return h }()).SetTimeout(time.Second * time.Duration(c.timeout)).F().Retry().Attempt(5). WaitTime(time.Millisecond * 500).MaxWaitTime(time.Second * 5). - Do() + Func(func(con *dataflow.Context) error { + if con.Error != nil { + log.Warnf("上报Event到 HTTP 服务器 %v 时出现错误: %v 将重试.", c.addr, con.Error) + return con.Error + } + return nil + }).Do() if err != nil { log.Warnf("上报Event数据 %v 到 %v 失败: %v", m.ToJson(), c.addr, err) return } + log.Debugf("上报Event数据 %v 到 %v", m.ToJson(), c.addr) if gjson.Valid(res) { c.bot.CQHandleQuickOperation(gjson.Parse(m.ToJson()), gjson.Parse(res)) } @@ -183,6 +191,29 @@ func (s *httpServer) GetGroupMemberInfo(c *gin.Context) { c.JSON(200, s.bot.CQGetGroupMemberInfo(gid, uid)) } +func (s *httpServer) GetGroupFileSystemInfo(c *gin.Context) { + gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64) + c.JSON(200, s.bot.CQGetGroupFileSystemInfo(gid)) +} + +func (s *httpServer) GetGroupRootFiles(c *gin.Context) { + gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64) + c.JSON(200, s.bot.CQGetGroupRootFiles(gid)) +} + +func (s *httpServer) GetGroupFilesByFolderId(c *gin.Context) { + gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64) + folderId := getParam(c, "folder_id") + c.JSON(200, s.bot.CQGetGroupFilesByFolderId(gid, folderId)) +} + +func (s *httpServer) GetGroupFileUrl(c *gin.Context) { + gid, _ := strconv.ParseInt(getParam(c, "group_id"), 10, 64) + fid := getParam(c, "file_id") + busid, _ := strconv.ParseInt(getParam(c, "busid"), 10, 32) + c.JSON(200, s.bot.CQGetGroupFileUrl(gid, fid, int32(busid))) +} + func (s *httpServer) SendMessage(c *gin.Context) { if getParam(c, "message_type") == "private" { s.SendPrivateMessage(c) @@ -317,6 +348,10 @@ func (s *httpServer) GetForwardMessage(c *gin.Context) { c.JSON(200, s.bot.CQGetForwardMessage(resId)) } +func (s *httpServer) GetGroupSystemMessage(c *gin.Context) { + c.JSON(200, s.bot.CQGetGroupSystemMessages()) +} + func (s *httpServer) DeleteMessage(c *gin.Context) { mid, _ := strconv.ParseInt(getParam(c, "message_id"), 10, 32) c.JSON(200, s.bot.CQDeleteMessage(int32(mid))) @@ -447,6 +482,18 @@ var httpApi = map[string]func(s *httpServer, c *gin.Context){ "get_group_member_info": func(s *httpServer, c *gin.Context) { s.GetGroupMemberInfo(c) }, + "get_group_file_system_info": func(s *httpServer, c *gin.Context) { + s.GetGroupFileSystemInfo(c) + }, + "get_group_root_files": func(s *httpServer, c *gin.Context) { + s.GetGroupRootFiles(c) + }, + "get_group_files_by_folder": func(s *httpServer, c *gin.Context) { + s.GetGroupFilesByFolderId(c) + }, + "get_group_file_url": func(s *httpServer, c *gin.Context) { + s.GetGroupFileUrl(c) + }, "send_msg": func(s *httpServer, c *gin.Context) { s.SendMessage(c) }, @@ -504,6 +551,9 @@ var httpApi = map[string]func(s *httpServer, c *gin.Context){ "get_msg": func(s *httpServer, c *gin.Context) { s.GetMessage(c) }, + "get_group_system_msg": func(s *httpServer, c *gin.Context) { + s.GetGroupSystemMessage(c) + }, "get_group_honor_info": func(s *httpServer, c *gin.Context) { s.GetGroupHonorInfo(c) }, diff --git a/server/websocket.go b/server/websocket.go index df3a923..668ea00 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -495,6 +495,21 @@ var wsApi = map[string]func(*coolq.CQBot, gjson.Result) coolq.MSG{ "get_version_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { return bot.CQGetVersionInfo() }, + "get_group_system_msg": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { + return bot.CQGetGroupSystemMessages() + }, + "get_group_file_system_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { + return bot.CQGetGroupFileSystemInfo(p.Get("group_id").Int()) + }, + "get_group_root_files": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { + return bot.CQGetGroupRootFiles(p.Get("group_id").Int()) + }, + "get_group_files_by_folder": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { + return bot.CQGetGroupFilesByFolderId(p.Get("group_id").Int(), p.Get("folder_id").Str) + }, + "get_group_file_url": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { + return bot.CQGetGroupFileUrl(p.Get("group_id").Int(), p.Get("file_id").Str, int32(p.Get("busid").Int())) + }, "_get_vip_info": func(bot *coolq.CQBot, p gjson.Result) coolq.MSG { return bot.CQGetVipInfo(p.Get("user_id").Int()) },