diff options
Diffstat (limited to 'pkg/discord/discord.go')
| -rw-r--r-- | pkg/discord/discord.go | 106 |
1 files changed, 95 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 +} |
