gospt/src/commands/commands.go

1303 lines
32 KiB
Go
Raw Normal View History

2023-01-08 00:57:51 +00:00
package commands
import (
"database/sql"
2023-01-08 00:57:51 +00:00
"encoding/json"
"errors"
2023-01-08 00:57:51 +00:00
"fmt"
"io"
2023-01-16 17:59:56 +00:00
"math"
2023-01-08 04:28:01 +00:00
"net/url"
2023-01-08 02:14:36 +00:00
"os"
"path/filepath"
"strings"
2023-02-17 21:45:24 +00:00
"sync"
2023-01-08 04:58:18 +00:00
"time"
2023-01-08 00:57:51 +00:00
2023-02-17 21:45:24 +00:00
"gfx.cafe/util/go/frand"
2023-03-10 07:48:02 +00:00
"git.asdf.cafe/abs3nt/gospt/src/auth"
"git.asdf.cafe/abs3nt/gospt/src/cache"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
2023-01-08 00:57:51 +00:00
"github.com/zmb3/spotify/v2"
_ "modernc.org/sqlite"
2023-01-08 00:57:51 +00:00
)
2023-02-17 21:45:24 +00:00
type Commands struct {
2023-02-17 22:08:25 +00:00
Context *gctx.Context
cl *spotify.Client
mu sync.RWMutex
2023-02-17 21:52:19 +00:00
user string
2023-01-15 04:52:47 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Client() *spotify.Client {
c.mu.Lock()
if c.cl == nil {
c.cl = c.connectClient()
}
c.mu.Unlock()
c.mu.RLock()
defer c.mu.RUnlock()
return c.cl
}
2023-02-17 21:52:19 +00:00
func (c *Commands) User() string {
c.Client()
return c.user
}
2023-02-17 21:45:24 +00:00
func (c *Commands) connectClient() *spotify.Client {
2023-02-17 22:08:25 +00:00
ctx := c.Context
2023-02-17 21:45:24 +00:00
client, err := auth.GetClient(ctx)
if err != nil {
panic(err)
}
currentUser, err := client.CurrentUser(ctx)
if err != nil {
panic(err)
}
2023-02-17 21:52:19 +00:00
c.user = currentUser.ID
2023-02-17 22:08:25 +00:00
return client
2023-02-17 21:45:24 +00:00
}
func (c *Commands) SetVolume(ctx *gctx.Context, vol int) error {
return c.Client().Volume(ctx, vol)
}
func (c *Commands) SetPosition(ctx *gctx.Context, pos int) error {
err := c.Client().Seek(ctx, pos)
2023-01-15 04:52:47 +00:00
if err != nil {
return err
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Seek(ctx *gctx.Context, fwd bool) error {
current, err := c.Client().PlayerCurrentlyPlaying(ctx)
2023-01-15 04:52:47 +00:00
if err != nil {
return err
}
newPos := current.Progress + 5000
if !fwd {
newPos = current.Progress - 5000
}
2023-02-17 21:45:24 +00:00
err = c.Client().Seek(ctx, newPos)
2023-01-15 04:52:47 +00:00
if err != nil {
return err
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) ChangeVolume(ctx *gctx.Context, vol int) error {
state, err := c.Client().PlayerState(ctx)
2023-01-15 04:52:47 +00:00
if err != nil {
return err
}
newVolume := state.Device.Volume + vol
if newVolume > 100 {
newVolume = 100
}
if newVolume < 0 {
newVolume = 0
}
2023-02-17 21:45:24 +00:00
return c.Client().Volume(ctx, newVolume)
2023-01-15 04:52:47 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Play(ctx *gctx.Context) error {
err := c.Client().Play(ctx)
2023-01-08 00:57:51 +00:00
if err != nil {
2023-01-08 02:14:36 +00:00
if isNoActiveError(err) {
2023-02-17 21:45:24 +00:00
deviceID, err := c.activateDevice(ctx)
2023-01-08 18:00:00 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
DeviceID: &deviceID,
})
2023-01-08 18:00:00 +00:00
if err != nil {
return err
}
} else {
return err
2023-01-08 02:14:36 +00:00
}
2023-01-08 00:57:51 +00:00
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) ActiveDeviceExists(ctx *gctx.Context) bool {
current, err := c.Client().PlayerDevices(ctx)
2023-01-11 21:57:47 +00:00
if err != nil {
return false
}
for _, dev := range current {
if dev.Active {
return true
}
}
return false
}
2023-02-17 21:45:24 +00:00
func (c *Commands) UserArtists(ctx *gctx.Context, page int) (*spotify.FullArtistCursorPage, error) {
artists, err := c.Client().CurrentUsersFollowedArtists(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
2023-01-13 02:48:41 +00:00
if err != nil {
return nil, err
}
return artists, nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) ArtistAlbums(ctx *gctx.Context, artist spotify.ID, page int) (*spotify.SimpleAlbumPage, error) {
albums, err := c.Client().GetArtistAlbums(ctx, artist, []spotify.AlbumType{1, 2, 3, 4}, spotify.Market(spotify.CountryUSA), spotify.Limit(50), spotify.Offset((page-1)*50))
2023-01-13 03:06:06 +00:00
if err != nil {
return nil, err
}
return albums, nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Search(ctx *gctx.Context, search string, page int) (*spotify.SearchResult, error) {
result, err := c.Client().Search(ctx, search, spotify.SearchTypeAlbum|spotify.SearchTypeArtist|spotify.SearchTypeTrack|spotify.SearchTypePlaylist, spotify.Limit(50), spotify.Offset((page-1)*50))
2023-01-13 09:12:31 +00:00
if err != nil {
return nil, err
}
return result, nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) AlbumTracks(ctx *gctx.Context, album spotify.ID, page int) (*spotify.SimpleTrackPage, error) {
tracks, err := c.Client().GetAlbumTracks(ctx, album, spotify.Limit(50), spotify.Offset((page-1)*50), spotify.Market(spotify.CountryUSA))
2023-01-13 03:06:06 +00:00
if err != nil {
return nil, err
}
return tracks, nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) UserAlbums(ctx *gctx.Context, page int) (*spotify.SavedAlbumPage, error) {
albums, err := c.Client().CurrentUsersAlbums(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
2023-01-13 02:48:41 +00:00
if err != nil {
return nil, err
}
return albums, nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) PlayUrl(ctx *gctx.Context, args []string) error {
2023-01-09 23:52:21 +00:00
url, err := url.Parse(args[0])
2023-01-08 04:28:01 +00:00
if err != nil {
return err
}
track_id := strings.Split(url.Path, "/")[2]
2023-02-17 21:45:24 +00:00
err = c.Client().QueueSong(ctx, spotify.ID(track_id))
2023-01-08 04:28:01 +00:00
if err != nil {
if isNoActiveError(err) {
2023-02-17 21:45:24 +00:00
deviceID, err := c.activateDevice(ctx)
2023-01-08 18:00:00 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().QueueSongOpt(ctx, spotify.ID(track_id), &spotify.PlayOptions{
DeviceID: &deviceID,
})
2023-01-08 05:34:31 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().NextOpt(ctx, &spotify.PlayOptions{
DeviceID: &deviceID,
})
2023-01-08 05:34:31 +00:00
if err != nil {
return err
}
return nil
2023-01-08 18:00:00 +00:00
} else {
return err
2023-01-08 04:28:01 +00:00
}
}
2023-02-17 21:45:24 +00:00
err = c.Client().Next(ctx)
2023-01-08 05:34:31 +00:00
if err != nil {
return err
}
2023-01-08 04:28:01 +00:00
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) QueueSong(ctx *gctx.Context, id spotify.ID) error {
err := c.Client().QueueSong(ctx, id)
2023-01-08 05:34:31 +00:00
if err != nil {
if isNoActiveError(err) {
2023-02-17 21:45:24 +00:00
deviceID, err := c.activateDevice(ctx)
2023-01-08 18:00:00 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().QueueSongOpt(ctx, id, &spotify.PlayOptions{
DeviceID: &deviceID,
})
2023-01-08 05:34:31 +00:00
if err != nil {
return err
}
return nil
2023-01-08 18:00:00 +00:00
} else {
return err
2023-01-08 05:34:31 +00:00
}
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) PlaySongInPlaylist(ctx *gctx.Context, context *spotify.URI, offset int) error {
e := c.Client().PlayOpt(ctx, &spotify.PlayOptions{
2023-01-10 02:03:31 +00:00
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
PlaybackContext: context,
})
2023-01-11 21:30:55 +00:00
if e != nil {
if isNoActiveError(e) {
2023-02-17 21:45:24 +00:00
deviceID, err := c.activateDevice(ctx)
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
2023-01-11 21:30:55 +00:00
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
PlaybackContext: context,
DeviceID: &deviceID,
2023-01-11 21:30:55 +00:00
})
if err != nil {
if isNoActiveError(err) {
2023-02-17 21:45:24 +00:00
deviceID, err := c.activateDevice(ctx)
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
PlaybackContext: context,
DeviceID: &deviceID,
})
if err != nil {
return err
}
}
}
2023-02-17 21:45:24 +00:00
err = c.Client().Play(ctx)
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
} else {
return e
}
}
return nil
2023-01-10 02:03:31 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) PlayLikedSongs(ctx *gctx.Context, position int) error {
err := c.ClearRadio(ctx)
2023-01-10 02:03:31 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
playlist, _, err := c.GetRadioPlaylist(ctx, "Saved Songs")
2023-01-10 02:03:31 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
songs, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset(position))
2023-01-10 02:03:31 +00:00
if err != nil {
return err
}
to_add := []spotify.ID{}
for _, song := range songs.Tracks {
to_add = append(to_add, song.ID)
}
2023-02-17 21:45:24 +00:00
_, err = c.Client().AddTracksToPlaylist(ctx, playlist.ID, to_add...)
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
2023-01-10 02:03:31 +00:00
PlaybackContext: &playlist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
})
if err != nil {
if isNoActiveError(err) {
2023-02-17 21:45:24 +00:00
deviceID, err := c.activateDevice(ctx)
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &playlist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
DeviceID: &deviceID,
})
if err != nil {
return err
}
}
}
2023-01-10 02:03:31 +00:00
for page := 2; page <= 5; page++ {
2023-02-17 21:45:24 +00:00
songs, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset((50*(page-1))+position))
2023-01-10 02:03:31 +00:00
if err != nil {
return err
}
to_add := []spotify.ID{}
for _, song := range songs.Tracks {
to_add = append(to_add, song.ID)
}
2023-02-17 21:45:24 +00:00
c.Client().AddTracksToPlaylist(ctx, playlist.ID, to_add...)
2023-01-10 02:03:31 +00:00
}
return err
}
2023-02-17 21:45:24 +00:00
func (c *Commands) RadioGivenArtist(ctx *gctx.Context, artist spotify.SimpleArtist) error {
2023-01-13 06:19:43 +00:00
seed := spotify.Seeds{
Artists: []spotify.ID{artist.ID},
2023-01-13 06:19:43 +00:00
}
2023-02-17 21:45:24 +00:00
recomendations, err := c.Client().GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
2023-01-13 06:19:43 +00:00
if err != nil {
return err
}
recomendationIds := []spotify.ID{}
for _, song := range recomendations.Tracks {
recomendationIds = append(recomendationIds, song.ID)
}
2023-02-17 21:45:24 +00:00
err = c.ClearRadio(ctx)
2023-01-13 06:19:43 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
radioPlaylist, db, err := c.GetRadioPlaylist(ctx, artist.Name)
2023-01-13 06:19:43 +00:00
if err != nil {
return err
}
queue := []spotify.ID{}
for _, rec := range recomendationIds {
2023-02-17 21:45:24 +00:00
exists, err := c.SongExists(db, rec)
if err != nil {
return err
}
if !exists {
_, err := db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(rec)))
if err != nil {
return err
}
queue = append(queue, rec)
}
2023-01-13 06:19:43 +00:00
}
2023-02-17 21:45:24 +00:00
_, err = c.Client().AddTracksToPlaylist(ctx, radioPlaylist.ID, queue...)
2023-01-13 06:19:43 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
c.Client().PlayOpt(ctx, &spotify.PlayOptions{
2023-01-13 06:19:43 +00:00
PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
})
2023-02-17 21:45:24 +00:00
err = c.Client().Repeat(ctx, "context")
2023-01-13 06:19:43 +00:00
if err != nil {
return err
}
for i := 0; i < 4; i++ {
2023-02-17 21:45:24 +00:00
id := frand.Intn(len(recomendationIds)-2) + 1
2023-01-13 06:19:43 +00:00
seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]},
}
2023-02-17 21:45:24 +00:00
additional_recs, err := c.Client().GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
2023-01-13 06:19:43 +00:00
if err != nil {
return err
}
additionalRecsIds := []spotify.ID{}
for _, song := range additional_recs.Tracks {
2023-02-17 21:45:24 +00:00
exists, err := c.SongExists(db, song.ID)
if err != nil {
return err
}
if !exists {
_, err = db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
if err != nil {
return err
}
additionalRecsIds = append(additionalRecsIds, song.ID)
}
2023-01-13 06:19:43 +00:00
}
2023-02-17 21:45:24 +00:00
_, err = c.Client().AddTracksToPlaylist(ctx, radioPlaylist.ID, additionalRecsIds...)
2023-01-13 06:19:43 +00:00
if err != nil {
return err
}
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) RadioGivenSong(ctx *gctx.Context, song spotify.SimpleTrack, pos int) error {
2023-01-09 05:08:51 +00:00
start := time.Now().UnixMilli()
2023-01-08 04:58:18 +00:00
seed := spotify.Seeds{
Tracks: []spotify.ID{song.ID},
2023-01-08 04:58:18 +00:00
}
2023-02-17 21:45:24 +00:00
recomendations, err := c.Client().GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
2023-01-08 04:58:18 +00:00
if err != nil {
return err
}
2023-01-08 07:57:33 +00:00
recomendationIds := []spotify.ID{}
2023-01-08 04:58:18 +00:00
for _, song := range recomendations.Tracks {
2023-01-08 07:57:33 +00:00
recomendationIds = append(recomendationIds, song.ID)
}
2023-02-17 21:45:24 +00:00
err = c.ClearRadio(ctx)
2023-01-08 16:33:53 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
radioPlaylist, db, err := c.GetRadioPlaylist(ctx, song.Name)
if err != nil {
return err
}
_, err = db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
2023-01-08 16:33:53 +00:00
if err != nil {
return err
}
queue := []spotify.ID{song.ID}
2023-01-12 02:12:29 +00:00
for _, rec := range recomendationIds {
2023-02-17 21:45:24 +00:00
exists, err := c.SongExists(db, rec)
if err != nil {
return err
}
if !exists {
_, err := db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(rec)))
if err != nil {
return err
}
queue = append(queue, rec)
}
2023-01-12 02:12:29 +00:00
}
2023-02-17 21:45:24 +00:00
_, err = c.Client().AddTracksToPlaylist(ctx, radioPlaylist.ID, queue...)
2023-01-08 07:57:33 +00:00
if err != nil {
return err
}
2023-01-09 05:08:51 +00:00
delay := time.Now().UnixMilli() - start
if pos != 0 {
pos = pos + int(delay)
}
2023-02-17 21:45:24 +00:00
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
2023-01-08 07:57:33 +00:00
PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
2023-01-09 05:08:51 +00:00
PositionMs: pos,
2023-01-08 07:57:33 +00:00
})
if err != nil {
if isNoActiveError(err) {
2023-02-17 21:45:24 +00:00
deviceID, err := c.activateDevice(ctx)
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
DeviceID: &deviceID,
PositionMs: pos,
})
if err != nil {
return err
}
}
}
2023-02-17 21:45:24 +00:00
err = c.Client().Repeat(ctx, "context")
2023-01-08 07:57:33 +00:00
if err != nil {
return err
2023-01-08 04:58:18 +00:00
}
for i := 0; i < 4; i++ {
2023-02-17 21:45:24 +00:00
id := frand.Intn(len(recomendationIds)-2) + 1
2023-01-08 07:57:33 +00:00
seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]},
}
2023-02-17 21:45:24 +00:00
additional_recs, err := c.Client().GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
2023-01-08 07:57:33 +00:00
if err != nil {
return err
}
additionalRecsIds := []spotify.ID{}
for _, song := range additional_recs.Tracks {
2023-02-17 21:45:24 +00:00
exists, err := c.SongExists(db, song.ID)
if err != nil {
return err
}
if !exists {
_, err = db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
if err != nil {
return err
}
additionalRecsIds = append(additionalRecsIds, song.ID)
}
2023-01-08 07:57:33 +00:00
}
2023-02-17 21:45:24 +00:00
_, err = c.Client().AddTracksToPlaylist(ctx, radioPlaylist.ID, additionalRecsIds...)
2023-01-08 07:57:33 +00:00
if err != nil {
return err
}
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) SongExists(db *sql.DB, song spotify.ID) (bool, error) {
song_id := string(song)
sqlStmt := `SELECT id FROM radio WHERE id = ?`
2023-04-07 01:11:06 +00:00
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
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Radio(ctx *gctx.Context) error {
current_song, err := c.Client().PlayerCurrentlyPlaying(ctx)
2023-01-09 03:44:35 +00:00
if err != nil {
return err
}
var seed_song spotify.SimpleTrack
if current_song.Item != nil {
seed_song = current_song.Item.SimpleTrack
}
if current_song.Item == nil {
2023-02-17 21:45:24 +00:00
_, err := c.activateDevice(ctx)
2023-01-09 03:44:35 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
tracks, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(10))
2023-01-09 03:44:35 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
seed_song = tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack
2023-04-07 01:11:06 +00:00
} else if !current_song.Playing {
tracks, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(10))
if err != nil {
return err
2023-01-09 03:44:35 +00:00
}
2023-04-07 01:11:06 +00:00
seed_song = tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack
2023-01-09 03:44:35 +00:00
}
2023-02-17 21:45:24 +00:00
return c.RadioGivenSong(ctx, seed_song, current_song.Progress)
2023-01-09 03:44:35 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) RefillRadio(ctx *gctx.Context) error {
status, err := c.Client().PlayerCurrentlyPlaying(ctx)
2023-01-08 07:57:33 +00:00
if err != nil {
return err
}
2023-01-10 21:16:38 +00:00
if !status.Playing {
return nil
}
2023-01-08 07:57:33 +00:00
to_remove := []spotify.ID{}
2023-02-17 21:45:24 +00:00
radioPlaylist, db, err := c.GetRadioPlaylist(ctx, "")
if err != nil {
2023-04-07 01:11:06 +00:00
return err
}
2023-01-10 21:16:38 +00:00
if status.PlaybackContext.URI != radioPlaylist.URI {
return nil
}
2023-02-17 21:45:24 +00:00
playlistItems, err := c.Client().GetPlaylistItems(ctx, radioPlaylist.ID)
2023-01-19 07:13:40 +00:00
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("orig playlist items: %w", err)
2023-01-19 07:13:40 +00:00
}
2023-01-08 07:57:33 +00:00
page := 0
2023-04-07 01:11:06 +00:00
for {
2023-02-17 21:45:24 +00:00
tracks, err := c.Client().GetPlaylistItems(ctx, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50))
2023-01-08 07:57:33 +00:00
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("tracks: %w", err)
}
if len(tracks.Items) == 0 {
break
2023-01-08 07:57:33 +00:00
}
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 {
2023-01-19 07:13:40 +00:00
var trackGroups []spotify.ID
for idx, item := range to_remove {
if idx%100 == 0 {
2023-02-17 21:45:24 +00:00
_, err = c.Client().RemoveTracksFromPlaylist(ctx, radioPlaylist.ID, trackGroups...)
2023-01-19 07:13:40 +00:00
trackGroups = []spotify.ID{}
}
trackGroups = append(trackGroups, item)
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("error clearing playlist: %w", err)
}
2023-01-08 07:57:33 +00:00
}
2023-04-07 01:11:06 +00:00
c.Client().RemoveTracksFromPlaylist(ctx, radioPlaylist.ID, trackGroups...)
2023-01-19 07:13:40 +00:00
}
2023-01-19 07:13:40 +00:00
to_add := 500 - (playlistItems.Total - len(to_remove))
2023-02-17 21:45:24 +00:00
playlistItems, err = c.Client().GetPlaylistItems(ctx, radioPlaylist.ID)
2023-01-19 07:13:40 +00:00
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("playlist items: %w", err)
2023-01-19 07:13:40 +00:00
}
total := playlistItems.Total
pages := int(math.Ceil(float64(total) / 50))
randomPage := 1
if pages > 1 {
2023-04-07 01:11:06 +00:00
randomPage = frand.Intn(pages-1) + 1
2023-01-19 07:13:40 +00:00
}
2023-02-17 21:45:24 +00:00
playlistPage, err := c.Client().GetPlaylistItems(ctx, radioPlaylist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50))
2023-01-19 07:13:40 +00:00
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("playlist page: %w", err)
2023-01-19 07:13:40 +00:00
}
pageSongs := playlistPage.Items
2023-02-17 21:45:24 +00:00
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
2023-01-19 07:13:40 +00:00
seedCount := 5
if len(pageSongs) < seedCount {
seedCount = len(pageSongs)
}
seedIds := []spotify.ID{}
for idx, song := range pageSongs {
if idx >= seedCount {
break
2023-01-08 07:57:33 +00:00
}
2023-01-19 07:13:40 +00:00
seedIds = append(seedIds, song.Track.Track.ID)
}
seed := spotify.Seeds{
Tracks: seedIds,
}
2023-02-17 21:45:24 +00:00
recomendations, err := c.Client().GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(95))
2023-01-19 07:13:40 +00:00
if err != nil {
return err
}
recomendationIds := []spotify.ID{}
for _, song := range recomendations.Tracks {
2023-02-17 21:45:24 +00:00
exists, err := c.SongExists(db, song.ID)
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("err check song existnce: %w", err)
}
if !exists {
recomendationIds = append(recomendationIds, song.ID)
}
2023-01-19 07:13:40 +00:00
}
queue := []spotify.ID{}
for idx, rec := range recomendationIds {
if idx > to_add {
break
}
_, err = db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", rec.String()))
2023-04-07 01:11:06 +00:00
if err != nil {
return err
}
2023-01-19 07:13:40 +00:00
queue = append(queue, rec)
}
to_add -= len(queue)
2023-02-17 21:45:24 +00:00
_, err = c.Client().AddTracksToPlaylist(ctx, radioPlaylist.ID, queue...)
2023-01-19 07:13:40 +00:00
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("add tracks: %w", err)
2023-01-19 07:13:40 +00:00
}
2023-02-17 21:45:24 +00:00
err = c.Client().Repeat(ctx, "context")
2023-01-19 07:13:40 +00:00
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("repeat: %w", err)
2023-01-19 07:13:40 +00:00
}
for to_add > 0 {
2023-02-17 21:45:24 +00:00
id := frand.Intn(len(recomendationIds)-2) + 1
2023-01-08 04:58:18 +00:00
seed := spotify.Seeds{
2023-01-19 07:13:40 +00:00
Tracks: []spotify.ID{recomendationIds[id]},
2023-01-08 04:58:18 +00:00
}
2023-02-17 21:45:24 +00:00
additional_recs, err := c.Client().GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
2023-01-08 04:58:18 +00:00
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("get recs: %w", err)
2023-01-08 04:58:18 +00:00
}
2023-01-19 07:13:40 +00:00
additionalRecsIds := []spotify.ID{}
for idx, song := range additional_recs.Tracks {
2023-02-17 21:45:24 +00:00
exists, err := c.SongExists(db, song.ID)
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("check song existence: %w", err)
}
if !exists {
2023-01-19 07:13:40 +00:00
if idx > to_add {
break
}
additionalRecsIds = append(additionalRecsIds, song.ID)
queue = append(queue, song.ID)
2023-01-08 07:57:33 +00:00
}
}
2023-01-19 07:13:40 +00:00
to_add -= len(queue)
2023-02-17 21:45:24 +00:00
_, err = c.Client().AddTracksToPlaylist(ctx, radioPlaylist.ID, additionalRecsIds...)
2023-01-08 07:57:33 +00:00
if err != nil {
2023-02-20 23:31:12 +00:00
return fmt.Errorf("add tracks to playlist: %w", err)
2023-01-08 04:58:18 +00:00
}
}
2023-01-08 07:57:33 +00:00
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) ClearRadio(ctx *gctx.Context) error {
radioPlaylist, db, err := c.GetRadioPlaylist(ctx, "")
2023-01-08 07:57:33 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().UnfollowPlaylist(ctx, radioPlaylist.ID)
2023-01-08 07:57:33 +00:00
if err != nil {
return err
}
db.Query("DROP TABLE IF EXISTS radio")
2023-01-08 07:57:33 +00:00
configDir, _ := os.UserConfigDir()
os.Remove(filepath.Join(configDir, "gospt/radio.json"))
2023-02-17 21:45:24 +00:00
c.Client().Pause(ctx)
2023-01-08 04:58:18 +00:00
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Devices(ctx *gctx.Context) error {
devices, err := c.Client().PlayerDevices(ctx)
2023-01-08 02:14:36 +00:00
if err != nil {
return err
}
return PrintDevices(devices)
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Pause(ctx *gctx.Context) error {
err := c.Client().Pause(ctx)
2023-01-08 00:57:51 +00:00
if err != nil {
return err
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) TogglePlay(ctx *gctx.Context) error {
current, err := c.Client().PlayerCurrentlyPlaying(ctx)
2023-01-08 18:00:00 +00:00
if err != nil {
return err
}
if !current.Playing {
2023-02-17 21:45:24 +00:00
return c.Play(ctx)
2023-01-08 18:00:00 +00:00
}
2023-02-17 21:45:24 +00:00
return c.Pause(ctx)
2023-01-08 18:00:00 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Like(ctx *gctx.Context) error {
playing, err := c.Client().PlayerCurrentlyPlaying(ctx)
2023-01-08 08:00:29 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().AddTracksToLibrary(ctx, playing.Item.ID)
2023-01-08 08:00:29 +00:00
if err != nil {
return err
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Unlike(ctx *gctx.Context) error {
playing, err := c.Client().PlayerCurrentlyPlaying(ctx)
2023-01-08 08:00:29 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().RemoveTracksFromLibrary(ctx, playing.Item.ID)
2023-01-08 08:00:29 +00:00
if err != nil {
return err
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Next(ctx *gctx.Context, amt int) error {
2023-01-17 05:02:23 +00:00
if amt == 1 {
2023-02-17 21:45:24 +00:00
err := c.Client().Next(ctx)
2023-01-17 05:02:23 +00:00
if err != nil {
if isNoActiveError(err) {
2023-02-17 21:45:24 +00:00
deviceId, err := c.activateDevice(ctx)
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().NextOpt(ctx, &spotify.PlayOptions{
DeviceID: &deviceId,
})
if err != nil {
return err
}
}
2023-01-17 05:02:23 +00:00
return err
}
return nil
}
// found := false
// playingIndex := 0
2023-02-17 21:45:24 +00:00
current, err := c.Client().PlayerCurrentlyPlaying(ctx)
2023-01-08 00:57:51 +00:00
if err != nil {
return err
}
2023-01-17 05:02:23 +00:00
playbackContext := current.PlaybackContext.Type
switch playbackContext {
case "playlist":
found := false
currentTrackIndex := 0
2023-04-07 01:11:06 +00:00
page := 1
2023-01-17 05:02:23 +00:00
for !found {
2023-02-17 21:45:24 +00:00
playlist, err := c.Client().GetPlaylistItems(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50))
2023-01-17 05:02:23 +00:00
if err != nil {
return err
}
for idx, track := range playlist.Items {
if track.Track.Track.ID == current.Item.ID {
currentTrackIndex = idx + (50 * (page - 1))
found = true
break
}
}
page++
}
2023-02-17 21:45:24 +00:00
c.Client().PlayOpt(ctx, &spotify.PlayOptions{
2023-01-17 05:02:23 +00:00
PlaybackContext: &current.PlaybackContext.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: currentTrackIndex + amt,
},
})
2023-01-17 05:06:20 +00:00
return nil
case "album":
found := false
currentTrackIndex := 0
2023-04-07 01:11:06 +00:00
page := 1
2023-01-17 05:06:20 +00:00
for !found {
2023-02-17 21:45:24 +00:00
playlist, err := c.Client().GetAlbumTracks(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50))
2023-01-17 05:06:20 +00:00
if err != nil {
return err
}
for idx, track := range playlist.Tracks {
if track.ID == current.Item.ID {
currentTrackIndex = idx + (50 * (page - 1))
found = true
break
}
}
page++
}
2023-02-17 21:45:24 +00:00
c.Client().PlayOpt(ctx, &spotify.PlayOptions{
2023-01-17 05:06:20 +00:00
PlaybackContext: &current.PlaybackContext.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: currentTrackIndex + amt,
},
})
return nil
2023-01-17 05:02:23 +00:00
default:
2023-01-17 05:06:20 +00:00
for i := 0; i < amt; i++ {
2023-02-17 21:45:24 +00:00
c.Client().Next(ctx)
2023-01-17 05:02:23 +00:00
}
}
2023-01-08 00:57:51 +00:00
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Previous(ctx *gctx.Context) error {
err := c.Client().Previous(ctx)
2023-01-08 18:00:00 +00:00
if err != nil {
return err
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Status(ctx *gctx.Context) error {
state, err := cache.DefaultCache().GetOrDo("state", func() (string, error) {
state, err := c.Client().PlayerState(ctx)
if err != nil {
return "", err
}
str, err := c.FormatState(state)
if err != nil {
return "", nil
}
return str, nil
}, 5*time.Second)
2023-01-08 00:57:51 +00:00
if err != nil {
return err
}
fmt.Println(state)
return nil
2023-01-08 00:57:51 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Link(ctx *gctx.Context) (string, error) {
state, err := c.Client().PlayerState(ctx)
2023-01-10 06:37:44 +00:00
if err != nil {
return "", err
}
return state.Item.ExternalURLs["spotify"], nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) LinkContext(ctx *gctx.Context) (string, error) {
state, err := c.Client().PlayerState(ctx)
2023-01-27 06:28:57 +00:00
if err != nil {
return "", err
}
2023-04-07 01:11:06 +00:00
return state.PlaybackContext.ExternalURLs["spotify"], nil
2023-01-27 06:28:57 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) NowPlaying(ctx *gctx.Context) error {
2023-02-17 21:31:19 +00:00
song, err := cache.DefaultCache().GetOrDo("now_playing", func() (string, error) {
2023-02-17 21:52:19 +00:00
current, err := c.Client().PlayerCurrentlyPlaying(ctx)
2023-02-17 21:31:19 +00:00
if err != nil {
return "", err
}
str := FormatSong(current)
return str, nil
}, 5*time.Second)
2023-01-09 04:04:58 +00:00
if err != nil {
return err
}
2023-02-17 21:31:19 +00:00
fmt.Println(song)
return nil
}
2023-02-17 21:52:19 +00:00
2023-02-17 21:31:19 +00:00
func FormatSong(current *spotify.CurrentlyPlaying) string {
icon := "▶"
if !current.Playing {
icon = "⏸"
}
return fmt.Sprintf("%s %s - %s", icon, current.Item.Name, current.Item.Artists[0].Name)
2023-01-09 04:04:58 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Shuffle(ctx *gctx.Context) error {
state, err := c.Client().PlayerState(ctx)
2023-01-08 00:57:51 +00:00
if err != nil {
2023-04-07 01:11:06 +00:00
return fmt.Errorf("failed to get current playstate")
2023-01-08 00:57:51 +00:00
}
2023-02-17 21:45:24 +00:00
err = c.Client().Shuffle(ctx, !state.ShuffleState)
2023-01-08 00:57:51 +00:00
if err != nil {
return err
}
ctx.Println("Shuffle set to", !state.ShuffleState)
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Repeat(ctx *gctx.Context) error {
state, err := c.Client().PlayerState(ctx)
2023-01-08 18:00:00 +00:00
if err != nil {
2023-04-07 01:11:06 +00:00
return fmt.Errorf("failed to get current playstate")
2023-01-08 18:00:00 +00:00
}
newState := "off"
if state.RepeatState == "off" {
newState = "context"
}
// spotifyd only supports binary value for repeat, context or off, change when/if spotifyd is better
2023-02-17 21:45:24 +00:00
err = c.Client().Repeat(ctx, newState)
2023-01-08 18:00:00 +00:00
if err != nil {
return err
}
ctx.Println("Repeat set to", newState)
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) TrackList(ctx *gctx.Context, page int) (*spotify.SavedTrackPage, error) {
return c.Client().CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
2023-01-08 00:57:51 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) GetQueue(ctx *gctx.Context) (*spotify.Queue, error) {
return c.Client().GetQueue(ctx)
2023-01-09 04:04:58 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) Playlists(ctx *gctx.Context, page int) (*spotify.SimplePlaylistPage, error) {
return c.Client().CurrentUsersPlaylists(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
2023-01-09 03:44:35 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) PlaylistTracks(ctx *gctx.Context, playlist spotify.ID, page int) (*spotify.PlaylistTrackPage, error) {
return c.Client().GetPlaylistTracks(ctx, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
2023-01-09 03:44:35 +00:00
}
func (c *Commands) FormatState(state *spotify.PlayerState) (string, error) {
2023-01-08 00:57:51 +00:00
state.Item.AvailableMarkets = []string{}
state.Item.Album.AvailableMarkets = []string{}
out, err := json.MarshalIndent(state, "", " ")
if err != nil {
return "", err
2023-01-08 00:57:51 +00:00
}
return (string(out)), nil
2023-01-08 00:57:51 +00:00
}
2023-01-08 02:14:36 +00:00
2023-02-17 21:45:24 +00:00
func (c *Commands) PrintPlaying(current *spotify.CurrentlyPlaying) error {
2023-01-19 22:48:50 +00:00
icon := "▶"
if !current.Playing {
icon = "⏸"
}
2023-04-07 01:11:06 +00:00
fmt.Printf("%s %s - %s\n", icon, current.Item.Name, current.Item.Artists[0].Name)
2023-01-09 04:04:58 +00:00
return nil
}
2023-01-08 02:14:36 +00:00
func PrintDevices(devices []spotify.PlayerDevice) error {
out, err := json.MarshalIndent(devices, "", " ")
if err != nil {
return err
}
fmt.Println(string(out))
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) SetDevice(ctx *gctx.Context, device spotify.PlayerDevice) error {
2023-01-08 18:00:00 +00:00
out, err := json.MarshalIndent(device, "", " ")
2023-01-08 02:14:36 +00:00
if err != nil {
return err
}
2023-01-08 18:00:00 +00:00
configDir, _ := os.UserConfigDir()
2023-04-07 01:11:06 +00:00
err = os.WriteFile(filepath.Join(configDir, "gospt/device.json"), out, 0o600)
2023-01-08 02:14:36 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
_, err = c.activateDevice(ctx)
2023-01-08 02:14:36 +00:00
if err != nil {
return err
}
return nil
}
func isNoActiveError(err error) bool {
return strings.Contains(err.Error(), "No active device found")
}
2023-02-17 21:45:24 +00:00
func (c *Commands) RadioFromPlaylist(ctx *gctx.Context, playlist spotify.SimplePlaylist) error {
2023-01-11 21:30:55 +00:00
total := playlist.Tracks.Total
if total == 0 {
2023-04-07 01:11:06 +00:00
return fmt.Errorf("this playlist is empty")
2023-01-11 21:30:55 +00:00
}
2023-01-16 17:59:56 +00:00
pages := int(math.Ceil(float64(total) / 50))
2023-01-13 06:19:43 +00:00
randomPage := 1
2023-01-16 17:59:56 +00:00
if pages > 1 {
2023-04-07 01:11:06 +00:00
randomPage = frand.Intn(pages-1) + 1
2023-01-11 21:30:55 +00:00
}
2023-02-17 21:45:24 +00:00
playlistPage, err := c.Client().GetPlaylistItems(ctx, playlist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50))
2023-01-08 07:57:33 +00:00
if err != nil {
return err
}
2023-01-11 21:30:55 +00:00
pageSongs := playlistPage.Items
2023-02-17 21:45:24 +00:00
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
2023-01-11 21:30:55 +00:00
seedCount := 5
if len(pageSongs) < seedCount {
seedCount = len(pageSongs)
2023-01-08 07:57:33 +00:00
}
2023-01-11 21:30:55 +00:00
seedIds := []spotify.ID{}
for idx, song := range pageSongs {
if idx >= seedCount {
break
}
seedIds = append(seedIds, song.Track.Track.ID)
}
2023-02-17 21:45:24 +00:00
return c.RadioGivenList(ctx, seedIds[:seedCount], playlist.Name)
2023-01-11 21:30:55 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) RadioFromAlbum(ctx *gctx.Context, album spotify.SimpleAlbum) error {
tracks, err := c.AlbumTracks(ctx, album.ID, 1)
2023-01-13 06:19:43 +00:00
if err != nil {
return err
}
total := tracks.Total
if total == 0 {
2023-04-07 01:11:06 +00:00
return fmt.Errorf("this playlist is empty")
2023-01-13 06:19:43 +00:00
}
2023-01-16 17:59:56 +00:00
pages := int(math.Ceil(float64(total) / 50))
2023-01-13 06:19:43 +00:00
randomPage := 1
2023-01-16 17:59:56 +00:00
if pages > 1 {
2023-04-07 01:11:06 +00:00
randomPage = frand.Intn(pages-1) + 1
2023-01-13 06:19:43 +00:00
}
2023-02-17 21:45:24 +00:00
albumTrackPage, err := c.AlbumTracks(ctx, album.ID, randomPage)
2023-01-13 06:19:43 +00:00
if err != nil {
return err
}
pageSongs := albumTrackPage.Tracks
2023-02-17 21:45:24 +00:00
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
2023-01-13 06:19:43 +00:00
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)
}
2023-02-17 21:45:24 +00:00
return c.RadioGivenList(ctx, seedIds[:seedCount], album.Name)
2023-01-13 06:19:43 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) RadioFromSavedTracks(ctx *gctx.Context) error {
savedSongs, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset(0))
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
if savedSongs.Total == 0 {
2023-04-07 01:11:06 +00:00
return fmt.Errorf("you have no saved songs")
2023-01-11 21:30:55 +00:00
}
2023-01-16 17:59:56 +00:00
pages := int(math.Ceil(float64(savedSongs.Total) / 50))
randomPage := 1
if pages > 1 {
2023-04-07 01:11:06 +00:00
randomPage = frand.Intn(pages-1) + 1
2023-01-16 17:59:56 +00:00
}
2023-02-17 21:45:24 +00:00
trackPage, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset(randomPage*50))
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
pageSongs := trackPage.Tracks
2023-02-17 21:45:24 +00:00
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
2023-01-11 21:30:55 +00:00
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)
2023-02-17 21:45:24 +00:00
return c.RadioGivenList(ctx, seedIds, "Saved Tracks")
2023-01-11 21:30:55 +00:00
}
2023-02-17 21:45:24 +00:00
func (c *Commands) RadioGivenList(ctx *gctx.Context, song_ids []spotify.ID, name string) error {
2023-01-11 21:30:55 +00:00
seed := spotify.Seeds{
Tracks: song_ids,
}
2023-02-17 21:45:24 +00:00
recomendations, err := c.Client().GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
recomendationIds := []spotify.ID{}
for _, song := range recomendations.Tracks {
recomendationIds = append(recomendationIds, song.ID)
}
2023-02-17 21:45:24 +00:00
err = c.ClearRadio(ctx)
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
radioPlaylist, db, err := c.GetRadioPlaylist(ctx, name)
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
queue := []spotify.ID{song_ids[0]}
2023-01-12 02:12:29 +00:00
for _, rec := range recomendationIds {
2023-02-17 21:45:24 +00:00
exists, err := c.SongExists(db, rec)
if err != nil {
return err
}
if !exists {
_, err := db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(rec)))
if err != nil {
return err
}
queue = append(queue, rec)
}
2023-01-12 02:12:29 +00:00
}
2023-02-17 21:45:24 +00:00
_, err = c.Client().AddTracksToPlaylist(ctx, radioPlaylist.ID, queue...)
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
2023-01-11 21:30:55 +00:00
PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
})
if err != nil {
if isNoActiveError(err) {
2023-02-17 21:45:24 +00:00
deviceId, err := c.activateDevice(ctx)
if err != nil {
return err
}
2023-02-17 21:45:24 +00:00
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
DeviceID: &deviceId,
})
if err != nil {
return err
}
}
2023-01-11 21:30:55 +00:00
}
for i := 0; i < 4; i++ {
2023-02-17 21:45:24 +00:00
id := frand.Intn(len(recomendationIds)-2) + 1
2023-01-11 21:30:55 +00:00
seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]},
}
2023-02-17 21:45:24 +00:00
additional_recs, err := c.Client().GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
additionalRecsIds := []spotify.ID{}
for _, song := range additional_recs.Tracks {
2023-02-17 21:45:24 +00:00
exists, err := c.SongExists(db, song.ID)
if err != nil {
return err
}
if !exists {
_, err = db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
if err != nil {
return err
}
2023-01-12 02:12:29 +00:00
additionalRecsIds = append(additionalRecsIds, song.ID)
}
2023-01-11 21:30:55 +00:00
}
2023-02-17 21:45:24 +00:00
_, err = c.Client().AddTracksToPlaylist(ctx, radioPlaylist.ID, additionalRecsIds...)
2023-01-11 21:30:55 +00:00
if err != nil {
return err
}
}
return nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) activateDevice(ctx *gctx.Context) (spotify.ID, error) {
var device *spotify.PlayerDevice
2023-01-08 18:00:00 +00:00
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"))
if err != nil {
return "", err
2023-01-08 18:00:00 +00:00
}
defer deviceFile.Close()
deviceValue, err := io.ReadAll(deviceFile)
2023-01-08 18:00:00 +00:00
if err != nil {
return "", err
2023-01-08 18:00:00 +00:00
}
err = json.Unmarshal(deviceValue, &device)
if err != nil {
return "", err
2023-01-08 18:00:00 +00:00
}
2023-02-17 21:45:24 +00:00
err = c.Client().TransferPlayback(ctx, device.ID, true)
2023-01-08 18:00:00 +00:00
if err != nil {
return "", err
2023-01-08 18:00:00 +00:00
}
} else {
fmt.Println("YOU MUST RUN gospt setdevice FIRST")
2023-01-08 07:57:33 +00:00
}
return device.ID, nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) GetRadioPlaylist(ctx *gctx.Context, name string) (*spotify.FullPlaylist, *sql.DB, error) {
2023-01-08 07:57:33 +00:00
configDir, _ := os.UserConfigDir()
playlistFile, err := os.ReadFile(filepath.Join(configDir, "gospt/radio.json"))
if errors.Is(err, os.ErrNotExist) {
2023-02-17 21:45:24 +00:00
return c.CreateRadioPlaylist(ctx, name)
}
if err != nil {
return nil, nil, err
2023-01-08 07:57:33 +00:00
}
var playlist *spotify.FullPlaylist
err = json.Unmarshal(playlistFile, &playlist)
if err != nil {
return nil, nil, err
}
db, err := sql.Open("sqlite", filepath.Join(configDir, "gospt/radio.db"))
2023-04-07 01:11:06 +00:00
if err != nil {
return nil, nil, err
}
return playlist, db, nil
}
2023-02-17 21:45:24 +00:00
func (c *Commands) CreateRadioPlaylist(ctx *gctx.Context, name string) (*spotify.FullPlaylist, *sql.DB, error) {
2023-01-09 02:47:11 +00:00
// private flag doesnt work
configDir, _ := os.UserConfigDir()
2023-02-17 21:52:19 +00:00
playlist, err := c.Client().CreatePlaylistForUser(ctx, c.User(), name+" - autoradio", "Automanaged radio playlist", false, false)
2023-01-08 07:57:33 +00:00
if err != nil {
return nil, nil, err
2023-01-08 07:57:33 +00:00
}
raw, err := json.MarshalIndent(playlist, "", " ")
2023-01-08 07:57:33 +00:00
if err != nil {
return nil, nil, err
2023-01-08 07:57:33 +00:00
}
2023-04-07 01:11:06 +00:00
err = os.WriteFile(filepath.Join(configDir, "gospt/radio.json"), raw, 0o600)
2023-01-08 07:57:33 +00:00
if err != nil {
return nil, nil, err
2023-01-08 07:57:33 +00:00
}
db, err := sql.Open("sqlite", filepath.Join(configDir, "gospt/radio.db"))
2023-04-07 01:11:06 +00:00
if err != nil {
return nil, nil, err
}
db.QueryContext(ctx, "DROP TABLE IF EXISTS radio")
db.QueryContext(ctx, "CREATE TABLE IF NOT EXISTS radio (id string PRIMARY KEY)")
return playlist, db, nil
2023-01-08 07:57:33 +00:00
}