From 30554f4ffcccd626b92173fc5eeb1a6b1661ed31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Tue, 10 Oct 2023 22:02:52 +0900 Subject: [PATCH] add channel patch/delete --- codegen/patchopenapiof/main.go | 51 +++++++++++ helper.go | 14 --- http.go | 24 +++++- http_test.go | 11 +++ openapi.go | 53 ++++++++++-- openapi_channel.go | 136 ++++++++++++++++++++++++------ openapi_codegen_patchopenapiof.go | 23 +++++ openapi_guild.go | 2 +- openapi_wss.go | 5 +- 9 files changed, 268 insertions(+), 51 deletions(-) create mode 100644 codegen/patchopenapiof/main.go create mode 100644 http_test.go create mode 100644 openapi_codegen_patchopenapiof.go diff --git a/codegen/patchopenapiof/main.go b/codegen/patchopenapiof/main.go new file mode 100644 index 0000000..b5ac570 --- /dev/null +++ b/codegen/patchopenapiof/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "os" + "strings" +) + +const head = `// Code generated by codegen/patchopenapiof. DO NOT EDIT. + +package nano + +import ( + "io" + "unsafe" + + "github.com/pkg/errors" +) +` + +const template = ` +func (bot *Bot) patchOpenAPIof[T any](ep string, body io.Reader) (*[T any], error) { + resp := &struct { + CodeMessageBase + [T any] + }{} + err := bot.PatchOpenAPI(ep, resp, body) + if err != nil { + err = errors.Wrap(err, getCallerFuncName()) + return nil, err + } + return (*[T any])(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil +} +` + +func main() { + f, err := os.Create("openapi_codegen_patchopenapiof.go") + if err != nil { + panic(err) + } + defer f.Close() + _, err = f.WriteString(head) + if err != nil { + panic(err) + } + for _, name := range os.Args[1:] { + _, err = f.WriteString(strings.ReplaceAll(template, "[T any]", name)) + if err != nil { + panic(err) + } + } +} diff --git a/helper.go b/helper.go index f931603..7031b8d 100644 --- a/helper.go +++ b/helper.go @@ -5,20 +5,6 @@ import ( "strings" ) -// getCurrentFuncName 获取当前函数名 -func getCurrentFuncName() string { - pc, _, _, ok := runtime.Caller(1) - if !ok { - return "" - } - fullname := runtime.FuncForPC(pc).Name() - i := strings.LastIndex(fullname, ".") + 1 - if i <= 0 || i >= len(fullname) { - return fullname - } - return fullname[i:] -} - // getCallerFuncName 获取调用者函数名 func getCallerFuncName() string { pc, _, _, ok := runtime.Caller(2) diff --git a/http.go b/http.go index ae36c60..293e6ea 100644 --- a/http.go +++ b/http.go @@ -21,6 +21,16 @@ func NewHTTPEndpointGetRequestWithAuth(ep string, auth string) (req *http.Reques return } +// NewHTTPEndpointDeleteRequestWithAuth 新建带鉴权头的 HTTP DELETE 请求 +func NewHTTPEndpointDeleteRequestWithAuth(ep string, auth string) (req *http.Request, err error) { + req, err = http.NewRequest("DELETE", StandardAPI+ep, nil) + if err != nil { + return + } + req.Header.Add("Authorization", auth) + return +} + // NewHTTPEndpointPostRequestWithAuth 新建带鉴权头的 HTTP POST 请求 func NewHTTPEndpointPostRequestWithAuth(ep string, auth string, body io.Reader) (req *http.Request, err error) { req, err = http.NewRequest("POST", StandardAPI+ep, body) @@ -31,6 +41,16 @@ func NewHTTPEndpointPostRequestWithAuth(ep string, auth string, body io.Reader) return } +// NewHTTPEndpointPatchRequestWithAuth 新建带鉴权头的 HTTP PATCH 请求 +func NewHTTPEndpointPatchRequestWithAuth(ep string, auth string, body io.Reader) (req *http.Request, err error) { + req, err = http.NewRequest("PATCH", StandardAPI+ep, body) + if err != nil { + return + } + req.Header.Add("Authorization", auth) + return +} + // WriteHTTPQueryIfNotNil 如果非空则将请求添加到 baseurl 后 // // ex. WriteHTTPQueryIfNotNil("http://a.com/api", "a", 0, "b", 1, "c", 2) is http://a.com/api?b=1&c=2 @@ -65,8 +85,8 @@ func WriteHTTPQueryIfNotNil(baseurl string, queries ...any) string { return sb.String()[:sb.Len()-1] } -// WritePostBodyFromJSON 从 json 结构体 ptr 写入 bytes.Buffer, 忽略 error (内部使用不会出错) -func WritePostBodyFromJSON(ptr any) *bytes.Buffer { +// WriteBodyFromJSON 从 json 结构体 ptr 写入 bytes.Buffer, 忽略 error (内部使用不会出错) +func WriteBodyFromJSON(ptr any) *bytes.Buffer { buf := bytes.NewBuffer(make([]byte, 0, 1024)) _ = json.NewEncoder(buf).Encode(ptr) return buf diff --git a/http_test.go b/http_test.go new file mode 100644 index 0000000..e91b9f5 --- /dev/null +++ b/http_test.go @@ -0,0 +1,11 @@ +package nano + +import "testing" + +func TestWriteHTTPQueryIfNotNil(t *testing.T) { + expstr := "https://api.sgroup.qq.com/testapi?b=1&d=0.5" + str := WriteHTTPQueryIfNotNil(StandardAPI+"/testapi", "a", 0, "b", 1, "c", "", "d", 0.5) + if str != expstr { + t.Fatal("expected", expstr, "but got", str) + } +} diff --git a/openapi.go b/openapi.go index 8ef7ed9..39114c0 100644 --- a/openapi.go +++ b/openapi.go @@ -27,9 +27,26 @@ func (bot *Bot) GetOpenAPI(ep string, ptr any) error { if err != nil { return errors.Wrap(err, getCallerFuncName()) } - respbbase := (*CodeMessageBase)(*(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&ptr), unsafe.Sizeof(uintptr(0))))) - if respbbase.C != 0 { - return errors.Wrap(errors.New("code: "+strconv.Itoa(respbbase.C)+", msg: "+respbbase.M), getCallerFuncName()) + respbase := (*CodeMessageBase)(*(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&ptr), unsafe.Sizeof(uintptr(0))))) + if respbase.C != 0 { + return errors.Wrap(errors.New("code: "+strconv.Itoa(respbase.C)+", msg: "+respbase.M), getCallerFuncName()) + } + return nil +} + +// DeleteOpenAPI 向 ep 发送 DELETE 请求 +func (bot *Bot) DeleteOpenAPI(ep string) error { + req, err := NewHTTPEndpointDeleteRequestWithAuth(ep, bot.Authorization()) + if err != nil { + return errors.Wrap(err, getCallerFuncName()) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return errors.Wrap(err, getCallerFuncName()) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return errors.Wrap(errors.New("code: "+strconv.Itoa(resp.StatusCode)+", msg: "+resp.Status), getCallerFuncName()) } return nil } @@ -51,9 +68,33 @@ func (bot *Bot) PostOpenAPI(ep string, ptr any, body io.Reader) error { if err != nil { return errors.Wrap(err, getCallerFuncName()) } - respbbase := (*CodeMessageBase)(*(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&ptr), unsafe.Sizeof(uintptr(0))))) - if respbbase.C != 0 { - return errors.Wrap(errors.New("code: "+strconv.Itoa(respbbase.C)+", msg: "+respbbase.M), getCallerFuncName()) + respbase := (*CodeMessageBase)(*(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&ptr), unsafe.Sizeof(uintptr(0))))) + if respbase.C != 0 { + return errors.Wrap(errors.New("code: "+strconv.Itoa(respbase.C)+", msg: "+respbase.M), getCallerFuncName()) + } + return nil +} + +//go:generate go run codegen/patchopenapiof/main.go Channel + +// PatchOpenAPI 从 ep 得到 json 结构化数据返回值写到 ptr, ptr 必须在开头继承 CodeMessageBase +func (bot *Bot) PatchOpenAPI(ep string, ptr any, body io.Reader) error { + req, err := NewHTTPEndpointPatchRequestWithAuth(ep, bot.Authorization(), body) + if err != nil { + return errors.Wrap(err, getCallerFuncName()) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return errors.Wrap(err, getCallerFuncName()) + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(ptr) + if err != nil { + return errors.Wrap(err, getCallerFuncName()) + } + respbase := (*CodeMessageBase)(*(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&ptr), unsafe.Sizeof(uintptr(0))))) + if respbase.C != 0 { + return errors.Wrap(errors.New("code: "+strconv.Itoa(respbase.C)+", msg: "+respbase.M), getCallerFuncName()) } return nil } diff --git a/openapi_channel.go b/openapi_channel.go index 56c8dde..0841ba6 100644 --- a/openapi_channel.go +++ b/openapi_channel.go @@ -1,34 +1,76 @@ package nano +// ChannelType https://bot.q.qq.com/wiki/develop/api/openapi/channel/model.html#channeltype +type ChannelType int + +const ( + ChannelTypeText ChannelType = iota // 文字子频道 + ChannelTypeReserved1 // 保留,不可用 + ChannelTypeAudio // 语音子频道 + ChannelTypeReserved2 // 保留,不可用 + ChannelTypeSubchannel // 子频道分组 + ChannelTypeLive = 10000 + iota // 直播子频道 + ChannelTypeApplication // 应用子频道 + ChannelTypeForum // 论坛子频道 +) + +// ChannelSubType https://bot.q.qq.com/wiki/develop/api/openapi/channel/model.html#channelsubtype +type ChannelSubType int + +const ( + ChannelSubTypeChat ChannelSubType = iota // 闲聊 + ChannelSubTypeAnnounce // 公告 + ChannelSubTypeKouryaku // 攻略 + ChannelSubTypeGame // 开黑 +) + +// PrivateType https://bot.q.qq.com/wiki/develop/api/openapi/channel/model.html#privatetype +type PrivateType int + +const ( + PrivateTypePublic PrivateType = iota // 公开频道 + PrivateTypeOnlyAdmin // 群主管理员可见 + PrivateTypeAdminAndShimei // 群主管理员+指定成员,可使用 修改子频道权限接口 指定成员 +) + +// SpeakPermission https://bot.q.qq.com/wiki/develop/api/openapi/channel/model.html#speakpermission +type SpeakPermission int + +const ( + SpeakPermissionInvalid = iota // 无效类型 + SpeakPermissionAll // 所有人 + SpeakPermissionAdminAndShimei // 群主管理员+指定成员,可使用 修改子频道权限接口 指定成员 +) + // Channel 子频道对象 // // https://bot.q.qq.com/wiki/develop/api/openapi/channel/model.html type Channel struct { - ID string `json:"id"` - GuildID string `json:"guild_id"` - Name string `json:"name"` - Type int `json:"type"` - SubType int `json:"sub_type"` - Position int `json:"position"` - ParentID string `json:"parent_id"` - OwnerID string `json:"owner_id"` - PrivateType int `json:"private_type"` - SpeakPermission int `json:"speak_permission"` - ApplicationID string `json:"application_id"` - Permissions string `json:"permissions"` + ID string `json:"id"` + GuildID string `json:"guild_id"` + Name string `json:"name"` + Type ChannelType `json:"type"` + SubType ChannelSubType `json:"sub_type"` + Position int `json:"position"` + ParentID string `json:"parent_id"` + OwnerID string `json:"owner_id"` + PrivateType PrivateType `json:"private_type"` + SpeakPermission SpeakPermission `json:"speak_permission"` + ApplicationID string `json:"application_id"` // ApplicationID see https://bot.q.qq.com/wiki/develop/api/openapi/channel/model.html#%E5%BA%94%E7%94%A8%E5%AD%90%E9%A2%91%E9%81%93%E7%9A%84%E5%BA%94%E7%94%A8%E7%B1%BB%E5%9E%8B + Permissions string `json:"permissions"` } // ChannelArray []Channel 的别名 type ChannelArray []Channel -// GetChannelsOfGuild 获取 id 指定的频道下的子频道列表 +// GetChannelsOfGuild 获取 guild_id 指定的频道下的子频道列表 // // https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channels.html func (bot *Bot) GetChannelsOfGuild(id string) (*ChannelArray, error) { return bot.getOpenAPIofChannelArray("/guilds/" + id + "/channels") } -// GetChannelByID 用于获取 id 指定的子频道的详情 +// GetChannelByID 用于获取 channel_id 指定的子频道的详情 // // https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channel.html func (bot *Bot) GetChannelByID(id string) (*Channel, error) { @@ -36,15 +78,61 @@ func (bot *Bot) GetChannelByID(id string) (*Channel, error) { } // ChannelPost 子频道 post 操作所用对象 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/channel/post_channels.html type ChannelPost struct { - Name string `json:"name"` - Type int `json:"type"` - SubType int `json:"sub_type"` - Position int `json:"position"` - ParentID string `json:"parent_id"` - OwnerID string `json:"owner_id,omitempty"` - PrivateType int `json:"private_type"` - PrivateUserIds []string `json:"private_user_ids,omitempty"` - SpeakPermission int `json:"speak_permission,omitempty"` - ApplicationID string `json:"application_id,omitempty"` + Name string `json:"name"` + Type ChannelType `json:"type"` + SubType ChannelSubType `json:"sub_type"` + Position int `json:"position"` + ParentID string `json:"parent_id"` + OwnerID string `json:"owner_id,omitempty"` + PrivateType PrivateType `json:"private_type"` + PrivateUserIds []string `json:"private_user_ids,omitempty"` + SpeakPermission SpeakPermission `json:"speak_permission,omitempty"` + ApplicationID string `json:"application_id,omitempty"` +} + +// CreateChannel 用于在 guild_id 指定的频道下创建一个子频道 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/channel/post_channels.html +func (bot *Bot) CreateChannel(id string, config *ChannelPost) (*Channel, error) { + return bot.postOpenAPIofChannel("/guilds/"+id+"/channels", WriteBodyFromJSON(config)) +} + +// ChannelPatch 子频道 patch 操作所用对象 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/channel/patch_channel.html +type ChannelPatch struct { + Name string `json:"name,omitempty"` + Position int `json:"position,omitempty"` + ParentID *string `json:"parent_id,omitempty"` + PrivateType *PrivateType `json:"private_type,omitempty"` + SpeakPermission *SpeakPermission `json:"speak_permission,omitempty"` +} + +// PatchChannel 修改 channel_id 指定的子频道的信息 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/channel/patch_channel.html +func (bot *Bot) PatchChannel(id string, config *ChannelPatch) (*Channel, error) { + return bot.patchOpenAPIofChannel("/channels/"+id, WriteBodyFromJSON(config)) +} + +// DeleteChannel 删除 channel_id 指定的子频道 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/channel/delete_channel.html +func (bot *Bot) DeleteChannel(id string) error { + return bot.DeleteOpenAPI("/channels/" + id) +} + +// GetOnlineNumsInChannel 查询音视频/直播子频道 channel_id 的在线成员数 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_online_nums.html +func (bot *Bot) GetOnlineNumsInChannel(id string) (int, error) { + resp := struct { + CodeMessageBase + N int `json:"online_nums"` + }{} + err := bot.GetOpenAPI("/channels/"+id+"/online_nums", &resp) + return resp.N, err } diff --git a/openapi_codegen_patchopenapiof.go b/openapi_codegen_patchopenapiof.go new file mode 100644 index 0000000..a74ffaa --- /dev/null +++ b/openapi_codegen_patchopenapiof.go @@ -0,0 +1,23 @@ +// Code generated by codegen/patchopenapiof. DO NOT EDIT. + +package nano + +import ( + "io" + "unsafe" + + "github.com/pkg/errors" +) + +func (bot *Bot) patchOpenAPIofChannel(ep string, body io.Reader) (*Channel, error) { + resp := &struct { + CodeMessageBase + Channel + }{} + err := bot.PatchOpenAPI(ep, resp, body) + if err != nil { + err = errors.Wrap(err, getCallerFuncName()) + return nil, err + } + return (*Channel)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil +} diff --git a/openapi_guild.go b/openapi_guild.go index 8b2c0d3..1270d5f 100644 --- a/openapi_guild.go +++ b/openapi_guild.go @@ -20,7 +20,7 @@ type Guild struct { // GuildArray []Guild 的别名 type GuildArray []Guild -// GetGuildByID 获取 id 指定的频道的详情 +// GetGuildByID 获取 guild_id 指定的频道的详情 // // https://bot.q.qq.com/wiki/develop/api/openapi/guild/get_guild.html func (bot *Bot) GetGuildByID(id string) (*Guild, error) { diff --git a/openapi_wss.go b/openapi_wss.go index eb13c98..8d5dd6b 100644 --- a/openapi_wss.go +++ b/openapi_wss.go @@ -9,10 +9,7 @@ func (bot *Bot) GetGeneralWSSGateway() (string, error) { U string `json:"url"` }{} err := bot.GetOpenAPI("/gateway", &resp) - if err != nil { - return "", err - } - return resp.U, nil + return resp.U, err } // ShardWSSGateway 带分片 WSS 接入点响应数据