diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 66e182d..35b8d37 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -89,11 +89,29 @@ func QueueSong(ctx *gctx.Context, client *spotify.Client, id spotify.ID) error { } func PlaySongInPlaylist(ctx *gctx.Context, client *spotify.Client, context *spotify.URI, offset int) error { - err := client.PlayOpt(ctx, &spotify.PlayOptions{ + e := client.PlayOpt(ctx, &spotify.PlayOptions{ PlaybackOffset: &spotify.PlaybackOffset{Position: offset}, PlaybackContext: context, }) - return err + if e != nil { + if isNoActiveError(e) { + err := activateDevice(ctx, client) + if err != nil { + return err + } + err = client.PlayOpt(ctx, &spotify.PlayOptions{ + PlaybackOffset: &spotify.PlaybackOffset{Position: offset}, + PlaybackContext: context, + }) + err = client.Play(ctx) + if err != nil { + return err + } + } else { + return e + } + } + return nil } func PlayLikedSongs(ctx *gctx.Context, client *spotify.Client, position int) error { @@ -420,7 +438,6 @@ func Repeat(ctx *gctx.Context, client *spotify.Client) error { if err != nil { return fmt.Errorf("Failed to get current playstate") } - fmt.Println(state.RepeatState) newState := "off" if state.RepeatState == "off" { newState = "context" @@ -496,15 +513,124 @@ func isNoActiveError(err error) bool { return strings.Contains(err.Error(), "No active device found") } -func activateDevice(ctx *gctx.Context, client *spotify.Client) error { - to_play := true - current, err := client.PlayerCurrentlyPlaying(ctx) +func RadioFromPlaylist(ctx *gctx.Context, client *spotify.Client, playlist spotify.SimplePlaylist) error { + rand.Seed(time.Now().Unix()) + total := playlist.Tracks.Total + if total == 0 { + return fmt.Errorf("This playlist is empty") + } + pages := (total / 50) + randomPage := 0 + if pages != 0 { + randomPage = rand.Intn(int(pages)) + } + playlistPage, err := client.GetPlaylistItems(ctx, playlist.ID, spotify.Limit(50), spotify.Offset(randomPage*50)) if err != nil { return err } - if current.Item == nil || !current.Playing { - to_play = false + pageSongs := playlistPage.Items + rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] }) + seedCount := 5 + if len(pageSongs) < seedCount { + seedCount = len(pageSongs) } + seedIds := []spotify.ID{} + for idx, song := range pageSongs { + if idx >= seedCount { + break + } + seedIds = append(seedIds, song.Track.Track.ID) + } + return RadioGivenList(ctx, client, seedIds[:seedCount]) +} + +func RadioFromSavedTracks(ctx *gctx.Context, client *spotify.Client) error { + rand.Seed(time.Now().Unix()) + savedSongs, err := client.CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset(0)) + if err != nil { + return err + } + if savedSongs.Total == 0 { + return fmt.Errorf("You have no saved songs") + } + pages := (savedSongs.Total / 50) + randomPage := rand.Intn(int(pages)) + trackPage, err := client.CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset(randomPage*50)) + if err != nil { + return err + } + pageSongs := trackPage.Tracks + rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] }) + seedCount := 4 + seedIds := []spotify.ID{} + for idx, song := range pageSongs { + if idx >= seedCount { + break + } + seedIds = append(seedIds, song.ID) + } + seedIds = append(seedIds, savedSongs.Tracks[0].ID) + return RadioGivenList(ctx, client, seedIds) +} + +func RadioGivenList(ctx *gctx.Context, client *spotify.Client, song_ids []spotify.ID) error { + seed := spotify.Seeds{ + Tracks: song_ids, + } + recomendations, err := client.GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(99)) + if err != nil { + return err + } + recomendationIds := []spotify.ID{} + for _, song := range recomendations.Tracks { + recomendationIds = append(recomendationIds, song.ID) + } + err = ClearRadio(ctx, client) + if err != nil { + return err + } + radioPlaylist, err := GetRadioPlaylist(ctx, client) + if err != nil { + return err + } + queue := []spotify.ID{song_ids[0]} + queue = append(queue, recomendationIds...) + _, err = client.AddTracksToPlaylist(ctx, radioPlaylist.ID, queue...) + if err != nil { + return err + } + client.PlayOpt(ctx, &spotify.PlayOptions{ + PlaybackContext: &radioPlaylist.URI, + PlaybackOffset: &spotify.PlaybackOffset{ + Position: 0, + }, + }) + err = client.Repeat(ctx, "context") + if err != nil { + return err + } + for i := 0; i < 4; i++ { + id := rand.Intn(len(recomendationIds)-2) + 1 + seed := spotify.Seeds{ + Tracks: []spotify.ID{recomendationIds[id]}, + } + additional_recs, err := client.GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(100)) + if err != nil { + return err + } + additionalRecsIds := []spotify.ID{} + for _, song := range additional_recs.Tracks { + additionalRecsIds = append(additionalRecsIds, song.ID) + } + _, err = client.AddTracksToPlaylist(ctx, radioPlaylist.ID, additionalRecsIds...) + if err != nil { + return err + } + } + return nil +} + +func activateDevice(ctx *gctx.Context, client *spotify.Client) error { configDir, _ := os.UserConfigDir() if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err == nil { deviceFile, err := os.Open(filepath.Join(configDir, "gospt/device.json")) @@ -521,7 +647,7 @@ func activateDevice(ctx *gctx.Context, client *spotify.Client) error { if err != nil { return err } - err = client.TransferPlayback(ctx, device.ID, to_play) + err = client.TransferPlayback(ctx, device.ID, true) if err != nil { return err } diff --git a/internal/tui/main.go b/internal/tui/main.go index 683463b..242df96 100644 --- a/internal/tui/main.go +++ b/internal/tui/main.go @@ -2,7 +2,6 @@ package tui import ( "fmt" - "os" "time" "gospt/internal/commands" @@ -59,7 +58,9 @@ func (m *mainModel) Tick() { select { case <-ticker.C: playing, _ := m.client.PlayerCurrentlyPlaying(m.ctx) - currentlyPlaying = "Now playing " + playing.Item.Name + " by " + playing.Item.Artists[0].Name + if playing.Playing { + currentlyPlaying = "Now playing " + playing.Item.Name + " by " + playing.Item.Artists[0].Name + } case <-quit: ticker.Stop() return @@ -72,16 +73,29 @@ func HandlePlay(ctx *gctx.Context, client *spotify.Client, uri *spotify.URI, pos var err error err = commands.PlaySongInPlaylist(ctx, client, uri, pos) if err != nil { - fmt.Println() - os.Exit(1) + return } } func HandleRadio(ctx *gctx.Context, client *spotify.Client, id spotify.ID) { err := commands.RadioGivenSong(ctx, client, id, 0) if err != nil { - fmt.Println(err) - os.Exit(1) + return + } +} + +func HandlePlaylistRadio(ctx *gctx.Context, client *spotify.Client, playlist spotify.SimplePlaylist) { + err := commands.RadioFromPlaylist(ctx, client, playlist) + if err != nil { + return + } +} + +func HandleLibraryRadio(ctx *gctx.Context, client *spotify.Client) { + err := commands.RadioFromSavedTracks(ctx, client) + if err != nil { + fmt.Println(err.Error()) + return } } @@ -156,7 +170,7 @@ func HandlePlayLikedSong(ctx *gctx.Context, client *spotify.Client, position int err := commands.PlayLikedSongs(ctx, client, position) if err != nil { fmt.Println(err.Error()) - os.Exit(1) + return } } @@ -180,7 +194,7 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { new_items, err := DeviceView(m.ctx, m.client) if err != nil { fmt.Println(err.Error()) - os.Exit(1) + return m, tea.Quit } m.list.SetItems(new_items) m.list.ResetSelected() @@ -215,7 +229,7 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { new_items, err := PlaylistView(m.ctx, m.client, playlist) if err != nil { fmt.Println(err.Error()) - os.Exit(1) + return m, tea.Quit } m.list.SetItems(new_items) m.list.ResetSelected() @@ -225,7 +239,7 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { new_items, err := SavedTracksView(m.ctx, m.client) if err != nil { fmt.Println(err.Error()) - os.Exit(1) + return m, tea.Quit } m.list.SetItems(new_items) m.list.ResetSelected() @@ -256,9 +270,13 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "main": switch m.list.SelectedItem().(mainItem).SpotifyItem.(type) { case spotify.SimplePlaylist: - m.list.NewStatusMessage("Not implemented yet") + currentlyPlaying = m.list.SelectedItem().FilterValue() + m.list.NewStatusMessage("Starting radio for " + currentlyPlaying) + go HandlePlaylistRadio(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)) case *spotify.SavedTrackPage: - m.list.NewStatusMessage("Not implemented yet") + currentlyPlaying = m.list.SelectedItem().FilterValue() + m.list.NewStatusMessage("Starting radio for " + currentlyPlaying) + go HandleLibraryRadio(m.ctx, m.client) } case "playlist": currentlyPlaying = m.list.SelectedItem().FilterValue() @@ -323,7 +341,7 @@ func DisplayMain(ctx *gctx.Context, client *spotify.Client) error { if _, err := p.Run(); err != nil { fmt.Println("Error running program:", err) - os.Exit(1) + return err } return nil } @@ -446,6 +464,6 @@ func HandleSetDevice(ctx *gctx.Context, client *spotify.Client, player spotify.P err = commands.SetDevice(ctx, client, player) if err != nil { fmt.Println(err.Error()) - os.Exit(1) + return } }