Compare commits

...

25 Commits

Author SHA1 Message Date
2a28c74caf
tview
All checks were successful
builder / build (push) Successful in 38s
2024-05-31 18:16:20 -07:00
c1ac26e684
update
All checks were successful
builder / build (push) Successful in 1m17s
2024-05-30 15:19:52 -07:00
8a21c7cb52
error out on invalid arguments
All checks were successful
builder / build (push) Successful in 1m10s
deployer / go-releaser (push) Successful in 2m3s
2024-04-07 10:43:58 -07:00
a7152075fa
job names
All checks were successful
builder / build (push) Successful in 23s
deployer / go-releaser (push) Successful in 55s
2024-04-06 11:17:37 -07:00
c28448c295
Gitea token
All checks were successful
build-binary / Build (push) Successful in 23s
deployer / Build (push) Successful in 1m1s
2024-04-06 10:48:25 -07:00
ec69919ec6
Gitea token
All checks were successful
build-binary / Build (push) Successful in 21s
deployer / Build (push) Successful in 1m41s
2024-04-06 10:45:34 -07:00
dd2284770d
remove
Some checks failed
build-binary / Build (push) Successful in 1m1s
deployer / Build (push) Failing after 25s
2024-04-06 10:42:15 -07:00
52dbe52c50
maybe
Some checks failed
build-binary / Build (push) Has been cancelled
ci/woodpecker/push/woodpecker Pipeline was successful
2024-04-06 10:41:58 -07:00
bdec590110
build
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Build / Build (push) Successful in 1m23s
2024-04-06 10:28:14 -07:00
b516cb8d2f
build
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
Build / Build (push) Failing after 2s
2024-04-06 09:46:14 -07:00
bef9898bf2
better completions
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-04-02 21:30:26 -07:00
786ee38671
build
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-03-29 18:40:06 -07:00
823774f7bd
dependency bump 2024-03-29 16:49:10 -07:00
ba07377ca8
chore: dep updates 2024-03-26 12:23:17 -07:00
dc2c74dd4b
move
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-02-23 12:48:32 -08:00
c26d11299a
make border full terminal size
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-02-21 22:13:09 -08:00
c1d4a0b5e0
fix
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-02-20 18:58:40 -08:00
338bef9f75
toggle
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline failed
2024-02-20 09:00:57 -08:00
bafaa8a941
make
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-02-19 18:00:40 -08:00
e6344bef01
cleanup
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-02-19 17:58:05 -08:00
ae4291032e
license
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-19 17:21:17 -08:00
ad77e043d6
readme
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-19 13:33:36 -08:00
2116052d5e
auto retry
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-19 10:26:44 -08:00
1faea3fd7f
v3
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-19 10:11:47 -08:00
f6f7d3bd70
get client only when needed
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-02-18 23:33:32 -08:00
43 changed files with 948 additions and 565 deletions

View File

@ -0,0 +1,15 @@
name: builder
run-name: ${{ gitea.actor }} is building
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: "1.22"
cache: true
- run: go mod tidy
- run: go build -o gspot

View File

@ -0,0 +1,28 @@
name: deployer
run-name: ${{ gitea.actor }} is releasing
on:
push:
tags:
- "*"
jobs:
go-releaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup up go
uses: actions/setup-go@v5
with:
go-version: '1.22'
cache: true
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: nightly
args: release --clean
env:
GITEA_TOKEN: ${{ secrets.ACCESS_TOKEN_GITEA}}

View File

@ -30,31 +30,29 @@ archives:
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
format_overrides:
- goos: windows
format: zip
files:
- completions/*
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
name_template: "checksums.txt"
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
groups:
- title: Added
regexp: '^.*?ADD(\([[:word:]]+\))??!?:.+$'
order: 0
- title: 'Bug fixes'
regexp: '^.*?BUG(\([[:word:]]+\))??!?:.+$'
order: 1
- title: 'Enhancements'
regexp: '^.*?IMPROVED(\([[:word:]]+\))??!?:.+$'
order: 1
- title: 'Docs'
regexp: '^.*?DOC(\([[:word:]]+\))??!?:.+$'
order: 1
- title: 'CI'
regexp: '^.*?CI(\([[:word:]]+\))??!?:.+$'
order: 1
- title: Others
order: 999
- title: Added
regexp: '^.*?ADD(\([[:word:]]+\))??!?:.+$'
order: 0
- title: "Bug fixes"
regexp: '^.*?BUG(\([[:word:]]+\))??!?:.+$'
order: 1
- title: "Enhancements"
regexp: '^.*?IMPROVED(\([[:word:]]+\))??!?:.+$'
order: 1
- title: "Docs"
regexp: '^.*?DOC(\([[:word:]]+\))??!?:.+$'
order: 1
- title: "CI"
regexp: '^.*?CI(\([[:word:]]+\))??!?:.+$'
order: 1
- title: Others
order: 999

View File

@ -1,14 +0,0 @@
steps:
build:
image: golang:1.22
commands:
- go mod tidy
- go build -o gspot
publish:
image: goreleaser/goreleaser:nightly
commands:
- goreleaser release --clean
secrets: [ gitea_token ]
when:
event: tag

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 abs3nt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,16 +1,14 @@
build: gspot
gspot: $(shell find . -name '*.go')
build:
go build -ldflags="-X 'git.asdf.cafe/abs3nt/gspot/src/components/cli.Version=$(shell git show -s --date=short --pretty='format:%h (%ad)' HEAD)'" -o dist/ .
run:
go run main.go
run: build
./dist/gspot
tidy:
go mod tidy
clean:
rm -rf bin
rm -rf dist
uninstall:
rm -f /usr/bin/gspot

83
README.md Normal file
View File

@ -0,0 +1,83 @@
IF YOU ARE ON GITHUB.COM GO HERE INSTEAD: https://git.asdf.cafe/abs3nt/gspot :)
If you open an issue or PR on github I won't see it please use git.asdf.cafe. Register on asdf and open your PRs there
This project is still under heavy development and some things might not work or not work as intended. Don't hesitate to open an issue to let me know.
---
[![status-badge](https://ci.asdf.cafe/api/badges/abs3nt/gspot/status.svg)](https://ci.asdf.cafe/abs3nt/gspot)
# To install (with a package manager):
## Archlinux ([AUR])
```yay -S gspot-git```
# To build from source by pulling and building the binary
```git clone https://git.asdf.cafe/abs3nt/gspot```
```cd gspot```
```make build && sudo make install```
[AUR]: https://aur.archlinux.org/packages/gspot-git
# Configuration
go here https://developer.spotify.com/dashboard/applications to make a spotify application. you will need a client ID and a client secret. Set your redirect uri like this:
```http://localhost:8888/callback```
add your information to ~/.config/gspot/gspot.yml like this
```
client_id: "idgoeshere"
client_secret: "secretgoeshere"
port: "8888"
```
if you dont want to store your secret in the file in plaintext you can use a command to retreive it:
```
client_secret_cmd: "secret spotify_secret"
```
you should have either client_secret or client_secret_cmd
you can enable debug logging by adding
```
log_level: "debug"
log_output: "file"
```
it will log to ~/.config/gspot/gspot.log
## RUNNING
```gspot```
you will be asked to login, you will only have to do this the first time. After login you will be asked to select your default device.
helpful keybinds are shown in the bottom of the screen, hit ? to see all of them
To use the custom radio feature:
```gspot radio```
or hit ctrl+r on any track in the TUI. This will start an extended radio. To replenish the current radio run ```gspot refillradio``` and all the songs already listened will be removed and that number of new recomendations will be added.
This radio uses slightly different logic than the standard spotify radio to give a longer playlist and more recomendation. With a cronjob you can schedule refill to run to have an infinite and morphing radio station.
To view help:
```gspot --help```
Very open to contributations feel free to open a PR
[tmux plugin](https://git.asdf.cafe/abs3nt/tmux-gspot)
[wiki](https://git.asdf.cafe/abs3nt/gspot/wiki)

View File

@ -1,20 +1,16 @@
#compdef gspot
_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
local -a opts
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}")
else
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}")
fi
if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts
else
_files
fi
}
compdef _cli_zsh_autocomplete gspot
if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts
else
_files
fi

99
go.mod
View File

@ -1,6 +1,6 @@
module git.asdf.cafe/abs3nt/gspot
go 1.22.0
go 1.22.3
require (
gfx.cafe/util/go/frand v0.0.0-20231226111635-bc00a6a250fb
@ -8,41 +8,50 @@ require (
git.asdf.cafe/abs3nt/gunner v0.0.1
github.com/atotto/clipboard v0.1.4
github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.9.1
github.com/charmbracelet/bubbletea v0.26.4
github.com/charmbracelet/lipgloss v0.11.0
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
golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.10.0
golang.org/x/sync v0.3.0
google.golang.org/api v0.133.0
modernc.org/sqlite v1.29.1
github.com/urfave/cli/v3 v3.0.0-alpha9
github.com/zmb3/spotify/v2 v2.4.2
go.uber.org/fx v1.22.0
golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0
golang.org/x/net v0.25.0
golang.org/x/oauth2 v0.20.0
golang.org/x/sync v0.7.0
google.golang.org/api v0.182.0
modernc.org/sqlite v1.29.10
)
require (
cloud.google.com/go/compute v1.22.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/auth v0.5.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/charmbracelet/x/ansi v0.1.2 // indirect
github.com/charmbracelet/x/input v0.1.1 // indirect
github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.2 // indirect
github.com/cristalhq/aconfig v0.18.5 // indirect
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.7.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.4 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/joho/godotenv v1.4.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
@ -50,30 +59,32 @@ require (
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/rivo/tview v0.0.0-20240524063012-037df494fb76 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.opencensus.io v0.24.0 // 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
go.uber.org/zap v1.23.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895 // indirect
google.golang.org/grpc v1.56.2 // indirect
google.golang.org/protobuf v1.31.0 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.41.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
go.opentelemetry.io/otel v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
go.uber.org/dig v1.17.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b // indirect
modernc.org/libc v1.50.9 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect

277
go.sum
View File

@ -13,16 +13,18 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/auth v0.5.0 h1:GtSZfKJkPrZi/s3AkiHnUYVI4dTP/kg8+I3unm0omag=
cloud.google.com/go/auth v0.5.0/go.mod h1:Kqvlz1cf1sNA0D+sYJnkPQOP+JMHkuHeIgVmCRtZOLc=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.22.0 h1:cB8R6FtUtT1TYGl5R3xuxnW6OUIc/DrT2aiR16TTG7Y=
cloud.google.com/go/compute v1.22.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@ -45,37 +47,32 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40=
github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/input v0.1.1 h1:YDOJaTUKCqtGnq9PHzx3pkkl4pXDOANUHmhH3DqMtM4=
github.com/charmbracelet/x/input v0.1.1/go.mod h1:jvdTVUnNWj/RD6hjC4FsoB0SeZCJ2ZBkiuFP9zXvZI0=
github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI=
github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw=
github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg=
github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
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/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
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=
@ -89,13 +86,23 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc=
github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -125,8 +132,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -138,8 +145,9 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -149,51 +157,50 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg=
github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@ -204,45 +211,43 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/tview v0.0.0-20240524063012-037df494fb76 h1:iqvDlgyjmqleATtFbA7c14djmPh2n4mCYUv7JlD/ruA=
github.com/rivo/tview v0.0.0-20240524063012-037df494fb76/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2 h1:tcc3ZFBvjydcgrAxavZRYqFqCKzy0FJ+UY4ATq4QVXk=
github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
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/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo=
github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
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/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.2 h1:j3yNN5lKVEMZQItJF4MHCSZbfNWmXO+KaC+3RFaLlLc=
github.com/zmb3/spotify/v2 v2.4.2/go.mod h1:XOV7BrThayFYB9AAfB+L0Q0wyxBuLCARk4fI/ZXCBW8=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -250,28 +255,34 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI=
go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU=
go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk=
go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc=
go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
go.uber.org/fx v1.22.0 h1:pApUK7yL0OUHMd8vkunWSlLxZVFFk70jR2nKde8X2NM=
go.uber.org/fx v1.22.0/go.mod h1:HT2M7d7RHo+ebKGh9NRcrsrHHfpZ60nW3QRubMRfv48=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -282,8 +293,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 h1:Mi0bCswbz+9cXmwFAdxoo5GPFMKONUpua6iUdtQS7lk=
golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -306,8 +317,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -336,20 +347,21 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -360,8 +372,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -389,35 +401,36 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -463,8 +476,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -485,15 +498,14 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.133.0 h1:N7Ym5Hl0Dpn0I0o7R1z4UpVA1GCDyS8vbPu1/ObV73A=
google.golang.org/api v0.133.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk=
google.golang.org/api v0.182.0 h1:if5fPvudRQ78GeRx3RayIoiuV7modtErPIZC/T2bIvE=
google.golang.org/api v0.182.0/go.mod h1:cGhjy4caqA5yXRzEhkHI8Y9mfyC2VLTlER2l08xaqtM=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -518,19 +530,16 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU=
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895 h1:co8AMhI481nhd3WBfW2mq5msyQHNBcGn7G9GCEqz45k=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No=
google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -543,12 +552,9 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -561,15 +567,14 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@ -580,16 +585,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=
modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b h1:BnN1t+pb1cy61zbvSUV7SeI0PwosMhlAEi/vBY4qxp8=
modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.50.9 h1:hIWf1uz55lorXQhfoEoezdUHjxzuO6ceshET/yWjSjk=
modernc.org/libc v1.50.9/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA=
modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.29.10 h1:3u93dz83myFnMilBGCOLbr+HjklS6+5rJLx4q86RDAg=
modernc.org/sqlite v1.29.10/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@ -21,7 +21,6 @@ func main() {
fxplus.Context,
cache.NewCache,
commands.NewCommander,
services.NewSpotifyClient,
logger.NewLogger,
),
fx.Invoke(

View File

@ -1,26 +1,32 @@
package cli
import (
"context"
"fmt"
"os"
"strconv"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"github.com/zmb3/spotify/v2"
"go.uber.org/fx"
"git.asdf.cafe/abs3nt/gspot/src/components/commands"
"git.asdf.cafe/abs3nt/gspot/src/components/tui"
"git.asdf.cafe/abs3nt/gspot/src/components/tuitview"
)
var Version = "dev"
func Run(c *commands.Commander, s fx.Shutdowner) {
app := &cli.App{
Name: "gspot",
EnableBashCompletion: true,
Version: Version,
Action: func(ctx *cli.Context) error {
app := &cli.Command{
Name: "gspot",
EnableShellCompletion: true,
Version: Version,
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unknown command: %s", strings.Join(cmd.Args().Slice(), " "))
}
return tui.StartTea(c, "main")
},
Commands: []*cli.Command{
@ -28,7 +34,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "play",
Aliases: []string{"pl", "start", "s"},
Usage: "Plays spotify",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Play()
},
Category: "Playback",
@ -37,10 +46,15 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "playurl",
Aliases: []string{"plu"},
Usage: "Plays a spotify url",
Args: true,
ArgsUsage: "url",
Action: func(ctx *cli.Context) error {
return c.PlayUrl(ctx.Args().First())
Action: func(ctx context.Context, cmd *cli.Command) error {
if !cmd.Args().Present() {
return fmt.Errorf("no url provided")
}
if cmd.NArg() > 1 {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.PlayURL(cmd.Args().First())
},
Category: "Playback",
},
@ -48,7 +62,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "pause",
Aliases: []string{"pa"},
Usage: "Pauses spotify",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Pause()
},
Category: "Playback",
@ -57,7 +74,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "toggleplay",
Aliases: []string{"t"},
Usage: "Toggles play/pause",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.TogglePlay()
},
Category: "Playback",
@ -66,7 +86,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "link",
Aliases: []string{"yy"},
Usage: "Prints the current song's spotify link",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.PrintLink()
},
Category: "Sharing",
@ -75,7 +98,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "linkcontext",
Aliases: []string{"lc"},
Usage: "Prints the current album or playlist",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.PrintLinkContext()
},
Category: "Sharing",
@ -84,7 +110,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "youtube-link",
Aliases: []string{"yl"},
Usage: "Prints the current song's youtube link",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.PrintYoutubeLink()
},
Category: "Sharing",
@ -93,11 +122,13 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "next",
Aliases: []string{"n", "skip"},
Usage: "Skips to the next song",
Args: true,
ArgsUsage: "amount",
Action: func(cCtx *cli.Context) error {
if cCtx.NArg() > 0 {
amt, err := strconv.Atoi(cCtx.Args().First())
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() > 1 {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
if cmd.NArg() > 0 {
amt, err := strconv.Atoi(cmd.Args().First())
if err != nil {
return err
}
@ -111,7 +142,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "previous",
Aliases: []string{"b", "prev", "back"},
Usage: "Skips to the previous song",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Previous()
},
Category: "Playback",
@ -120,7 +154,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "like",
Aliases: []string{"l"},
Usage: "Likes the current song",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Like()
},
Category: "Library Management",
@ -129,7 +166,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "unlike",
Aliases: []string{"u"},
Usage: "Unlikes the current song",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.UnLike()
},
Category: "Library Management",
@ -146,8 +186,11 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Usage: "bypass cache",
},
},
Action: func(cCtx *cli.Context) error {
return c.NowPlaying(cCtx.Bool("force"))
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.NowPlaying(cmd.Bool("force"))
},
Category: "Info",
},
@ -156,14 +199,16 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Aliases: []string{"v"},
Usage: "Control the volume",
Category: "Playback",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
{
Name: "up",
Usage: "Increase the volume",
Args: true,
ArgsUsage: "percent",
Action: func(cCtx *cli.Context) error {
amt, err := strconv.Atoi(cCtx.Args().First())
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() > 1 {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
amt, err := strconv.Atoi(cmd.Args().First())
if err != nil {
return err
}
@ -174,10 +219,12 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "down",
Aliases: []string{"dn"},
Usage: "Decrease the volume",
Args: true,
ArgsUsage: "percent",
Action: func(cCtx *cli.Context) error {
amt, err := strconv.Atoi(cCtx.Args().First())
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() > 1 {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
amt, err := strconv.Atoi(cmd.Args().First())
if err != nil {
return err
}
@ -188,7 +235,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "mute",
Aliases: []string{"m"},
Usage: "Mute",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Mute()
},
},
@ -196,7 +246,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "unmute",
Aliases: []string{"um"},
Usage: "Unmute",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.UnMute()
},
},
@ -204,7 +257,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "togglemute",
Aliases: []string{"tm"},
Usage: "Toggle mute",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.ToggleMute()
},
},
@ -214,15 +270,17 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "download_cover",
Usage: "Downloads the cover of the current song",
Aliases: []string{"dl"},
Args: true,
ArgsUsage: "path",
BashComplete: func(cCtx *cli.Context) {
if cCtx.NArg() > 0 {
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
if cmd.NArg() > 0 {
return
}
},
Action: func(cCtx *cli.Context) error {
return c.DownloadCover(cCtx.Args().First())
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() > 1 {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.DownloadCover(cmd.Args().First())
},
Category: "Info",
},
@ -230,7 +288,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "radio",
Usage: "Starts a radio from the current song",
Aliases: []string{"r"},
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Radio()
},
Category: "Radio",
@ -239,7 +300,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "clearradio",
Usage: "Clears the radio queue",
Aliases: []string{"cr"},
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.ClearRadio()
},
Category: "Radio",
@ -248,7 +312,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "refillradio",
Usage: "Refills the radio queue with similar songs",
Aliases: []string{"rr"},
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.RefillRadio()
},
Category: "Radio",
@ -256,7 +323,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
{
Name: "status",
Usage: "Prints the current status",
Action: func(ctx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Status()
},
Category: "Info",
@ -265,7 +335,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "devices",
Usage: "Lists available devices",
Aliases: []string{"d"},
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.ListDevices()
},
Category: "Info",
@ -273,20 +346,25 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
{
Name: "setdevice",
Usage: "Set the active device",
Args: true,
ArgsUsage: "<device_id>",
Action: func(cCtx *cli.Context) error {
if cCtx.NArg() == 0 {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() == 0 {
return fmt.Errorf("no device id provided")
}
return c.SetDevice(spotify.ID(cCtx.Args().First()))
if cmd.NArg() > 1 {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.SetDevice(spotify.ID(cmd.Args().First()))
},
Category: "Playback",
},
{
Name: "repeat",
Usage: "Toggle repeat mode",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Repeat()
},
Category: "Playback",
@ -294,7 +372,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
{
Name: "shuffle",
Usage: "Toggle shuffle mode",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Shuffle()
},
Category: "Playback",
@ -302,28 +383,48 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
{
Name: "tui",
Usage: "Starts the TUI",
Action: func(ctx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return tui.StartTea(c, "main")
},
},
{
Name: "tview",
Usage: "Starts the TUI using tview (experimental)",
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
// start tview tui
return tuitview.TuitView(c)
},
},
{
Name: "seek",
Usage: "Seek to a position in the song",
Aliases: []string{"sk"},
Category: "Playback",
Action: func(cCtx *cli.Context) error {
pos, err := strconv.Atoi(cCtx.Args().First())
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() > 1 {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
pos, err := strconv.Atoi(cmd.Args().First())
if err != nil {
return err
}
return c.SetPosition(pos)
},
Subcommands: []*cli.Command{
Commands: []*cli.Command{
{
Name: "forward",
Aliases: []string{"f"},
Usage: "Seek forward",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Seek(true)
},
},
@ -331,7 +432,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
Name: "backward",
Aliases: []string{"b"},
Usage: "Seek backward",
Action: func(cCtx *cli.Context) error {
Action: func(ctx context.Context, cmd *cli.Command) error {
if cmd.Args().Present() {
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
}
return c.Seek(false)
},
},
@ -339,7 +443,7 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
},
},
}
if err := app.Run(os.Args); err != nil {
if err := app.Run(c.Context, os.Args); err != nil {
c.Log.Error("COMMANDER", "run error", err)
s.Shutdown(fx.ExitCode(1))
}

View File

@ -26,7 +26,7 @@ func (c *Commander) activateDevice() (spotify.ID, error) {
if err != nil {
return "", err
}
err = c.Client.TransferPlayback(c.Context, device.ID, true)
err = c.Client().TransferPlayback(c.Context, device.ID, true)
if err != nil {
return "", err
}

View File

@ -5,7 +5,7 @@ import (
)
func (c *Commander) AlbumTracks(album spotify.ID, page int) (*spotify.SimpleTrackPage, error) {
tracks, err := c.Client.
tracks, err := c.Client().
GetAlbumTracks(c.Context, album, spotify.Limit(50), spotify.Offset((page-1)*50), spotify.Market(spotify.CountryUSA))
if err != nil {
return nil, err
@ -14,5 +14,5 @@ func (c *Commander) AlbumTracks(album spotify.ID, page int) (*spotify.SimpleTrac
}
func (c *Commander) UserAlbums(page int) (*spotify.SavedAlbumPage, error) {
return c.Client.CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
return c.Client().CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
}

View File

@ -3,7 +3,7 @@ package commands
import "github.com/zmb3/spotify/v2"
func (c *Commander) ArtistAlbums(artist spotify.ID, page int) (*spotify.SimpleAlbumPage, error) {
albums, err := c.Client.
albums, err := c.Client().
GetArtistAlbums(c.Context, artist, []spotify.AlbumType{1, 2, 3, 4}, spotify.Market(spotify.CountryUSA), spotify.Limit(50), spotify.Offset((page-1)*50))
if err != nil {
return nil, err

View File

@ -3,12 +3,14 @@ package commands
import (
"context"
"log/slog"
"os"
"sync"
"github.com/zmb3/spotify/v2"
"go.uber.org/fx"
"git.asdf.cafe/abs3nt/gspot/src/components/cache"
"git.asdf.cafe/abs3nt/gspot/src/config"
"git.asdf.cafe/abs3nt/gspot/src/services"
)
type CommanderResult struct {
@ -21,33 +23,53 @@ type CommanderParams struct {
fx.In
Context context.Context
Client *spotify.Client
Log *slog.Logger
Cache *cache.Cache
Config *config.Config
}
type Commander struct {
Context context.Context
Client *spotify.Client
User *spotify.PrivateUser
Log *slog.Logger
Cache *cache.Cache
mu sync.RWMutex
cl *spotify.Client
conf *config.Config
}
func NewCommander(p CommanderParams) CommanderResult {
currentUser, err := p.Client.CurrentUser(p.Context)
if err != nil {
slog.Error("COMMANDER", "error getting current user", err)
os.Exit(1)
}
c := &Commander{
Context: p.Context,
Client: p.Client,
User: currentUser,
Log: p.Log,
Cache: p.Cache,
conf: p.Config,
}
return CommanderResult{
Commander: c,
}
}
func (c *Commander) 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
}
func (c *Commander) connectClient() *spotify.Client {
client, err := services.GetClient(c.conf)
if err != nil {
panic(err)
}
currentUser, err := client.CurrentUser(c.Context)
if err != nil {
panic(err)
}
c.User = currentUser
return client
}

View File

@ -10,7 +10,7 @@ import (
)
func (c *Commander) ListDevices() error {
devices, err := c.Client.PlayerDevices(c.Context)
devices, err := c.Client().PlayerDevices(c.Context)
if err != nil {
return err
}
@ -27,11 +27,11 @@ func PrintDevices(devices []spotify.PlayerDevice) error {
}
func (c *Commander) SetDevice(device spotify.ID) error {
err := c.Client.TransferPlayback(c.Context, device, true)
err := c.Client().TransferPlayback(c.Context, device, true)
if err != nil {
return err
}
devices, err := c.Client.PlayerDevices(c.Context)
devices, err := c.Client().PlayerDevices(c.Context)
if err != nil {
return err
}

View File

@ -10,7 +10,7 @@ func (c *Commander) DownloadCover(path string) error {
path = "cover.png"
}
destinationPath := filepath.Clean(path)
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return err
}

View File

@ -1,9 +1,9 @@
package commands
func (c *Commander) Like() error {
playing, err := c.Client.PlayerCurrentlyPlaying(c.Context)
playing, err := c.Client().PlayerCurrentlyPlaying(c.Context)
if err != nil {
return err
}
return c.Client.AddTracksToLibrary(c.Context, playing.Item.ID)
return c.Client().AddTracksToLibrary(c.Context, playing.Item.ID)
}

View File

@ -3,7 +3,7 @@ package commands
import "fmt"
func (c *Commander) PrintLink() error {
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return err
}
@ -12,7 +12,7 @@ func (c *Commander) PrintLink() error {
}
func (c *Commander) PrintLinkContext() error {
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return err
}

View File

@ -9,7 +9,7 @@ import (
func (c *Commander) Next(amt int, inqueue bool) error {
if inqueue {
for i := 0; i < amt; i++ {
err := c.Client.Next(c.Context)
err := c.Client().Next(c.Context)
if err != nil {
return err
}
@ -17,15 +17,15 @@ func (c *Commander) Next(amt int, inqueue bool) error {
return nil
}
if amt == 1 {
err := c.Client.Next(c.Context)
err := c.Client().Next(c.Context)
if err != nil {
if isNoActiveError(err) {
deviceId, err := c.activateDevice()
deviceID, err := c.activateDevice()
if err != nil {
return err
}
err = c.Client.NextOpt(c.Context, &spotify.PlayOptions{
DeviceID: &deviceId,
err = c.Client().NextOpt(c.Context, &spotify.PlayOptions{
DeviceID: &deviceID,
})
if err != nil {
return err
@ -37,7 +37,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
}
// found := false
// playingIndex := 0
current, err := c.Client.PlayerCurrentlyPlaying(c.Context)
current, err := c.Client().PlayerCurrentlyPlaying(c.Context)
if err != nil {
return err
}
@ -48,7 +48,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
currentTrackIndex := 0
page := 1
for !found {
playlist, err := c.Client.
playlist, err := c.Client().
GetPlaylistItems(
c.Context,
spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]),
@ -68,7 +68,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
page++
}
pos := currentTrackIndex + amt
return c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
return c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &current.PlaybackContext.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: &pos,
@ -79,7 +79,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
currentTrackIndex := 0
page := 1
for !found {
playlist, err := c.Client.
playlist, err := c.Client().
GetAlbumTracks(
c.Context,
spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]),
@ -99,7 +99,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
page++
}
pos := currentTrackIndex + amt
return c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
return c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &current.PlaybackContext.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: &pos,
@ -107,7 +107,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
})
default:
for i := 0; i < amt; i++ {
err := c.Client.Next(c.Context)
err := c.Client().Next(c.Context)
if err != nil {
return err
}

View File

@ -9,7 +9,7 @@ import (
func (c *Commander) NowPlaying(force bool) error {
if force {
current, err := c.Client.PlayerCurrentlyPlaying(c.Context)
current, err := c.Client().PlayerCurrentlyPlaying(c.Context)
if err != nil {
return err
}
@ -19,7 +19,7 @@ func (c *Commander) NowPlaying(force bool) error {
return err
}
song, err := c.Cache.GetOrDo("now_playing", func() (string, error) {
current, err := c.Client.PlayerCurrentlyPlaying(c.Context)
current, err := c.Client().PlayerCurrentlyPlaying(c.Context)
if err != nil {
return "", err
}

View File

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

View File

@ -1,6 +1,7 @@
package commands
import (
"fmt"
"net/url"
"strings"
@ -8,14 +9,14 @@ import (
)
func (c *Commander) Play() error {
err := c.Client.Play(c.Context)
err := c.Client().Play(c.Context)
if err != nil {
if isNoActiveError(err) {
deviceID, err := c.activateDevice()
if err != nil {
return err
}
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
DeviceID: &deviceID,
})
if err != nil {
@ -39,20 +40,20 @@ func (c *Commander) PlayLikedSongs(position int) error {
return err
}
c.Log.Debug("got playlist", "id", playlist.ID)
songs, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(position))
songs, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(position))
if err != nil {
return err
}
to_add := []spotify.ID{}
toAdd := []spotify.ID{}
for _, song := range songs.Tracks {
to_add = append(to_add, song.ID)
toAdd = append(toAdd, song.ID)
}
_, err = c.Client.AddTracksToPlaylist(c.Context, playlist.ID, to_add...)
_, err = c.Client().AddTracksToPlaylist(c.Context, playlist.ID, toAdd...)
if err != nil {
return err
}
c.Log.Debug("added songs to playlist")
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &playlist.URI,
})
if err != nil {
@ -62,7 +63,7 @@ func (c *Commander) PlayLikedSongs(position int) error {
if err != nil {
return err
}
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &playlist.URI,
DeviceID: &deviceID,
})
@ -74,15 +75,15 @@ func (c *Commander) PlayLikedSongs(position int) error {
c.Log.Debug("starting loop")
for page := 2; page <= 5; page++ {
c.Log.Debug("doing loop", "page", page)
songs, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((50*(page-1))+position))
songs, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((50*(page-1))+position))
if err != nil {
return err
}
to_add := []spotify.ID{}
toAdd := []spotify.ID{}
for _, song := range songs.Tracks {
to_add = append(to_add, song.ID)
toAdd = append(toAdd, song.ID)
}
_, err = c.Client.AddTracksToPlaylist(c.Context, playlist.ID, to_add...)
_, err = c.Client().AddTracksToPlaylist(c.Context, playlist.ID, toAdd...)
if err != nil {
return err
}
@ -91,26 +92,30 @@ func (c *Commander) PlayLikedSongs(position int) error {
return err
}
func (c *Commander) PlayUrl(urlString string) error {
func (c *Commander) PlayURL(urlString string) error {
url, err := url.Parse(urlString)
if err != nil {
return err
}
track_id := strings.Split(url.Path, "/")[2]
err = c.Client.QueueSong(c.Context, spotify.ID(track_id))
splittUrl := strings.Split(url.Path, "/")
if len(splittUrl) < 3 {
return fmt.Errorf("invalid url")
}
trackID := splittUrl[2]
err = c.Client().QueueSong(c.Context, spotify.ID(trackID))
if err != nil {
if isNoActiveError(err) {
deviceID, err := c.activateDevice()
if err != nil {
return err
}
err = c.Client.QueueSongOpt(c.Context, spotify.ID(track_id), &spotify.PlayOptions{
err = c.Client().QueueSongOpt(c.Context, spotify.ID(trackID), &spotify.PlayOptions{
DeviceID: &deviceID,
})
if err != nil {
return err
}
err = c.Client.NextOpt(c.Context, &spotify.PlayOptions{
err = c.Client().NextOpt(c.Context, &spotify.PlayOptions{
DeviceID: &deviceID,
})
if err != nil {
@ -121,7 +126,7 @@ func (c *Commander) PlayUrl(urlString string) error {
return err
}
}
err = c.Client.Next(c.Context)
err = c.Client().Next(c.Context)
if err != nil {
return err
}
@ -129,7 +134,7 @@ func (c *Commander) PlayUrl(urlString string) error {
}
func (c *Commander) PlaySongInPlaylist(context *spotify.URI, offset *int) error {
e := c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
e := c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
PlaybackContext: context,
})
@ -139,7 +144,7 @@ func (c *Commander) PlaySongInPlaylist(context *spotify.URI, offset *int) error
if err != nil {
return err
}
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
PlaybackContext: context,
DeviceID: &deviceID,
@ -150,7 +155,7 @@ func (c *Commander) PlaySongInPlaylist(context *spotify.URI, offset *int) error
if err != nil {
return err
}
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
PlaybackContext: context,
DeviceID: &deviceID,
@ -160,7 +165,7 @@ func (c *Commander) PlaySongInPlaylist(context *spotify.URI, offset *int) error
}
}
}
err = c.Client.Play(c.Context)
err = c.Client().Play(c.Context)
if err != nil {
return err
}

View File

@ -3,15 +3,15 @@ package commands
import "github.com/zmb3/spotify/v2"
func (c *Commander) Playlists(page int) (*spotify.SimplePlaylistPage, error) {
return c.Client.CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
return c.Client().CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
}
func (c *Commander) PlaylistTracks(playlist spotify.ID, page int) (*spotify.PlaylistItemPage, error) {
return c.Client.GetPlaylistItems(c.Context, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
return c.Client().GetPlaylistItems(c.Context, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
}
func (c *Commander) DeleteTracksFromPlaylist(tracks []spotify.ID, playlist spotify.ID) error {
_, err := c.Client.RemoveTracksFromPlaylist(c.Context, playlist, tracks...)
_, err := c.Client().RemoveTracksFromPlaylist(c.Context, playlist, tracks...)
if err != nil {
return err
}
@ -19,5 +19,5 @@ func (c *Commander) DeleteTracksFromPlaylist(tracks []spotify.ID, playlist spoti
}
func (c *Commander) TrackList(page int) (*spotify.SavedTrackPage, error) {
return c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
return c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
}

View File

@ -5,15 +5,15 @@ import (
)
func (c *Commander) Previous() error {
err := c.Client.Previous(c.Context)
err := c.Client().Previous(c.Context)
if err != nil {
if isNoActiveError(err) {
deviceId, err := c.activateDevice()
deviceID, err := c.activateDevice()
if err != nil {
return err
}
err = c.Client.PreviousOpt(c.Context, &spotify.PlayOptions{
DeviceID: &deviceId,
err = c.Client().PreviousOpt(c.Context, &spotify.PlayOptions{
DeviceID: &deviceID,
})
if err != nil {
return err

View File

@ -3,14 +3,14 @@ package commands
import "github.com/zmb3/spotify/v2"
func (c *Commander) QueueSong(id spotify.ID) error {
err := c.Client.QueueSong(c.Context, id)
err := c.Client().QueueSong(c.Context, id)
if err != nil {
if isNoActiveError(err) {
deviceID, err := c.activateDevice()
if err != nil {
return err
}
err = c.Client.QueueSongOpt(c.Context, id, &spotify.PlayOptions{
err = c.Client().QueueSongOpt(c.Context, id, &spotify.PlayOptions{
DeviceID: &deviceID,
})
if err != nil {

View File

@ -16,18 +16,18 @@ import (
)
func (c *Commander) Radio() error {
current_song, err := c.Client.PlayerCurrentlyPlaying(c.Context)
currentSong, err := c.Client().PlayerCurrentlyPlaying(c.Context)
if err != nil {
return err
}
if current_song.Item != nil {
return c.RadioGivenSong(current_song.Item.SimpleTrack, current_song.Progress)
if currentSong.Item != nil {
return c.RadioGivenSong(currentSong.Item.SimpleTrack, currentSong.Progress)
}
_, err = c.activateDevice()
if err != nil {
return err
}
tracks, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(10))
tracks, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(10))
if err != nil {
return err
}
@ -44,7 +44,7 @@ func (c *Commander) RadioFromPlaylist(playlist spotify.SimplePlaylist) error {
if pages > 1 {
randomPage = frand.Intn(pages-1) + 1
}
playlistPage, err := c.Client.GetPlaylistItems(
playlistPage, err := c.Client().GetPlaylistItems(
c.Context,
playlist.ID,
spotify.Limit(50),
@ -70,7 +70,7 @@ func (c *Commander) RadioFromPlaylist(playlist spotify.SimplePlaylist) error {
}
func (c *Commander) RadioFromSavedTracks() error {
savedSongs, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
savedSongs, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
if err != nil {
return err
}
@ -82,7 +82,7 @@ func (c *Commander) RadioFromSavedTracks() error {
if pages > 1 {
randomPage = frand.Intn(pages-1) + 1
}
trackPage, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(randomPage*50))
trackPage, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(randomPage*50))
if err != nil {
return err
}
@ -104,7 +104,7 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
seed := spotify.Seeds{
Artists: []spotify.ID{artist.ID},
}
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
if err != nil {
return err
}
@ -134,17 +134,17 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
queue = append(queue, rec)
}
}
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
if err != nil {
return err
}
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI,
})
if err != nil {
return err
}
err = c.Client.Repeat(c.Context, "context")
err = c.Client().Repeat(c.Context, "context")
if err != nil {
return err
}
@ -153,12 +153,13 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]},
}
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
additionalRecs, err := c.Client().
GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
if err != nil {
return err
}
additionalRecsIds := []spotify.ID{}
for _, song := range additional_recs.Tracks {
for _, song := range additionalRecs.Tracks {
exists, err := c.SongExists(db, song.ID)
if err != nil {
return err
@ -171,7 +172,7 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
additionalRecsIds = append(additionalRecsIds, song.ID)
}
}
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
if err != nil {
return err
}
@ -179,12 +180,12 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
return nil
}
func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos spotify.Numeric) error {
start := time.Now().UnixMilli()
seed := spotify.Seeds{
Tracks: []spotify.ID{song.ID},
}
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
if err != nil {
return err
}
@ -218,15 +219,15 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
queue = append(queue, rec)
}
}
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
if err != nil {
return err
}
delay := time.Now().UnixMilli() - start
if pos != 0 {
pos = pos + int(delay)
pos = pos + spotify.Numeric(delay)
}
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI,
PositionMs: pos,
})
@ -236,7 +237,7 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
if err != nil {
return err
}
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI,
DeviceID: &deviceID,
PositionMs: pos,
@ -246,7 +247,7 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
}
}
}
err = c.Client.Repeat(c.Context, "context")
err = c.Client().Repeat(c.Context, "context")
if err != nil {
return err
}
@ -255,12 +256,13 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]},
}
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
additionalRecs, err := c.Client().
GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
if err != nil {
return err
}
additionalRecsIds := []spotify.ID{}
for _, song := range additional_recs.Tracks {
for _, song := range additionalRecs.Tracks {
exists, err := c.SongExists(db, song.ID)
if err != nil {
return err
@ -273,7 +275,7 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
additionalRecsIds = append(additionalRecsIds, song.ID)
}
}
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
if err != nil {
return err
}
@ -286,14 +288,14 @@ func (c *Commander) ClearRadio() error {
if err != nil {
return err
}
err = c.Client.UnfollowPlaylist(c.Context, radioPlaylist.ID)
err = c.Client().UnfollowPlaylist(c.Context, radioPlaylist.ID)
if err != nil {
return err
}
_, _ = db.Query("DROP TABLE IF EXISTS radio")
configDir, _ := os.UserConfigDir()
os.Remove(filepath.Join(configDir, "gspot/radio.json"))
_ = c.Client.Pause(c.Context)
_ = c.Client().Pause(c.Context)
return nil
}
@ -321,7 +323,7 @@ func (c *Commander) GetRadioPlaylist(name string) (*spotify.FullPlaylist, *sql.D
func (c *Commander) CreateRadioPlaylist(name string) (*spotify.FullPlaylist, *sql.DB, error) {
// private flag doesnt work
configDir, _ := os.UserConfigDir()
playlist, err := c.Client.
playlist, err := c.Client().
CreatePlaylistForUser(c.Context, c.User.ID, name+" - autoradio", "Automanaged radio playlist", false, false)
if err != nil {
return nil, nil, err
@ -344,9 +346,9 @@ func (c *Commander) CreateRadioPlaylist(name string) (*spotify.FullPlaylist, *sq
}
func (c *Commander) SongExists(db *sql.DB, song spotify.ID) (bool, error) {
song_id := string(song)
songID := string(song)
sqlStmt := `SELECT id FROM radio WHERE id = ?`
err := db.QueryRow(sqlStmt, song_id).Scan(&song_id)
err := db.QueryRow(sqlStmt, songID).Scan(&songID)
if err != nil {
if err != sql.ErrNoRows {
return false, err
@ -359,14 +361,14 @@ func (c *Commander) SongExists(db *sql.DB, song spotify.ID) (bool, error) {
}
func (c *Commander) RefillRadio() error {
status, err := c.Client.PlayerCurrentlyPlaying(c.Context)
status, err := c.Client().PlayerCurrentlyPlaying(c.Context)
if err != nil {
return err
}
if !status.Playing {
return nil
}
to_remove := []spotify.ID{}
toRemove := []spotify.ID{}
radioPlaylist, db, err := c.GetRadioPlaylist("")
if err != nil {
return err
@ -376,14 +378,14 @@ func (c *Commander) RefillRadio() error {
return nil
}
playlistItems, err := c.Client.GetPlaylistItems(c.Context, radioPlaylist.ID)
playlistItems, err := c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID)
if err != nil {
return fmt.Errorf("orig playlist items: %w", err)
}
page := 0
for {
tracks, err := c.Client.GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50))
tracks, err := c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50))
if err != nil {
return fmt.Errorf("tracks: %w", err)
}
@ -394,15 +396,15 @@ func (c *Commander) RefillRadio() error {
if track.Track.Track.ID == status.Item.ID {
break
}
to_remove = append(to_remove, track.Track.Track.ID)
toRemove = append(toRemove, track.Track.Track.ID)
}
page++
}
if len(to_remove) > 0 {
if len(toRemove) > 0 {
var trackGroups []spotify.ID
for idx, item := range to_remove {
for idx, item := range toRemove {
if idx%100 == 0 {
_, err = c.Client.RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
_, err = c.Client().RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
trackGroups = []spotify.ID{}
}
trackGroups = append(trackGroups, item)
@ -410,14 +412,14 @@ func (c *Commander) RefillRadio() error {
return fmt.Errorf("error clearing playlist: %w", err)
}
}
_, err := c.Client.RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
_, err := c.Client().RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
if err != nil {
return err
}
}
to_add := 500 - (playlistItems.Total - len(to_remove))
playlistItems, err = c.Client.GetPlaylistItems(c.Context, radioPlaylist.ID)
toAdd := 500 - (int(playlistItems.Total) - len(toRemove))
playlistItems, err = c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID)
if err != nil {
return fmt.Errorf("playlist items: %w", err)
}
@ -427,7 +429,7 @@ func (c *Commander) RefillRadio() error {
if pages > 1 {
randomPage = frand.Intn(pages-1) + 1
}
playlistPage, err := c.Client.
playlistPage, err := c.Client().
GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50))
if err != nil {
return fmt.Errorf("playlist page: %w", err)
@ -448,7 +450,7 @@ func (c *Commander) RefillRadio() error {
seed := spotify.Seeds{
Tracks: seedIds,
}
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(95))
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(95))
if err != nil {
return err
}
@ -464,7 +466,7 @@ func (c *Commander) RefillRadio() error {
}
queue := []spotify.ID{}
for idx, rec := range recomendationIds {
if idx > to_add {
if idx > toAdd {
break
}
_, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", rec.String()))
@ -473,40 +475,41 @@ func (c *Commander) RefillRadio() error {
}
queue = append(queue, rec)
}
to_add -= len(queue)
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
toAdd -= len(queue)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
if err != nil {
return fmt.Errorf("add tracks: %w", err)
}
err = c.Client.Repeat(c.Context, "context")
err = c.Client().Repeat(c.Context, "context")
if err != nil {
return fmt.Errorf("repeat: %w", err)
}
for to_add > 0 {
for toAdd > 0 {
id := frand.Intn(len(recomendationIds)-2) + 1
seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]},
}
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
additionalRecs, err := c.Client().
GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
if err != nil {
return fmt.Errorf("get recs: %w", err)
}
additionalRecsIds := []spotify.ID{}
for idx, song := range additional_recs.Tracks {
for idx, song := range additionalRecs.Tracks {
exists, err := c.SongExists(db, song.ID)
if err != nil {
return fmt.Errorf("check song existence: %w", err)
}
if !exists {
if idx > to_add {
if idx > toAdd {
break
}
additionalRecsIds = append(additionalRecsIds, song.ID)
queue = append(queue, song.ID)
}
}
to_add -= len(queue)
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
toAdd -= len(queue)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
if err != nil {
return fmt.Errorf("add tracks to playlist: %w", err)
}
@ -548,11 +551,11 @@ func (c *Commander) RadioFromAlbum(album spotify.SimpleAlbum) error {
return c.RadioGivenList(seedIds[:seedCount], album.Name)
}
func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
func (c *Commander) RadioGivenList(songs []spotify.ID, name string) error {
seed := spotify.Seeds{
Tracks: song_ids,
Tracks: songs,
}
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
if err != nil {
return err
}
@ -568,7 +571,7 @@ func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
if err != nil {
return err
}
queue := []spotify.ID{song_ids[0]}
queue := []spotify.ID{songs[0]}
for _, rec := range recomendationIds {
exists, err := c.SongExists(db, rec)
if err != nil {
@ -582,22 +585,22 @@ func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
queue = append(queue, rec)
}
}
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
if err != nil {
return err
}
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI,
})
if err != nil {
if isNoActiveError(err) {
deviceId, err := c.activateDevice()
deviceID, err := c.activateDevice()
if err != nil {
return err
}
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI,
DeviceID: &deviceId,
DeviceID: &deviceID,
})
if err != nil {
return err
@ -609,12 +612,12 @@ func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]},
}
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
additionalRecs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
if err != nil {
return err
}
additionalRecsIds := []spotify.ID{}
for _, song := range additional_recs.Tracks {
for _, song := range additionalRecs.Tracks {
exists, err := c.SongExists(db, song.ID)
if err != nil {
return err
@ -627,7 +630,7 @@ func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
additionalRecsIds = append(additionalRecsIds, song.ID)
}
}
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
if err != nil {
return err
}

View File

@ -1,7 +1,7 @@
package commands
func (c *Commander) Repeat() error {
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return err
}
@ -10,7 +10,7 @@ func (c *Commander) Repeat() error {
newState = "context"
}
// spotifyd only supports binary value for repeat, context or off, change when/if spotifyd is better
err = c.Client.Repeat(c.Context, newState)
err = c.Client().Repeat(c.Context, newState)
if err != nil {
return err
}

View File

@ -3,7 +3,7 @@ package commands
import "github.com/zmb3/spotify/v2"
func (c *Commander) Search(search string, page int) (*spotify.SearchResult, error) {
result, err := c.Client.
result, err := c.Client().
Search(c.Context, search, spotify.SearchTypeAlbum|spotify.SearchTypeArtist|spotify.SearchTypeTrack|spotify.SearchTypePlaylist, spotify.Limit(50), spotify.Offset((page-1)*50))
if err != nil {
return nil, err

View File

@ -1,7 +1,7 @@
package commands
func (c *Commander) Seek(fwd bool) error {
current, err := c.Client.PlayerCurrentlyPlaying(c.Context)
current, err := c.Client().PlayerCurrentlyPlaying(c.Context)
if err != nil {
return err
}
@ -9,7 +9,7 @@ func (c *Commander) Seek(fwd bool) error {
if !fwd {
newPos = current.Progress - 5000
}
err = c.Client.Seek(c.Context, newPos)
err = c.Client().Seek(c.Context, int(newPos))
if err != nil {
return err
}
@ -17,7 +17,7 @@ func (c *Commander) Seek(fwd bool) error {
}
func (c *Commander) SetPosition(pos int) error {
err := c.Client.Seek(c.Context, pos)
err := c.Client().Seek(c.Context, pos)
if err != nil {
return err
}

View File

@ -1,11 +1,11 @@
package commands
func (c *Commander) Shuffle() error {
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return err
}
err = c.Client.Shuffle(c.Context, !state.ShuffleState)
err = c.Client().Shuffle(c.Context, !state.ShuffleState)
if err != nil {
return err
}

View File

@ -10,7 +10,7 @@ import (
func (c *Commander) Status() error {
state, err := c.Cache.GetOrDo("state", func() (string, error) {
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return "", err
}

View File

@ -1,12 +1,12 @@
package commands
func (c *Commander) TogglePlay() error {
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return err
return c.Play()
}
if state.Playing {
return c.Client.Pause(c.Context)
return c.Pause()
}
return c.Client.Play(c.Context)
return c.Play()
}

View File

@ -1,9 +1,9 @@
package commands
func (c *Commander) UnLike() error {
playing, err := c.Client.PlayerCurrentlyPlaying(c.Context)
playing, err := c.Client().PlayerCurrentlyPlaying(c.Context)
if err != nil {
return err
}
return c.Client.RemoveTracksFromLibrary(c.Context, playing.Item.ID)
return c.Client().RemoveTracksFromLibrary(c.Context, playing.Item.ID)
}

View File

@ -1,18 +1,18 @@
package commands
func (c *Commander) ChangeVolume(amount int) error {
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return err
}
newVolume := state.Device.Volume + amount
newVolume := int(state.Device.Volume) + amount
if newVolume > 100 {
newVolume = 100
}
if newVolume < 0 {
newVolume = 0
}
return c.Client.Volume(c.Context, newVolume)
return c.Client().Volume(c.Context, newVolume)
}
func (c *Commander) Mute() error {
@ -24,7 +24,7 @@ func (c *Commander) UnMute() error {
}
func (c *Commander) ToggleMute() error {
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return err
}

View File

@ -7,7 +7,7 @@ import (
)
func (c *Commander) PrintYoutubeLink() error {
state, err := c.Client.PlayerState(c.Context)
state, err := c.Client().PlayerState(c.Context)
if err != nil {
return err
}

View File

@ -32,10 +32,10 @@ func (m *mainModel) LoadMoreItems() {
for _, item := range items {
m.list.InsertItem(len(m.list.Items())+1, item)
}
main_updates <- m
mainUpdates <- m
return
case "artists":
artists, err := m.commands.Client.CurrentUsersFollowedArtists(
artists, err := m.commands.Client().CurrentUsersFollowedArtists(
m.commands.Context,
spotify.Limit(50),
spotify.Offset((page)*50),
@ -60,7 +60,7 @@ func (m *mainModel) LoadMoreItems() {
for _, item := range items {
m.list.InsertItem(len(m.list.Items())+1, item)
}
main_updates <- m
mainUpdates <- m
return
case "album":
tracks, err := m.commands.AlbumTracks(m.album.ID, (page + 1))
@ -80,7 +80,7 @@ func (m *mainModel) LoadMoreItems() {
for _, item := range items {
m.list.InsertItem(len(m.list.Items())+1, item)
}
main_updates <- m
mainUpdates <- m
return
case "albums":
albums, err := m.commands.UserAlbums(page + 1)
@ -99,7 +99,7 @@ func (m *mainModel) LoadMoreItems() {
for _, item := range items {
m.list.InsertItem(len(m.list.Items())+1, item)
}
main_updates <- m
mainUpdates <- m
return
case "main":
playlists, err := m.commands.Playlists(page + 1)
@ -117,7 +117,7 @@ func (m *mainModel) LoadMoreItems() {
for _, item := range items {
m.list.InsertItem(len(m.list.Items())+1, item)
}
main_updates <- m
mainUpdates <- m
return
case "playlist":
playlistItems, err := m.commands.PlaylistTracks(m.playlist.ID, (page + 1))
@ -139,7 +139,7 @@ func (m *mainModel) LoadMoreItems() {
for _, item := range items {
m.list.InsertItem(len(m.list.Items())+1, item)
}
main_updates <- m
mainUpdates <- m
return
case "tracks":
tracks, err := m.commands.TrackList(page + 1)
@ -159,7 +159,7 @@ func (m *mainModel) LoadMoreItems() {
for _, item := range items {
m.list.InsertItem(len(m.list.Items())+1, item)
}
main_updates <- m
mainUpdates <- m
return
}
}

View File

@ -22,7 +22,7 @@ var (
DocStyle = lipgloss.NewStyle().Margin(0, 2).Border(lipgloss.DoubleBorder(), true, true, true, true)
currentlyPlaying *spotify.CurrentlyPlaying
playbackContext string
main_updates chan *mainModel
mainUpdates chan *mainModel
page = 1
loading = false
showingMessage = false
@ -190,32 +190,32 @@ func (m *mainModel) GoBack() (tea.Cmd, error) {
return tea.Quit, nil
case Albums, Artists, Tracks, Playlist, Devices, Search, Queue:
m.mode = Main
new_items, err := MainView(m.commands)
newItems, err := MainView(m.commands)
if err != nil {
return nil, err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
case Album:
m.mode = Albums
new_items, err := AlbumsView(m.commands)
newItems, err := AlbumsView(m.commands)
if err != nil {
return nil, err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
case Artist:
m.mode = Artists
new_items, err := ArtistsView(m.commands)
newItems, err := ArtistsView(m.commands)
if err != nil {
return nil, err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
case ArtistAlbum:
m.mode = Artist
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
newItems, err := ArtistAlbumsView(m.artist.ID, m.commands)
if err != nil {
return nil, err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
case SearchArtists, SearchTracks, SearchAlbums, SearchPlaylists:
m.mode = Search
items, result, err := SearchView(m.commands, m.search)
@ -226,39 +226,39 @@ func (m *mainModel) GoBack() (tea.Cmd, error) {
m.list.SetItems(items)
case SearchArtist:
m.mode = SearchArtists
new_items, err := SearchArtistsView(m.commands, m.searchResults.Artists)
newItems, err := SearchArtistsView(m.commands, m.searchResults.Artists)
if err != nil {
return nil, err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
case SearchArtistAlbum:
m.mode = SearchArtist
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
newItems, err := ArtistAlbumsView(m.artist.ID, m.commands)
if err != nil {
return nil, err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
case SearchAlbum:
m.mode = SearchAlbums
new_items, err := SearchAlbumsView(m.commands, m.searchResults.Albums)
newItems, err := SearchAlbumsView(m.commands, m.searchResults.Albums)
if err != nil {
return nil, err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
case SearchPlaylist:
m.mode = SearchPlaylists
new_items, err := SearchPlaylistsView(m.commands, m.searchResults.Playlists)
newItems, err := SearchPlaylistsView(m.commands, m.searchResults.Playlists)
if err != nil {
return nil, err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
default:
page = 0
}
return nil, nil
}
type SpotifyUrl struct {
type SpotifyURL struct {
ExternalURLs map[string]string
}
@ -343,11 +343,11 @@ func (m *mainModel) QueueItem() error {
}()
if m.mode == Queue {
go func() {
new_items, err := QueueView(m.commands)
newItems, err := QueueView(m.commands)
if err != nil {
return
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
}()
}
return nil
@ -364,11 +364,11 @@ func (m *mainModel) DeleteTrackFromPlaylist() error {
if err != nil {
m.SendMessage(err.Error(), 5*time.Second)
}
new_items, err := PlaylistView(m.commands, m.playlist)
newItems, err := PlaylistView(m.commands, m.playlist)
if err != nil {
m.SendMessage(err.Error(), 5*time.Second)
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
}()
return nil
}
@ -382,11 +382,11 @@ func (m *mainModel) SelectItem() error {
if err != nil {
m.SendMessage(err.Error(), 5*time.Second)
}
new_items, err := QueueView(m.commands)
newItems, err := QueueView(m.commands)
if err != nil {
m.SendMessage(err.Error(), 5*time.Second)
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
}()
case Search:
@ -394,150 +394,150 @@ func (m *mainModel) SelectItem() error {
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
case *spotify.FullArtistPage:
m.mode = SearchArtists
new_items, err := SearchArtistsView(m.commands, item)
newItems, err := SearchArtistsView(m.commands, item)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case *spotify.SimpleAlbumPage:
m.mode = SearchAlbums
new_items, err := SearchAlbumsView(m.commands, item)
newItems, err := SearchAlbumsView(m.commands, item)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case *spotify.SimplePlaylistPage:
m.mode = SearchPlaylists
new_items, err := SearchPlaylistsView(m.commands, item)
newItems, err := SearchPlaylistsView(m.commands, item)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case *spotify.FullTrackPage:
m.mode = SearchTracks
new_items, err := SearchTracksView(item)
newItems, err := SearchTracksView(item)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
}
case SearchArtists:
page = 1
m.mode = SearchArtist
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
newItems, err := ArtistAlbumsView(m.artist.ID, m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case SearchArtist:
page = 1
m.mode = SearchArtistAlbum
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
new_items, err := AlbumTracksView(m.album.ID, m.commands)
newItems, err := AlbumTracksView(m.album.ID, m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case SearchAlbums:
page = 1
m.mode = SearchAlbum
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
new_items, err := AlbumTracksView(m.album.ID, m.commands)
newItems, err := AlbumTracksView(m.album.ID, m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case SearchPlaylists:
page = 1
m.mode = SearchPlaylist
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
m.playlist = playlist
new_items, err := PlaylistView(m.commands, playlist)
newItems, err := PlaylistView(m.commands, playlist)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case Main:
page = 1
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
case spotify.Queue:
m.mode = Queue
new_items, err := QueueView(m.commands)
newItems, err := QueueView(m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case *spotify.FullArtistCursorPage:
m.mode = Artists
new_items, err := ArtistsView(m.commands)
newItems, err := ArtistsView(m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case *spotify.SavedAlbumPage:
m.mode = Albums
new_items, err := AlbumsView(m.commands)
newItems, err := AlbumsView(m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case spotify.SimplePlaylist:
m.mode = Playlist
m.playlist = item
new_items, err := PlaylistView(m.commands, item)
newItems, err := PlaylistView(m.commands, item)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case *spotify.SavedTrackPage:
m.mode = Tracks
new_items, err := SavedTracksView(m.commands)
newItems, err := SavedTracksView(m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
}
case Albums:
page = 1
m.mode = Album
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
new_items, err := AlbumTracksView(m.album.ID, m.commands)
newItems, err := AlbumTracksView(m.album.ID, m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case Artist:
m.mode = ArtistAlbum
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
new_items, err := AlbumTracksView(m.album.ID, m.commands)
newItems, err := AlbumTracksView(m.album.ID, m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case Artists:
m.mode = Artist
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
newItems, err := ArtistAlbumsView(m.artist.ID, m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
case Album, ArtistAlbum, SearchArtistAlbum, SearchAlbum:
pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.TotalPages)
@ -579,17 +579,17 @@ func (m *mainModel) SelectItem() error {
}
}()
m.mode = "main"
new_items, err := MainView(m.commands)
newItems, err := MainView(m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
}
return nil
}
func (m *mainModel) Init() tea.Cmd {
main_updates = make(chan *mainModel)
mainUpdates = make(chan *mainModel)
return Tick()
}
@ -602,7 +602,7 @@ func Tick() tea.Cmd {
}
func (m *mainModel) TickPlayback() {
playing, _ := m.commands.Client.PlayerCurrentlyPlaying(m.commands.Context)
playing, _ := m.commands.Client().PlayerCurrentlyPlaying(m.commands.Context)
if playing != nil && playing.Playing && playing.Item != nil {
if currentlyPlaying == nil || currentlyPlaying.Item == nil ||
currentlyPlaying.Item.ID != playing.Item.ID {
@ -617,7 +617,7 @@ func (m *mainModel) TickPlayback() {
select {
case <-ticker.C:
m.commands.Log.Debug("TICKING PLAYBACK")
playing, _ := m.commands.Client.PlayerCurrentlyPlaying(m.commands.Context)
playing, _ := m.commands.Client().PlayerCurrentlyPlaying(m.commands.Context)
if playing != nil && playing.Playing && playing.Item != nil {
if currentlyPlaying == nil || currentlyPlaying.Item == nil ||
currentlyPlaying.Item.ID != playing.Item.ID {
@ -665,29 +665,29 @@ func (m *mainModel) Typing(msg tea.KeyMsg) (bool, tea.Cmd) {
func (m *mainModel) getContext(playing *spotify.CurrentlyPlaying) (string, error) {
context := playing.PlaybackContext
uri_split := strings.Split(string(context.URI), ":")
if len(uri_split) < 3 {
uriSplit := strings.Split(string(context.URI), ":")
if len(uriSplit) < 3 {
return "", fmt.Errorf("NO URI")
}
id := strings.Split(string(context.URI), ":")[2]
switch context.Type {
case "album":
m.commands.Log.Debug("ALBUM CONTEXT")
album, err := m.commands.Client.GetAlbum(m.commands.Context, spotify.ID(id))
album, err := m.commands.Client().GetAlbum(m.commands.Context, spotify.ID(id))
if err != nil {
return "", err
}
return album.Name, nil
case "playlist":
m.commands.Log.Debug("PLAYLIST CONTEXT")
playlist, err := m.commands.Client.GetPlaylist(m.commands.Context, spotify.ID(id))
playlist, err := m.commands.Client().GetPlaylist(m.commands.Context, spotify.ID(id))
if err != nil {
return "", err
}
return playlist.Name, nil
case "artist":
m.commands.Log.Debug("ARTIST CONTEXT")
artist, err := m.commands.Client.GetArtist(m.commands.Context, spotify.ID(id))
artist, err := m.commands.Client().GetArtist(m.commands.Context, spotify.ID(id))
if err != nil {
return "", err
}
@ -699,7 +699,7 @@ func (m *mainModel) getContext(playing *spotify.CurrentlyPlaying) (string, error
func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Update list items from LoadMore
select {
case update := <-main_updates:
case update := <-mainUpdates:
m.list.SetItems(update.list.Items())
default:
}
@ -721,11 +721,11 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.mode == Queue && len(m.list.Items()) != 0 {
if m.list.Items()[0].(mainItem).SpotifyItem.(spotify.FullTrack).Name != playing.Item.Name {
go func() {
new_items, err := QueueView(m.commands)
newItems, err := QueueView(m.commands)
if err != nil {
return
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
}()
}
}
@ -806,11 +806,11 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// enter device selection
if msg.String() == "d" {
m.mode = Devices
new_items, err := DeviceView(m.commands)
newItems, err := DeviceView(m.commands)
if err != nil {
return m, tea.Quit
}
m.list.SetItems(new_items)
m.list.SetItems(newItems)
m.list.ResetSelected()
}
// go back
@ -859,6 +859,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.WindowSizeMsg:
h, v := DocStyle.GetFrameSize()
m.list.SetSize(msg.Width-h, msg.Height-v-1)
DocStyle.Width(msg.Width - h)
}
// return

View File

@ -16,7 +16,7 @@ const regex = `<.*?>`
func DeviceView(commands *commands.Commander) ([]list.Item, error) {
items := []list.Item{}
devices, err := commands.Client.PlayerDevices(commands.Context)
devices, err := commands.Client().PlayerDevices(commands.Context)
if err != nil {
return nil, err
}
@ -32,7 +32,7 @@ func DeviceView(commands *commands.Commander) ([]list.Item, error) {
func QueueView(commands *commands.Commander) ([]list.Item, error) {
items := []list.Item{}
tracks, err := commands.Client.GetQueue(commands.Context)
tracks, err := commands.Client().GetQueue(commands.Context)
if err != nil {
return nil, err
}
@ -63,7 +63,7 @@ func QueueView(commands *commands.Commander) ([]list.Item, error) {
func PlaylistView(commands *commands.Commander, playlist spotify.SimplePlaylist) ([]list.Item, error) {
items := []list.Item{}
playlistItems, err := commands.Client.GetPlaylistItems(
playlistItems, err := commands.Client().GetPlaylistItems(
commands.Context,
playlist.ID,
spotify.Limit(50),
@ -89,7 +89,7 @@ func PlaylistView(commands *commands.Commander, playlist spotify.SimplePlaylist)
func ArtistsView(commands *commands.Commander) ([]list.Item, error) {
items := []list.Item{}
artists, err := commands.Client.CurrentUsersFollowedArtists(commands.Context, spotify.Limit(50), spotify.Offset(0))
artists, err := commands.Client().CurrentUsersFollowedArtists(commands.Context, spotify.Limit(50), spotify.Offset(0))
if err != nil {
return nil, err
}
@ -145,7 +145,7 @@ func SearchView(commands *commands.Commander, search string) ([]list.Item, *Sear
func AlbumsView(commands *commands.Commander) ([]list.Item, error) {
items := []list.Item{}
albums, err := commands.Client.CurrentUsersAlbums(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
albums, err := commands.Client().CurrentUsersAlbums(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
if err != nil {
return nil, err
}
@ -171,7 +171,7 @@ func SearchPlaylistsView(commands *commands.Commander, playlists *spotify.Simple
for _, playlist := range playlists.Playlists {
items = append(items, mainItem{
Name: playlist.Name,
Desc: stripHtmlRegex(playlist.Description),
Desc: stripHTMLRegex(playlist.Description),
SpotifyItem: playlist,
})
}
@ -249,7 +249,7 @@ func SearchTracksView(tracks *spotify.FullTrackPage) ([]list.Item, error) {
func SavedTracksView(commands *commands.Commander) ([]list.Item, error) {
items := []list.Item{}
tracks, err := commands.Client.CurrentUsersTracks(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
tracks, err := commands.Client().CurrentUsersTracks(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
if err != nil {
return nil, err
}
@ -269,28 +269,28 @@ func SavedTracksView(commands *commands.Commander) ([]list.Item, error) {
func MainView(c *commands.Commander) ([]list.Item, error) {
c.Log.Debug("SWITCHING TO MAIN VIEW")
wg := errgroup.Group{}
var saved_items *spotify.SavedTrackPage
var savedItems *spotify.SavedTrackPage
var playlists *spotify.SimplePlaylistPage
var artists *spotify.FullArtistCursorPage
var albums *spotify.SavedAlbumPage
wg.Go(func() (err error) {
saved_items, err = c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
savedItems, err = c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
return
})
wg.Go(func() (err error) {
playlists, err = c.Client.CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset(0))
playlists, err = c.Client().CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset(0))
return
})
wg.Go(func() (err error) {
artists, err = c.Client.CurrentUsersFollowedArtists(c.Context, spotify.Limit(50), spotify.Offset(0))
artists, err = c.Client().CurrentUsersFollowedArtists(c.Context, spotify.Limit(50), spotify.Offset(0))
return
})
wg.Go(func() (err error) {
albums, err = c.Client.CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset(0))
albums, err = c.Client().CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset(0))
return
})
@ -300,11 +300,11 @@ func MainView(c *commands.Commander) ([]list.Item, error) {
}
items := []list.Item{}
if saved_items != nil && saved_items.Total != 0 {
if savedItems != nil && savedItems.Total != 0 {
items = append(items, mainItem{
Name: "Saved Tracks",
Desc: fmt.Sprintf("%d saved songs", saved_items.Total),
SpotifyItem: saved_items,
Desc: fmt.Sprintf("%d saved songs", savedItems.Total),
SpotifyItem: savedItems,
})
}
if albums != nil && albums.Total != 0 {
@ -330,7 +330,7 @@ func MainView(c *commands.Commander) ([]list.Item, error) {
for _, playlist := range playlists.Playlists {
items = append(items, mainItem{
Name: playlist.Name,
Desc: stripHtmlRegex(playlist.Description),
Desc: stripHTMLRegex(playlist.Description),
SpotifyItem: playlist,
})
}
@ -338,7 +338,7 @@ func MainView(c *commands.Commander) ([]list.Item, error) {
return items, nil
}
func stripHtmlRegex(s string) string {
func stripHTMLRegex(s string) string {
r := regexp.MustCompile(regex)
return r.ReplaceAllString(s, "")
}

View File

@ -0,0 +1,102 @@
package tuitview
import (
"sync/atomic"
"git.asdf.cafe/abs3nt/gspot/src/components/commands"
"github.com/rivo/tview"
"github.com/zmb3/spotify/v2"
)
var (
tracksLoading = atomic.Bool{}
playlistsLoading = atomic.Bool{}
tracksPage = 1
playlistsPage = 1
)
func TuitView(cmd *commands.Commander) error {
tracksLoading.Store(false)
playlistsLoading.Store(false)
playlistsList := tview.NewList().ShowSecondaryText(false)
playlistsList.SetBorder(true).SetTitle("Playlists")
savedTracksList := tview.NewList().ShowSecondaryText(false)
savedTracksList.SetWrapAround(false)
savedTracksList.SetBorder(true).SetTitle("Tracks")
savedTracksList.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
go cmd.PlayLikedSongs(index)
})
flex := tview.NewFlex().AddItem(playlistsList, 0, 1, false).AddItem(savedTracksList, 0, 2, true)
playlists, err := cmd.Playlists(1)
if err != nil {
return err
}
for _, playlist := range playlists.Playlists {
playlistsList.AddItem(playlist.Name, "", 0, func() {
playlistTracksList := tview.NewList().ShowSecondaryText(false)
playlistTracksList.SetBorder(true).SetTitle(playlist.Name)
playlistTracksList.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
go cmd.PlaySongInPlaylist((*spotify.URI)(&secondaryText), &index)
})
tracks, err := cmd.PlaylistTracks(playlist.ID, 1)
if err != nil {
return
}
for _, track := range tracks.Items {
playlistTracksList.AddItem(track.Track.Track.Name+" - "+track.Track.Track.Artists[0].Name, string(playlist.URI), 0, nil)
}
flex.Clear()
flex.AddItem(playlistsList, 0, 1, false)
flex.AddItem(playlistTracksList, 0, 2, false)
})
}
tracks, err := cmd.TrackList(1)
if err != nil {
return err
}
for _, track := range tracks.Tracks {
savedTracksList.AddItem(track.Name+" - "+track.Artists[0].Name, "", 0, nil)
}
playlistsList.SetChangedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
if playlistsList.GetItemCount()%50 != 0 {
return
}
if playlistsList.GetItemCount()-index < 40 {
go func() {
if playlistsLoading.Load() {
return
}
playlistsLoading.Store(true)
defer playlistsLoading.Store(false)
playlistsPage++
newPlaylists, _ := cmd.Playlists(playlistsPage)
for _, playlist := range newPlaylists.Playlists {
savedTracksList.AddItem(playlist.Name, "", 0, nil)
}
}()
}
})
savedTracksList.SetChangedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
if savedTracksList.GetItemCount()%50 != 0 {
return
}
if savedTracksList.GetItemCount()-index < 40 {
go func() {
if tracksLoading.Load() {
return
}
tracksLoading.Store(true)
defer tracksLoading.Store(false)
tracksPage++
tracks, _ := cmd.TrackList(tracksPage)
for _, track := range tracks.Tracks {
savedTracksList.AddItem(track.Name+" - "+track.Artists[0].Name, "", 0, nil)
}
}()
}
})
if err := tview.NewApplication().EnableMouse(true).SetRoot(flex, true).Run(); err != nil {
return err
}
return nil
}

View File

@ -1,7 +1,7 @@
package config
type Config struct {
ClientId string `yaml:"client_id"`
ClientID string `yaml:"client_id"`
ClientSecret string `yaml:"client_secret"`
ClientSecretCmd string `yaml:"client_secret_cmd"`
Port string `yaml:"port"`

View File

@ -13,18 +13,12 @@ import (
"github.com/zmb3/spotify/v2"
spotifyauth "github.com/zmb3/spotify/v2/auth"
"go.uber.org/fx"
"golang.org/x/exp/slog"
"golang.org/x/oauth2"
"git.asdf.cafe/abs3nt/gspot/src/config"
)
type SpotifyClientResult struct {
fx.Out
Client *spotify.Client
}
var (
auth *spotifyauth.Authenticator
ch = make(chan *spotify.Client)
@ -38,25 +32,25 @@ func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error)
return fn(req)
}
func NewSpotifyClient(conf *config.Config) (c SpotifyClientResult, err error) {
if conf.ClientId == "" || (conf.ClientSecret == "" && conf.ClientSecretCmd == "") || conf.Port == "" {
return SpotifyClientResult{}, fmt.Errorf("INVALID CONFIG")
func GetClient(conf *config.Config) (c *spotify.Client, err error) {
if conf.ClientID == "" || (conf.ClientSecret == "" && conf.ClientSecretCmd == "") || conf.Port == "" {
return nil, fmt.Errorf("INVALID CONFIG")
}
if conf.ClientSecretCmd != "" {
args := strings.Fields(conf.ClientSecretCmd)
cmd := args[0]
secret_command := exec.Command(cmd)
secretCommand := exec.Command(cmd)
if len(args) > 1 {
secret_command.Args = args
secretCommand.Args = args
}
secret, err := secret_command.Output()
secret, err := secretCommand.Output()
if err != nil {
panic(err)
}
conf.ClientSecret = strings.TrimSpace(string(secret))
}
auth = spotifyauth.New(
spotifyauth.WithClientID(conf.ClientId),
spotifyauth.WithClientID(conf.ClientID),
spotifyauth.WithClientSecret(conf.ClientSecret),
spotifyauth.WithRedirectURL(fmt.Sprintf("http://localhost:%s/callback", conf.Port)),
spotifyauth.WithScopes(
@ -83,13 +77,13 @@ func NewSpotifyClient(conf *config.Config) (c SpotifyClientResult, err error) {
authFilePath := filepath.Join(configDir, "gspot/auth.json")
authFile, err := os.Open(authFilePath)
if err != nil {
return SpotifyClientResult{}, err
return nil, err
}
defer authFile.Close()
tok := &oauth2.Token{}
err = json.NewDecoder(authFile).Decode(tok)
if err != nil {
return SpotifyClientResult{}, err
return nil, err
}
authCtx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
@ -98,20 +92,20 @@ func NewSpotifyClient(conf *config.Config) (c SpotifyClientResult, err error) {
}),
})
authClient := auth.Client(authCtx, tok)
client := spotify.New(authClient)
new_token, err := client.Token()
client := spotify.New(authClient, spotify.WithRetry(true))
newToken, err := client.Token()
if err != nil {
return SpotifyClientResult{}, err
return nil, err
}
out, err := json.MarshalIndent(new_token, "", " ")
out, err := json.MarshalIndent(newToken, "", " ")
if err != nil {
return SpotifyClientResult{}, err
return nil, err
}
err = os.WriteFile(authFilePath, out, 0o600)
if err != nil {
return SpotifyClientResult{}, fmt.Errorf("failed to save auth")
return nil, fmt.Errorf("failed to save auth")
}
return SpotifyClientResult{Client: client}, nil
return client, nil
}
// first start an HTTP server
http.HandleFunc("/callback", completeAuth)
@ -136,10 +130,10 @@ func NewSpotifyClient(conf *config.Config) (c SpotifyClientResult, err error) {
// use the client to make calls that require authorization
user, err := client.CurrentUser(context.Background())
if err != nil {
return SpotifyClientResult{}, err
return nil, err
}
slog.Info("AUTH", "You are logged in as:", user.ID)
return SpotifyClientResult{Client: client}, nil
return client, nil
}
func completeAuth(w http.ResponseWriter, r *http.Request) {
@ -162,7 +156,7 @@ func completeAuth(w http.ResponseWriter, r *http.Request) {
slog.Error("AUTHENTICATOR", "failed to save auth", err)
}
// use the token to get an authenticated client
client := spotify.New(auth.Client(r.Context(), tok))
client := spotify.New(auth.Client(r.Context(), tok), spotify.WithRetry(true))
fmt.Fprintf(w, "Login Completed!")
ch <- client
}