1// Discordgo - Discord bindings for Go 2// Available at https://github.com/bwmarrin/discordgo 3 4// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved. 5// Use of this source code is governed by a BSD-style 6// license that can be found in the LICENSE file. 7 8// This file contains code related to Discord voice suppport 9 10package discordgo 11 12import ( 13 "encoding/binary" 14 "encoding/json" 15 "fmt" 16 "net" 17 "strconv" 18 "strings" 19 "sync" 20 "time" 21 22 "github.com/gorilla/websocket" 23 "golang.org/x/crypto/nacl/secretbox" 24) 25 26// ------------------------------------------------------------------------------------------------ 27// Code related to both VoiceConnection Websocket and UDP connections. 28// ------------------------------------------------------------------------------------------------ 29 30// A VoiceConnection struct holds all the data and functions related to a Discord Voice Connection. 31type VoiceConnection struct { 32 sync.RWMutex 33 34 Debug bool // If true, print extra logging -- DEPRECATED 35 LogLevel int 36 Ready bool // If true, voice is ready to send/receive audio 37 UserID string 38 GuildID string 39 ChannelID string 40 deaf bool 41 mute bool 42 speaking bool 43 reconnecting bool // If true, voice connection is trying to reconnect 44 45 OpusSend chan []byte // Chan for sending opus audio 46 OpusRecv chan *Packet // Chan for receiving opus audio 47 48 wsConn *websocket.Conn 49 wsMutex sync.Mutex 50 udpConn *net.UDPConn 51 session *Session 52 53 sessionID string 54 token string 55 endpoint string 56 57 // Used to send a close signal to goroutines 58 close chan struct{} 59 60 // Used to allow blocking until connected 61 connected chan bool 62 63 // Used to pass the sessionid from onVoiceStateUpdate 64 // sessionRecv chan string UNUSED ATM 65 66 op4 voiceOP4 67 op2 voiceOP2 68 69 voiceSpeakingUpdateHandlers []VoiceSpeakingUpdateHandler 70} 71 72// VoiceSpeakingUpdateHandler type provides a function definition for the 73// VoiceSpeakingUpdate event 74type VoiceSpeakingUpdateHandler func(vc *VoiceConnection, vs *VoiceSpeakingUpdate) 75 76// Speaking sends a speaking notification to Discord over the voice websocket. 77// This must be sent as true prior to sending audio and should be set to false 78// once finished sending audio. 79// b : Send true if speaking, false if not. 80func (v *VoiceConnection) Speaking(b bool) (err error) { 81 82 v.log(LogDebug, "called (%t)", b) 83 84 type voiceSpeakingData struct { 85 Speaking bool `json:"speaking"` 86 Delay int `json:"delay"` 87 } 88 89 type voiceSpeakingOp struct { 90 Op int `json:"op"` // Always 5 91 Data voiceSpeakingData `json:"d"` 92 } 93 94 if v.wsConn == nil { 95 return fmt.Errorf("no VoiceConnection websocket") 96 } 97 98 data := voiceSpeakingOp{5, voiceSpeakingData{b, 0}} 99 v.wsMutex.Lock() 100 err = v.wsConn.WriteJSON(data) 101 v.wsMutex.Unlock() 102 103 v.Lock() 104 defer v.Unlock() 105 if err != nil { 106 v.speaking = false 107 v.log(LogError, "Speaking() write json error, %s", err) 108 return 109 } 110 111 v.speaking = b 112 113 return 114} 115 116// ChangeChannel sends Discord a request to change channels within a Guild 117// !!! NOTE !!! This function may be removed in favour of just using ChannelVoiceJoin 118func (v *VoiceConnection) ChangeChannel(channelID string, mute, deaf bool) (err error) { 119 120 v.log(LogInformational, "called") 121 122 data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, &channelID, mute, deaf}} 123 v.wsMutex.Lock() 124 err = v.session.wsConn.WriteJSON(data) 125 v.wsMutex.Unlock() 126 if err != nil { 127 return 128 } 129 v.ChannelID = channelID 130 v.deaf = deaf 131 v.mute = mute 132 v.speaking = false 133 134 return 135} 136 137// Disconnect disconnects from this voice channel and closes the websocket 138// and udp connections to Discord. 139func (v *VoiceConnection) Disconnect() (err error) { 140 141 // Send a OP4 with a nil channel to disconnect 142 v.Lock() 143 if v.sessionID != "" { 144 data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}} 145 v.session.wsMutex.Lock() 146 err = v.session.wsConn.WriteJSON(data) 147 v.session.wsMutex.Unlock() 148 v.sessionID = "" 149 } 150 v.Unlock() 151 152 // Close websocket and udp connections 153 v.Close() 154 155 v.log(LogInformational, "Deleting VoiceConnection %s", v.GuildID) 156 157 v.session.Lock() 158 delete(v.session.VoiceConnections, v.GuildID) 159 v.session.Unlock() 160 161 return 162} 163 164// Close closes the voice ws and udp connections 165func (v *VoiceConnection) Close() { 166 167 v.log(LogInformational, "called") 168 169 v.Lock() 170 defer v.Unlock() 171 172 v.Ready = false 173 v.speaking = false 174 175 if v.close != nil { 176 v.log(LogInformational, "closing v.close") 177 close(v.close) 178 v.close = nil 179 } 180 181 if v.udpConn != nil { 182 v.log(LogInformational, "closing udp") 183 err := v.udpConn.Close() 184 if err != nil { 185 v.log(LogError, "error closing udp connection, %s", err) 186 } 187 v.udpConn = nil 188 } 189 190 if v.wsConn != nil { 191 v.log(LogInformational, "sending close frame") 192 193 // To cleanly close a connection, a client should send a close 194 // frame and wait for the server to close the connection. 195 v.wsMutex.Lock() 196 err := v.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 197 v.wsMutex.Unlock() 198 if err != nil { 199 v.log(LogError, "error closing websocket, %s", err) 200 } 201 202 // TODO: Wait for Discord to actually close the connection. 203 time.Sleep(1 * time.Second) 204 205 v.log(LogInformational, "closing websocket") 206 err = v.wsConn.Close() 207 if err != nil { 208 v.log(LogError, "error closing websocket, %s", err) 209 } 210 211 v.wsConn = nil 212 } 213} 214 215// AddHandler adds a Handler for VoiceSpeakingUpdate events. 216func (v *VoiceConnection) AddHandler(h VoiceSpeakingUpdateHandler) { 217 v.Lock() 218 defer v.Unlock() 219 220 v.voiceSpeakingUpdateHandlers = append(v.voiceSpeakingUpdateHandlers, h) 221} 222 223// VoiceSpeakingUpdate is a struct for a VoiceSpeakingUpdate event. 224type VoiceSpeakingUpdate struct { 225 UserID string `json:"user_id"` 226 SSRC int `json:"ssrc"` 227 Speaking bool `json:"speaking"` 228} 229 230// ------------------------------------------------------------------------------------------------ 231// Unexported Internal Functions Below. 232// ------------------------------------------------------------------------------------------------ 233 234// A voiceOP4 stores the data for the voice operation 4 websocket event 235// which provides us with the NaCl SecretBox encryption key 236type voiceOP4 struct { 237 SecretKey [32]byte `json:"secret_key"` 238 Mode string `json:"mode"` 239} 240 241// A voiceOP2 stores the data for the voice operation 2 websocket event 242// which is sort of like the voice READY packet 243type voiceOP2 struct { 244 SSRC uint32 `json:"ssrc"` 245 Port int `json:"port"` 246 Modes []string `json:"modes"` 247 HeartbeatInterval time.Duration `json:"heartbeat_interval"` 248 IP string `json:"ip"` 249} 250 251// WaitUntilConnected waits for the Voice Connection to 252// become ready, if it does not become ready it returns an err 253func (v *VoiceConnection) waitUntilConnected() error { 254 255 v.log(LogInformational, "called") 256 257 i := 0 258 for { 259 v.RLock() 260 ready := v.Ready 261 v.RUnlock() 262 if ready { 263 return nil 264 } 265 266 if i > 10 { 267 return fmt.Errorf("timeout waiting for voice") 268 } 269 270 time.Sleep(1 * time.Second) 271 i++ 272 } 273} 274 275// Open opens a voice connection. This should be called 276// after VoiceChannelJoin is used and the data VOICE websocket events 277// are captured. 278func (v *VoiceConnection) open() (err error) { 279 280 v.log(LogInformational, "called") 281 282 v.Lock() 283 defer v.Unlock() 284 285 // Don't open a websocket if one is already open 286 if v.wsConn != nil { 287 v.log(LogWarning, "refusing to overwrite non-nil websocket") 288 return 289 } 290 291 // TODO temp? loop to wait for the SessionID 292 i := 0 293 for { 294 if v.sessionID != "" { 295 break 296 } 297 if i > 20 { // only loop for up to 1 second total 298 return fmt.Errorf("did not receive voice Session ID in time") 299 } 300 time.Sleep(50 * time.Millisecond) 301 i++ 302 } 303 304 // Connect to VoiceConnection Websocket 305 vg := "wss://" + strings.TrimSuffix(v.endpoint, ":80") 306 v.log(LogInformational, "connecting to voice endpoint %s", vg) 307 v.wsConn, _, err = websocket.DefaultDialer.Dial(vg, nil) 308 if err != nil { 309 v.log(LogWarning, "error connecting to voice endpoint %s, %s", vg, err) 310 v.log(LogDebug, "voice struct: %#v\n", v) 311 return 312 } 313 314 type voiceHandshakeData struct { 315 ServerID string `json:"server_id"` 316 UserID string `json:"user_id"` 317 SessionID string `json:"session_id"` 318 Token string `json:"token"` 319 } 320 type voiceHandshakeOp struct { 321 Op int `json:"op"` // Always 0 322 Data voiceHandshakeData `json:"d"` 323 } 324 data := voiceHandshakeOp{0, voiceHandshakeData{v.GuildID, v.UserID, v.sessionID, v.token}} 325 326 err = v.wsConn.WriteJSON(data) 327 if err != nil { 328 v.log(LogWarning, "error sending init packet, %s", err) 329 return 330 } 331 332 v.close = make(chan struct{}) 333 go v.wsListen(v.wsConn, v.close) 334 335 // add loop/check for Ready bool here? 336 // then return false if not ready? 337 // but then wsListen will also err. 338 339 return 340} 341 342// wsListen listens on the voice websocket for messages and passes them 343// to the voice event handler. This is automatically called by the Open func 344func (v *VoiceConnection) wsListen(wsConn *websocket.Conn, close <-chan struct{}) { 345 346 v.log(LogInformational, "called") 347 348 for { 349 _, message, err := v.wsConn.ReadMessage() 350 if err != nil { 351 // 4014 indicates a manual disconnection by someone in the guild; 352 // we shouldn't reconnect. 353 if websocket.IsCloseError(err, 4014) { 354 v.log(LogInformational, "received 4014 manual disconnection") 355 356 // Abandon the voice WS connection 357 v.Lock() 358 v.wsConn = nil 359 v.Unlock() 360 361 v.session.Lock() 362 delete(v.session.VoiceConnections, v.GuildID) 363 v.session.Unlock() 364 365 v.Close() 366 367 return 368 } 369 370 // Detect if we have been closed manually. If a Close() has already 371 // happened, the websocket we are listening on will be different to the 372 // current session. 373 v.RLock() 374 sameConnection := v.wsConn == wsConn 375 v.RUnlock() 376 if sameConnection { 377 378 v.log(LogError, "voice endpoint %s websocket closed unexpectantly, %s", v.endpoint, err) 379 380 // Start reconnect goroutine then exit. 381 go v.reconnect() 382 } 383 return 384 } 385 386 // Pass received message to voice event handler 387 select { 388 case <-close: 389 return 390 default: 391 go v.onEvent(message) 392 } 393 } 394} 395 396// wsEvent handles any voice websocket events. This is only called by the 397// wsListen() function. 398func (v *VoiceConnection) onEvent(message []byte) { 399 400 v.log(LogDebug, "received: %s", string(message)) 401 402 var e Event 403 if err := json.Unmarshal(message, &e); err != nil { 404 v.log(LogError, "unmarshall error, %s", err) 405 return 406 } 407 408 switch e.Operation { 409 410 case 2: // READY 411 412 if err := json.Unmarshal(e.RawData, &v.op2); err != nil { 413 v.log(LogError, "OP2 unmarshall error, %s, %s", err, string(e.RawData)) 414 return 415 } 416 417 // Start the voice websocket heartbeat to keep the connection alive 418 go v.wsHeartbeat(v.wsConn, v.close, v.op2.HeartbeatInterval) 419 // TODO monitor a chan/bool to verify this was successful 420 421 // Start the UDP connection 422 err := v.udpOpen() 423 if err != nil { 424 v.log(LogError, "error opening udp connection, %s", err) 425 return 426 } 427 428 // Start the opusSender. 429 // TODO: Should we allow 48000/960 values to be user defined? 430 if v.OpusSend == nil { 431 v.OpusSend = make(chan []byte, 2) 432 } 433 go v.opusSender(v.udpConn, v.close, v.OpusSend, 48000, 960) 434 435 // Start the opusReceiver 436 if !v.deaf { 437 if v.OpusRecv == nil { 438 v.OpusRecv = make(chan *Packet, 2) 439 } 440 441 go v.opusReceiver(v.udpConn, v.close, v.OpusRecv) 442 } 443 444 return 445 446 case 3: // HEARTBEAT response 447 // add code to use this to track latency? 448 return 449 450 case 4: // udp encryption secret key 451 v.Lock() 452 defer v.Unlock() 453 454 v.op4 = voiceOP4{} 455 if err := json.Unmarshal(e.RawData, &v.op4); err != nil { 456 v.log(LogError, "OP4 unmarshall error, %s, %s", err, string(e.RawData)) 457 return 458 } 459 return 460 461 case 5: 462 if len(v.voiceSpeakingUpdateHandlers) == 0 { 463 return 464 } 465 466 voiceSpeakingUpdate := &VoiceSpeakingUpdate{} 467 if err := json.Unmarshal(e.RawData, voiceSpeakingUpdate); err != nil { 468 v.log(LogError, "OP5 unmarshall error, %s, %s", err, string(e.RawData)) 469 return 470 } 471 472 for _, h := range v.voiceSpeakingUpdateHandlers { 473 h(v, voiceSpeakingUpdate) 474 } 475 476 default: 477 v.log(LogDebug, "unknown voice operation, %d, %s", e.Operation, string(e.RawData)) 478 } 479 480 return 481} 482 483type voiceHeartbeatOp struct { 484 Op int `json:"op"` // Always 3 485 Data int `json:"d"` 486} 487 488// NOTE :: When a guild voice server changes how do we shut this down 489// properly, so a new connection can be setup without fuss? 490// 491// wsHeartbeat sends regular heartbeats to voice Discord so it knows the client 492// is still connected. If you do not send these heartbeats Discord will 493// disconnect the websocket connection after a few seconds. 494func (v *VoiceConnection) wsHeartbeat(wsConn *websocket.Conn, close <-chan struct{}, i time.Duration) { 495 496 if close == nil || wsConn == nil { 497 return 498 } 499 500 var err error 501 ticker := time.NewTicker(i * time.Millisecond) 502 defer ticker.Stop() 503 for { 504 v.log(LogDebug, "sending heartbeat packet") 505 v.wsMutex.Lock() 506 err = wsConn.WriteJSON(voiceHeartbeatOp{3, int(time.Now().Unix())}) 507 v.wsMutex.Unlock() 508 if err != nil { 509 v.log(LogError, "error sending heartbeat to voice endpoint %s, %s", v.endpoint, err) 510 return 511 } 512 513 select { 514 case <-ticker.C: 515 // continue loop and send heartbeat 516 case <-close: 517 return 518 } 519 } 520} 521 522// ------------------------------------------------------------------------------------------------ 523// Code related to the VoiceConnection UDP connection 524// ------------------------------------------------------------------------------------------------ 525 526type voiceUDPData struct { 527 Address string `json:"address"` // Public IP of machine running this code 528 Port uint16 `json:"port"` // UDP Port of machine running this code 529 Mode string `json:"mode"` // always "xsalsa20_poly1305" 530} 531 532type voiceUDPD struct { 533 Protocol string `json:"protocol"` // Always "udp" ? 534 Data voiceUDPData `json:"data"` 535} 536 537type voiceUDPOp struct { 538 Op int `json:"op"` // Always 1 539 Data voiceUDPD `json:"d"` 540} 541 542// udpOpen opens a UDP connection to the voice server and completes the 543// initial required handshake. This connection is left open in the session 544// and can be used to send or receive audio. This should only be called 545// from voice.wsEvent OP2 546func (v *VoiceConnection) udpOpen() (err error) { 547 548 v.Lock() 549 defer v.Unlock() 550 551 if v.wsConn == nil { 552 return fmt.Errorf("nil voice websocket") 553 } 554 555 if v.udpConn != nil { 556 return fmt.Errorf("udp connection already open") 557 } 558 559 if v.close == nil { 560 return fmt.Errorf("nil close channel") 561 } 562 563 if v.endpoint == "" { 564 return fmt.Errorf("empty endpoint") 565 } 566 567 host := v.op2.IP + ":" + strconv.Itoa(v.op2.Port) 568 addr, err := net.ResolveUDPAddr("udp", host) 569 if err != nil { 570 v.log(LogWarning, "error resolving udp host %s, %s", host, err) 571 return 572 } 573 574 v.log(LogInformational, "connecting to udp addr %s", addr.String()) 575 v.udpConn, err = net.DialUDP("udp", nil, addr) 576 if err != nil { 577 v.log(LogWarning, "error connecting to udp addr %s, %s", addr.String(), err) 578 return 579 } 580 581 // Create a 70 byte array and put the SSRC code from the Op 2 VoiceConnection event 582 // into it. Then send that over the UDP connection to Discord 583 sb := make([]byte, 70) 584 binary.BigEndian.PutUint32(sb, v.op2.SSRC) 585 _, err = v.udpConn.Write(sb) 586 if err != nil { 587 v.log(LogWarning, "udp write error to %s, %s", addr.String(), err) 588 return 589 } 590 591 // Create a 70 byte array and listen for the initial handshake response 592 // from Discord. Once we get it parse the IP and PORT information out 593 // of the response. This should be our public IP and PORT as Discord 594 // saw us. 595 rb := make([]byte, 70) 596 rlen, _, err := v.udpConn.ReadFromUDP(rb) 597 if err != nil { 598 v.log(LogWarning, "udp read error, %s, %s", addr.String(), err) 599 return 600 } 601 602 if rlen < 70 { 603 v.log(LogWarning, "received udp packet too small") 604 return fmt.Errorf("received udp packet too small") 605 } 606 607 // Loop over position 4 through 20 to grab the IP address 608 // Should never be beyond position 20. 609 var ip string 610 for i := 4; i < 20; i++ { 611 if rb[i] == 0 { 612 break 613 } 614 ip += string(rb[i]) 615 } 616 617 // Grab port from position 68 and 69 618 port := binary.BigEndian.Uint16(rb[68:70]) 619 620 // Take the data from above and send it back to Discord to finalize 621 // the UDP connection handshake. 622 data := voiceUDPOp{1, voiceUDPD{"udp", voiceUDPData{ip, port, "xsalsa20_poly1305"}}} 623 624 v.wsMutex.Lock() 625 err = v.wsConn.WriteJSON(data) 626 v.wsMutex.Unlock() 627 if err != nil { 628 v.log(LogWarning, "udp write error, %#v, %s", data, err) 629 return 630 } 631 632 // start udpKeepAlive 633 go v.udpKeepAlive(v.udpConn, v.close, 5*time.Second) 634 // TODO: find a way to check that it fired off okay 635 636 return 637} 638 639// udpKeepAlive sends a udp packet to keep the udp connection open 640// This is still a bit of a "proof of concept" 641func (v *VoiceConnection) udpKeepAlive(udpConn *net.UDPConn, close <-chan struct{}, i time.Duration) { 642 643 if udpConn == nil || close == nil { 644 return 645 } 646 647 var err error 648 var sequence uint64 649 650 packet := make([]byte, 8) 651 652 ticker := time.NewTicker(i) 653 defer ticker.Stop() 654 for { 655 656 binary.LittleEndian.PutUint64(packet, sequence) 657 sequence++ 658 659 _, err = udpConn.Write(packet) 660 if err != nil { 661 v.log(LogError, "write error, %s", err) 662 return 663 } 664 665 select { 666 case <-ticker.C: 667 // continue loop and send keepalive 668 case <-close: 669 return 670 } 671 } 672} 673 674// opusSender will listen on the given channel and send any 675// pre-encoded opus audio to Discord. Supposedly. 676func (v *VoiceConnection) opusSender(udpConn *net.UDPConn, close <-chan struct{}, opus <-chan []byte, rate, size int) { 677 678 if udpConn == nil || close == nil { 679 return 680 } 681 682 // VoiceConnection is now ready to receive audio packets 683 // TODO: this needs reviewed as I think there must be a better way. 684 v.Lock() 685 v.Ready = true 686 v.Unlock() 687 defer func() { 688 v.Lock() 689 v.Ready = false 690 v.Unlock() 691 }() 692 693 var sequence uint16 694 var timestamp uint32 695 var recvbuf []byte 696 var ok bool 697 udpHeader := make([]byte, 12) 698 var nonce [24]byte 699 700 // build the parts that don't change in the udpHeader 701 udpHeader[0] = 0x80 702 udpHeader[1] = 0x78 703 binary.BigEndian.PutUint32(udpHeader[8:], v.op2.SSRC) 704 705 // start a send loop that loops until buf chan is closed 706 ticker := time.NewTicker(time.Millisecond * time.Duration(size/(rate/1000))) 707 defer ticker.Stop() 708 for { 709 710 // Get data from chan. If chan is closed, return. 711 select { 712 case <-close: 713 return 714 case recvbuf, ok = <-opus: 715 if !ok { 716 return 717 } 718 // else, continue loop 719 } 720 721 v.RLock() 722 speaking := v.speaking 723 v.RUnlock() 724 if !speaking { 725 err := v.Speaking(true) 726 if err != nil { 727 v.log(LogError, "error sending speaking packet, %s", err) 728 } 729 } 730 731 // Add sequence and timestamp to udpPacket 732 binary.BigEndian.PutUint16(udpHeader[2:], sequence) 733 binary.BigEndian.PutUint32(udpHeader[4:], timestamp) 734 735 // encrypt the opus data 736 copy(nonce[:], udpHeader) 737 v.RLock() 738 sendbuf := secretbox.Seal(udpHeader, recvbuf, &nonce, &v.op4.SecretKey) 739 v.RUnlock() 740 741 // block here until we're exactly at the right time :) 742 // Then send rtp audio packet to Discord over UDP 743 select { 744 case <-close: 745 return 746 case <-ticker.C: 747 // continue 748 } 749 _, err := udpConn.Write(sendbuf) 750 751 if err != nil { 752 v.log(LogError, "udp write error, %s", err) 753 v.log(LogDebug, "voice struct: %#v\n", v) 754 return 755 } 756 757 if (sequence) == 0xFFFF { 758 sequence = 0 759 } else { 760 sequence++ 761 } 762 763 if (timestamp + uint32(size)) >= 0xFFFFFFFF { 764 timestamp = 0 765 } else { 766 timestamp += uint32(size) 767 } 768 } 769} 770 771// A Packet contains the headers and content of a received voice packet. 772type Packet struct { 773 SSRC uint32 774 Sequence uint16 775 Timestamp uint32 776 Type []byte 777 Opus []byte 778 PCM []int16 779} 780 781// opusReceiver listens on the UDP socket for incoming packets 782// and sends them across the given channel 783// NOTE :: This function may change names later. 784func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct{}, c chan *Packet) { 785 786 if udpConn == nil || close == nil { 787 return 788 } 789 790 recvbuf := make([]byte, 1024) 791 var nonce [24]byte 792 793 for { 794 rlen, err := udpConn.Read(recvbuf) 795 if err != nil { 796 // Detect if we have been closed manually. If a Close() has already 797 // happened, the udp connection we are listening on will be different 798 // to the current session. 799 v.RLock() 800 sameConnection := v.udpConn == udpConn 801 v.RUnlock() 802 if sameConnection { 803 804 v.log(LogError, "udp read error, %s, %s", v.endpoint, err) 805 v.log(LogDebug, "voice struct: %#v\n", v) 806 807 go v.reconnect() 808 } 809 return 810 } 811 812 select { 813 case <-close: 814 return 815 default: 816 // continue loop 817 } 818 819 // For now, skip anything except audio. 820 if rlen < 12 || (recvbuf[0] != 0x80 && recvbuf[0] != 0x90) { 821 continue 822 } 823 824 // build a audio packet struct 825 p := Packet{} 826 p.Type = recvbuf[0:2] 827 p.Sequence = binary.BigEndian.Uint16(recvbuf[2:4]) 828 p.Timestamp = binary.BigEndian.Uint32(recvbuf[4:8]) 829 p.SSRC = binary.BigEndian.Uint32(recvbuf[8:12]) 830 // decrypt opus data 831 copy(nonce[:], recvbuf[0:12]) 832 p.Opus, _ = secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey) 833 834 if len(p.Opus) > 8 && recvbuf[0] == 0x90 { 835 // Extension bit is set, first 8 bytes is the extended header 836 p.Opus = p.Opus[8:] 837 } 838 839 if c != nil { 840 select { 841 case c <- &p: 842 case <-close: 843 return 844 } 845 } 846 } 847} 848 849// Reconnect will close down a voice connection then immediately try to 850// reconnect to that session. 851// NOTE : This func is messy and a WIP while I find what works. 852// It will be cleaned up once a proven stable option is flushed out. 853// aka: this is ugly shit code, please don't judge too harshly. 854func (v *VoiceConnection) reconnect() { 855 856 v.log(LogInformational, "called") 857 858 v.Lock() 859 if v.reconnecting { 860 v.log(LogInformational, "already reconnecting to channel %s, exiting", v.ChannelID) 861 v.Unlock() 862 return 863 } 864 v.reconnecting = true 865 v.Unlock() 866 867 defer func() { v.reconnecting = false }() 868 869 // Close any currently open connections 870 v.Close() 871 872 wait := time.Duration(1) 873 for { 874 875 <-time.After(wait * time.Second) 876 wait *= 2 877 if wait > 600 { 878 wait = 600 879 } 880 881 if v.session.DataReady == false || v.session.wsConn == nil { 882 v.log(LogInformational, "cannot reconnect to channel %s with unready session", v.ChannelID) 883 continue 884 } 885 886 v.log(LogInformational, "trying to reconnect to channel %s", v.ChannelID) 887 888 _, err := v.session.ChannelVoiceJoin(v.GuildID, v.ChannelID, v.mute, v.deaf) 889 if err == nil { 890 v.log(LogInformational, "successfully reconnected to channel %s", v.ChannelID) 891 return 892 } 893 894 v.log(LogInformational, "error reconnecting to channel %s, %s", v.ChannelID, err) 895 896 // if the reconnect above didn't work lets just send a disconnect 897 // packet to reset things. 898 // Send a OP4 with a nil channel to disconnect 899 data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}} 900 v.session.wsMutex.Lock() 901 err = v.session.wsConn.WriteJSON(data) 902 v.session.wsMutex.Unlock() 903 if err != nil { 904 v.log(LogError, "error sending disconnect packet, %s", err) 905 } 906 907 } 908} 909