mirror of
https://github.com/mgerb/go-discord-bot
synced 2026-01-09 16:42:48 +00:00
expanded permissions functionality - added packr for bundling static assets
This commit is contained in:
@@ -33,22 +33,23 @@ const (
|
||||
)
|
||||
|
||||
// store our connection objects in a map tied to a guild id
|
||||
var activeConnections = make(map[string]*audioConnection)
|
||||
var activeConnections = make(map[string]*AudioConnection)
|
||||
|
||||
type audioConnection struct {
|
||||
guild *discordgo.Guild
|
||||
session *discordgo.Session
|
||||
voiceConnection *discordgo.VoiceConnection
|
||||
currentChannel *discordgo.Channel
|
||||
sounds map[string]*audioClip
|
||||
soundQueue chan string
|
||||
voiceClipQueue chan *discordgo.Packet
|
||||
soundPlayingLock bool
|
||||
audioListenerLock bool
|
||||
mutex *sync.Mutex // mutex for single audio connection
|
||||
// AudioConnection -
|
||||
type AudioConnection struct {
|
||||
Guild *discordgo.Guild `json:"guild"`
|
||||
Session *discordgo.Session `json:"-"`
|
||||
VoiceConnection *discordgo.VoiceConnection `json:"-"`
|
||||
CurrentChannel *discordgo.Channel `json:"current_channel"`
|
||||
Sounds map[string]*AudioClip `json:"-"`
|
||||
SoundQueue chan string `json:"-"`
|
||||
VoiceClipQueue chan *discordgo.Packet `json:"-"`
|
||||
SoundPlayingLock bool `json:"-"`
|
||||
AudioListenerLock bool `json:"-"`
|
||||
Mutex *sync.Mutex `json:"-"` // mutex for single audio connection
|
||||
}
|
||||
|
||||
type audioClip struct {
|
||||
type AudioClip struct {
|
||||
Name string
|
||||
Extension string
|
||||
Content [][]byte
|
||||
@@ -76,13 +77,13 @@ func SoundsHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
}
|
||||
|
||||
// create new connection instance
|
||||
newInstance := &audioConnection{
|
||||
guild: newGuild,
|
||||
session: s,
|
||||
sounds: make(map[string]*audioClip, 0),
|
||||
soundQueue: make(chan string, maxSoundQueue),
|
||||
mutex: &sync.Mutex{},
|
||||
audioListenerLock: false,
|
||||
newInstance := &AudioConnection{
|
||||
Guild: newGuild,
|
||||
Session: s,
|
||||
Sounds: make(map[string]*AudioClip, 0),
|
||||
SoundQueue: make(chan string, maxSoundQueue),
|
||||
Mutex: &sync.Mutex{},
|
||||
AudioListenerLock: false,
|
||||
}
|
||||
|
||||
activeConnections[c.GuildID] = newInstance
|
||||
@@ -95,7 +96,7 @@ func SoundsHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
go activeConnections[c.GuildID].handleMessage(m)
|
||||
}
|
||||
|
||||
func (conn *audioConnection) handleMessage(m *discordgo.MessageCreate) {
|
||||
func (conn *AudioConnection) handleMessage(m *discordgo.MessageCreate) {
|
||||
|
||||
// check if valid command
|
||||
if strings.HasPrefix(m.Content, config.Config.BotPrefix) {
|
||||
@@ -120,22 +121,22 @@ func (conn *audioConnection) handleMessage(m *discordgo.MessageCreate) {
|
||||
}
|
||||
|
||||
// dismiss bot from currnet channel if it's in one
|
||||
func (conn *audioConnection) dismiss() {
|
||||
if conn.voiceConnection != nil && !conn.soundPlayingLock && len(conn.soundQueue) == 0 {
|
||||
conn.voiceConnection.Disconnect()
|
||||
func (conn *AudioConnection) dismiss() {
|
||||
if conn.VoiceConnection != nil && !conn.SoundPlayingLock && len(conn.SoundQueue) == 0 {
|
||||
conn.VoiceConnection.Disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
// summon bot to channel that user is currently in
|
||||
func (conn *audioConnection) summon(m *discordgo.MessageCreate) {
|
||||
func (conn *AudioConnection) summon(m *discordgo.MessageCreate) {
|
||||
|
||||
// Join the channel the user issued the command from if not in it
|
||||
if conn.voiceConnection == nil || conn.voiceConnection.ChannelID != m.ChannelID {
|
||||
if conn.VoiceConnection == nil || conn.VoiceConnection.ChannelID != m.ChannelID {
|
||||
|
||||
var err error
|
||||
|
||||
// Find the channel that the message came from.
|
||||
c, err := conn.session.State.Channel(m.ChannelID)
|
||||
c, err := conn.Session.State.Channel(m.ChannelID)
|
||||
if err != nil {
|
||||
// Could not find channel.
|
||||
log.Error("User channel not found.")
|
||||
@@ -143,7 +144,7 @@ func (conn *audioConnection) summon(m *discordgo.MessageCreate) {
|
||||
}
|
||||
|
||||
// Find the guild for that channel.
|
||||
g, err := conn.session.State.Guild(c.GuildID)
|
||||
g, err := conn.Session.State.Guild(c.GuildID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
@@ -153,19 +154,19 @@ func (conn *audioConnection) summon(m *discordgo.MessageCreate) {
|
||||
for _, vs := range g.VoiceStates {
|
||||
if vs.UserID == m.Author.ID {
|
||||
|
||||
conn.voiceConnection, err = conn.session.ChannelVoiceJoin(g.ID, vs.ChannelID, false, false)
|
||||
conn.VoiceConnection, err = conn.Session.ChannelVoiceJoin(g.ID, vs.ChannelID, false, false)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
// set the current channel
|
||||
conn.currentChannel = c
|
||||
conn.CurrentChannel = c
|
||||
|
||||
// start listening to audio if not locked
|
||||
if !conn.audioListenerLock {
|
||||
if !conn.AudioListenerLock {
|
||||
go conn.startAudioListener()
|
||||
conn.audioListenerLock = true
|
||||
conn.AudioListenerLock = true
|
||||
}
|
||||
|
||||
return
|
||||
@@ -176,10 +177,10 @@ func (conn *audioConnection) summon(m *discordgo.MessageCreate) {
|
||||
}
|
||||
|
||||
// play audio in channel that user is in
|
||||
func (conn *audioConnection) playAudio(soundName string, m *discordgo.MessageCreate) {
|
||||
func (conn *AudioConnection) playAudio(soundName string, m *discordgo.MessageCreate) {
|
||||
|
||||
// check if sound exists in memory
|
||||
if _, ok := conn.sounds[soundName]; !ok {
|
||||
if _, ok := conn.Sounds[soundName]; !ok {
|
||||
// try to load the sound if not found in memory
|
||||
err := conn.loadFile(soundName)
|
||||
|
||||
@@ -194,7 +195,7 @@ func (conn *audioConnection) playAudio(soundName string, m *discordgo.MessageCre
|
||||
|
||||
// add sound to queue if queue isn't full
|
||||
select {
|
||||
case conn.soundQueue <- soundName:
|
||||
case conn.SoundQueue <- soundName:
|
||||
|
||||
default:
|
||||
return
|
||||
@@ -203,7 +204,7 @@ func (conn *audioConnection) playAudio(soundName string, m *discordgo.MessageCre
|
||||
}
|
||||
|
||||
// load audio file into memory
|
||||
func (conn *audioConnection) loadFile(fileName string) error {
|
||||
func (conn *AudioConnection) loadFile(fileName string) error {
|
||||
|
||||
// scan directory for file
|
||||
files, _ := ioutil.ReadDir(config.Config.SoundsPath)
|
||||
@@ -250,7 +251,7 @@ func (conn *audioConnection) loadFile(fileName string) error {
|
||||
return errors.New("NewEncoder error.")
|
||||
}
|
||||
|
||||
conn.sounds[fileName] = &audioClip{
|
||||
conn.Sounds[fileName] = &AudioClip{
|
||||
Content: make([][]byte, 0),
|
||||
Name: fileName,
|
||||
Extension: fextension,
|
||||
@@ -274,17 +275,17 @@ func (conn *audioConnection) loadFile(fileName string) error {
|
||||
}
|
||||
|
||||
// append sound bytes to the content for this audio file
|
||||
conn.sounds[fileName].Content = append(conn.sounds[fileName].Content, opus)
|
||||
conn.Sounds[fileName].Content = append(conn.Sounds[fileName].Content, opus)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (conn *audioConnection) clipAudio(m *discordgo.MessageCreate) {
|
||||
if len(conn.voiceClipQueue) < 10 {
|
||||
conn.session.ChannelMessageSend(m.ChannelID, "Clip failed.")
|
||||
func (conn *AudioConnection) clipAudio(m *discordgo.MessageCreate) {
|
||||
if len(conn.VoiceClipQueue) < 10 {
|
||||
conn.Session.ChannelMessageSend(m.ChannelID, "Clip failed.")
|
||||
} else {
|
||||
writePacketsToFile(m.Author.Username, conn.voiceClipQueue)
|
||||
conn.session.ChannelMessageSend(m.ChannelID, "Sound clipped!")
|
||||
writePacketsToFile(m.Author.Username, conn.VoiceClipQueue)
|
||||
conn.Session.ChannelMessageSend(m.ChannelID, "Sound clipped!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,10 +330,10 @@ loop:
|
||||
}
|
||||
|
||||
// start listening to the voice channel
|
||||
func (conn *audioConnection) startAudioListener() {
|
||||
func (conn *AudioConnection) startAudioListener() {
|
||||
|
||||
if conn.voiceClipQueue == nil {
|
||||
conn.voiceClipQueue = make(chan *discordgo.Packet, voiceClipQueuePacketSize)
|
||||
if conn.VoiceClipQueue == nil {
|
||||
conn.VoiceClipQueue = make(chan *discordgo.Packet, voiceClipQueuePacketSize)
|
||||
}
|
||||
|
||||
speakers := make(map[uint32]*gopus.Decoder)
|
||||
@@ -343,7 +344,7 @@ loop:
|
||||
|
||||
select {
|
||||
// grab incomming audio
|
||||
case opusChannel, ok := <-conn.voiceConnection.OpusRecv:
|
||||
case opusChannel, ok := <-conn.VoiceConnection.OpusRecv:
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -365,16 +366,16 @@ loop:
|
||||
}
|
||||
|
||||
// if channel is full trim off from beginning
|
||||
if len(conn.voiceClipQueue) == cap(conn.voiceClipQueue) {
|
||||
<-conn.voiceClipQueue
|
||||
if len(conn.VoiceClipQueue) == cap(conn.VoiceClipQueue) {
|
||||
<-conn.VoiceClipQueue
|
||||
}
|
||||
|
||||
// add current packet to channel queue
|
||||
conn.voiceClipQueue <- opusChannel
|
||||
conn.VoiceClipQueue <- opusChannel
|
||||
|
||||
// check if voice connection fails then break out of audio listener
|
||||
default:
|
||||
if !conn.voiceConnection.Ready {
|
||||
if !conn.VoiceConnection.Ready {
|
||||
break loop
|
||||
}
|
||||
|
||||
@@ -385,31 +386,31 @@ loop:
|
||||
}
|
||||
|
||||
// remove lock upon exit
|
||||
conn.audioListenerLock = false
|
||||
conn.AudioListenerLock = false
|
||||
}
|
||||
|
||||
// playSounds - plays the current buffer to the provided channel.
|
||||
func (conn *audioConnection) playSounds() (err error) {
|
||||
func (conn *AudioConnection) playSounds() (err error) {
|
||||
|
||||
for {
|
||||
newSoundName := <-conn.soundQueue
|
||||
newSoundName := <-conn.SoundQueue
|
||||
|
||||
conn.toggleSoundPlayingLock(true)
|
||||
|
||||
if !conn.voiceConnection.Ready {
|
||||
if !conn.VoiceConnection.Ready {
|
||||
continue
|
||||
}
|
||||
|
||||
// Start speaking.
|
||||
_ = conn.voiceConnection.Speaking(true)
|
||||
_ = conn.VoiceConnection.Speaking(true)
|
||||
|
||||
// Send the buffer data.
|
||||
for _, buff := range conn.sounds[newSoundName].Content {
|
||||
conn.voiceConnection.OpusSend <- buff
|
||||
for _, buff := range conn.Sounds[newSoundName].Content {
|
||||
conn.VoiceConnection.OpusSend <- buff
|
||||
}
|
||||
|
||||
// Stop speaking
|
||||
_ = conn.voiceConnection.Speaking(false)
|
||||
_ = conn.VoiceConnection.Speaking(false)
|
||||
|
||||
// Sleep for a specificed amount of time before ending.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
@@ -419,10 +420,10 @@ func (conn *audioConnection) playSounds() (err error) {
|
||||
|
||||
}
|
||||
|
||||
func (conn *audioConnection) toggleSoundPlayingLock(playing bool) {
|
||||
conn.mutex.Lock()
|
||||
conn.soundPlayingLock = playing
|
||||
conn.mutex.Unlock()
|
||||
func (conn *AudioConnection) toggleSoundPlayingLock(playing bool) {
|
||||
conn.Mutex.Lock()
|
||||
conn.SoundPlayingLock = playing
|
||||
conn.Mutex.Unlock()
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
|
||||
@@ -28,6 +28,7 @@ type configFile struct {
|
||||
ClipsPath string `json:"clips_path"`
|
||||
|
||||
AdminEmails []string `json:"admin_emails"`
|
||||
ModEmails []string `json:"mod_emails"`
|
||||
ServerAddr string `json:"server_addr"`
|
||||
JWTKey string `json:"jwt_key"`
|
||||
|
||||
|
||||
@@ -11,27 +11,34 @@ import (
|
||||
"gopkg.in/dgrijalva/jwt-go.v3"
|
||||
)
|
||||
|
||||
// permission levels
|
||||
const (
|
||||
PermAdmin = 3
|
||||
PermMod = 2
|
||||
PermUser = 1
|
||||
)
|
||||
|
||||
// CustomClaims -
|
||||
type CustomClaims struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Discriminator string `json:"discriminator"`
|
||||
Email string `json:"email"`
|
||||
Permissions string `json:"permissions"`
|
||||
Permissions int `json:"permissions"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
// GetJWT - get json web token
|
||||
func GetJWT(user discord.User) (string, error) {
|
||||
|
||||
permissions := "user"
|
||||
permissions := PermUser
|
||||
|
||||
// check if email is in config admin list
|
||||
for _, email := range config.Config.AdminEmails {
|
||||
if user.Email == email {
|
||||
permissions = "admin"
|
||||
break
|
||||
}
|
||||
if checkEmailPermissions(user.Email, config.Config.ModEmails) {
|
||||
permissions = PermMod
|
||||
}
|
||||
|
||||
if checkEmailPermissions(user.Email, config.Config.AdminEmails) {
|
||||
permissions = PermAdmin
|
||||
}
|
||||
|
||||
claims := CustomClaims{
|
||||
@@ -50,6 +57,31 @@ func GetJWT(user discord.User) (string, error) {
|
||||
return token.SignedString([]byte(config.Config.JWTKey))
|
||||
}
|
||||
|
||||
func checkEmailPermissions(email string, emails []string) bool {
|
||||
for _, e := range emails {
|
||||
if email == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AuthPermissions - secure end points based on auth levels
|
||||
func AuthPermissions(p int) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
cl, _ := c.Get("claims")
|
||||
|
||||
if claims, ok := cl.(*CustomClaims); ok {
|
||||
if p <= claims.Permissions {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
unauthorizedResponse(c, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// AuthorizedJWT - jwt middleware
|
||||
func AuthorizedJWT() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
@@ -58,8 +90,7 @@ func AuthorizedJWT() gin.HandlerFunc {
|
||||
tokenString := strings.Split(c.GetHeader("Authorization"), " ")
|
||||
|
||||
if len(tokenString) != 2 {
|
||||
c.JSON(401, "Unauthorized")
|
||||
c.Abort()
|
||||
unauthorizedResponse(c, nil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -69,9 +100,7 @@ func AuthorizedJWT() gin.HandlerFunc {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
c.JSON(401, "Unauthorized")
|
||||
c.Abort()
|
||||
unauthorizedResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -79,12 +108,18 @@ func AuthorizedJWT() gin.HandlerFunc {
|
||||
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
|
||||
c.Set("claims", claims)
|
||||
} else {
|
||||
log.Error(err)
|
||||
c.JSON(401, "Unauthorized")
|
||||
c.Abort()
|
||||
unauthorizedResponse(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func unauthorizedResponse(c *gin.Context, err error) {
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
c.JSON(401, "unauthorized")
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package webserver
|
||||
|
||||
import (
|
||||
"github.com/gobuffalo/packr"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mgerb/go-discord-bot/server/config"
|
||||
"github.com/mgerb/go-discord-bot/server/webserver/handlers"
|
||||
@@ -10,13 +12,15 @@ import (
|
||||
func getRouter() *gin.Engine {
|
||||
router := gin.Default()
|
||||
|
||||
router.Static("/static", "./dist/static")
|
||||
box := packr.NewBox("../../dist/static")
|
||||
|
||||
router.StaticFS("/static", box)
|
||||
router.Static("/public/sounds", config.Config.SoundsPath)
|
||||
router.Static("/public/youtube", "./youtube")
|
||||
router.Static("/public/clips", config.Config.ClipsPath)
|
||||
|
||||
router.NoRoute(func(c *gin.Context) {
|
||||
c.File("./dist/static/index.html")
|
||||
c.Data(200, "text/html", box.Bytes("index.html"))
|
||||
})
|
||||
|
||||
api := router.Group("/api")
|
||||
|
||||
Reference in New Issue
Block a user