mirror of
https://github.com/fumiama/NanoBot.git
synced 2026-06-08 20:20:23 +08:00
finish base
This commit is contained in:
16
bot.go
Normal file
16
bot.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Bot 一个机器人实例的配置
|
||||||
|
type Bot struct {
|
||||||
|
AppID string // AppID is BotAppID(开发者ID)
|
||||||
|
Token string // Token is 机器人令牌
|
||||||
|
Key string // Key is 机器人密钥
|
||||||
|
Timeout time.Duration // Timeout is API 调用超时
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorization 返回 Authorization Header value
|
||||||
|
func (bot *Bot) Authorization() string {
|
||||||
|
return "Bot " + bot.AppID + "." + bot.Token
|
||||||
|
}
|
||||||
50
codegen/getopenapiof/main.go
Normal file
50
codegen/getopenapiof/main.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const head = `// Code generated by codegen/getopenapiof. DO NOT EDIT.
|
||||||
|
|
||||||
|
package nano
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
func (bot *Bot) getOpenAPIof[T any](ep string) (*[T any], error) {
|
||||||
|
resp := &struct {
|
||||||
|
CodeMessageBase
|
||||||
|
[T any]
|
||||||
|
}{}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
f, err := os.Create("openapi_codegen_getopenapiof.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
define.go
Normal file
14
define.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
var (
|
||||||
|
// StandardAPI 正式环境接口域名
|
||||||
|
StandardAPI = `https://api.sgroup.qq.com`
|
||||||
|
// SandboxAPI 沙箱环境接口域名
|
||||||
|
SandboxAPI = `https://sandbox.api.sgroup.qq.com`
|
||||||
|
)
|
||||||
|
|
||||||
|
// CodeMessageBase 各种消息都有的 code + message 基类
|
||||||
|
type CodeMessageBase struct {
|
||||||
|
C int `json:"code"`
|
||||||
|
M string `json:"message"`
|
||||||
|
}
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module github.com/fumiama/NanoBot
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require github.com/pkg/errors v0.9.1
|
||||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
34
helper.go
Normal file
34
helper.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"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)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
fullname := runtime.FuncForPC(pc).Name()
|
||||||
|
i := strings.LastIndex(fullname, ".") + 1
|
||||||
|
if i <= 0 || i >= len(fullname) {
|
||||||
|
return fullname
|
||||||
|
}
|
||||||
|
return fullname[i:]
|
||||||
|
}
|
||||||
53
http.go
Normal file
53
http.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHTTPEndpointGetRequestWithAuth 新建带鉴权头的 HTTP 请求
|
||||||
|
func NewHTTPEndpointGetRequestWithAuth(ep string, auth string) (req *http.Request, err error) {
|
||||||
|
req, err = http.NewRequest("GET", StandardAPI+ep, nil)
|
||||||
|
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
|
||||||
|
func WriteHTTPQueryIfNotNil(baseurl string, queries ...any) string {
|
||||||
|
if len(queries) == 0 {
|
||||||
|
return baseurl
|
||||||
|
}
|
||||||
|
hasstart := false
|
||||||
|
queryname := ""
|
||||||
|
sb := strings.Builder{}
|
||||||
|
for i, q := range queries {
|
||||||
|
if i%2 == 0 {
|
||||||
|
queryname = q.(string)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if reflect.ValueOf(q).IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !hasstart {
|
||||||
|
sb.WriteString(baseurl)
|
||||||
|
sb.WriteByte('?')
|
||||||
|
hasstart = true
|
||||||
|
}
|
||||||
|
sb.WriteString(queryname)
|
||||||
|
sb.WriteByte('=')
|
||||||
|
sb.WriteString(url.QueryEscape(fmt.Sprint(q)))
|
||||||
|
sb.WriteByte('&')
|
||||||
|
}
|
||||||
|
if sb.Len() <= 4 {
|
||||||
|
return baseurl
|
||||||
|
}
|
||||||
|
return sb.String()[:sb.Len()-1]
|
||||||
|
}
|
||||||
34
openapi.go
Normal file
34
openapi.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run codegen/getopenapiof/main.go ShardWSSGateway User Guild GuildArray Channel ChannelArray
|
||||||
|
|
||||||
|
// GetOpenAPI 从 ep 获取 json 结构化数据写到 ptr, ptr 必须在开头继承 CodeMessageBase
|
||||||
|
func (bot *Bot) GetOpenAPI(ep string, ptr any) error {
|
||||||
|
req, err := NewHTTPEndpointGetRequestWithAuth(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()
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(ptr)
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
36
openapi_channel.go
Normal file
36
openapi_channel.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChannelArray []Channel 的别名
|
||||||
|
type ChannelArray []Channel
|
||||||
|
|
||||||
|
// GetChannelsOfGuild 获取 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 指定的子频道的详情
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/openapi/channel/get_channel.html
|
||||||
|
func (bot *Bot) GetChannelByID(id string) (*Channel, error) {
|
||||||
|
return bot.getOpenAPIofChannel("/channels/" + id)
|
||||||
|
}
|
||||||
87
openapi_codegen_getopenapiof.go
Normal file
87
openapi_codegen_getopenapiof.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// Code generated by codegen/getopenapiof. DO NOT EDIT.
|
||||||
|
|
||||||
|
package nano
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (bot *Bot) getOpenAPIofShardWSSGateway(ep string) (*ShardWSSGateway, error) {
|
||||||
|
resp := &struct {
|
||||||
|
CodeMessageBase
|
||||||
|
ShardWSSGateway
|
||||||
|
}{}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) getOpenAPIofUser(ep string) (*User, error) {
|
||||||
|
resp := &struct {
|
||||||
|
CodeMessageBase
|
||||||
|
User
|
||||||
|
}{}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) getOpenAPIofGuild(ep string) (*Guild, error) {
|
||||||
|
resp := &struct {
|
||||||
|
CodeMessageBase
|
||||||
|
Guild
|
||||||
|
}{}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) getOpenAPIofGuildArray(ep string) (*GuildArray, error) {
|
||||||
|
resp := &struct {
|
||||||
|
CodeMessageBase
|
||||||
|
GuildArray
|
||||||
|
}{}
|
||||||
|
err := bot.GetOpenAPI(ep, resp)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, getCallerFuncName())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return (*GuildArray)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) getOpenAPIofChannel(ep string) (*Channel, error) {
|
||||||
|
resp := &struct {
|
||||||
|
CodeMessageBase
|
||||||
|
Channel
|
||||||
|
}{}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bot *Bot) getOpenAPIofChannelArray(ep string) (*ChannelArray, error) {
|
||||||
|
resp := &struct {
|
||||||
|
CodeMessageBase
|
||||||
|
ChannelArray
|
||||||
|
}{}
|
||||||
|
err := bot.GetOpenAPI(ep, resp)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, getCallerFuncName())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return (*ChannelArray)(unsafe.Add(unsafe.Pointer(resp), unsafe.Sizeof(CodeMessageBase{}))), nil
|
||||||
|
}
|
||||||
28
openapi_guild.go
Normal file
28
openapi_guild.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Guild 频道对象
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/openapi/guild/model.html
|
||||||
|
type Guild struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Icon string `json:"icon"`
|
||||||
|
OwnerID string `json:"owner_id"`
|
||||||
|
Owner bool `json:"owner"`
|
||||||
|
JoinedAt time.Time `json:"joined_at"`
|
||||||
|
MemberCount int `json:"member_count"`
|
||||||
|
MaxMembers int `json:"max_members"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GuildArray []Guild 的别名
|
||||||
|
type GuildArray []Guild
|
||||||
|
|
||||||
|
// GetGuildByID 获取 id 指定的频道的详情
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/openapi/guild/get_guild.html
|
||||||
|
func (bot *Bot) GetGuildByID(id string) (*Guild, error) {
|
||||||
|
return bot.getOpenAPIofGuild("/guilds/" + id)
|
||||||
|
}
|
||||||
31
openapi_user.go
Normal file
31
openapi_user.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
// User 用户对象
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/openapi/user/model.html
|
||||||
|
type User struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
Bot bool `json:"bot"`
|
||||||
|
UnionOpenid string `json:"union_openid"`
|
||||||
|
UnionUserAccount string `json:"union_user_account"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMe 获取当前用户(机器人)详情
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/openapi/user/me.html
|
||||||
|
func (bot *Bot) GetMe() (*User, error) {
|
||||||
|
return bot.getOpenAPIofUser("/users/@me")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMyGuilds 获取当前用户(机器人)频道列表,支持分页
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/openapi/user/guilds.html
|
||||||
|
func (bot *Bot) GetMyGuilds(before, after string, limit int) (*GuildArray, error) {
|
||||||
|
return bot.getOpenAPIofGuildArray(WriteHTTPQueryIfNotNil("/users/@me/guilds",
|
||||||
|
"before", before,
|
||||||
|
"after", after,
|
||||||
|
"limit", limit,
|
||||||
|
))
|
||||||
|
}
|
||||||
37
openapi_wss.go
Normal file
37
openapi_wss.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
// GetGeneralWSSGateway 获取通用 WSS 接入点
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/openapi/wss/url_get.html
|
||||||
|
func (bot *Bot) GetGeneralWSSGateway() (string, error) {
|
||||||
|
resp := struct {
|
||||||
|
CodeMessageBase
|
||||||
|
U string `json:"url"`
|
||||||
|
}{}
|
||||||
|
err := bot.GetOpenAPI("/gateway", &resp)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return resp.U, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShardWSSGateway 带分片 WSS 接入点响应数据
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/openapi/wss/shard_url_get.html#%E8%BF%94%E5%9B%9E
|
||||||
|
type ShardWSSGateway struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
Shards int `json:"shards"`
|
||||||
|
SessionStartLimit struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
Remaining int `json:"remaining"`
|
||||||
|
ResetAfter int `json:"reset_after"`
|
||||||
|
MaxConcurrency int `json:"max_concurrency"`
|
||||||
|
} `json:"session_start_limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetShardWSSGateway 获取带分片 WSS 接入点
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/openapi/wss/shard_url_get.html
|
||||||
|
func (bot *Bot) GetShardWSSGateway() (*ShardWSSGateway, error) {
|
||||||
|
return bot.getOpenAPIofShardWSSGateway("/gateway/bot")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user