diff options
| author | Mel <einebeere@gmail.com> | 2022-04-04 00:05:12 +0200 |
|---|---|---|
| committer | Mel <einebeere@gmail.com> | 2022-04-04 00:05:12 +0200 |
| commit | 4cbe353d824d5064ee6f72f4693ee3c759b50e0a (patch) | |
| tree | 9e0d47d6098e87b913a44348cf3d9811d992f190 /pkg/discord | |
| parent | dfeef2aad6fb25eadc13c12e1caea7160bfe6c3e (diff) | |
| download | jinx-4cbe353d824d5064ee6f72f4693ee3c759b50e0a.tar.zst jinx-4cbe353d824d5064ee6f72f4693ee3c759b50e0a.zip | |
Successful Identify
Diffstat (limited to 'pkg/discord')
| -rw-r--r-- | pkg/discord/discord.go | 106 | ||||
| -rw-r--r-- | pkg/discord/entities.go | 15 | ||||
| -rw-r--r-- | pkg/discord/payloads.go | 47 |
3 files changed, 157 insertions, 11 deletions
diff --git a/pkg/discord/discord.go b/pkg/discord/discord.go index 13de020..1fefe6c 100644 --- a/pkg/discord/discord.go +++ b/pkg/discord/discord.go @@ -2,10 +2,14 @@ package discord import ( "bytes" + "context" "encoding/json" "errors" "fmt" + "log" + "math/rand" "net/http" + "time" "github.com/gorilla/websocket" ) @@ -15,41 +19,59 @@ const USER_AGENT = "DiscordBot (https://jinx.rnrd.eu/, v0.0.0) Jinx" type Discord struct { Token string + Conn *websocket.Conn } func NewClient(token string) *Discord { return &Discord{ Token: token, + Conn: nil, } } -func (d *Discord) Connect() error { - gatewayURL, err := d.gateway() +func (d *Discord) Connect(ctx context.Context) error { + gatewayURL, err := d.getGateway() if err != nil { return err } - fmt.Printf("Gateway: %s\n", gatewayURL) + fmt.Printf("gateway: %s\n", gatewayURL) connectHeader := http.Header{} - conn, _, err := websocket.DefaultDialer.Dial(gatewayURL, connectHeader) + d.Conn, _, err = websocket.DefaultDialer.Dial(gatewayURL, connectHeader) if err != nil { return err } - defer conn.Close() - - messageType, message, err := conn.ReadMessage() - if err != nil { + var helloMsg GatewayPayload[GatewayHelloMsg] + if err = d.Conn.ReadJSON(&helloMsg); err != nil { return err } - fmt.Printf("Type: %d, Message: %s\n", messageType, message) + fmt.Printf("connection response Payload: %+v\n", helloMsg) + + if helloMsg.Op != GATEWAY_OP_HELLO { + return fmt.Errorf("expected opcode %d, got %d", GATEWAY_OP_HELLO, helloMsg.Op) + } + + go d.startHeartbeat(ctx, helloMsg.Data.HeartbeatInterval) + + if err = d.identify(); err != nil { + return err + } return nil } -func (d *Discord) gateway() (string, error) { +func (d *Discord) Disconnect() error { + if d.Conn == nil { + return errors.New("not connected") + } + + return d.Conn.Close() +} + +func (d *Discord) getGateway() (string, error) { url := DISCORD_URl + "gateway" req, err := http.NewRequest("GET", url, nil) @@ -77,7 +99,7 @@ func (d *Discord) gateway() (string, error) { switch resp.StatusCode { case 200: default: - return "", errors.New("Gateway response status code: " + resp.Status) + return "", errors.New("gateway response status code: " + resp.Status) } body := struct { @@ -92,3 +114,65 @@ func (d *Discord) gateway() (string, error) { url = body.URL + "?v=9&encoding=json" return url, nil } + +func (d *Discord) startHeartbeat(ctx context.Context, interval uint64) { + // REF: heartbeat_interval * jitter + jitter := rand.Intn(int(interval)) + time.Sleep(time.Duration(jitter) * time.Millisecond) + + ticker := time.NewTicker(time.Duration(interval) * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + fmt.Println("sending heartbeat.") + + msg := GatewayPayload[any]{ + Op: GATEWAY_OP_HEARTBEAT, + } + + if err := d.Conn.WriteJSON(msg); err != nil { + log.Fatalf("error sending heartbeat: %s\n", err) + } + } + } +} + +func (d *Discord) identify() error { + msg := GatewayPayload[GatewayIdentifyMsg]{ + Op: GATEWAY_OP_IDENTIFY, + Data: GatewayIdentifyMsg{ + Token: d.Token, + Properties: GatewayIdentifyProperties{ + OS: "linux", + Browser: "jinx", + Device: "jinx", + }, + }, + Sequence: 0, + } + + if err := d.Conn.WriteJSON(msg); err != nil { + return err + } + + var res GatewayPayload[GatewayReadyMsg] + if err := d.Conn.ReadJSON(&res); err != nil { + return err + } + + fmt.Printf("identify response payload: %+v\n", res) + + if res.Op != GATEWAY_OP_DISPATCH { + return fmt.Errorf("expected opcode %d, got %d", GATEWAY_OP_DISPATCH, res.Op) + } + + if res.EventName != "READY" { + return fmt.Errorf("expected event name %s, got %s", "READY", res.EventName) + } + + return nil +} diff --git a/pkg/discord/entities.go b/pkg/discord/entities.go new file mode 100644 index 0000000..85965bf --- /dev/null +++ b/pkg/discord/entities.go @@ -0,0 +1,15 @@ +package discord + +type Snowflake string + +type User struct { + ID Snowflake `json:"id"` + Username string `json:"username"` + Discriminator string `json:"discriminator"` +} + +type Guild struct { + ID Snowflake `json:"id"` + Name string `json:"name"` + Unavailable bool `json:"unavailable"` +} diff --git a/pkg/discord/payloads.go b/pkg/discord/payloads.go new file mode 100644 index 0000000..d7ab6d4 --- /dev/null +++ b/pkg/discord/payloads.go @@ -0,0 +1,47 @@ +package discord + +type GatewayOp uint8 + +const ( + GATEWAY_OP_DISPATCH GatewayOp = 0 + GATEWAY_OP_HEARTBEAT GatewayOp = 1 + GATEWAY_OP_IDENTIFY GatewayOp = 2 + GATEWAY_OP_PRESENCE_UPDATE GatewayOp = 3 + GATEWAY_OP_VOICE_STATE_UPDATE GatewayOp = 4 + GATEWAY_OP_RESUME GatewayOp = 6 + GATEWAY_OP_RECONNECT GatewayOp = 7 + GATEWAY_OP_REQUEST_GUILD_MEMBERS GatewayOp = 8 + GATEWAY_OP_INVALID_SESSION GatewayOp = 9 + GATEWAY_OP_HELLO GatewayOp = 10 + GATEWAY_OP_HEARTBEAT_ACK GatewayOp = 11 +) + +type GatewayPayload[D any] struct { + Op GatewayOp `json:"op"` + Data D `json:"d,omitempty"` + Sequence uint64 `json:"s,omitempty"` + EventName string `json:"t,omitempty"` +} + +type GatewayIdentifyMsg struct { + Token string `json:"token"` + Intents uint64 `json:"intents"` + Properties GatewayIdentifyProperties `json:"properties"` +} +type GatewayHelloMsg struct { + HeartbeatInterval uint64 `json:"heartbeat_interval"` +} + +type GatewayReadyMsg struct { + Version uint64 `json:"v"` + User User `json:"user"` + Guilds []Guild `json:"guilds"` + SessionID string `json:"session_id"` + Shard []int `json:"shard"` +} + +type GatewayIdentifyProperties struct { + OS string `json:"$os"` + Browser string `json:"$browser"` + Device string `json:"$device"` +} |
