diff --git a/codegen/getopenapiof/main.go b/codegen/getopenapiof/main.go index 4a66a5b..cd20cd7 100644 --- a/codegen/getopenapiof/main.go +++ b/codegen/getopenapiof/main.go @@ -22,12 +22,11 @@ func (bot *Bot) getOpenAPIof[T any](ep string) (*[T any], error) { CodeMessageBase [T any] }{} - err := bot.GetOpenAPI(ep, resp) + err := bot.GetOpenAPI(ep, "", resp) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*[T any])(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*[T any])(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } ` diff --git a/codegen/patchopenapiof/main.go b/codegen/patchopenapiof/main.go index b5ac570..7f8d439 100644 --- a/codegen/patchopenapiof/main.go +++ b/codegen/patchopenapiof/main.go @@ -23,12 +23,11 @@ func (bot *Bot) patchOpenAPIof[T any](ep string, body io.Reader) (*[T any], erro CodeMessageBase [T any] }{} - err := bot.PatchOpenAPI(ep, resp, body) + 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 + return (*[T any])(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } ` diff --git a/codegen/postopenapiof/main.go b/codegen/postopenapiof/main.go index fdd54b5..6753efe 100644 --- a/codegen/postopenapiof/main.go +++ b/codegen/postopenapiof/main.go @@ -18,17 +18,16 @@ import ( ` const template = ` -func (bot *Bot) postOpenAPIof[T any](ep string, body io.Reader) (*[T any], error) { +func (bot *Bot) postOpenAPIof[T any](ep, contenttype string, body io.Reader) (*[T any], error) { resp := &struct { CodeMessageBase [T any] }{} - err := bot.PostOpenAPI(ep, resp, body) + err := bot.PostOpenAPI(ep, contenttype, 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 + return (*[T any])(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } ` diff --git a/codegen/putopenapiof/main.go b/codegen/putopenapiof/main.go index ad672cd..11b6730 100644 --- a/codegen/putopenapiof/main.go +++ b/codegen/putopenapiof/main.go @@ -23,12 +23,11 @@ func (bot *Bot) putOpenAPIof[T any](ep string, body io.Reader) (*[T any], error) CodeMessageBase [T any] }{} - err := bot.PutOpenAPI(ep, resp, body) + err := bot.PutOpenAPI(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 + return (*[T any])(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } ` diff --git a/go.mod b/go.mod index 17c6ae2..38c57e8 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,9 @@ module github.com/fumiama/NanoBot go 1.20 -require github.com/pkg/errors v0.9.1 +require ( + github.com/fumiama/go-base16384 v1.7.0 + github.com/pkg/errors v0.9.1 +) + +require golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 7c401c3..c0bd33b 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,17 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA= +github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/helper.go b/helper.go index 7031b8d..31a765f 100644 --- a/helper.go +++ b/helper.go @@ -5,9 +5,8 @@ import ( "strings" ) -// getCallerFuncName 获取调用者函数名 -func getCallerFuncName() string { - pc, _, _, ok := runtime.Caller(2) +func getFuncNameWithSkip(n int) string { + pc, _, _, ok := runtime.Caller(n) if !ok { return "" } @@ -18,3 +17,13 @@ func getCallerFuncName() string { } return fullname[i:] } + +// getThisFuncName 获取正在执行的函数名 +func getThisFuncName() string { + return getFuncNameWithSkip(1) +} + +// getCallerFuncName 获取调用者函数名 +func getCallerFuncName() string { + return getFuncNameWithSkip(2) +} diff --git a/http.go b/http.go index 265d7ed..da08977 100644 --- a/http.go +++ b/http.go @@ -2,70 +2,63 @@ package nano import ( "bytes" + "encoding/base64" "encoding/json" "fmt" "io" + "mime/multipart" "net/http" "net/url" + "os" "reflect" "strings" + + base14 "github.com/fumiama/go-base16384" ) -// NewHTTPEndpointGetRequestWithAuth 新建带鉴权头的 HTTP GET 请求 -func NewHTTPEndpointGetRequestWithAuth(ep string, auth string) (req *http.Request, err error) { - req, err = http.NewRequest("GET", StandardAPI+ep, nil) +func newHTTPEndpointRequestWithAuth(method, contenttype, ep string, auth string, body io.Reader) (req *http.Request, err error) { + req, err = http.NewRequest(method, StandardAPI+ep, body) if err != nil { return } req.Header.Add("Authorization", auth) + if contenttype == "" { + contenttype = "application/json" + } + req.Header.Add("Content-Type", contenttype) return } +// NewHTTPEndpointGetRequestWithAuth 新建带鉴权头的 HTTP GET 请求 +func NewHTTPEndpointGetRequestWithAuth(ep string, contenttype string, auth string) (*http.Request, error) { + return newHTTPEndpointRequestWithAuth("GET", contenttype, ep, auth, nil) +} + // NewHTTPEndpointPutRequestWithAuth 新建带鉴权头的 HTTP PUT 请求 -func NewHTTPEndpointPutRequestWithAuth(ep string, auth string, body io.Reader) (req *http.Request, err error) { - req, err = http.NewRequest("PUT", StandardAPI+ep, body) - if err != nil { - return - } - req.Header.Add("Authorization", auth) - return +func NewHTTPEndpointPutRequestWithAuth(ep string, contenttype string, auth string, body io.Reader) (*http.Request, error) { + return newHTTPEndpointRequestWithAuth("PUT", contenttype, ep, auth, body) } // NewHTTPEndpointDeleteRequestWithAuth 新建带鉴权头的 HTTP DELETE 请求 -func NewHTTPEndpointDeleteRequestWithAuth(ep string, auth string, body io.Reader) (req *http.Request, err error) { - req, err = http.NewRequest("DELETE", StandardAPI+ep, body) - if err != nil { - return - } - req.Header.Add("Authorization", auth) - return +func NewHTTPEndpointDeleteRequestWithAuth(ep string, contenttype string, auth string, body io.Reader) (*http.Request, error) { + return newHTTPEndpointRequestWithAuth("DELETE", contenttype, ep, auth, body) } // 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) - if err != nil { - return - } - req.Header.Add("Authorization", auth) - return +func NewHTTPEndpointPostRequestWithAuth(ep string, contenttype string, auth string, body io.Reader) (*http.Request, error) { + return newHTTPEndpointRequestWithAuth("POST", contenttype, ep, auth, body) } // 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 +func NewHTTPEndpointPatchRequestWithAuth(ep string, contenttype string, auth string, body io.Reader) (*http.Request, error) { + return newHTTPEndpointRequestWithAuth("PATCH", contenttype, ep, auth, body) } // WriteHTTPQueryIfNotNil 如果非空则将请求添加到 baseurl 后 // // ex. WriteHTTPQueryIfNotNil("http://a.com/api", "a", 0, "b", 1, "c", 2) is http://a.com/api?b=1&c=2 func WriteHTTPQueryIfNotNil(baseurl string, queries ...any) string { - if len(queries) == 0 { + if len(queries) < 2 { return baseurl } hasstart := false @@ -101,3 +94,82 @@ func WriteBodyFromJSON(ptr any) *bytes.Buffer { _ = json.NewEncoder(buf).Encode(ptr) return buf } + +// WriteBodyByMultipartFormData 使用 multipart/form-data 上传 +func WriteBodyByMultipartFormData(params ...any) (*bytes.Buffer, string, error) { + if len(params)%2 != 0 { + panic("invalid params to " + getThisFuncName()) + } + fieldname := "" + buf := bytes.NewBuffer(make([]byte, 0, 65536)) + w := multipart.NewWriter(buf) + defer w.Close() + for i, x := range params { + if i%2 == 0 { // 参数 + fieldname = x.(string) + continue + } + rx := reflect.ValueOf(x) + if rx.IsZero() { + continue + } + r, err := w.CreateFormField(fieldname) + if err != nil { + return nil, "", err + } + if rx.Elem().Kind() == reflect.Struct { // 使用 json 编码 + err = json.NewEncoder(r).Encode(x) + if err != nil { + return nil, "", err + } + continue + } + switch o := x.(type) { + case string: + if strings.HasPrefix(o, "file:///") { // 是文件路径 + f, err := os.Open(o[8:]) + if err != nil { + return nil, "", err + } + defer f.Close() + _, err = io.Copy(r, f) + if err != nil { + return nil, "", err + } + continue + } + if strings.HasPrefix(o, "base64://") { // 是 base64 + _, err = io.Copy(r, base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(o[9:]))) + if err != nil { + return nil, "", err + } + continue + } + if strings.HasPrefix(o, "base16384://") { // 是 base16384 + _, err = io.Copy(r, base14.NewDecoder(bytes.NewBufferString(o[12:]))) + if err != nil { + return nil, "", err + } + continue + } + _, err = io.WriteString(r, o) + if err != nil { + return nil, "", err + } + continue + case []byte: + _, err = r.Write(o) + if err != nil { + return nil, "", err + } + continue + default: + _, err = io.WriteString(r, fmt.Sprint(o)) + if err != nil { + return nil, "", err + } + continue + } + } + return buf, w.FormDataContentType(), nil +} diff --git a/openapi.go b/openapi.go index 2fa3aed..457d5b8 100644 --- a/openapi.go +++ b/openapi.go @@ -11,11 +11,11 @@ import ( "github.com/pkg/errors" ) -//go:generate go run codegen/getopenapiof/main.go ShardWSSGateway User Guild Channel Member RoleMembers GuildRoleList ChannelPermissions +//go:generate go run codegen/getopenapiof/main.go ShardWSSGateway User Guild Channel Member RoleMembers GuildRoleList ChannelPermissions Message // GetOpenAPI 从 ep 获取 json 结构化数据写到 ptr, ptr 除 Slice 外必须在开头继承 CodeMessageBase -func (bot *Bot) GetOpenAPI(ep string, ptr any) error { - req, err := NewHTTPEndpointGetRequestWithAuth(ep, bot.Authorization()) +func (bot *Bot) GetOpenAPI(ep, contenttype string, ptr any) error { + req, err := NewHTTPEndpointGetRequestWithAuth(ep, contenttype, bot.Authorization()) if err != nil { return errors.Wrap(err, getCallerFuncName()) } @@ -50,8 +50,8 @@ func (bot *Bot) GetOpenAPI(ep string, ptr any) error { //go:generate go run codegen/putopenapiof/main.go GuildRoleChannelID // PutOpenAPI 向 ep 发送 PUT 并获取 json 结构化数据返回写到 ptr, ptr 除 Slice 外必须在开头继承 CodeMessageBase -func (bot *Bot) PutOpenAPI(ep string, ptr any, body io.Reader) error { - req, err := NewHTTPEndpointPutRequestWithAuth(ep, bot.Authorization(), body) +func (bot *Bot) PutOpenAPI(ep, contenttype string, ptr any, body io.Reader) error { + req, err := NewHTTPEndpointPutRequestWithAuth(ep, contenttype, bot.Authorization(), body) if err != nil { return errors.Wrap(err, getCallerFuncName()) } @@ -84,8 +84,8 @@ func (bot *Bot) PutOpenAPI(ep string, ptr any, body io.Reader) error { } // DeleteOpenAPI 向 ep 发送 DELETE 请求 -func (bot *Bot) DeleteOpenAPI(ep string, body io.Reader) error { - req, err := NewHTTPEndpointDeleteRequestWithAuth(ep, bot.Authorization(), body) +func (bot *Bot) DeleteOpenAPI(ep, contenttype string, body io.Reader) error { + req, err := NewHTTPEndpointDeleteRequestWithAuth(ep, contenttype, bot.Authorization(), body) if err != nil { return errors.Wrap(err, getCallerFuncName()) } @@ -103,11 +103,11 @@ func (bot *Bot) DeleteOpenAPI(ep string, body io.Reader) error { return nil } -//go:generate go run codegen/postopenapiof/main.go Channel GuildRoleCreate +//go:generate go run codegen/postopenapiof/main.go Channel GuildRoleCreate Message // PostOpenAPI 从 ep 得到 json 结构化数据返回值写到 ptr, ptr 除 Slice 外必须在开头继承 CodeMessageBase -func (bot *Bot) PostOpenAPI(ep string, ptr any, body io.Reader) error { - req, err := NewHTTPEndpointPostRequestWithAuth(ep, bot.Authorization(), body) +func (bot *Bot) PostOpenAPI(ep, contenttype string, ptr any, body io.Reader) error { + req, err := NewHTTPEndpointPostRequestWithAuth(ep, contenttype, bot.Authorization(), body) if err != nil { return errors.Wrap(err, getCallerFuncName()) } @@ -142,8 +142,8 @@ func (bot *Bot) PostOpenAPI(ep string, ptr any, body io.Reader) error { //go:generate go run codegen/patchopenapiof/main.go Channel GuildRolePatch // PatchOpenAPI 从 ep 得到 json 结构化数据返回值写到 ptr, ptr 除 Slice 外必须在开头继承 CodeMessageBase -func (bot *Bot) PatchOpenAPI(ep string, ptr any, body io.Reader) error { - req, err := NewHTTPEndpointPatchRequestWithAuth(ep, bot.Authorization(), body) +func (bot *Bot) PatchOpenAPI(ep, contenttype string, ptr any, body io.Reader) error { + req, err := NewHTTPEndpointPatchRequestWithAuth(ep, contenttype, bot.Authorization(), body) if err != nil { return errors.Wrap(err, getCallerFuncName()) } diff --git a/openapi_channel.go b/openapi_channel.go index 07f90f7..8339bc9 100644 --- a/openapi_channel.go +++ b/openapi_channel.go @@ -64,7 +64,7 @@ type Channel struct { // // https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channels.html func (bot *Bot) GetChannelsOfGuild(id string) (channels []Channel, err error) { - err = bot.GetOpenAPI("/guilds/"+id+"/channels", &channels) + err = bot.GetOpenAPI("/guilds/"+id+"/channels", "", &channels) return } @@ -95,7 +95,7 @@ type ChannelPost struct { // // https://bot.q.qq.com/wiki/develop/api/openapi/channel/post_channels.html func (bot *Bot) CreateChannelInGuild(id string, config *ChannelPost) (*Channel, error) { - return bot.postOpenAPIofChannel("/guilds/"+id+"/channels", WriteBodyFromJSON(config)) + return bot.postOpenAPIofChannel("/guilds/"+id+"/channels", "", WriteBodyFromJSON(config)) } // ChannelPatch 子频道 patch 操作所用对象 @@ -120,7 +120,7 @@ func (bot *Bot) PatchChannelOf(id string, config *ChannelPatch) (*Channel, error // // https://bot.q.qq.com/wiki/develop/api/openapi/channel/delete_channel.html func (bot *Bot) DeleteChannelOf(id string) error { - return bot.DeleteOpenAPI("/channels/"+id, nil) + return bot.DeleteOpenAPI("/channels/"+id, "", nil) } // GetOnlineNumsInChannel 查询音视频/直播子频道 channel_id 的在线成员数 @@ -131,7 +131,7 @@ func (bot *Bot) GetOnlineNumsInChannel(id string) (int, error) { CodeMessageBase N int `json:"online_nums"` }{} - err := bot.GetOpenAPI("/channels/"+id+"/online_nums", &resp) + err := bot.GetOpenAPI("/channels/"+id+"/online_nums", "", &resp) return resp.N, err } @@ -156,7 +156,7 @@ func (bot *Bot) GetChannelPermissionsOfUser(channelid, userid string) (*ChannelP // // https://bot.q.qq.com/wiki/develop/api/openapi/channel_permissions/put_channel_permissions.html func (bot *Bot) SetChannelPermissionsOfUser(channelid, userid string, add, remove string) error { - return bot.PutOpenAPI("/channels/"+channelid+"/members/"+userid+"/permissions", nil, WriteBodyFromJSON(&struct { + return bot.PutOpenAPI("/channels/"+channelid+"/members/"+userid+"/permissions", "", nil, WriteBodyFromJSON(&struct { A string `json:"add"` R string `json:"remove"` }{add, remove})) @@ -173,7 +173,7 @@ func (bot *Bot) GetChannelPermissionsOfRole(channelid, roleid string) (*ChannelP // // https://bot.q.qq.com/wiki/develop/api/openapi/channel_permissions/put_channel_roles_permissions.html func (bot *Bot) SetChannelPermissionsOfRole(channelid, roleid string, add, remove string) error { - return bot.PutOpenAPI("/channels/"+channelid+"/roles/"+roleid+"/permissions", nil, WriteBodyFromJSON(&struct { + return bot.PutOpenAPI("/channels/"+channelid+"/roles/"+roleid+"/permissions", "", nil, WriteBodyFromJSON(&struct { A string `json:"add"` R string `json:"remove"` }{add, remove})) diff --git a/openapi_codegen_getopenapiof.go b/openapi_codegen_getopenapiof.go index ed81fdc..9fcf172 100644 --- a/openapi_codegen_getopenapiof.go +++ b/openapi_codegen_getopenapiof.go @@ -13,12 +13,11 @@ func (bot *Bot) getOpenAPIofShardWSSGateway(ep string) (*ShardWSSGateway, error) CodeMessageBase ShardWSSGateway }{} - err := bot.GetOpenAPI(ep, resp) + err := bot.GetOpenAPI(ep, "", resp) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*ShardWSSGateway)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*ShardWSSGateway)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } func (bot *Bot) getOpenAPIofUser(ep string) (*User, error) { @@ -26,12 +25,11 @@ func (bot *Bot) getOpenAPIofUser(ep string) (*User, error) { CodeMessageBase User }{} - err := bot.GetOpenAPI(ep, resp) + err := bot.GetOpenAPI(ep, "", resp) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*User)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*User)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } func (bot *Bot) getOpenAPIofGuild(ep string) (*Guild, error) { @@ -39,12 +37,11 @@ func (bot *Bot) getOpenAPIofGuild(ep string) (*Guild, error) { CodeMessageBase Guild }{} - err := bot.GetOpenAPI(ep, resp) + err := bot.GetOpenAPI(ep, "", resp) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*Guild)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*Guild)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } func (bot *Bot) getOpenAPIofChannel(ep string) (*Channel, error) { @@ -52,12 +49,11 @@ func (bot *Bot) getOpenAPIofChannel(ep string) (*Channel, error) { CodeMessageBase Channel }{} - err := bot.GetOpenAPI(ep, resp) + err := bot.GetOpenAPI(ep, "", resp) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*Channel)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*Channel)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } func (bot *Bot) getOpenAPIofMember(ep string) (*Member, error) { @@ -65,12 +61,11 @@ func (bot *Bot) getOpenAPIofMember(ep string) (*Member, error) { CodeMessageBase Member }{} - err := bot.GetOpenAPI(ep, resp) + err := bot.GetOpenAPI(ep, "", resp) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*Member)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*Member)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } func (bot *Bot) getOpenAPIofRoleMembers(ep string) (*RoleMembers, error) { @@ -78,12 +73,11 @@ func (bot *Bot) getOpenAPIofRoleMembers(ep string) (*RoleMembers, error) { CodeMessageBase RoleMembers }{} - err := bot.GetOpenAPI(ep, resp) + err := bot.GetOpenAPI(ep, "", resp) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*RoleMembers)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*RoleMembers)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } func (bot *Bot) getOpenAPIofGuildRoleList(ep string) (*GuildRoleList, error) { @@ -91,12 +85,11 @@ func (bot *Bot) getOpenAPIofGuildRoleList(ep string) (*GuildRoleList, error) { CodeMessageBase GuildRoleList }{} - err := bot.GetOpenAPI(ep, resp) + err := bot.GetOpenAPI(ep, "", resp) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*GuildRoleList)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*GuildRoleList)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } func (bot *Bot) getOpenAPIofChannelPermissions(ep string) (*ChannelPermissions, error) { @@ -104,10 +97,21 @@ func (bot *Bot) getOpenAPIofChannelPermissions(ep string) (*ChannelPermissions, CodeMessageBase ChannelPermissions }{} - err := bot.GetOpenAPI(ep, resp) + err := bot.GetOpenAPI(ep, "", resp) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*ChannelPermissions)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*ChannelPermissions)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err +} + +func (bot *Bot) getOpenAPIofMessage(ep string) (*Message, error) { + resp := &struct { + CodeMessageBase + Message + }{} + err := bot.GetOpenAPI(ep, "", resp) + if err != nil { + err = errors.Wrap(err, getCallerFuncName()) + } + return (*Message)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } diff --git a/openapi_codegen_patchopenapiof.go b/openapi_codegen_patchopenapiof.go index 552aa55..22edfa1 100644 --- a/openapi_codegen_patchopenapiof.go +++ b/openapi_codegen_patchopenapiof.go @@ -14,12 +14,11 @@ func (bot *Bot) patchOpenAPIofChannel(ep string, body io.Reader) (*Channel, erro CodeMessageBase Channel }{} - err := bot.PatchOpenAPI(ep, resp, body) + 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 + return (*Channel)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } func (bot *Bot) patchOpenAPIofGuildRolePatch(ep string, body io.Reader) (*GuildRolePatch, error) { @@ -27,10 +26,9 @@ func (bot *Bot) patchOpenAPIofGuildRolePatch(ep string, body io.Reader) (*GuildR CodeMessageBase GuildRolePatch }{} - err := bot.PatchOpenAPI(ep, resp, body) + err := bot.PatchOpenAPI(ep, "", resp, body) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*GuildRolePatch)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*GuildRolePatch)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } diff --git a/openapi_codegen_postopenapiof.go b/openapi_codegen_postopenapiof.go index 58b8cf7..44d6438 100644 --- a/openapi_codegen_postopenapiof.go +++ b/openapi_codegen_postopenapiof.go @@ -9,28 +9,38 @@ import ( "github.com/pkg/errors" ) -func (bot *Bot) postOpenAPIofChannel(ep string, body io.Reader) (*Channel, error) { +func (bot *Bot) postOpenAPIofChannel(ep, contenttype string, body io.Reader) (*Channel, error) { resp := &struct { CodeMessageBase Channel }{} - err := bot.PostOpenAPI(ep, resp, body) + err := bot.PostOpenAPI(ep, contenttype, resp, body) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*Channel)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*Channel)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } -func (bot *Bot) postOpenAPIofGuildRoleCreate(ep string, body io.Reader) (*GuildRoleCreate, error) { +func (bot *Bot) postOpenAPIofGuildRoleCreate(ep, contenttype string, body io.Reader) (*GuildRoleCreate, error) { resp := &struct { CodeMessageBase GuildRoleCreate }{} - err := bot.PostOpenAPI(ep, resp, body) + err := bot.PostOpenAPI(ep, contenttype, resp, body) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*GuildRoleCreate)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*GuildRoleCreate)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err +} + +func (bot *Bot) postOpenAPIofMessage(ep, contenttype string, body io.Reader) (*Message, error) { + resp := &struct { + CodeMessageBase + Message + }{} + err := bot.PostOpenAPI(ep, contenttype, resp, body) + if err != nil { + err = errors.Wrap(err, getCallerFuncName()) + } + return (*Message)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } diff --git a/openapi_codegen_putopenapiof.go b/openapi_codegen_putopenapiof.go index 026131c..ac4a895 100644 --- a/openapi_codegen_putopenapiof.go +++ b/openapi_codegen_putopenapiof.go @@ -14,10 +14,9 @@ func (bot *Bot) putOpenAPIofGuildRoleChannelID(ep string, body io.Reader) (*Guil CodeMessageBase GuildRoleChannelID }{} - err := bot.PutOpenAPI(ep, resp, body) + err := bot.PutOpenAPI(ep, "", resp, body) if err != nil { err = errors.Wrap(err, getCallerFuncName()) - return nil, err } - return (*GuildRoleChannelID)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil + return (*GuildRoleChannelID)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), err } diff --git a/openapi_markdown.go b/openapi_markdown.go new file mode 100644 index 0000000..946a39f --- /dev/null +++ b/openapi_markdown.go @@ -0,0 +1,4 @@ +package nano + +type MessageMarkdown struct { +} diff --git a/openapi_member.go b/openapi_member.go index de5b755..b7d30c4 100644 --- a/openapi_member.go +++ b/openapi_member.go @@ -23,7 +23,7 @@ func (bot *Bot) GetGuildMembersIn(id, after string, limit uint32) (members []Mem err = bot.GetOpenAPI(WriteHTTPQueryIfNotNil("/guilds/"+id+"/members", "after", after, "limit", limit, - ), &members) + ), "", &members) return } @@ -58,7 +58,7 @@ func (bot *Bot) GetGuildMemberOf(guildid, userid string) (*Member, error) { // // - delhistmsgdays: 消息撤回时间范围仅支持固定的天数:3,7,15,30。 特殊的时间范围:-1: 撤回全部消息。默认值为0不撤回任何消息。 func (bot *Bot) DeleteGuildMemberOf(guildid, userid string, addblklst bool, delhistmsgdays int) error { - return bot.DeleteOpenAPI("/guilds/"+guildid+"/members/"+userid, WriteBodyFromJSON(&struct { + return bot.DeleteOpenAPI("/guilds/"+guildid+"/members/"+userid, "", WriteBodyFromJSON(&struct { A bool `json:"add_blacklist"` D int `json:"delete_history_msg_days"` }{addblklst, delhistmsgdays})) diff --git a/openapi_message.go b/openapi_message.go new file mode 100644 index 0000000..d0a97cc --- /dev/null +++ b/openapi_message.go @@ -0,0 +1,168 @@ +package nano + +import ( + "encoding/json" + "reflect" + "time" + + "github.com/pkg/errors" +) + +var ( + ErrEmptyMessagePost = errors.New("empty message post") +) + +// Message 消息对象 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#%E6%B6%88%E6%81%AF%E5%AF%B9%E8%B1%A1-message +type Message struct { + ID string `json:"id"` + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id"` + Content string `json:"content"` + Timestamp time.Time `json:"timestamp"` + EditedTimestamp time.Time `json:"edited_timestamp"` + MentionEveryone bool `json:"mention_everyone"` + Author User `json:"author"` + Attachments []MessageAttachment `json:"attachments"` + Embeds []MessageEmbed `json:"embeds"` + Member Member `json:"member"` + Ark MessageArk `json:"ark"` + SeqInChannel string `json:"seq_in_channel"` + MessageReference MessageReference `json:"message_reference"` + SrcGuildID string `json:"src_guild_id"` + Data *struct { + MessageAudit *MessageAudited `json:"message_audit,omitempty"` + } `json:"data,omitempty"` +} + +// MessageEmbed https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembed +type MessageEmbed struct { + Title string `json:"title"` + Prompt string `json:"prompt"` + Thumbnail MessageEmbedThumbnail `json:"thumbnail"` + Fields []MessageEmbedField `json:"fields"` +} + +// MessageEmbedThumbnail https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembedthumbnail +type MessageEmbedThumbnail struct { + URL string `json:"url"` +} + +// MessageEmbedField https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageembedfield +type MessageEmbedField struct { + Name string `json:"name"` +} + +// MessageAttachment https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageattachment +type MessageAttachment struct { + URL string `json:"url"` +} + +// MessageArk https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messageark +type MessageArk struct { + TemplateID int `json:"template_id"` + KV []MessageArkKV `json:"kv"` +} + +// MessageArkKV https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagearkkv +type MessageArkKV struct { + Key string `json:"key"` + Value string `json:"value"` + Obj []MessageArkObj `json:"obj"` +} + +// MessageArkObj https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagearkobj +type MessageArkObj struct { + ObjKV []MessageArkObjKV `json:"obj_kv"` +} + +// MessageArkObjKV https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagearkobjkv +type MessageArkObjKV struct { + Key string `json:"key"` + Value string `json:"value"` +} + +// MessageReference https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#messagereference +type MessageReference struct { + MessageID string `json:"message_id"` + IgnoreGetMessageError bool `json:"ignore_get_message_error"` +} + +// MessageAudited 消息审核对象 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/message/model.html#%E6%B6%88%E6%81%AF%E5%AE%A1%E6%A0%B8%E5%AF%B9%E8%B1%A1-messageaudited +type MessageAudited struct { + AuditID string `json:"audit_id"` +} + +// GetMessageFromChannel 获取子频道 channel_id 下的消息 message_id 的详情 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/message/get_message_of_id.html +func (bot *Bot) GetMessageFromChannel(messageid, channelid string) (*Message, error) { + return bot.getOpenAPIofMessage("/channels/" + channelid + "/messages/" + messageid) +} + +// MessagePost 发送消息所需参数 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/message/post_messages.html#%E9%80%9A%E7%94%A8%E5%8F%82%E6%95%B0 +type MessagePost struct { + Content string `json:"content,omitempty"` + Embed *MessageEmbed `json:"embed,omitempty"` + Ark *MessageArk `json:"ark,omitempty"` + MessageReference *MessageReference `json:"message_reference,omitempty"` + Image string `json:"image,omitempty"` + ImageFile string `json:"-"` // ImageFile 为图片路径 file:/// or base64:// or base16384:// , 与 Image 参数二选一, 优先 Image + ReplyMessageID string `json:"msg_id,omitempty"` + ReplyEventID string `json:"event_id,omitempty"` + Markdown *MessageMarkdown `json:"markdown,omitempty"` +} + +// PostMessageToChannel 向 channel_id 指定的子频道发送消息 +// +// https://bot.q.qq.com/wiki/develop/api/openapi/message/post_messages.html +func (bot *Bot) PostMessageToChannel(id string, content *MessagePost) (*Message, error) { + if content.ImageFile == "" { + return bot.postOpenAPIofMessage("/channels/"+id+"/messages", "", WriteBodyFromJSON(content)) + } + x := reflect.ValueOf(content).Elem() + t := x.Type() + msg := []any{} + for i := 0; i < x.NumField(); i++ { + xi := x.Field(i) + if xi.IsZero() { + continue + } + tag := t.Field(i).Tag.Get("json") + if tag == "-" { + tag = "file_image" + } + msg = append(msg, tag) + if xi.Kind() == reflect.Struct { + data, err := json.Marshal(xi.Interface()) + if err != nil { + return nil, err + } + msg = append(msg, data) + continue + } + msg = append(msg, xi.String()) + } + if len(msg) < 2 { + return nil, ErrEmptyMessagePost + } + body, contenttype, err := WriteBodyByMultipartFormData(msg...) + if err != nil { + return nil, errors.Wrap(err, getThisFuncName()) + } + return bot.postOpenAPIofMessage("/channels/"+id+"/messages", contenttype, body) +} + +// DeleteMessageInChannel 回子频道 channel_id 下的消息 message_id +// +// https://bot.q.qq.com/wiki/develop/api/openapi/message/delete_message.html +func (bot *Bot) DeleteMessageInChannel(channelid, messageid string, hidetip bool) error { + return bot.DeleteOpenAPI(WriteHTTPQueryIfNotNil("/channels/"+channelid+"/messages/"+messageid, + "hidetip", hidetip, + ), "", nil) +} diff --git a/openapi_role.go b/openapi_role.go index 1011896..5d1b1c8 100644 --- a/openapi_role.go +++ b/openapi_role.go @@ -58,7 +58,7 @@ type GuildRoleCreate struct { // // 参数为非必填,但至少需要传其中之一,默认为空或 0 func (bot *Bot) CreateGuildRoleOf(id string, name string, color uint32, hoist int32) (*GuildRoleCreate, error) { - return bot.postOpenAPIofGuildRoleCreate("/guilds/"+id+"/roles", WriteBodyFromJSON(&struct { + return bot.postOpenAPIofGuildRoleCreate("/guilds/"+id+"/roles", "", WriteBodyFromJSON(&struct { N string `json:"name,omitempty"` C uint32 `json:"color,omitempty"` H int32 `json:"hoist,omitempty"` @@ -89,7 +89,7 @@ func (bot *Bot) PatchGuildRoleOf(guildid, roleid string, name string, color uint // // https://bot.q.qq.com/wiki/develop/api/openapi/guild/delete_guild_role.html func (bot *Bot) DeleteGuildRoleOf(guildid, roleid string) error { - return bot.DeleteOpenAPI("/guilds/"+guildid+"/roles/"+roleid, nil) + return bot.DeleteOpenAPI("/guilds/"+guildid+"/roles/"+roleid, "", nil) } // GuildRoleChannelID 频道身份组成员返回 只填充了子频道 id 字段的对象 @@ -140,5 +140,5 @@ func (bot *Bot) RemoveRoleFromMemberOfGuild(guildid, userid, roleid, channelid s }{channelid}, }) } - return bot.DeleteOpenAPI("/guilds/"+guildid+"/members/"+userid+"/roles/"+roleid, body) + return bot.DeleteOpenAPI("/guilds/"+guildid+"/members/"+userid+"/roles/"+roleid, "", body) } diff --git a/openapi_user.go b/openapi_user.go index ef98585..47a9c05 100644 --- a/openapi_user.go +++ b/openapi_user.go @@ -27,6 +27,6 @@ func (bot *Bot) GetMyGuilds(before, after string, limit int) (guilds []Guild, er "before", before, "after", after, "limit", limit, - ), &guilds) + ), "", &guilds) return } diff --git a/openapi_wss.go b/openapi_wss.go index 8d5dd6b..1823257 100644 --- a/openapi_wss.go +++ b/openapi_wss.go @@ -8,7 +8,7 @@ func (bot *Bot) GetGeneralWSSGateway() (string, error) { CodeMessageBase U string `json:"url"` }{} - err := bot.GetOpenAPI("/gateway", &resp) + err := bot.GetOpenAPI("/gateway", "", &resp) return resp.U, err }