2024-02-18 09:58:47 -08:00
|
|
|
package commands
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2024-02-18 12:17:07 -08:00
|
|
|
"math"
|
2024-02-18 09:58:47 -08:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"gfx.cafe/util/go/frand"
|
|
|
|
"github.com/zmb3/spotify/v2"
|
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (c *Commander) Radio() error {
|
2024-02-18 23:33:32 -08:00
|
|
|
current_song, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if current_song.Item != nil {
|
|
|
|
return c.RadioGivenSong(current_song.Item.SimpleTrack, current_song.Progress)
|
|
|
|
}
|
2024-02-18 15:04:34 -08:00
|
|
|
_, err = c.activateDevice()
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
tracks, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(10))
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return c.RadioGivenSong(tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack, 0)
|
|
|
|
}
|
|
|
|
|
2024-02-18 15:04:34 -08:00
|
|
|
func (c *Commander) RadioFromPlaylist(playlist spotify.SimplePlaylist) error {
|
|
|
|
total := playlist.Tracks.Total
|
|
|
|
if total == 0 {
|
|
|
|
return fmt.Errorf("this playlist is empty")
|
|
|
|
}
|
|
|
|
pages := int(math.Ceil(float64(total) / 50))
|
|
|
|
randomPage := 1
|
|
|
|
if pages > 1 {
|
|
|
|
randomPage = frand.Intn(pages-1) + 1
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
playlistPage, err := c.Client().GetPlaylistItems(
|
2024-02-18 18:51:25 -08:00
|
|
|
c.Context,
|
|
|
|
playlist.ID,
|
|
|
|
spotify.Limit(50),
|
|
|
|
spotify.Offset((randomPage-1)*50),
|
|
|
|
)
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return 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)
|
|
|
|
}
|
|
|
|
return c.RadioGivenList(seedIds[:seedCount], playlist.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commander) RadioFromSavedTracks() error {
|
2024-02-18 23:33:32 -08:00
|
|
|
savedSongs, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if savedSongs.Total == 0 {
|
|
|
|
return fmt.Errorf("you have no saved songs")
|
|
|
|
}
|
|
|
|
pages := int(math.Ceil(float64(savedSongs.Total) / 50))
|
|
|
|
randomPage := 1
|
|
|
|
if pages > 1 {
|
|
|
|
randomPage = frand.Intn(pages-1) + 1
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
trackPage, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(randomPage*50))
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pageSongs := trackPage.Tracks
|
|
|
|
frand.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 c.RadioGivenList(seedIds, "Saved Tracks")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
|
|
|
|
seed := spotify.Seeds{
|
|
|
|
Artists: []spotify.ID{artist.ID},
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
recomendationIds := []spotify.ID{}
|
|
|
|
for _, song := range recomendations.Tracks {
|
|
|
|
recomendationIds = append(recomendationIds, song.ID)
|
|
|
|
}
|
|
|
|
err = c.ClearRadio()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
radioPlaylist, db, err := c.GetRadioPlaylist(artist.Name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
queue := []spotify.ID{}
|
|
|
|
for _, rec := range recomendationIds {
|
|
|
|
exists, err := c.SongExists(db, rec)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
_, err := db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(rec)))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
queue = append(queue, rec)
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
2024-02-18 15:04:34 -08:00
|
|
|
PlaybackContext: &radioPlaylist.URI,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
err = c.Client().Repeat(c.Context, "context")
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for i := 0; i < 4; i++ {
|
|
|
|
id := frand.Intn(len(recomendationIds)-2) + 1
|
|
|
|
seed := spotify.Seeds{
|
|
|
|
Tracks: []spotify.ID{recomendationIds[id]},
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
additional_recs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
additionalRecsIds := []spotify.ID{}
|
|
|
|
for _, song := range additional_recs.Tracks {
|
|
|
|
exists, err := c.SongExists(db, song.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
_, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
additionalRecsIds = append(additionalRecsIds, song.ID)
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-18 09:58:47 -08:00
|
|
|
func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
|
|
|
|
start := time.Now().UnixMilli()
|
|
|
|
seed := spotify.Seeds{
|
|
|
|
Tracks: []spotify.ID{song.ID},
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
recomendationIds := []spotify.ID{}
|
|
|
|
for _, song := range recomendations.Tracks {
|
|
|
|
recomendationIds = append(recomendationIds, song.ID)
|
|
|
|
}
|
|
|
|
err = c.ClearRadio()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
radioPlaylist, db, err := c.GetRadioPlaylist(song.Name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
queue := []spotify.ID{song.ID}
|
|
|
|
for _, rec := range recomendationIds {
|
|
|
|
exists, err := c.SongExists(db, rec)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
_, err := db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(rec)))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
queue = append(queue, rec)
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
delay := time.Now().UnixMilli() - start
|
|
|
|
if pos != 0 {
|
|
|
|
pos = pos + int(delay)
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
2024-02-18 09:58:47 -08:00
|
|
|
PlaybackContext: &radioPlaylist.URI,
|
|
|
|
PositionMs: pos,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
if isNoActiveError(err) {
|
2024-02-18 15:04:34 -08:00
|
|
|
deviceID, err := c.activateDevice()
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
2024-02-18 09:58:47 -08:00
|
|
|
PlaybackContext: &radioPlaylist.URI,
|
|
|
|
DeviceID: &deviceID,
|
|
|
|
PositionMs: pos,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
err = c.Client().Repeat(c.Context, "context")
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for i := 0; i < 4; i++ {
|
|
|
|
id := frand.Intn(len(recomendationIds)-2) + 1
|
|
|
|
seed := spotify.Seeds{
|
|
|
|
Tracks: []spotify.ID{recomendationIds[id]},
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
additional_recs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
additionalRecsIds := []spotify.ID{}
|
|
|
|
for _, song := range additional_recs.Tracks {
|
|
|
|
exists, err := c.SongExists(db, song.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
_, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
additionalRecsIds = append(additionalRecsIds, song.ID)
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commander) ClearRadio() error {
|
|
|
|
radioPlaylist, db, err := c.GetRadioPlaylist("")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
err = c.Client().UnfollowPlaylist(c.Context, radioPlaylist.ID)
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, _ = db.Query("DROP TABLE IF EXISTS radio")
|
|
|
|
configDir, _ := os.UserConfigDir()
|
2024-02-18 18:51:25 -08:00
|
|
|
os.Remove(filepath.Join(configDir, "gspot/radio.json"))
|
2024-02-18 23:33:32 -08:00
|
|
|
_ = c.Client().Pause(c.Context)
|
2024-02-18 09:58:47 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commander) GetRadioPlaylist(name string) (*spotify.FullPlaylist, *sql.DB, error) {
|
|
|
|
configDir, _ := os.UserConfigDir()
|
2024-02-18 18:51:25 -08:00
|
|
|
playlistFile, err := os.ReadFile(filepath.Join(configDir, "gspot/radio.json"))
|
2024-02-18 09:58:47 -08:00
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
|
|
return c.CreateRadioPlaylist(name)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
var playlist *spotify.FullPlaylist
|
|
|
|
err = json.Unmarshal(playlistFile, &playlist)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2024-02-18 18:51:25 -08:00
|
|
|
db, err := sql.Open("sqlite", filepath.Join(configDir, "gspot/radio.db"))
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
return playlist, db, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commander) CreateRadioPlaylist(name string) (*spotify.FullPlaylist, *sql.DB, error) {
|
|
|
|
// private flag doesnt work
|
|
|
|
configDir, _ := os.UserConfigDir()
|
2024-02-18 23:33:32 -08:00
|
|
|
playlist, err := c.Client().
|
2024-02-18 09:58:47 -08:00
|
|
|
CreatePlaylistForUser(c.Context, c.User.ID, name+" - autoradio", "Automanaged radio playlist", false, false)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
raw, err := json.MarshalIndent(playlist, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2024-02-18 18:51:25 -08:00
|
|
|
err = os.WriteFile(filepath.Join(configDir, "gspot/radio.json"), raw, 0o600)
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2024-02-18 18:51:25 -08:00
|
|
|
db, err := sql.Open("sqlite", filepath.Join(configDir, "gspot/radio.db"))
|
2024-02-18 09:58:47 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
_, _ = db.QueryContext(c.Context, "DROP TABLE IF EXISTS radio")
|
|
|
|
_, _ = db.QueryContext(c.Context, "CREATE TABLE IF NOT EXISTS radio (id string PRIMARY KEY)")
|
|
|
|
return playlist, db, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commander) SongExists(db *sql.DB, song spotify.ID) (bool, error) {
|
|
|
|
song_id := string(song)
|
|
|
|
sqlStmt := `SELECT id FROM radio WHERE id = ?`
|
|
|
|
err := db.QueryRow(sqlStmt, song_id).Scan(&song_id)
|
|
|
|
if err != nil {
|
|
|
|
if err != sql.ErrNoRows {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
2024-02-18 12:17:07 -08:00
|
|
|
|
|
|
|
func (c *Commander) RefillRadio() error {
|
2024-02-18 23:33:32 -08:00
|
|
|
status, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
2024-02-18 12:17:07 -08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-02-18 23:33:32 -08:00
|
|
|
playlistItems, err := c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID)
|
2024-02-18 12:17:07 -08:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("orig playlist items: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
page := 0
|
|
|
|
for {
|
2024-02-18 23:33:32 -08:00
|
|
|
tracks, err := c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50))
|
2024-02-18 12:17:07 -08:00
|
|
|
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 {
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err = c.Client().RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
|
2024-02-18 12:17:07 -08:00
|
|
|
trackGroups = []spotify.ID{}
|
|
|
|
}
|
|
|
|
trackGroups = append(trackGroups, item)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error clearing playlist: %w", err)
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err := c.Client().RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
|
2024-02-18 12:17:07 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
to_add := 500 - (playlistItems.Total - len(to_remove))
|
2024-02-18 23:33:32 -08:00
|
|
|
playlistItems, err = c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID)
|
2024-02-18 12:17:07 -08:00
|
|
|
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
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
playlistPage, err := c.Client().
|
2024-02-18 12:17:07 -08:00
|
|
|
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,
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(95))
|
2024-02-18 12:17:07 -08:00
|
|
|
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)
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
2024-02-18 12:17:07 -08:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("add tracks: %w", err)
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
err = c.Client().Repeat(c.Context, "context")
|
2024-02-18 12:17:07 -08:00
|
|
|
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]},
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
additional_recs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
2024-02-18 12:17:07 -08:00
|
|
|
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)
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
2024-02-18 12:17:07 -08:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("add tracks to playlist: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2024-02-18 15:04:34 -08:00
|
|
|
|
|
|
|
func (c *Commander) RadioFromAlbum(album spotify.SimpleAlbum) error {
|
|
|
|
tracks, err := c.AlbumTracks(album.ID, 1)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
total := tracks.Total
|
|
|
|
if total == 0 {
|
|
|
|
return fmt.Errorf("this playlist is empty")
|
|
|
|
}
|
|
|
|
pages := int(math.Ceil(float64(total) / 50))
|
|
|
|
randomPage := 1
|
|
|
|
if pages > 1 {
|
|
|
|
randomPage = frand.Intn(pages-1) + 1
|
|
|
|
}
|
|
|
|
albumTrackPage, err := c.AlbumTracks(album.ID, randomPage)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pageSongs := albumTrackPage.Tracks
|
|
|
|
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.ID)
|
|
|
|
}
|
|
|
|
return c.RadioGivenList(seedIds[:seedCount], album.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
|
|
|
|
seed := spotify.Seeds{
|
|
|
|
Tracks: song_ids,
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
recomendationIds := []spotify.ID{}
|
|
|
|
for _, song := range recomendations.Tracks {
|
|
|
|
recomendationIds = append(recomendationIds, song.ID)
|
|
|
|
}
|
|
|
|
err = c.ClearRadio()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
radioPlaylist, db, err := c.GetRadioPlaylist(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
queue := []spotify.ID{song_ids[0]}
|
|
|
|
for _, rec := range recomendationIds {
|
|
|
|
exists, err := c.SongExists(db, rec)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
_, err := db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(rec)))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
queue = append(queue, rec)
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
2024-02-18 15:04:34 -08:00
|
|
|
PlaybackContext: &radioPlaylist.URI,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
if isNoActiveError(err) {
|
|
|
|
deviceId, err := c.activateDevice()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
2024-02-18 15:04:34 -08:00
|
|
|
PlaybackContext: &radioPlaylist.URI,
|
|
|
|
DeviceID: &deviceId,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := 0; i < 4; i++ {
|
|
|
|
id := frand.Intn(len(recomendationIds)-2) + 1
|
|
|
|
seed := spotify.Seeds{
|
|
|
|
Tracks: []spotify.ID{recomendationIds[id]},
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
additional_recs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
additionalRecsIds := []spotify.ID{}
|
|
|
|
for _, song := range additional_recs.Tracks {
|
|
|
|
exists, err := c.SongExists(db, song.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
_, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
additionalRecsIds = append(additionalRecsIds, song.ID)
|
|
|
|
}
|
|
|
|
}
|
2024-02-18 23:33:32 -08:00
|
|
|
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
2024-02-18 15:04:34 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|