diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8ca988c4..801395151 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,8 @@ on: [push, pull_request,workflow_dispatch] env: BINARY_PREFIX: "go-cqhttp_" BINARY_SUFFIX: "" + COMMIT_ID: "${{ github.sha }}" PR_PROMPT: "::warning:: Build artifact will not be uploaded due to the workflow is trigged by pull request." - LD_FLAGS: "-w -s" jobs: build: @@ -46,6 +46,7 @@ jobs: if $IS_PR ; then echo $PR_PROMPT; fi export BINARY_NAME="$BINARY_PREFIX$GOOS_$GOARCH$BINARY_SUFFIX" export CGO_ENABLED=0 + export LD_FLAGS="-w -s -X github.com/Mrs4s/go-cqhttp/internal/base.Version=${COMMIT_ID::7}" go build -o "output/$BINARY_NAME" -trimpath -ldflags "$LD_FLAGS" . - name: Upload artifact uses: actions/upload-artifact@v2 diff --git a/.github/workflows/golint.yml b/.github/workflows/golint.yml index 624634e4d..5a9ef49c8 100644 --- a/.github/workflows/golint.yml +++ b/.github/workflows/golint.yml @@ -37,4 +37,5 @@ jobs: if: ${{ github.event.pull_request }} uses: reviewdog/action-suggester@v1 with: + github_token: ${{ secrets.GITHUB_TOKEN }} tool_name: golangci-lint diff --git a/.golangci.yml b/.golangci.yml index ca674a0e7..4ff3d91cc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -28,6 +28,7 @@ linters: - errcheck - exportloopref - exhaustive + - bidichk #- funlen #- goconst - gocritic diff --git a/cmd/api-generator/main.go b/cmd/api-generator/main.go index 3a6a73b50..cf3a2367e 100644 --- a/cmd/api-generator/main.go +++ b/cmd/api-generator/main.go @@ -22,10 +22,9 @@ type Param struct { } type Router struct { - Func string - Path string - Aliases []string - Params []Param + Func string + Path []string + Params []Param } type generator struct { @@ -53,9 +52,12 @@ func (g *generator) generate(routers []Router) { } func (g *generator) router(router Router) { - g.WriteString(`case ` + strconv.Quote(router.Path)) - for _, alias := range router.Aliases { - g.WriteString(`, ` + strconv.Quote(alias)) + g.WriteString(`case `) + for i, p := range router.Path { + if i != 0 { + g.WriteString(`, `) + } + g.WriteString(strconv.Quote(p)) } g.WriteString(":\n") @@ -92,10 +94,12 @@ func conv(v, t string) string { return v + ".Bool()" case "string": return v + ".String()" - case "int32", "uint32", "int": + case "int32", "int": return t + "(" + v + ".Int())" case "uint64": return v + ".Uint()" + case "uint32": + return "uint32(" + v + ".Uint())" } } @@ -112,7 +116,7 @@ func main() { for _, decl := range file.Decls { switch decl := decl.(type) { case *ast.FuncDecl: - if decl.Recv == nil { + if !decl.Name.IsExported() || decl.Recv == nil { continue } if st, ok := decl.Recv.List[0].Type.(*ast.StarExpr); !ok || st.X.(*ast.Ident).Name != "CQBot" { @@ -137,12 +141,10 @@ func main() { for _, comment := range decl.Doc.List { annotation, args := match(comment.Text) switch annotation { - case "": - continue case "route": - router.Path = args - case "alias": - router.Aliases = append(router.Aliases, args) + for _, route := range strings.Split(args, ",") { + router.Path = append(router.Path, unquote(route)) + } case "default": for name, value := range parseMap(args, "=") { for i, p := range router.Params { @@ -160,8 +162,11 @@ func main() { } } } + sort.Slice(router.Path, func(i, j int) bool { + return router.Path[i] < router.Path[j] + }) } - if router.Path != "" { + if router.Path != nil { routers = append(routers, router) } else { println(decl.Name.Name) @@ -170,7 +175,7 @@ func main() { } sort.Slice(routers, func(i, j int) bool { - return routers[i].Path < routers[j].Path + return routers[i].Path[0] < routers[j].Path[0] }) out := new(bytes.Buffer) diff --git a/cmd/gocq/main.go b/cmd/gocq/main.go index 30494ee0b..2ce945b3a 100644 --- a/cmd/gocq/main.go +++ b/cmd/gocq/main.go @@ -6,6 +6,7 @@ import ( "crypto/md5" "crypto/sha1" "encoding/hex" + "fmt" "os" "path" "sync" @@ -114,8 +115,6 @@ func Main() { byteKey = []byte(arg[p]) para.Hide(p) } - case "faststart": - base.FastStart = true } } } @@ -133,9 +132,8 @@ func Main() { log.SetLevel(log.DebugLevel) log.SetReportCaller(true) log.Warnf("已开启Debug模式.") - log.Debugf("开发交流群: 192548878") + // log.Debugf("开发交流群: 192548878") } - log.Info("用户交流群: 721829413") if !global.PathExists("device.json") { log.Warn("虚拟设备信息不存在, 将自动生成随机设备.") client.GenRandomDevice() @@ -205,21 +203,7 @@ func Main() { time.Sleep(time.Second * 5) } log.Info("开始尝试登录并同步消息...") - log.Infof("使用协议: %v", func() string { - switch client.SystemDeviceInfo.Protocol { - case client.IPad: - return "iPad" - case client.AndroidPhone: - return "Android Phone" - case client.AndroidWatch: - return "Android Watch" - case client.MacOS: - return "MacOS" - case client.QiDian: - return "企点" - } - return "未知" - }()) + log.Infof("使用协议: %s", client.SystemDeviceInfo.Protocol) cli = newClient() isQRCodeLogin := (base.Account.Uin == 0 || len(base.Account.Password) == 0) && !base.Account.Encrypt isTokenLogin := false @@ -279,7 +263,7 @@ func Main() { reLoginLock.Lock() defer reLoginLock.Unlock() times = 1 - if cli.Online { + if cli.Online.Load() { return } log.Warnf("Bot已离线: %v", e.Message) @@ -299,7 +283,7 @@ func Main() { } else { time.Sleep(time.Second) } - if cli.Online { + if cli.Online.Load() { log.Infof("登录已完成") break } @@ -407,6 +391,13 @@ func newClient() *client.QQClient { log.Error("Protocol -> " + e.Message) case "DEBUG": log.Debug("Protocol -> " + e.Message) + case "DUMP": + if !global.PathExists(global.DumpsPath) { + _ = os.MkdirAll(global.DumpsPath, 0o755) + } + dumpFile := path.Join(global.DumpsPath, fmt.Sprintf("%v.dump", time.Now().Unix())) + log.Errorf("出现错误 %v. 详细信息已转储至文件 %v 请连同日志提交给开发者处理", e.Message, dumpFile) + _ = os.WriteFile(dumpFile, e.Dump, 0o644) } }) return c diff --git a/coolq/api.go b/coolq/api.go index f63c4e839..2e9677dc6 100644 --- a/coolq/api.go +++ b/coolq/api.go @@ -12,8 +12,11 @@ import ( "runtime" "strconv" "strings" + "sync" "time" + "github.com/segmentio/asm/base64" + "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/MiraiGo/message" @@ -29,6 +32,19 @@ import ( "github.com/Mrs4s/go-cqhttp/modules/filter" ) +type guildMemberPageToken struct { + guildID uint64 + nextIndex uint32 + nextRoleID uint64 + nextQueryParam string +} + +var defaultPageToken = &guildMemberPageToken{ + guildID: 0, + nextIndex: 0, + nextRoleID: 2, +} + // CQGetLoginInfo 获取登录号信息 // // https://git.io/Jtz1I @@ -116,7 +132,7 @@ func (bot *CQBot) CQGetGuildChannelList(guildID uint64, noCache bool) global.MSG if noCache { channels, err := bot.Client.GuildService.FetchChannelList(guildID) if err != nil { - log.Errorf("获取频道 %v 子频道列表时出现错误: %v", guildID, err) + log.Warnf("获取频道 %v 子频道列表时出现错误: %v", guildID, err) return Failed(100, "API_ERROR", err.Error()) } guild.Channels = channels @@ -128,24 +144,83 @@ func (bot *CQBot) CQGetGuildChannelList(guildID uint64, noCache bool) global.MSG return OK(channels) } -/* // CQGetGuildMembers 获取频道成员列表 -// @route(get_guild_members) -func (bot *CQBot) CQGetGuildMembers(guildID uint64) global.MSG { +// @route(get_guild_member_list) +func (bot *CQBot) CQGetGuildMembers(guildID uint64, nextToken string) global.MSG { guild := bot.Client.GuildService.FindGuild(guildID) if guild == nil { return Failed(100, "GUILD_NOT_FOUND") } - return OK(nil) // todo + token := defaultPageToken + if nextToken != "" { + i, exists := bot.nextTokenCache.Get(nextToken) + if !exists { + return Failed(100, "NEXT_TOKEN_NOT_EXISTS") + } + token = i.(*guildMemberPageToken) + if token.guildID != guildID { + return Failed(100, "GUILD_NOT_MATCH") + } + } + ret, err := bot.Client.GuildService.FetchGuildMemberListWithRole(guildID, 0, token.nextIndex, token.nextRoleID, token.nextQueryParam) + if err != nil { + return Failed(100, "API_ERROR", err.Error()) + } + res := global.MSG{ + "members": convertGuildMemberInfo(ret.Members), + "finished": ret.Finished, + "next_token": nil, + } + if !ret.Finished { + next := &guildMemberPageToken{ + guildID: guildID, + nextIndex: ret.NextIndex, + nextRoleID: ret.NextRoleId, + nextQueryParam: ret.NextQueryParam, + } + id := base64.StdEncoding.EncodeToString(binary.NewWriterF(func(w *binary.Writer) { + w.WriteUInt64(uint64(time.Now().UnixNano())) + w.WriteString(utils.RandomString(5)) + })) + bot.nextTokenCache.Add(id, next, time.Minute*10) + res["next_token"] = id + } + return OK(res) +} + +// CQGetGuildMemberProfile 获取频道成员资料 +// @route(get_guild_member_profile) +func (bot *CQBot) CQGetGuildMemberProfile(guildID, userID uint64) global.MSG { + if bot.Client.GuildService.FindGuild(guildID) == nil { + return Failed(100, "GUILD_NOT_FOUND") + } + profile, err := bot.Client.GuildService.FetchGuildMemberProfileInfo(guildID, userID) + if err != nil { + log.Warnf("获取频道 %v 成员 %v 资料时出现错误: %v", guildID, userID, err) + return Failed(100, "API_ERROR", err.Error()) + } + roles := make([]global.MSG, 0, len(profile.Roles)) + for _, role := range profile.Roles { + roles = append(roles, global.MSG{ + "role_id": fU64(role.RoleId), + "role_name": role.RoleName, + }) + } + return OK(global.MSG{ + "tiny_id": fU64(profile.TinyId), + "nickname": profile.Nickname, + "avatar_url": profile.AvatarUrl, + "join_time": profile.JoinTime, + "roles": roles, + }) } -*/ // CQGetGuildRoles 获取频道角色列表 // @route(get_guild_roles) func (bot *CQBot) CQGetGuildRoles(guildID uint64) global.MSG { r, err := bot.Client.GuildService.GetGuildRoles(guildID) if err != nil { - log.Errorf("获取频道 %v 角色列表时出现错误: %v", guildID, err) + log.Warnf("获取频道 %v 角色列表时出现错误: %v", guildID, err) return Failed(100, "API_ERROR", err.Error()) } roles := make([]global.MSG, len(r)) @@ -175,7 +250,7 @@ func (bot *CQBot) CQCreateGuildRole(guildID uint64, name string, color uint32, i } role, err := bot.Client.GuildService.CreateGuildRole(guildID, name, color, independent, userSlice) if err != nil { - log.Errorf("创建频道 %v 角色时出现错误: %v", guildID, err) + log.Warnf("创建频道 %v 角色时出现错误: %v", guildID, err) return Failed(100, "API_ERROR", err.Error()) } return OK(global.MSG{ @@ -188,7 +263,7 @@ func (bot *CQBot) CQCreateGuildRole(guildID uint64, name string, color uint32, i func (bot *CQBot) CQDeleteGuildRole(guildID uint64, roleID uint64) global.MSG { err := bot.Client.GuildService.DeleteGuildRole(guildID, roleID) if err != nil { - log.Errorf("删除频道 %v 角色时出现错误: %v", guildID, err) + log.Warnf("删除频道 %v 角色时出现错误: %v", guildID, err) return Failed(100, "API_ERROR", err.Error()) } return OK(nil) @@ -205,7 +280,7 @@ func (bot *CQBot) CQSetGuildMemberRole(guildID uint64, set bool, roleID uint64, } err := bot.Client.GuildService.SetUserRoleInGuild(guildID, set, roleID, userSlice) if err != nil { - log.Errorf("设置用户在频道 %v 中的角色时出现错误: %v", guildID, err) + log.Warnf("设置用户在频道 %v 中的角色时出现错误: %v", guildID, err) return Failed(100, "API_ERROR", err.Error()) } return OK(nil) @@ -216,7 +291,7 @@ func (bot *CQBot) CQSetGuildMemberRole(guildID uint64, set bool, roleID uint64, func (bot *CQBot) CQModifyRoleInGuild(guildID uint64, roleID uint64, name string, color uint32, indepedent bool) global.MSG { err := bot.Client.GuildService.ModifyRoleInGuild(guildID, roleID, name, color, indepedent) if err != nil { - log.Errorf("修改频道 %v 角色时出现错误: %v", guildID, err) + log.Warnf("修改频道 %v 角色时出现错误: %v", guildID, err) return Failed(100, "API_ERROR", err.Error()) } return OK(nil) @@ -238,7 +313,7 @@ func (bot *CQBot) CQGetTopicChannelFeeds(guildID, channelID uint64) global.MSG { } feeds, err := bot.Client.GuildService.GetTopicChannelFeeds(guildID, channelID) if err != nil { - log.Errorf("获取频道 %v 帖子时出现错误: %v", channelID, err) + log.Warnf("获取频道 %v 帖子时出现错误: %v", channelID, err) return Failed(100, "API_ERROR", err.Error()) } c := make([]global.MSG, 0, len(feeds)) @@ -270,7 +345,7 @@ func (bot *CQBot) CQGetFriendList() global.MSG { func (bot *CQBot) CQGetUnidirectionalFriendList() global.MSG { list, err := bot.Client.GetUnidirectionalFriendList() if err != nil { - log.Errorf("获取单向好友列表时出现错误: %v", err) + log.Warnf("获取单向好友列表时出现错误: %v", err) return Failed(100, "API_ERROR", err.Error()) } fs := make([]global.MSG, 0, len(list)) @@ -291,13 +366,13 @@ func (bot *CQBot) CQGetUnidirectionalFriendList() global.MSG { func (bot *CQBot) CQDeleteUnidirectionalFriend(uin int64) global.MSG { list, err := bot.Client.GetUnidirectionalFriendList() if err != nil { - log.Errorf("获取单向好友列表时出现错误: %v", err) + log.Warnf("获取单向好友列表时出现错误: %v", err) return Failed(100, "API_ERROR", err.Error()) } for _, f := range list { if f.Uin == uin { if err = bot.Client.DeleteUnidirectionalFriend(uin); err != nil { - log.Errorf("删除单向好友时出现错误: %v", err) + log.Warnf("删除单向好友时出现错误: %v", err) return Failed(100, "API_ERROR", err.Error()) } return OK(nil) @@ -314,7 +389,7 @@ func (bot *CQBot) CQDeleteFriend(uin int64) global.MSG { return Failed(100, "FRIEND_NOT_FOUND", "好友不存在") } if err := bot.Client.DeleteFriend(uin); err != nil { - log.Errorf("删除好友时出现错误: %v", err) + log.Warnf("删除好友时出现错误: %v", err) return Failed(100, "DELETE_API_ERROR", err.Error()) } return OK(nil) @@ -442,7 +517,7 @@ func (bot *CQBot) CQGetGroupMemberInfo(groupID, userID int64, noCache bool) glob func (bot *CQBot) CQGetGroupFileSystemInfo(groupID int64) global.MSG { fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { - log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) + log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } return OK(fs) @@ -455,12 +530,12 @@ func (bot *CQBot) CQGetGroupFileSystemInfo(groupID int64) global.MSG { func (bot *CQBot) CQGetGroupRootFiles(groupID int64) global.MSG { fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { - log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) + log.Warnf("获取群 %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) + log.Warnf("获取群 %v 根目录文件失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } return OK(global.MSG{ @@ -476,12 +551,12 @@ func (bot *CQBot) CQGetGroupRootFiles(groupID int64) global.MSG { func (bot *CQBot) CQGetGroupFilesByFolderID(groupID int64, folderID string) global.MSG { fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { - log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) + log.Warnf("获取群 %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) + log.Warnf("获取群 %v 根目录 %v 子文件失败: %v", groupID, folderID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } return OK(global.MSG{ @@ -494,6 +569,7 @@ func (bot *CQBot) CQGetGroupFilesByFolderID(groupID int64, folderID string) glob // // 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 // @route(get_group_file_url) +// @rename(bus_id->"[busid\x2Cbus_id].0") func (bot *CQBot) CQGetGroupFileURL(groupID int64, fileID string, busID int32) global.MSG { url := bot.Client.GetGroupFileUrl(groupID, fileID, busID) if url == "" { @@ -510,19 +586,19 @@ func (bot *CQBot) CQGetGroupFileURL(groupID int64, fileID string, busID int32) g // @route(upload_group_file) func (bot *CQBot) CQUploadGroupFile(groupID int64, file, name, folder string) global.MSG { if !global.PathExists(file) { - log.Errorf("上传群文件 %v 失败: 文件不存在", file) + log.Warnf("上传群文件 %v 失败: 文件不存在", file) return Failed(100, "FILE_NOT_FOUND", "文件不存在") } fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { - log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) + log.Warnf("获取群 %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) + log.Warnf("上传群 %v 文件 %v 失败: %v", groupID, file, err) return Failed(100, "FILE_SYSTEM_UPLOAD_API_ERROR", err.Error()) } return OK(nil) @@ -534,11 +610,11 @@ func (bot *CQBot) CQUploadGroupFile(groupID int64, file, name, folder string) gl func (bot *CQBot) CQGroupFileCreateFolder(groupID int64, parentID, name string) global.MSG { fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { - log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) + log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } if err = fs.CreateFolder(parentID, name); err != nil { - log.Errorf("创建群 %v 文件夹失败: %v", groupID, err) + log.Warnf("创建群 %v 文件夹失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } return OK(nil) @@ -551,11 +627,11 @@ func (bot *CQBot) CQGroupFileCreateFolder(groupID int64, parentID, name string) func (bot *CQBot) CQGroupFileDeleteFolder(groupID int64, id string) global.MSG { fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { - log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) + log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } if err = fs.DeleteFolder(id); err != nil { - log.Errorf("删除群 %v 文件夹 %v 时出现文件: %v", groupID, id, err) + log.Warnf("删除群 %v 文件夹 %v 时出现文件: %v", groupID, id, err) return Failed(200, "FILE_SYSTEM_API_ERROR", err.Error()) } return OK(nil) @@ -564,15 +640,15 @@ func (bot *CQBot) CQGroupFileDeleteFolder(groupID int64, id string) global.MSG { // CQGroupFileDeleteFile 拓展API-删除群文件 // // @route(delete_group_file) -// @rename(id->file_id) +// @rename(id->file_id, bus_id->"[busid\x2Cbus_id].0") func (bot *CQBot) CQGroupFileDeleteFile(groupID int64, id string, busID int32) global.MSG { fs, err := bot.Client.GetGroupFileSystem(groupID) if err != nil { - log.Errorf("获取群 %v 文件系统信息失败: %v", groupID, err) + log.Warnf("获取群 %v 文件系统信息失败: %v", groupID, err) return Failed(100, "FILE_SYSTEM_API_ERROR", err.Error()) } if res := fs.DeleteFile("", id, busID); res != "" { - log.Errorf("删除群 %v 文件 %v 时出现文件: %v", groupID, id, res) + log.Warnf("删除群 %v 文件 %v 时出现文件: %v", groupID, id, res) return Failed(200, "FILE_SYSTEM_API_ERROR", res) } return OK(nil) @@ -678,7 +754,7 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R fixAt := func(elem []message.IMessageElement) { for _, e := range elem { if at, ok := e.(*message.AtElement); ok && at.Target != 0 && at.Display == "" { - mem, _ := bot.Client.GuildService.GetGuildMemberProfileInfo(guildID, uint64(at.Target)) + mem, _ := bot.Client.GuildService.FetchGuildMemberProfileInfo(guildID, uint64(at.Target)) if mem != nil { at.Display = "@" + mem.Nickname } else { @@ -712,43 +788,33 @@ func (bot *CQBot) CQSendGuildChannelMessage(guildID, channelID uint64, m gjson.R return OK(global.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 -// @route(send_group_forward_msg) -// @rename(m->messages) -func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) global.MSG { - if m.Type != gjson.JSON { - return Failed(100) - } - fm := message.NewForwardMessage() +func (bot *CQBot) uploadForwardElement(m gjson.Result, groupID int64) *message.ForwardElement { ts := time.Now().Add(-time.Minute * 5) - hasCustom := false - m.ForEach(func(_, item gjson.Result) bool { - if item.Get("data.uin").Exists() || item.Get("data.user_id").Exists() { - hasCustom = true - return false - } - return true - }) + fm := message.NewForwardMessage() + var lazyUpload []func() + var wg sync.WaitGroup resolveElement := func(elems []message.IMessageElement) []message.IMessageElement { for i, elem := range elems { - switch elem.(type) { + iescape := i + switch o := elem.(type) { case *LocalImageElement, *LocalVideoElement: - gm, err := bot.uploadMedia(elem, groupID, true) - if err != nil { - log.Warnf("警告: 群 %d %s上传失败: %v", groupID, elem.Type().String(), err) - continue - } - elems[i] = gm + wg.Add(1) + lazyUpload = append(lazyUpload, func() { + defer wg.Done() + gm, err := bot.uploadMedia(o, groupID, true) + if err != nil { + log.Warnf("警告: 群 %d %s上传失败: %v", groupID, o.Type().String(), err) + } else { + elems[iescape] = gm + } + }) } } return elems } - var convert func(e gjson.Result) *message.ForwardNode - convert = func(e gjson.Result) *message.ForwardNode { + convert := func(e gjson.Result) *message.ForwardNode { if e.Get("type").Str != "node" { return nil } @@ -762,7 +828,7 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) globa SenderName: m.Attribute.SenderName, Time: func() int32 { msgTime := m.Attribute.Timestamp - if hasCustom && msgTime == 0 { + if msgTime == 0 { return int32(ts.Unix()) } return int32(msgTime) @@ -790,23 +856,16 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) globa return true }) if nested { // 处理嵌套 - nest := message.NewForwardMessage() - for _, item := range c.Array() { - node := convert(item) - if node != nil { - nest.AddNode(node) - } - } - elem := bot.Client.UploadGroupForwardMessage(groupID, nest) + fe := bot.uploadForwardElement(c, groupID) return &message.ForwardNode{ SenderId: uin, SenderName: name, Time: int32(msgTime), - Message: []message.IMessageElement{elem}, + Message: []message.IMessageElement{fe}, } } } - content := bot.ConvertObjectMessage(e.Get("data.content"), MessageSourceGroup) + content := bot.ConvertObjectMessage(c, MessageSourceGroup) if uin != 0 && name != "" && len(content) > 0 { return &message.ForwardNode{ SenderId: uin, @@ -818,6 +877,7 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) globa log.Warnf("警告: 非法 Forward node 将跳过. uin: %v name: %v content count: %v", uin, name, len(content)) return nil } + if m.IsArray() { for _, item := range m.Array() { node := convert(item) @@ -831,8 +891,27 @@ func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) globa fm.AddNode(node) } } - if fm.Length() > 0 { - fe := bot.Client.UploadGroupForwardMessage(groupID, fm) + + for _, upload := range lazyUpload { + go upload() + } + wg.Wait() + + return bot.Client.UploadGroupForwardMessage(groupID, fm) +} + +// 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 +// @route(send_group_forward_msg) +// @rename(m->messages) +func (bot *CQBot) CQSendGroupForwardMessage(groupID int64, m gjson.Result) global.MSG { + if m.Type != gjson.JSON { + return Failed(100) + } + + fe := bot.uploadForwardElement(m, groupID) + if fe != nil { ret := bot.Client.SendGroupForwardMessage(groupID, fe) if ret == nil || ret.Id == -1 { log.Warnf("合并转发(群)消息发送失败: 账号可能被风控.") @@ -1051,14 +1130,14 @@ func (bot *CQBot) CQProcessFriendRequest(flag string, approve bool) global.MSG { func (bot *CQBot) CQProcessGroupRequest(flag, subType, reason string, approve bool) global.MSG { msgs, err := bot.Client.GetGroupSystemMessages() if err != nil { - log.Errorf("获取群系统消息失败: %v", err) + log.Warnf("获取群系统消息失败: %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("处理群系统消息失败: 无法操作已处理的消息.") + log.Warnf("处理群系统消息失败: 无法操作已处理的消息.") return Failed(100, "FLAG_HAS_BEEN_CHECKED", "消息已被处理") } if approve { @@ -1073,7 +1152,7 @@ func (bot *CQBot) CQProcessGroupRequest(flag, subType, reason string, approve bo for _, req := range msgs.InvitedRequests { if strconv.FormatInt(req.RequestId, 10) == flag { if req.Checked { - log.Errorf("处理群系统消息失败: 无法操作已处理的消息.") + log.Warnf("处理群系统消息失败: 无法操作已处理的消息.") return Failed(100, "FLAG_HAS_BEEN_CHECKED", "消息已被处理") } if approve { @@ -1085,7 +1164,7 @@ func (bot *CQBot) CQProcessGroupRequest(flag, subType, reason string, approve bo } } } - log.Errorf("处理群系统消息失败: 消息 %v 不存在.", flag) + log.Warnf("处理群系统消息失败: 消息 %v 不存在.", flag) return Failed(100, "FLAG_NOT_FOUND", "FLAG不存在") } @@ -1349,7 +1428,7 @@ func (bot *CQBot) CQHandleQuickOperation(context, operation gjson.Result) global func (bot *CQBot) CQGetImage(file string) global.MSG { var b []byte var err error - if cache.EnableCacheDB && strings.HasSuffix(file, ".image") { + if strings.HasSuffix(file, ".image") { var f []byte f, err = hex.DecodeString(strings.TrimSuffix(file, ".image")) b = cache.Image.Get(f) @@ -1440,20 +1519,31 @@ func (bot *CQBot) CQGetForwardMessage(resID string) global.MSG { if m == nil { return Failed(100, "MSG_NOT_FOUND", "消息不存在") } - r := make([]global.MSG, 0, len(m.Nodes)) - for _, n := range m.Nodes { - bot.checkMedia(n.Message, 0) - r = append(r, global.MSG{ - "sender": global.MSG{ - "user_id": n.SenderId, - "nickname": n.SenderName, - }, - "time": n.Time, - "content": ToFormattedMessage(n.Message, MessageSource{SourceType: MessageSourceGroup}, false), - }) + + var transformNodes func(nodes []*message.ForwardNode) []global.MSG + transformNodes = func(nodes []*message.ForwardNode) []global.MSG { + r := make([]global.MSG, len(nodes)) + for i, n := range nodes { + bot.checkMedia(n.Message, 0) + content := ToFormattedMessage(n.Message, MessageSource{SourceType: MessageSourceGroup}, false) + if len(n.Message) == 1 { + if forward, ok := n.Message[0].(*message.ForwardMessage); ok { + content = transformNodes(forward.Nodes) + } + } + r[i] = global.MSG{ + "sender": global.MSG{ + "user_id": n.SenderId, + "nickname": n.SenderName, + }, + "time": n.Time, + "content": content, + } + } + return r } return OK(global.MSG{ - "messages": r, + "messages": transformNodes(m.Nodes), }) } @@ -1490,6 +1580,70 @@ func (bot *CQBot) CQGetMessage(messageID int32) global.MSG { return OK(m) } +// CQGetGuildMessage 获取频道消息 +// @route(get_guild_msg) +func (bot *CQBot) CQGetGuildMessage(messageID string, noCache bool) global.MSG { + source, seq := decodeGuildMessageID(messageID) + if source == nil { + log.Warnf("获取消息时出现错误: 无效消息ID") + return Failed(100, "INVALID_MESSAGE_ID", "无效消息ID") + } + m := global.MSG{ + "message_id": messageID, + "message_source": func() string { + if source.SourceType == MessageSourceGuildDirect { + return "direct" + } + return "channel" + }(), + "message_seq": seq, + "guild_id": fU64(source.PrimaryID), + "reactions": []int{}, + } + // nolint: exhaustive + switch source.SourceType { + case MessageSourceGuildChannel: + m["channel_id"] = fU64(source.SubID) + if noCache { + pull, err := bot.Client.GuildService.PullGuildChannelMessage(source.PrimaryID, source.SubID, seq, seq) + if err != nil { + log.Warnf("获取消息时出现错误: %v", err) + return Failed(100, "API_ERROR", err.Error()) + } + if len(m) == 0 { + log.Warnf("获取消息时出现错误: 消息不存在") + return Failed(100, "MSG_NOT_FOUND", "消息不存在") + } + m["time"] = pull[0].Time + m["sender"] = global.MSG{ + "user_id": pull[0].Sender.TinyId, + "tiny_id": fU64(pull[0].Sender.TinyId), + "nickname": pull[0].Sender.Nickname, + } + m["message"] = ToFormattedMessage(pull[0].Elements, *source, false) + m["reactions"] = convertReactions(pull[0].Reactions) + bot.InsertGuildChannelMessage(pull[0]) + } else { + channelMsgByDB, err := db.GetGuildChannelMessageByID(messageID) + if err != nil { + log.Warnf("获取消息时出现错误: %v", err) + return Failed(100, "MSG_NOT_FOUND", "消息不存在") + } + m["time"] = channelMsgByDB.Attribute.Timestamp + m["sender"] = global.MSG{ + "user_id": channelMsgByDB.Attribute.SenderTinyID, + "tiny_id": fU64(channelMsgByDB.Attribute.SenderTinyID), + "nickname": channelMsgByDB.Attribute.SenderName, + } + m["message"] = ToFormattedMessage(bot.ConvertContentMessage(channelMsgByDB.Content, MessageSourceGuildChannel), *source) + } + case MessageSourceGuildDirect: + // todo(mrs4s): 支持 direct 消息 + m["tiny_id"] = fU64(source.SubID) + } + return OK(m) +} + // 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 @@ -1580,8 +1734,7 @@ func (bot *CQBot) CQCanSendRecord() global.MSG { // CQOcrImage 扩展API-图片OCR // // https://docs.go-cqhttp.org/api/#%E5%9B%BE%E7%89%87-ocr -// @route(ocr_image) -// @alias(.ocr_image) +// @route(ocr_image,".ocr_image") // @rename(image_id->image) func (bot *CQBot) CQOcrImage(imageID string) global.MSG { img, err := bot.makeImageOrVideoElem(map[string]string{"file": imageID}, false, MessageSourceGroup) @@ -1649,8 +1802,8 @@ func (bot *CQBot) CQGetStatus() global.MSG { "app_enabled": true, "plugins_good": nil, "app_good": true, - "online": bot.Client.Online, - "good": bot.Client.Online, + "online": bot.Client.Online.Load(), + "good": bot.Client.Online.Load(), "stat": bot.Client.GetStatistics(), }) } @@ -1748,7 +1901,7 @@ func (bot *CQBot) CQGetVersionInfo() global.MSG { "version": base.Version, "protocol": func() int { switch client.SystemDeviceInfo.Protocol { - case client.IPad: + case client.Unset, client.IPad: return 0 case client.AndroidPhone: return 1 diff --git a/coolq/bot.go b/coolq/bot.go index 384697c35..099be8aa7 100644 --- a/coolq/bot.go +++ b/coolq/bot.go @@ -12,15 +12,15 @@ import ( "sync" "time" - "github.com/Mrs4s/go-cqhttp/db" - "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/utils" "github.com/pkg/errors" + "github.com/segmentio/asm/base64" log "github.com/sirupsen/logrus" + "github.com/Mrs4s/go-cqhttp/db" "github.com/Mrs4s/go-cqhttp/global" "github.com/Mrs4s/go-cqhttp/internal/base" ) @@ -34,6 +34,7 @@ type CQBot struct { friendReqCache sync.Map tempSessionCache sync.Map + nextTokenCache *utils.Cache } // Event 事件 @@ -67,7 +68,8 @@ func (e *Event) JSONString() string { // NewQQBot 初始化一个QQBot实例 func NewQQBot(cli *client.QQClient) *CQBot { bot := &CQBot{ - Client: cli, + Client: cli, + nextTokenCache: utils.NewCache(time.Second * 10), } bot.Client.OnPrivateMessage(bot.privateMessageEvent) bot.Client.OnGroupMessage(bot.groupMessageEvent) @@ -78,6 +80,7 @@ func NewQQBot(cli *client.QQClient) *CQBot { bot.Client.OnTempMessage(bot.tempMessageEvent) bot.Client.GuildService.OnGuildChannelMessage(bot.guildChannelMessageEvent) bot.Client.GuildService.OnGuildMessageReactionsUpdated(bot.guildMessageReactionsUpdatedEvent) + bot.Client.GuildService.OnGuildMessageRecalled(bot.guildChannelMessageRecalledEvent) bot.Client.GuildService.OnGuildChannelUpdated(bot.guildChannelUpdatedEvent) bot.Client.GuildService.OnGuildChannelCreated(bot.guildChannelCreatedEvent) bot.Client.GuildService.OnGuildChannelDestroyed(bot.guildChannelDestroyedEvent) @@ -311,6 +314,10 @@ func (bot *CQBot) SendPrivateMessage(target int64, groupID int64, m *message.Sen id = bot.InsertPrivateMessage(msg) } case ok || groupID != 0: // 临时会话 + if !base.AllowTempSession { + log.Warnf("发送临时会话消息失败: 已关闭临时会话信息发送功能") + return -1 + } switch { case groupID != 0 && bot.Client.FindGroup(groupID) == nil: log.Errorf("错误: 找不到群(%v)", groupID) @@ -373,7 +380,11 @@ func (bot *CQBot) SendGuildChannelMessage(guildID, channelID uint64, m *message. } e = n - case *LocalVoiceElement, *PokeElement, *message.MusicShareElement: + case *message.MusicShareElement: + bot.Client.SendGuildMusicShare(guildID, channelID, i) + return "-1" // todo: fix this + + case *LocalVoiceElement, *PokeElement: log.Warnf("警告: 频道暂不支持发送 %v 消息", i.Type().String()) continue } @@ -510,9 +521,31 @@ func (bot *CQBot) InsertTempMessage(target int64, m *message.TempMessage) int32 } */ +// InsertGuildChannelMessage 频道消息入数据库 +func (bot *CQBot) InsertGuildChannelMessage(m *message.GuildChannelMessage) string { + id := encodeGuildMessageID(m.GuildId, m.ChannelId, m.Id, MessageSourceGuildChannel) + msg := &db.StoredGuildChannelMessage{ + ID: id, + Attribute: &db.StoredGuildMessageAttribute{ + MessageSeq: m.Id, + InternalID: m.InternalId, + SenderTinyID: m.Sender.TinyId, + SenderName: m.Sender.Nickname, + Timestamp: m.Time, + }, + GuildID: m.GuildId, + ChannelID: m.ChannelId, + Content: ToMessageContent(m.Elements), + } + if err := db.InsertGuildChannelMessage(msg); err != nil { + log.Warnf("记录聊天数据时出现错误: %v", err) + return "" + } + return msg.ID +} + // Release 释放Bot实例 func (bot *CQBot) Release() { - } func (bot *CQBot) dispatchEventMessage(m global.MSG) { @@ -540,7 +573,9 @@ func (bot *CQBot) dispatchEventMessage(m global.MSG) { }(f) } wg.Wait() - global.PutBuffer(event.buffer) + if event.buffer != nil { + global.PutBuffer(event.buffer) + } } func formatGroupName(group *client.GroupInfo) string { @@ -579,3 +614,30 @@ func encodeMessageID(target int64, seq int32) string { w.WriteUInt32(uint32(seq)) })) } + +// encodeGuildMessageID 将频道信息编码为字符串 +// 当信息来源为 Channel 时 primaryID 为 guildID , subID 为 channelID +// 当信息来源为 Direct 时 primaryID 为 guildID , subID 为 tinyID +func encodeGuildMessageID(primaryID, subID, seq uint64, source MessageSourceType) string { + return base64.StdEncoding.EncodeToString(binary.NewWriterF(func(w *binary.Writer) { + w.WriteByte(byte(source)) + w.WriteUInt64(primaryID) + w.WriteUInt64(subID) + w.WriteUInt64(seq) + })) +} + +func decodeGuildMessageID(id string) (source *MessageSource, seq uint64) { + b, _ := base64.StdEncoding.DecodeString(id) + if len(b) < 25 { + return + } + r := binary.NewReader(b) + source = &MessageSource{ + SourceType: MessageSourceType(r.ReadByte()), + PrimaryID: uint64(r.ReadInt64()), + SubID: uint64(r.ReadInt64()), + } + seq = uint64(r.ReadInt64()) + return +} diff --git a/coolq/converter.go b/coolq/converter.go index bd53eb283..44bc17cc5 100644 --- a/coolq/converter.go +++ b/coolq/converter.go @@ -52,13 +52,17 @@ func convertGroupMemberInfo(groupID int64, m *client.GroupMemberInfo) global.MSG } } -func convertGuildMemberInfo(m *client.GuildMemberInfo) global.MSG { - return global.MSG{ - "tiny_id": fU64(m.TinyId), - "title": m.Title, - "nickname": m.Nickname, - "role": m.Role, +func convertGuildMemberInfo(m []*client.GuildMemberInfo) (r []global.MSG) { + for _, mem := range m { + r = append(r, global.MSG{ + "tiny_id": fU64(mem.TinyId), + "title": mem.Title, + "nickname": mem.Nickname, + "role_id": fU64(mem.Role), + "role_name": mem.RoleName, + }) } + return } func (bot *CQBot) formatGroupMessage(m *message.GroupMessage) global.MSG { @@ -150,7 +154,6 @@ func convertChannelInfo(c *client.ChannelInfo) global.MSG { "channel_type": c.ChannelType, "channel_name": c.ChannelName, "owner_guild_id": fU64(c.Meta.GuildId), - "creator_id": c.Meta.CreatorUin, "creator_tiny_id": fU64(c.Meta.CreatorTinyId), "create_time": c.Meta.CreateTime, "current_slow_mode": c.Meta.CurrentSlowMode, @@ -202,6 +205,21 @@ func convertChannelFeedInfo(f *topic.Feed) global.MSG { return m } +func convertReactions(reactions []*message.GuildMessageEmojiReaction) (r []global.MSG) { + r = make([]global.MSG, len(reactions)) + for i, re := range reactions { + r[i] = global.MSG{ + "emoji_id": re.EmojiId, + "emoji_index": re.Face.Index, + "emoji_type": re.EmojiType, + "emoji_name": re.Face.Name, + "count": re.Count, + "clicked": re.Clicked, + } + } + return +} + func fU64(v uint64) string { return strconv.FormatUint(v, 10) } diff --git a/coolq/cqcode.go b/coolq/cqcode.go index 7315cde6d..80d591360 100644 --- a/coolq/cqcode.go +++ b/coolq/cqcode.go @@ -72,13 +72,14 @@ type MessageSource struct { } // MessageSourceType 消息来源类型 -type MessageSourceType int32 +type MessageSourceType byte // MessageSourceType 常量 const ( - MessageSourcePrivate MessageSourceType = 0 - MessageSourceGroup MessageSourceType = 1 - MessageSourceGuildChannel MessageSourceType = 2 + MessageSourcePrivate MessageSourceType = 1 << iota + MessageSourceGroup + MessageSourceGuildChannel + MessageSourceGuildDirect ) const ( @@ -110,7 +111,7 @@ func ToArrayMessage(e []message.IMessageElement, source MessageSource) (r []glob _, ok := e.(*message.ReplyElement) return ok }) - if reply != nil && source.SourceType == MessageSourceGroup { + if reply != nil && source.SourceType&(MessageSourceGroup|MessageSourcePrivate) != 0 { replyElem := reply.(*message.ReplyElement) rid := int64(source.PrimaryID) if rid == 0 { @@ -276,7 +277,7 @@ func ToStringMessage(e []message.IMessageElement, source MessageSource, isRaw .. _, ok := e.(*message.ReplyElement) return ok }) - if reply != nil && source.SourceType == MessageSourceGroup { + if reply != nil && source.SourceType&(MessageSourceGroup|MessageSourcePrivate) != 0 { replyElem := reply.(*message.ReplyElement) rid := int64(source.PrimaryID) if rid == 0 { @@ -413,7 +414,7 @@ func ToMessageContent(e []message.IMessageElement) (r []global.MSG) { case *message.RedBagElement: m = global.MSG{ "type": "redbag", - "data": global.MSG{"title": o.Title, "type": o.MsgType}, + "data": global.MSG{"title": o.Title, "type": int(o.MsgType)}, } case *message.ForwardElement: m = global.MSG{ @@ -448,6 +449,11 @@ func ToMessageContent(e []message.IMessageElement) (r []global.MSG) { "type": "image", "data": data, } + case *message.GuildImageElement: + m = global.MSG{ + "type": "image", + "data": global.MSG{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url}, + } case *message.FriendImageElement: data := global.MSG{"file": hex.EncodeToString(o.Md5) + ".image", "url": o.Url} if o.Flash { @@ -666,7 +672,7 @@ func (bot *CQBot) ConvertObjectMessage(m gjson.Result, sourceType MessageSourceT d := make(map[string]string) convertElem := func(e gjson.Result) { t := e.Get("type").Str - if t == "reply" && sourceType == MessageSourceGroup { + if t == "reply" && sourceType&(MessageSourceGroup|MessageSourcePrivate) != 0 { if len(r) > 0 { if _, ok := r[0].(*message.ReplyElement); ok { log.Warnf("警告: 一条信息只能包含一个 Reply 元素.") @@ -1303,7 +1309,7 @@ func (bot *CQBot) makeImageOrVideoElem(d map[string]string, video bool, sourceTy } rawPath := path.Join(global.ImagePath, f) if video { - if strings.HasSuffix(f, ".video") && cache.EnableCacheDB { + if strings.HasSuffix(f, ".video") { hash, err := hex.DecodeString(strings.TrimSuffix(f, ".video")) if err == nil { if b := cache.Video.Get(hash); b != nil { @@ -1328,7 +1334,7 @@ func (bot *CQBot) makeImageOrVideoElem(d map[string]string, video bool, sourceTy return &LocalImageElement{File: cacheFile}, nil } } - if strings.HasSuffix(f, ".image") && cache.EnableCacheDB { + if strings.HasSuffix(f, ".image") { hash, err := hex.DecodeString(strings.TrimSuffix(f, ".image")) if err == nil { if b := cache.Image.Get(hash); b != nil { diff --git a/coolq/event.go b/coolq/event.go index 82a6fe15f..d89707dbe 100644 --- a/coolq/event.go +++ b/coolq/event.go @@ -155,21 +155,22 @@ func (bot *CQBot) guildChannelMessageEvent(c *client.QQClient, m *message.GuildC SubID: m.ChannelId, } log.Infof("收到来自频道 %v(%v) 子频道 %v(%v) 内 %v(%v) 的消息: %v", guild.GuildName, guild.GuildId, channel.ChannelName, m.ChannelId, m.Sender.Nickname, m.Sender.TinyId, ToStringMessage(m.Elements, source, true)) - // todo: 数据库支持 + id := bot.InsertGuildChannelMessage(m) bot.dispatchEventMessage(global.MSG{ "post_type": "message", "message_type": "guild", "sub_type": "channel", "guild_id": fU64(m.GuildId), "channel_id": fU64(m.ChannelId), - "message_id": fmt.Sprintf("%v-%v", m.Id, m.InternalId), + "message_id": id, "user_id": fU64(m.Sender.TinyId), "message": ToFormattedMessage(m.Elements, source, false), // todo: 增加对频道消息 Reply 的支持 "self_id": bot.Client.Uin, "self_tiny_id": fU64(bot.Client.GuildService.TinyId), "time": m.Time, "sender": global.MSG{ - "user_id": fU64(m.Sender.TinyId), + "user_id": m.Sender.TinyId, + "tiny_id": fU64(m.Sender.TinyId), "nickname": m.Sender.Nickname, }, }) @@ -180,7 +181,8 @@ func (bot *CQBot) guildMessageReactionsUpdatedEvent(c *client.QQClient, e *clien if guild == nil { return } - str := fmt.Sprintf("频道 %v(%v) 消息 %v 表情贴片已更新: ", guild.GuildName, guild.GuildId, e.MessageId) + msgID := encodeGuildMessageID(e.GuildId, e.ChannelId, e.MessageId, MessageSourceGuildChannel) + str := fmt.Sprintf("频道 %v(%v) 消息 %v 表情贴片已更新: ", guild.GuildName, guild.GuildId, msgID) currentReactions := make([]global.MSG, len(e.CurrentReactions)) for i, r := range e.CurrentReactions { str += fmt.Sprintf("%v*%v ", r.Face.Name, r.Count) @@ -198,18 +200,47 @@ func (bot *CQBot) guildMessageReactionsUpdatedEvent(c *client.QQClient, e *clien } log.Infof(str) bot.dispatchEventMessage(global.MSG{ - "post_type": "notice", - "notice_type": "message_reactions_updated", - "message_sender_uin": e.MessageSenderUin, - "guild_id": fU64(e.GuildId), - "channel_id": fU64(e.ChannelId), - "message_id": fmt.Sprint(e.MessageId), // todo: 支持数据库后转换为数据库id - "operator_id": fU64(e.OperatorId), - "current_reactions": currentReactions, - "time": time.Now().Unix(), - "self_id": bot.Client.Uin, - "self_tiny_id": fU64(bot.Client.GuildService.TinyId), - "user_id": e.OperatorId, + "post_type": "notice", + "notice_type": "message_reactions_updated", + "guild_id": fU64(e.GuildId), + "channel_id": fU64(e.ChannelId), + "message_id": msgID, + "operator_id": fU64(e.OperatorId), + "current_reactions": currentReactions, + "time": time.Now().Unix(), + "self_id": bot.Client.Uin, + "self_tiny_id": fU64(bot.Client.GuildService.TinyId), + "user_id": e.OperatorId, + }) +} + +func (bot *CQBot) guildChannelMessageRecalledEvent(c *client.QQClient, e *client.GuildMessageRecalledEvent) { + guild := c.GuildService.FindGuild(e.GuildId) + if guild == nil { + return + } + channel := guild.FindChannel(e.ChannelId) + if channel == nil { + return + } + operator, err := c.GuildService.FetchGuildMemberProfileInfo(e.GuildId, e.OperatorId) + if err != nil { + log.Errorf("处理频道撤回事件时出现错误: 获取操作者资料时出现错误 %v", err) + return + } + msgID := encodeGuildMessageID(e.GuildId, e.ChannelId, e.MessageId, MessageSourceGuildChannel) + log.Infof("用户 %v(%v) 撤回了频道 %v(%v) 子频道 %v(%v) 的消息 %v", operator.Nickname, operator.TinyId, guild.GuildName, guild.GuildId, channel.ChannelName, channel.ChannelId, msgID) + bot.dispatchEventMessage(global.MSG{ + "post_type": "notice", + "notice_type": "guild_channel_recall", + "guild_id": fU64(e.GuildId), + "channel_id": fU64(e.ChannelId), + "operator_id": fU64(e.OperatorId), + "message_id": msgID, + "time": time.Now().Unix(), + "self_id": bot.Client.Uin, + "self_tiny_id": fU64(bot.Client.GuildService.TinyId), + "user_id": e.OperatorId, }) } @@ -239,7 +270,7 @@ func (bot *CQBot) guildChannelCreatedEvent(c *client.QQClient, e *client.GuildCh if guild == nil { return } - member, _ := c.GuildService.GetGuildMemberProfileInfo(e.GuildId, e.OperatorId) + member, _ := c.GuildService.FetchGuildMemberProfileInfo(e.GuildId, e.OperatorId) if member == nil { member = &client.GuildUserProfile{Nickname: "未知"} } @@ -263,7 +294,7 @@ func (bot *CQBot) guildChannelDestroyedEvent(c *client.QQClient, e *client.Guild if guild == nil { return } - member, _ := c.GuildService.GetGuildMemberProfileInfo(e.GuildId, e.OperatorId) + member, _ := c.GuildService.FetchGuildMemberProfileInfo(e.GuildId, e.OperatorId) if member == nil { member = &client.GuildUserProfile{Nickname: "未知"} } @@ -695,7 +726,6 @@ func (bot *CQBot) groupDecrease(groupCode, userUin int64, operator *client.Group } func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) { - // TODO(wdvxdr): remove these old cache file in v1.0.0 for _, elem := range e { switch i := elem.(type) { case *message.GroupImageElement: @@ -713,12 +743,8 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) { w.WriteString(i.ImageId) w.WriteString(i.Url) }) - filename := hex.EncodeToString(i.Md5) + ".image" - if cache.EnableCacheDB { - cache.Image.Insert(i.Md5, data) - } else if !global.PathExists(path.Join(global.ImagePath, filename)) { - _ = os.WriteFile(path.Join(global.ImagePath, filename), data, 0o644) - } + cache.Image.Insert(i.Md5, data) + case *message.GuildImageElement: data := binary.NewWriterF(func(w *binary.Writer) { w.Write(i.Md5) @@ -727,14 +753,9 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) { w.WriteString(i.Url) }) filename := hex.EncodeToString(i.Md5) + ".image" - if cache.EnableCacheDB { - cache.Image.Insert(i.Md5, data) - } else if !global.PathExists(path.Join(global.ImagePath, filename)) { - _ = os.WriteFile(path.Join(global.ImagePath, filename), data, 0o644) - } - + cache.Image.Insert(i.Md5, data) if i.Url != "" && !global.PathExists(path.Join(global.ImagePath, "guild-images", filename)) { - if err := global.DownloadFile(i.Url, path.Join(global.ImagePath, "guild-images", filename), -1, map[string]string{}); err != nil { + if err := global.DownloadFile(i.Url, path.Join(global.ImagePath, "guild-images", filename), -1, nil); err != nil { log.Warnf("下载频道图片时出现错误: %v", err) } } @@ -745,12 +766,8 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) { w.WriteString(i.ImageId) w.WriteString(i.Url) }) - filename := hex.EncodeToString(i.Md5) + ".image" - if cache.EnableCacheDB { - cache.Image.Insert(i.Md5, data) - } else if !global.PathExists(path.Join(global.ImagePath, filename)) { - _ = os.WriteFile(path.Join(global.ImagePath, filename), data, 0o644) - } + cache.Image.Insert(i.Md5, data) + case *message.VoiceElement: // todo: don't download original file? i.Name = strings.ReplaceAll(i.Name, "{", "") @@ -773,11 +790,7 @@ func (bot *CQBot) checkMedia(e []message.IMessageElement, sourceID int64) { w.Write(i.Uuid) }) filename := hex.EncodeToString(i.Md5) + ".video" - if cache.EnableCacheDB { - cache.Video.Insert(i.Md5, data) - } else if !global.PathExists(path.Join(global.VideoPath, filename)) { - _ = os.WriteFile(path.Join(global.VideoPath, filename), data, 0o644) - } + cache.Video.Insert(i.Md5, data) i.Name = filename i.Url = bot.Client.GetShortVideoUrl(i.Uuid, i.Md5) } diff --git a/coolq/feed.go b/coolq/feed.go index 0c362df7b..b8b09bc9a 100644 --- a/coolq/feed.go +++ b/coolq/feed.go @@ -20,7 +20,7 @@ func FeedContentsToArrayMessage(contents []topic.IFeedRichContentElement) []glob case *topic.AtElement: m = global.MSG{ "type": "at", - "data": global.MSG{"qq": elem.Id}, + "data": global.MSG{"id": elem.Id, "qq": elem.Id}, } case *topic.EmojiElement: m = global.MSG{ diff --git a/db/database.go b/db/database.go index 21445e299..6495f8236 100644 --- a/db/database.go +++ b/db/database.go @@ -19,11 +19,15 @@ type ( GetGroupMessageByGlobalID(int32) (*StoredGroupMessage, error) // GetPrivateMessageByGlobalID 通过 GlobalID 来获取私聊消息 GetPrivateMessageByGlobalID(int32) (*StoredPrivateMessage, error) + // GetGuildChannelMessageByID 通过 ID 来获取频道消息 + GetGuildChannelMessageByID(string) (*StoredGuildChannelMessage, error) // InsertGroupMessage 向数据库写入新的群消息 InsertGroupMessage(*StoredGroupMessage) error // InsertPrivateMessage 向数据库写入新的私聊消息 InsertPrivateMessage(*StoredPrivateMessage) error + // InsertGuildChannelMessage 向数据库写入新的频道消息 + InsertGuildChannelMessage(*StoredGuildChannelMessage) error } StoredMessage interface { @@ -58,6 +62,16 @@ type ( Content []global.MSG `bson:"content"` } + // StoredGuildChannelMessage 持久化频道消息 + StoredGuildChannelMessage struct { + ID string `bson:"_id"` + Attribute *StoredGuildMessageAttribute `bson:"attribute"` + GuildID uint64 `bson:"guildId"` + ChannelID uint64 `bson:"channelId"` + QuotedInfo *QuotedInfo `bson:"quotedInfo"` + Content []global.MSG `bson:"content"` + } + // StoredMessageAttribute 持久化消息属性 StoredMessageAttribute struct { MessageSeq int32 `bson:"messageSeq"` @@ -67,6 +81,15 @@ type ( Timestamp int64 `bson:"timestamp"` } + // StoredGuildMessageAttribute 持久化频道消息属性 + StoredGuildMessageAttribute struct { + MessageSeq uint64 `bson:"messageSeq"` + InternalID uint64 `bson:"internalId"` + SenderTinyID uint64 `bson:"senderTinyId"` + SenderName string `bson:"senderName"` + Timestamp int64 `bson:"timestamp"` + } + // QuotedInfo 引用回复 QuotedInfo struct { PrevID string `bson:"prevId"` diff --git a/db/leveldb/leveldb.go b/db/leveldb/leveldb.go index ec76d438c..cfe7716e1 100644 --- a/db/leveldb/leveldb.go +++ b/db/leveldb/leveldb.go @@ -5,6 +5,8 @@ import ( "encoding/gob" "path" + "github.com/Mrs4s/MiraiGo/utils" + "github.com/Mrs4s/MiraiGo/binary" "github.com/pkg/errors" "github.com/syndtr/goleveldb/leveldb" @@ -21,16 +23,19 @@ type LevelDBImpl struct { } const ( - group byte = 0x0 - private byte = 0x1 + group byte = 0x0 + private byte = 0x1 + guildChannel byte = 0x2 ) func init() { gob.Register(db.StoredMessageAttribute{}) + gob.Register(db.StoredGuildMessageAttribute{}) gob.Register(db.QuotedInfo{}) gob.Register(global.MSG{}) gob.Register(db.StoredGroupMessage{}) gob.Register(db.StoredPrivateMessage{}) + gob.Register(db.StoredGuildChannelMessage{}) db.Register("leveldb", func(node yaml.Node) db.Database { conf := new(config.LevelDBConfig) @@ -48,7 +53,7 @@ func (ldb *LevelDBImpl) Open() error { WriteBuffer: 128 * opt.KiB, }) if err != nil { - return errors.Wrap(err, "open level ldb error") + return errors.Wrap(err, "open leveldb error") } ldb.db = d return nil @@ -102,6 +107,24 @@ func (ldb *LevelDBImpl) GetPrivateMessageByGlobalID(id int32) (*db.StoredPrivate return p, nil } +func (ldb *LevelDBImpl) GetGuildChannelMessageByID(id string) (*db.StoredGuildChannelMessage, error) { + v, err := ldb.db.Get([]byte(id), nil) + if err != nil { + return nil, errors.Wrap(err, "get value error") + } + r := binary.NewReader(v) + switch r.ReadByte() { + case guildChannel: + g := &db.StoredGuildChannelMessage{} + if err = gob.NewDecoder(bytes.NewReader(r.ReadAvailable())).Decode(g); err != nil { + return nil, errors.Wrap(err, "decode message error") + } + return g, nil + default: + return nil, errors.New("unknown message flag") + } +} + func (ldb *LevelDBImpl) InsertGroupMessage(msg *db.StoredGroupMessage) error { buf := global.NewBuffer() defer global.PutBuffer(buf) @@ -127,3 +150,16 @@ func (ldb *LevelDBImpl) InsertPrivateMessage(msg *db.StoredPrivateMessage) error }), nil) return errors.Wrap(err, "put data error") } + +func (ldb *LevelDBImpl) InsertGuildChannelMessage(msg *db.StoredGuildChannelMessage) error { + buf := global.NewBuffer() + defer global.PutBuffer(buf) + if err := gob.NewEncoder(buf).Encode(msg); err != nil { + return errors.Wrap(err, "encode message error") + } + err := ldb.db.Put(utils.S2B(msg.ID), binary.NewWriterF(func(w *binary.Writer) { + w.WriteByte(guildChannel) + w.Write(buf.Bytes()) + }), nil) + return errors.Wrap(err, "put data error") +} diff --git a/db/mongodb/mongodb.go b/db/mongodb/mongodb.go index 6387d5a33..9c8449d7d 100644 --- a/db/mongodb/mongodb.go +++ b/db/mongodb/mongodb.go @@ -20,8 +20,9 @@ type MongoDBImpl struct { } const ( - MongoGroupMessageCollection = "group-messages" - MongoPrivateMessageCollection = "private-messages" + MongoGroupMessageCollection = "group-messages" + MongoPrivateMessageCollection = "private-messages" + MongoGuildChannelMessageCollection = "guild-channel-messages" ) func init() { @@ -72,14 +73,29 @@ func (m *MongoDBImpl) GetPrivateMessageByGlobalID(id int32) (*db.StoredPrivateMe return &ret, nil } +func (m *MongoDBImpl) GetGuildChannelMessageByID(id string) (*db.StoredGuildChannelMessage, error) { + coll := m.mongo.Collection(MongoGuildChannelMessageCollection) + var ret db.StoredGuildChannelMessage + if err := coll.FindOne(context.Background(), bson.D{{"_id", id}}).Decode(&ret); err != nil { + return nil, errors.Wrap(err, "query error") + } + return &ret, nil +} + func (m *MongoDBImpl) InsertGroupMessage(msg *db.StoredGroupMessage) error { coll := m.mongo.Collection(MongoGroupMessageCollection) - _, err := coll.InsertOne(context.Background(), msg) + _, err := coll.UpdateOne(context.Background(), bson.D{{"_id", msg.ID}}, bson.D{{"$set", msg}}, options.Update().SetUpsert(true)) return errors.Wrap(err, "insert error") } func (m *MongoDBImpl) InsertPrivateMessage(msg *db.StoredPrivateMessage) error { coll := m.mongo.Collection(MongoPrivateMessageCollection) - _, err := coll.InsertOne(context.Background(), msg) + _, err := coll.UpdateOne(context.Background(), bson.D{{"_id", msg.ID}}, bson.D{{"$set", msg}}, options.Update().SetUpsert(true)) + return errors.Wrap(err, "insert error") +} + +func (m *MongoDBImpl) InsertGuildChannelMessage(msg *db.StoredGuildChannelMessage) error { + coll := m.mongo.Collection(MongoGuildChannelMessageCollection) + _, err := coll.UpdateOne(context.Background(), bson.D{{"_id", msg.ID}}, bson.D{{"$set", msg}}, options.Update().SetUpsert(true)) return errors.Wrap(err, "insert error") } diff --git a/db/multidb.go b/db/multidb.go index 741ebf7ba..6d3825ce6 100644 --- a/db/multidb.go +++ b/db/multidb.go @@ -69,6 +69,13 @@ func GetPrivateMessageByGlobalID(id int32) (*StoredPrivateMessage, error) { return backends[0].GetPrivateMessageByGlobalID(id) } +func GetGuildChannelMessageByID(id string) (*StoredGuildChannelMessage, error) { + if len(backends) == 0 { + return nil, DatabaseDisabledError + } + return backends[0].GetGuildChannelMessageByID(id) +} + func InsertGroupMessage(m *StoredGroupMessage) error { for _, b := range backends { if err := b.InsertGroupMessage(m); err != nil { @@ -86,3 +93,12 @@ func InsertPrivateMessage(m *StoredPrivateMessage) error { } return nil } + +func InsertGuildChannelMessage(m *StoredGuildChannelMessage) error { + for _, b := range backends { + if err := b.InsertGuildChannelMessage(m); err != nil { + return errors.Wrap(err, "insert message to backend error") + } + } + return nil +} diff --git a/docs/config.md b/docs/config.md index d3a2df226..3bd136900 100644 --- a/docs/config.md +++ b/docs/config.md @@ -167,6 +167,16 @@ database: # 数据库相关设置 > 注5:关于MIME扫描, 详见[MIME](file.md#MIME) +### 环境变量 + +go-cqhttp 配置文件可以使用占位符来读取**环境变量**的值。 + +```yaml +account: # 账号相关 + uin: ${CQ_UIN} # 读取环境变量 CQ_UIN + password: ${CQ_PWD:123456} # 当 CQ_PWD 为空时使用默认值 123456 +``` + ## 在线状态 | 状态 | 值 | diff --git a/docs/guild.md b/docs/guild.md index f1676729c..9a20285aa 100644 --- a/docs/guild.md +++ b/docs/guild.md @@ -29,6 +29,10 @@ API以及字段相关命名均为参考QQ官方命名或相似产品命名规则 - `at` 消息的 `target` 依然使用 `qq` 字段, 以保证一致性. 但内容为 `tiny_id` - 所有事件的 `self_id` 均为 BOT 的QQ号. `tiny_id` 将放在 `self_tiny_id` 字段 - 遵循我们一贯的原则, 将不会支持主动加频道/主动拉人/红包相关消息类型 +- 频道相关的API仅能在 `Android Phone` 和 `iPad` 协议上使用. +- 由于频道相关ID的数据类型均为 `uint64` , 为保证不超过某些语言的安全值范围, 在 `v1.0.0-beta8-fix3` 以后, 所有ID相关数据将转换为 `string` 类型, API调用 `uint64` + 或 `string` 均可接受. +- 为保证一致性, 所有频道接口返回的 `用户ID` 均命名为 `tiny_id`, 所有频道相关接口的 `用户ID` 入参均命名为 `user_id` ## API @@ -41,7 +45,7 @@ API以及字段相关命名均为参考QQ官方命名或相似产品命名规则 | 字段 | 类型 | 说明 | | ------------- | ----- | ---------- | | `nickname` | string | 昵称 | -| `tiny_id` | uint64 | 自身的ID | +| `tiny_id` | string | 自身的ID | | `avatar_url` | string | 头像链接 | ### 获取频道列表 @@ -56,7 +60,7 @@ GuildInfo: | 字段 | 类型 | 说明 | | ------------- | ----- | ---------- | -| `guild_id` | uint64 | 频道ID | +| `guild_id` | string | 频道ID | | `guild_name` | string | 频道名称 | | `guild_display_id` | int64 | 频道显示ID, 公测后可能作为搜索ID使用 | @@ -68,13 +72,13 @@ GuildInfo: | 字段 | 类型 | 说明 | | ---------- | ----- | ---- | -| `guild_id` | uint64 | 频道ID | +| `guild_id` | string | 频道ID | **响应数据** | 字段 | 类型 | 说明 | | ------------- | ----- | ---------- | -| `guild_id` | uint64 | 频道ID | +| `guild_id` | string | 频道ID | | `guild_name` | string | 频道名称 | | `guild_profile` | string | 频道简介 | | `create_time` | int64 | 创建时间 | @@ -82,7 +86,7 @@ GuildInfo: | `max_robot_count` | int64 | 频道BOT数上限 | | `max_admin_count` | int64 | 频道管理员人数上限 | | `member_count` | int64 | 已加入人数 | -| `owner_id` | uint64 | 创建者ID | +| `owner_id` | string | 创建者ID | ### 获取子频道列表 @@ -92,7 +96,7 @@ GuildInfo: | 字段 | 类型 | 说明 | | ---------- | ----- | ---- | -| `guild_id` | uint64 | 频道ID | +| `guild_id` | string | 频道ID | | `no_cache` | bool | 是否无视缓存 | **响应数据** @@ -103,13 +107,12 @@ ChannelInfo: | 字段 | 类型 | 说明 | | ------------- | ----- | ---------- | -| `owner_guild_id` | uint64 | 所属频道ID | -| `channel_id` | uint64 | 子频道ID | +| `owner_guild_id` | string | 所属频道ID | +| `channel_id` | string | 子频道ID | | `channel_type` | int32 | 子频道类型 | -| `channel_name` | string | 之频道名称 | +| `channel_name` | string | 子频道名称 | | `create_time` | int64 | 创建时间 | -| `creator_id` | int64 | 创建者QQ号 | -| `creator_tiny_id` | uint64 | 创建者ID | +| `creator_tiny_id` | string | 创建者ID | | `talk_permission` | int32 | 发言权限类型 | | `visible_type` | int32 | 可视性类型 | | `current_slow_mode` | int32 | 当前启用的慢速模式Key | @@ -131,35 +134,74 @@ SlowModeInfo: | 1 | 文字频道 | | 2 | 语音频道 | | 5 | 直播频道 | +| 7 | 主题频道 | ### 获取频道成员列表 -终结点: `/get_guild_members` +终结点: `/get_guild_member_list` + +> 由于频道人数较多(数万), 请尽量不要全量拉取成员列表, 这将会导致严重的性能问题 +> +> 尽量使用 `get_guild_member_profile` 接口代替全量拉取 **参数** | 字段 | 类型 | 说明 | | ---------- | ----- | ---- | -| `guild_id` | uint64 | 频道ID | +| `guild_id` | string | 频道ID | +| `next_token` | string | 翻页Token | -**响应数据** +> `next_token` 为空的情况下, 将返回第一页的数据, 并在返回值附带下一页的 `token` -> 注意: 类型内无任何成员将返回 `null` +**响应数据** | 字段 | 类型 | 说明 | | ------------- | ----- | ---------- | -| `members` | []GuildMemberInfo | 普通成员列表 | -| `bots` | []GuildMemberInfo | 机器人列表 | -| `admins` | []GuildMemberInfo | 管理员列表 | +| `members` | []GuildMemberInfo | 成员列表 | +| `finished` | bool | 是否最终页 | +| `next_token` | string | 翻页Token | GuildMemberInfo: | 字段 | 类型 | 说明 | | ------------- | ----- | ---------- | -| `tiny_id` | uint64 | 成员ID | -| `title` | string | 成员头衔 | -| `nickname` | string | 成员昵称 | -| `role` | int32 | 成员权限 | +| `tiny_id` | string | 成员ID | +| `title` | string | 成员头衔 | +| `nickname` | string | 成员昵称 | +| `role_id` | string | 所在权限组ID | +| `role_name` | string | 所在权限组名称 | + +> 默认情况下频道管理员的权限组ID为 `2`, 部分频道可能会另行创建, 需手动判断 +> +> 此接口仅展现最新的权限组, 获取用户加入的所有权限组请使用 `get_guild_member_profile` 接口 + +### 单独获取频道成员信息 + +终结点: `/get_guild_member_profile` + +**参数** + +| 字段 | 类型 | 说明 | +| ---------- | ----- | ---- | +| `guild_id` | string | 频道ID | +| `user_id` | string | 用户ID | + +**响应数据** + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `tiny_id` | string | 用户ID | +| `nickname` | string | 用户昵称 | +| `avatar_url` | string | 头像地址 | +| `join_time` | int64 | 加入时间 | +| `roles` | []RoleInfo | 加入的所有权限组 | + +RoleInfo: + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `role_id` | string | 权限组ID | +| `role_name` | string | 权限组名称 | ### 发送信息到子频道 @@ -169,8 +211,8 @@ GuildMemberInfo: | 字段 | 类型 | 说明 | | ---------- | ----- | ---- | -| `guild_id` | uint64 | 频道ID | -| `channel_id` | uint64 | 子频道ID | +| `guild_id` | string | 频道ID | +| `channel_id` | string | 子频道ID | | `message` | Message | 消息, 与原有消息类型相同 | **响应数据** @@ -179,6 +221,108 @@ GuildMemberInfo: | ------------- | ----- | ---------- | | `message_id` | string | 消息ID | +### 获取话题频道帖子 + +终结点: `/get_topic_channel_feeds` + +**参数** + +| 字段 | 类型 | 说明 | +| ---------- | ----- | ---- | +| `guild_id` | string | 频道ID | +| `channel_id` | string | 子频道ID | + +**响应数据** + +返回 `FeedInfo` 数组 + +FeedInfo: + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `id` | string | 帖子ID | +| `channel_id` | string | 子频道ID | +| `guild_id` | string | 频道ID | +| `create_time` | int64 | 发帖时间 | +| `title` | string | 帖子标题 | +| `sub_title` | string | 帖子副标题 | +| `poster_info` | PosterInfo | 发帖人信息 | +| `resource` | ResourceInfo | 媒体资源信息 | +| `resource.images` | []FeedMedia | 帖子附带的图片列表 | +| `resource.videos` | []FeedMedia | 帖子附带的视频列表 | +| `contents` | []FeedContent | 帖子内容 | + +PosterInfo: + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `tiny_id` | string | 发帖人ID | +| `nickname` | string | 发帖人昵称 | +| `icon_url` | string | 发帖人头像链接 | + +FeedMedia: + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `file_id` | string | 媒体ID | +| `pattern_id` | string | 控件ID?(不确定) | +| `url` | string | 媒体链接 | +| `height` | int32 | 媒体高度 | +| `width` | int32 | 媒体宽度 | + +FeedContent: + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `type` | string | 内容类型 | +| `data` | Data | 内容数据 | + +#### 内容类型列表: + +| 类型 | 说明 | +| ----- | ---------- | +| `text` | 文本 | +| `face` | 表情 | +| `at` | At | +| `url_quote` | 链接引用 | +| `channel_quote` | 子频道引用 | + +#### 内容类型对应数据列表: + +- `text` + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `text` | string | 文本内容 | + +- `face` + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `id` | string | 表情ID | + +- `at` + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `id` | string | 目标ID | +| `qq` | string | 目标ID, 为确保和 `array message` 的一致性保留 | + +- `url_quote` + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `display_text` | string | 显示文本 | +| `url` | string | 链接 | + +- `channel_quote` + +| 字段 | 类型 | 说明 | +| ------------- | ----- | ---------- | +| `display_text` | string | 显示文本 | +| `guild_id` | string | 频道ID | +| `channel_id` | string | 子频道ID | + ## 事件 ### 收到频道消息 @@ -190,13 +334,15 @@ GuildMemberInfo: | `post_type` | string | `message` | 上报类型 | | `message_type` | string | `guild` | 消息类型 | | `sub_type` | string | `channel` | 消息子类型 | -| `guild_id` | uint64 | | 频道ID | -| `channel_id` | uint64 | | 子频道ID | -| `user_id` | uint64 | | 消息发送者ID | +| `guild_id` | string | | 频道ID | +| `channel_id` | string | | 子频道ID | +| `user_id` | string | | 消息发送者ID | | `message_id` | string | | 消息ID | | `sender` | Sender | | 发送者 | | `message` | Message | | 消息内容 | +> 注: 此处的 `Sender` 对象为保证一致性, `user_id` 为 `uint64` 类型, 并添加了 `string` 类型的 `tiny_id` 字段 + ### 频道消息表情贴更新 **上报数据** @@ -205,9 +351,9 @@ GuildMemberInfo: | ------------- | ------ | -------------- | -------------- | | `post_type` | string | `notice` | 上报类型 | | `notice_type` | string | `message_reactions_updated` | 消息类型 | -| `guild_id` | uint64 | | 频道ID | -| `channel_id` | uint64 | | 子频道ID | -| `user_id` | uint64 | | 操作者ID | +| `guild_id` | string | | 频道ID | +| `channel_id` | string | | 子频道ID | +| `user_id` | string | | 操作者ID | | `message_id` | string | | 消息ID | | `current_reactions` | []ReactionInfo | | 当前消息被贴表情列表 | @@ -230,10 +376,10 @@ ReactionInfo: | ------------- | ------ | -------------- | -------------- | | `post_type` | string | `notice` | 上报类型 | | `notice_type` | string | `channel_updated` | 消息类型 | -| `guild_id` | uint64 | | 频道ID | -| `channel_id` | uint64 | | 子频道ID | -| `user_id` | uint64 | | 操作者ID | -| `operator_id` | uint64 | | 操作者ID | +| `guild_id` | string | | 频道ID | +| `channel_id` | string | | 子频道ID | +| `user_id` | string | | 操作者ID | +| `operator_id` | string | | 操作者ID | | `old_info` | ChannelInfo | | 更新前的频道信息 | | `new_info` | ChannelInfo | | 更新后的频道信息 | @@ -245,10 +391,10 @@ ReactionInfo: | ------------- | ------ | -------------- | -------------- | | `post_type` | string | `notice` | 上报类型 | | `notice_type` | string | `channel_created` | 消息类型 | -| `guild_id` | uint64 | | 频道ID | -| `channel_id` | uint64 | | 子频道ID | -| `user_id` | uint64 | | 操作者ID | -| `operator_id` | uint64 | | 操作者ID | +| `guild_id` | string | | 频道ID | +| `channel_id` | string | | 子频道ID | +| `user_id` | string | | 操作者ID | +| `operator_id` | string | | 操作者ID | | `channel_info` | ChannelInfo | | 频道信息 | ### 子频道删除 @@ -259,8 +405,8 @@ ReactionInfo: | ------------- | ------ | -------------- | -------------- | | `post_type` | string | `notice` | 上报类型 | | `notice_type` | string | `channel_destroyed` | 消息类型 | -| `guild_id` | uint64 | | 频道ID | -| `channel_id` | uint64 | | 子频道ID | -| `user_id` | uint64 | | 操作者ID | -| `operator_id` | uint64 | | 操作者ID | +| `guild_id` | string | | 频道ID | +| `channel_id` | string | | 子频道ID | +| `user_id` | string | | 操作者ID | +| `operator_id` | string | | 操作者ID | | `channel_info` | ChannelInfo | | 频道信息 | \ No newline at end of file diff --git a/global/fs.go b/global/fs.go index 91d346b1f..f46934442 100644 --- a/global/fs.go +++ b/global/fs.go @@ -32,6 +32,8 @@ const ( VideoPath = "data/videos" // CachePath go-cqhttp使用的缓存目录 CachePath = "data/cache" + // DumpsPath go-cqhttp使用错误转储目录 + DumpsPath = "dumps" ) var ( diff --git a/global/net.go b/global/net.go index 541c54cae..ce74edbe9 100644 --- a/global/net.go +++ b/global/net.go @@ -71,9 +71,6 @@ func DownloadFile(url, path string, limit int64, headers map[string]string) erro req.Header.Set(k, v) } - if _, ok := headers["User-Agent"]; !ok { - req.Header["User-Agent"] = []string{UserAgent} - } resp, err := client.Do(req) if err != nil { return err diff --git a/global/terminal/double_click_windows.go b/global/terminal/double_click_windows.go index 2ac3186ed..1fa746cf0 100644 --- a/global/terminal/double_click_windows.go +++ b/global/terminal/double_click_windows.go @@ -5,6 +5,7 @@ package terminal import ( "os" + "path/filepath" "syscall" "unsafe" @@ -44,7 +45,10 @@ func NoMoreDoubleClick() error { return errors.Errorf("打开go-cqhttp.bat失败: %v", err) } _ = f.Truncate(0) - _, err = f.WriteString("%Created by go-cqhttp. DO NOT EDIT ME!%\nstart cmd /K go-cqhttp.exe") + + ex, _ := os.Executable() + exPath := filepath.Base(ex) + _, err = f.WriteString("%Created by go-cqhttp. DO NOT EDIT ME!%\nstart cmd /K \"" + exPath + "\"") if err != nil { return errors.Errorf("写入go-cqhttp.bat失败: %v", err) } diff --git a/go.mod b/go.mod index 0d5410f74..852d52aff 100644 --- a/go.mod +++ b/go.mod @@ -5,40 +5,40 @@ go 1.17 require ( github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f github.com/Microsoft/go-winio v0.5.1 - github.com/Mrs4s/MiraiGo v0.0.0-20211208080234-25c67a3ee1c1 + github.com/Mrs4s/MiraiGo v0.0.0-20220209092529-5d071b034c17 + github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc github.com/dustin/go-humanize v1.0.0 github.com/fumiama/go-hide-param v0.1.4 github.com/gabriel-vasile/mimetype v1.4.0 github.com/gocq/qrcode v0.0.0-20211114040510-366b953fcd98 - github.com/gorilla/websocket v1.4.2 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/klauspost/compress v1.13.6 github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible - github.com/mattn/go-colorable v0.1.11 + github.com/mattn/go-colorable v0.1.12 github.com/pkg/errors v0.9.1 - github.com/segmentio/asm v1.1.0 + github.com/segmentio/asm v1.1.3 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 github.com/syndtr/goleveldb v1.0.0 - github.com/tidwall/gjson v1.11.0 + github.com/tidwall/gjson v1.12.1 github.com/wdvxdr1123/go-silk v0.0.0-20210316130616-d47b553def60 - go.mongodb.org/mongo-driver v1.7.4 - golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa + go.mongodb.org/mongo-driver v1.8.1 + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac + golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) require ( - github.com/RomiChan/protobuf v0.0.0-20211204042931-ff4f35848737 // indirect + github.com/RomiChan/protobuf v0.0.0-20211223055824-048df49a8956 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-stack/stack v1.8.1 // indirect + github.com/fumiama/imgsz v0.0.2 // indirect + github.com/go-stack/stack v1.8.0 // indirect github.com/gocq/rs v1.0.1 // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang/snappy v0.0.1 // indirect github.com/google/go-cmp v0.5.5 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.1.0 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/lestrrat-go/strftime v1.0.5 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/pierrec/lz4/v4 v4.1.11 // indirect @@ -50,12 +50,13 @@ require ( github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.0.2 // indirect github.com/xdg-go/stringprep v1.0.2 // indirect - github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + go.uber.org/atomic v1.9.0 // indirect golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 // indirect - golang.org/x/text v0.3.7 // indirect - modernc.org/libc v1.11.70 // indirect - modernc.org/mathutil v1.4.1 // indirect - modernc.org/memory v1.0.5 // indirect + golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect + golang.org/x/text v0.3.6 // indirect + modernc.org/libc v1.8.1 // indirect + modernc.org/mathutil v1.2.2 // indirect + modernc.org/memory v1.0.4 // indirect ) diff --git a/go.sum b/go.sum index 9ea0cba0d..ba4622513 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,13 @@ github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f h1:2dk3eOnYllh+wUOuDhOoC2vUVoJF/5z478ryJ+wzEII= github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f/go.mod h1:4a58ifQTEe2uwwsaqbh3i2un5/CBPg+At/qHpt18Tmk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Mrs4s/MiraiGo v0.0.0-20211208080234-25c67a3ee1c1 h1:UipCzEST10GzJnvlhHsY4g39xzwVzSHE+5Go9d0dTPY= -github.com/Mrs4s/MiraiGo v0.0.0-20211208080234-25c67a3ee1c1/go.mod h1:YD9gBKkxC9lPPtx3doYXRG26VBkK6YXjrS76cv01C5w= -github.com/RomiChan/protobuf v0.0.0-20211204042931-ff4f35848737 h1:p4o7/eSoP39jwnGZz08N1IpH/mNzg9SdCn7kPM9A9BE= -github.com/RomiChan/protobuf v0.0.0-20211204042931-ff4f35848737/go.mod h1:CKKOWC7mBxd36zxsCB1V8DTrwlTNRQvkSVbYqyUiGEE= +github.com/Mrs4s/MiraiGo v0.0.0-20220209092529-5d071b034c17 h1:OQVnlWt5if0TOFoNGDjILazBjVTncSlPxGXabD+4MvE= +github.com/Mrs4s/MiraiGo v0.0.0-20220209092529-5d071b034c17/go.mod h1:rtKLkhMEi2YjsrXaNztT4uagUOPBxf6a+TNREkG097I= +github.com/RomiChan/protobuf v0.0.0-20211223055824-048df49a8956 h1:hnaAkKz4t+xpSNVp5mnuloRMd3Rj2Lfg5biZ3emv//c= +github.com/RomiChan/protobuf v0.0.0-20211223055824-048df49a8956/go.mod h1:CKKOWC7mBxd36zxsCB1V8DTrwlTNRQvkSVbYqyUiGEE= +github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc h1:AAx50/fb/xS4lvsdQg+bFbGvqSDhyV1MF+p2PLCamZ0= +github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc/go.mod h1:OMmITAib6POA37xCichWM0aRnoVpSMZO1rB/G01wrr0= github.com/bits-and-blooms/bitset v1.2.1 h1:M+/hrU9xlMp7t4TyTDQW97d3tRPVuKFC6zBEK16QnXY= github.com/bits-and-blooms/bitset v1.2.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -17,35 +18,12 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fumiama/go-hide-param v0.1.4 h1:y7TRTzZMdCH9GOXnIzU3B+1BSkcmvejVGmGsz4t0DGU= github.com/fumiama/go-hide-param v0.1.4/go.mod h1:vJkQlJIEI56nIyp7tCQu1/2QOyKtZpudsnJkGk9U1aY= +github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak= +github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4= github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gocq/qrcode v0.0.0-20211114040510-366b953fcd98 h1:NJDZEa7gibUa0w4tie8qKeQFKdeKFUbecWyQDPdRx40= github.com/gocq/qrcode v0.0.0-20211114040510-366b953fcd98/go.mod h1:E5TBHc60dsWtOL7sbXCb3P9i4xrj2J7Zm5sEJftIc1w= github.com/gocq/rs v1.0.1 h1:ng7nhXmnx3SnfM0DOqmbP6GmQp1xGwRG9XmBiLFDWuM= @@ -53,36 +31,21 @@ github.com/gocq/rs v1.0.1/go.mod h1:8oaQnRvqn1fMh8i5zsetgQo03OUXksJV1k+dpmExxcY= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s= github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -94,10 +57,8 @@ github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkL github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= github.com/lestrrat-go/strftime v1.0.5 h1:A7H3tT8DhTz8u65w+JRpiBxM4dINQhUXAZnhBa2xeOE= github.com/lestrrat-go/strftime v1.0.5/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -107,10 +68,8 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pierrec/lz4/v4 v4.1.11 h1:LVs17FAZJFOjgmJXl9Tf13WfLUvZq7/RjfEJrnwZ9OE= github.com/pierrec/lz4/v4 v4.1.11/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -118,23 +77,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/segmentio/asm v1.1.0 h1:fkVr8k5J4sKoFjTGVD6r1yKvDKqmvrEh3K7iyVxgBs8= -github.com/segmentio/asm v1.1.0/go.mod h1:4EUJGaKsB8ImLUwOGORVsNd9vTRDeh44JGsY4aKp5I4= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -142,8 +92,9 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/tidwall/gjson v1.11.0 h1:C16pk7tQNiH6VlCrtIXL1w8GaOsi1X3W8KDkE1BuYd4= github.com/tidwall/gjson v1.11.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -157,182 +108,74 @@ github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.mongodb.org/mongo-driver v1.7.4 h1:sllcioag8Mec0LYkftYWq+cKNPIR4Kqq3iv9ZXY0g/E= -go.mongodb.org/mongo-driver v1.7.4/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.mongodb.org/mongo-driver v1.8.1 h1:OZE4Wni/SJlrcmSIBRYNzunX5TKxjrTS4jKSnA99oKU= +go.mongodb.org/mongo-driver v1.8.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 h1:0qxwC5n+ttVOINCBeRHO0nq9X7uy8SDsPoi5OaCdIEI= golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 h1:7NCfEGl0sfUojmX78nK9pBJuUlSZWEJA/TwASvfiPLo= -golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY= +golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.33.11/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.34.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.0/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.4/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.5/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.7/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.8/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g= -modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60= -modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw= -modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI= -modernc.org/ccgo/v3 v3.11.1/go.mod h1:lWHxfsn13L3f7hgGsGlU28D9eUOf6y3ZYHKoPaKU0ag= -modernc.org/ccgo/v3 v3.11.3/go.mod h1:0oHunRBMBiXOKdaglfMlRPBALQqsfrCKXgw9okQ3GEw= -modernc.org/ccgo/v3 v3.12.4/go.mod h1:Bk+m6m2tsooJchP/Yk5ji56cClmN6R1cqc9o/YtbgBQ= -modernc.org/ccgo/v3 v3.12.6/go.mod h1:0Ji3ruvpFPpz+yu+1m0wk68pdr/LENABhTrDkMDWH6c= -modernc.org/ccgo/v3 v3.12.8/go.mod h1:Hq9keM4ZfjCDuDXxaHptpv9N24JhgBZmUG5q60iLgUo= -modernc.org/ccgo/v3 v3.12.11/go.mod h1:0jVcmyDwDKDGWbcrzQ+xwJjbhZruHtouiBEvDfoIsdg= -modernc.org/ccgo/v3 v3.12.14/go.mod h1:GhTu1k0YCpJSuWwtRAEHAol5W7g1/RRfS4/9hc9vF5I= -modernc.org/ccgo/v3 v3.12.18/go.mod h1:jvg/xVdWWmZACSgOiAhpWpwHWylbJaSzayCqNOJKIhs= -modernc.org/ccgo/v3 v3.12.20/go.mod h1:aKEdssiu7gVgSy/jjMastnv/q6wWGRbszbheXgWRHc8= -modernc.org/ccgo/v3 v3.12.21/go.mod h1:ydgg2tEprnyMn159ZO/N4pLBqpL7NOkJ88GT5zNU2dE= -modernc.org/ccgo/v3 v3.12.22/go.mod h1:nyDVFMmMWhMsgQw+5JH6B6o4MnZ+UQNw1pp52XYFPRk= -modernc.org/ccgo/v3 v3.12.25/go.mod h1:UaLyWI26TwyIT4+ZFNjkyTbsPsY3plAEB6E7L/vZV3w= -modernc.org/ccgo/v3 v3.12.29/go.mod h1:FXVjG7YLf9FetsS2OOYcwNhcdOLGt8S9bQ48+OP75cE= -modernc.org/ccgo/v3 v3.12.36/go.mod h1:uP3/Fiezp/Ga8onfvMLpREq+KUjUmYMxXPO8tETHtA8= -modernc.org/ccgo/v3 v3.12.38/go.mod h1:93O0G7baRST1vNj4wnZ49b1kLxt0xCW5Hsa2qRaZPqc= -modernc.org/ccgo/v3 v3.12.43/go.mod h1:k+DqGXd3o7W+inNujK15S5ZYuPoWYLpF5PYougCmthU= -modernc.org/ccgo/v3 v3.12.46/go.mod h1:UZe6EvMSqOxaJ4sznY7b23/k13R8XNlyWsO5bAmSgOE= -modernc.org/ccgo/v3 v3.12.47/go.mod h1:m8d6p0zNps187fhBwzY/ii6gxfjob1VxWb919Nk1HUk= -modernc.org/ccgo/v3 v3.12.50/go.mod h1:bu9YIwtg+HXQxBhsRDE+cJjQRuINuT9PUK4orOco/JI= -modernc.org/ccgo/v3 v3.12.51/go.mod h1:gaIIlx4YpmGO2bLye04/yeblmvWEmE4BBBls4aJXFiE= -modernc.org/ccgo/v3 v3.12.53/go.mod h1:8xWGGTFkdFEWBEsUmi+DBjwu/WLy3SSOrqEmKUjMeEg= -modernc.org/ccgo/v3 v3.12.54/go.mod h1:yANKFTm9llTFVX1FqNKHE0aMcQb1fuPJx6p8AcUx+74= -modernc.org/ccgo/v3 v3.12.55/go.mod h1:rsXiIyJi9psOwiBkplOaHye5L4MOOaCjHg1Fxkj7IeU= -modernc.org/ccgo/v3 v3.12.56/go.mod h1:ljeFks3faDseCkr60JMpeDb2GSO3TKAmrzm7q9YOcMU= -modernc.org/ccgo/v3 v3.12.57/go.mod h1:hNSF4DNVgBl8wYHpMvPqQWDQx8luqxDnNGCMM4NFNMc= -modernc.org/ccgo/v3 v3.12.60/go.mod h1:k/Nn0zdO1xHVWjPYVshDeWKqbRWIfif5dtsIOCUVMqM= +modernc.org/libc v1.8.1 h1:y9oPIhwcaFXxX7kMp6Qb2ZLKzr0mDkikWN3CV5GS63o= modernc.org/libc v1.8.1/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w= -modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q= -modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg= -modernc.org/libc v1.11.2/go.mod h1:ioIyrl3ETkugDO3SGZ+6EOKvlP3zSOycUETe4XM4n8M= -modernc.org/libc v1.11.5/go.mod h1:k3HDCP95A6U111Q5TmG3nAyUcp3kR5YFZTeDS9v8vSU= -modernc.org/libc v1.11.6/go.mod h1:ddqmzR6p5i4jIGK1d/EiSw97LBcE3dK24QEwCFvgNgE= -modernc.org/libc v1.11.11/go.mod h1:lXEp9QOOk4qAYOtL3BmMve99S5Owz7Qyowzvg6LiZso= -modernc.org/libc v1.11.13/go.mod h1:ZYawJWlXIzXy2Pzghaf7YfM8OKacP3eZQI81PDLFdY8= -modernc.org/libc v1.11.16/go.mod h1:+DJquzYi+DMRUtWI1YNxrlQO6TcA5+dRRiq8HWBWRC8= -modernc.org/libc v1.11.19/go.mod h1:e0dgEame6mkydy19KKaVPBeEnyJB4LGNb0bBH1EtQ3I= -modernc.org/libc v1.11.24/go.mod h1:FOSzE0UwookyT1TtCJrRkvsOrX2k38HoInhw+cSCUGk= -modernc.org/libc v1.11.26/go.mod h1:SFjnYi9OSd2W7f4ct622o/PAYqk7KHv6GS8NZULIjKY= -modernc.org/libc v1.11.27/go.mod h1:zmWm6kcFXt/jpzeCgfvUNswM0qke8qVwxqZrnddlDiE= -modernc.org/libc v1.11.28/go.mod h1:Ii4V0fTFcbq3qrv3CNn+OGHAvzqMBvC7dBNyC4vHZlg= -modernc.org/libc v1.11.31/go.mod h1:FpBncUkEAtopRNJj8aRo29qUiyx5AvAlAxzlx9GNaVM= -modernc.org/libc v1.11.34/go.mod h1:+Tzc4hnb1iaX/SKAutJmfzES6awxfU1BPvrrJO0pYLg= -modernc.org/libc v1.11.37/go.mod h1:dCQebOwoO1046yTrfUE5nX1f3YpGZQKNcITUYWlrAWo= -modernc.org/libc v1.11.39/go.mod h1:mV8lJMo2S5A31uD0k1cMu7vrJbSA3J3waQJxpV4iqx8= -modernc.org/libc v1.11.42/go.mod h1:yzrLDU+sSjLE+D4bIhS7q1L5UwXDOw99PLSX0BlZvSQ= -modernc.org/libc v1.11.44/go.mod h1:KFq33jsma7F5WXiYelU8quMJasCCTnHK0mkri4yPHgA= -modernc.org/libc v1.11.45/go.mod h1:Y192orvfVQQYFzCNsn+Xt0Hxt4DiO4USpLNXBlXg/tM= -modernc.org/libc v1.11.47/go.mod h1:tPkE4PzCTW27E6AIKIR5IwHAQKCAtudEIeAV1/SiyBg= -modernc.org/libc v1.11.49/go.mod h1:9JrJuK5WTtoTWIFQ7QjX2Mb/bagYdZdscI3xrvHbXjE= -modernc.org/libc v1.11.51/go.mod h1:R9I8u9TS+meaWLdbfQhq2kFknTW0O3aw3kEMqDDxMaM= -modernc.org/libc v1.11.53/go.mod h1:5ip5vWYPAoMulkQ5XlSJTy12Sz5U6blOQiYasilVPsU= -modernc.org/libc v1.11.54/go.mod h1:S/FVnskbzVUrjfBqlGFIPA5m7UwB3n9fojHhCNfSsnw= -modernc.org/libc v1.11.55/go.mod h1:j2A5YBRm6HjNkoSs/fzZrSxCuwWqcMYTDPLNx0URn3M= -modernc.org/libc v1.11.56/go.mod h1:pakHkg5JdMLt2OgRadpPOTnyRXm/uzu+Yyg/LSLdi18= -modernc.org/libc v1.11.58/go.mod h1:ns94Rxv0OWyoQrDqMFfWwka2BcaF6/61CqJRK9LP7S8= -modernc.org/libc v1.11.70 h1:OHnBZYEJF8CuLOH++G4XYL2lZ4yLH/kkKTRf6gqV5UE= -modernc.org/libc v1.11.70/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8= -modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM= modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc= -modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14= -modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= -modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/base/flag.go b/internal/base/flag.go index 8cc0ab6cb..27940538a 100644 --- a/internal/base/flag.go +++ b/internal/base/flag.go @@ -39,6 +39,7 @@ var ( LogForceNew bool // 是否在每次启动时强制创建全新的文件储存日志 LogColorful bool // 是否启用日志颜色 FastStart bool // 是否为快速启动 + AllowTempSession bool // 是否允许发送临时会话信息 PostFormat string // 上报格式 string or array Proxy string // 存储 proxy_rewrite,用于设置代理 @@ -63,6 +64,7 @@ func Parse() { flag.BoolVar(&LittleH, "h", false, "this Help") flag.StringVar(&LittleWD, "w", "", "cover the working directory") d := flag.Bool("D", false, "debug mode") + flag.BoolVar(&FastStart, "faststart", false, "skip waiting 5 seconds") flag.Parse() if *d { @@ -85,6 +87,7 @@ func Init() { SkipMimeScan = conf.Message.SkipMimeScan ReportSelfMessage = conf.Message.ReportSelfMessage UseSSOAddress = conf.Account.UseSSOAddress + AllowTempSession = conf.Account.AllowTempSession } { // others Proxy = conf.Message.ProxyRewrite diff --git a/internal/btree/btree.go b/internal/btree/btree.go index ccbdb00b4..7b3b49cb7 100644 --- a/internal/btree/btree.go +++ b/internal/btree/btree.go @@ -475,15 +475,7 @@ func (d *DB) Insert(chash *byte, data []byte) { d.flushSuper() } -// Get look up item with the given key 'hash' in the database file. Length of the -// item is stored in 'len'. Returns a pointer to the contents of the item. -// The returned pointer should be released with free() after use. -func (d *DB) Get(hash *byte) []byte { - off := d.lookup(d.top, hash) - if off == 0 { - return nil - } - +func (d *DB) readValue(off int64) []byte { d.fd.Seek(off, io.SeekStart) length, err := read32(d.fd) if err != nil { @@ -497,6 +489,17 @@ func (d *DB) Get(hash *byte) []byte { return data[:n] } +// Get look up item with the given key 'hash' in the database file. Length of the +// item is stored in 'len'. Returns a pointer to the contents of the item. +// The returned pointer should be released with free() after use. +func (d *DB) Get(hash *byte) []byte { + off := d.lookup(d.top, hash) + if off == 0 { + return nil + } + return d.readValue(off) +} + // Delete remove item with the given key 'hash' from the database file. func (d *DB) Delete(hash *byte) error { var h [hashSize]byte @@ -522,3 +525,29 @@ func (d *DB) Delete(hash *byte) error { d.flushSuper() return nil } + +// Foreach iterates over all items in the database file. +func (d *DB) Foreach(iter func(key [16]byte, value []byte)) { + if d.top != 0 { + top := d.get(d.top) + d.iterate(top, iter) + } +} + +func (d *DB) iterate(table *table, iter func(key [16]byte, value []byte)) { + for i := 0; i < table.size; i++ { + item := table.items[i] + offset := item.offset + iter(item.hash, d.readValue(offset)) + + if item.child != 0 { + child := d.get(item.child) + d.iterate(child, iter) + } + } + item := table.items[table.size] + if item.child != 0 { + child := d.get(item.child) + d.iterate(child, iter) + } +} diff --git a/internal/btree/btree_test.go b/internal/btree/btree_test.go index 5afc4aed8..5d16c5410 100644 --- a/internal/btree/btree_test.go +++ b/internal/btree/btree_test.go @@ -5,6 +5,7 @@ import ( "os" "testing" + "github.com/Mrs4s/MiraiGo/utils" assert2 "github.com/stretchr/testify/assert" ) @@ -26,36 +27,71 @@ func TestBtree(t *testing.T) { f := tempfile(t) defer os.Remove(f) bt, err := Create(f) - assert2.NoError(t, err) + assert := assert2.New(t) + assert.NoError(err) - var tests = []string{ + tests := []string{ "hello world", "123", "We are met on a great battle-field of that war.", "Abraham Lincoln, November 19, 1863, Gettysburg, Pennsylvania", } - var sha = make([]*byte, len(tests)) + sha := make([]*byte, len(tests)) for i, tt := range tests { - var hash = sha1.New() + hash := sha1.New() hash.Write([]byte(tt)) sha[i] = &hash.Sum(nil)[0] bt.Insert(sha[i], []byte(tt)) } - assert2.NoError(t, bt.Close()) + assert.NoError(bt.Close()) bt, err = Open(f) - assert2.NoError(t, err) + assert.NoError(err) + var ss []string + bt.Foreach(func(key [16]byte, value []byte) { + ss = append(ss, string(value)) + }) + assert.ElementsMatch(tests, ss) + for i, tt := range tests { - assert2.Equal(t, []byte(tt), bt.Get(sha[i])) + assert.Equal([]byte(tt), bt.Get(sha[i])) } for i := range tests { - assert2.NoError(t, bt.Delete(sha[i])) + assert.NoError(bt.Delete(sha[i])) } for i := range tests { - assert2.Equal(t, []byte(nil), bt.Get(sha[i])) + assert.Equal([]byte(nil), bt.Get(sha[i])) } + assert.NoError(bt.Close()) +} +func testForeach(t *testing.T, elemSize int) { + expected := make([]string, elemSize) + for i := 0; i < elemSize; i++ { + expected[i] = utils.RandomString(20) + } + f := tempfile(t) + defer os.Remove(f) + bt, err := Create(f) + assert2.NoError(t, err) + for _, v := range expected { + hash := sha1.New() + hash.Write([]byte(v)) + bt.Insert(&hash.Sum(nil)[0], []byte(v)) + } + var got []string + bt.Foreach(func(key [16]byte, value []byte) { + got = append(got, string(value)) + }) + assert2.ElementsMatch(t, expected, got) assert2.NoError(t, bt.Close()) } + +func TestDB_Foreach(t *testing.T) { + elemSizes := []int{0, 5, 100, 200} + for _, size := range elemSizes { + testForeach(t, size) + } +} diff --git a/internal/btree/helper.go b/internal/btree/helper.go index 4a9761041..eb9c1f78c 100644 --- a/internal/btree/helper.go +++ b/internal/btree/helper.go @@ -45,7 +45,7 @@ func resethash(sha1 *byte) { // reading table func read32(r io.Reader) (int32, error) { - var b = make([]byte, 4) + b := make([]byte, 4) _, err := r.Read(b) if err != nil { return 0, err diff --git a/internal/cache/cache.go b/internal/cache/cache.go index d4f4e7fcb..30cb53899 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -12,11 +12,6 @@ import ( "github.com/Mrs4s/go-cqhttp/internal/btree" ) -// todo(wdvxdr): always enable db-cache in v1.0.0 - -// EnableCacheDB 是否启用 btree db缓存图片等 -var EnableCacheDB bool - // Media Cache DBs var ( Image Cache @@ -63,17 +58,15 @@ func (c *Cache) Delete(md5 []byte) { // Init 初始化 Cache func Init() { node, ok := base.Database["cache"] - if !ok { - return - } - EnableCacheDB = true var conf map[string]string - err := node.Decode(&conf) - if err != nil { - log.Fatalf("failed to read cache config: %v", err) + if ok { + err := node.Decode(&conf) + if err != nil { + log.Fatalf("failed to read cache config: %v", err) + } } - var open = func(typ string, cache *Cache) { + open := func(typ string, cache *Cache) { file := conf[typ] if file == "" { file = fmt.Sprintf("data/%s.db", typ) diff --git a/internal/param/param.go b/internal/param/param.go index 0b709678b..c38bf1cb2 100644 --- a/internal/param/param.go +++ b/internal/param/param.go @@ -3,7 +3,6 @@ package param import ( "math" - "reflect" "regexp" "strings" "sync" @@ -94,37 +93,3 @@ func Base64DecodeString(s string) ([]byte, error) { n, err := e.Decode(dst, utils.S2B(s)) return dst[:n], err } - -// SetAtDefault 在变量 variable 为默认值 defaultValue 的时候修改为 value -func SetAtDefault(variable, value, defaultValue interface{}) { - v := reflect.ValueOf(variable) - v2 := reflect.ValueOf(value) - if v.Kind() != reflect.Ptr || v.IsNil() { - return - } - v = v.Elem() - if v.Interface() != defaultValue { - return - } - if v.Kind() != v2.Kind() { - return - } - v.Set(v2) -} - -// SetExcludeDefault 在目标值 value 不为默认值 defaultValue 时修改 variable 为 value -func SetExcludeDefault(variable, value, defaultValue interface{}) { - v := reflect.ValueOf(variable) - v2 := reflect.ValueOf(value) - if v.Kind() != reflect.Ptr || v.IsNil() { - return - } - v = v.Elem() - if reflect.Indirect(v2).Interface() != defaultValue { - return - } - if v.Kind() != v2.Kind() { - return - } - v.Set(v2) -} diff --git a/modules/api/api.go b/modules/api/api.go index b0a047b72..5ece47689 100644 --- a/modules/api/api.go +++ b/modules/api/api.go @@ -18,6 +18,9 @@ func (c *Caller) call(action string, p Getter) global.MSG { p0 := p.Get("context") p1 := p.Get("operation") return c.bot.CQHandleQuickOperation(p0, p1) + case ".ocr_image", "ocr_image": + p0 := p.Get("image").String() + return c.bot.CQOcrImage(p0) case "_get_model_show": p0 := p.Get("model").String() return c.bot.CQGetModelShow(p0) @@ -48,7 +51,7 @@ func (c *Caller) call(action string, p Getter) global.MSG { case "create_guild_role": p0 := p.Get("guild_id").Uint() p1 := p.Get("name").String() - p2 := uint32(p.Get("color").Int()) + p2 := uint32(p.Get("color").Uint()) p3 := p.Get("independent").Bool() p4 := p.Get("initial_users") return c.bot.CQCreateGuildRole(p0, p1, p2, p3, p4) @@ -61,7 +64,7 @@ func (c *Caller) call(action string, p Getter) global.MSG { case "delete_group_file": p0 := p.Get("group_id").Int() p1 := p.Get("file_id").String() - p2 := int32(p.Get("bus_id").Int()) + p2 := int32(p.Get("[busid,bus_id].0").Int()) return c.bot.CQGroupFileDeleteFile(p0, p1, p2) case "delete_group_folder": p0 := p.Get("group_id").Int() @@ -99,7 +102,7 @@ func (c *Caller) call(action string, p Getter) global.MSG { case "get_group_file_url": p0 := p.Get("group_id").Int() p1 := p.Get("file_id").String() - p2 := int32(p.Get("bus_id").Int()) + p2 := int32(p.Get("[busid,bus_id].0").Int()) return c.bot.CQGetGroupFileURL(p0, p1, p2) case "get_group_files_by_folder": p0 := p.Get("group_id").Int() @@ -140,9 +143,21 @@ func (c *Caller) call(action string, p Getter) global.MSG { return c.bot.CQGetGuildChannelList(p0, p1) case "get_guild_list": return c.bot.CQGetGuildList() + case "get_guild_member_list": + p0 := p.Get("guild_id").Uint() + p1 := p.Get("next_token").String() + return c.bot.CQGetGuildMembers(p0, p1) + case "get_guild_member_profile": + p0 := p.Get("guild_id").Uint() + p1 := p.Get("user_id").Uint() + return c.bot.CQGetGuildMemberProfile(p0, p1) case "get_guild_meta_by_guest": p0 := p.Get("guild_id").Uint() return c.bot.CQGetGuildMetaByGuest(p0) + case "get_guild_msg": + p0 := p.Get("message_id").String() + p1 := p.Get("no_cache").Bool() + return c.bot.CQGetGuildMessage(p0, p1) case "get_guild_roles": p0 := p.Get("guild_id").Uint() return c.bot.CQGetGuildRoles(p0) @@ -175,9 +190,6 @@ func (c *Caller) call(action string, p Getter) global.MSG { case "mark_msg_as_read": p0 := int32(p.Get("message_id").Int()) return c.bot.CQMarkMessageAsRead(p0) - case "ocr_image", ".ocr_image": - p0 := p.Get("image").String() - return c.bot.CQOcrImage(p0) case "qidian_get_account_info": return c.bot.CQGetQiDianAccountInfo() case "reload_event_filter": @@ -248,7 +260,7 @@ func (c *Caller) call(action string, p Getter) global.MSG { p1 := p.Get("user_id").Int() p2 := uint32(1800) if pt := p.Get("duration"); pt.Exists() { - p2 = uint32(pt.Int()) + p2 = uint32(pt.Uint()) } return c.bot.CQSetGroupBan(p0, p1, p2) case "set_group_card": @@ -296,7 +308,7 @@ func (c *Caller) call(action string, p Getter) global.MSG { p0 := p.Get("guild_id").Uint() p1 := p.Get("role_id").Uint() p2 := p.Get("name").String() - p3 := uint32(p.Get("color").Int()) + p3 := uint32(p.Get("color").Uint()) p4 := p.Get("indepedent").Bool() return c.bot.CQModifyRoleInGuild(p0, p1, p2, p3, p4) case "upload_group_file": diff --git a/modules/config/config.go b/modules/config/config.go index 33930cc69..84c4a3e2b 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -6,14 +6,12 @@ import ( _ "embed" // embed the default config file "fmt" "os" - "strconv" + "regexp" "strings" "sync" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" - - "github.com/Mrs4s/go-cqhttp/internal/param" ) // defaultConfig 默认配置文件 @@ -30,12 +28,13 @@ type Reconnect struct { // Account 账号配置 type Account struct { - Uin int64 `yaml:"uin"` - Password string `yaml:"password"` - Encrypt bool `yaml:"encrypt"` - Status int `yaml:"status"` - ReLogin *Reconnect `yaml:"relogin"` - UseSSOAddress bool `yaml:"use-sso-address"` + Uin int64 `yaml:"uin"` + Password string `yaml:"password"` + Encrypt bool `yaml:"encrypt"` + Status int `yaml:"status"` + ReLogin *Reconnect `yaml:"relogin"` + UseSSOAddress bool `yaml:"use-sso-address"` + AllowTempSession bool `yaml:"allow-temp-session"` } // Config 总配置文件 @@ -72,9 +71,8 @@ type Config struct { // Server 的简介和初始配置 type Server struct { - Brief string - Default string - ParseEnv func() (string, *yaml.Node) + Brief string + Default string } // LevelDBConfig leveldb 相关配置 @@ -91,57 +89,24 @@ type MongoDBConfig struct { // Parse 从默认配置文件路径中获取 func Parse(path string) *Config { - fromEnv := os.Getenv("GCQ_UIN") != "" - file, err := os.ReadFile(path) config := &Config{} if err == nil { - err = yaml.NewDecoder(strings.NewReader(os.ExpandEnv(string(file)))).Decode(config) - if err != nil && !fromEnv { + err = yaml.NewDecoder(strings.NewReader(expand(string(file), os.Getenv))).Decode(config) + if err != nil { log.Fatal("配置文件不合法!", err) } - } else if !fromEnv { + } else { generateConfig() os.Exit(0) } - if fromEnv { - // type convert tools - toInt64 := func(str string) int64 { - i, _ := strconv.ParseInt(str, 10, 64) - return i - } - - // load config from environment variable - param.SetAtDefault(&config.Account.Uin, toInt64(os.Getenv("GCQ_UIN")), int64(0)) - param.SetAtDefault(&config.Account.Password, os.Getenv("GCQ_PWD"), "") - param.SetAtDefault(&config.Account.Status, int32(toInt64(os.Getenv("GCQ_STATUS"))), int32(0)) - param.SetAtDefault(&config.Account.ReLogin.Disabled, !param.EnsureBool(os.Getenv("GCQ_RELOGIN_DISABLED"), true), false) - param.SetAtDefault(&config.Account.ReLogin.Delay, uint(toInt64(os.Getenv("GCQ_RELOGIN_DELAY"))), uint(0)) - param.SetAtDefault(&config.Account.ReLogin.MaxTimes, uint(toInt64(os.Getenv("GCQ_RELOGIN_MAX_TIMES"))), uint(0)) - dbConf := &LevelDBConfig{Enable: param.EnsureBool(os.Getenv("GCQ_LEVELDB"), true)} - if config.Database == nil { - config.Database = make(map[string]yaml.Node) - } - config.Database["leveldb"] = func() yaml.Node { - n := &yaml.Node{} - _ = n.Encode(dbConf) - return *n - }() - - for _, s := range serverconfs { - if s.ParseEnv != nil { - name, node := s.ParseEnv() - if node != nil { - config.Servers = append(config.Servers, map[string]yaml.Node{name: *node}) - } - } - } - } return config } -var serverconfs []*Server -var mu sync.Mutex +var ( + serverconfs []*Server + mu sync.Mutex +) // AddServer 添加该服务的简介和默认配置 func AddServer(s *Server) { @@ -182,3 +147,27 @@ func generateConfig() { fmt.Println("默认配置文件已生成,请修改 config.yml 后重新启动!") _, _ = input.ReadString('\n') } + +// expand 使用正则进行环境变量展开 +// os.ExpandEnv 字符 $ 无法逃逸 +// https://github.com/golang/go/issues/43482 +func expand(s string, mapping func(string) string) string { + r := regexp.MustCompile(`\${([a-zA-Z_]+[a-zA-Z0-9_:/.]*)}`) + return r.ReplaceAllStringFunc(s, func(s string) string { + s = strings.Trim(s, "${}") + // todo: use strings.Cut once go1.18 is released + before, after, ok := cut(s, ":") + m := mapping(before) + if ok && m == "" { + return after + } + return m + }) +} + +func cut(s, sep string) (before, after string, found bool) { + if i := strings.Index(s, sep); i >= 0 { + return s[:i], s[i+len(sep):], true + } + return s, "", false +} diff --git a/modules/config/config_test.go b/modules/config/config_test.go new file mode 100644 index 000000000..ae44b8e68 --- /dev/null +++ b/modules/config/config_test.go @@ -0,0 +1,48 @@ +package config + +import ( + "strings" + "testing" +) + +func Test_expand(t *testing.T) { + nullStringMapping := func(_ string) string { + return "" + } + tests := []struct { + src string + mapping func(string) string + expected string + }{ + { + src: "foo: ${bar}", + mapping: strings.ToUpper, + expected: "foo: BAR", + }, + { + src: "$123", + mapping: strings.ToUpper, + expected: "$123", + }, + { + src: "foo: ${bar:123456}", + mapping: nullStringMapping, + expected: "foo: 123456", + }, + { + src: "foo: ${bar:127.0.0.1:5700}", + mapping: nullStringMapping, + expected: "foo: 127.0.0.1:5700", + }, + { + src: "foo: ${bar:ws//localhost:9999/ws}", + mapping: nullStringMapping, + expected: "foo: ws//localhost:9999/ws", + }, + } + for i, tt := range tests { + if got := expand(tt.src, tt.mapping); got != tt.expected { + t.Errorf("testcase %d failed, expected %v but got %v", i, tt.expected, got) + } + } +} diff --git a/modules/config/default_config.yml b/modules/config/default_config.yml index 50570a5f5..69e23cd93 100644 --- a/modules/config/default_config.yml +++ b/modules/config/default_config.yml @@ -13,6 +13,8 @@ account: # 账号相关 # 是否使用服务器下发的新地址进行重连 # 注意, 此设置可能导致在海外服务器上连接情况更差 use-sso-address: true + # 是否允许发送临时会话消息 + allow-temp-session: false heartbeat: # 心跳频率, 单位秒 diff --git a/server/http.go b/server/http.go index fc233c2fc..7d696c529 100644 --- a/server/http.go +++ b/server/http.go @@ -2,17 +2,16 @@ package server import ( "bytes" - "context" "crypto/hmac" "crypto/sha1" "encoding/hex" "encoding/json" "fmt" "io" - "math/rand" "net/http" "net/url" "os" + "regexp" "strconv" "strings" "time" @@ -24,7 +23,6 @@ import ( "github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/global" - "github.com/Mrs4s/go-cqhttp/internal/param" "github.com/Mrs4s/go-cqhttp/modules/api" "github.com/Mrs4s/go-cqhttp/modules/config" "github.com/Mrs4s/go-cqhttp/modules/filter" @@ -40,14 +38,18 @@ type HTTPServer struct { Enabled bool `yaml:"enabled"` MaxQueueSize int `yaml:"max-queue-size"` } `yaml:"long-polling"` - Post []struct { - URL string `yaml:"url"` - Secret string `yaml:"secret"` - } + Post []httpServerPost `yaml:"post"` MiddleWares `yaml:"middlewares"` } +type httpServerPost struct { + URL string `yaml:"url"` + Secret string `yaml:"secret"` + MaxRetries *uint64 `yaml:"max-retries"` + RetriesInterval *uint64 `yaml:"retries-interval"` +} + type httpServer struct { HTTP *http.Server api *api.Caller @@ -56,12 +58,14 @@ type httpServer struct { // HTTPClient 反向HTTP上报客户端 type HTTPClient struct { - bot *coolq.CQBot - secret string - addr string - filter string - apiPort int - timeout int32 + bot *coolq.CQBot + secret string + addr string + filter string + apiPort int + timeout int32 + MaxRetries uint64 + RetriesInterval uint64 } type httpCtx struct { @@ -70,73 +74,44 @@ type httpCtx struct { postForm url.Values } -const httpDefault = ` # HTTP 通信设置 - - http: - # 服务端监听地址 - host: 127.0.0.1 - # 服务端监听端口 - port: 5700 - # 反向HTTP超时时间, 单位秒 - # 最小值为5,小于5将会忽略本项设置 - timeout: 5 - # 长轮询拓展 - long-polling: - # 是否开启 - enabled: false - # 消息队列大小,0 表示不限制队列大小,谨慎使用 - max-queue-size: 2000 +const httpDefault = ` + - http: # HTTP 通信设置 + host: 127.0.0.1 # 服务端监听地址 + port: 5700 # 服务端监听端口 + timeout: 5 # 反向 HTTP 超时时间, 单位秒,<5 时将被忽略 + long-polling: # 长轮询拓展 + enabled: false # 是否开启 + max-queue-size: 2000 # 消息队列大小,0 表示不限制队列大小,谨慎使用 middlewares: <<: *default # 引用默认中间件 - # 反向HTTP POST地址列表 - post: - #- url: '' # 地址 - # secret: '' # 密钥 + post: # 反向HTTP POST地址列表 + #- url: '' # 地址 + # secret: '' # 密钥 + # max-retries: 3 # 最大重试,0 时禁用 + # retries-interval: 1500 # 重试时间,单位毫秒,0 时立即 #- url: http://127.0.0.1:5701/ # 地址 - # secret: '' # 密钥 + # secret: '' # 密钥 + # max-retries: 10 # 最大重试,0 时禁用 + # retries-interval: 1000 # 重试时间,单位毫秒,0 时立即 ` func init() { - config.AddServer(&config.Server{ - Brief: "HTTP通信", - Default: httpDefault, - ParseEnv: func() (string, *yaml.Node) { - if os.Getenv("GCQ_HTTP_PORT") != "" { - // type convert tools - toInt64 := func(str string) int64 { - i, _ := strconv.ParseInt(str, 10, 64) - return i - } - accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN") - node := &yaml.Node{} - httpConf := &HTTPServer{ - Host: "0.0.0.0", - Port: 5700, - MiddleWares: MiddleWares{ - AccessToken: accessTokenEnv, - }, - } - param.SetExcludeDefault(&httpConf.Disabled, param.EnsureBool(os.Getenv("GCQ_HTTP_DISABLE"), false), false) - param.SetExcludeDefault(&httpConf.Host, os.Getenv("GCQ_HTTP_HOST"), "") - param.SetExcludeDefault(&httpConf.Port, int(toInt64(os.Getenv("GCQ_HTTP_PORT"))), 0) - if os.Getenv("GCQ_HTTP_POST_URL") != "" { - httpConf.Post = append(httpConf.Post, struct { - URL string `yaml:"url"` - Secret string `yaml:"secret"` - }{os.Getenv("GCQ_HTTP_POST_URL"), os.Getenv("GCQ_HTTP_POST_SECRET")}) - } - _ = node.Encode(httpConf) - return "http", node - } - return "", nil - }, - }) + config.AddServer(&config.Server{Brief: "HTTP通信", Default: httpDefault}) } -func (h *httpCtx) Get(s string) gjson.Result { - j := h.json.Get(s) - if j.Exists() { - return j +var joinQuery = regexp.MustCompile(`\[(.+?),(.+?)]\.0`) + +func (h *httpCtx) get(s string, join bool) gjson.Result { + // support gjson advanced syntax: + // h.Get("[a,b].0") see usage in http_test.go + if join && joinQuery.MatchString(s) { + matched := joinQuery.FindStringSubmatch(s) + if r := h.get(matched[1], false); r.Exists() { + return r + } + return h.get(matched[2], false) } + validJSONParam := func(p string) bool { return (strings.HasPrefix(p, "{") || strings.HasPrefix(p, "[")) && gjson.Valid(p) } @@ -159,6 +134,14 @@ func (h *httpCtx) Get(s string) gjson.Result { return gjson.Result{} } +func (h *httpCtx) Get(s string) gjson.Result { + j := h.json.Get(s) + if j.Exists() { + return j + } + return h.get(s, true) +} + func (s *httpServer) ServeHTTP(writer http.ResponseWriter, request *http.Request) { var ctx httpCtx contentType := request.Header.Get("Content-Type") @@ -242,6 +225,13 @@ func checkAuth(req *http.Request, token string) int { } } +func puint64Operator(p *uint64, def uint64) uint64 { + if p == nil { + return def + } + return *p +} + // runHTTP 启动HTTP服务器与HTTP上报客户端 func runHTTP(bot *coolq.CQBot, node yaml.Node) { var conf HTTPServer @@ -285,12 +275,14 @@ client: for _, c := range conf.Post { if c.URL != "" { go HTTPClient{ - bot: bot, - secret: c.Secret, - addr: c.URL, - apiPort: conf.Port, - filter: conf.Filter, - timeout: conf.Timeout, + bot: bot, + secret: c.Secret, + addr: c.URL, + apiPort: conf.Port, + filter: conf.Filter, + timeout: conf.Timeout, + MaxRetries: puint64Operator(c.MaxRetries, 3), + RetriesInterval: puint64Operator(c.RetriesInterval, 1500), }.Run() } } @@ -330,10 +322,7 @@ func (c *HTTPClient) onBotPushEvent(e *coolq.Event) { } var res *http.Response - var err error - const maxAttemptTimes = 5 - - for i := 0; i <= maxAttemptTimes; i++ { + for i := uint64(0); i <= c.MaxRetries; i++ { // see https://stackoverflow.com/questions/31337891/net-http-http-contentlength-222-with-body-length-0 // we should create a new request for every single post trial req, err := http.NewRequest("POST", c.addr, bytes.NewReader(e.JSONBytes())) @@ -344,24 +333,22 @@ func (c *HTTPClient) onBotPushEvent(e *coolq.Event) { req.Header = header res, err = client.Do(req) - if err == nil { + if res != nil { //goland:noinspection GoDeferInLoop defer res.Body.Close() + } + if err == nil { break } - if i != maxAttemptTimes { + if i < c.MaxRetries { log.Warnf("上报 Event 数据到 %v 失败: %v 将进行第 %d 次重试", c.addr, err, i+1) + } else { + log.Warnf("上报 Event 数据 %s 到 %v 失败: %v 停止上报:已达重试上线", e.JSONBytes(), c.addr, err) + return } - const maxWait = int64(time.Second * 3) - const minWait = int64(time.Millisecond * 500) - wait := rand.Int63n(maxWait-minWait) + minWait - time.Sleep(time.Duration(wait)) + time.Sleep(time.Millisecond * time.Duration(c.RetriesInterval)) } - if err != nil { - log.Warnf("上报Event数据 %s 到 %v 失败: %v", e.JSONBytes(), c.addr, err) - return - } log.Debugf("上报Event数据 %s 到 %v", e.JSONBytes(), c.addr) r, err := io.ReadAll(res.Body) @@ -372,14 +359,3 @@ func (c *HTTPClient) onBotPushEvent(e *coolq.Event) { c.bot.CQHandleQuickOperation(gjson.Parse(e.JSONString()), gjson.ParseBytes(r)) } } - -func (s *httpServer) ShutDown() { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := s.HTTP.Shutdown(ctx); err != nil { - log.Fatal("http Server Shutdown:", err) - } - <-ctx.Done() - log.Println("timeout of 5 seconds.") - log.Println("http Server exiting") -} diff --git a/server/http_test.go b/server/http_test.go new file mode 100644 index 000000000..1d2a92ca7 --- /dev/null +++ b/server/http_test.go @@ -0,0 +1,42 @@ +package server + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" +) + +func TestHttpCtx_Get(t *testing.T) { + cases := []struct { + ctx *httpCtx + key string + expected string + }{ + { + ctx: &httpCtx{ + json: gjson.Result{}, + query: url.Values{ + "sub_type": []string{"hello"}, + "type": []string{"world"}, + }, + }, + key: "[sub_type,type].0", + expected: "hello", + }, + { + ctx: &httpCtx{ + json: gjson.Result{}, + query: url.Values{ + "type": []string{"114514"}, + }, + }, + key: "[sub_type,type].0", + expected: "114514", + }, + } + for _, c := range cases { + assert.Equal(t, c.expected, c.ctx.Get(c.key).String()) + } +} diff --git a/server/websocket.go b/server/websocket.go index 6cdf658a9..20d3a73ba 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "net/http" - "os" "runtime/debug" "strconv" "strings" @@ -13,14 +12,13 @@ import ( "time" "github.com/Mrs4s/MiraiGo/utils" - "github.com/gorilla/websocket" + "github.com/RomiChan/websocket" log "github.com/sirupsen/logrus" "github.com/tidwall/gjson" "gopkg.in/yaml.v3" "github.com/Mrs4s/go-cqhttp/coolq" "github.com/Mrs4s/go-cqhttp/global" - "github.com/Mrs4s/go-cqhttp/internal/param" "github.com/Mrs4s/go-cqhttp/modules/api" "github.com/Mrs4s/go-cqhttp/modules/config" "github.com/Mrs4s/go-cqhttp/modules/filter" @@ -60,6 +58,7 @@ type wsConn struct { func (c *wsConn) WriteText(b []byte) error { c.mu.Lock() defer c.mu.Unlock() + _ = c.conn.SetWriteDeadline(time.Now().Add(time.Second * 15)) return c.conn.WriteMessage(websocket.TextMessage, b) } @@ -122,52 +121,10 @@ func init() { config.AddServer(&config.Server{ Brief: "正向 Websocket 通信", Default: wsDefault, - ParseEnv: func() (string, *yaml.Node) { - if os.Getenv("GCQ_WS_PORT") != "" { - // type convert tools - toInt64 := func(str string) int64 { - i, _ := strconv.ParseInt(str, 10, 64) - return i - } - accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN") - node := &yaml.Node{} - wsServerConf := &WebsocketServer{ - Host: "0.0.0.0", - Port: 6700, - MiddleWares: MiddleWares{ - AccessToken: accessTokenEnv, - }, - } - param.SetExcludeDefault(&wsServerConf.Disabled, param.EnsureBool(os.Getenv("GCQ_WS_DISABLE"), false), false) - param.SetExcludeDefault(&wsServerConf.Host, os.Getenv("GCQ_WS_HOST"), "") - param.SetExcludeDefault(&wsServerConf.Port, int(toInt64(os.Getenv("GCQ_WS_PORT"))), 0) - _ = node.Encode(wsServerConf) - return "ws", node - } - return "", nil - }, }) config.AddServer(&config.Server{ Brief: "反向 Websocket 通信", Default: wsReverseDefault, - ParseEnv: func() (string, *yaml.Node) { - if os.Getenv("GCQ_RWS_API") != "" || os.Getenv("GCQ_RWS_EVENT") != "" || os.Getenv("GCQ_RWS_UNIVERSAL") != "" { - accessTokenEnv := os.Getenv("GCQ_ACCESS_TOKEN") - node := &yaml.Node{} - rwsConf := &WebsocketReverse{ - MiddleWares: MiddleWares{ - AccessToken: accessTokenEnv, - }, - } - param.SetExcludeDefault(&rwsConf.Disabled, param.EnsureBool(os.Getenv("GCQ_RWS_DISABLE"), false), false) - param.SetExcludeDefault(&rwsConf.API, os.Getenv("GCQ_RWS_API"), "") - param.SetExcludeDefault(&rwsConf.Event, os.Getenv("GCQ_RWS_EVENT"), "") - param.SetExcludeDefault(&rwsConf.Universal, os.Getenv("GCQ_RWS_UNIVERSAL"), "") - _ = node.Encode(rwsConf) - return "ws-reverse", node - } - return "", nil - }, }) } @@ -269,13 +226,21 @@ func (c *websocketClient) connect(typ, url string, conptr **wsConn) { } log.Infof("已连接到反向WebSocket %s服务器 %v", typ, url) - wrappedConn := &wsConn{conn: conn, apiCaller: api.NewCaller(c.bot)} - if c.limiter != nil { - wrappedConn.apiCaller.Use(c.limiter) + + var wrappedConn *wsConn + if conptr != nil && *conptr != nil { + wrappedConn = *conptr + } else { + wrappedConn = new(wsConn) + if conptr != nil { + *conptr = wrappedConn + } } - if conptr != nil { - *conptr = wrappedConn + wrappedConn.conn = conn + wrappedConn.apiCaller = api.NewCaller(c.bot) + if c.limiter != nil { + wrappedConn.apiCaller.Use(c.limiter) } if typ != "Event" { @@ -460,6 +425,7 @@ func (c *wsConn) handleRequest(_ *coolq.CQBot, payload []byte) { c.mu.Lock() defer c.mu.Unlock() + _ = c.conn.SetWriteDeadline(time.Now().Add(time.Second * 15)) writer, _ := c.conn.NextWriter(websocket.TextMessage) _ = json.NewEncoder(writer).Encode(ret) _ = writer.Close()