From a4bf60e6e9a68b060896a6f9c01c1d88a2204cdc Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Thu, 30 Jun 2022 19:07:30 +0200 Subject: [PATCH 1/8] initial working rpc stuff --- _examples/rpc/rpc.go | 49 ++++++++++ discord/activity.go | 10 +-- go.mod | 2 +- go.sum | 3 +- rpc/client.go | 206 +++++++++++++++++++++++++++++++++++++++++++ rpc/cmd.go | 41 +++++++++ rpc/conn.go | 75 ++++++++++++++++ rpc/event.go | 34 +++++++ rpc/handler.go | 26 ++++++ rpc/message.go | 150 +++++++++++++++++++++++++++++++ rpc/pipe.go | 52 +++++++++++ 11 files changed, 641 insertions(+), 7 deletions(-) create mode 100644 _examples/rpc/rpc.go create mode 100644 rpc/client.go create mode 100644 rpc/cmd.go create mode 100644 rpc/conn.go create mode 100644 rpc/event.go create mode 100644 rpc/handler.go create mode 100644 rpc/message.go create mode 100644 rpc/pipe.go diff --git a/_examples/rpc/rpc.go b/_examples/rpc/rpc.go new file mode 100644 index 000000000..a776b43ca --- /dev/null +++ b/_examples/rpc/rpc.go @@ -0,0 +1,49 @@ +package main + +import ( + "os" + "os/signal" + "syscall" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/rpc" + "github.com/disgoorg/log" + "github.com/disgoorg/snowflake/v2" +) + +var clientID = snowflake.GetEnv("disgo_client_id") + +func main() { + log.SetLevel(log.LevelDebug) + log.SetFlags(log.LstdFlags | log.Lshortfile) + log.Info("example is starting...") + + eventHandler := func(event rpc.Event, data rpc.MessageData) { + //log.Infof("event: %s, data: %#v", event, data) + } + + client, err := rpc.NewClient(clientID, eventHandler) + if err != nil { + log.Fatal(err) + return + } + defer client.Close() + + err = client.Send(rpc.CmdAuthorize, rpc.CmdArgsAuthorize{ + ClientID: clientID, + Scopes: []discord.OAuth2Scope{discord.OAuth2ScopeMessagesRead}, + }, rpc.CmdHandler(func(data rpc.CmdRsAuthorize) { + println("handleAuthorize") + })) + + if err != nil { + log.Fatal(err) + } + + //oauth2Client := rest.cl + + log.Info("example is now running. Press CTRL-C to exit.") + s := make(chan os.Signal, 1) + signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + <-s +} diff --git a/discord/activity.go b/discord/activity.go index 9f6e66d7f..6f1548466 100644 --- a/discord/activity.go +++ b/discord/activity.go @@ -17,11 +17,11 @@ const ( // Activity represents the fields of a user's presence type Activity struct { - ID string `json:"id"` - Name string `json:"name"` + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` Type ActivityType `json:"type"` - URL *string `json:"url"` - CreatedAt int64 `json:"created_at"` + URL *string `json:"url,omitempty"` + CreatedAt int64 `json:"created_at,omitempty"` Timestamps *ActivityTimestamps `json:"timestamps,omitempty"` ApplicationID snowflake.ID `json:"application_id,omitempty"` Details *string `json:"details,omitempty"` @@ -32,7 +32,7 @@ type Activity struct { Secrets *ActivitySecrets `json:"secrets,omitempty"` Instance *bool `json:"instance,omitempty"` Flags ActivityFlags `json:"flags,omitempty"` - Buttons []string `json:"buttons"` + Buttons []string `json:"buttons,omitempty"` } // ActivityFlags add additional information to an activity diff --git a/go.mod b/go.mod index 8c3f2edfb..905038cff 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( ) require ( - github.com/davecgh/go-spew v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index dcfd2a059..5d0c785b6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ -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/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/disgoorg/log v1.2.0 h1:sqlXnu/ZKAlIlHV9IO+dbMto7/hCQ474vlIdMWk8QKo= github.com/disgoorg/log v1.2.0/go.mod h1:3x1KDG6DI1CE2pDwi3qlwT3wlXpeHW/5rVay+1qDqOo= github.com/disgoorg/snowflake/v2 v2.0.0 h1:+xvyyDddXmXLHmiG8SZiQ3sdZdZPbUR22fSHoqwkrOA= diff --git a/rpc/client.go b/rpc/client.go new file mode 100644 index 000000000..77b2ae09d --- /dev/null +++ b/rpc/client.go @@ -0,0 +1,206 @@ +package rpc + +import ( + "bytes" + "errors" + "io" + "net" + + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/internal/insecurerandstr" + "github.com/disgoorg/disgo/json" + "github.com/disgoorg/log" + "github.com/disgoorg/snowflake/v2" +) + +type EventHandleFunc func(event Event, data MessageData) + +type Client interface { + Logger() log.Logger + ServerConfig() ServerConfig + User() discord.User + V() int + + Send(cmd Cmd, args CmdArgs, handler CommandHandler) error + Close() +} + +func NewClient(clientID snowflake.ID, eventHandleFunc EventHandleFunc) (Client, error) { + conn, err := DialPipe() + if err != nil { + return nil, err + } + + client := &clientImpl{ + logger: log.Default(), + conn: conn, + eventHandleFunc: eventHandleFunc, + commandHandlers: map[string]internalHandler{}, + readyChan: make(chan struct{}, 1), + } + + buff := new(bytes.Buffer) + if err = json.NewEncoder(buff).Encode(Handshake{ + V: 1, + ClientID: clientID, + }); err != nil { + return nil, err + } + if err = client.send(OpCodeHandshake, buff); err != nil { + return nil, err + } + + go client.listen(conn) + + <-client.readyChan + + return client, nil +} + +type clientImpl struct { + logger log.Logger + conn *Conn + clientID snowflake.ID + eventHandleFunc EventHandleFunc + commandHandlers map[string]internalHandler + + readyChan chan struct{} + user discord.User + serverConfig ServerConfig + v int +} + +func (c *clientImpl) Logger() log.Logger { + return c.logger +} + +func (c *clientImpl) ServerConfig() ServerConfig { + return c.serverConfig +} + +func (c *clientImpl) User() discord.User { + return c.user +} + +func (c *clientImpl) V() int { + return c.v +} + +func (c *clientImpl) send(opCode OpCode, r io.Reader) error { + writer, err := c.conn.NextWriter(opCode) + if err != nil { + return err + } + defer writer.Close() + + buff := new(bytes.Buffer) + newWriter := io.MultiWriter(writer, buff) + + _, err = io.Copy(newWriter, r) + if err != nil { + return err + } + + data, _ := io.ReadAll(buff) + c.logger.Debugf("Sending message: opCode: %d, data: %s", opCode, string(data)) + + return err +} + +func (c *clientImpl) Send(cmd Cmd, args CmdArgs, handler CommandHandler) error { + nonce := insecurerandstr.RandStr(32) + buff := new(bytes.Buffer) + if err := json.NewEncoder(buff).Encode(Message{ + Cmd: cmd, + Nonce: nonce, + Args: args, + }); err != nil { + return err + } + + errChan := make(chan error, 1) + + c.commandHandlers[nonce] = internalHandler{ + handler: handler, + errChan: errChan, + } + if err := c.send(OpCodeFrame, buff); err != nil { + delete(c.commandHandlers, nonce) + close(errChan) + return err + } + return <-errChan +} + +func (c *clientImpl) listen(conn *Conn) { +loop: + for { + println("reading...") + opCode, reader, err := conn.NextReader() + if errors.Is(err, net.ErrClosed) { + c.logger.Error("Connection closed") + break loop + } + if err != nil { + c.logger.Errorf("Error reading message: %s", err) + continue + } + + data, err := io.ReadAll(reader) + if err != nil { + c.logger.Errorf("Error reading message: %s", err) + continue + } + c.logger.Debugf("Received message: opCode: %d, data: %s", opCode, string(data)) + + reader = bytes.NewReader(data) + + switch opCode { + case OpCodePing: + if err = c.send(OpCodePong, reader); err != nil { + c.logger.Errorf("Error sending pong: %s", err) + continue + } + + case OpCodeFrame: + var v Message + if err = json.NewDecoder(reader).Decode(&v); err != nil { + c.logger.Errorf("failed to decode message: %s", err) + continue + } + + if v.Cmd == CmdDispatch { + if d, ok := v.Data.(EventDataReady); ok { + c.readyChan <- struct{}{} + c.user = d.User + c.serverConfig = d.Config + c.v = d.V + } + c.eventHandleFunc(v.Event, v.Data) + continue + } + if handler, ok := c.commandHandlers[v.Nonce]; ok { + if v.Event == EventError { + handler.errChan <- v.Data.(EventDataError) + } else { + handler.handler.Handle(v.Data) + handler.errChan <- nil + } + close(handler.errChan) + delete(c.commandHandlers, v.Nonce) + } else { + c.logger.Errorf("No handler for nonce: %s", v.Nonce) + } + + case OpCodeClose: + c.Close() + break loop + } + } +} + +func (c *clientImpl) Close() { + if c.conn != nil { + _ = c.conn.Close() + } +} diff --git a/rpc/cmd.go b/rpc/cmd.go new file mode 100644 index 000000000..ee18e6831 --- /dev/null +++ b/rpc/cmd.go @@ -0,0 +1,41 @@ +package rpc + +import ( + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/snowflake/v2" +) + +type CmdArgsAuthorize struct { + ClientID snowflake.ID `json:"client_id"` + Scopes []discord.OAuth2Scope `json:"scopes"` + RPCToken string `json:"rpc_token,omitempty"` + Username string `json:"username,omitempty"` +} + +func (CmdArgsAuthorize) cmdArgs() {} + +type CmdRsAuthorize struct { + Code string `json:"code"` +} + +func (CmdRsAuthorize) messageData() {} + +type CmdArgsSetActivity struct { + PID int `json:"pid"` + Activity discord.Activity `json:"activity"` +} + +func (CmdArgsSetActivity) cmdArgs() {} + +type CmdRsSetActivity struct { + discord.Activity +} + +func (CmdRsSetActivity) messageData() {} + +type CmdArgsSubscribe struct { + Evt string `json:"evt"` +} + +type CmdArgsUnsubscribe struct { +} diff --git a/rpc/conn.go b/rpc/conn.go new file mode 100644 index 000000000..28356e456 --- /dev/null +++ b/rpc/conn.go @@ -0,0 +1,75 @@ +package rpc + +import ( + "bufio" + "bytes" + "encoding/binary" + "io" + "net" +) + +func newConn(conn net.Conn) *Conn { + return &Conn{ + Conn: conn, + w: bufio.NewWriter(conn), + } +} + +type Conn struct { + net.Conn + w *bufio.Writer +} + +func (c *Conn) NextWriter(opCode OpCode) (io.WriteCloser, error) { + return &messageWriter{ + conn: c, + opCode: opCode, + }, nil +} + +type messageWriter struct { + conn *Conn + opCode OpCode +} + +func (w *messageWriter) Write(p []byte) (int, error) { + if err := binary.Write(w.conn.w, binary.LittleEndian, w.opCode); err != nil { + return 0, err + } + + if err := binary.Write(w.conn.w, binary.LittleEndian, int32(len(p))); err != nil { + return 0, err + } + + return w.conn.w.Write(p) +} + +func (w *messageWriter) Close() error { + return w.conn.w.Flush() +} + +func (c *Conn) NextReader() (OpCode, io.Reader, error) { + var opCode OpCode + if err := binary.Read(c, binary.LittleEndian, &opCode); err != nil { + return 0, nil, err + } + + var length int32 + if err := binary.Read(c, binary.LittleEndian, &length); err != nil { + return 0, nil, err + } + + data := make([]byte, length) + if _, err := c.Read(data); err != nil { + return 0, nil, err + } + + return opCode, bytes.NewReader(data), nil +} + +func (c *Conn) Close() error { + if err := binary.Write(c, binary.LittleEndian, OpCodeClose); err != nil { + return err + } + return c.Conn.Close() +} diff --git a/rpc/event.go b/rpc/event.go new file mode 100644 index 000000000..65b061972 --- /dev/null +++ b/rpc/event.go @@ -0,0 +1,34 @@ +package rpc + +import ( + "fmt" + + "github.com/disgoorg/disgo/discord" +) + +type EventDataReady struct { + V int `json:"v"` + Config ServerConfig `json:"config"` + User discord.User `json:"user"` +} + +func (EventDataReady) messageData() {} + +type ServerConfig struct { + CDNHost string `json:"cdn_host"` + APIEndpoint string `json:"api_endpoint"` + Environment string `json:"environment"` +} + +var _ error = (*EventDataError)(nil) + +type EventDataError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (e EventDataError) Error() string { + return fmt.Sprintf("%d: %s", e.Code, e.Message) +} + +func (EventDataError) messageData() {} diff --git a/rpc/handler.go b/rpc/handler.go new file mode 100644 index 000000000..2c2428f96 --- /dev/null +++ b/rpc/handler.go @@ -0,0 +1,26 @@ +package rpc + +type internalHandler struct { + handler CommandHandler + errChan chan error +} + +type CommandHandler interface { + Handle(data MessageData) +} + +func CmdHandler[T MessageData](handler func(data T)) CommandHandler { + return &defaultCommandHandler[T]{ + handler: handler, + } +} + +type defaultCommandHandler[T MessageData] struct { + handler func(data T) +} + +func (h *defaultCommandHandler[T]) Handle(data MessageData) { + if d, ok := data.(T); ok { + h.handler(d) + } +} diff --git a/rpc/message.go b/rpc/message.go new file mode 100644 index 000000000..2f9709a81 --- /dev/null +++ b/rpc/message.go @@ -0,0 +1,150 @@ +package rpc + +import ( + "fmt" + + "github.com/disgoorg/disgo/json" + "github.com/disgoorg/snowflake/v2" +) + +type Message struct { + Cmd Cmd `json:"cmd"` + Args CmdArgs `json:"args,omitempty"` + + Event Event `json:"evt,omitempty"` + Data MessageData `json:"data,omitempty"` + + Nonce string `json:"nonce,omitempty"` +} + +func (m *Message) UnmarshalJSON(data []byte) error { + type message Message + var v struct { + Data json.RawMessage `json:"data"` + message + } + + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + m.Cmd = v.Cmd + m.Event = v.Event + m.Nonce = v.Nonce + + var ( + messageData MessageData + err error + ) + + if v.Event == EventError { + var d EventDataError + err = json.Unmarshal(v.Data, &d) + messageData = d + } else { + switch v.Cmd { + case CmdDispatch: + switch v.Event { + case EventReady: + var d EventDataReady + err = json.Unmarshal(v.Data, &d) + messageData = d + + default: + err = fmt.Errorf("unknown event: %s", v.Event) + } + + case CmdAuthorize: + var d CmdRsAuthorize + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdSetActivity: + var d CmdRsSetActivity + err = json.Unmarshal(v.Data, &d) + messageData = d + + default: + err = fmt.Errorf("unknown cmd: %s", v.Cmd) + } + } + + if err != nil { + return err + } + + m.Data = messageData + return nil +} + +type Cmd string + +const ( + CmdDispatch Cmd = "DISPATCH" + CmdAuthorize Cmd = "AUTHORIZE" + CmdAuthenticate Cmd = "AUTHENTICATE" + CmdGetGuild Cmd = "GET_GUILD" + CmdGetGuilds Cmd = "GET_GUILDS" + CmdGetChannel Cmd = "GET_CHANNEL" + CmdGetChannels Cmd = "GET_CHANNELS" + CmdSubscribe Cmd = "SUBSCRIBE" + CmdUnsubscribe Cmd = "UNSUBSCRIBE" + CmdSetUserVoiceSettings Cmd = "SET_USER_VOICE_SETTINGS" + CmdSelectVoiceChannel Cmd = "SELECT_VOICE_CHANNEL" + CmdGetSelectedVoiceChannel Cmd = "GET_SELECTED_VOICE_CHANNEL" + CmdSelectTextChannel Cmd = "SELECT_TEXT_CHANNEL" + CmdGetVoiceSettings Cmd = "GET_VOICE_SETTINGS" + CmdSetVoiceSettings Cmd = "SET_VOICE_SETTINGS" + CmdSetCertifiedDevices Cmd = "SET_CERTIFIED_DEVICES" + CmdSetActivity Cmd = "SET_ACTIVITY" + CmdSendActivityRequest Cmd = "SEND_ACTIVITY_JOIN_INVITE" + CmdCloseActivityRequest Cmd = "CLOSE_ACTIVITY_REQUEST" +) + +type CmdArgs interface { + cmdArgs() +} + +type Event string + +const ( + EventReady Event = "READY" + EventError Event = "ERROR" + EventGuildStatus Event = "GUILD_STATUS" + EventGuildCreate Event = "GUILD" + EventChannelCreate Event = "CHANNEL_CREATE" + EventVoiceChannelSelect Event = "VOICE_CHANNEL_SELECT" + EventVoiceStateCreate Event = "VOICE_STATE_CREATE" + EventVoiceStateUpdate Event = "VOICE_STATE_UPDATE" + EventVoiceStateDelete Event = "VOICE_STATE_DELETE" + EventVoiceSettingsUpdate Event = "VOICE_SETTINGS_UPDATE" + EventVoiceConnectionStatus Event = "VOICE_CONNECTION_STATUS" + EventSpeakingStart Event = "SPEAKING_START" + EventSpeakingStop Event = "SPEAKING_STOP" + EventMessageCreate Event = "MESSAGE_CREATE" + EventMessageUpdate Event = "MESSAGE_UPDATE" + EventMessageDelete Event = "MESSAGE_DELETE" + EventNotificationCreate Event = "NOTIFICATION_CREATE" + EventActivityJoin Event = "ACTIVITY_JOIN" + EventActivitySpectate Event = "ACTIVITY_SPECTATE" + EventActivityJoinRequest Event = "ACTIVITY_JOIN_REQUEST" +) + +type MessageData interface { + messageData() +} + +type Handshake struct { + V int `json:"v"` + ClientID snowflake.ID `json:"client_id"` +} + +type OpCode int32 + +const ( + OpCodeHandshake OpCode = iota + OpCodeFrame + OpCodeClose + OpCodePing + OpCodePong +) diff --git a/rpc/pipe.go b/rpc/pipe.go new file mode 100644 index 000000000..f80b6b668 --- /dev/null +++ b/rpc/pipe.go @@ -0,0 +1,52 @@ +package rpc + +import ( + "fmt" + "net" + "os" + "runtime" + "strings" +) + +var unixPaths = []string{"XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP"} + +func DialPipe() (*Conn, error) { + conn, err := openPipe(GetDiscordIPCPath(0)) + if conn != nil { + return conn, nil + } + return nil, err +} + +func GetDiscordIPCPath(i int) string { + if strings.HasPrefix(runtime.GOOS, "windows") { + return fmt.Sprintf("\\\\?\\pipe\\discord-ipc-%d", i) + } + + tmpPath := "/tmp" + for _, path := range unixPaths { + if v := os.Getenv(path); v != "" { + tmpPath = v + break + } + } + return fmt.Sprintf("%sdiscord-ipc-%d", tmpPath, i) +} + +func openPipe(path string) (*Conn, error) { + var ( + conn net.Conn + err error + ) + + if strings.HasPrefix(runtime.GOOS, "windows") { + return nil, nil + } else { + conn, err = net.Dial("unix", path) + } + + if err != nil { + return nil, err + } + return newConn(conn), nil +} From f2917d2ba60cf9e9efe2562b6fda744edcb01a38 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Fri, 1 Jul 2022 03:41:02 +0200 Subject: [PATCH 2/8] ipc & ws transport support. working message read example --- _examples/rpc/rpc.go | 47 ++++++++++--- discord/application.go | 8 +++ go.mod | 2 + go.sum | 8 +++ rest/oauth2.go | 8 ++- rpc/client.go | 148 ++++++++++++++++++++++------------------- rpc/cmd.go | 41 +++++++++++- rpc/conn.go | 75 --------------------- rpc/event.go | 24 +++++++ rpc/ipc_transport.go | 135 +++++++++++++++++++++++++++++++++++++ rpc/message.go | 46 ++++++++----- rpc/pipe.go | 52 --------------- rpc/pipe_unix.go | 24 +++++++ rpc/pipe_win.go | 18 +++++ rpc/ws_transport.go | 54 +++++++++++++++ 15 files changed, 464 insertions(+), 226 deletions(-) delete mode 100644 rpc/conn.go create mode 100644 rpc/ipc_transport.go delete mode 100644 rpc/pipe.go create mode 100644 rpc/pipe_unix.go create mode 100644 rpc/pipe_win.go create mode 100644 rpc/ws_transport.go diff --git a/_examples/rpc/rpc.go b/_examples/rpc/rpc.go index a776b43ca..fbf613bcb 100644 --- a/_examples/rpc/rpc.go +++ b/_examples/rpc/rpc.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "os/signal" "syscall" @@ -11,7 +12,11 @@ import ( "github.com/disgoorg/snowflake/v2" ) -var clientID = snowflake.GetEnv("disgo_client_id") +var ( + clientID = snowflake.GetEnv("disgo_client_id") + clientSecret = os.Getenv("disgo_client_secret") + channelID = snowflake.GetEnv("disgo_channel_id") +) func main() { log.SetLevel(log.LevelDebug) @@ -22,25 +27,49 @@ func main() { //log.Infof("event: %s, data: %#v", event, data) } - client, err := rpc.NewClient(clientID, eventHandler) + client, err := rpc.NewIPCClient(clientID, eventHandler) if err != nil { log.Fatal(err) return } defer client.Close() - err = client.Send(rpc.CmdAuthorize, rpc.CmdArgsAuthorize{ - ClientID: clientID, - Scopes: []discord.OAuth2Scope{discord.OAuth2ScopeMessagesRead}, + var tokenRs *discord.AccessTokenResponse + if err = client.Send(rpc.Message{ + Cmd: rpc.CmdAuthorize, + Args: rpc.CmdArgsAuthorize{ + ClientID: clientID, + Scopes: []discord.OAuth2Scope{discord.OAuth2ScopeRPC, discord.OAuth2ScopeMessagesRead}, + }, }, rpc.CmdHandler(func(data rpc.CmdRsAuthorize) { - println("handleAuthorize") - })) + tokenRs, err = client.OAuth2().GetAccessToken(clientID, clientSecret, data.Code, "http://localhost") + if err != nil { + log.Fatal(err) + } + })); err != nil { + log.Fatal(err) + } - if err != nil { + if err = client.Send(rpc.Message{ + Cmd: rpc.CmdAuthenticate, + Args: rpc.CmdArgsAuthenticate{ + AccessToken: tokenRs.AccessToken, + }, + }, nil); err != nil { log.Fatal(err) } - //oauth2Client := rest.cl + if err = client.Send(rpc.Message{ + Cmd: rpc.CmdSubscribe, + Event: rpc.EventMessageCreate, + Args: rpc.CmdArgsSubscribeMessage{ + ChannelID: channelID, + }, + }, rpc.CmdHandler(func(data rpc.CmdRsSubscribe) { + fmt.Printf("handleSubscribe: %s\n", data.Evt) + })); err != nil { + log.Fatal(err) + } log.Info("example is now running. Press CTRL-C to exit.") s := make(chan os.Signal, 1) diff --git a/discord/application.go b/discord/application.go index 341cc0b10..0f592dc49 100644 --- a/discord/application.go +++ b/discord/application.go @@ -47,6 +47,14 @@ func (a Application) CoverURL(opts ...CDNOpt) *string { return formatAssetURL(route.ApplicationCover, opts, a.ID, *a.Cover) } +type OAuth2Application struct { + ID snowflake.ID `json:"id"` + Name string `json:"name"` + Icon *string `json:"icon,omitempty"` + Description string `json:"description"` + RPCOrigins []string `json:"rpc_origins"` +} + type PartialApplication struct { ID snowflake.ID `json:"id"` Flags ApplicationFlags `json:"flags"` diff --git a/go.mod b/go.mod index 905038cff..4c00f43ee 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/disgoorg/disgo go 1.18 require ( + github.com/Microsoft/go-winio v0.5.2 github.com/disgoorg/log v1.2.0 github.com/disgoorg/snowflake/v2 v2.0.0 github.com/gorilla/websocket v1.5.0 @@ -14,5 +15,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index 5d0c785b6..b75e4affd 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 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= @@ -11,11 +13,17 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b h1:qYTY2tN72LhgDj2rtWG+LI6TXFl2ygFQQ4YezfVaGQE= github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 h1:Xt4/LzbTwfocTk9ZLEu4onjeFucl88iW+v4j4PWbQuE= golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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= diff --git a/rest/oauth2.go b/rest/oauth2.go index 8ebf224ad..9d05f98b6 100644 --- a/rest/oauth2.go +++ b/rest/oauth2.go @@ -127,9 +127,11 @@ func (s *oAuth2Impl) SetGuildCommandPermissions(bearerToken string, applicationI func (s *oAuth2Impl) exchangeAccessToken(clientID snowflake.ID, clientSecret string, grantType discord.GrantType, codeOrRefreshToken string, redirectURI string, opts ...RequestOpt) (exchange *discord.AccessTokenResponse, err error) { values := url.Values{ - "client_id": []string{clientID.String()}, - "client_secret": []string{clientSecret}, - "grant_type": []string{grantType.String()}, + "client_id": []string{clientID.String()}, + "grant_type": []string{grantType.String()}, + } + if clientSecret != "" { + values["client_secret"] = []string{clientSecret} } switch grantType { case discord.GrantTypeAuthorizationCode: diff --git a/rpc/client.go b/rpc/client.go index 77b2ae09d..43f71f4b1 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -2,68 +2,86 @@ package rpc import ( "bytes" + "context" "errors" "io" "net" + "time" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/internal/insecurerandstr" "github.com/disgoorg/disgo/json" + "github.com/disgoorg/disgo/rest" "github.com/disgoorg/log" "github.com/disgoorg/snowflake/v2" ) type EventHandleFunc func(event Event, data MessageData) +type Transport interface { + NextWriter() (io.WriteCloser, error) + NextReader() (io.Reader, error) + Close() error +} + type Client interface { Logger() log.Logger ServerConfig() ServerConfig User() discord.User V() int + OAuth2() rest.OAuth2 - Send(cmd Cmd, args CmdArgs, handler CommandHandler) error + Send(message Message, handler CommandHandler) error Close() } -func NewClient(clientID snowflake.ID, eventHandleFunc EventHandleFunc) (Client, error) { - conn, err := DialPipe() +func NewIPCClient(clientID snowflake.ID, eventHandleFunc EventHandleFunc) (Client, error) { + transport, err := NewIPCTransport(clientID) + if err != nil { + return nil, err + } + return newClient(transport, eventHandleFunc) +} + +func NewWSClient(clientID snowflake.ID, origin string, eventHandleFunc EventHandleFunc) (Client, error) { + transport, err := NewWSTransport(clientID, origin) if err != nil { return nil, err } + return newClient(transport, eventHandleFunc) +} +func newClient(transport Transport, eventHandleFunc EventHandleFunc) (Client, error) { client := &clientImpl{ logger: log.Default(), - conn: conn, + transport: transport, eventHandleFunc: eventHandleFunc, commandHandlers: map[string]internalHandler{}, + oauth2: rest.NewOAuth2(rest.NewClient("")), readyChan: make(chan struct{}, 1), } - buff := new(bytes.Buffer) - if err = json.NewEncoder(buff).Encode(Handshake{ - V: 1, - ClientID: clientID, - }); err != nil { - return nil, err - } - if err = client.send(OpCodeHandshake, buff); err != nil { - return nil, err - } - - go client.listen(conn) + go client.listen(transport) - <-client.readyChan + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-client.readyChan: + } return client, nil } type clientImpl struct { logger log.Logger - conn *Conn - clientID snowflake.ID + transport Transport eventHandleFunc EventHandleFunc commandHandlers map[string]internalHandler + oauth2 rest.OAuth2 + readyChan chan struct{} user discord.User serverConfig ServerConfig @@ -86,8 +104,12 @@ func (c *clientImpl) V() int { return c.v } -func (c *clientImpl) send(opCode OpCode, r io.Reader) error { - writer, err := c.conn.NextWriter(opCode) +func (c *clientImpl) OAuth2() rest.OAuth2 { + return c.oauth2 +} + +func (c *clientImpl) send(r io.Reader) error { + writer, err := c.transport.NextWriter() if err != nil { return err } @@ -102,19 +124,17 @@ func (c *clientImpl) send(opCode OpCode, r io.Reader) error { } data, _ := io.ReadAll(buff) - c.logger.Debugf("Sending message: opCode: %d, data: %s", opCode, string(data)) + c.logger.Debugf("Sending message: data: %s", string(data)) return err } -func (c *clientImpl) Send(cmd Cmd, args CmdArgs, handler CommandHandler) error { +func (c *clientImpl) Send(message Message, handler CommandHandler) error { nonce := insecurerandstr.RandStr(32) buff := new(bytes.Buffer) - if err := json.NewEncoder(buff).Encode(Message{ - Cmd: cmd, - Nonce: nonce, - Args: args, - }); err != nil { + + message.Nonce = nonce + if err := json.NewEncoder(buff).Encode(message); err != nil { return err } @@ -124,7 +144,7 @@ func (c *clientImpl) Send(cmd Cmd, args CmdArgs, handler CommandHandler) error { handler: handler, errChan: errChan, } - if err := c.send(OpCodeFrame, buff); err != nil { + if err := c.send(buff); err != nil { delete(c.commandHandlers, nonce) close(errChan) return err @@ -132,11 +152,10 @@ func (c *clientImpl) Send(cmd Cmd, args CmdArgs, handler CommandHandler) error { return <-errChan } -func (c *clientImpl) listen(conn *Conn) { +func (c *clientImpl) listen(transport Transport) { loop: for { - println("reading...") - opCode, reader, err := conn.NextReader() + reader, err := transport.NextReader() if errors.Is(err, net.ErrClosed) { c.logger.Error("Connection closed") break loop @@ -151,56 +170,45 @@ loop: c.logger.Errorf("Error reading message: %s", err) continue } - c.logger.Debugf("Received message: opCode: %d, data: %s", opCode, string(data)) + c.logger.Debugf("Received message: data: %s", string(data)) reader = bytes.NewReader(data) - switch opCode { - case OpCodePing: - if err = c.send(OpCodePong, reader); err != nil { - c.logger.Errorf("Error sending pong: %s", err) - continue - } + var v Message + if err = json.NewDecoder(reader).Decode(&v); err != nil { + c.logger.Errorf("failed to decode message: %s", err) + continue + } - case OpCodeFrame: - var v Message - if err = json.NewDecoder(reader).Decode(&v); err != nil { - c.logger.Errorf("failed to decode message: %s", err) - continue + if v.Cmd == CmdDispatch { + if d, ok := v.Data.(EventDataReady); ok { + c.readyChan <- struct{}{} + c.user = d.User + c.serverConfig = d.Config + c.v = d.V } - - if v.Cmd == CmdDispatch { - if d, ok := v.Data.(EventDataReady); ok { - c.readyChan <- struct{}{} - c.user = d.User - c.serverConfig = d.Config - c.v = d.V - } - c.eventHandleFunc(v.Event, v.Data) - continue - } - if handler, ok := c.commandHandlers[v.Nonce]; ok { - if v.Event == EventError { - handler.errChan <- v.Data.(EventDataError) - } else { + c.eventHandleFunc(v.Event, v.Data) + continue + } + if handler, ok := c.commandHandlers[v.Nonce]; ok { + if v.Event == EventError { + handler.errChan <- v.Data.(EventDataError) + } else { + if handler.handler != nil { handler.handler.Handle(v.Data) - handler.errChan <- nil } - close(handler.errChan) - delete(c.commandHandlers, v.Nonce) - } else { - c.logger.Errorf("No handler for nonce: %s", v.Nonce) + handler.errChan <- nil } - - case OpCodeClose: - c.Close() - break loop + close(handler.errChan) + delete(c.commandHandlers, v.Nonce) + } else { + c.logger.Errorf("No handler for nonce: %s", v.Nonce) } } } func (c *clientImpl) Close() { - if c.conn != nil { - _ = c.conn.Close() + if c.transport != nil { + _ = c.transport.Close() } } diff --git a/rpc/cmd.go b/rpc/cmd.go index ee18e6831..371220e47 100644 --- a/rpc/cmd.go +++ b/rpc/cmd.go @@ -1,6 +1,8 @@ package rpc import ( + "time" + "github.com/disgoorg/disgo/discord" "github.com/disgoorg/snowflake/v2" ) @@ -20,6 +22,21 @@ type CmdRsAuthorize struct { func (CmdRsAuthorize) messageData() {} +type CmdArgsAuthenticate struct { + AccessToken string `json:"access_token"` +} + +func (CmdArgsAuthenticate) cmdArgs() {} + +type CmdRsAuthenticate struct { + User discord.User `json:"user"` + Scopes []discord.OAuth2Scope `json:"scopes"` + Expires time.Time `json:"expires"` + Application discord.OAuth2Application `json:"application"` +} + +func (CmdRsAuthenticate) messageData() {} + type CmdArgsSetActivity struct { PID int `json:"pid"` Activity discord.Activity `json:"activity"` @@ -33,9 +50,31 @@ type CmdRsSetActivity struct { func (CmdRsSetActivity) messageData() {} -type CmdArgsSubscribe struct { +type CmdArgsSubscribe interface { + CmdArgs + cmdArgsSubscribe() +} + +type CmdArgsSubscribeMessage struct { + ChannelID snowflake.ID `json:"channel_id"` +} + +func (CmdArgsSubscribeMessage) cmdArgs() {} +func (CmdArgsSubscribeMessage) cmdArgsSubscribe() {} + +type CmdRsSubscribe struct { Evt string `json:"evt"` } +func (CmdRsSubscribe) messageData() {} + type CmdArgsUnsubscribe struct { } + +func (CmdArgsUnsubscribe) cmdArgs() {} + +type CmdRsUnsubscribe struct { + Evt string `json:"evt"` +} + +func (CmdRsUnsubscribe) messageData() {} diff --git a/rpc/conn.go b/rpc/conn.go deleted file mode 100644 index 28356e456..000000000 --- a/rpc/conn.go +++ /dev/null @@ -1,75 +0,0 @@ -package rpc - -import ( - "bufio" - "bytes" - "encoding/binary" - "io" - "net" -) - -func newConn(conn net.Conn) *Conn { - return &Conn{ - Conn: conn, - w: bufio.NewWriter(conn), - } -} - -type Conn struct { - net.Conn - w *bufio.Writer -} - -func (c *Conn) NextWriter(opCode OpCode) (io.WriteCloser, error) { - return &messageWriter{ - conn: c, - opCode: opCode, - }, nil -} - -type messageWriter struct { - conn *Conn - opCode OpCode -} - -func (w *messageWriter) Write(p []byte) (int, error) { - if err := binary.Write(w.conn.w, binary.LittleEndian, w.opCode); err != nil { - return 0, err - } - - if err := binary.Write(w.conn.w, binary.LittleEndian, int32(len(p))); err != nil { - return 0, err - } - - return w.conn.w.Write(p) -} - -func (w *messageWriter) Close() error { - return w.conn.w.Flush() -} - -func (c *Conn) NextReader() (OpCode, io.Reader, error) { - var opCode OpCode - if err := binary.Read(c, binary.LittleEndian, &opCode); err != nil { - return 0, nil, err - } - - var length int32 - if err := binary.Read(c, binary.LittleEndian, &length); err != nil { - return 0, nil, err - } - - data := make([]byte, length) - if _, err := c.Read(data); err != nil { - return 0, nil, err - } - - return opCode, bytes.NewReader(data), nil -} - -func (c *Conn) Close() error { - if err := binary.Write(c, binary.LittleEndian, OpCodeClose); err != nil { - return err - } - return c.Conn.Close() -} diff --git a/rpc/event.go b/rpc/event.go index 65b061972..64d2714c9 100644 --- a/rpc/event.go +++ b/rpc/event.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/snowflake/v2" ) type EventDataReady struct { @@ -20,6 +21,29 @@ type ServerConfig struct { Environment string `json:"environment"` } +type EventDataMessageCreate struct { + ChannelID snowflake.ID `json:"channel_id"` + Message discord.Message `json:"message"` +} + +func (EventDataMessageCreate) messageData() {} + +type EventDataMessageUpdate struct { + ChannelID snowflake.ID `json:"channel_id"` + Message discord.Message `json:"message"` +} + +func (EventDataMessageUpdate) messageData() {} + +type EventDataMessageDelete struct { + ChannelID snowflake.ID `json:"channel_id"` + Message struct { + ID snowflake.ID `json:"id"` + } `json:"message"` +} + +func (EventDataMessageDelete) messageData() {} + var _ error = (*EventDataError)(nil) type EventDataError struct { diff --git a/rpc/ipc_transport.go b/rpc/ipc_transport.go new file mode 100644 index 000000000..15aafbd95 --- /dev/null +++ b/rpc/ipc_transport.go @@ -0,0 +1,135 @@ +package rpc + +import ( + "bufio" + "bytes" + "encoding/binary" + "encoding/json" + "io" + "net" + + "github.com/disgoorg/snowflake/v2" +) + +var IPCVersion = 1 + +type Handshake struct { + V int `json:"v"` + ClientID snowflake.ID `json:"client_id"` +} + +type OpCode int32 + +const ( + OpCodeHandshake OpCode = iota + OpCodeFrame + OpCodeClose + OpCodePing + OpCodePong +) + +func NewIPCTransport(clientID snowflake.ID) (Transport, error) { + conn, err := openPipe(GetDiscordIPCPath(0)) + if err != nil { + return nil, err + } + + t := &ipcTransport{ + conn: conn, + w: bufio.NewWriter(conn), + } + + if err = t.handshake(clientID); err != nil { + return nil, err + } + + return t, nil +} + +type ipcTransport struct { + conn net.Conn + w *bufio.Writer +} + +func (t *ipcTransport) handshake(clientID snowflake.ID) error { + w, err := t.nextWriter(OpCodeHandshake) + if err != nil { + return err + } + defer func() { + _ = w.Close() + }() + + return json.NewEncoder(w).Encode(Handshake{ + V: IPCVersion, + ClientID: clientID, + }) +} + +func (t *ipcTransport) NextWriter() (io.WriteCloser, error) { + return t.nextWriter(OpCodeFrame) +} + +func (t *ipcTransport) nextWriter(opCode OpCode) (io.WriteCloser, error) { + return &messageWriter{ + t: t, + opCode: opCode, + }, nil +} + +type messageWriter struct { + t *ipcTransport + opCode OpCode +} + +func (w *messageWriter) Write(p []byte) (int, error) { + if err := binary.Write(w.t.w, binary.LittleEndian, w.opCode); err != nil { + return 0, err + } + + if err := binary.Write(w.t.w, binary.LittleEndian, int32(len(p))); err != nil { + return 0, err + } + + return w.t.w.Write(p) +} + +func (w *messageWriter) Close() error { + return w.t.w.Flush() +} + +func (t *ipcTransport) NextReader() (io.Reader, error) { + var opCode OpCode + if err := binary.Read(t.conn, binary.LittleEndian, &opCode); err != nil { + return nil, err + } + + var length int32 + if err := binary.Read(t.conn, binary.LittleEndian, &length); err != nil { + return nil, err + } + + data := make([]byte, length) + if _, err := t.conn.Read(data); err != nil { + return nil, err + } + + /*if opCode == OpCodePing { + if w, err := t.nextWriter(OpCodePong); err == nil { + defer func() { + _ = w.Close() + }() + _, _ = w.Write(data) + } + return t.NextReader() + }*/ + + return bytes.NewReader(data), nil +} + +func (t *ipcTransport) Close() error { + if err := binary.Write(t.conn, binary.LittleEndian, OpCodeClose); err != nil { + return err + } + return t.conn.Close() +} diff --git a/rpc/message.go b/rpc/message.go index 2f9709a81..5beb934f5 100644 --- a/rpc/message.go +++ b/rpc/message.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/disgoorg/disgo/json" - "github.com/disgoorg/snowflake/v2" ) type Message struct { @@ -50,6 +49,21 @@ func (m *Message) UnmarshalJSON(data []byte) error { err = json.Unmarshal(v.Data, &d) messageData = d + case EventMessageCreate: + var d EventDataMessageCreate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventMessageUpdate: + var d EventDataMessageUpdate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventMessageDelete: + var d EventDataMessageDelete + err = json.Unmarshal(v.Data, &d) + messageData = d + default: err = fmt.Errorf("unknown event: %s", v.Event) } @@ -59,6 +73,21 @@ func (m *Message) UnmarshalJSON(data []byte) error { err = json.Unmarshal(v.Data, &d) messageData = d + case CmdAuthenticate: + var d CmdRsAuthenticate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdSubscribe: + var d CmdRsSubscribe + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdUnsubscribe: + var d CmdRsUnsubscribe + err = json.Unmarshal(v.Data, &d) + messageData = d + case CmdSetActivity: var d CmdRsSetActivity err = json.Unmarshal(v.Data, &d) @@ -133,18 +162,3 @@ const ( type MessageData interface { messageData() } - -type Handshake struct { - V int `json:"v"` - ClientID snowflake.ID `json:"client_id"` -} - -type OpCode int32 - -const ( - OpCodeHandshake OpCode = iota - OpCodeFrame - OpCodeClose - OpCodePing - OpCodePong -) diff --git a/rpc/pipe.go b/rpc/pipe.go deleted file mode 100644 index f80b6b668..000000000 --- a/rpc/pipe.go +++ /dev/null @@ -1,52 +0,0 @@ -package rpc - -import ( - "fmt" - "net" - "os" - "runtime" - "strings" -) - -var unixPaths = []string{"XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP"} - -func DialPipe() (*Conn, error) { - conn, err := openPipe(GetDiscordIPCPath(0)) - if conn != nil { - return conn, nil - } - return nil, err -} - -func GetDiscordIPCPath(i int) string { - if strings.HasPrefix(runtime.GOOS, "windows") { - return fmt.Sprintf("\\\\?\\pipe\\discord-ipc-%d", i) - } - - tmpPath := "/tmp" - for _, path := range unixPaths { - if v := os.Getenv(path); v != "" { - tmpPath = v - break - } - } - return fmt.Sprintf("%sdiscord-ipc-%d", tmpPath, i) -} - -func openPipe(path string) (*Conn, error) { - var ( - conn net.Conn - err error - ) - - if strings.HasPrefix(runtime.GOOS, "windows") { - return nil, nil - } else { - conn, err = net.Dial("unix", path) - } - - if err != nil { - return nil, err - } - return newConn(conn), nil -} diff --git a/rpc/pipe_unix.go b/rpc/pipe_unix.go new file mode 100644 index 000000000..8f44f51e5 --- /dev/null +++ b/rpc/pipe_unix.go @@ -0,0 +1,24 @@ +//go:build !windows + +package rpc + +import ( + "net" +) + +var paths = []string{"XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP"} + +func GetDiscordIPCPath(i int) string { + tmpPath := "/tmp" + for _, path := range paths { + if v := os.Getenv(path); v != "" { + tmpPath = v + break + } + } + return fmt.Sprintf("%sdiscord-ipc-%d", tmpPath, i) +} + +func openPipe(path string) (net.Conn, error) { + return net.Dial("unix", path) +} diff --git a/rpc/pipe_win.go b/rpc/pipe_win.go new file mode 100644 index 000000000..b6821f3df --- /dev/null +++ b/rpc/pipe_win.go @@ -0,0 +1,18 @@ +//go:build windows + +package rpc + +import ( + "fmt" + "net" + + "github.com/Microsoft/go-winio" +) + +func GetDiscordIPCPath(i int) string { + return fmt.Sprintf("\\\\?\\pipe\\discord-ipc-%d", i) +} + +func openPipe(path string) (net.Conn, error) { + return winio.DialPipe(path, nil) +} diff --git a/rpc/ws_transport.go b/rpc/ws_transport.go new file mode 100644 index 000000000..45ce896da --- /dev/null +++ b/rpc/ws_transport.go @@ -0,0 +1,54 @@ +package rpc + +import ( + "errors" + "fmt" + "io" + "net/http" + + "github.com/disgoorg/snowflake/v2" + "github.com/gorilla/websocket" +) + +const WSVersion = 1 + +func NewWSTransport(clientID snowflake.ID, origin string) (Transport, error) { + conn, err := openWS(clientID, origin) + if err != nil { + return nil, err + } + + return &wsTransport{ + conn: conn, + }, nil +} + +type wsTransport struct { + conn *websocket.Conn +} + +func (t *wsTransport) NextWriter() (io.WriteCloser, error) { + return t.conn.NextWriter(websocket.TextMessage) +} + +func (t *wsTransport) NextReader() (io.Reader, error) { + mt, reader, err := t.conn.NextReader() + if err != nil { + return nil, err + } + if mt != websocket.TextMessage { + return nil, errors.New("invalid message type") + } + return reader, nil +} + +func (t *wsTransport) Close() error { + return t.conn.Close() +} + +func openWS(clientID snowflake.ID, origin string) (*websocket.Conn, error) { + conn, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://127.0.0.1:6463?v=%d&client_id=%d&encoding=json", WSVersion, clientID), http.Header{ + "Origin": []string{origin}, + }) + return conn, err +} From 97bab68530dad8c1ce584e49a50b2e0d44cf9367 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Fri, 1 Jul 2022 19:21:27 +0200 Subject: [PATCH 3/8] event & cmd payloads --- rpc/cmd.go | 270 +++++++++++++++++++++++++++++++++++++++++++++++++-- rpc/event.go | 139 +++++++++++++++++++++++--- 2 files changed, 391 insertions(+), 18 deletions(-) diff --git a/rpc/cmd.go b/rpc/cmd.go index 371220e47..e7f91c00a 100644 --- a/rpc/cmd.go +++ b/rpc/cmd.go @@ -37,6 +37,259 @@ type CmdRsAuthenticate struct { func (CmdRsAuthenticate) messageData() {} +type CmdArgsGetGuild struct { + GuildID snowflake.ID `json:"guild_id"` + Timeout int `json:"timeout"` +} + +func (CmdArgsGetGuild) cmdArgs() {} + +type PartialGuild struct { + ID snowflake.ID `json:"id"` + Name string `json:"name"` + IconURL *string `json:"icon_url,omitempty"` +} + +type CmdRsGetGuild struct { + PartialGuild +} + +func (CmdRsGetGuild) messageData() {} + +type CmdRsGetGuilds struct { + Guilds []PartialGuild `json:"guilds"` +} + +func (CmdRsGetGuilds) messageData() {} + +type CmdArgsGetChannel struct { + ChannelID snowflake.ID `json:"channel_id"` +} + +func (CmdArgsGetChannel) cmdArgs() {} + +type PartialChannel struct { + ID snowflake.ID `json:"id"` + GuildID *snowflake.ID `json:"guild_id,omitempty"` + Name string `json:"name"` + Type discord.ChannelType `json:"type"` + Topic *string `json:"topic,omitempty"` + Bitrate int `json:"bitrate,omitempty"` + UserLimit int `json:"user_limit,omitempty"` + Position int `json:"position,omitempty"` + VoiceStates []VoiceState `json:"voice_states,omitempty"` + Messages []Message `json:"messages,omitempty"` +} + +type VoiceState struct { + discord.VoiceState + Volume int `json:"volume"` + Pan Pan `json:"pan"` +} + +type Pan struct { + Left float32 `json:"left"` + Right float32 `json:"right"` +} + +type CmdRsGetChannel struct { + PartialChannel +} + +func (CmdRsGetChannel) messageData() {} + +type CmdArgsGetChannels struct { + GuildID snowflake.ID `json:"guild_id"` +} + +func (CmdArgsGetChannels) cmdArgs() {} + +type CmdRsGetChannels struct { + Channels []PartialChannel `json:"channels"` +} + +func (CmdRsGetChannels) messageData() {} + +type CmdArgsSetUserVoiceSettings struct { + UserID snowflake.ID `json:"user_id"` + Pain *Pan `json:"pain"` + Volume *int `json:"volume"` + Mute *bool `json:"mute"` +} + +func (CmdArgsSetUserVoiceSettings) cmdArgs() {} + +type CmdRsSetUserVoiceSettings struct { + UserID snowflake.ID `json:"user_id"` + Pain Pan `json:"pain"` + Volume int `json:"volume"` + Mute bool `json:"mute"` +} + +func (CmdRsSetUserVoiceSettings) messageData() {} + +type CmdArgsSelectVoiceChannel struct { + ChannelID snowflake.ID `json:"channel_id"` + Timeout int `json:"timeout"` + Force bool `json:"force"` +} + +func (CmdArgsSelectVoiceChannel) cmdArgs() {} + +type CmdRsSelectVoiceChannel struct { + PartialChannel +} + +func (CmdRsSelectVoiceChannel) messageData() {} + +type CmdRsGetSelectedVoiceChannel struct { + *PartialChannel +} + +func (CmdRsGetSelectedVoiceChannel) messageData() {} + +type CmdArgsSelectTextChannel struct { + ChannelID *snowflake.ID `json:"channel_id"` + Timeout int `json:"timeout"` +} + +func (CmdArgsSelectTextChannel) cmdArgs() {} + +type CmdRsSelectTextChannel struct { + *PartialChannel +} + +func (CmdRsSelectTextChannel) messageData() {} + +type CmdRsGetVoiceSettings struct { + VoiceSettings +} + +type VoiceSettings struct { + Input VoiceSettingsIO `json:"input"` + Output VoiceSettingsIO `json:"output"` + Mode VoiceSettingsMode `json:"mode"` + AutomaticGainControl bool `json:"automatic_gain_control"` + EchoCancellation bool `json:"echo_cancellation"` + NoiseSuppression bool `json:"noise_suppression"` + QOS bool `json:"qos"` + SilenceWarning bool `json:"silence_warning"` + Deaf bool `json:"deaf"` + Mute bool `json:"mute"` +} + +type Device struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type VoiceSettingsIO struct { + DeviceID string `json:"device_id"` + Volume int `json:"volume"` + AvailableDevices []Device `json:"available_devices"` +} + +type VoiceSettingsModeType string + +const ( + VoiceSettingsModeTypePushToTalk VoiceSettingsModeType = "PUSH_TO_TALK" + VoiceSettingsModeTypeActivity VoiceSettingsModeType = "ACTIVITY" +) + +type VoiceSettingsMode struct { + Type VoiceSettingsModeType `json:"type"` + AutoThreshold bool `json:"auto_threshold"` + Threshold int `json:"threshold"` + Shortcut ShortcutKeyCombo `json:"shortcut"` + Delay float32 `json:"delay"` +} + +type ShortcutKeyComboType int + +const ( + ShortcutKeyComboTypeKeyboardKey ShortcutKeyComboType = iota + ShortcutKeyComboTypeMouseButton + ShortcutKeyComboTypeModifierKey + ShortcutKeyComboTypeGamepadButton +) + +type ShortcutKeyCombo struct { + Type ShortcutKeyComboType `json:"type"` + Code int `json:"code"` + Name string `json:"name"` +} + +func (CmdRsGetVoiceSettings) messageData() {} + +type CmdArgsSetVoiceSettings struct { + Input *VoiceSettings `json:"input"` + Output *VoiceSettings `json:"output"` + Mode *VoiceSettingsMode `json:"mode"` + AutomaticGainControl *bool `json:"automatic_gain_control"` + EchoCancellation *bool `json:"echo_cancellation"` + NoiseSuppression *bool `json:"noise_suppression"` + QOS *bool `json:"qos"` + SilenceWarning *bool `json:"silence_warning"` + Deaf *bool `json:"deaf"` + Mute *bool `json:"mute"` +} + +func (CmdArgsSetVoiceSettings) cmdArgs() {} + +type CmdRsSetVoiceSettings struct { + VoiceSettings +} + +func (CmdRsSetVoiceSettings) messageData() {} + +type DeviceType string + +const ( + DeviceTypeAudioInput DeviceType = "audioinput" + DeviceTypeAudioOutput DeviceType = "audiooutput" + DeviceTypeVideoInput DeviceType = "videoinput" +) + +type DeviceVendor struct { + Name string `json:"name"` + URL string `json:"url"` +} + +type DeviceModel struct { + Name string `json:"name"` + URL string `json:"url"` +} + +type CertifiedDevice struct { + Type DeviceType `json:"type"` + ID string `json:"id"` + Vendor DeviceVendor `json:"vendor"` + Model DeviceModel `json:"model"` + Related []string `json:"related"` + EchoCancellation bool `json:"echo_cancellation"` + NoiseSuppression bool `json:"noise_suppression"` + AutomaticGainControl bool `json:"automatic_gain_control"` + HardwareMute bool `json:"hardware_mute"` +} + +type CmdArgsSetCertifiedDevices struct { + Devices []CertifiedDevice `json:"devices"` +} + +func (CmdArgsSetCertifiedDevices) cmdArgs() {} + +type CmdArgsSendActivityJoinInvite struct { + UserID snowflake.ID `json:"user_id"` +} + +func (CmdArgsSendActivityJoinInvite) cmdArgs() {} + +type CmdArgsCloseActivityRequest struct { + UserID snowflake.ID `json:"user_id"` +} + +func (CmdArgsCloseActivityRequest) cmdArgs() {} + type CmdArgsSetActivity struct { PID int `json:"pid"` Activity discord.Activity `json:"activity"` @@ -62,19 +315,22 @@ type CmdArgsSubscribeMessage struct { func (CmdArgsSubscribeMessage) cmdArgs() {} func (CmdArgsSubscribeMessage) cmdArgsSubscribe() {} -type CmdRsSubscribe struct { - Evt string `json:"evt"` +type CmdArgsSubscribeGuild struct { + GuildID snowflake.ID `json:"guild_id"` } -func (CmdRsSubscribe) messageData() {} +func (CmdArgsSubscribeGuild) cmdArgs() {} +func (CmdArgsSubscribeGuild) cmdArgsSubscribe() {} -type CmdArgsUnsubscribe struct { +type CmdArgsSubscribeSpeaking struct { + ChannelID snowflake.ID `json:"channel_id"` } -func (CmdArgsUnsubscribe) cmdArgs() {} +func (CmdArgsSubscribeSpeaking) cmdArgs() {} +func (CmdArgsSubscribeSpeaking) cmdArgsSubscribe() {} -type CmdRsUnsubscribe struct { +type CmdRsSubscribe struct { Evt string `json:"evt"` } -func (CmdRsUnsubscribe) messageData() {} +func (CmdRsSubscribe) messageData() {} diff --git a/rpc/event.go b/rpc/event.go index 64d2714c9..78f7b0ab1 100644 --- a/rpc/event.go +++ b/rpc/event.go @@ -7,6 +7,12 @@ import ( "github.com/disgoorg/snowflake/v2" ) +type ServerConfig struct { + CDNHost string `json:"cdn_host"` + APIEndpoint string `json:"api_endpoint"` + Environment string `json:"environment"` +} + type EventDataReady struct { V int `json:"v"` Config ServerConfig `json:"config"` @@ -15,12 +21,96 @@ type EventDataReady struct { func (EventDataReady) messageData() {} -type ServerConfig struct { - CDNHost string `json:"cdn_host"` - APIEndpoint string `json:"api_endpoint"` - Environment string `json:"environment"` +var _ error = (*EventDataError)(nil) + +type EventDataError struct { + Code int `json:"code"` + Message string `json:"message"` +} + +func (e EventDataError) Error() string { + return fmt.Sprintf("%d: %s", e.Code, e.Message) +} + +func (EventDataError) messageData() {} + +type EventDataGuildStatus struct { + Guild PartialGuild `json:"guild"` +} + +func (EventDataGuildStatus) messageData() {} + +type EventDataGuildCreate struct { + ID snowflake.ID `json:"id"` + Name string `json:"name"` +} + +func (EventDataGuildCreate) messageData() {} + +type EventDataChannelCreate struct { + ID snowflake.ID `json:"id"` + Name string `json:"name"` + Type discord.ChannelType `json:"type"` +} + +func (EventDataChannelCreate) messageData() {} + +type EventDataSelectVoiceChannel struct { + ChannelID *snowflake.ID `json:"channel_id"` + GuildID *snowflake.ID `json:"guild_id"` +} + +func (EventDataSelectVoiceChannel) messageData() {} + +type EventDataVoiceSettingsUpdate struct { + VoiceSettings +} + +func (EventDataVoiceSettingsUpdate) messageData() {} + +type EventDataVoiceStateCreate struct { + VoiceState +} + +func (EventDataVoiceStateCreate) messageData() {} + +type EventDataVoiceStateUpdate struct { + VoiceState +} + +func (EventDataVoiceStateUpdate) messageData() {} + +type EventDataEventDataVoiceStateDelete struct { + VoiceState +} + +func (EventDataEventDataVoiceStateDelete) messageData() {} + +type VoiceStateType string + +const ( + VoiceStateTypeDisconnected VoiceStateType = "DISCONNECTED" + VoiceStateTypeAwaitingEndpoint VoiceStateType = "AWAITING_ENDPOINT" + VoiceStateTypeAuthenticating VoiceStateType = "AUTHENTICATING" + VoiceStateTypeConnecting VoiceStateType = "CONNECTING" + VoiceStateTypeConnected VoiceStateType = "CONNECTED" + VoiceStateTypeVoiceDisconnected VoiceStateType = "VOICE_DISCONNECTED" + VoiceStateTypeVoiceConnecting VoiceStateType = "VOICE_CONNECTING" + VoiceStateTypeVoiceConnected VoiceStateType = "VOICE_CONNECTED" + VoiceStateTypeNoRoute VoiceStateType = "NO_ROUTE" + VoiceStateTypeICEChecking VoiceStateType = "ICE_CHECKING" +) + +type EventDataVoiceConnectionStatus struct { + State VoiceStateType `json:"state"` + Hostname string `json:"hostname"` + Pings []float32 `json:"pings"` + AveragePing float32 `json:"average_ping"` + LastPing float32 `json:"last_ping"` } +func (EventDataVoiceConnectionStatus) messageData() {} + type EventDataMessageCreate struct { ChannelID snowflake.ID `json:"channel_id"` Message discord.Message `json:"message"` @@ -44,15 +134,42 @@ type EventDataMessageDelete struct { func (EventDataMessageDelete) messageData() {} -var _ error = (*EventDataError)(nil) +type EventDataSpeakingStart struct { + UserID snowflake.ID `json:"user_id"` +} -type EventDataError struct { - Code int `json:"code"` - Message string `json:"message"` +func (EventDataSpeakingStart) messageData() {} + +type EventDataSpeakingStop struct { + UserID snowflake.ID `json:"user_id"` } -func (e EventDataError) Error() string { - return fmt.Sprintf("%d: %s", e.Code, e.Message) +func (EventDataSpeakingStop) messageData() {} + +type EventDataNotificationCreate struct { + ChannelID snowflake.ID `json:"channel_id"` + Message discord.Message `json:"message"` + IconURL string `json:"icon_url"` + Title string `json:"title"` + Body string `json:"body"` } -func (EventDataError) messageData() {} +func (EventDataNotificationCreate) messageData() {} + +type EventDataActivityJoin struct { + Secret string `json:"secret"` +} + +func (EventDataActivityJoin) messageData() {} + +type EventDataActivitySpectate struct { + Secret string `json:"secret"` +} + +func (EventDataActivitySpectate) messageData() {} + +type EventDataActivityJoinRequest struct { + User discord.User `json:"user"` +} + +func (EventDataActivityJoinRequest) messageData() {} From e5551b59038bc1a8fcaf0e0a88df734636217e93 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Fri, 1 Jul 2022 19:58:01 +0200 Subject: [PATCH 4/8] fix errors --- rpc/cmd.go | 6 ++++++ rpc/pipe_unix.go | 2 ++ 2 files changed, 8 insertions(+) diff --git a/rpc/cmd.go b/rpc/cmd.go index e7f91c00a..ea243447b 100644 --- a/rpc/cmd.go +++ b/rpc/cmd.go @@ -334,3 +334,9 @@ type CmdRsSubscribe struct { } func (CmdRsSubscribe) messageData() {} + +type CmdRsUnsubscribe struct { + Evt string `json:"evt"` +} + +func (CmdRsUnsubscribe) messageData() {} diff --git a/rpc/pipe_unix.go b/rpc/pipe_unix.go index 8f44f51e5..6bfbc2588 100644 --- a/rpc/pipe_unix.go +++ b/rpc/pipe_unix.go @@ -3,7 +3,9 @@ package rpc import ( + "fmt" "net" + "os" ) var paths = []string{"XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP"} From 550cd08b1f6ba33b68c425a2421e1d4d584796aa Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sat, 2 Jul 2022 01:09:54 +0200 Subject: [PATCH 5/8] some fixes and missing event unmarshalling --- _examples/rpc/rpc.go | 21 ++----- rpc/client.go | 64 +++++++++++++------- rpc/cmd.go | 8 +-- rpc/event.go | 8 +-- rpc/handler.go | 12 ++-- rpc/ipc_transport.go | 17 +++--- rpc/message.go | 136 ++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 206 insertions(+), 60 deletions(-) diff --git a/_examples/rpc/rpc.go b/_examples/rpc/rpc.go index fbf613bcb..2a98876a9 100644 --- a/_examples/rpc/rpc.go +++ b/_examples/rpc/rpc.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "os/signal" "syscall" @@ -23,11 +22,7 @@ func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) log.Info("example is starting...") - eventHandler := func(event rpc.Event, data rpc.MessageData) { - //log.Infof("event: %s, data: %#v", event, data) - } - - client, err := rpc.NewIPCClient(clientID, eventHandler) + client, err := rpc.NewIPCClient(clientID) if err != nil { log.Fatal(err) return @@ -41,7 +36,7 @@ func main() { ClientID: clientID, Scopes: []discord.OAuth2Scope{discord.OAuth2ScopeRPC, discord.OAuth2ScopeMessagesRead}, }, - }, rpc.CmdHandler(func(data rpc.CmdRsAuthorize) { + }, rpc.NewHandler(func(data rpc.CmdRsAuthorize) { tokenRs, err = client.OAuth2().GetAccessToken(clientID, clientSecret, data.Code, "http://localhost") if err != nil { log.Fatal(err) @@ -59,14 +54,10 @@ func main() { log.Fatal(err) } - if err = client.Send(rpc.Message{ - Cmd: rpc.CmdSubscribe, - Event: rpc.EventMessageCreate, - Args: rpc.CmdArgsSubscribeMessage{ - ChannelID: channelID, - }, - }, rpc.CmdHandler(func(data rpc.CmdRsSubscribe) { - fmt.Printf("handleSubscribe: %s\n", data.Evt) + if err = client.Subscribe(rpc.EventMessageCreate, rpc.CmdArgsSubscribeMessage{ + ChannelID: channelID, + }, rpc.NewHandler(func(data rpc.EventDataMessageCreate) { + log.Info("message: ", data.Message.Content) })); err != nil { log.Fatal(err) } diff --git a/rpc/client.go b/rpc/client.go index 43f71f4b1..8f5d3aea0 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -11,12 +11,11 @@ import ( "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/internal/insecurerandstr" "github.com/disgoorg/disgo/json" - "github.com/disgoorg/disgo/rest" "github.com/disgoorg/log" "github.com/disgoorg/snowflake/v2" ) -type EventHandleFunc func(event Event, data MessageData) +var Version = 1 type Transport interface { NextWriter() (io.WriteCloser, error) @@ -29,35 +28,35 @@ type Client interface { ServerConfig() ServerConfig User() discord.User V() int - OAuth2() rest.OAuth2 - Send(message Message, handler CommandHandler) error + Subscribe(event Event, args CmdArgs, handler Handler) error + Unsubscribe(event Event, args CmdArgs) error + Send(message Message, handler Handler) error Close() } -func NewIPCClient(clientID snowflake.ID, eventHandleFunc EventHandleFunc) (Client, error) { +func NewIPCClient(clientID snowflake.ID) (Client, error) { transport, err := NewIPCTransport(clientID) if err != nil { return nil, err } - return newClient(transport, eventHandleFunc) + return newClient(transport) } -func NewWSClient(clientID snowflake.ID, origin string, eventHandleFunc EventHandleFunc) (Client, error) { +func NewWSClient(clientID snowflake.ID, origin string) (Client, error) { transport, err := NewWSTransport(clientID, origin) if err != nil { return nil, err } - return newClient(transport, eventHandleFunc) + return newClient(transport) } -func newClient(transport Transport, eventHandleFunc EventHandleFunc) (Client, error) { +func newClient(transport Transport) (Client, error) { client := &clientImpl{ logger: log.Default(), transport: transport, - eventHandleFunc: eventHandleFunc, + eventHandlers: map[Event]Handler{}, commandHandlers: map[string]internalHandler{}, - oauth2: rest.NewOAuth2(rest.NewClient("")), readyChan: make(chan struct{}, 1), } @@ -75,12 +74,11 @@ func newClient(transport Transport, eventHandleFunc EventHandleFunc) (Client, er } type clientImpl struct { - logger log.Logger - transport Transport - eventHandleFunc EventHandleFunc - commandHandlers map[string]internalHandler + logger log.Logger + transport Transport - oauth2 rest.OAuth2 + eventHandlers map[Event]Handler + commandHandlers map[string]internalHandler readyChan chan struct{} user discord.User @@ -104,10 +102,6 @@ func (c *clientImpl) V() int { return c.v } -func (c *clientImpl) OAuth2() rest.OAuth2 { - return c.oauth2 -} - func (c *clientImpl) send(r io.Reader) error { writer, err := c.transport.NextWriter() if err != nil { @@ -129,7 +123,31 @@ func (c *clientImpl) send(r io.Reader) error { return err } -func (c *clientImpl) Send(message Message, handler CommandHandler) error { +func (c *clientImpl) Subscribe(event Event, args CmdArgs, handler Handler) error { + if _, ok := c.eventHandlers[event]; ok { + return errors.New("event already subscribed") + } + c.eventHandlers[event] = handler + return c.Send(Message{ + Cmd: CmdSubscribe, + Args: args, + Event: event, + }, nil) +} + +func (c *clientImpl) Unsubscribe(event Event, args CmdArgs) error { + if _, ok := c.eventHandlers[event]; ok { + delete(c.eventHandlers, event) + return c.Send(Message{ + Cmd: CmdUnsubscribe, + Args: args, + Event: event, + }, nil) + } + return errors.New("event not subscribed") +} + +func (c *clientImpl) Send(message Message, handler Handler) error { nonce := insecurerandstr.RandStr(32) buff := new(bytes.Buffer) @@ -187,7 +205,9 @@ loop: c.serverConfig = d.Config c.v = d.V } - c.eventHandleFunc(v.Event, v.Data) + if handler, ok := c.eventHandlers[v.Event]; ok { + handler.Handle(v.Data) + } continue } if handler, ok := c.commandHandlers[v.Nonce]; ok { diff --git a/rpc/cmd.go b/rpc/cmd.go index ea243447b..ab856e6d0 100644 --- a/rpc/cmd.go +++ b/rpc/cmd.go @@ -112,16 +112,16 @@ func (CmdRsGetChannels) messageData() {} type CmdArgsSetUserVoiceSettings struct { UserID snowflake.ID `json:"user_id"` - Pain *Pan `json:"pain"` - Volume *int `json:"volume"` - Mute *bool `json:"mute"` + Pan *Pan `json:"pan,omitempty"` + Volume *int `json:"volume,omitempty"` + Mute *bool `json:"mute,omitempty"` } func (CmdArgsSetUserVoiceSettings) cmdArgs() {} type CmdRsSetUserVoiceSettings struct { UserID snowflake.ID `json:"user_id"` - Pain Pan `json:"pain"` + Pan Pan `json:"pan"` Volume int `json:"volume"` Mute bool `json:"mute"` } diff --git a/rpc/event.go b/rpc/event.go index 78f7b0ab1..7634949c8 100644 --- a/rpc/event.go +++ b/rpc/event.go @@ -55,12 +55,12 @@ type EventDataChannelCreate struct { func (EventDataChannelCreate) messageData() {} -type EventDataSelectVoiceChannel struct { +type EventDataVoiceChannelSelect struct { ChannelID *snowflake.ID `json:"channel_id"` GuildID *snowflake.ID `json:"guild_id"` } -func (EventDataSelectVoiceChannel) messageData() {} +func (EventDataVoiceChannelSelect) messageData() {} type EventDataVoiceSettingsUpdate struct { VoiceSettings @@ -80,11 +80,11 @@ type EventDataVoiceStateUpdate struct { func (EventDataVoiceStateUpdate) messageData() {} -type EventDataEventDataVoiceStateDelete struct { +type EventDataVoiceStateDelete struct { VoiceState } -func (EventDataEventDataVoiceStateDelete) messageData() {} +func (EventDataVoiceStateDelete) messageData() {} type VoiceStateType string diff --git a/rpc/handler.go b/rpc/handler.go index 2c2428f96..34d5ea4a6 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -1,25 +1,25 @@ package rpc type internalHandler struct { - handler CommandHandler + handler Handler errChan chan error } -type CommandHandler interface { +type Handler interface { Handle(data MessageData) } -func CmdHandler[T MessageData](handler func(data T)) CommandHandler { - return &defaultCommandHandler[T]{ +func NewHandler[T MessageData](handler func(data T)) Handler { + return &defaultHandler[T]{ handler: handler, } } -type defaultCommandHandler[T MessageData] struct { +type defaultHandler[T MessageData] struct { handler func(data T) } -func (h *defaultCommandHandler[T]) Handle(data MessageData) { +func (h *defaultHandler[T]) Handle(data MessageData) { if d, ok := data.(T); ok { h.handler(d) } diff --git a/rpc/ipc_transport.go b/rpc/ipc_transport.go index 15aafbd95..47af2dc67 100644 --- a/rpc/ipc_transport.go +++ b/rpc/ipc_transport.go @@ -11,8 +11,6 @@ import ( "github.com/disgoorg/snowflake/v2" ) -var IPCVersion = 1 - type Handshake struct { V int `json:"v"` ClientID snowflake.ID `json:"client_id"` @@ -61,7 +59,7 @@ func (t *ipcTransport) handshake(clientID snowflake.ID) error { }() return json.NewEncoder(w).Encode(Handshake{ - V: IPCVersion, + V: Version, ClientID: clientID, }) } @@ -104,6 +102,11 @@ func (t *ipcTransport) NextReader() (io.Reader, error) { return nil, err } + if opCode == OpCodeClose { + _ = t.Close() + return nil, net.ErrClosed + } + var length int32 if err := binary.Read(t.conn, binary.LittleEndian, &length); err != nil { return nil, err @@ -114,15 +117,13 @@ func (t *ipcTransport) NextReader() (io.Reader, error) { return nil, err } - /*if opCode == OpCodePing { + if opCode == OpCodePing { if w, err := t.nextWriter(OpCodePong); err == nil { - defer func() { - _ = w.Close() - }() _, _ = w.Write(data) + _ = w.Close() } return t.NextReader() - }*/ + } return bytes.NewReader(data), nil } diff --git a/rpc/message.go b/rpc/message.go index 5beb934f5..1512b61c7 100644 --- a/rpc/message.go +++ b/rpc/message.go @@ -49,6 +49,51 @@ func (m *Message) UnmarshalJSON(data []byte) error { err = json.Unmarshal(v.Data, &d) messageData = d + case EventGuildStatus: + var d EventDataGuildStatus + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventGuildCreate: + var d EventDataGuildCreate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventChannelCreate: + var d EventDataChannelCreate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventVoiceChannelSelect: + var d EventDataVoiceChannelSelect + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventVoiceSettingsUpdate: + var d EventDataVoiceSettingsUpdate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventVoiceStateCreate: + var d EventDataVoiceStateCreate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventVoiceStateUpdate: + var d EventDataVoiceStateUpdate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventVoiceStateDelete: + var d EventDataVoiceStateDelete + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventVoiceConnectionStatus: + var d EventDataVoiceConnectionStatus + err = json.Unmarshal(v.Data, &d) + messageData = d + case EventMessageCreate: var d EventDataMessageCreate err = json.Unmarshal(v.Data, &d) @@ -64,6 +109,36 @@ func (m *Message) UnmarshalJSON(data []byte) error { err = json.Unmarshal(v.Data, &d) messageData = d + case EventSpeakingStart: + var d EventDataSpeakingStart + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventSpeakingStop: + var d EventDataSpeakingStop + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventNotificationCreate: + var d EventDataNotificationCreate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventActivityJoin: + var d EventDataActivityJoin + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventActivitySpectate: + var d EventDataActivitySpectate + err = json.Unmarshal(v.Data, &d) + messageData = d + + case EventActivityJoinRequest: + var d EventDataActivityJoinRequest + err = json.Unmarshal(v.Data, &d) + messageData = d + default: err = fmt.Errorf("unknown event: %s", v.Event) } @@ -78,6 +153,65 @@ func (m *Message) UnmarshalJSON(data []byte) error { err = json.Unmarshal(v.Data, &d) messageData = d + case CmdGetGuild: + var d CmdRsGetGuild + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdGetGuilds: + var d CmdRsGetGuilds + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdGetChannel: + var d CmdRsGetChannel + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdGetChannels: + var d CmdRsGetChannels + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdSetUserVoiceSettings: + var d CmdRsSetUserVoiceSettings + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdSelectVoiceChannel: + var d CmdRsSelectVoiceChannel + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdGetSelectedVoiceChannel: + var d CmdRsGetSelectedVoiceChannel + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdSelectTextChannel: + var d CmdRsSelectTextChannel + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdGetVoiceSettings: + var d CmdRsGetVoiceSettings + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdSetVoiceSettings: + var d CmdRsSetVoiceSettings + err = json.Unmarshal(v.Data, &d) + messageData = d + + case CmdSetCertifiedDevices: + // no response data + + case CmdSendActivityJoinInvite: + // no response data + + case CmdCloseActivityRequest: + // no response data + case CmdSubscribe: var d CmdRsSubscribe err = json.Unmarshal(v.Data, &d) @@ -126,7 +260,7 @@ const ( CmdSetVoiceSettings Cmd = "SET_VOICE_SETTINGS" CmdSetCertifiedDevices Cmd = "SET_CERTIFIED_DEVICES" CmdSetActivity Cmd = "SET_ACTIVITY" - CmdSendActivityRequest Cmd = "SEND_ACTIVITY_JOIN_INVITE" + CmdSendActivityJoinInvite Cmd = "SEND_ACTIVITY_JOIN_INVITE" CmdCloseActivityRequest Cmd = "CLOSE_ACTIVITY_REQUEST" ) From 17e63c99cb81843633360f56bfcf21d1defd3788 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sat, 2 Jul 2022 01:11:02 +0200 Subject: [PATCH 6/8] fix example --- _examples/rpc/rpc.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/_examples/rpc/rpc.go b/_examples/rpc/rpc.go index 2a98876a9..42207bfa8 100644 --- a/_examples/rpc/rpc.go +++ b/_examples/rpc/rpc.go @@ -6,6 +6,7 @@ import ( "syscall" "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/rest" "github.com/disgoorg/disgo/rpc" "github.com/disgoorg/log" "github.com/disgoorg/snowflake/v2" @@ -22,6 +23,8 @@ func main() { log.SetFlags(log.LstdFlags | log.Lshortfile) log.Info("example is starting...") + oauth2Client := rest.NewOAuth2(rest.NewClient("")) + client, err := rpc.NewIPCClient(clientID) if err != nil { log.Fatal(err) @@ -37,7 +40,7 @@ func main() { Scopes: []discord.OAuth2Scope{discord.OAuth2ScopeRPC, discord.OAuth2ScopeMessagesRead}, }, }, rpc.NewHandler(func(data rpc.CmdRsAuthorize) { - tokenRs, err = client.OAuth2().GetAccessToken(clientID, clientSecret, data.Code, "http://localhost") + tokenRs, err = oauth2Client.GetAccessToken(clientID, clientSecret, data.Code, "http://localhost") if err != nil { log.Fatal(err) } From a5968202d48cfc156d1a0b83bdc52b40822c0052 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Mon, 11 Jul 2022 00:44:14 +0200 Subject: [PATCH 7/8] add rpc client config, add port & pipe retrying, some refactoring --- _examples/rpc/rpc.go | 2 +- rpc/client.go | 46 +++++++++++++++------------- rpc/config.go | 73 ++++++++++++++++++++++++++++++++++++++++++++ rpc/ipc_transport.go | 13 ++++++-- rpc/pipe_unix.go | 2 +- rpc/pipe_win.go | 2 +- rpc/ws_transport.go | 15 +++++++-- 7 files changed, 123 insertions(+), 30 deletions(-) create mode 100644 rpc/config.go diff --git a/_examples/rpc/rpc.go b/_examples/rpc/rpc.go index 42207bfa8..2635fd005 100644 --- a/_examples/rpc/rpc.go +++ b/_examples/rpc/rpc.go @@ -25,7 +25,7 @@ func main() { oauth2Client := rest.NewOAuth2(rest.NewClient("")) - client, err := rpc.NewIPCClient(clientID) + client, err := rpc.NewClient(clientID) if err != nil { log.Fatal(err) return diff --git a/rpc/client.go b/rpc/client.go index 8f5d3aea0..a9b4f2265 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -17,6 +17,8 @@ import ( var Version = 1 +type TransportCreate func(clientID snowflake.ID, origin string) (Transport, error) + type Transport interface { NextWriter() (io.WriteCloser, error) NextReader() (io.Reader, error) @@ -28,6 +30,7 @@ type Client interface { ServerConfig() ServerConfig User() discord.User V() int + Transport() Transport Subscribe(event Event, args CmdArgs, handler Handler) error Unsubscribe(event Event, args CmdArgs) error @@ -35,32 +38,27 @@ type Client interface { Close() } -func NewIPCClient(clientID snowflake.ID) (Client, error) { - transport, err := NewIPCTransport(clientID) - if err != nil { - return nil, err - } - return newClient(transport) -} - -func NewWSClient(clientID snowflake.ID, origin string) (Client, error) { - transport, err := NewWSTransport(clientID, origin) - if err != nil { - return nil, err - } - return newClient(transport) -} +func NewClient(clientID snowflake.ID, opts ...ConfigOpt) (Client, error) { + config := DefaultConfig() + config.Apply(opts) -func newClient(transport Transport) (Client, error) { client := &clientImpl{ - logger: log.Default(), - transport: transport, + logger: config.Logger, eventHandlers: map[Event]Handler{}, commandHandlers: map[string]internalHandler{}, readyChan: make(chan struct{}, 1), } - go client.listen(transport) + if config.Transport == nil { + var err error + config.Transport, err = config.TransportCreate(clientID, config.Origin) + if err != nil { + return nil, err + } + } + client.transport = config.Transport + + go client.listen(client.transport) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -102,6 +100,10 @@ func (c *clientImpl) V() int { return c.v } +func (c *clientImpl) Transport() Transport { + return c.transport +} + func (c *clientImpl) send(r io.Reader) error { writer, err := c.transport.NextWriter() if err != nil { @@ -118,7 +120,7 @@ func (c *clientImpl) send(r io.Reader) error { } data, _ := io.ReadAll(buff) - c.logger.Debugf("Sending message: data: %s", string(data)) + c.logger.Tracef("Sending message: data: %s", string(data)) return err } @@ -144,7 +146,7 @@ func (c *clientImpl) Unsubscribe(event Event, args CmdArgs) error { Event: event, }, nil) } - return errors.New("event not subscribed") + return nil } func (c *clientImpl) Send(message Message, handler Handler) error { @@ -188,7 +190,7 @@ loop: c.logger.Errorf("Error reading message: %s", err) continue } - c.logger.Debugf("Received message: data: %s", string(data)) + c.logger.Tracef("Received message: data: %s", string(data)) reader = bytes.NewReader(data) diff --git a/rpc/config.go b/rpc/config.go new file mode 100644 index 000000000..d0b005f12 --- /dev/null +++ b/rpc/config.go @@ -0,0 +1,73 @@ +package rpc + +import ( + "github.com/disgoorg/log" +) + +// DefaultConfig is the configuration which is used by default +func DefaultConfig() *Config { + return &Config{ + Logger: log.Default(), + TransportCreate: NewIPCTransport, + } +} + +type Config struct { + Logger log.Logger + Transport Transport + TransportCreate TransportCreate + Origin string +} + +// ConfigOpt can be used to supply optional parameters to NewIPCClient or NewWSClient +type ConfigOpt func(config *Config) + +// Apply applies the given ConfigOpt(s) to the Config +func (c *Config) Apply(opts []ConfigOpt) { + for _, opt := range opts { + opt(c) + } +} + +// WithLogger applies a custom logger to the rpc Client +func WithLogger(logger log.Logger) ConfigOpt { + return func(config *Config) { + config.Logger = logger + } +} + +// WithTransport applies your own Transport to the rpc Client +func WithTransport(transport Transport) ConfigOpt { + return func(config *Config) { + config.Transport = transport + } +} + +// WithTransportCreate applies a custom logger to the rpc Client +func WithTransportCreate(transportCreate TransportCreate) ConfigOpt { + return func(config *Config) { + config.TransportCreate = transportCreate + } +} + +// WithIPCTransport applies the ipc Transport to the rpc Client +func WithIPCTransport() ConfigOpt { + return func(config *Config) { + config.TransportCreate = NewIPCTransport + } +} + +// WithWSTransport applies the ws Transport to the rpc Client +func WithWSTransport(origin string) ConfigOpt { + return func(config *Config) { + config.TransportCreate = NewWSTransport + config.Origin = origin + } +} + +// WithOrigin sets the origin for the ws Transport +func WithOrigin(origin string) ConfigOpt { + return func(config *Config) { + config.Origin = origin + } +} diff --git a/rpc/ipc_transport.go b/rpc/ipc_transport.go index 47af2dc67..525e6a6f0 100644 --- a/rpc/ipc_transport.go +++ b/rpc/ipc_transport.go @@ -26,8 +26,17 @@ const ( OpCodePong ) -func NewIPCTransport(clientID snowflake.ID) (Transport, error) { - conn, err := openPipe(GetDiscordIPCPath(0)) +func NewIPCTransport(clientID snowflake.ID, _ string) (Transport, error) { + var ( + conn net.Conn + err error + ) + for i := 0; i < 10; i++ { + conn, err = openPipe(getDiscordIPCPath(i)) + if err == nil { + break + } + } if err != nil { return nil, err } diff --git a/rpc/pipe_unix.go b/rpc/pipe_unix.go index 6bfbc2588..ecd5e934e 100644 --- a/rpc/pipe_unix.go +++ b/rpc/pipe_unix.go @@ -10,7 +10,7 @@ import ( var paths = []string{"XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP"} -func GetDiscordIPCPath(i int) string { +func getDiscordIPCPath(i int) string { tmpPath := "/tmp" for _, path := range paths { if v := os.Getenv(path); v != "" { diff --git a/rpc/pipe_win.go b/rpc/pipe_win.go index b6821f3df..c11d1af22 100644 --- a/rpc/pipe_win.go +++ b/rpc/pipe_win.go @@ -9,7 +9,7 @@ import ( "github.com/Microsoft/go-winio" ) -func GetDiscordIPCPath(i int) string { +func getDiscordIPCPath(i int) string { return fmt.Sprintf("\\\\?\\pipe\\discord-ipc-%d", i) } diff --git a/rpc/ws_transport.go b/rpc/ws_transport.go index 45ce896da..8de40c7e4 100644 --- a/rpc/ws_transport.go +++ b/rpc/ws_transport.go @@ -13,7 +13,16 @@ import ( const WSVersion = 1 func NewWSTransport(clientID snowflake.ID, origin string) (Transport, error) { - conn, err := openWS(clientID, origin) + var ( + conn *websocket.Conn + err error + ) + for port := 6463; port < 6472; port++ { + conn, err = openWS(clientID, origin, port) + if err == nil { + break + } + } if err != nil { return nil, err } @@ -46,8 +55,8 @@ func (t *wsTransport) Close() error { return t.conn.Close() } -func openWS(clientID snowflake.ID, origin string) (*websocket.Conn, error) { - conn, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://127.0.0.1:6463?v=%d&client_id=%d&encoding=json", WSVersion, clientID), http.Header{ +func openWS(clientID snowflake.ID, origin string, port int) (*websocket.Conn, error) { + conn, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://127.0.0.1:%d?v=%d&client_id=%d&encoding=json", port, WSVersion, clientID), http.Header{ "Origin": []string{origin}, }) return conn, err From ea2ffae8a292486247c42ed874a590b42a1e5ba8 Mon Sep 17 00:00:00 2001 From: TopiSenpai Date: Sat, 4 Feb 2023 00:19:46 +0100 Subject: [PATCH 8/8] go mod tidy --- _examples/rpc/rpc.go | 5 +++-- go.mod | 2 +- go.sum | 13 +++---------- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/_examples/rpc/rpc.go b/_examples/rpc/rpc.go index 2635fd005..9925be79f 100644 --- a/_examples/rpc/rpc.go +++ b/_examples/rpc/rpc.go @@ -5,11 +5,12 @@ import ( "os/signal" "syscall" + "github.com/disgoorg/log" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/rest" "github.com/disgoorg/disgo/rpc" - "github.com/disgoorg/log" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/go.mod b/go.mod index 5ff9c0a24..979fe10d5 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/disgoorg/disgo go 1.18 require ( - github.com/disgoorg/json v1.0.0 github.com/Microsoft/go-winio v0.5.2 + github.com/disgoorg/json v1.0.0 github.com/disgoorg/log v1.2.0 github.com/disgoorg/snowflake/v2 v2.0.1 github.com/gorilla/websocket v1.5.0 diff --git a/go.sum b/go.sum index 5c2f896bb..7d1f66978 100644 --- a/go.sum +++ b/go.sum @@ -3,9 +3,6 @@ github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v 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/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/disgoorg/json v1.0.0 h1:kDhSM661fgIuNoZF3BO5/odaR5NSq80AWb937DH+Pdo= github.com/disgoorg/json v1.0.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= github.com/disgoorg/log v1.2.0 h1:sqlXnu/ZKAlIlHV9IO+dbMto7/hCQ474vlIdMWk8QKo= @@ -20,15 +17,9 @@ github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b h1:qYTY2tN72LhgDj github.com/sasha-s/go-csync v0.0.0-20210812194225-61421b77c44b/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 h1:Xt4/LzbTwfocTk9ZLEu4onjeFucl88iW+v4j4PWbQuE= -golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= @@ -37,6 +28,8 @@ golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8 h1:Xt4/LzbTwfocTk9ZLEu4onjeFucl88iW+v4j4PWbQuE= golang.org/x/exp v0.0.0-20220325121720-054d8573a5d8/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=