Add universal music handler and update discordgo version

This commit is contained in:
Alex Vanin 2019-10-20 18:22:52 +03:00
parent 8b5673434a
commit 65fc1ccad4
7 changed files with 246 additions and 155 deletions

View file

@ -1,17 +1,12 @@
package discord
import (
"encoding/binary"
"fmt"
"io"
"log"
"os"
"galched-bot/modules/settings"
"galched-bot/modules/subday"
"go.uber.org/atomic"
"github.com/bwmarrin/discordgo"
"github.com/pkg/errors"
)
@ -36,15 +31,9 @@ func New(s *settings.Settings, subday *subday.Subday) (*Discord, error) {
processor.AddHandler(subdayHandler)
}
polka, err := loadSong(s.PolkaPath)
if err != nil {
return nil, errors.Wrap(err, "cannot read polka song")
for _, songHandler := range SongHandlers(s) {
processor.AddHandler(songHandler)
}
processor.AddHandler(&polkaHandler{
polka: polka,
voiceChannel: s.DiscordVoiceChannel,
lock: atomic.NewBool(false),
})
log.Printf("discord: added %d message handlers", len(processor.handlers))
if len(processor.handlers) > 0 {
@ -60,48 +49,6 @@ func New(s *settings.Settings, subday *subday.Subday) (*Discord, error) {
}, nil
}
func loadSong(path string) ([][]byte, error) {
file, err := os.Open(path)
if err != nil {
return nil, errors.Wrap(err, "error opening dca file")
}
var (
opuslen int16
buffer = make([][]byte, 0)
)
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 == io.EOF || err == io.ErrUnexpectedEOF {
err := file.Close()
if err != nil {
return nil, err
}
return buffer, nil
}
if err != nil {
return nil, errors.Wrap(err, "error reading from dca file")
}
// 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 {
return nil, errors.Wrap(err, "error reading from dca file")
}
// Append encoded pcm data to the buffer.
buffer = append(buffer, InBuf)
}
}
func LogMessage(m *discordgo.MessageCreate) {
log.Printf("discord: msg [%s]: %s", m.Author.Username, m.Content)
}

View file

@ -1,92 +0,0 @@
package discord
import (
"log"
"time"
"github.com/bwmarrin/discordgo"
"go.uber.org/atomic"
)
type polkaHandler struct {
polka [][]byte
voiceChannel string
lock *atomic.Bool
}
func (h *polkaHandler) Signature() string {
return "!song"
}
func (h *polkaHandler) Description() string {
return "сыграть гимн галчед (только для избранных)"
}
func (h *polkaHandler) IsValid(msg string) bool {
return msg == "!song"
}
func (h *polkaHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) {
if m.Author.Username != "YoMedved" && m.Author.Username != "Rummy_Quamox" && m.Author.Username != "Lidiya_owl" {
log.Printf("discord: unathorized polka message from %s", m.Author.Username)
return
}
// Find the channel that the message came from.
c, err := s.State.Channel(m.ChannelID)
if err != nil {
// Could not find channel.
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 guild's current voice states.
LogMessage(m)
if h.lock.CAS(false, true) {
defer h.lock.Store(false)
err = h.playSound(s, g.ID, h.voiceChannel)
if err != nil {
log.Println("discord: error playing sound:", err)
}
time.Sleep(10 * time.Second)
return
}
}
// playSound plays the current buffer to the provided channel.
func (h *polkaHandler) playSound(s *discordgo.Session, guildID, channelID string) (err error) {
// Join the provided voice channel.
vc, err := s.ChannelVoiceJoin(guildID, channelID, false, true)
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 h.polka {
vc.OpusSend <- buff
}
// Stop speaking
vc.Speaking(false)
// Sleep for a specified amount of time before ending.
time.Sleep(250 * time.Millisecond)
// Disconnect from the provided voice channel.
vc.Disconnect()
return nil
}

View file

@ -0,0 +1,191 @@
package discord
import (
"encoding/binary"
"io"
"log"
"os"
"time"
"github.com/bwmarrin/discordgo"
"github.com/pkg/errors"
"go.uber.org/atomic"
"galched-bot/modules/settings"
)
type (
songData [][]byte
SongHandler struct {
globalLock *atomic.Bool
songLock *atomic.Bool
song songData
signature string
description string
voiceChannel string
permissions []string
timeout time.Duration
}
)
func (h *SongHandler) Signature() string {
return h.signature
}
func (h *SongHandler) Description() string {
return h.description
}
func (h *SongHandler) IsValid(msg string) bool {
return msg == h.signature
}
func (h *SongHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) {
var permitted bool
for i := range h.permissions {
if m.Author.Username == h.permissions[i] {
permitted = true
break
}
}
if len(h.permissions) > 0 && !permitted {
log.Printf("discord: unathorized %s message from %s",
h.signature, m.Author.Username)
return
}
// Find the channel that the message came from.
c, err := s.State.Channel(m.ChannelID)
if err != nil {
// Could not find channel.
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 guild's current voice states.
LogMessage(m)
if h.globalLock.CAS(false, true) {
if h.songLock.CAS(false, true) {
err = playSound(s, g.ID, h.voiceChannel, h.song)
if err != nil {
log.Println("discord: error playing sound:", err)
}
h.globalLock.Store(false)
time.Sleep(h.timeout)
defer h.songLock.Store(false)
return
}
h.globalLock.Store(false)
}
}
func SongHandlers(s *settings.Settings) []MessageHandler {
result := make([]MessageHandler, 0, len(s.Songs))
g := new(atomic.Bool)
for i := range s.Songs {
song, err := loadSong(s.Songs[i].Path)
if err != nil {
log.Println("discord: error loading song file", err)
continue
}
handler := &SongHandler{
globalLock: g,
songLock: new(atomic.Bool),
song: song,
signature: s.Songs[i].Signature,
description: s.Songs[i].Description,
voiceChannel: s.DiscordVoiceChannel,
permissions: s.Songs[i].Permissions,
timeout: s.Songs[i].Timeout,
}
result = append(result, handler)
}
return result
}
// playSound plays the current buffer to the provided channel.
func playSound(s *discordgo.Session, guildID, channelID string, song songData) error {
// Join the provided voice channel.
vc, err := s.ChannelVoiceJoin(guildID, channelID, false, true)
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 song {
vc.OpusSend <- buff
}
// Stop speaking
vc.Speaking(false)
// Sleep for a specified amount of time before ending.
time.Sleep(250 * time.Millisecond)
// Disconnect from the provided voice channel.
vc.Disconnect()
return nil
}
func loadSong(path string) (songData, error) {
file, err := os.Open(path)
if err != nil {
return nil, errors.Wrap(err, "error opening dca file")
}
var (
opuslen int16
buffer = make(songData, 0)
)
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 == io.EOF || err == io.ErrUnexpectedEOF {
err := file.Close()
if err != nil {
return nil, err
}
return buffer, nil
}
if err != nil {
return nil, errors.Wrap(err, "error reading from dca file")
}
// 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 {
return nil, errors.Wrap(err, "error reading from dca file")
}
// Append encoded pcm data to the buffer.
buffer = append(buffer, InBuf)
}
}

View file

@ -3,10 +3,11 @@ package settings
import (
"io/ioutil"
"log"
"time"
)
const (
version = "4.1.0"
version = "4.2.0"
twitchUser = "galchedbot"
twitchIRCRoom = "galched"
discordTokenPath = "./tokens/.discordtoken"
@ -21,6 +22,14 @@ const (
)
type (
SongInfo struct {
Path string
Signature string
Description string
Permissions []string
Timeout time.Duration
}
Settings struct {
Version string
DiscordToken string
@ -29,8 +38,8 @@ type (
TwitchToken string
SubdayDataPath string
PermittedRoles []string
PolkaPath string
DiscordVoiceChannel string
Songs []SongInfo
}
)
@ -51,8 +60,22 @@ func New() (*Settings, error) {
TwitchUser: twitchUser,
TwitchIRCRoom: twitchIRCRoom,
SubdayDataPath: subdayDataPath,
PolkaPath: "songs/polka.dca",
DiscordVoiceChannel: "301793085522706432",
PermittedRoles: []string{subRole1, subRole2, galchedRole, smorcRole},
Songs: []SongInfo{
{
Path: "songs/polka.dca",
Signature: "!song",
Description: "сыграть гимн галчед (только для избранных",
Permissions: []string{"AlexV", "Rummy_Quamox", "Lidiya_owl"},
Timeout: 10 * time.Second,
},
{
Path: "songs/whisper.dca",
Signature: "!sax",
Description: "kreygasm",
Timeout: 20 * time.Second,
},
},
}, nil
}