mirror of
https://github.com/mgerb/go-discord-bot
synced 2026-01-09 16:42:48 +00:00
fix voice listener - add voice state handler
This commit is contained in:
6
server/Gopkg.lock
generated
6
server/Gopkg.lock
generated
@@ -18,12 +18,12 @@
|
|||||||
version = "v1.1.0"
|
version = "v1.1.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:99c79dc08249968d203bf4210220c01bd8250301600f5daf0a81f4c5f184f8d9"
|
digest = "1:edf119770bbfa2586be839aa1a4dd3ab75a678e5921ee10e6914353af0966acc"
|
||||||
name = "github.com/bwmarrin/discordgo"
|
name = "github.com/bwmarrin/discordgo"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "befbea878e0f1d0bb21be6723b6cf6db2689f6a8"
|
revision = "c27ad65527ecbc264c674cd3d0e85bb09de942e3"
|
||||||
version = "v0.22.0"
|
version = "v0.23.2"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:3ee1d175a75b911a659fbd860060874c4f503e793c5870d13e5a0ede529a63cf"
|
digest = "1:3ee1d175a75b911a659fbd860060874c4f503e793c5870d13e5a0ede529a63cf"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/bwmarrin/discordgo"
|
name = "github.com/bwmarrin/discordgo"
|
||||||
version = "0.22.0"
|
version = "0.23.1"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/gin-gonic/gin"
|
name = "github.com/gin-gonic/gin"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ func Start(token string) *discordgo.Session {
|
|||||||
|
|
||||||
// add bot handlers
|
// add bot handlers
|
||||||
_session.AddHandler(bothandlers.SoundsHandler)
|
_session.AddHandler(bothandlers.SoundsHandler)
|
||||||
|
_session.AddHandler(bothandlers.VoiceStateHandler)
|
||||||
_session.AddHandler(bothandlers.LoggerHandler)
|
_session.AddHandler(bothandlers.LoggerHandler)
|
||||||
_session.AddHandler(func(_s *discordgo.Session, m *discordgo.MessageCreate) {
|
_session.AddHandler(func(_s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||||
if m.Content == config.Config.BotPrefix+"restart" {
|
if m.Content == config.Config.BotPrefix+"restart" {
|
||||||
|
|||||||
@@ -33,16 +33,15 @@ var ActiveConnections = make(map[string]*AudioConnection)
|
|||||||
|
|
||||||
// AudioConnection -
|
// AudioConnection -
|
||||||
type AudioConnection struct {
|
type AudioConnection struct {
|
||||||
Guild *discordgo.Guild `json:"guild"`
|
Guild *discordgo.Guild `json:"guild"`
|
||||||
Session *discordgo.Session `json:"-"`
|
Session *discordgo.Session `json:"-"`
|
||||||
VoiceConnection *discordgo.VoiceConnection `json:"-"`
|
CurrentChannel *discordgo.Channel `json:"current_channel"`
|
||||||
CurrentChannel *discordgo.Channel `json:"current_channel"`
|
Sounds map[string]*AudioClip `json:"-"`
|
||||||
Sounds map[string]*AudioClip `json:"-"`
|
SoundQueue chan string `json:"-"`
|
||||||
SoundQueue chan string `json:"-"`
|
VoiceClipQueue chan *discordgo.Packet `json:"-"`
|
||||||
VoiceClipQueue chan *discordgo.Packet `json:"-"`
|
SoundPlayingLock bool `json:"-"`
|
||||||
SoundPlayingLock bool `json:"-"`
|
AudioListenerLock bool `json:"-"`
|
||||||
AudioListenerLock bool `json:"-"`
|
Mutex *sync.Mutex `json:"-"` // mutex for single audio connection
|
||||||
Mutex *sync.Mutex `json:"-"` // mutex for single audio connection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AudioClip -
|
// AudioClip -
|
||||||
@@ -52,9 +51,31 @@ type AudioClip struct {
|
|||||||
Content [][]byte
|
Content [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// SoundsHandler -
|
// VoiceStateHandler - when users enter voice channels
|
||||||
func SoundsHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
|
func VoiceStateHandler(s *discordgo.Session, v *discordgo.VoiceStateUpdate) {
|
||||||
|
if conn := ActiveConnections[v.GuildID]; conn != nil {
|
||||||
|
|
||||||
|
voiceConnection := conn.getVoiceConnection()
|
||||||
|
|
||||||
|
if voiceConnection.Ready && voiceConnection.ChannelID == v.VoiceState.ChannelID {
|
||||||
|
|
||||||
|
user, err := model.UserGet(db.GetConn(), v.VoiceState.UserID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.VoiceJoinSound != nil {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
conn.PlayAudio(*user.VoiceJoinSound, nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SoundsHandler - play sounds
|
||||||
|
func SoundsHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||||
// get channel state to get guild id
|
// get channel state to get guild id
|
||||||
c, err := s.State.Channel(m.ChannelID)
|
c, err := s.State.Channel(m.ChannelID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -119,18 +140,20 @@ func (conn *AudioConnection) handleMessage(m *discordgo.MessageCreate) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dismiss bot from currnet channel if it's in one
|
// dismiss bot from current channel if it's in one
|
||||||
func (conn *AudioConnection) dismiss() {
|
func (conn *AudioConnection) dismiss() {
|
||||||
if conn.VoiceConnection != nil && !conn.SoundPlayingLock && len(conn.SoundQueue) == 0 {
|
voiceConnection := conn.getVoiceConnection()
|
||||||
conn.VoiceConnection.Disconnect()
|
if voiceConnection != nil && !conn.SoundPlayingLock && len(conn.SoundQueue) == 0 {
|
||||||
|
voiceConnection.Disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// summon bot to channel that user is currently in
|
// summon bot to channel that user is currently in
|
||||||
func (conn *AudioConnection) summon(m *discordgo.MessageCreate) {
|
func (conn *AudioConnection) summon(m *discordgo.MessageCreate) {
|
||||||
|
voiceConnection := conn.getVoiceConnection()
|
||||||
|
|
||||||
// Join the channel the user issued the command from if not in it
|
// Join the channel the user issued the command from if not in it
|
||||||
if conn.VoiceConnection == nil || conn.VoiceConnection.ChannelID != m.ChannelID {
|
if voiceConnection == nil || voiceConnection.ChannelID != m.ChannelID {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -159,27 +182,22 @@ func (conn *AudioConnection) summon(m *discordgo.MessageCreate) {
|
|||||||
log.Error(err)
|
log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, ok := conn.Session.VoiceConnections[c.GuildID]; ok {
|
|
||||||
conn.VoiceConnection = conn.Session.VoiceConnections[c.GuildID]
|
|
||||||
} else {
|
|
||||||
log.Error("Voice connection not found on discord object")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the current channel
|
// set the current channel
|
||||||
conn.CurrentChannel = c
|
conn.CurrentChannel = c
|
||||||
|
|
||||||
// start listening to audio if not locked
|
go conn.startAudioListener()
|
||||||
if !conn.AudioListenerLock {
|
|
||||||
go conn.startAudioListener()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// play a random sound clip
|
func (conn *AudioConnection) getVoiceConnection() *discordgo.VoiceConnection {
|
||||||
|
return conn.Session.VoiceConnections[conn.Guild.ID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// PlayRandomAudio - play a random sound clip
|
||||||
func (conn *AudioConnection) PlayRandomAudio(m *discordgo.MessageCreate, userID *string) {
|
func (conn *AudioConnection) PlayRandomAudio(m *discordgo.MessageCreate, userID *string) {
|
||||||
files, _ := ioutil.ReadDir(config.Config.SoundsPath)
|
files, _ := ioutil.ReadDir(config.Config.SoundsPath)
|
||||||
if len(files) > 0 {
|
if len(files) > 0 {
|
||||||
@@ -195,10 +213,13 @@ func (conn *AudioConnection) PlayRandomAudio(m *discordgo.MessageCreate, userID
|
|||||||
// if MessageCreate is null play in current channel
|
// if MessageCreate is null play in current channel
|
||||||
func (conn *AudioConnection) PlayAudio(soundName string, m *discordgo.MessageCreate, userID *string) {
|
func (conn *AudioConnection) PlayAudio(soundName string, m *discordgo.MessageCreate, userID *string) {
|
||||||
|
|
||||||
|
voiceConnection := conn.getVoiceConnection()
|
||||||
|
|
||||||
// summon bot to channel if new message passed in
|
// summon bot to channel if new message passed in
|
||||||
if m != nil {
|
if m != nil {
|
||||||
conn.summon(m)
|
conn.summon(m)
|
||||||
} else if conn.VoiceConnection == nil || !conn.VoiceConnection.Ready {
|
} else if voiceConnection == nil || !voiceConnection.Ready {
|
||||||
|
log.Error("[PlayAudio] Voice connection is not ready")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,22 +238,25 @@ func (conn *AudioConnection) PlayAudio(soundName string, m *discordgo.MessageCre
|
|||||||
select {
|
select {
|
||||||
case conn.SoundQueue <- soundName:
|
case conn.SoundQueue <- soundName:
|
||||||
|
|
||||||
var newUserID string
|
var newUserID *string
|
||||||
fromWebUI := false
|
fromWebUI := false
|
||||||
|
|
||||||
// from discord
|
// from discord
|
||||||
if m != nil && m.Author != nil {
|
if m != nil && m.Author != nil {
|
||||||
newUserID = m.Author.ID
|
newUserID = &m.Author.ID
|
||||||
} else {
|
} else {
|
||||||
fromWebUI = true
|
fromWebUI = true
|
||||||
newUserID = *userID
|
newUserID = userID
|
||||||
}
|
}
|
||||||
|
|
||||||
// log event when user plays sound clip
|
// newUserID will be null if bot plays voice join sound
|
||||||
err := model.LogSoundPlayedEvent(db.GetConn(), newUserID, soundName, fromWebUI)
|
if newUserID != nil {
|
||||||
|
// log event when user plays sound clip
|
||||||
|
err := model.LogSoundPlayedEvent(db.GetConn(), *newUserID, soundName, fromWebUI)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -243,27 +267,26 @@ func (conn *AudioConnection) PlayAudio(soundName string, m *discordgo.MessageCre
|
|||||||
if !conn.SoundPlayingLock {
|
if !conn.SoundPlayingLock {
|
||||||
conn.playSoundsInQueue()
|
conn.playSoundsInQueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// playSoundsInQueue - play sounds until audio queue is empty
|
// playSoundsInQueue - play sounds until audio queue is empty
|
||||||
func (conn *AudioConnection) playSoundsInQueue() {
|
func (conn *AudioConnection) playSoundsInQueue() {
|
||||||
conn.toggleSoundPlayingLock(true)
|
conn.toggleSoundPlayingLock(true)
|
||||||
|
voiceConnection := conn.getVoiceConnection()
|
||||||
// Start speaking.
|
// Start speaking.
|
||||||
conn.VoiceConnection.Speaking(true)
|
voiceConnection.Speaking(true)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case newSoundName := <-conn.SoundQueue:
|
case newSoundName := <-conn.SoundQueue:
|
||||||
|
|
||||||
if !conn.VoiceConnection.Ready {
|
if !voiceConnection.Ready {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the buffer data.
|
// Send the buffer data.
|
||||||
for _, buff := range conn.Sounds[newSoundName].Content {
|
for _, buff := range conn.Sounds[newSoundName].Content {
|
||||||
conn.VoiceConnection.OpusSend <- buff
|
voiceConnection.OpusSend <- buff
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sleep for a specificed amount of time before ending.
|
// Sleep for a specificed amount of time before ending.
|
||||||
@@ -271,7 +294,7 @@ func (conn *AudioConnection) playSoundsInQueue() {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
// Stop speaking
|
// Stop speaking
|
||||||
conn.VoiceConnection.Speaking(false)
|
voiceConnection.Speaking(false)
|
||||||
conn.toggleSoundPlayingLock(false)
|
conn.toggleSoundPlayingLock(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -359,34 +382,65 @@ loop:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start listening to the voice channel
|
// start listening to the voice channel
|
||||||
|
// endless loop - look into closing if ever set up for multiple servers to prevent memory leak
|
||||||
|
// should be fine to keep this open for now
|
||||||
func (conn *AudioConnection) startAudioListener() {
|
func (conn *AudioConnection) startAudioListener() {
|
||||||
|
|
||||||
|
if conn.AudioListenerLock {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("[startAudioListener] Voice connection listener started")
|
||||||
|
|
||||||
conn.AudioListenerLock = true
|
conn.AudioListenerLock = true
|
||||||
|
|
||||||
if conn.VoiceClipQueue == nil {
|
if conn.VoiceClipQueue == nil {
|
||||||
conn.VoiceClipQueue = make(chan *discordgo.Packet, voiceClipQueuePacketSize)
|
conn.VoiceClipQueue = make(chan *discordgo.Packet, voiceClipQueuePacketSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new channel to watch for voice connection
|
localSleep := func() {
|
||||||
// when voice connection is not ready the loop will exit
|
time.Sleep(time.Second / 10)
|
||||||
exitChan := make(chan bool)
|
}
|
||||||
|
// Concurrently check and see if voice connection is not in ready state
|
||||||
|
// because we need to exit the sound handler.
|
||||||
|
vcExitChan := make(chan bool, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
if !conn.VoiceConnection.Ready {
|
voiceConnection := conn.getVoiceConnection()
|
||||||
exitChan <- true
|
if voiceConnection != nil {
|
||||||
break
|
voiceConnection.RLock()
|
||||||
|
ready := voiceConnection != nil && voiceConnection.Ready
|
||||||
|
voiceConnection.RUnlock()
|
||||||
|
|
||||||
|
if !ready {
|
||||||
|
vcExitChan <- true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second * 1)
|
localSleep()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
loop:
|
|
||||||
for {
|
for {
|
||||||
|
voiceConnection := conn.getVoiceConnection()
|
||||||
|
if voiceConnection == nil {
|
||||||
|
localSleep()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
voiceConnection.RLock()
|
||||||
|
ready := voiceConnection != nil && voiceConnection.Ready
|
||||||
|
voiceConnection.RUnlock()
|
||||||
|
|
||||||
|
// if connection lost wait for ready
|
||||||
|
if !ready {
|
||||||
|
localSleep()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
// grab incoming audio
|
// grab incoming audio
|
||||||
case opusChannel, ok := <-conn.VoiceConnection.OpusRecv:
|
case opusChannel, ok := <-voiceConnection.OpusRecv:
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -398,13 +452,12 @@ loop:
|
|||||||
|
|
||||||
// add current packet to channel queue
|
// add current packet to channel queue
|
||||||
conn.VoiceClipQueue <- opusChannel
|
conn.VoiceClipQueue <- opusChannel
|
||||||
|
break
|
||||||
|
|
||||||
// check if voice connection fails then break out of audio listener
|
// if voice is interrupted continue loop (e.g. disconnects)
|
||||||
case <-exitChan:
|
case <-vcExitChan:
|
||||||
break loop
|
log.Info("[startAudioListener] exitChan is exiting")
|
||||||
|
localSleep()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove lock upon exit
|
|
||||||
conn.AudioListenerLock = false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,20 @@ import (
|
|||||||
|
|
||||||
// User -
|
// User -
|
||||||
type User struct {
|
type User struct {
|
||||||
ID string `gorm:"primary_key" json:"id"`
|
ID string `gorm:"primary_key" json:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
DeletedAt *time.Time `json:"deleted_at"`
|
DeletedAt *time.Time `json:"deleted_at"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
Discriminator string `json:"discriminator"`
|
Discriminator string `json:"discriminator"`
|
||||||
Token string `gorm:"-" json:"token"`
|
Token string `gorm:"-" json:"token"`
|
||||||
Verified bool `json:"verified"`
|
Verified bool `json:"verified"`
|
||||||
MFAEnabled bool `json:"mfa_enabled"`
|
MFAEnabled bool `json:"mfa_enabled"`
|
||||||
Bot bool `json:"bot"`
|
Bot bool `json:"bot"`
|
||||||
Permissions *int `gorm:"default:1;not null" json:"permissions"`
|
Permissions *int `gorm:"default:1;not null" json:"permissions"`
|
||||||
|
VoiceJoinSound *string `json:"voice_join_sound"` // sound clip that plays when user joins channel
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserSave -
|
// UserSave -
|
||||||
@@ -33,3 +34,10 @@ func UserSave(conn *gorm.DB, u *User) error {
|
|||||||
// with the actual object in FirstOrCreate method
|
// with the actual object in FirstOrCreate method
|
||||||
return conn.Where(&User{ID: u.ID}).Assign(userCopy).FirstOrCreate(u).Error
|
return conn.Where(&User{ID: u.ID}).Assign(userCopy).FirstOrCreate(u).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserGet - get user by id
|
||||||
|
func UserGet(conn *gorm.DB, id string) (*User, error) {
|
||||||
|
user := &User{ID: id}
|
||||||
|
err := conn.First(user).Error
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user