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

finish base

This commit is contained in:
源文雨
2023-10-10 14:10:13 +09:00
parent 6c87546b44
commit 3dbb8d7b34
13 changed files with 427 additions and 0 deletions

16
bot.go Normal file
View 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
}

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

View 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
View 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
View 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
View 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")
}