mirror of
https://github.com/fumiama/NanoBot.git
synced 2026-06-11 05:30:24 +08:00
finish Init Connect
This commit is contained in:
166
bot.go
166
bot.go
@@ -1,6 +1,21 @@
|
|||||||
package nano
|
package nano
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/RomiChan/syncx"
|
||||||
|
"github.com/RomiChan/websocket"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var clients = syncx.Map[string, *Bot]{}
|
||||||
|
|
||||||
// Bot 一个机器人实例的配置
|
// Bot 一个机器人实例的配置
|
||||||
type Bot struct {
|
type Bot struct {
|
||||||
@@ -11,15 +26,158 @@ type Bot struct {
|
|||||||
Timeout time.Duration // Timeout is API 调用超时
|
Timeout time.Duration // Timeout is API 调用超时
|
||||||
Handler *Handler // Handler 注册对各种事件的处理
|
Handler *Handler // Handler 注册对各种事件的处理
|
||||||
|
|
||||||
handlers map[string]GeneralHandleType // handlers 方便调用的 handler
|
gateway string // gateway 获得的网关
|
||||||
|
shard [2]byte // shard 分片
|
||||||
|
seq uint32
|
||||||
|
handlers map[string]GeneralHandleType // handlers 方便调用的 handler
|
||||||
|
mu sync.Mutex // 写锁
|
||||||
|
conn *websocket.Conn
|
||||||
|
heartbeat uint32 // heartbeat 心跳周期, 单位毫秒
|
||||||
|
intents uint32
|
||||||
|
properties json.RawMessage
|
||||||
|
ready EventReady
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init 初始化, 只需执行一次
|
// Init 初始化, 只需执行一次
|
||||||
func (b *Bot) Init() {
|
func (b *Bot) Init(gateway string, shard [2]byte, intents uint32, properties json.RawMessage) *Bot {
|
||||||
|
b.gateway = gateway
|
||||||
|
b.shard = shard
|
||||||
|
b.intents = intents
|
||||||
|
b.properties = properties
|
||||||
|
if b.Handler != nil {
|
||||||
|
h := reflect.ValueOf(b.Handler).Elem()
|
||||||
|
t := h.Type()
|
||||||
|
b.handlers = make(map[string]GeneralHandleType, h.NumField()*4)
|
||||||
|
for i := 0; i < h.NumField(); i++ {
|
||||||
|
f := h.Field(i)
|
||||||
|
if f.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tp := t.Field(i).Name[2:] // skip On
|
||||||
|
log.Infoln("[bot] 注册处理函数", tp)
|
||||||
|
handler := f.Interface()
|
||||||
|
b.handlers[tp] = *(*GeneralHandleType)(unsafe.Add(unsafe.Pointer(&handler), unsafe.Sizeof(uintptr(0))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorization 返回 Authorization Header value
|
// Authorization 返回 Authorization Header value
|
||||||
func (bot *Bot) Authorization() string {
|
func (bot *Bot) Authorization() string {
|
||||||
return "Bot " + bot.AppID + "." + bot.Token
|
return "Bot " + bot.AppID + "." + bot.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// receive 收一个 payload
|
||||||
|
func (bot *Bot) reveive() (payload WebsocketPayload, err error) {
|
||||||
|
err = bot.conn.ReadJSON(&payload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect 连接到 Gateway + 鉴权连接
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/gateway/reference.html#_1-%E8%BF%9E%E6%8E%A5%E5%88%B0-gateway
|
||||||
|
func (bot *Bot) Connect() {
|
||||||
|
network, address := resolveURI(bot.gateway)
|
||||||
|
log.Infoln("[bot] 开始尝试连接到网关:", network, ", AppID:", bot.AppID)
|
||||||
|
dialer := websocket.Dialer{
|
||||||
|
NetDial: func(_, addr string) (net.Conn, error) {
|
||||||
|
if network == "unix" {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
host = addr
|
||||||
|
}
|
||||||
|
filepath, err := base64.RawURLEncoding.DecodeString(host)
|
||||||
|
if err == nil {
|
||||||
|
addr = BytesToString(filepath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return net.Dial(network, addr) // support unix socket transport
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
conn, resp, err := dialer.Dial(address, http.Header{})
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[bot] 连接到网关 %v 时出现错误: %v", bot.gateway, err)
|
||||||
|
time.Sleep(2 * time.Second) // 等待两秒后重新连接
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bot.conn = conn
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
payload, err := bot.reveive()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[bot] 获取心跳间隔时出现错误:", err)
|
||||||
|
_ = conn.Close()
|
||||||
|
time.Sleep(2 * time.Second) // 等待两秒后重新连接
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bot.heartbeat, err = payload.GetHeartbeatInterval()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[bot] 解析心跳间隔时出现错误:", err)
|
||||||
|
_ = conn.Close()
|
||||||
|
time.Sleep(2 * time.Second) // 等待两秒后重新连接
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
payload.Op = OpCodeIdentify
|
||||||
|
err = payload.WrapData(&OpCodeIdentifyMessage{
|
||||||
|
Token: bot.Authorization(),
|
||||||
|
Intents: bot.intents,
|
||||||
|
Shard: bot.shard,
|
||||||
|
Properties: bot.properties,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[bot] 包装 Identify 时出现错误:", err)
|
||||||
|
_ = conn.Close()
|
||||||
|
time.Sleep(2 * time.Second) // 等待两秒后重新连接
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = bot.SendPayload(&payload)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[bot] 发送 Identify 时出现错误:", err)
|
||||||
|
_ = conn.Close()
|
||||||
|
time.Sleep(2 * time.Second) // 等待两秒后重新连接
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
payload, err = bot.reveive()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[bot] 获取 EventReady 时出现错误:", err)
|
||||||
|
_ = conn.Close()
|
||||||
|
time.Sleep(2 * time.Second) // 等待两秒后重新连接
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bot.ready, err = payload.GetEventReady()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[bot] 解析 EventReady 时出现错误:", err)
|
||||||
|
_ = conn.Close()
|
||||||
|
time.Sleep(2 * time.Second) // 等待两秒后重新连接
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
clients.Store(bot.ready.User.ID, bot)
|
||||||
|
log.Infoln("[bot] 连接到网关成功, 用户名:", bot.ready.User.Username)
|
||||||
|
go bot.doheartbeat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// doheartbeat 按指定间隔进行心跳包收发
|
||||||
|
func (bot *Bot) doheartbeat() {
|
||||||
|
payload := struct {
|
||||||
|
Op OpCode `json:"op"`
|
||||||
|
D *uint32 `json:"d"`
|
||||||
|
}{Op: OpCodeHeartbeat}
|
||||||
|
t := time.NewTicker(time.Duration(bot.heartbeat) * time.Millisecond)
|
||||||
|
defer t.Stop()
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
for range t.C {
|
||||||
|
if bot.seq == 0 {
|
||||||
|
payload.D = nil
|
||||||
|
} else {
|
||||||
|
payload.D = &bot.seq
|
||||||
|
}
|
||||||
|
bot.mu.Lock()
|
||||||
|
err := bot.conn.WriteJSON(&payload)
|
||||||
|
bot.mu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[bot] 发送心跳时出现错误:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
69
define.go
69
define.go
@@ -1,69 +0,0 @@
|
|||||||
package nano
|
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// StandardAPI 正式环境接口域名
|
|
||||||
StandardAPI = `https://api.sgroup.qq.com`
|
|
||||||
// SandboxAPI 沙箱环境接口域名
|
|
||||||
SandboxAPI = `https://sandbox.api.sgroup.qq.com`
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
OpenAPI = StandardAPI // OpenAPI 实际使用的 API, 默认 StandardAPI, 可自行赋值配置
|
|
||||||
)
|
|
||||||
|
|
||||||
// CodeMessageBase 各种消息都有的 code + message 基类
|
|
||||||
type CodeMessageBase struct {
|
|
||||||
C int `json:"code"`
|
|
||||||
M string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpCode https://bot.q.qq.com/wiki/develop/api/gateway/opcode.html
|
|
||||||
type OpCode int
|
|
||||||
|
|
||||||
const (
|
|
||||||
OpCodeDispatch OpCode = iota // Receive
|
|
||||||
OpCodeHeartbeat // Send/Receive
|
|
||||||
OpCodeIdentify // Send
|
|
||||||
OpCodeEmpty1
|
|
||||||
OpCodeEmpty2
|
|
||||||
OpCodeEmpty3
|
|
||||||
OpCodeResume // Send
|
|
||||||
OpCodeReconnect // Receive
|
|
||||||
OpCodeEmpty4
|
|
||||||
OpCodeInvalidSession // Receive
|
|
||||||
OpCodeHello // Receive
|
|
||||||
OpCodeHeartbeatACK // Receive/Reply
|
|
||||||
OpCodeHTTPCallbackACK // Reply
|
|
||||||
)
|
|
||||||
|
|
||||||
// WebsocketPayload payload 指的是在 websocket 连接上传输的数据,网关的上下行消息采用的都是同一个结构
|
|
||||||
//
|
|
||||||
// https://bot.q.qq.com/wiki/develop/api/gateway/reference.html
|
|
||||||
type WebsocketPayload struct {
|
|
||||||
Op OpCode `json:"op"`
|
|
||||||
D json.RawMessage `json:"d"`
|
|
||||||
S int `json:"s"`
|
|
||||||
T string `json:"t"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://bot.q.qq.com/wiki/develop/api/gateway/intents.html
|
|
||||||
const (
|
|
||||||
IntentGuilds = 1 << 0
|
|
||||||
IntentGuildMembers = 1 << 1
|
|
||||||
IntentGuildMessages = 1 << 9
|
|
||||||
IntentGuildMessageReactions = 1 << 10
|
|
||||||
IntentDirectMessage = 1 << 12
|
|
||||||
IntentOpenForumsEvent = 1 << 18
|
|
||||||
IntentAudioOrLiveChannelMember = 1 << 19
|
|
||||||
IntentInteraction = 1 << 26
|
|
||||||
IntentMessageAudit = 1 << 27
|
|
||||||
IntentForumsEvent = 1 << 28
|
|
||||||
IntentAudioAction = 1 << 29
|
|
||||||
IntentPublicGuildMessages = 1 << 30
|
|
||||||
|
|
||||||
IntentAll = IntentGuilds | IntentGuildMembers | IntentGuildMessages | IntentGuildMessageReactions |
|
|
||||||
IntentDirectMessage | IntentOpenForumsEvent | IntentAudioOrLiveChannelMember | IntentInteraction |
|
|
||||||
IntentMessageAudit | IntentForumsEvent | IntentAudioAction | IntentPublicGuildMessages
|
|
||||||
)
|
|
||||||
8
go.mod
8
go.mod
@@ -3,8 +3,14 @@ module github.com/fumiama/NanoBot
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e
|
||||||
|
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5
|
||||||
github.com/fumiama/go-base16384 v1.7.0
|
github.com/fumiama/go-base16384 v1.7.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/sirupsen/logrus v1.9.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.3.7 // indirect
|
require (
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
)
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -1,14 +1,24 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA=
|
||||||
|
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
|
||||||
|
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 h1:bBmmB7he0iVN4m5mcehfheeRUEer/Avo4ujnxI3uCqs=
|
||||||
|
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5/go.mod h1:0UcFaCkhp6vZw6l5Dpq0Dp673CoF9GdvA8lTfst0GiU=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/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 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA=
|
||||||
github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
|
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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
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/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=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
49
helper.go
49
helper.go
@@ -1,8 +1,11 @@
|
|||||||
package nano
|
package nano
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"net/url"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getFuncNameWithSkip(n int) string {
|
func getFuncNameWithSkip(n int) string {
|
||||||
@@ -66,3 +69,49 @@ func UnderlineToCamel(s string) string {
|
|||||||
}
|
}
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveURI github.com/wdvxdr1123/ZeroBot/driver/uri.go
|
||||||
|
func resolveURI(addr string) (network, address string) {
|
||||||
|
network, address = "tcp", addr
|
||||||
|
uri, err := url.Parse(addr)
|
||||||
|
if err == nil && uri.Scheme != "" {
|
||||||
|
scheme, ext, _ := strings.Cut(uri.Scheme, "+")
|
||||||
|
if ext != "" {
|
||||||
|
network = ext
|
||||||
|
uri.Scheme = scheme // remove `+unix`/`+tcp4`
|
||||||
|
if ext == "unix" {
|
||||||
|
uri.Host, uri.Path, _ = strings.Cut(uri.Path, ":")
|
||||||
|
uri.Host = base64.StdEncoding.EncodeToString(StringToBytes(uri.Host)) // special handle for unix
|
||||||
|
}
|
||||||
|
address = uri.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// slice is the runtime representation of a slice.
|
||||||
|
// It cannot be used safely or portably and its representation may
|
||||||
|
// change in a later release.
|
||||||
|
//
|
||||||
|
// Unlike reflect.SliceHeader, its Data field is sufficient to guarantee the
|
||||||
|
// data it references will not be garbage collected.
|
||||||
|
type slice struct {
|
||||||
|
data unsafe.Pointer
|
||||||
|
len int
|
||||||
|
cap int
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesToString 没有内存开销的转换
|
||||||
|
func BytesToString(b []byte) string {
|
||||||
|
return *(*string)(unsafe.Pointer(&b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToBytes 没有内存开销的转换
|
||||||
|
func StringToBytes(s string) (b []byte) {
|
||||||
|
bh := (*slice)(unsafe.Pointer(&b))
|
||||||
|
sh := (*slice)(unsafe.Pointer(&s))
|
||||||
|
bh.data = sh.data
|
||||||
|
bh.len = sh.len
|
||||||
|
bh.cap = sh.len
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|||||||
21
intent.go
Normal file
21
intent.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/gateway/intents.html
|
||||||
|
const (
|
||||||
|
IntentGuilds = 1 << 0
|
||||||
|
IntentGuildMembers = 1 << 1
|
||||||
|
IntentGuildMessages = 1 << 9
|
||||||
|
IntentGuildMessageReactions = 1 << 10
|
||||||
|
IntentDirectMessage = 1 << 12
|
||||||
|
IntentOpenForumsEvent = 1 << 18
|
||||||
|
IntentAudioOrLiveChannelMember = 1 << 19
|
||||||
|
IntentInteraction = 1 << 26
|
||||||
|
IntentMessageAudit = 1 << 27
|
||||||
|
IntentForumsEvent = 1 << 28
|
||||||
|
IntentAudioAction = 1 << 29
|
||||||
|
IntentPublicGuildMessages = 1 << 30
|
||||||
|
|
||||||
|
IntentAll = IntentGuilds | IntentGuildMembers | IntentGuildMessages | IntentGuildMessageReactions |
|
||||||
|
IntentDirectMessage | IntentOpenForumsEvent | IntentAudioOrLiveChannelMember | IntentInteraction |
|
||||||
|
IntentMessageAudit | IntentForumsEvent | IntentAudioAction | IntentPublicGuildMessages
|
||||||
|
)
|
||||||
30
opcode.go
Normal file
30
opcode.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// OpCode https://bot.q.qq.com/wiki/develop/api/gateway/opcode.html
|
||||||
|
type OpCode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
OpCodeDispatch OpCode = iota // Receive
|
||||||
|
OpCodeHeartbeat // Send/Receive
|
||||||
|
OpCodeIdentify // Send
|
||||||
|
OpCodeEmpty1
|
||||||
|
OpCodeEmpty2
|
||||||
|
OpCodeEmpty3
|
||||||
|
OpCodeResume // Send
|
||||||
|
OpCodeReconnect // Receive
|
||||||
|
OpCodeEmpty4
|
||||||
|
OpCodeInvalidSession // Receive
|
||||||
|
OpCodeHello // Receive
|
||||||
|
OpCodeHeartbeatACK // Receive/Reply
|
||||||
|
OpCodeHTTPCallbackACK // Reply
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpCodeIdentifyMessage https://bot.q.qq.com/wiki/develop/api/gateway/reference.html#_2-%E9%89%B4%E6%9D%83%E8%BF%9E%E6%8E%A5
|
||||||
|
type OpCodeIdentifyMessage struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
Intents uint32 `json:"intents"`
|
||||||
|
Shard [2]byte `json:"shard"`
|
||||||
|
Properties json.RawMessage `json:"properties"`
|
||||||
|
}
|
||||||
17
openapi.go
17
openapi.go
@@ -11,6 +11,23 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StandardAPI 正式环境接口域名
|
||||||
|
StandardAPI = `https://api.sgroup.qq.com`
|
||||||
|
// SandboxAPI 沙箱环境接口域名
|
||||||
|
SandboxAPI = `https://sandbox.api.sgroup.qq.com`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
OpenAPI = StandardAPI // OpenAPI 实际使用的 API, 默认 StandardAPI, 可自行赋值配置
|
||||||
|
)
|
||||||
|
|
||||||
|
// CodeMessageBase 各种消息都有的 code + message 基类
|
||||||
|
type CodeMessageBase struct {
|
||||||
|
C int `json:"code"`
|
||||||
|
M string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
func checkrespbaseunsafe(ptr any) error {
|
func checkrespbaseunsafe(ptr any) error {
|
||||||
respbase := (*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 respbase.C != 0 {
|
if respbase.C != 0 {
|
||||||
|
|||||||
64
payload.go
Normal file
64
payload.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package nano
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebsocketPayload payload 指的是在 websocket 连接上传输的数据,网关的上下行消息采用的都是同一个结构
|
||||||
|
//
|
||||||
|
// https://bot.q.qq.com/wiki/develop/api/gateway/reference.html
|
||||||
|
type WebsocketPayload struct {
|
||||||
|
Op OpCode `json:"op"`
|
||||||
|
D json.RawMessage `json:"d,omitempty"`
|
||||||
|
S int `json:"s,omitempty"`
|
||||||
|
T string `json:"t,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHeartbeatInterval OpCodeHello 获得心跳周期 单位毫秒
|
||||||
|
func (wp *WebsocketPayload) GetHeartbeatInterval() (uint32, error) {
|
||||||
|
if wp.Op != OpCodeHello {
|
||||||
|
return 0, errors.New(getThisFuncName() + " unexpected OpCode " + strconv.Itoa(int(wp.Op)))
|
||||||
|
}
|
||||||
|
data := &struct {
|
||||||
|
H uint32 `json:"heartbeat_interval"`
|
||||||
|
}{}
|
||||||
|
err := json.Unmarshal(wp.D, data)
|
||||||
|
return data.H, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendPayload 发送 ws 包
|
||||||
|
func (bot *Bot) SendPayload(wp *WebsocketPayload) error {
|
||||||
|
bot.mu.Lock()
|
||||||
|
defer bot.mu.Unlock()
|
||||||
|
return bot.conn.WriteJSON(wp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapData 将结构体序列化到 wp.D
|
||||||
|
func (wp *WebsocketPayload) WrapData(v any) (err error) {
|
||||||
|
wp.D, err = json.Marshal(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventReady https://bot.q.qq.com/wiki/develop/api/gateway/reference.html#_2-%E9%89%B4%E6%9D%83%E8%BF%9E%E6%8E%A5
|
||||||
|
type EventReady struct {
|
||||||
|
Version int `json:"version"`
|
||||||
|
SessionID string `json:"session_id"`
|
||||||
|
User *User `json:"user"`
|
||||||
|
Shard [2]byte `json:"shard"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEventReady OpCodeDispatch READY
|
||||||
|
func (wp *WebsocketPayload) GetEventReady() (er EventReady, err error) {
|
||||||
|
if wp.Op != OpCodeDispatch {
|
||||||
|
err = errors.New(getThisFuncName() + " unexpected OpCode " + strconv.Itoa(int(wp.Op)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if wp.T != "READY" {
|
||||||
|
err = errors.New(getThisFuncName() + " unexpected event type " + wp.T)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(wp.D, &er)
|
||||||
|
return
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user