package main import ( "context" "dndmusicbot/opus" discordspeaker "dndmusicbot/speaker" "encoding/json" "log" "net/url" "os" "strconv" "time" "github.com/faiface/beep" "github.com/faiface/beep/effects" "github.com/fhs/gompd/v2/mpd" "github.com/google/uuid" "github.com/kataras/go-events" "golang.org/x/time/rate" ) type SongInfo struct { Playlist uuid.UUID `json:"playlist,omitempty"` PlaylistName string `json:"playlistname,omitempty"` Title string `json:"current,omitempty"` Channel string `json:"channel,omitempty"` Position int64 `json:"position"` Length int64 `json:"len,omitempty"` Pause bool `json:"pause"` Song string `json:"song,omitempty"` } var ( l = rate.Sometimes{Interval: 800 * time.Millisecond} ) var ( ev events.EventEmmiter ) func init() { log.Println("events.go loading...") ev = events.New() ev.On("load_playlist", loadPlaylist) ev.On("add_playlist", addPlaylist) //ev.On("preload_song", app.preloadSong) //ev.On("song_over", app.songInfo) //ev.On("song_start", app.songInfo) ev.On("player", songInfo) //ev.On("song_position", app.songPosition) ev.On("ambiance_play", ambiancePlay) ev.On("ambiance_stop", ambianceStop) ev.On("ambiance_add", ambianceAdd) ev.On("stop", stop) ev.On("next", nextSong) ev.On("prev", prevSong) ev.On("vol_up", volup) ev.On("vol_down", voldown) ev.On("vol_set", volset) //ev.On("tick", app.checkQueue) ev.On("tick", songPosition) //ev.On("tick", app.checkTimeleft) } func volup(payload ...interface{}) { if !(len(payload) > 0) { log.Println("volup called without a payload.") return } var t string switch data := payload[0].(type) { case json.RawMessage: var err error err = json.Unmarshal(data, &t) if err != nil { log.Println(err) return } default: log.Println("volup called with invalid payload.") return } switch t { case "playlist": pl_volume.Volume = pl_volume.Volume + 0.1 case "ambiance": amb_volume.Volume = amb_volume.Volume + 0.1 } } func voldown(payload ...interface{}) { if !(len(payload) > 0) { log.Println("voldown called without a payload.") return } var t string switch data := payload[0].(type) { case json.RawMessage: var err error err = json.Unmarshal(data, &t) if err != nil { log.Println(err) return } default: log.Println("voldown called with invalid payload.") return } switch t { case "playlist": pl_volume.Volume = pl_volume.Volume - 0.1 case "ambiance": amb_volume.Volume = amb_volume.Volume - 0.1 } } func volset(payload ...interface{}) { if !(len(payload) > 0) { log.Println("volset called without a payload.") return } var data map[string]string switch js := payload[0].(type) { case json.RawMessage: json.Unmarshal(js, &data) default: log.Println("volset called with invalid payload.") return } vol, err := strconv.ParseFloat(data["vol"], 64) if err != nil { log.Println(err) return } switch data["type"] { case "playlist": pl_volume.Volume = vol case "ambiance": amb_volume.Volume = vol } sendVolume() } func sendVolume() { msg := make(map[string]interface{}) out := make(map[string]float64) msg["event"] = "volume" out["playlist"] = pl_volume.Volume out["ambiance"] = amb_volume.Volume msg["payload"] = out ws_msg <- msg } func songInfoEvent(event string) map[string]interface{} { msg := make(map[string]interface{}) msg["event"] = event status, err := mpdc.Status() if err != nil { log.Println(err) return nil } cur, err := mpdc.CurrentSong() if err != nil { log.Println(err) return nil } info := new(SongInfo) if status["state"] != "play" { info.Pause = true msg["payload"] = info return msg } duration, ok := status["duration"] if ok && duration != "" { slen, err := strconv.ParseFloat(duration, 64) if err != nil { log.Println(err) return nil } info.Length = time.Duration(slen * float64(time.Second)).Milliseconds() } elapsed, ok := status["elapsed"] if ok && elapsed != "" { spos, err := strconv.ParseFloat(elapsed, 64) if err != nil { log.Println(err) return nil } info.Position = time.Duration(spos * float64(time.Second)).Milliseconds() } album, ok := cur["Album"] if ok { plid, err := uuid.ParseBytes([]byte(album)) if err != nil { log.Println(err) return nil } pl, err := GetPlaylist(plid) if err != nil { log.Println(err) return nil } info.Playlist = pl.Id info.PlaylistName = pl.Title } title, ok := cur["Title"] if ok { info.Title = title } artist, ok := cur["Artist"] if ok { info.Channel = artist } location, ok := cur["Location"] if ok { info.Song = location } msg["payload"] = *info return msg } func ambiancePlay(payload ...interface{}) { if !(len(payload) > 0) { log.Println("ambiance_play called without a payload.") return } var id string switch data := payload[0].(type) { case json.RawMessage: var err error err = json.Unmarshal(data, &id) if err != nil { log.Println(err) return } default: log.Println("loadPlaylist called with invalid payload.") return } amb, err := GetAmbiance(id) if err != nil { log.Println(err) return } f, err := os.Open(amb.Path) if err != nil { log.Println(err) return } play, err := opus.New(f) if err != nil { log.Println(err) return } loop := beep.Loop(-1, play) volume := &effects.Volume{ Streamer: loop, Base: 2, Volume: -2.5, Silent: false, } discordspeaker.Lock() amb_mixer.Clear() amb_mixer.Add(volume) discordspeaker.Unlock() msg := make(map[string]interface{}) out := make(map[string]interface{}) amb_curr = amb msg["event"] = "ambiance_play" out["id"] = id msg["payload"] = out ws_msg <- msg } func ambianceStop(payload ...interface{}) { log.Println("Stopping ambiance") discordspeaker.Lock() amb_mixer.Clear() discordspeaker.Unlock() msg := make(map[string]interface{}) msg["event"] = "ambiance_stop" ws_msg <- msg } func ambianceAdd(payload ...interface{}) { if !(len(payload) > 0) { log.Println("addPlaylist called without a payload.") return } log.Println("ambiance_add event received") var data map[string]string switch js := payload[0].(type) { case json.RawMessage: json.Unmarshal(js, &data) default: log.Println("newPlaylist called with invalid payload.") return } amburl, ok := data["url"] if !ok { log.Println("addPlaylist without url") return } ambtitle, ok := data["title"] if !ok { log.Println("addPlaylist without title") return } amb, err := AddAmbiance(amburl, ambtitle) if err != nil { log.Println(err) return } msg := make(map[string]interface{}) out := make(map[string]interface{}) msg["event"] = "ambiance_add" out["title"] = amb.Title out["id"] = amb.Id msg["payload"] = out ws_msg <- msg } func songPosition(payload ...interface{}) { status, err := mpdc.Status() if err != nil { log.Println(err) return } if status["state"] == "stop" { return } l.Do(func() { msg := make(map[string]interface{}) out := make(map[string]interface{}) slen, err := strconv.ParseFloat(status["duration"], 64) if err != nil { log.Println(err) return } spos, err := strconv.ParseFloat(status["elapsed"], 64) if err != nil { return } msg["event"] = "song_position" out["len"] = time.Duration(slen * float64(time.Second)).Milliseconds() out["position"] = time.Duration(spos * float64(time.Second)).Milliseconds() msg["payload"] = out ws_msg <- msg }) } func songInfo(payload ...interface{}) { log.Println("song_info event received") msg := songInfoEvent("song_info") if msg != nil { ws_msg <- msg } } func stop(payload ...interface{}) { log.Println("stop event received") mpd_mutex.Lock() if mpd_plcf != nil { mpd_plcf() } mpdc.Stop() mpd_mutex.Unlock() msg := make(map[string]interface{}) msg["event"] = "stop" ws_msg <- msg } func prevSong(payload ...interface{}) { log.Println("prev_song event received") mpd_mutex.Lock() err := mpdc.Previous() mpd_mutex.Unlock() if err != nil { log.Println(err) } } func nextSong(payload ...interface{}) { log.Println("next_song event received") mpd_mutex.Lock() err := mpdc.Next() mpd_mutex.Unlock() if err != nil { log.Println(err) } } func addPlaylist(payload ...interface{}) { if !(len(payload) > 0) { log.Println("addPlaylist called without a payload.") return } log.Println("add_playlist event received") var data map[string]string switch js := payload[0].(type) { case json.RawMessage: json.Unmarshal(js, &data) default: log.Println("newPlaylist called with invalid payload.") return } plurl, ok := data["url"] if !ok { log.Println("addPlaylist without url") return } pltitle, ok := data["title"] if !ok { log.Println("addPlaylist without title") return } pl, err := url.Parse(plurl) if err != nil { log.Println("addPlaylist invalid url") return } plid := pl.Query().Get("list") if plid == "" { log.Println("addPlaylist missing list in url") return } _, err = YTPlaylist(plid) if err != nil { log.Println("Error getting youtube playlist info,", plid) } id, err := AddPlaylist(pltitle, plid) if err != nil { log.Println("Error getting youtube playlist info,", plid) } msg := make(map[string]interface{}) msg["event"] = "new_playlist" msg["payload"] = map[string]string{"url": id.String(), "title": pltitle} ws_msg <- msg } func loadPlaylist(payload ...interface{}) { log.Println("load_playlist event received") if !(len(payload) > 0) { log.Println("loadPlaylist called without a payload.") return } var id uuid.UUID switch data := payload[0].(type) { case json.RawMessage: var err error var tmp string json.Unmarshal(data, &tmp) id, err = uuid.Parse(tmp) if err != nil { log.Println("Unable to parse UUID,", err) } case uuid.UUID: id = data default: log.Println("loadPlaylist called with invalid payload.") return } log.Println("Loading new playlist: ", id) pl, err := GetPlaylist(id) if err != nil { log.Println("Unable to find playlist with id,", id) return } list, err := YTPlaylist(pl.Url) if err != nil { log.Println("Error getting playlist info,", id) return } list, err = ShufflePlaylist(list) if err != nil { log.Println("Unable to shuffle playlist") return } mpd_mutex.Lock() if mpd_plcf != nil { mpd_plcf() } mpdc.Stop() mpdc.Clear() mpd_mutex.Unlock() ctx, cancel := context.WithCancel(context.Background()) mpd_plcf = cancel go func() { defer cancel() for _, vid := range list { log.Printf("Adding %s", vid) ytinfo, err := YTVideo(vid) if err != nil { log.Println(err) continue } _, yt, err := cache.GetOrCreateBytes(vid+".txt", func() ([]byte, error) { uri, err := NewYTdl(vid) if err != nil { return nil, err } return uri, nil }) if err != nil { log.Println(err) continue } // Run as a local function so we can defer the mutex unlock incase one of these errors. ok := func() (ok bool) { mpd_mutex.Lock() defer mpd_mutex.Unlock() if ctx.Err() != nil { return false } ok = true // state:stop songid, err := mpdc.AddID(string(yt), 0) if err != nil { log.Println(err) return } mpdcmd := mpdc.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("artist"), ytinfo.Channel) err = mpdcmd.OK() if err != nil { log.Println(err) return } mpdcmd = mpdc.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("title"), ytinfo.Title) err = mpdcmd.OK() if err != nil { log.Println(err) return } mpdcmd = mpdc.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("location"), vid) err = mpdcmd.OK() if err != nil { log.Println(err) return } mpdcmd = mpdc.Command("%s %d %s %s", mpd.Quoted("addtagid"), songid, mpd.Quoted("album"), pl.Id.String()) err = mpdcmd.OK() if err != nil { log.Println(err) return } mpdc.Play(-1) return }() if !ok { break } } }() }