Compare commits

..

No commits in common. "master" and "v0.0.18" have entirely different histories.

26 changed files with 487 additions and 864 deletions

View File

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

View File

@ -1,28 +0,0 @@
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@v6
with:
distribution: goreleaser
version: nightly
args: release --clean
env:
GITEA_TOKEN: ${{ secrets.ACCESS_TOKEN_GITEA}}

View File

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

14
.woodpecker.yml Normal file
View File

@ -0,0 +1,14 @@
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
View File

@ -1,21 +0,0 @@
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,14 +1,16 @@
build: build: gspot
gspot: $(shell find . -name '*.go')
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/ . 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: build run:
./dist/gspot go run main.go
tidy: tidy:
go mod tidy go mod tidy
clean: clean:
rm -rf dist rm -rf bin
uninstall: uninstall:
rm -f /usr/bin/gspot rm -f /usr/bin/gspot

View File

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

106
go.mod
View File

@ -1,81 +1,79 @@
module git.asdf.cafe/abs3nt/gspot module git.asdf.cafe/abs3nt/gspot
go 1.22.3 go 1.22.0
require ( require (
gfx.cafe/util/go/frand v0.0.0-20231226111635-bc00a6a250fb
gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb
git.asdf.cafe/abs3nt/gunner v0.0.1 git.asdf.cafe/abs3nt/gunner v0.0.1
github.com/atotto/clipboard v0.1.4 github.com/atotto/clipboard v0.1.4
github.com/charmbracelet/bubbles v0.20.0 github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v1.1.2 github.com/charmbracelet/bubbletea v0.25.0
github.com/charmbracelet/lipgloss v0.13.1 github.com/charmbracelet/lipgloss v0.9.1
github.com/lmittmann/tint v1.0.5 github.com/lmittmann/tint v1.0.4
github.com/rivo/tview v0.0.0-20241016194538-c5e4fb24af13 github.com/urfave/cli/v2 v2.27.1
github.com/urfave/cli/v3 v3.0.0-alpha9.1 github.com/zmb3/spotify/v2 v2.4.1
github.com/zmb3/spotify/v2 v2.4.2 go.uber.org/fx v1.20.1
go.uber.org/fx v1.23.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/net v0.17.0
golang.org/x/net v0.30.0 golang.org/x/oauth2 v0.10.0
golang.org/x/oauth2 v0.23.0 golang.org/x/sync v0.3.0
golang.org/x/sync v0.8.0 google.golang.org/api v0.133.0
google.golang.org/api v0.203.0 modernc.org/sqlite v1.29.1
modernc.org/sqlite v1.33.1
) )
require ( require (
cloud.google.com/go/auth v0.9.9 // indirect cloud.google.com/go/compute v1.22.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.5.2 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.4.0 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/charmbracelet/x/term v0.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cristalhq/aconfig v0.18.6 // indirect github.com/cristalhq/aconfig v0.18.5 // indirect
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect
github.com/dustin/go-humanize v1.0.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.1 // indirect
github.com/gdamore/tcell/v2 v2.7.4 // 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/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/s2a-go v0.1.8 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/s2a-go v0.1.4 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/joho/godotenv v1.5.1 // indirect github.com/joho/godotenv v1.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2 // indirect
github.com/sahilm/fuzzy v0.1.1 // 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
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.opentelemetry.io/otel v1.31.0 // indirect go.uber.org/dig v1.17.0 // indirect
go.opentelemetry.io/otel/metric v1.31.0 // indirect go.uber.org/multierr v1.6.0 // indirect
go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/zap v1.23.0 // indirect
go.uber.org/dig v1.18.0 // indirect golang.org/x/crypto v0.14.0 // indirect
go.uber.org/multierr v1.11.0 // indirect golang.org/x/sys v0.16.0 // indirect
go.uber.org/zap v1.27.0 // indirect golang.org/x/term v0.13.0 // indirect
golang.org/x/crypto v0.28.0 // indirect golang.org/x/text v0.13.0 // indirect
golang.org/x/sys v0.26.0 // indirect google.golang.org/appengine v1.6.7 // indirect
golang.org/x/term v0.25.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895 // indirect
golang.org/x/text v0.19.0 // indirect google.golang.org/grpc v1.56.2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/protobuf v1.31.0 // indirect
google.golang.org/grpc v1.67.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
google.golang.org/protobuf v1.35.1 // indirect modernc.org/libc v1.41.0 // indirect
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect
modernc.org/libc v1.61.0 // indirect
modernc.org/mathutil v1.6.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect modernc.org/memory v1.7.2 // indirect
modernc.org/strutil v1.2.0 // indirect modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect modernc.org/token v1.1.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect

296
go.sum
View File

@ -13,18 +13,16 @@ 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.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.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ=
cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 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.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.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 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.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/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute v1.22.0 h1:cB8R6FtUtT1TYGl5R3xuxnW6OUIc/DrT2aiR16TTG7Y=
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= 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/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 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/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@ -37,35 +35,50 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gfx.cafe/util/go/frand v0.0.0-20231226111635-bc00a6a250fb h1:sBsE/GNN43F9a9/FQjbUHMNm3YXLe+YDOzuEJ0e2w0Y=
gfx.cafe/util/go/frand v0.0.0-20231226111635-bc00a6a250fb/go.mod h1:LNHxMJl0WnIr5+OChYxlVopxk+j7qxZv0XvWCzB6uGE=
gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb h1:JL2ZB1wCxGS/mBIsTwDrgGEndHVQtZuba9dgSk+OsSE=
gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb/go.mod h1:qcgf/NcKZwJCETErwNtofMa10hQtP28ec1bN2nl8ahA=
git.asdf.cafe/abs3nt/gunner v0.0.1 h1:N6kCe7fH83kzm1Sjp/5uZbl8FM5s7KoYCfmhO8qyQbA= git.asdf.cafe/abs3nt/gunner v0.0.1 h1:N6kCe7fH83kzm1Sjp/5uZbl8FM5s7KoYCfmhO8qyQbA=
git.asdf.cafe/abs3nt/gunner v0.0.1/go.mod h1:Q4zhiPfmffCVAb5xIzZn6Momm91uf/deqRVd2/vdjd4= git.asdf.cafe/abs3nt/gunner v0.0.1/go.mod h1:Q4zhiPfmffCVAb5xIzZn6Momm91uf/deqRVd2/vdjd4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 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 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 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 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbletea v1.1.2 h1:naQXF2laRxyLyil/i7fxdpiz1/k06IKquhm4vBfHsIc= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v1.1.2/go.mod h1:9HIU/hBV24qKjlehyj8z1r/tR9TYTQEag+cWZnuXo8E= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= 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/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.13.1 h1:Oik/oqDTMVA01GetT4JdEC033dNzWoQHdWnHnQmXE2A= github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.13.1/go.mod h1:zaYVJ2xKSKEnTEEbX6uAHabh2d975RJ+0yfkFpRBz5U= github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/charmbracelet/x/ansi v0.4.0 h1:NqwHA4B23VwsDn4H3VcNX1W1tOmgnvY1NDx5tOXdnOU=
github.com/charmbracelet/x/ansi v0.4.0/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 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/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/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/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.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig v0.18.6 h1:8KRBznzdjUUiaa7HeIpYbMx1uPE1/xOBEU1ajsnmNME= github.com/cristalhq/aconfig v0.18.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
github.com/cristalhq/aconfig v0.18.6/go.mod h1:9ogrGEt9yU5V4pif/ThkVUfhj8JkdV+iDeahZGgfnDU= github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 h1:HG2ql5fGe4FLL2fUv6o+o0YRyF1mWEcYkNfWGWD82k4= github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 h1:HG2ql5fGe4FLL2fUv6o+o0YRyF1mWEcYkNfWGWD82k4=
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1/go.mod h1:gQIKkh+HkVcODvMNz/cLbH65Pk9b0r4tfolCOsI8G9I= github.com/cristalhq/aconfig/aconfigdotenv v0.17.1/go.mod h1:gQIKkh+HkVcODvMNz/cLbH65Pk9b0r4tfolCOsI8G9I=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -76,24 +89,13 @@ 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.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.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.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/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
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/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
github.com/gdamore/tcell/v2 v2.7.4/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 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-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-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/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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -123,6 +125,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.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 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.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/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -134,9 +138,8 @@ 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.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.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.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.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 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/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= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -146,92 +149,100 @@ 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-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-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-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 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.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= 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/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 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 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 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 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/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.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.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/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/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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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/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.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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw= github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= 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 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/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 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 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-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= 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/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= 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 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 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/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/tview v0.0.0-20241016194538-c5e4fb24af13 h1:SG5LUOAzLU9svb9HTLJI2WnLHQDEe86fXWJ4h2fQg0s= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/tview v0.0.0-20241016194538-c5e4fb24af13/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2 h1:tcc3ZFBvjydcgrAxavZRYqFqCKzy0FJ+UY4ATq4QVXk=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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/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.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.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.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.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v3 v3.0.0-alpha9.1 h1:1fJU+bltkwN8lF4Sni/X0i1d8XwPIrS82ivZ8qsp/q4= github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v3 v3.0.0-alpha9.1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zmb3/spotify/v2 v2.4.2 h1:j3yNN5lKVEMZQItJF4MHCSZbfNWmXO+KaC+3RFaLlLc= github.com/zmb3/spotify/v2 v2.4.1 h1:2ENzO3XQLOQBuxgT1Z9+PlCBSkjNgzFzmRaPns0tjM4=
github.com/zmb3/spotify/v2 v2.4.2/go.mod h1:XOV7BrThayFYB9AAfB+L0Q0wyxBuLCARk4fI/ZXCBW8= github.com/zmb3/spotify/v2 v2.4.1/go.mod h1:p3r7mCCxHepzVaJOe3w1dlx9SL+T8iiQR14tfXJpuTE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -239,34 +250,28 @@ 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.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
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-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-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-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-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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -277,8 +282,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-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-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-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 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/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= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -301,8 +306,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.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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.15.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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -331,21 +336,20 @@ 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-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-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-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.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.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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-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-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-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-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.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -356,8 +360,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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -385,36 +389,35 @@ 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-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-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-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-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-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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.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-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.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.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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.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.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.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.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.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.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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -460,8 +463,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.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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -482,14 +485,15 @@ 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.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.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU= google.golang.org/api v0.133.0 h1:N7Ym5Hl0Dpn0I0o7R1z4UpVA1GCDyS8vbPu1/ObV73A=
google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI= google.golang.org/api v0.133.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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.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.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.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 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.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/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-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -514,17 +518,19 @@ 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-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-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-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-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-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-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-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-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-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= 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/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -537,9 +543,12 @@ 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.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.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.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.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 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/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -552,14 +561,15 @@ 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.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-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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/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.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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@ -570,28 +580,16 @@ 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-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.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0= modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
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.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M=
modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY=
modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE=
modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM=
modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

45
main.go
View File

@ -1,13 +1,8 @@
package main package main
import ( import (
"context" "gfx.cafe/util/go/fxplus"
"errors"
"fmt"
"log/slog"
"go.uber.org/fx" "go.uber.org/fx"
"go.uber.org/fx/fxevent"
"git.asdf.cafe/abs3nt/gspot/src/components/cache" "git.asdf.cafe/abs3nt/gspot/src/components/cache"
"git.asdf.cafe/abs3nt/gspot/src/components/cli" "git.asdf.cafe/abs3nt/gspot/src/components/cli"
@ -19,15 +14,11 @@ import (
func main() { func main() {
var s fx.Shutdowner var s fx.Shutdowner
app := fx.New( app := fx.New(
fx.WithLogger(func(logger *slog.Logger) fxevent.Logger { fxplus.WithLogger,
l := &fxevent.SlogLogger{Logger: logger}
l.UseLogLevel(slog.LevelDebug)
return l
}),
fx.Populate(&s), fx.Populate(&s),
services.Config, services.Config,
fx.Provide( fx.Provide(
Context, fxplus.Context,
cache.NewCache, cache.NewCache,
commands.NewCommander, commands.NewCommander,
logger.NewLogger, logger.NewLogger,
@ -38,33 +29,3 @@ func main() {
) )
app.Run() app.Run()
} }
type AsyncInit func(func(ctx context.Context) error)
var ErrContextShutdown = errors.New("shutdown")
func Context(
lc fx.Lifecycle,
s fx.Shutdowner,
log *slog.Logger,
) (context.Context, AsyncInit) {
if log == nil {
log = slog.Default()
}
ctx, cn := context.WithCancelCause(context.Background())
lc.Append(fx.Hook{
OnStop: func(ctx context.Context) error {
cn(fmt.Errorf("%w: %w", context.Canceled, ErrContextShutdown))
return nil
},
})
return ctx, func(fn func(ctx context.Context) error) {
go func() {
err := fn(ctx)
if err != nil {
log.Error("Failed to run async hook", "err", err)
s.Shutdown()
}
}()
}
}

View File

@ -1,3 +0,0 @@
{
"extends": ["config:recommended"]
}

View File

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

View File

@ -20,12 +20,12 @@ func (c *Commander) Next(amt int, inqueue bool) error {
err := c.Client().Next(c.Context) err := c.Client().Next(c.Context)
if err != nil { if err != nil {
if isNoActiveError(err) { if isNoActiveError(err) {
deviceID, err := c.activateDevice() deviceId, err := c.activateDevice()
if err != nil { if err != nil {
return err return err
} }
err = c.Client().NextOpt(c.Context, &spotify.PlayOptions{ err = c.Client().NextOpt(c.Context, &spotify.PlayOptions{
DeviceID: &deviceID, DeviceID: &deviceId,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -1,7 +1,6 @@
package commands package commands
import ( import (
"fmt"
"net/url" "net/url"
"strings" "strings"
@ -44,11 +43,11 @@ func (c *Commander) PlayLikedSongs(position int) error {
if err != nil { if err != nil {
return err return err
} }
toAdd := []spotify.ID{} to_add := []spotify.ID{}
for _, song := range songs.Tracks { for _, song := range songs.Tracks {
toAdd = append(toAdd, song.ID) to_add = append(to_add, song.ID)
} }
_, err = c.Client().AddTracksToPlaylist(c.Context, playlist.ID, toAdd...) _, err = c.Client().AddTracksToPlaylist(c.Context, playlist.ID, to_add...)
if err != nil { if err != nil {
return err return err
} }
@ -79,11 +78,11 @@ func (c *Commander) PlayLikedSongs(position int) error {
if err != nil { if err != nil {
return err return err
} }
toAdd := []spotify.ID{} to_add := []spotify.ID{}
for _, song := range songs.Tracks { for _, song := range songs.Tracks {
toAdd = append(toAdd, song.ID) to_add = append(to_add, song.ID)
} }
_, err = c.Client().AddTracksToPlaylist(c.Context, playlist.ID, toAdd...) _, err = c.Client().AddTracksToPlaylist(c.Context, playlist.ID, to_add...)
if err != nil { if err != nil {
return err return err
} }
@ -92,24 +91,20 @@ func (c *Commander) PlayLikedSongs(position int) error {
return err return err
} }
func (c *Commander) PlayURL(urlString string) error { func (c *Commander) PlayUrl(urlString string) error {
url, err := url.Parse(urlString) url, err := url.Parse(urlString)
if err != nil { if err != nil {
return err return err
} }
splittUrl := strings.Split(url.Path, "/") track_id := strings.Split(url.Path, "/")[2]
if len(splittUrl) < 3 { err = c.Client().QueueSong(c.Context, spotify.ID(track_id))
return fmt.Errorf("invalid url")
}
trackID := splittUrl[2]
err = c.Client().QueueSong(c.Context, spotify.ID(trackID))
if err != nil { if err != nil {
if isNoActiveError(err) { if isNoActiveError(err) {
deviceID, err := c.activateDevice() deviceID, err := c.activateDevice()
if err != nil { if err != nil {
return err return err
} }
err = c.Client().QueueSongOpt(c.Context, spotify.ID(trackID), &spotify.PlayOptions{ err = c.Client().QueueSongOpt(c.Context, spotify.ID(track_id), &spotify.PlayOptions{
DeviceID: &deviceID, DeviceID: &deviceID,
}) })
if err != nil { if err != nil {

View File

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

View File

@ -6,22 +6,22 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"math/rand"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"gfx.cafe/util/go/frand"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
) )
func (c *Commander) Radio() error { func (c *Commander) Radio() error {
currentSong, err := c.Client().PlayerCurrentlyPlaying(c.Context) current_song, err := c.Client().PlayerCurrentlyPlaying(c.Context)
if err != nil { if err != nil {
return err return err
} }
if currentSong.Item != nil { if current_song.Item != nil {
return c.RadioGivenSong(currentSong.Item.SimpleTrack, currentSong.Progress) return c.RadioGivenSong(current_song.Item.SimpleTrack, current_song.Progress)
} }
_, err = c.activateDevice() _, err = c.activateDevice()
if err != nil { if err != nil {
@ -31,21 +31,30 @@ func (c *Commander) Radio() error {
if err != nil { if err != nil {
return err return err
} }
return c.RadioGivenSong(tracks.Tracks[rand.Intn(len(tracks.Tracks))].SimpleTrack, 0) return c.RadioGivenSong(tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack, 0)
} }
func (c *Commander) RadioFromPlaylist(playlist spotify.SimplePlaylist) error { func (c *Commander) RadioFromPlaylist(playlist spotify.SimplePlaylist) error {
total := playlist.Tracks.Total
if total == 0 {
return fmt.Errorf("this playlist is empty")
}
pages := int(math.Ceil(float64(total) / 50))
randomPage := 1
if pages > 1 {
randomPage = frand.Intn(pages-1) + 1
}
playlistPage, err := c.Client().GetPlaylistItems( playlistPage, err := c.Client().GetPlaylistItems(
c.Context, c.Context,
playlist.ID, playlist.ID,
spotify.Limit(50), spotify.Limit(50),
spotify.Offset(0), spotify.Offset((randomPage-1)*50),
) )
if err != nil { if err != nil {
return err return err
} }
pageSongs := playlistPage.Items pageSongs := playlistPage.Items
rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] }) frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
seedCount := 5 seedCount := 5
if len(pageSongs) < seedCount { if len(pageSongs) < seedCount {
seedCount = len(pageSongs) seedCount = len(pageSongs)
@ -71,14 +80,14 @@ func (c *Commander) RadioFromSavedTracks() error {
pages := int(math.Ceil(float64(savedSongs.Total) / 50)) pages := int(math.Ceil(float64(savedSongs.Total) / 50))
randomPage := 1 randomPage := 1
if pages > 1 { if pages > 1 {
randomPage = rand.Intn(pages-1) + 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 { if err != nil {
return err return err
} }
pageSongs := trackPage.Tracks pageSongs := trackPage.Tracks
rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] }) frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
seedCount := 4 seedCount := 4
seedIds := []spotify.ID{} seedIds := []spotify.ID{}
for idx, song := range pageSongs { for idx, song := range pageSongs {
@ -140,17 +149,16 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
return err return err
} }
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
id := rand.Intn(len(recomendationIds)-2) + 1 id := frand.Intn(len(recomendationIds)-2) + 1
seed := spotify.Seeds{ seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]}, Tracks: []spotify.ID{recomendationIds[id]},
} }
additionalRecs, err := c.Client(). additional_recs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
if err != nil { if err != nil {
return err return err
} }
additionalRecsIds := []spotify.ID{} additionalRecsIds := []spotify.ID{}
for _, song := range additionalRecs.Tracks { for _, song := range additional_recs.Tracks {
exists, err := c.SongExists(db, song.ID) exists, err := c.SongExists(db, song.ID)
if err != nil { if err != nil {
return err return err
@ -171,7 +179,7 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
return nil return nil
} }
func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos spotify.Numeric) error { func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
start := time.Now().UnixMilli() start := time.Now().UnixMilli()
seed := spotify.Seeds{ seed := spotify.Seeds{
Tracks: []spotify.ID{song.ID}, Tracks: []spotify.ID{song.ID},
@ -216,7 +224,7 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos spotify.Numeric
} }
delay := time.Now().UnixMilli() - start delay := time.Now().UnixMilli() - start
if pos != 0 { if pos != 0 {
pos = pos + spotify.Numeric(delay) pos = pos + int(delay)
} }
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{ err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI, PlaybackContext: &radioPlaylist.URI,
@ -243,17 +251,16 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos spotify.Numeric
return err return err
} }
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
id := rand.Intn(len(recomendationIds)-2) + 1 id := frand.Intn(len(recomendationIds)-2) + 1
seed := spotify.Seeds{ seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]}, Tracks: []spotify.ID{recomendationIds[id]},
} }
additionalRecs, err := c.Client(). additional_recs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
if err != nil { if err != nil {
return err return err
} }
additionalRecsIds := []spotify.ID{} additionalRecsIds := []spotify.ID{}
for _, song := range additionalRecs.Tracks { for _, song := range additional_recs.Tracks {
exists, err := c.SongExists(db, song.ID) exists, err := c.SongExists(db, song.ID)
if err != nil { if err != nil {
return err return err
@ -337,9 +344,9 @@ func (c *Commander) CreateRadioPlaylist(name string) (*spotify.FullPlaylist, *sq
} }
func (c *Commander) SongExists(db *sql.DB, song spotify.ID) (bool, error) { func (c *Commander) SongExists(db *sql.DB, song spotify.ID) (bool, error) {
songID := string(song) song_id := string(song)
sqlStmt := `SELECT id FROM radio WHERE id = ?` sqlStmt := `SELECT id FROM radio WHERE id = ?`
err := db.QueryRow(sqlStmt, songID).Scan(&songID) err := db.QueryRow(sqlStmt, song_id).Scan(&song_id)
if err != nil { if err != nil {
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
return false, err return false, err
@ -356,25 +363,24 @@ func (c *Commander) RefillRadio() error {
if err != nil { if err != nil {
return err return err
} }
paused := false
if !status.Playing { if !status.Playing {
paused = true return nil
} }
toRemove := []spotify.ID{} to_remove := []spotify.ID{}
radioPlaylist, db, err := c.GetRadioPlaylist("") radioPlaylist, db, err := c.GetRadioPlaylist("")
if err != nil { if err != nil {
return err return err
} }
if status.PlaybackContext.URI != radioPlaylist.URI {
return nil
}
playlistItems, err := c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID) playlistItems, err := c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID)
if err != nil { if err != nil {
return fmt.Errorf("orig playlist items: %w", err) return fmt.Errorf("orig playlist items: %w", err)
} }
if status.PlaybackContext.URI != radioPlaylist.URI || paused {
return c.RadioFromPlaylist(radioPlaylist.SimplePlaylist)
}
page := 0 page := 0
for { 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))
@ -388,14 +394,14 @@ func (c *Commander) RefillRadio() error {
if track.Track.Track.ID == status.Item.ID { if track.Track.Track.ID == status.Item.ID {
break break
} }
toRemove = append(toRemove, track.Track.Track.ID) to_remove = append(to_remove, track.Track.Track.ID)
} }
page++ page++
} }
if len(toRemove) > 0 { if len(to_remove) > 0 {
var trackGroups []spotify.ID var trackGroups []spotify.ID
for idx, item := range toRemove { for idx, item := range to_remove {
if idx%100 == 0 && idx != 0 { 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 = []spotify.ID{}
} }
@ -410,7 +416,7 @@ func (c *Commander) RefillRadio() error {
} }
} }
toAdd := 500 - (int(playlistItems.Total) - len(toRemove)) to_add := 500 - (playlistItems.Total - len(to_remove))
playlistItems, err = c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID) playlistItems, err = c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID)
if err != nil { if err != nil {
return fmt.Errorf("playlist items: %w", err) return fmt.Errorf("playlist items: %w", err)
@ -419,7 +425,7 @@ func (c *Commander) RefillRadio() error {
pages := int(math.Ceil(float64(total) / 50)) pages := int(math.Ceil(float64(total) / 50))
randomPage := 1 randomPage := 1
if pages > 1 { if pages > 1 {
randomPage = rand.Intn(pages-1) + 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)) GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50))
@ -427,7 +433,7 @@ func (c *Commander) RefillRadio() error {
return fmt.Errorf("playlist page: %w", err) return fmt.Errorf("playlist page: %w", err)
} }
pageSongs := playlistPage.Items pageSongs := playlistPage.Items
rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] }) frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
seedCount := 5 seedCount := 5
if len(pageSongs) < seedCount { if len(pageSongs) < seedCount {
seedCount = len(pageSongs) seedCount = len(pageSongs)
@ -458,7 +464,7 @@ func (c *Commander) RefillRadio() error {
} }
queue := []spotify.ID{} queue := []spotify.ID{}
for idx, rec := range recomendationIds { for idx, rec := range recomendationIds {
if idx > toAdd { if idx > to_add {
break break
} }
_, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", rec.String())) _, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", rec.String()))
@ -467,7 +473,7 @@ func (c *Commander) RefillRadio() error {
} }
queue = append(queue, rec) queue = append(queue, rec)
} }
toAdd -= len(queue) to_add -= len(queue)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...) _, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
if err != nil { if err != nil {
return fmt.Errorf("add tracks: %w", err) return fmt.Errorf("add tracks: %w", err)
@ -476,31 +482,30 @@ func (c *Commander) RefillRadio() error {
if err != nil { if err != nil {
return fmt.Errorf("repeat: %w", err) return fmt.Errorf("repeat: %w", err)
} }
for toAdd > 0 { for to_add > 0 {
id := rand.Intn(len(recomendationIds)-2) + 1 id := frand.Intn(len(recomendationIds)-2) + 1
seed := spotify.Seeds{ seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]}, Tracks: []spotify.ID{recomendationIds[id]},
} }
additionalRecs, err := c.Client(). additional_recs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
if err != nil { if err != nil {
return fmt.Errorf("get recs: %w", err) return fmt.Errorf("get recs: %w", err)
} }
additionalRecsIds := []spotify.ID{} additionalRecsIds := []spotify.ID{}
for idx, song := range additionalRecs.Tracks { for idx, song := range additional_recs.Tracks {
exists, err := c.SongExists(db, song.ID) exists, err := c.SongExists(db, song.ID)
if err != nil { if err != nil {
return fmt.Errorf("check song existence: %w", err) return fmt.Errorf("check song existence: %w", err)
} }
if !exists { if !exists {
if idx > toAdd { if idx > to_add {
break break
} }
additionalRecsIds = append(additionalRecsIds, song.ID) additionalRecsIds = append(additionalRecsIds, song.ID)
queue = append(queue, song.ID) queue = append(queue, song.ID)
} }
} }
toAdd -= len(queue) to_add -= len(queue)
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...) _, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
if err != nil { if err != nil {
return fmt.Errorf("add tracks to playlist: %w", err) return fmt.Errorf("add tracks to playlist: %w", err)
@ -521,14 +526,14 @@ func (c *Commander) RadioFromAlbum(album spotify.SimpleAlbum) error {
pages := int(math.Ceil(float64(total) / 50)) pages := int(math.Ceil(float64(total) / 50))
randomPage := 1 randomPage := 1
if pages > 1 { if pages > 1 {
randomPage = rand.Intn(pages-1) + 1 randomPage = frand.Intn(pages-1) + 1
} }
albumTrackPage, err := c.AlbumTracks(album.ID, randomPage) albumTrackPage, err := c.AlbumTracks(album.ID, randomPage)
if err != nil { if err != nil {
return err return err
} }
pageSongs := albumTrackPage.Tracks pageSongs := albumTrackPage.Tracks
rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] }) frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
seedCount := 5 seedCount := 5
if len(pageSongs) < seedCount { if len(pageSongs) < seedCount {
seedCount = len(pageSongs) seedCount = len(pageSongs)
@ -543,9 +548,9 @@ func (c *Commander) RadioFromAlbum(album spotify.SimpleAlbum) error {
return c.RadioGivenList(seedIds[:seedCount], album.Name) return c.RadioGivenList(seedIds[:seedCount], album.Name)
} }
func (c *Commander) RadioGivenList(songs []spotify.ID, name string) error { func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
seed := spotify.Seeds{ seed := spotify.Seeds{
Tracks: songs, Tracks: song_ids,
} }
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 { if err != nil {
@ -563,7 +568,7 @@ func (c *Commander) RadioGivenList(songs []spotify.ID, name string) error {
if err != nil { if err != nil {
return err return err
} }
queue := []spotify.ID{songs[0]} queue := []spotify.ID{song_ids[0]}
for _, rec := range recomendationIds { for _, rec := range recomendationIds {
exists, err := c.SongExists(db, rec) exists, err := c.SongExists(db, rec)
if err != nil { if err != nil {
@ -586,13 +591,13 @@ func (c *Commander) RadioGivenList(songs []spotify.ID, name string) error {
}) })
if err != nil { if err != nil {
if isNoActiveError(err) { if isNoActiveError(err) {
deviceID, err := c.activateDevice() deviceId, err := c.activateDevice()
if err != nil { if err != nil {
return err return err
} }
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{ err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI, PlaybackContext: &radioPlaylist.URI,
DeviceID: &deviceID, DeviceID: &deviceId,
}) })
if err != nil { if err != nil {
return err return err
@ -600,16 +605,16 @@ func (c *Commander) RadioGivenList(songs []spotify.ID, name string) error {
} }
} }
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
id := rand.Intn(len(recomendationIds)-2) + 1 id := frand.Intn(len(recomendationIds)-2) + 1
seed := spotify.Seeds{ seed := spotify.Seeds{
Tracks: []spotify.ID{recomendationIds[id]}, Tracks: []spotify.ID{recomendationIds[id]},
} }
additionalRecs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100)) additional_recs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
if err != nil { if err != nil {
return err return err
} }
additionalRecsIds := []spotify.ID{} additionalRecsIds := []spotify.ID{}
for _, song := range additionalRecs.Tracks { for _, song := range additional_recs.Tracks {
exists, err := c.SongExists(db, song.ID) exists, err := c.SongExists(db, song.ID)
if err != nil { if err != nil {
return err return err

View File

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

View File

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

View File

@ -5,7 +5,7 @@ func (c *Commander) ChangeVolume(amount int) error {
if err != nil { if err != nil {
return err return err
} }
newVolume := int(state.Device.Volume) + amount newVolume := state.Device.Volume + amount
if newVolume > 100 { if newVolume > 100 {
newVolume = 100 newVolume = 100
} }

View File

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

View File

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

View File

@ -171,7 +171,7 @@ func SearchPlaylistsView(commands *commands.Commander, playlists *spotify.Simple
for _, playlist := range playlists.Playlists { for _, playlist := range playlists.Playlists {
items = append(items, mainItem{ items = append(items, mainItem{
Name: playlist.Name, Name: playlist.Name,
Desc: stripHTMLRegex(playlist.Description), Desc: stripHtmlRegex(playlist.Description),
SpotifyItem: playlist, SpotifyItem: playlist,
}) })
} }
@ -269,13 +269,13 @@ func SavedTracksView(commands *commands.Commander) ([]list.Item, error) {
func MainView(c *commands.Commander) ([]list.Item, error) { func MainView(c *commands.Commander) ([]list.Item, error) {
c.Log.Debug("SWITCHING TO MAIN VIEW") c.Log.Debug("SWITCHING TO MAIN VIEW")
wg := errgroup.Group{} wg := errgroup.Group{}
var savedItems *spotify.SavedTrackPage var saved_items *spotify.SavedTrackPage
var playlists *spotify.SimplePlaylistPage var playlists *spotify.SimplePlaylistPage
var artists *spotify.FullArtistCursorPage var artists *spotify.FullArtistCursorPage
var albums *spotify.SavedAlbumPage var albums *spotify.SavedAlbumPage
wg.Go(func() (err error) { wg.Go(func() (err error) {
savedItems, err = c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0)) saved_items, err = c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
return return
}) })
@ -300,11 +300,11 @@ func MainView(c *commands.Commander) ([]list.Item, error) {
} }
items := []list.Item{} items := []list.Item{}
if savedItems != nil && savedItems.Total != 0 { if saved_items != nil && saved_items.Total != 0 {
items = append(items, mainItem{ items = append(items, mainItem{
Name: "Saved Tracks", Name: "Saved Tracks",
Desc: fmt.Sprintf("%d saved songs", savedItems.Total), Desc: fmt.Sprintf("%d saved songs", saved_items.Total),
SpotifyItem: savedItems, SpotifyItem: saved_items,
}) })
} }
if albums != nil && albums.Total != 0 { if albums != nil && albums.Total != 0 {
@ -330,7 +330,7 @@ func MainView(c *commands.Commander) ([]list.Item, error) {
for _, playlist := range playlists.Playlists { for _, playlist := range playlists.Playlists {
items = append(items, mainItem{ items = append(items, mainItem{
Name: playlist.Name, Name: playlist.Name,
Desc: stripHTMLRegex(playlist.Description), Desc: stripHtmlRegex(playlist.Description),
SpotifyItem: playlist, SpotifyItem: playlist,
}) })
} }
@ -338,7 +338,7 @@ func MainView(c *commands.Commander) ([]list.Item, error) {
return items, nil return items, nil
} }
func stripHTMLRegex(s string) string { func stripHtmlRegex(s string) string {
r := regexp.MustCompile(regex) r := regexp.MustCompile(regex)
return r.ReplaceAllString(s, "") return r.ReplaceAllString(s, "")
} }

View File

@ -1,102 +0,0 @@
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 package config
type Config struct { type Config struct {
ClientID string `yaml:"client_id"` ClientId string `yaml:"client_id"`
ClientSecret string `yaml:"client_secret"` ClientSecret string `yaml:"client_secret"`
ClientSecretCmd string `yaml:"client_secret_cmd"` ClientSecretCmd string `yaml:"client_secret_cmd"`
Port string `yaml:"port"` Port string `yaml:"port"`

View File

@ -33,24 +33,24 @@ func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error)
} }
func GetClient(conf *config.Config) (c *spotify.Client, err error) { func GetClient(conf *config.Config) (c *spotify.Client, err error) {
if conf.ClientID == "" || (conf.ClientSecret == "" && conf.ClientSecretCmd == "") || conf.Port == "" { if conf.ClientId == "" || (conf.ClientSecret == "" && conf.ClientSecretCmd == "") || conf.Port == "" {
return nil, fmt.Errorf("INVALID CONFIG") return nil, fmt.Errorf("INVALID CONFIG")
} }
if conf.ClientSecretCmd != "" { if conf.ClientSecretCmd != "" {
args := strings.Fields(conf.ClientSecretCmd) args := strings.Fields(conf.ClientSecretCmd)
cmd := args[0] cmd := args[0]
secretCommand := exec.Command(cmd) secret_command := exec.Command(cmd)
if len(args) > 1 { if len(args) > 1 {
secretCommand.Args = args secret_command.Args = args
} }
secret, err := secretCommand.Output() secret, err := secret_command.Output()
if err != nil { if err != nil {
panic(err) panic(err)
} }
conf.ClientSecret = strings.TrimSpace(string(secret)) conf.ClientSecret = strings.TrimSpace(string(secret))
} }
auth = spotifyauth.New( auth = spotifyauth.New(
spotifyauth.WithClientID(conf.ClientID), spotifyauth.WithClientID(conf.ClientId),
spotifyauth.WithClientSecret(conf.ClientSecret), spotifyauth.WithClientSecret(conf.ClientSecret),
spotifyauth.WithRedirectURL(fmt.Sprintf("http://localhost:%s/callback", conf.Port)), spotifyauth.WithRedirectURL(fmt.Sprintf("http://localhost:%s/callback", conf.Port)),
spotifyauth.WithScopes( spotifyauth.WithScopes(
@ -92,12 +92,12 @@ func GetClient(conf *config.Config) (c *spotify.Client, err error) {
}), }),
}) })
authClient := auth.Client(authCtx, tok) authClient := auth.Client(authCtx, tok)
client := spotify.New(authClient, spotify.WithRetry(true)) client := spotify.New(authClient)
newToken, err := client.Token() new_token, err := client.Token()
if err != nil { if err != nil {
return nil, err return nil, err
} }
out, err := json.MarshalIndent(newToken, "", " ") out, err := json.MarshalIndent(new_token, "", " ")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -156,7 +156,7 @@ func completeAuth(w http.ResponseWriter, r *http.Request) {
slog.Error("AUTHENTICATOR", "failed to save auth", err) slog.Error("AUTHENTICATOR", "failed to save auth", err)
} }
// use the token to get an authenticated client // use the token to get an authenticated client
client := spotify.New(auth.Client(r.Context(), tok), spotify.WithRetry(true)) client := spotify.New(auth.Client(r.Context(), tok))
fmt.Fprintf(w, "Login Completed!") fmt.Fprintf(w, "Login Completed!")
ch <- client ch <- client
} }