Merge pull request #141 from b1naryth1ef/feature/multiple-voice-connection
Add the ability to have multiple cross-guild voice connections open.
This commit is contained in:
commit
0dac7777c1
4 changed files with 177 additions and 78 deletions
41
state.go
41
state.go
|
@ -460,6 +460,45 @@ func (s *State) MessageRemove(message *Message) error {
|
||||||
return errors.New("Message not found.")
|
return errors.New("Message not found.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *State) VoiceStateUpdate(update *VoiceStateUpdate) error {
|
||||||
|
var exists bool
|
||||||
|
var guild *Guild
|
||||||
|
|
||||||
|
for _, guild = range s.Guilds {
|
||||||
|
if guild.ID == update.GuildID {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Leaving Channel
|
||||||
|
if update.ChannelID == "" {
|
||||||
|
for i, state := range guild.VoiceStates {
|
||||||
|
if state.UserID == update.UserID {
|
||||||
|
guild.VoiceStates = append(guild.VoiceStates[:i], guild.VoiceStates[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exists := false
|
||||||
|
for _, state := range guild.VoiceStates {
|
||||||
|
if state.UserID == update.UserID {
|
||||||
|
state.ChannelID = update.ChannelID
|
||||||
|
exists = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
guild.VoiceStates = append(guild.VoiceStates, update.VoiceState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Message gets a message by channel and message ID.
|
// Message gets a message by channel and message ID.
|
||||||
func (s *State) Message(channelID, messageID string) (*Message, error) {
|
func (s *State) Message(channelID, messageID string) (*Message, error) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
|
@ -521,6 +560,8 @@ func (s *State) onInterface(se *Session, i interface{}) (err error) {
|
||||||
err = s.MessageAdd(t.Message)
|
err = s.MessageAdd(t.Message)
|
||||||
case *MessageDelete:
|
case *MessageDelete:
|
||||||
err = s.MessageRemove(t.Message)
|
err = s.MessageRemove(t.Message)
|
||||||
|
case *VoiceStateUpdate:
|
||||||
|
err = s.VoiceStateUpdate(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -54,8 +54,8 @@ type Session struct {
|
||||||
// Whether the UDP Connection is ready
|
// Whether the UDP Connection is ready
|
||||||
UDPReady bool
|
UDPReady bool
|
||||||
|
|
||||||
// Stores all details related to voice connections
|
// Stores a mapping of guild id's to VoiceConnections
|
||||||
Voice *Voice
|
VoiceConnections map[string]*VoiceConnection
|
||||||
|
|
||||||
// Managed state object, updated internally with events when
|
// Managed state object, updated internally with events when
|
||||||
// StateEnabled is true.
|
// StateEnabled is true.
|
||||||
|
@ -203,6 +203,7 @@ type VoiceState struct {
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
ChannelID string `json:"channel_id"`
|
ChannelID string `json:"channel_id"`
|
||||||
|
GuildID string `json:"guild_id"`
|
||||||
Suppress bool `json:"suppress"`
|
Suppress bool `json:"suppress"`
|
||||||
SelfMute bool `json:"self_mute"`
|
SelfMute bool `json:"self_mute"`
|
||||||
SelfDeaf bool `json:"self_deaf"`
|
SelfDeaf bool `json:"self_deaf"`
|
||||||
|
|
101
voice.go
101
voice.go
|
@ -12,6 +12,7 @@ package discordgo
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -24,37 +25,45 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Code related to both Voice Websocket and UDP connections.
|
// Code related to both VoiceConnection Websocket and UDP connections.
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// A Voice struct holds all data and functions related to Discord Voice support.
|
// A VoiceConnectionConnection struct holds all the data and functions related to a Discord Voice Connection.
|
||||||
type Voice struct {
|
type VoiceConnection struct {
|
||||||
sync.Mutex // future use
|
sync.Mutex // future use
|
||||||
Ready bool // If true, voice is ready to send/receive audio
|
Ready bool // If true, voice is ready to send/receive audio
|
||||||
Debug bool // If true, print extra logging
|
Debug bool // If true, print extra logging
|
||||||
|
Receive bool // If false, don't try to receive packets
|
||||||
OP2 *voiceOP2 // exported for dgvoice, may change.
|
OP2 *voiceOP2 // exported for dgvoice, may change.
|
||||||
OpusSend chan []byte // Chan for sending opus audio
|
OpusSend chan []byte // Chan for sending opus audio
|
||||||
OpusRecv chan *Packet // Chan for receiving opus audio
|
OpusRecv chan *Packet // Chan for receiving opus audio
|
||||||
|
GuildID string
|
||||||
|
ChannelID string
|
||||||
|
UserID string
|
||||||
// FrameRate int // This can be used to set the FrameRate of Opus data
|
// FrameRate int // This can be used to set the FrameRate of Opus data
|
||||||
// FrameSize int // This can be used to set the FrameSize of Opus data
|
// FrameSize int // This can be used to set the FrameSize of Opus data
|
||||||
|
|
||||||
wsConn *websocket.Conn
|
wsConn *websocket.Conn
|
||||||
UDPConn *net.UDPConn // this will become unexported soon.
|
UDPConn *net.UDPConn // this will become unexported soon.
|
||||||
|
session *Session
|
||||||
|
|
||||||
sessionID string
|
sessionID string
|
||||||
token string
|
token string
|
||||||
endpoint string
|
endpoint string
|
||||||
guildID string
|
|
||||||
channelID string
|
|
||||||
userID string
|
|
||||||
op4 voiceOP4
|
op4 voiceOP4
|
||||||
|
|
||||||
// Used to send a close signal to goroutines
|
// Used to send a close signal to goroutines
|
||||||
close chan struct{}
|
close chan struct{}
|
||||||
|
|
||||||
|
// Used to allow blocking until connected
|
||||||
|
connected chan bool
|
||||||
|
|
||||||
|
// Used to pass the sessionid from onVoiceStateUpdate
|
||||||
|
sessionRecv chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Code related to the Voice websocket connection
|
// Code related to the VoiceConnection websocket connection
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// A voiceOP4 stores the data for the voice operation 4 websocket event
|
// A voiceOP4 stores the data for the voice operation 4 websocket event
|
||||||
|
@ -86,10 +95,9 @@ type voiceHandshakeOp struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens a voice connection. This should be called
|
// Open opens a voice connection. This should be called
|
||||||
// after VoiceChannelJoin is used and the data VOICE websocket events
|
// after VoiceConnectionChannelJoin is used and the data VOICE websocket events
|
||||||
// are captured.
|
// are captured.
|
||||||
func (v *Voice) Open() (err error) {
|
func (v *VoiceConnection) Open() (err error) {
|
||||||
|
|
||||||
v.Lock()
|
v.Lock()
|
||||||
defer v.Unlock()
|
defer v.Unlock()
|
||||||
|
|
||||||
|
@ -98,7 +106,7 @@ func (v *Voice) Open() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to Voice Websocket
|
// Connect to VoiceConnection Websocket
|
||||||
vg := fmt.Sprintf("wss://%s", strings.TrimSuffix(v.endpoint, ":80"))
|
vg := fmt.Sprintf("wss://%s", strings.TrimSuffix(v.endpoint, ":80"))
|
||||||
v.wsConn, _, err = websocket.DefaultDialer.Dial(vg, nil)
|
v.wsConn, _, err = websocket.DefaultDialer.Dial(vg, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -106,7 +114,7 @@ func (v *Voice) Open() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data := voiceHandshakeOp{0, voiceHandshakeData{v.guildID, v.userID, v.sessionID, v.token}}
|
data := voiceHandshakeOp{0, voiceHandshakeData{v.GuildID, v.UserID, v.sessionID, v.token}}
|
||||||
|
|
||||||
err = v.wsConn.WriteJSON(data)
|
err = v.wsConn.WriteJSON(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -123,9 +131,24 @@ func (v *Voice) Open() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *VoiceConnection) WaitUntilConnected() error {
|
||||||
|
if v.Ready {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value, ok := <-v.connected
|
||||||
|
|
||||||
|
if (!value && !v.Ready) || !ok {
|
||||||
|
delete(v.session.VoiceConnections, v.GuildID)
|
||||||
|
return errors.New("Timed out connecting to voice")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// wsListen listens on the voice websocket for messages and passes them
|
// wsListen listens on the voice websocket for messages and passes them
|
||||||
// to the voice event handler. This is automatically called by the Open func
|
// to the voice event handler. This is automatically called by the Open func
|
||||||
func (v *Voice) wsListen(wsConn *websocket.Conn, close <-chan struct{}) {
|
func (v *VoiceConnection) wsListen(wsConn *websocket.Conn, close <-chan struct{}) {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
messageType, message, err := v.wsConn.ReadMessage()
|
messageType, message, err := v.wsConn.ReadMessage()
|
||||||
|
@ -133,7 +156,7 @@ func (v *Voice) wsListen(wsConn *websocket.Conn, close <-chan struct{}) {
|
||||||
// TODO: add reconnect, matching wsapi.go:listen()
|
// TODO: add reconnect, matching wsapi.go:listen()
|
||||||
// TODO: Handle this problem better.
|
// TODO: Handle this problem better.
|
||||||
// TODO: needs proper logging
|
// TODO: needs proper logging
|
||||||
fmt.Println("Voice Listen Error:", err)
|
fmt.Println("VoiceConnection Listen Error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +172,7 @@ func (v *Voice) wsListen(wsConn *websocket.Conn, close <-chan struct{}) {
|
||||||
|
|
||||||
// wsEvent handles any voice websocket events. This is only called by the
|
// wsEvent handles any voice websocket events. This is only called by the
|
||||||
// wsListen() function.
|
// wsListen() function.
|
||||||
func (v *Voice) wsEvent(messageType int, message []byte) {
|
func (v *VoiceConnection) wsEvent(messageType int, message []byte) {
|
||||||
|
|
||||||
if v.Debug {
|
if v.Debug {
|
||||||
fmt.Println("wsEvent received: ", messageType)
|
fmt.Println("wsEvent received: ", messageType)
|
||||||
|
@ -195,7 +218,13 @@ func (v *Voice) wsEvent(messageType int, message []byte) {
|
||||||
if v.OpusRecv == nil {
|
if v.OpusRecv == nil {
|
||||||
v.OpusRecv = make(chan *Packet, 2)
|
v.OpusRecv = make(chan *Packet, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.Receive {
|
||||||
go v.opusReceiver(v.UDPConn, v.close, v.OpusRecv)
|
go v.opusReceiver(v.UDPConn, v.close, v.OpusRecv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the ready event
|
||||||
|
v.connected <- true
|
||||||
return
|
return
|
||||||
|
|
||||||
case 3: // HEARTBEAT response
|
case 3: // HEARTBEAT response
|
||||||
|
@ -240,7 +269,7 @@ type voiceHeartbeatOp struct {
|
||||||
// wsHeartbeat sends regular heartbeats to voice Discord so it knows the client
|
// wsHeartbeat sends regular heartbeats to voice Discord so it knows the client
|
||||||
// is still connected. If you do not send these heartbeats Discord will
|
// is still connected. If you do not send these heartbeats Discord will
|
||||||
// disconnect the websocket connection after a few seconds.
|
// disconnect the websocket connection after a few seconds.
|
||||||
func (v *Voice) wsHeartbeat(wsConn *websocket.Conn, close <-chan struct{}, i time.Duration) {
|
func (v *VoiceConnection) wsHeartbeat(wsConn *websocket.Conn, close <-chan struct{}, i time.Duration) {
|
||||||
|
|
||||||
if close == nil || wsConn == nil {
|
if close == nil || wsConn == nil {
|
||||||
return
|
return
|
||||||
|
@ -278,10 +307,10 @@ type voiceSpeakingOp struct {
|
||||||
// This must be sent as true prior to sending audio and should be set to false
|
// This must be sent as true prior to sending audio and should be set to false
|
||||||
// once finished sending audio.
|
// once finished sending audio.
|
||||||
// b : Send true if speaking, false if not.
|
// b : Send true if speaking, false if not.
|
||||||
func (v *Voice) Speaking(b bool) (err error) {
|
func (v *VoiceConnection) Speaking(b bool) (err error) {
|
||||||
|
|
||||||
if v.wsConn == nil {
|
if v.wsConn == nil {
|
||||||
return fmt.Errorf("No Voice websocket.")
|
return fmt.Errorf("No VoiceConnection websocket.")
|
||||||
}
|
}
|
||||||
|
|
||||||
data := voiceSpeakingOp{5, voiceSpeakingData{b, 0}}
|
data := voiceSpeakingOp{5, voiceSpeakingData{b, 0}}
|
||||||
|
@ -295,7 +324,7 @@ func (v *Voice) Speaking(b bool) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
// Code related to the Voice UDP connection
|
// Code related to the VoiceConnection UDP connection
|
||||||
// ------------------------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
type voiceUDPData struct {
|
type voiceUDPData struct {
|
||||||
|
@ -318,7 +347,7 @@ type voiceUDPOp struct {
|
||||||
// initial required handshake. This connection is left open in the session
|
// initial required handshake. This connection is left open in the session
|
||||||
// and can be used to send or receive audio. This should only be called
|
// and can be used to send or receive audio. This should only be called
|
||||||
// from voice.wsEvent OP2
|
// from voice.wsEvent OP2
|
||||||
func (v *Voice) udpOpen() (err error) {
|
func (v *VoiceConnection) udpOpen() (err error) {
|
||||||
|
|
||||||
v.Lock()
|
v.Lock()
|
||||||
defer v.Unlock()
|
defer v.Unlock()
|
||||||
|
@ -354,7 +383,7 @@ func (v *Voice) udpOpen() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a 70 byte array and put the SSRC code from the Op 2 Voice event
|
// Create a 70 byte array and put the SSRC code from the Op 2 VoiceConnection event
|
||||||
// into it. Then send that over the UDP connection to Discord
|
// into it. Then send that over the UDP connection to Discord
|
||||||
sb := make([]byte, 70)
|
sb := make([]byte, 70)
|
||||||
binary.BigEndian.PutUint32(sb, v.OP2.SSRC)
|
binary.BigEndian.PutUint32(sb, v.OP2.SSRC)
|
||||||
|
@ -377,7 +406,7 @@ func (v *Voice) udpOpen() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if rlen < 70 {
|
if rlen < 70 {
|
||||||
fmt.Println("Voice RLEN should be 70 but isn't")
|
fmt.Println("VoiceConnection RLEN should be 70 but isn't")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop over position 4 though 20 to grab the IP address
|
// Loop over position 4 though 20 to grab the IP address
|
||||||
|
@ -412,7 +441,7 @@ func (v *Voice) udpOpen() (err error) {
|
||||||
|
|
||||||
// udpKeepAlive sends a udp packet to keep the udp connection open
|
// udpKeepAlive sends a udp packet to keep the udp connection open
|
||||||
// This is still a bit of a "proof of concept"
|
// This is still a bit of a "proof of concept"
|
||||||
func (v *Voice) udpKeepAlive(UDPConn *net.UDPConn, close <-chan struct{}, i time.Duration) {
|
func (v *VoiceConnection) udpKeepAlive(UDPConn *net.UDPConn, close <-chan struct{}, i time.Duration) {
|
||||||
|
|
||||||
if UDPConn == nil || close == nil {
|
if UDPConn == nil || close == nil {
|
||||||
return
|
return
|
||||||
|
@ -446,7 +475,7 @@ func (v *Voice) udpKeepAlive(UDPConn *net.UDPConn, close <-chan struct{}, i time
|
||||||
|
|
||||||
// opusSender will listen on the given channel and send any
|
// opusSender will listen on the given channel and send any
|
||||||
// pre-encoded opus audio to Discord. Supposedly.
|
// pre-encoded opus audio to Discord. Supposedly.
|
||||||
func (v *Voice) opusSender(UDPConn *net.UDPConn, close <-chan struct{}, opus <-chan []byte, rate, size int) {
|
func (v *VoiceConnection) opusSender(UDPConn *net.UDPConn, close <-chan struct{}, opus <-chan []byte, rate, size int) {
|
||||||
|
|
||||||
if UDPConn == nil || close == nil {
|
if UDPConn == nil || close == nil {
|
||||||
return
|
return
|
||||||
|
@ -454,7 +483,7 @@ func (v *Voice) opusSender(UDPConn *net.UDPConn, close <-chan struct{}, opus <-c
|
||||||
|
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
|
|
||||||
// Voice is now ready to receive audio packets
|
// VoiceConnection is now ready to receive audio packets
|
||||||
// TODO: this needs reviewed as I think there must be a better way.
|
// TODO: this needs reviewed as I think there must be a better way.
|
||||||
v.Ready = true
|
v.Ready = true
|
||||||
defer func() { v.Ready = false }()
|
defer func() { v.Ready = false }()
|
||||||
|
@ -536,7 +565,7 @@ type Packet struct {
|
||||||
// opusReceiver listens on the UDP socket for incoming packets
|
// opusReceiver listens on the UDP socket for incoming packets
|
||||||
// and sends them across the given channel
|
// and sends them across the given channel
|
||||||
// NOTE :: This function may change names later.
|
// NOTE :: This function may change names later.
|
||||||
func (v *Voice) opusReceiver(UDPConn *net.UDPConn, close <-chan struct{}, c chan *Packet) {
|
func (v *VoiceConnection) opusReceiver(UDPConn *net.UDPConn, close <-chan struct{}, c chan *Packet) {
|
||||||
|
|
||||||
if UDPConn == nil || close == nil {
|
if UDPConn == nil || close == nil {
|
||||||
return
|
return
|
||||||
|
@ -581,11 +610,17 @@ func (v *Voice) opusReceiver(UDPConn *net.UDPConn, close <-chan struct{}, c chan
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the voice ws and udp connections
|
// Close closes the voice ws and udp connections
|
||||||
func (v *Voice) Close() {
|
func (v *VoiceConnection) Close() {
|
||||||
|
|
||||||
v.Lock()
|
v.Lock()
|
||||||
defer v.Unlock()
|
defer v.Unlock()
|
||||||
|
|
||||||
|
// Send a OP4 with a nil channel to disconnect
|
||||||
|
if v.sessionID != "" {
|
||||||
|
data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}}
|
||||||
|
v.session.wsConn.WriteJSON(data)
|
||||||
|
v.sessionID = ""
|
||||||
|
}
|
||||||
|
|
||||||
v.Ready = false
|
v.Ready = false
|
||||||
|
|
||||||
if v.close != nil {
|
if v.close != nil {
|
||||||
|
@ -608,4 +643,14 @@ func (v *Voice) Close() {
|
||||||
}
|
}
|
||||||
v.wsConn = nil
|
v.wsConn = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(v.session.VoiceConnections, v.GuildID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request to change channels
|
||||||
|
func (v *VoiceConnection) ChangeChannel(channelID string) (err error) {
|
||||||
|
data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, &channelID, true, true}}
|
||||||
|
err = v.session.wsConn.WriteJSON(data)
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
98
wsapi.go
98
wsapi.go
|
@ -55,6 +55,8 @@ func (s *Session) Open() (err error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
s.VoiceConnections = make(map[string]*VoiceConnection)
|
||||||
|
|
||||||
if s.wsConn != nil {
|
if s.wsConn != nil {
|
||||||
err = errors.New("Web socket already opened.")
|
err = errors.New("Web socket already opened.")
|
||||||
return
|
return
|
||||||
|
@ -248,6 +250,7 @@ func (s *Session) UpdateStatus(idle int, game string) (err error) {
|
||||||
func (s *Session) event(messageType int, message []byte) {
|
func (s *Session) event(messageType int, message []byte) {
|
||||||
var err error
|
var err error
|
||||||
var reader io.Reader
|
var reader io.Reader
|
||||||
|
|
||||||
reader = bytes.NewBuffer(message)
|
reader = bytes.NewBuffer(message)
|
||||||
|
|
||||||
if messageType == 2 {
|
if messageType == 2 {
|
||||||
|
@ -333,58 +336,60 @@ type voiceChannelJoinOp struct {
|
||||||
// cID : Channel ID of the channel to join.
|
// cID : Channel ID of the channel to join.
|
||||||
// mute : If true, you will be set to muted upon joining.
|
// mute : If true, you will be set to muted upon joining.
|
||||||
// deaf : If true, you will be set to deafened upon joining.
|
// deaf : If true, you will be set to deafened upon joining.
|
||||||
func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (err error) {
|
// timeout : If greater than zero, the timeout in milliseconds after which connecting will fail
|
||||||
|
func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool, timeout int) (voice *VoiceConnection, err error) {
|
||||||
// Create new voice{} struct if one does not exist.
|
// If a voice connection for the guild exists, return that
|
||||||
// If you create this prior to calling this func then you can manually
|
if _, exists := s.VoiceConnections[gID]; exists {
|
||||||
// set some variables if needed, such as to enable debugging.
|
return s.VoiceConnections[gID], err
|
||||||
if s.Voice == nil {
|
|
||||||
s.Voice = &Voice{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the request to Discord that we want to join the voice channel
|
// Send the request to Discord that we want to join the voice channel
|
||||||
data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, &cID, mute, deaf}}
|
data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, &cID, mute, deaf}}
|
||||||
err = s.wsConn.WriteJSON(data)
|
err = s.wsConn.WriteJSON(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new voice session
|
||||||
|
voice = &VoiceConnection{
|
||||||
|
Receive: true,
|
||||||
|
session: s,
|
||||||
|
connected: make(chan bool),
|
||||||
|
sessionRecv: make(chan string),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store this in the waiting map so it can get a session/token
|
||||||
|
s.VoiceConnections[gID] = voice
|
||||||
|
|
||||||
// Store gID and cID for later use
|
// Store gID and cID for later use
|
||||||
s.Voice.guildID = gID
|
voice.GuildID = gID
|
||||||
s.Voice.channelID = cID
|
voice.ChannelID = cID
|
||||||
|
|
||||||
return
|
// Queue the timeout in case we fail to connect
|
||||||
|
if timeout > 0 {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(time.Millisecond * time.Duration(timeout))
|
||||||
|
if !voice.Ready {
|
||||||
|
voice.connected <- false
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelVoiceLeave disconnects from the currently connected
|
return voice, err
|
||||||
// voice channel.
|
|
||||||
func (s *Session) ChannelVoiceLeave() (err error) {
|
|
||||||
|
|
||||||
if s.Voice == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the request to Discord that we want to leave voice
|
|
||||||
data := voiceChannelJoinOp{4, voiceChannelJoinData{nil, nil, true, true}}
|
|
||||||
err = s.wsConn.WriteJSON(data)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close voice and nil data struct
|
|
||||||
s.Voice.Close()
|
|
||||||
s.Voice = nil
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// onVoiceStateUpdate handles Voice State Update events on the data
|
// onVoiceStateUpdate handles Voice State Update events on the data
|
||||||
// websocket. This comes immediately after the call to VoiceChannelJoin
|
// websocket. This comes immediately after the call to VoiceChannelJoin
|
||||||
// for the session user.
|
// for the session user.
|
||||||
func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) {
|
func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) {
|
||||||
|
// If we don't have a connection for the channel, don't bother
|
||||||
|
if st.ChannelID == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Ignore if Voice is nil
|
// Check if we have a voice connection to update
|
||||||
if s.Voice == nil {
|
voice, exists := s.VoiceConnections[st.GuildID]
|
||||||
|
if !exists {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -397,16 +402,14 @@ func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// This event comes for all users, if it's not for the session
|
// We only care about events that are about us
|
||||||
// user just ignore it.
|
|
||||||
// TODO Move this IF to the event() func
|
|
||||||
if st.UserID != self.ID {
|
if st.UserID != self.ID {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the SessionID for later use.
|
// Store the SessionID for later use.
|
||||||
s.Voice.userID = self.ID // TODO: Review
|
voice.UserID = self.ID // TODO: Review
|
||||||
s.Voice.sessionID = st.SessionID
|
voice.sessionRecv <- st.SessionID
|
||||||
}
|
}
|
||||||
|
|
||||||
// onVoiceServerUpdate handles the Voice Server Update data websocket event.
|
// onVoiceServerUpdate handles the Voice Server Update data websocket event.
|
||||||
|
@ -417,19 +420,28 @@ func (s *Session) onVoiceStateUpdate(se *Session, st *VoiceStateUpdate) {
|
||||||
// to a voice channel. In that case, need to re-establish connection to
|
// to a voice channel. In that case, need to re-establish connection to
|
||||||
// the new region endpoint.
|
// the new region endpoint.
|
||||||
func (s *Session) onVoiceServerUpdate(se *Session, st *VoiceServerUpdate) {
|
func (s *Session) onVoiceServerUpdate(se *Session, st *VoiceServerUpdate) {
|
||||||
|
voice, exists := s.VoiceConnections[st.GuildID]
|
||||||
|
|
||||||
|
// If no VoiceConnection exists, just skip this
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Store values for later use
|
// Store values for later use
|
||||||
s.Voice.token = st.Token
|
voice.token = st.Token
|
||||||
s.Voice.endpoint = st.Endpoint
|
voice.endpoint = st.Endpoint
|
||||||
s.Voice.guildID = st.GuildID
|
voice.GuildID = st.GuildID
|
||||||
|
|
||||||
// If currently connected to voice ws/udp, then disconnect.
|
// If currently connected to voice ws/udp, then disconnect.
|
||||||
// Has no effect if not connected.
|
// Has no effect if not connected.
|
||||||
s.Voice.Close()
|
voice.Close()
|
||||||
|
|
||||||
|
// Wait for the sessionID from onVoiceStateUpdate
|
||||||
|
voice.sessionID = <-voice.sessionRecv
|
||||||
|
|
||||||
// We now have enough information to open a voice websocket conenction
|
// We now have enough information to open a voice websocket conenction
|
||||||
// so, that's what the next call does.
|
// so, that's what the next call does.
|
||||||
err := s.Voice.Open()
|
err := voice.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("onVoiceServerUpdate Voice.Open error: ", err)
|
fmt.Println("onVoiceServerUpdate Voice.Open error: ", err)
|
||||||
// TODO better logging
|
// TODO better logging
|
||||||
|
|
Loading…
Reference in a new issue