more commands

This commit is contained in:
abs3nt 2024-02-18 01:30:19 -08:00
parent 039a8244f4
commit 5573d5e864
Signed by: abs3nt
GPG Key ID: A7BD96A8BAB04C09
20 changed files with 369 additions and 14 deletions

20
completions/zsh_autocomplete Executable file
View 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
View File

@ -6,6 +6,7 @@ require (
gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb
git.asdf.cafe/abs3nt/gunner v0.0.1 git.asdf.cafe/abs3nt/gunner v0.0.1
github.com/lmittmann/tint v1.0.4 github.com/lmittmann/tint v1.0.4
github.com/urfave/cli/v2 v2.27.1
github.com/zmb3/spotify/v2 v2.4.1 github.com/zmb3/spotify/v2 v2.4.1
go.uber.org/fx v1.20.1 go.uber.org/fx v1.20.1
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
@ -13,10 +14,14 @@ require (
) )
require ( require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cristalhq/aconfig v0.18.5 // indirect github.com/cristalhq/aconfig v0.18.5 // indirect
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/joho/godotenv v1.4.0 // 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/atomic v1.7.0 // indirect
go.uber.org/dig v1.17.0 // indirect go.uber.org/dig v1.17.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect

14
go.sum
View File

@ -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/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/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/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.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 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E= 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/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/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/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/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 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.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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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 h1:2ENzO3XQLOQBuxgT1Z9+PlCBSkjNgzFzmRaPns0tjM4=
github.com/zmb3/spotify/v2 v2.4.1/go.mod h1:p3r7mCCxHepzVaJOe3w1dlx9SL+T8iiQR14tfXJpuTE= github.com/zmb3/spotify/v2 v2.4.1/go.mod h1:p3r7mCCxHepzVaJOe3w1dlx9SL+T8iiQR14tfXJpuTE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=

BIN
gospt-ng Executable file

Binary file not shown.

13
main.go
View File

@ -4,14 +4,21 @@ import (
"go.uber.org/fx" "go.uber.org/fx"
"git.asdf.cafe/abs3nt/gospt-ng/src/app" "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" "git.asdf.cafe/abs3nt/gospt-ng/src/components/commands"
) )
func main() { func main() {
fx.New( var s fx.Shutdowner
app := fx.New(
fx.Populate(&s),
app.Config, app.Config,
fx.Invoke( fx.Provide(
commands.NewCommander, commands.NewCommander,
), ),
).Run() fx.Invoke(
cli.Run,
),
)
app.Run()
} }

View File

@ -14,7 +14,7 @@ import (
) )
var Services = fx.Options( var Services = fx.Options(
fxplus.WithLogger, fx.NopLogger,
fx.Provide( fx.Provide(
func() *slog.Logger { func() *slog.Logger {
return slog.New(tint.NewHandler(os.Stdout, &tint.Options{ return slog.New(tint.NewHandler(os.Stdout, &tint.Options{

104
src/components/cli/cli.go Normal file
View 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)
}
}

View 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
}

View File

@ -2,7 +2,6 @@ package commands
import ( import (
"context" "context"
"log/slog"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
"go.uber.org/fx" "go.uber.org/fx"
@ -31,10 +30,6 @@ func NewCommander(p CommanderParams) CommanderResult {
Context: p.Context, Context: p.Context,
Client: p.Client, Client: p.Client,
} }
err := c.Play()
if err != nil {
slog.Error("Error playing", err)
}
return CommanderResult{ return CommanderResult{
Commander: c, Commander: c,
} }

View 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
}

View File

@ -0,0 +1,7 @@
package commands
import "strings"
func isNoActiveError(err error) bool {
return strings.Contains(err.Error(), "No active device found")
}

View 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)
}

View 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
}

View 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
}

View 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
}

View File

@ -0,0 +1,5 @@
package commands
func (c *Commander) Pause() error {
return c.Client.Pause(c.Context)
}

View File

@ -1,5 +1,24 @@
package commands package commands
import "github.com/zmb3/spotify/v2"
func (c *Commander) Play() error { 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
} }

View 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
}

View 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)
}

View 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)
}