1
0
mirror of https://github.com/mgerb/go-discord-bot synced 2026-01-12 01:52:49 +00:00

work in progress - about to refactor how sounds are played

This commit is contained in:
2017-02-03 16:06:31 +00:00
parent 819afb3d2a
commit 0d3756fe54
34 changed files with 5947 additions and 60 deletions

54
server/bot/bot.go Normal file
View File

@@ -0,0 +1,54 @@
package bot
import (
"fmt"
"github.com/bwmarrin/discordgo"
)
// Variables used for command line parameters
var (
BotID string
Session *discordgo.Session
)
func Connect(token string) {
// Create a new Discord session using the provided bot token.
var err error
Session, err = discordgo.New("Bot " + token)
if err != nil {
fmt.Println("error creating Discord session,", err)
return
}
// Get the account information.
u, err := Session.User("@me")
if err != nil {
fmt.Println("error obtaining account details,", err)
}
// Store the account ID for later use.
BotID = u.ID
fmt.Println("Bot connected")
}
// Start - blocking function that starts a websocket listenting for discord callbacks
func Start() {
// Open the websocket and begin listening.
err := Session.Open()
if err != nil {
fmt.Println("error opening connection,", err)
return
}
fmt.Println("Bot is now running...")
// Simple way to keep program running until CTRL-C is pressed.
<-make(chan struct{})
return
}
func AddHandler(handler interface{}) {
Session.AddHandler(handler)
}

37
server/config/config.go Normal file
View File

@@ -0,0 +1,37 @@
package config
import (
"encoding/json"
"io/ioutil"
"log"
"os"
)
// Variables used for command line parameters
var Config configStruct
type configStruct struct {
Token string `json:"Token"`
BotPrefix string `json:"BotPrefix"` //prefix to use for bot commands
}
func Init() {
log.Println("Reading config file...")
file, e := ioutil.ReadFile("./config.json")
if e != nil {
log.Printf("File error: %v\n", e)
os.Exit(1)
}
log.Printf("%s\n", string(file))
err := json.Unmarshal(file, &Config)
if err != nil {
log.Println(err)
}
}

164
server/handlers/sounds.go Normal file
View File

@@ -0,0 +1,164 @@
package handlers
import (
"../config"
"encoding/binary"
"errors"
"fmt"
"github.com/bwmarrin/discordgo"
"io"
"os"
"strings"
"time"
)
var (
sounds = make(map[string][][]byte, 0)
soundPlayingLock = false
)
const SOUNDS_DIR string = "./sounds/"
func SoundsHandler(s *discordgo.Session, m *discordgo.MessageCreate) {
// exit function call if sound is playing
if soundPlayingLock {
fmt.Println("Exiting function call")
return
}
// check if valid command
if strings.HasPrefix(m.Content, config.Config.BotPrefix) {
soundName := strings.TrimPrefix(m.Content, config.Config.BotPrefix)
// check if sound exists in memory
if _, ok := sounds[soundName]; !ok {
// try to load the sound if not found in memory
err := loadFile(soundName)
if err != nil {
fmt.Println(err)
return
}
}
// Find the channel that the message came from.
c, err := s.State.Channel(m.ChannelID)
if err != nil {
// Could not find channel.
fmt.Println("User channel not found.")
return
}
// Find the guild for that channel.
g, err := s.State.Guild(c.GuildID)
if err != nil {
// Could not find guild.
return
}
// Look for the message sender in that guilds current voice states.
for _, vs := range g.VoiceStates {
if vs.UserID == m.Author.ID {
err = playSound(s, g.ID, vs.ChannelID, soundName)
if err != nil {
fmt.Println("Error playing sound:", err)
}
return
}
}
}
}
// load dca file into memory
func loadFile(fileName string) error {
fmt.Println("Loading file: " + fileName + ".dca")
file, err := os.Open(SOUNDS_DIR + fileName + ".dca")
if err != nil {
fmt.Println("Error opening dca file :", err)
return err
}
sounds[fileName] = make([][]byte, 0)
var opuslen int16
for {
// Read opus frame length from dca file.
err = binary.Read(file, binary.LittleEndian, &opuslen)
// If this is the end of the file, just return.
if err != nil {
file.Close()
if err == io.EOF {
return nil
} else if err == io.ErrUnexpectedEOF {
return err
}
}
if err != nil {
fmt.Println("Error reading from dca file :", err)
return err
}
// Read encoded pcm from dca file.
InBuf := make([]byte, opuslen)
err = binary.Read(file, binary.LittleEndian, &InBuf)
// Should not be any end of file errors
if err != nil {
fmt.Println("Error reading from dca file :", err)
return err
}
sounds[fileName] = append(sounds[fileName], InBuf)
}
}
// playSound plays the current buffer to the provided channel.
func playSound(s *discordgo.Session, guildID, channelID string, sound string) (err error) {
if _, ok := sounds[sound]; !ok {
return errors.New("Sound not found")
}
//prevent other sounds from interrupting
soundPlayingLock = true
// Join the provided voice channel.
vc, err := s.ChannelVoiceJoin(guildID, channelID, false, false)
if err != nil {
return err
}
// Sleep for a specified amount of time before playing the sound
time.Sleep(250 * time.Millisecond)
// Start speaking.
_ = vc.Speaking(true)
// Send the buffer data.
for _, buff := range sounds[sound] {
vc.OpusSend <- buff
}
// Stop speaking
_ = vc.Speaking(false)
// Sleep for a specificed amount of time before ending.
time.Sleep(250 * time.Millisecond)
// Disconnect from the provided voice channel.
_ = vc.Disconnect()
soundPlayingLock = false
return nil
}

25
server/main.go Normal file
View File

@@ -0,0 +1,25 @@
package main
import (
"./bot"
"./config"
"./handlers"
"./webserver"
)
func main() {
//read config file
config.Init()
//connect bot to account with token
bot.Connect(config.Config.Token)
//add handlers
bot.AddHandler(handlers.SoundsHandler)
// start new go routine for the discord websockets
go bot.Start()
// start the web server
webserver.Start()
}

174
server/webserver/server.go Normal file
View File

@@ -0,0 +1,174 @@
package webserver
import (
"fmt"
"github.com/buaazp/fasthttprouter"
"github.com/valyala/fasthttp"
"io"
"log"
"os"
"os/exec"
"strings"
"sync"
)
func logger(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
logger := ctx.Logger()
logger.Printf(ctx.RemoteAddr().String())
next(ctx)
}
}
func applyMiddleware(handler fasthttp.RequestHandler) fasthttp.RequestHandler {
newHandler := logger(handler)
//newHandler = fasthttp.CompressHandler(newHandler)
return newHandler
}
func registerRoutes(router *fasthttprouter.Router) {
router.PUT("/upload", fileUpload)
router.ServeFiles("/static/*filepath", "./static")
router.NotFound = func(ctx *fasthttp.RequestCtx) {
fasthttp.ServeFile(ctx, "./index.html")
}
}
func Start() {
router := fasthttprouter.New()
registerRoutes(router)
// apply our middleware
handlers := applyMiddleware(router.Handler)
// start web server
log.Fatal(fasthttp.ListenAndServe("0.0.0.0:8080", handlers))
}
func fileUpload(ctx *fasthttp.RequestCtx) {
file, err := ctx.FormFile("file")
if err != nil {
ctx.SetStatusCode(500)
fmt.Fprint(ctx, "Error reading file.")
return
}
src, err := file.Open()
if err != nil {
ctx.SetStatusCode(500)
fmt.Fprint(ctx, "Error opening file.")
return
}
defer src.Close()
// create uploads folder if it does not exist
if _, err := os.Stat("./uploads"); os.IsNotExist(err) {
os.Mkdir("./uploads", os.ModePerm)
}
// check if file already exists
if _, err := os.Stat("./uploads/" + file.Filename); err == nil {
ctx.SetStatusCode(403)
fmt.Fprint(ctx, "File already exists.")
return
}
dst, err := os.Create("./uploads/" + file.Filename)
if err != nil {
ctx.SetStatusCode(500)
fmt.Fprint(ctx, "Error writing file.")
return
}
defer dst.Close()
if _, err = io.Copy(dst, src); err != nil {
ctx.SetStatusCode(500)
fmt.Fprint(ctx, "Error writing file.")
return
}
err = convertFile(file.Filename)
if err != nil {
ctx.SetStatusCode(500)
fmt.Fprint(ctx, err.Error())
return
}
fmt.Fprint(ctx, "Success")
}
func convertFile(fileName string) error {
trimmedName := strings.Split(fileName, ".")[0]
outputPath := "./sounds/"
inputPath := "./uploads/"
fmt.Println(inputPath + fileName)
fmt.Println(outputPath + trimmedName + ".dca")
command := "./dca-rs " + "--raw " + "--i " + inputPath + fileName + " > " + outputPath + trimmedName + ".dca"
wg := new(sync.WaitGroup)
commands := []string{command}
for _, str := range commands {
wg.Add(1)
go exe_cmd(str, wg)
}
wg.Wait()
/*
cmd := exec.Command("./dca-rs", "--raw", "--i", inputPath+fileName+" > "+outputPath+trimmedName+".dca")
_, err := cmd.Output()
if err != nil {
fmt.Println(err.Error())
}
err = cmd.Wait()
if err == nil {
fmt.Println(err.Error())
}
*/
/*
outFile, err := os.Create(outputPath + trimmedName + ".dca")
if err != nil {
return err
}
defer outFile.Close()
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return err
}
writer := bufio.NewWriter(outFile)
defer writer.Flush()
fmt.Println(stdoutPipe)
go io.Copy(writer, stdoutPipe)
*/
fmt.Println("working")
return nil
}
func exe_cmd(cmd string, wg *sync.WaitGroup) {
fmt.Println(cmd)
parts := strings.Fields(cmd)
out, err := exec.Command(parts[0], parts[1]).Output()
if err != nil {
fmt.Println("error occured")
fmt.Printf("%s", err)
}
fmt.Printf("%s", out)
wg.Done()
}