more commands
This commit is contained in:
parent
039a8244f4
commit
5573d5e864
20
completions/zsh_autocomplete
Executable file
20
completions/zsh_autocomplete
Executable file
@ -0,0 +1,20 @@
|
||||
#compdef gospt-ng
|
||||
|
||||
_cli_zsh_autocomplete() {
|
||||
local -a opts
|
||||
local cur
|
||||
cur=${words[-1]}
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
||||
else
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
}
|
||||
|
||||
compdef _cli_zsh_autocomplete gospt-ng
|
5
go.mod
5
go.mod
@ -6,6 +6,7 @@ require (
|
||||
gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb
|
||||
git.asdf.cafe/abs3nt/gunner v0.0.1
|
||||
github.com/lmittmann/tint v1.0.4
|
||||
github.com/urfave/cli/v2 v2.27.1
|
||||
github.com/zmb3/spotify/v2 v2.4.1
|
||||
go.uber.org/fx v1.20.1
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
|
||||
@ -13,10 +14,14 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cristalhq/aconfig v0.18.5 // indirect
|
||||
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/joho/godotenv v1.4.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/dig v1.17.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
|
14
go.sum
14
go.sum
@ -45,6 +45,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
github.com/cristalhq/aconfig v0.18.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
|
||||
github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
@ -132,18 +134,22 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zmb3/spotify v1.3.0 h1:6Z2F1IMx0Hviq/dpf8nFwvKPppFEMXn8yfReSBVi16k=
|
||||
github.com/zmb3/spotify v1.3.0/go.mod h1:GD7AAEMUJVYc2Z7p2a2S0E3/5f/KxM/vOnErNr4j+Tw=
|
||||
github.com/zmb3/spotify/v2 v2.4.1 h1:2ENzO3XQLOQBuxgT1Z9+PlCBSkjNgzFzmRaPns0tjM4=
|
||||
github.com/zmb3/spotify/v2 v2.4.1/go.mod h1:p3r7mCCxHepzVaJOe3w1dlx9SL+T8iiQR14tfXJpuTE=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
|
13
main.go
13
main.go
@ -4,14 +4,21 @@ import (
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/app"
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/components/cli"
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/components/commands"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fx.New(
|
||||
var s fx.Shutdowner
|
||||
app := fx.New(
|
||||
fx.Populate(&s),
|
||||
app.Config,
|
||||
fx.Invoke(
|
||||
fx.Provide(
|
||||
commands.NewCommander,
|
||||
),
|
||||
).Run()
|
||||
fx.Invoke(
|
||||
cli.Run,
|
||||
),
|
||||
)
|
||||
app.Run()
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
var Services = fx.Options(
|
||||
fxplus.WithLogger,
|
||||
fx.NopLogger,
|
||||
fx.Provide(
|
||||
func() *slog.Logger {
|
||||
return slog.New(tint.NewHandler(os.Stdout, &tint.Options{
|
||||
|
104
src/components/cli/cli.go
Normal file
104
src/components/cli/cli.go
Normal file
@ -0,0 +1,104 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/components/commands"
|
||||
)
|
||||
|
||||
func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
defer func() {
|
||||
err := s.Shutdown()
|
||||
if err != nil {
|
||||
slog.Error("SHUTDOWN", "error shutting down", err)
|
||||
}
|
||||
}()
|
||||
app := &cli.App{
|
||||
EnableBashCompletion: true,
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "play",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Plays spotify",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.Play()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "pause",
|
||||
Aliases: []string{"pa"},
|
||||
Usage: "Pauses spotify",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.Pause()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "toggleplay",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Toggles play/pause",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.TogglePlay()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "link",
|
||||
Aliases: []string{"l"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.PrintLink()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "next",
|
||||
Aliases: []string{"n"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.Next()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "previous",
|
||||
Aliases: []string{"b"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.Previous()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "like",
|
||||
Aliases: []string{"lk"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.Like()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unlike",
|
||||
Aliases: []string{"ul"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.UnLike()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "nowplaying",
|
||||
Aliases: []string{"np"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.NowPlaying()
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "download_cover",
|
||||
Aliases: []string{"dc"},
|
||||
Args: true,
|
||||
ArgsUsage: "download_cover <path>",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.DownloadCover(cCtx.Args().First())
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
slog.Error("COMMANDER", "uh oh", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
39
src/components/commands/activate_device.go
Normal file
39
src/components/commands/activate_device.go
Normal file
@ -0,0 +1,39 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
func (c *Commander) activateDevice(ctx context.Context) (spotify.ID, error) {
|
||||
var device *spotify.PlayerDevice
|
||||
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 := io.ReadAll(deviceFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = json.Unmarshal(deviceValue, &device)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = c.Client.TransferPlayback(ctx, device.ID, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
slog.Error("COMMANDER", "failed to activated device", "YOU MUST RUN gospt setdevice FIRST")
|
||||
}
|
||||
return device.ID, nil
|
||||
}
|
@ -2,7 +2,6 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/zmb3/spotify/v2"
|
||||
"go.uber.org/fx"
|
||||
@ -31,10 +30,6 @@ func NewCommander(p CommanderParams) CommanderResult {
|
||||
Context: p.Context,
|
||||
Client: p.Client,
|
||||
}
|
||||
err := c.Play()
|
||||
if err != nil {
|
||||
slog.Error("Error playing", err)
|
||||
}
|
||||
return CommanderResult{
|
||||
Commander: c,
|
||||
}
|
||||
|
23
src/components/commands/downloadCover.go
Normal file
23
src/components/commands/downloadCover.go
Normal file
@ -0,0 +1,23 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func (c *Commander) DownloadCover(path string) error {
|
||||
destinationPath := filepath.Clean(path)
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(destinationPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = state.Item.Album.Images[0].Download(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
7
src/components/commands/errors.go
Normal file
7
src/components/commands/errors.go
Normal file
@ -0,0 +1,7 @@
|
||||
package commands
|
||||
|
||||
import "strings"
|
||||
|
||||
func isNoActiveError(err error) bool {
|
||||
return strings.Contains(err.Error(), "No active device found")
|
||||
}
|
9
src/components/commands/like.go
Normal file
9
src/components/commands/like.go
Normal file
@ -0,0 +1,9 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) Like() error {
|
||||
playing, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Client.AddTracksToLibrary(c.Context, playing.Item.ID)
|
||||
}
|
12
src/components/commands/link.go
Normal file
12
src/components/commands/link.go
Normal file
@ -0,0 +1,12 @@
|
||||
package commands
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (c *Commander) PrintLink() error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(state.Item.ExternalURLs["spotify"])
|
||||
return nil
|
||||
}
|
25
src/components/commands/next.go
Normal file
25
src/components/commands/next.go
Normal file
@ -0,0 +1,25 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
func (c *Commander) Next() error {
|
||||
err := c.Client.Next(c.Context)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceId, err := c.activateDevice(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.NextOpt(c.Context, &spotify.PlayOptions{
|
||||
DeviceID: &deviceId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
33
src/components/commands/nowPlaying.go
Normal file
33
src/components/commands/nowPlaying.go
Normal file
@ -0,0 +1,33 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
func (c *Commander) NowPlaying() error {
|
||||
current, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
str := FormatSong(current)
|
||||
fmt.Println(str)
|
||||
return nil
|
||||
}
|
||||
|
||||
func FormatSong(current *spotify.CurrentlyPlaying) string {
|
||||
out := "▶"
|
||||
if !current.Playing || current == nil {
|
||||
out = "⏸"
|
||||
}
|
||||
if current != nil {
|
||||
if current.Item != nil {
|
||||
out += fmt.Sprintf(" %s", current.Item.Name)
|
||||
if len(current.Item.Artists) > 0 {
|
||||
out += fmt.Sprintf(" - %s", current.Item.Artists[0].Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
5
src/components/commands/pause.go
Normal file
5
src/components/commands/pause.go
Normal file
@ -0,0 +1,5 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) Pause() error {
|
||||
return c.Client.Pause(c.Context)
|
||||
}
|
@ -1,5 +1,24 @@
|
||||
package commands
|
||||
|
||||
import "github.com/zmb3/spotify/v2"
|
||||
|
||||
func (c *Commander) Play() error {
|
||||
return c.Client.Play(c.Context)
|
||||
err := c.Client.Play(c.Context)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
25
src/components/commands/previous.go
Normal file
25
src/components/commands/previous.go
Normal file
@ -0,0 +1,25 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
func (c *Commander) Previous() error {
|
||||
err := c.Client.Previous(c.Context)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceId, err := c.activateDevice(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PreviousOpt(c.Context, &spotify.PlayOptions{
|
||||
DeviceID: &deviceId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
12
src/components/commands/toggle_play.go
Normal file
12
src/components/commands/toggle_play.go
Normal file
@ -0,0 +1,12 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) TogglePlay() error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if state.Playing {
|
||||
return c.Client.Pause(c.Context)
|
||||
}
|
||||
return c.Client.Play(c.Context)
|
||||
}
|
9
src/components/commands/unlike.go
Normal file
9
src/components/commands/unlike.go
Normal file
@ -0,0 +1,9 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) UnLike() error {
|
||||
playing, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Client.RemoveTracksFromLibrary(c.Context, playing.Item.ID)
|
||||
}
|
Loading…
Reference in New Issue
Block a user