gospt/internal/commands/commands.go

694 lines
17 KiB
Go
Raw Permalink Normal View History

2023-01-08 00:57:51 +00:00
package commands
import (
"encoding/json"
"fmt"
2023-01-08 02:14:36 +00:00
"io/ioutil"
2023-01-08 04:58:18 +00:00
"math/rand"
2023-01-08 04:28:01 +00:00
"net/url"
2023-01-08 02:14:36 +00:00
"os"
"path/filepath"
"strings"
2023-01-08 04:58:18 +00:00
"time"
2023-01-08 00:57:51 +00:00
2023-01-08 05:26:06 +00:00
"gospt/internal/gctx"
2023-01-08 00:57:51 +00:00
"github.com/zmb3/spotify/v2"
)
2023-01-08 05:26:06 +00:00
func Play(ctx *gctx.Context, client *spotify.Client) error {
2023-01-08 04:28:01 +00:00
err := 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-01-08 18:00:00 +00:00
err := activateDevice(ctx, client)
if err != nil {
return err
}
err = client.Play(ctx)
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-01-08 05:26:06 +00:00
func PlayUrl(ctx *gctx.Context, client *spotify.Client, 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]
err = client.QueueSong(ctx, spotify.ID(track_id))
if err != nil {
if isNoActiveError(err) {
2023-01-08 18:00:00 +00:00
err := activateDevice(ctx, client)
if err != nil {
return err
}
err = client.QueueSong(ctx, spotify.ID(track_id))
2023-01-08 05:34:31 +00:00
if err != nil {
return err
}
err = client.Next(ctx)
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
}
}
err = 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-01-08 05:34:31 +00:00
func QueueSong(ctx *gctx.Context, client *spotify.Client, id spotify.ID) error {
err := client.QueueSong(ctx, id)
if err != nil {
if isNoActiveError(err) {
2023-01-08 18:00:00 +00:00
err := activateDevice(ctx, client)
if err != nil {
return err
}
err = client.QueueSong(ctx, id)
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-01-10 02:03:31 +00:00
func PlaySongInPlaylist(ctx *gctx.Context, client *spotify.Client, context *spotify.URI, offset int) error {
2023-01-11 21:30:55 +00:00
e := 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) {
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
2023-01-10 02:03:31 +00:00
}
func PlayLikedSongs(ctx *gctx.Context, client *spotify.Client, position int) error {
err := ClearRadio(ctx, client)
if err != nil {
return err
}
playlist, err := GetRadioPlaylist(ctx, client)
if err != nil {
return err
}
songs, err := client.CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset(position))
if err != nil {
return err
}
to_add := []spotify.ID{}
for _, song := range songs.Tracks {
to_add = append(to_add, song.ID)
}
client.AddTracksToPlaylist(ctx, playlist.ID, to_add...)
client.PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &playlist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
})
for page := 2; page <= 5; page++ {
songs, err := client.CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset((50*(page-1))+position))
if err != nil {
return err
}
to_add := []spotify.ID{}
for _, song := range songs.Tracks {
to_add = append(to_add, song.ID)
}
client.AddTracksToPlaylist(ctx, playlist.ID, to_add...)
}
return err
}
2023-01-09 05:08:51 +00:00
func RadioGivenSong(ctx *gctx.Context, client *spotify.Client, song_id spotify.ID, pos int) error {
start := time.Now().UnixMilli()
2023-01-08 04:58:18 +00:00
seed := spotify.Seeds{
2023-01-09 03:44:35 +00:00
Tracks: []spotify.ID{song_id},
2023-01-08 04:58:18 +00:00
}
2023-01-09 03:53:17 +00:00
recomendations, err := 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-01-08 16:33:53 +00:00
err = ClearRadio(ctx, client)
if err != nil {
return err
}
radioPlaylist, err := GetRadioPlaylist(ctx, client)
if err != nil {
return err
}
2023-01-09 03:53:17 +00:00
queue := []spotify.ID{song_id}
queue = append(queue, recomendationIds...)
_, err = 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-01-08 07:57:33 +00:00
client.PlayOpt(ctx, &spotify.PlayOptions{
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
})
err = client.Repeat(ctx, "context")
if err != nil {
return err
2023-01-08 04:58:18 +00:00
}
for i := 0; i < 4; i++ {
2023-01-08 07:57:33 +00:00
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
}
2023-01-09 03:44:35 +00:00
func Radio(ctx *gctx.Context, client *spotify.Client) error {
rand.Seed(time.Now().Unix())
current_song, err := client.PlayerCurrentlyPlaying(ctx)
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 {
err := activateDevice(ctx, client)
if err != nil {
return err
}
2023-01-10 00:36:12 +00:00
2023-01-09 03:44:35 +00:00
tracks, err := client.CurrentUsersTracks(ctx, spotify.Limit(10))
if err != nil {
return err
}
seed_song = tracks.Tracks[rand.Intn(len(tracks.Tracks))].SimpleTrack
} else {
if !current_song.Playing {
2023-01-10 00:36:12 +00:00
2023-01-09 03:44:35 +00:00
tracks, err := client.CurrentUsersTracks(ctx, spotify.Limit(10))
if err != nil {
return err
}
seed_song = tracks.Tracks[rand.Intn(len(tracks.Tracks))].SimpleTrack
}
}
2023-01-09 05:08:51 +00:00
return RadioGivenSong(ctx, client, seed_song.ID, current_song.Progress)
2023-01-09 03:44:35 +00:00
}
2023-01-08 07:57:33 +00:00
func RefillRadio(ctx *gctx.Context, client *spotify.Client) error {
status, err := client.PlayerCurrentlyPlaying(ctx)
if err != nil {
return err
}
2023-01-10 21:16:38 +00:00
if !status.Playing {
fmt.Println("Nothing is playing")
return nil
}
2023-01-08 07:57:33 +00:00
to_remove := []spotify.ID{}
radioPlaylist, err := GetRadioPlaylist(ctx, client)
2023-01-10 21:16:38 +00:00
if status.PlaybackContext.URI != radioPlaylist.URI {
fmt.Println("You are not playing the radio, please run gospt radio to start")
return nil
}
2023-01-08 07:57:33 +00:00
found := false
page := 0
for !found {
tracks, err := client.GetPlaylistItems(ctx, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50))
if err != nil {
return err
}
for _, track := range tracks.Items {
if track.Track.Track.ID == status.Item.ID {
found = true
break
}
to_remove = append(to_remove, track.Track.Track.ID)
}
page++
}
recomendationIds := []spotify.ID{}
if len(to_remove) > 0 {
_, err = client.RemoveTracksFromPlaylist(ctx, radioPlaylist.ID, to_remove...)
if err != nil {
return err
}
current_song, err := client.PlayerCurrentlyPlaying(ctx)
if err != nil {
return err
}
2023-01-08 04:58:18 +00:00
seed := spotify.Seeds{
2023-01-08 07:57:33 +00:00
Tracks: []spotify.ID{current_song.Item.ID},
2023-01-08 04:58:18 +00:00
}
2023-01-08 07:57:33 +00:00
recomendations, err := client.GetRecommendations(ctx, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
2023-01-08 04:58:18 +00:00
if err != nil {
return err
}
2023-01-08 07:57:33 +00:00
for idx, song := range recomendations.Tracks {
2023-01-10 00:36:12 +00:00
if idx > len(to_remove) {
2023-01-08 07:57:33 +00:00
break
}
recomendationIds = append(recomendationIds, song.ID)
}
_, err = client.AddTracksToPlaylist(ctx, radioPlaylist.ID, recomendationIds...)
if err != nil {
return err
2023-01-08 04:58:18 +00:00
}
}
2023-01-08 07:57:33 +00:00
return nil
}
func ClearRadio(ctx *gctx.Context, client *spotify.Client) error {
radioPlaylist, err := GetRadioPlaylist(ctx, client)
if err != nil {
2023-01-10 00:36:12 +00:00
fmt.Println(err)
2023-01-08 07:57:33 +00:00
return err
}
err = client.UnfollowPlaylist(ctx, radioPlaylist.ID)
if err != nil {
return err
}
configDir, _ := os.UserConfigDir()
os.Remove(filepath.Join(configDir, "gospt/radio.json"))
client.Pause(ctx)
2023-01-08 04:58:18 +00:00
return nil
}
2023-01-08 05:26:06 +00:00
func Devices(ctx *gctx.Context, client *spotify.Client) error {
2023-01-08 02:14:36 +00:00
devices, err := client.PlayerDevices(ctx)
if err != nil {
return err
}
return PrintDevices(devices)
}
2023-01-08 05:26:06 +00:00
func Pause(ctx *gctx.Context, client *spotify.Client) error {
2023-01-08 00:57:51 +00:00
err := client.Pause(ctx)
if err != nil {
return err
}
return nil
}
2023-01-08 18:00:00 +00:00
func TogglePlay(ctx *gctx.Context, client *spotify.Client) error {
current, err := client.PlayerCurrentlyPlaying(ctx)
if err != nil {
return err
}
if !current.Playing {
return Play(ctx, client)
}
return Pause(ctx, client)
}
2023-01-08 08:00:29 +00:00
func Like(ctx *gctx.Context, client *spotify.Client) error {
playing, err := client.PlayerCurrentlyPlaying(ctx)
if err != nil {
return err
}
err = client.AddTracksToLibrary(ctx, playing.Item.ID)
if err != nil {
return err
}
return nil
}
func Unlike(ctx *gctx.Context, client *spotify.Client) error {
playing, err := client.PlayerCurrentlyPlaying(ctx)
if err != nil {
return err
}
err = client.RemoveTracksFromLibrary(ctx, playing.Item.ID)
if err != nil {
return err
}
return nil
}
2023-01-08 05:26:06 +00:00
func Skip(ctx *gctx.Context, client *spotify.Client) error {
2023-01-08 00:57:51 +00:00
err := client.Next(ctx)
if err != nil {
return err
}
return nil
}
2023-01-08 18:00:00 +00:00
func Previous(ctx *gctx.Context, client *spotify.Client) error {
err := client.Previous(ctx)
if err != nil {
return err
}
return nil
}
2023-01-08 05:26:06 +00:00
func Status(ctx *gctx.Context, client *spotify.Client) error {
2023-01-08 00:57:51 +00:00
state, err := client.PlayerState(ctx)
if err != nil {
return err
}
return PrintState(state)
}
2023-01-10 06:37:44 +00:00
func Link(ctx *gctx.Context, client *spotify.Client) (string, error) {
state, err := client.PlayerState(ctx)
if err != nil {
return "", err
}
return state.Item.ExternalURLs["spotify"], nil
}
2023-01-09 04:04:58 +00:00
func NowPlaying(ctx *gctx.Context, client *spotify.Client) error {
current, err := client.PlayerCurrentlyPlaying(ctx)
if err != nil {
return err
}
return PrintPlaying(current)
}
2023-01-08 05:26:06 +00:00
func Shuffle(ctx *gctx.Context, client *spotify.Client) error {
2023-01-08 00:57:51 +00:00
state, err := client.PlayerState(ctx)
if err != nil {
return fmt.Errorf("Failed to get current playstate")
}
err = client.Shuffle(ctx, !state.ShuffleState)
if err != nil {
return err
}
ctx.Println("Shuffle set to", !state.ShuffleState)
return nil
}
2023-01-08 18:00:00 +00:00
func Repeat(ctx *gctx.Context, client *spotify.Client) error {
state, err := client.PlayerState(ctx)
if err != nil {
return fmt.Errorf("Failed to get current playstate")
}
newState := "off"
if state.RepeatState == "off" {
newState = "context"
}
// spotifyd only supports binary value for repeat, context or off, change when/if spotifyd is better
err = client.Repeat(ctx, newState)
if err != nil {
return err
}
ctx.Println("Repeat set to", newState)
return nil
}
2023-01-08 05:26:06 +00:00
func TrackList(ctx *gctx.Context, client *spotify.Client, page int) (*spotify.SavedTrackPage, error) {
2023-01-08 00:57:51 +00:00
return client.CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
}
2023-01-09 04:04:58 +00:00
func GetQueue(ctx *gctx.Context, client *spotify.Client) (*spotify.Queue, error) {
return client.GetQueue(ctx)
}
2023-01-09 03:44:35 +00:00
func Playlists(ctx *gctx.Context, client *spotify.Client, page int) (*spotify.SimplePlaylistPage, error) {
return client.CurrentUsersPlaylists(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
}
func PlaylistTracks(ctx *gctx.Context, client *spotify.Client, playlist spotify.ID, page int) (*spotify.PlaylistTrackPage, error) {
return client.GetPlaylistTracks(ctx, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
}
2023-01-08 00:57:51 +00:00
func PrintState(state *spotify.PlayerState) error {
state.Item.AvailableMarkets = []string{}
state.Item.Album.AvailableMarkets = []string{}
out, err := json.MarshalIndent(state, "", " ")
if err != nil {
return err
}
fmt.Println(string(out))
return nil
}
2023-01-08 02:14:36 +00:00
2023-01-09 04:04:58 +00:00
func PrintPlaying(current *spotify.CurrentlyPlaying) error {
fmt.Println(fmt.Sprintf("%s by %s", current.Item.Name, current.Item.Artists[0].Name))
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-01-08 18:00:00 +00:00
func SetDevice(ctx *gctx.Context, client *spotify.Client, device spotify.PlayerDevice) error {
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()
err = ioutil.WriteFile(filepath.Join(configDir, "gospt/device.json"), out, 0o644)
2023-01-08 02:14:36 +00:00
if err != nil {
return err
}
2023-01-08 18:00:00 +00:00
err = activateDevice(ctx, client)
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-01-11 21:30:55 +00:00
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))
2023-01-08 07:57:33 +00:00
if err != nil {
return err
}
2023-01-11 21:30:55 +00:00
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)
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)
}
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 {
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
}
defer deviceFile.Close()
deviceValue, err := ioutil.ReadAll(deviceFile)
if err != nil {
return err
}
var device *spotify.PlayerDevice
err = json.Unmarshal(deviceValue, &device)
if err != nil {
return err
}
2023-01-11 21:30:55 +00:00
err = client.TransferPlayback(ctx, device.ID, true)
2023-01-08 18:00:00 +00:00
if err != nil {
return err
}
} else {
fmt.Println("YOU MUST RUN gospt setdevice FIRST")
2023-01-08 07:57:33 +00:00
}
return nil
}
func GetRadioPlaylist(ctx *gctx.Context, client *spotify.Client) (*spotify.FullPlaylist, error) {
configDir, _ := os.UserConfigDir()
if _, err := os.Stat(filepath.Join(configDir, "gospt/radio.json")); err == nil {
playlistFile, err := os.Open(filepath.Join(configDir, "gospt/radio.json"))
if err != nil {
return nil, err
}
defer playlistFile.Close()
playlistValue, err := ioutil.ReadAll(playlistFile)
if err != nil {
return nil, err
}
var playlist *spotify.FullPlaylist
err = json.Unmarshal(playlistValue, &playlist)
if err != nil {
return nil, err
}
return playlist, nil
}
2023-01-09 02:47:11 +00:00
// private flag doesnt work
2023-01-10 00:36:12 +00:00
playlist, err := client.CreatePlaylistForUser(ctx, ctx.UserId, "autoradio", "Automanaged radio playlist", false, false)
2023-01-08 07:57:33 +00:00
if err != nil {
return nil, err
}
out, err := json.MarshalIndent(playlist, "", " ")
if err != nil {
return nil, err
}
err = ioutil.WriteFile(filepath.Join(configDir, "gospt/radio.json"), out, 0o644)
if err != nil {
return nil, err
}
return playlist, nil
}