diff --git a/src/components/cli/cli.go b/src/components/cli/cli.go index ebbf242..2f060a6 100644 --- a/src/components/cli/cli.go +++ b/src/components/cli/cli.go @@ -235,6 +235,15 @@ func Run(c *commands.Commander, s fx.Shutdowner) { }, Category: "Radio", }, + { + Name: "refillradio", + Usage: "Refills the radio queue with similar songs", + Aliases: []string{"rr"}, + Action: func(cCtx *cli.Context) error { + return c.RefillRadio() + }, + Category: "Radio", + }, { Name: "status", Usage: "Prints the current status", diff --git a/src/components/commands/radio.go b/src/components/commands/radio.go index 0b54657..7a4a1f5 100644 --- a/src/components/commands/radio.go +++ b/src/components/commands/radio.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "os" "path/filepath" "time" @@ -211,3 +212,159 @@ func (c *Commander) SongExists(db *sql.DB, song spotify.ID) (bool, error) { return true, nil } + +func (c *Commander) RefillRadio() error { + status, err := c.Client.PlayerCurrentlyPlaying(c.Context) + if err != nil { + return err + } + if !status.Playing { + return nil + } + to_remove := []spotify.ID{} + radioPlaylist, db, err := c.GetRadioPlaylist("") + if err != nil { + return err + } + + if status.PlaybackContext.URI != radioPlaylist.URI { + return nil + } + + playlistItems, err := c.Client.GetPlaylistItems(c.Context, radioPlaylist.ID) + if err != nil { + return fmt.Errorf("orig playlist items: %w", err) + } + + page := 0 + for { + tracks, err := c.Client.GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50)) + if err != nil { + return fmt.Errorf("tracks: %w", err) + } + if len(tracks.Items) == 0 { + break + } + for _, track := range tracks.Items { + if track.Track.Track.ID == status.Item.ID { + break + } + to_remove = append(to_remove, track.Track.Track.ID) + } + page++ + } + if len(to_remove) > 0 { + var trackGroups []spotify.ID + for idx, item := range to_remove { + if idx%100 == 0 { + _, err = c.Client.RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...) + trackGroups = []spotify.ID{} + } + trackGroups = append(trackGroups, item) + if err != nil { + return fmt.Errorf("error clearing playlist: %w", err) + } + } + _, err := c.Client.RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...) + if err != nil { + return err + } + } + + to_add := 500 - (playlistItems.Total - len(to_remove)) + playlistItems, err = c.Client.GetPlaylistItems(c.Context, radioPlaylist.ID) + if err != nil { + return fmt.Errorf("playlist items: %w", err) + } + total := playlistItems.Total + pages := int(math.Ceil(float64(total) / 50)) + randomPage := 1 + if pages > 1 { + randomPage = frand.Intn(pages-1) + 1 + } + playlistPage, err := c.Client. + GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50)) + if err != nil { + return fmt.Errorf("playlist page: %w", err) + } + pageSongs := playlistPage.Items + frand.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) + } + seed := spotify.Seeds{ + Tracks: seedIds, + } + recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(95)) + if err != nil { + return err + } + recomendationIds := []spotify.ID{} + for _, song := range recomendations.Tracks { + exists, err := c.SongExists(db, song.ID) + if err != nil { + return fmt.Errorf("err check song existnce: %w", err) + } + if !exists { + recomendationIds = append(recomendationIds, song.ID) + } + } + queue := []spotify.ID{} + for idx, rec := range recomendationIds { + if idx > to_add { + break + } + _, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", rec.String())) + if err != nil { + return err + } + queue = append(queue, rec) + } + to_add -= len(queue) + _, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...) + if err != nil { + return fmt.Errorf("add tracks: %w", err) + } + err = c.Client.Repeat(c.Context, "context") + if err != nil { + return fmt.Errorf("repeat: %w", err) + } + for to_add > 0 { + id := frand.Intn(len(recomendationIds)-2) + 1 + seed := spotify.Seeds{ + Tracks: []spotify.ID{recomendationIds[id]}, + } + additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100)) + if err != nil { + return fmt.Errorf("get recs: %w", err) + } + additionalRecsIds := []spotify.ID{} + for idx, song := range additional_recs.Tracks { + exists, err := c.SongExists(db, song.ID) + if err != nil { + return fmt.Errorf("check song existence: %w", err) + } + if !exists { + if idx > to_add { + break + } + additionalRecsIds = append(additionalRecsIds, song.ID) + queue = append(queue, song.ID) + } + } + to_add -= len(queue) + _, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...) + if err != nil { + return fmt.Errorf("add tracks to playlist: %w", err) + } + } + return nil +}