diff --git a/discord/discord.go b/discord/discord.go deleted file mode 100644 index 3c429d4..0000000 --- a/discord/discord.go +++ /dev/null @@ -1,272 +0,0 @@ -package discord - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log" - "net/http" - "os" - "time" - - "github.com/gorilla/websocket" - "github.com/kataras/go-events" - "github.com/tidwall/gjson" -) - -const ( - GATEWAYBOTURL = "https://discord.com/api/gateway/bot" -) - -const ( - PermissionVoiceConnect = 0x0000000000100000 - PermissionVoiceSpeak = 0x0000000000200000 - PermissionSendMessages = 0x0000000000000800 -) - -type Connection struct { - events events.EventEmmiter - httpClient *http.Client - dialer *websocket.Dialer - conn *websocket.Conn - token string - seq int - ready chan bool - vstateUpdate chan bool - vserverUpdate chan bool -} - -type VoiceConnection struct { - d *Connection - conn *websocket.Conn - sid string - e string -} - -type VoiceStateUpdate struct { - Op int `json:"op"` - Data struct { - Guild string `json:"guild_id"` - Channel string `json:"channel_id"` - Mute bool `json:"self_mute"` - Deaf bool `json:"self_dead"` - } `json:"d"` -} - -type Identify struct { - Op int `json:"op"` - - Data struct { - Token string `json:"token"` - Intents int `json:"intents"` - - Properties struct { - OS string `json:"os"` - Browser string `json:"browser"` - Device string `json:"device"` - } `json:"properties"` - } `json:"d"` -} - -func NewConnection(token string) (*Connection, error) { - conn := new(Connection) - - conn.token = token - - conn.ready = make(chan bool) - conn.vserverUpdate = make(chan bool) - conn.vstateUpdate = make(chan bool) - - conn.events = events.New() - conn.events.On("READY", conn.eventready) - - conn.dialer = &websocket.Dialer{ - HandshakeTimeout: 45 * time.Second, - } - - conn.httpClient = &http.Client{} - gw, err := conn.GetGateway() - if err != nil { - return nil, err - } - - conn.conn, _, err = conn.dialer.Dial(gw, nil) - if err != nil { - return nil, err - } - - go func() { - for { - _, b, err := conn.conn.ReadMessage() - if err != nil { - log.Println(err) - break - } - - go conn.handleGatewayEvent(b) - } - }() - - // Identify - id := new(Identify) - id.Op = 2 - id.Data.Token = token - id.Data.Intents = PermissionVoiceConnect | PermissionVoiceSpeak | PermissionSendMessages - id.Data.Properties.OS = "linux" - id.Data.Properties.Browser = "dndmusicbot/0.0.1" - id.Data.Properties.Device = "dndmusicbot" - - json.NewEncoder(os.Stdout).Encode(id) - - err = conn.conn.WriteJSON(id) - if err != nil { - conn.conn.Close() - return nil, err - } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // Wait for Ready! - select { - case <-ctx.Done(): - return nil, err - case <-conn.ready: - } - - return conn, nil -} - -func (c *Connection) NewVoiceConnection(guild string, channel string) (*VoiceConnection, error) { - vc := new(VoiceConnection) - vc.d = c - - c.events.On("VOICE_STATE_UPDATE", vc.voiceStateUpdate) - c.events.On("VOICE_SERVER_UDPATE", vc.voiceServerUpdate) - - msg := new(VoiceStateUpdate) - msg.Op = 4 - msg.Data.Guild = guild - msg.Data.Channel = channel - msg.Data.Mute = false - msg.Data.Deaf = true - - c.conn.WriteJSON(msg) - - var state, server bool - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // We have to wait for both a vserverupdate and a vstateupdate before we continue. - for !state && !server { - select { - case <-ctx.Done(): - return nil, ctx.Err() - case <-c.vserverUpdate: - server = true - case <-c.vstateUpdate: - state = true - } - } - - // We should have gotten all the information by now.. continue.. - - return vc, nil -} - -func (vc *VoiceConnection) voiceStateUpdate(payload ...interface{}) { - vc.d.vstateUpdate <- true -} - -func (vc *VoiceConnection) voiceServerUpdate(payload ...interface{}) { - vc.d.vserverUpdate <- true -} - -// Ready event. Contains alot of information regarding our session. -func (c *Connection) eventready(payload ...interface{}) { - c.ready <- true -} - -func (c *Connection) handleGatewayEvent(js []byte) error { - eventdata := gjson.GetManyBytes(js, "op", "d", "s", "t") - - op := eventdata[0] - data := eventdata[1] - seq := eventdata[2] - eventname := eventdata[3] - - if seq.Exists() { - c.seq = int(seq.Int()) - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - c.conn.SetCloseHandler(func(code int, text string) error { - cancel() - return nil - }) - - switch op.Int() { - case 0: - fmt.Printf("Event: %s Data: %+v\n", eventname.String(), data) - c.events.Emit(events.EventName(eventname.String()), data) - case 10: // Hello - hb_interval, err := time.ParseDuration(fmt.Sprintf("%dms", data.Get("heartbeat_interval").Int())) - if err != nil { - return err - } - - log.Printf("We Received a Hello! Starting heartbeat on %.2fs interval\n", hb_interval.Seconds()) - - hb_ticker := time.NewTicker(hb_interval) - for { - select { - case <-ctx.Done(): - break - case <-hb_ticker.C: - msg := make(map[string]interface{}) - msg["op"] = 1 - if c.seq == 0 { - msg["d"] = nil - } else { - msg["d"] = c.seq - } - c.conn.SetWriteDeadline(time.Now().Add(10 * time.Second)) - log.Printf("Sending Heartbeat: %+v\n", msg) - err = c.conn.WriteJSON(msg) - if err != nil { - c.conn.Close() - return err - } - } - } - case 11: - log.Println("Heartbeat ACK!") - default: - log.Printf("%d: %+v\n", op.Int(), data) - } - - return nil -} - -func (c *Connection) GetGateway() (string, error) { - req, err := http.NewRequest("GET", GATEWAYBOTURL, nil) - if err != nil { - return "", err - } - - req.Header.Add("Authorization", "Bot "+c.token) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return "", err - } - - b, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - - return gjson.GetBytes(b, "url").String(), err -}