1
0
mirror of https://github.com/fumiama/NanoBot.git synced 2026-06-10 05:00:24 +08:00

add channel patch/delete

This commit is contained in:
源文雨
2023-10-10 22:02:52 +09:00
parent f04743c7a5
commit 30554f4ffc
9 changed files with 268 additions and 51 deletions

View File

@@ -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)
}
}
}

View File

@@ -5,20 +5,6 @@ import (
"strings" "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 获取调用者函数名 // getCallerFuncName 获取调用者函数名
func getCallerFuncName() string { func getCallerFuncName() string {
pc, _, _, ok := runtime.Caller(2) pc, _, _, ok := runtime.Caller(2)

24
http.go
View File

@@ -21,6 +21,16 @@ func NewHTTPEndpointGetRequestWithAuth(ep string, auth string) (req *http.Reques
return 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 请求 // NewHTTPEndpointPostRequestWithAuth 新建带鉴权头的 HTTP POST 请求
func NewHTTPEndpointPostRequestWithAuth(ep string, auth string, body io.Reader) (req *http.Request, err error) { func NewHTTPEndpointPostRequestWithAuth(ep string, auth string, body io.Reader) (req *http.Request, err error) {
req, err = http.NewRequest("POST", StandardAPI+ep, body) req, err = http.NewRequest("POST", StandardAPI+ep, body)
@@ -31,6 +41,16 @@ func NewHTTPEndpointPostRequestWithAuth(ep string, auth string, body io.Reader)
return 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 后 // WriteHTTPQueryIfNotNil 如果非空则将请求添加到 baseurl 后
// //
// ex. WriteHTTPQueryIfNotNil("http://a.com/api", "a", 0, "b", 1, "c", 2) is http://a.com/api?b=1&c=2 // 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] return sb.String()[:sb.Len()-1]
} }
// WritePostBodyFromJSON 从 json 结构体 ptr 写入 bytes.Buffer, 忽略 error (内部使用不会出错) // WriteBodyFromJSON 从 json 结构体 ptr 写入 bytes.Buffer, 忽略 error (内部使用不会出错)
func WritePostBodyFromJSON(ptr any) *bytes.Buffer { func WriteBodyFromJSON(ptr any) *bytes.Buffer {
buf := bytes.NewBuffer(make([]byte, 0, 1024)) buf := bytes.NewBuffer(make([]byte, 0, 1024))
_ = json.NewEncoder(buf).Encode(ptr) _ = json.NewEncoder(buf).Encode(ptr)
return buf return buf

11
http_test.go Normal file
View File

@@ -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)
}
}

View File

@@ -27,9 +27,26 @@ func (bot *Bot) GetOpenAPI(ep string, ptr any) error {
if err != nil { if err != nil {
return errors.Wrap(err, getCallerFuncName()) return errors.Wrap(err, getCallerFuncName())
} }
respbbase := (*CodeMessageBase)(*(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&ptr), unsafe.Sizeof(uintptr(0))))) respbase := (*CodeMessageBase)(*(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&ptr), unsafe.Sizeof(uintptr(0)))))
if respbbase.C != 0 { if respbase.C != 0 {
return errors.Wrap(errors.New("code: "+strconv.Itoa(respbbase.C)+", msg: "+respbbase.M), getCallerFuncName()) 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 return nil
} }
@@ -51,9 +68,33 @@ func (bot *Bot) PostOpenAPI(ep string, ptr any, body io.Reader) error {
if err != nil { if err != nil {
return errors.Wrap(err, getCallerFuncName()) return errors.Wrap(err, getCallerFuncName())
} }
respbbase := (*CodeMessageBase)(*(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&ptr), unsafe.Sizeof(uintptr(0))))) respbase := (*CodeMessageBase)(*(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(&ptr), unsafe.Sizeof(uintptr(0)))))
if respbbase.C != 0 { if respbase.C != 0 {
return errors.Wrap(errors.New("code: "+strconv.Itoa(respbbase.C)+", msg: "+respbbase.M), getCallerFuncName()) 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 return nil
} }

View File

@@ -1,34 +1,76 @@
package nano 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 子频道对象 // Channel 子频道对象
// //
// https://bot.q.qq.com/wiki/develop/api/openapi/channel/model.html // https://bot.q.qq.com/wiki/develop/api/openapi/channel/model.html
type Channel struct { type Channel struct {
ID string `json:"id"` ID string `json:"id"`
GuildID string `json:"guild_id"` GuildID string `json:"guild_id"`
Name string `json:"name"` Name string `json:"name"`
Type int `json:"type"` Type ChannelType `json:"type"`
SubType int `json:"sub_type"` SubType ChannelSubType `json:"sub_type"`
Position int `json:"position"` Position int `json:"position"`
ParentID string `json:"parent_id"` ParentID string `json:"parent_id"`
OwnerID string `json:"owner_id"` OwnerID string `json:"owner_id"`
PrivateType int `json:"private_type"` PrivateType PrivateType `json:"private_type"`
SpeakPermission int `json:"speak_permission"` SpeakPermission SpeakPermission `json:"speak_permission"`
ApplicationID string `json:"application_id"` 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"` Permissions string `json:"permissions"`
} }
// ChannelArray []Channel 的别名 // ChannelArray []Channel 的别名
type ChannelArray []Channel type ChannelArray []Channel
// GetChannelsOfGuild 获取 id 指定的频道下的子频道列表 // GetChannelsOfGuild 获取 guild_id 指定的频道下的子频道列表
// //
// https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channels.html // https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channels.html
func (bot *Bot) GetChannelsOfGuild(id string) (*ChannelArray, error) { func (bot *Bot) GetChannelsOfGuild(id string) (*ChannelArray, error) {
return bot.getOpenAPIofChannelArray("/guilds/" + id + "/channels") return bot.getOpenAPIofChannelArray("/guilds/" + id + "/channels")
} }
// GetChannelByID 用于获取 id 指定的子频道的详情 // GetChannelByID 用于获取 channel_id 指定的子频道的详情
// //
// https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channel.html // https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channel.html
func (bot *Bot) GetChannelByID(id string) (*Channel, error) { func (bot *Bot) GetChannelByID(id string) (*Channel, error) {
@@ -36,15 +78,61 @@ func (bot *Bot) GetChannelByID(id string) (*Channel, error) {
} }
// ChannelPost 子频道 post 操作所用对象 // ChannelPost 子频道 post 操作所用对象
//
// https://bot.q.qq.com/wiki/develop/api/openapi/channel/post_channels.html
type ChannelPost struct { type ChannelPost struct {
Name string `json:"name"` Name string `json:"name"`
Type int `json:"type"` Type ChannelType `json:"type"`
SubType int `json:"sub_type"` SubType ChannelSubType `json:"sub_type"`
Position int `json:"position"` Position int `json:"position"`
ParentID string `json:"parent_id"` ParentID string `json:"parent_id"`
OwnerID string `json:"owner_id,omitempty"` OwnerID string `json:"owner_id,omitempty"`
PrivateType int `json:"private_type"` PrivateType PrivateType `json:"private_type"`
PrivateUserIds []string `json:"private_user_ids,omitempty"` PrivateUserIds []string `json:"private_user_ids,omitempty"`
SpeakPermission int `json:"speak_permission,omitempty"` SpeakPermission SpeakPermission `json:"speak_permission,omitempty"`
ApplicationID string `json:"application_id,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
} }

View File

@@ -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
}

View File

@@ -20,7 +20,7 @@ type Guild struct {
// GuildArray []Guild 的别名 // GuildArray []Guild 的别名
type GuildArray []Guild type GuildArray []Guild
// GetGuildByID 获取 id 指定的频道的详情 // GetGuildByID 获取 guild_id 指定的频道的详情
// //
// https://bot.q.qq.com/wiki/develop/api/openapi/guild/get_guild.html // https://bot.q.qq.com/wiki/develop/api/openapi/guild/get_guild.html
func (bot *Bot) GetGuildByID(id string) (*Guild, error) { func (bot *Bot) GetGuildByID(id string) (*Guild, error) {

View File

@@ -9,10 +9,7 @@ func (bot *Bot) GetGeneralWSSGateway() (string, error) {
U string `json:"url"` U string `json:"url"`
}{} }{}
err := bot.GetOpenAPI("/gateway", &resp) err := bot.GetOpenAPI("/gateway", &resp)
if err != nil { return resp.U, err
return "", err
}
return resp.U, nil
} }
// ShardWSSGateway 带分片 WSS 接入点响应数据 // ShardWSSGateway 带分片 WSS 接入点响应数据