Compare commits
62 Commits
Author | SHA1 | Date | |
---|---|---|---|
e4f23c6805 | |||
2d5567621f | |||
40d0d2c919 | |||
eb03bcd385 | |||
5c423b082f | |||
958d214505 | |||
779cb517c9 | |||
1b52748db0 | |||
57c97cddb4 | |||
e05de59160 | |||
c5e536a472 | |||
2c3a2e1778 | |||
8ce5611154 | |||
4d3f1d85bf | |||
5e1dae88e0 | |||
cceb5fb8bb | |||
864c3163b0 | |||
2a70db8607 | |||
c1b8eee83d | |||
4549a21b9a | |||
007dcd39e3 | |||
a156f078aa | |||
58f09618e9 | |||
49880d6831 | |||
c3b56d8e7e | |||
efc8ebb943 | |||
54e6ec39d3 | |||
5e2327771a | |||
2760c3ae93 | |||
8347c24fbe | |||
a90f7d2ce9 | |||
4d4038fd99 | |||
5e40c8e2a2 | |||
2a28c74caf | |||
c1ac26e684 | |||
8a21c7cb52 | |||
a7152075fa | |||
c28448c295 | |||
ec69919ec6 | |||
dd2284770d | |||
52dbe52c50 | |||
bdec590110 | |||
b516cb8d2f | |||
bef9898bf2 | |||
786ee38671 | |||
823774f7bd | |||
ba07377ca8 | |||
dc2c74dd4b | |||
c26d11299a | |||
c1d4a0b5e0 | |||
338bef9f75 | |||
bafaa8a941 | |||
e6344bef01 | |||
ae4291032e | |||
ad77e043d6 | |||
2116052d5e | |||
1faea3fd7f | |||
f6f7d3bd70 | |||
86c2c38dfe | |||
59fff8dfef | |||
8e067ac28d | |||
c474690f3e |
15
.gitea/workflows/push.yaml
Normal file
15
.gitea/workflows/push.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
name: builder
|
||||
run-name: ${{ gitea.actor }} is building
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22"
|
||||
cache: true
|
||||
- run: go mod tidy
|
||||
- run: go build -o gspot
|
28
.gitea/workflows/releaser.yaml
Normal file
28
.gitea/workflows/releaser.yaml
Normal file
@ -0,0 +1,28 @@
|
||||
name: deployer
|
||||
run-name: ${{ gitea.actor }} is releasing
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
go-releaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup up go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
cache: true
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: nightly
|
||||
args: release --clean
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.ACCESS_TOKEN_GITEA}}
|
82
.golangci.yml
Normal file
82
.golangci.yml
Normal file
@ -0,0 +1,82 @@
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- gofmt
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- gocritic
|
||||
- bodyclose
|
||||
- gosec
|
||||
- prealloc
|
||||
- unconvert
|
||||
- unused
|
||||
|
||||
linters-settings:
|
||||
gocritic:
|
||||
# Which checks should be enabled; can't be combined with 'disabled-checks';
|
||||
# See https://go-critic.github.io/overview#checks-overview
|
||||
# To check which checks are enabled run `GL_DEBUG=gocritic ./build/bin/golangci-lint run`
|
||||
# By default list of stable checks is used.
|
||||
enabled-checks:
|
||||
- ruleguard
|
||||
- truncateCmp
|
||||
|
||||
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
|
||||
disabled-checks:
|
||||
- captLocal
|
||||
- assignOp
|
||||
- paramTypeCombine
|
||||
- importShadow
|
||||
- commentFormatting
|
||||
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||
enabled-tags:
|
||||
- performance
|
||||
- diagnostic
|
||||
- opinionated
|
||||
disabled-tags:
|
||||
- experimental
|
||||
settings:
|
||||
hugeParam:
|
||||
# size in bytes that makes the warning trigger (default 80)
|
||||
sizeThreshold: 1000
|
||||
rangeValCopy:
|
||||
sizeThreshold: 1024
|
||||
rangeExprCopy:
|
||||
# size in bytes that makes the warning trigger (default 512)
|
||||
sizeThreshold: 512
|
||||
# whether to check test functions (default true)
|
||||
skipTestFuncs: true
|
||||
truncateCmp:
|
||||
# whether to skip int/uint/uintptr types (default true)
|
||||
skipArchDependent: true
|
||||
underef:
|
||||
# whether to skip (*x).method() calls where x is a pointer receiver (default true)
|
||||
skipRecvDeref: true
|
||||
|
||||
govet:
|
||||
disable:
|
||||
- deepequalerrors
|
||||
- fieldalignment
|
||||
- shadow
|
||||
- unsafeptr
|
||||
goconst:
|
||||
min-len: 2
|
||||
min-occurrences: 2
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- golint
|
||||
text: "should be"
|
||||
- linters:
|
||||
- errcheck
|
||||
text: "not checked"
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA(1019|1029|5011)"
|
||||
|
@ -30,31 +30,29 @@ archives:
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
files:
|
||||
- completions/*
|
||||
- goos: windows
|
||||
format: zip
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
name_template: "checksums.txt"
|
||||
snapshot:
|
||||
name_template: "{{ incpatch .Version }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
groups:
|
||||
- title: Added
|
||||
regexp: '^.*?ADD(\([[:word:]]+\))??!?:.+$'
|
||||
order: 0
|
||||
- title: 'Bug fixes'
|
||||
regexp: '^.*?BUG(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: 'Enhancements'
|
||||
regexp: '^.*?IMPROVED(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: 'Docs'
|
||||
regexp: '^.*?DOC(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: 'CI'
|
||||
regexp: '^.*?CI(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: Others
|
||||
order: 999
|
||||
- title: Added
|
||||
regexp: '^.*?ADD(\([[:word:]]+\))??!?:.+$'
|
||||
order: 0
|
||||
- title: "Bug fixes"
|
||||
regexp: '^.*?BUG(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: "Enhancements"
|
||||
regexp: '^.*?IMPROVED(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: "Docs"
|
||||
regexp: '^.*?DOC(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: "CI"
|
||||
regexp: '^.*?CI(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: Others
|
||||
order: 999
|
||||
|
@ -1,14 +0,0 @@
|
||||
steps:
|
||||
build:
|
||||
image: golang:1.22
|
||||
commands:
|
||||
- go mod tidy
|
||||
- go build -o gspot
|
||||
|
||||
publish:
|
||||
image: goreleaser/goreleaser:nightly
|
||||
commands:
|
||||
- goreleaser release --clean
|
||||
secrets: [ gitea_token ]
|
||||
when:
|
||||
event: tag
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 abs3nt
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
10
Makefile
10
Makefile
@ -1,16 +1,14 @@
|
||||
build: gspot
|
||||
|
||||
gspot: $(shell find . -name '*.go')
|
||||
build:
|
||||
go build -ldflags="-X 'git.asdf.cafe/abs3nt/gspot/src/components/cli.Version=$(shell git show -s --date=short --pretty='format:%h (%ad)' HEAD)'" -o dist/ .
|
||||
|
||||
run:
|
||||
go run main.go
|
||||
run: build
|
||||
./dist/gspot
|
||||
|
||||
tidy:
|
||||
go mod tidy
|
||||
|
||||
clean:
|
||||
rm -rf bin
|
||||
rm -rf dist
|
||||
|
||||
uninstall:
|
||||
rm -f /usr/bin/gspot
|
||||
|
82
README.md
Normal file
82
README.md
Normal file
@ -0,0 +1,82 @@
|
||||
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)
|
@ -1,20 +1,16 @@
|
||||
#compdef gspot
|
||||
|
||||
_cli_zsh_autocomplete() {
|
||||
local -a opts
|
||||
local cur
|
||||
cur=${words[-1]}
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
||||
else
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||
fi
|
||||
local -a opts
|
||||
local cur
|
||||
cur=${words[-1]}
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}")
|
||||
else
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
}
|
||||
|
||||
compdef _cli_zsh_autocomplete gspot
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
|
106
go.mod
106
go.mod
@ -1,79 +1,81 @@
|
||||
module git.asdf.cafe/abs3nt/gspot
|
||||
|
||||
go 1.22.0
|
||||
go 1.22.3
|
||||
|
||||
require (
|
||||
gfx.cafe/util/go/frand v0.0.0-20231226111635-bc00a6a250fb
|
||||
gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb
|
||||
git.asdf.cafe/abs3nt/gunner v0.0.1
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.25.0
|
||||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/lmittmann/tint v1.0.4
|
||||
github.com/urfave/cli/v2 v2.27.1
|
||||
github.com/zmb3/spotify/v2 v2.4.1
|
||||
go.uber.org/fx v1.20.1
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/sync v0.3.0
|
||||
google.golang.org/api v0.133.0
|
||||
modernc.org/sqlite v1.29.1
|
||||
github.com/charmbracelet/bubbles v0.20.0
|
||||
github.com/charmbracelet/bubbletea v1.1.2
|
||||
github.com/charmbracelet/lipgloss v0.13.1
|
||||
github.com/lmittmann/tint v1.0.5
|
||||
github.com/rivo/tview v0.0.0-20241016194538-c5e4fb24af13
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.1
|
||||
github.com/zmb3/spotify/v2 v2.4.2
|
||||
go.uber.org/fx v1.23.0
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
||||
golang.org/x/net v0.30.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sync v0.8.0
|
||||
google.golang.org/api v0.203.0
|
||||
modernc.org/sqlite v1.33.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.22.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
cloud.google.com/go/auth v0.9.9 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.2 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cristalhq/aconfig v0.18.5 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.4.0 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
||||
github.com/cristalhq/aconfig v0.18.6 // indirect
|
||||
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gdamore/encoding v1.0.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/protobuf v1.5.3 // indirect
|
||||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/joho/godotenv v1.4.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // 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/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/dig v1.17.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895 // indirect
|
||||
google.golang.org/grpc v1.56.2 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.41.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
|
||||
go.opentelemetry.io/otel v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.31.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.31.0 // indirect
|
||||
go.uber.org/dig v1.18.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||
google.golang.org/grpc v1.67.1 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // 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/memory v1.7.2 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
|
296
go.sum
296
go.sum
@ -13,16 +13,18 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/auth v0.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.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v1.22.0 h1:cB8R6FtUtT1TYGl5R3xuxnW6OUIc/DrT2aiR16TTG7Y=
|
||||
cloud.google.com/go/compute v1.22.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
|
||||
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
@ -35,50 +37,35 @@ 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.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=
|
||||
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/go.mod h1:Q4zhiPfmffCVAb5xIzZn6Momm91uf/deqRVd2/vdjd4=
|
||||
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/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
|
||||
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
||||
github.com/charmbracelet/bubbletea v1.1.2 h1:naQXF2laRxyLyil/i7fxdpiz1/k06IKquhm4vBfHsIc=
|
||||
github.com/charmbracelet/bubbletea v1.1.2/go.mod h1:9HIU/hBV24qKjlehyj8z1r/tR9TYTQEag+cWZnuXo8E=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/charmbracelet/lipgloss v0.13.1 h1:Oik/oqDTMVA01GetT4JdEC033dNzWoQHdWnHnQmXE2A=
|
||||
github.com/charmbracelet/lipgloss v0.13.1/go.mod h1:zaYVJ2xKSKEnTEEbX6uAHabh2d975RJ+0yfkFpRBz5U=
|
||||
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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
github.com/cristalhq/aconfig v0.18.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
|
||||
github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
github.com/cristalhq/aconfig v0.18.6 h1:8KRBznzdjUUiaa7HeIpYbMx1uPE1/xOBEU1ajsnmNME=
|
||||
github.com/cristalhq/aconfig v0.18.6/go.mod h1:9ogrGEt9yU5V4pif/ThkVUfhj8JkdV+iDeahZGgfnDU=
|
||||
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -89,13 +76,24 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gdamore/encoding v1.0.0/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/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -125,8 +123,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@ -138,8 +134,9 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
@ -149,100 +146,92 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
|
||||
github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
|
||||
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
|
||||
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lmittmann/tint v1.0.5 h1:NQclAutOfYsqs2F1Lenue6OoWCajs5wJcP3DfWVpePw=
|
||||
github.com/lmittmann/tint v1.0.5/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
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/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
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/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/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/tview v0.0.0-20241016194538-c5e4fb24af13 h1:SG5LUOAzLU9svb9HTLJI2WnLHQDEe86fXWJ4h2fQg0s=
|
||||
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.4.7-0.20240127222946-601bbb3750c2 h1:tcc3ZFBvjydcgrAxavZRYqFqCKzy0FJ+UY4ATq4QVXk=
|
||||
github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
|
||||
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.1 h1:1fJU+bltkwN8lF4Sni/X0i1d8XwPIrS82ivZ8qsp/q4=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zmb3/spotify/v2 v2.4.1 h1:2ENzO3XQLOQBuxgT1Z9+PlCBSkjNgzFzmRaPns0tjM4=
|
||||
github.com/zmb3/spotify/v2 v2.4.1/go.mod h1:p3r7mCCxHepzVaJOe3w1dlx9SL+T8iiQR14tfXJpuTE=
|
||||
github.com/zmb3/spotify/v2 v2.4.2 h1:j3yNN5lKVEMZQItJF4MHCSZbfNWmXO+KaC+3RFaLlLc=
|
||||
github.com/zmb3/spotify/v2 v2.4.2/go.mod h1:XOV7BrThayFYB9AAfB+L0Q0wyxBuLCARk4fI/ZXCBW8=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@ -250,28 +239,34 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI=
|
||||
go.uber.org/dig v1.17.0/go.mod h1:rTxpf7l5I0eBTlE6/9RL+lDybC7WFwY2QH55ZSjy1mU=
|
||||
go.uber.org/fx v1.20.1 h1:zVwVQGS8zYvhh9Xxcu4w1M6ESyeMzebzj2NbSayZ4Mk=
|
||||
go.uber.org/fx v1.20.1/go.mod h1:iSYNbHf2y55acNCwCXKx7LbWb5WG1Bnue5RDXz1OREg=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
|
||||
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
|
||||
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
|
||||
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
|
||||
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
|
||||
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
|
||||
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
|
||||
go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw=
|
||||
go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg=
|
||||
go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -282,8 +277,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@ -306,8 +301,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -336,20 +331,21 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -360,8 +356,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -389,35 +385,36 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -463,8 +460,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -485,15 +482,14 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.133.0 h1:N7Ym5Hl0Dpn0I0o7R1z4UpVA1GCDyS8vbPu1/ObV73A=
|
||||
google.golang.org/api v0.133.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk=
|
||||
google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU=
|
||||
google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -518,19 +514,17 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
|
||||
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895 h1:co8AMhI481nhd3WBfW2mq5msyQHNBcGn7G9GCEqz45k=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||
google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@ -543,12 +537,9 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
|
||||
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -561,15 +552,14 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@ -580,16 +570,28 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk=
|
||||
modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4=
|
||||
modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0=
|
||||
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/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/sqlite v1.29.1 h1:19GY2qvWB4VPw0HppFlZCPAbmxFU41r+qjKZQdQ1ryA=
|
||||
modernc.org/sqlite v1.29.1/go.mod h1:hG41jCYxOAOoO6BRK66AdRlmOcDzXf7qnwlwjUIOqa0=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.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/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
50
main.go
50
main.go
@ -1,22 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"go.uber.org/fx"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/fx/fxevent"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gspot/src/app"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/components/cache"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/components/cli"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/components/commands"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/components/logger"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/services"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var s fx.Shutdowner
|
||||
app := fx.New(
|
||||
fx.WithLogger(func(logger *slog.Logger) fxevent.Logger {
|
||||
l := &fxevent.SlogLogger{Logger: logger}
|
||||
l.UseLogLevel(slog.LevelDebug)
|
||||
return l
|
||||
}),
|
||||
fx.Populate(&s),
|
||||
app.Config,
|
||||
services.Config,
|
||||
fx.Provide(
|
||||
Context,
|
||||
cache.NewCache,
|
||||
commands.NewCommander,
|
||||
logger.NewLogger,
|
||||
),
|
||||
fx.Invoke(
|
||||
cli.Run,
|
||||
@ -24,3 +38,33 @@ func main() {
|
||||
)
|
||||
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()
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
3
renovate.json
Normal file
3
renovate.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["config:recommended"]
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"gfx.cafe/util/go/fxplus"
|
||||
"git.asdf.cafe/abs3nt/gunner"
|
||||
"github.com/lmittmann/tint"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gspot/src/config"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/services"
|
||||
)
|
||||
|
||||
var Services = fx.Options(
|
||||
fx.NopLogger,
|
||||
fx.Provide(
|
||||
func() *slog.Logger {
|
||||
return slog.New(tint.NewHandler(os.Stdout, &tint.Options{
|
||||
Level: slog.LevelDebug.Level(),
|
||||
TimeFormat: "[15:04:05.000]",
|
||||
}))
|
||||
},
|
||||
services.NewSpotifyClient,
|
||||
fxplus.Context,
|
||||
),
|
||||
)
|
||||
|
||||
var Config = fx.Options(
|
||||
fx.Provide(
|
||||
func() *config.Config {
|
||||
c := &config.Config{}
|
||||
gunner.LoadApp(c, "gspot")
|
||||
return c
|
||||
},
|
||||
),
|
||||
Services,
|
||||
)
|
@ -1,32 +1,32 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gspot/src/components/commands"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/components/tui"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/components/tuitview"
|
||||
)
|
||||
|
||||
var Version = "dev"
|
||||
|
||||
func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
defer func() {
|
||||
err := s.Shutdown()
|
||||
if err != nil {
|
||||
c.Log.Error("SHUTDOWN", "error shutting down", err)
|
||||
}
|
||||
}()
|
||||
app := &cli.App{
|
||||
Name: "gspot",
|
||||
EnableBashCompletion: true,
|
||||
Version: Version,
|
||||
Action: func(ctx *cli.Context) error {
|
||||
app := &cli.Command{
|
||||
Name: "gspot",
|
||||
EnableShellCompletion: true,
|
||||
Version: Version,
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unknown command: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return tui.StartTea(c, "main")
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
@ -34,7 +34,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "play",
|
||||
Aliases: []string{"pl", "start", "s"},
|
||||
Usage: "Plays spotify",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Play()
|
||||
},
|
||||
Category: "Playback",
|
||||
@ -43,10 +46,15 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "playurl",
|
||||
Aliases: []string{"plu"},
|
||||
Usage: "Plays a spotify url",
|
||||
Args: true,
|
||||
ArgsUsage: "url",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
return c.PlayUrl(ctx.Args().First())
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if !cmd.Args().Present() {
|
||||
return fmt.Errorf("no url provided")
|
||||
}
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.PlayURL(cmd.Args().First())
|
||||
},
|
||||
Category: "Playback",
|
||||
},
|
||||
@ -54,7 +62,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "pause",
|
||||
Aliases: []string{"pa"},
|
||||
Usage: "Pauses spotify",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Pause()
|
||||
},
|
||||
Category: "Playback",
|
||||
@ -63,7 +74,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "toggleplay",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Toggles play/pause",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.TogglePlay()
|
||||
},
|
||||
Category: "Playback",
|
||||
@ -72,7 +86,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "link",
|
||||
Aliases: []string{"yy"},
|
||||
Usage: "Prints the current song's spotify link",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.PrintLink()
|
||||
},
|
||||
Category: "Sharing",
|
||||
@ -81,7 +98,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "linkcontext",
|
||||
Aliases: []string{"lc"},
|
||||
Usage: "Prints the current album or playlist",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.PrintLinkContext()
|
||||
},
|
||||
Category: "Sharing",
|
||||
@ -90,7 +110,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "youtube-link",
|
||||
Aliases: []string{"yl"},
|
||||
Usage: "Prints the current song's youtube link",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.PrintYoutubeLink()
|
||||
},
|
||||
Category: "Sharing",
|
||||
@ -99,11 +122,13 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "next",
|
||||
Aliases: []string{"n", "skip"},
|
||||
Usage: "Skips to the next song",
|
||||
Args: true,
|
||||
ArgsUsage: "amount",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.NArg() > 0 {
|
||||
amt, err := strconv.Atoi(cCtx.Args().First())
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
if cmd.NArg() > 0 {
|
||||
amt, err := strconv.Atoi(cmd.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -117,7 +142,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "previous",
|
||||
Aliases: []string{"b", "prev", "back"},
|
||||
Usage: "Skips to the previous song",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Previous()
|
||||
},
|
||||
Category: "Playback",
|
||||
@ -126,7 +154,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "like",
|
||||
Aliases: []string{"l"},
|
||||
Usage: "Likes the current song",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Like()
|
||||
},
|
||||
Category: "Library Management",
|
||||
@ -135,7 +166,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "unlike",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "Unlikes the current song",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.UnLike()
|
||||
},
|
||||
Category: "Library Management",
|
||||
@ -152,8 +186,11 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Usage: "bypass cache",
|
||||
},
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.NowPlaying(cCtx.Bool("force"))
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.NowPlaying(cmd.Bool("force"))
|
||||
},
|
||||
Category: "Info",
|
||||
},
|
||||
@ -162,14 +199,16 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Aliases: []string{"v"},
|
||||
Usage: "Control the volume",
|
||||
Category: "Playback",
|
||||
Subcommands: []*cli.Command{
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "up",
|
||||
Usage: "Increase the volume",
|
||||
Args: true,
|
||||
ArgsUsage: "percent",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
amt, err := strconv.Atoi(cCtx.Args().First())
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
amt, err := strconv.Atoi(cmd.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -180,10 +219,12 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "down",
|
||||
Aliases: []string{"dn"},
|
||||
Usage: "Decrease the volume",
|
||||
Args: true,
|
||||
ArgsUsage: "percent",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
amt, err := strconv.Atoi(cCtx.Args().First())
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
amt, err := strconv.Atoi(cmd.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -194,7 +235,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "mute",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "Mute",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Mute()
|
||||
},
|
||||
},
|
||||
@ -202,7 +246,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "unmute",
|
||||
Aliases: []string{"um"},
|
||||
Usage: "Unmute",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.UnMute()
|
||||
},
|
||||
},
|
||||
@ -210,7 +257,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "togglemute",
|
||||
Aliases: []string{"tm"},
|
||||
Usage: "Toggle mute",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.ToggleMute()
|
||||
},
|
||||
},
|
||||
@ -220,15 +270,17 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "download_cover",
|
||||
Usage: "Downloads the cover of the current song",
|
||||
Aliases: []string{"dl"},
|
||||
Args: true,
|
||||
ArgsUsage: "path",
|
||||
BashComplete: func(cCtx *cli.Context) {
|
||||
if cCtx.NArg() > 0 {
|
||||
ShellComplete: func(ctx context.Context, cmd *cli.Command) {
|
||||
if cmd.NArg() > 0 {
|
||||
return
|
||||
}
|
||||
},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
return c.DownloadCover(cCtx.Args().First())
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.DownloadCover(cmd.Args().First())
|
||||
},
|
||||
Category: "Info",
|
||||
},
|
||||
@ -236,7 +288,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "radio",
|
||||
Usage: "Starts a radio from the current song",
|
||||
Aliases: []string{"r"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Radio()
|
||||
},
|
||||
Category: "Radio",
|
||||
@ -245,7 +300,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "clearradio",
|
||||
Usage: "Clears the radio queue",
|
||||
Aliases: []string{"cr"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.ClearRadio()
|
||||
},
|
||||
Category: "Radio",
|
||||
@ -254,7 +312,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "refillradio",
|
||||
Usage: "Refills the radio queue with similar songs",
|
||||
Aliases: []string{"rr"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.RefillRadio()
|
||||
},
|
||||
Category: "Radio",
|
||||
@ -262,7 +323,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
{
|
||||
Name: "status",
|
||||
Usage: "Prints the current status",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Status()
|
||||
},
|
||||
Category: "Info",
|
||||
@ -271,7 +335,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "devices",
|
||||
Usage: "Lists available devices",
|
||||
Aliases: []string{"d"},
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.ListDevices()
|
||||
},
|
||||
Category: "Info",
|
||||
@ -279,20 +346,25 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
{
|
||||
Name: "setdevice",
|
||||
Usage: "Set the active device",
|
||||
Args: true,
|
||||
ArgsUsage: "<device_id>",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
if cCtx.NArg() == 0 {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.NArg() == 0 {
|
||||
return fmt.Errorf("no device id provided")
|
||||
}
|
||||
return c.SetDevice(spotify.ID(cCtx.Args().First()))
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.SetDevice(spotify.ID(cmd.Args().First()))
|
||||
},
|
||||
Category: "Playback",
|
||||
},
|
||||
{
|
||||
Name: "repeat",
|
||||
Usage: "Toggle repeat mode",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Repeat()
|
||||
},
|
||||
Category: "Playback",
|
||||
@ -300,7 +372,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
{
|
||||
Name: "shuffle",
|
||||
Usage: "Toggle shuffle mode",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Shuffle()
|
||||
},
|
||||
Category: "Playback",
|
||||
@ -308,28 +383,48 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
{
|
||||
Name: "tui",
|
||||
Usage: "Starts the TUI",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return tui.StartTea(c, "main")
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "tview",
|
||||
Usage: "Starts the TUI using tview (experimental)",
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
// start tview tui
|
||||
return tuitview.TuitView(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "seek",
|
||||
Usage: "Seek to a position in the song",
|
||||
Aliases: []string{"sk"},
|
||||
Category: "Playback",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
pos, err := strconv.Atoi(cCtx.Args().First())
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
pos, err := strconv.Atoi(cmd.Args().First())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SetPosition(pos)
|
||||
},
|
||||
Subcommands: []*cli.Command{
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "forward",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "Seek forward",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Seek(true)
|
||||
},
|
||||
},
|
||||
@ -337,7 +432,10 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
Name: "backward",
|
||||
Aliases: []string{"b"},
|
||||
Usage: "Seek backward",
|
||||
Action: func(cCtx *cli.Context) error {
|
||||
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||
if cmd.Args().Present() {
|
||||
return fmt.Errorf("unexpected arguments: %s", strings.Join(cmd.Args().Slice(), " "))
|
||||
}
|
||||
return c.Seek(false)
|
||||
},
|
||||
},
|
||||
@ -345,8 +443,9 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
if err := app.Run(c.Context, os.Args); err != nil {
|
||||
c.Log.Error("COMMANDER", "run error", err)
|
||||
os.Exit(1)
|
||||
s.Shutdown(fx.ExitCode(1))
|
||||
}
|
||||
s.Shutdown()
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ func (c *Commander) activateDevice() (spotify.ID, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = c.Client.TransferPlayback(c.Context, device.ID, true)
|
||||
err = c.Client().TransferPlayback(c.Context, device.ID, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
func (c *Commander) AlbumTracks(album spotify.ID, page int) (*spotify.SimpleTrackPage, error) {
|
||||
tracks, err := c.Client.
|
||||
tracks, err := c.Client().
|
||||
GetAlbumTracks(c.Context, album, spotify.Limit(50), spotify.Offset((page-1)*50), spotify.Market(spotify.CountryUSA))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -14,5 +14,5 @@ func (c *Commander) AlbumTracks(album spotify.ID, page int) (*spotify.SimpleTrac
|
||||
}
|
||||
|
||||
func (c *Commander) UserAlbums(page int) (*spotify.SavedAlbumPage, error) {
|
||||
return c.Client.CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
return c.Client().CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package commands
|
||||
import "github.com/zmb3/spotify/v2"
|
||||
|
||||
func (c *Commander) ArtistAlbums(artist spotify.ID, page int) (*spotify.SimpleAlbumPage, error) {
|
||||
albums, err := c.Client.
|
||||
albums, err := c.Client().
|
||||
GetArtistAlbums(c.Context, artist, []spotify.AlbumType{1, 2, 3, 4}, spotify.Market(spotify.CountryUSA), spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -3,12 +3,14 @@ package commands
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/zmb3/spotify/v2"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gspot/src/components/cache"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/config"
|
||||
"git.asdf.cafe/abs3nt/gspot/src/services"
|
||||
)
|
||||
|
||||
type CommanderResult struct {
|
||||
@ -21,33 +23,53 @@ type CommanderParams struct {
|
||||
fx.In
|
||||
|
||||
Context context.Context
|
||||
Client *spotify.Client
|
||||
Log *slog.Logger
|
||||
Cache *cache.Cache
|
||||
Config *config.Config
|
||||
}
|
||||
|
||||
type Commander struct {
|
||||
Context context.Context
|
||||
Client *spotify.Client
|
||||
User *spotify.PrivateUser
|
||||
Log *slog.Logger
|
||||
Cache *cache.Cache
|
||||
mu sync.RWMutex
|
||||
cl *spotify.Client
|
||||
conf *config.Config
|
||||
}
|
||||
|
||||
func NewCommander(p CommanderParams) CommanderResult {
|
||||
currentUser, err := p.Client.CurrentUser(p.Context)
|
||||
if err != nil {
|
||||
slog.Error("COMMANDER", "error getting current user", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
c := &Commander{
|
||||
Context: p.Context,
|
||||
Client: p.Client,
|
||||
User: currentUser,
|
||||
Log: p.Log,
|
||||
Cache: p.Cache,
|
||||
conf: p.Config,
|
||||
}
|
||||
return CommanderResult{
|
||||
Commander: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commander) Client() *spotify.Client {
|
||||
c.mu.Lock()
|
||||
if c.cl == nil {
|
||||
c.cl = c.connectClient()
|
||||
}
|
||||
c.mu.Unlock()
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.cl
|
||||
}
|
||||
|
||||
func (c *Commander) connectClient() *spotify.Client {
|
||||
client, err := services.GetClient(c.conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
currentUser, err := client.CurrentUser(c.Context)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.User = currentUser
|
||||
return client
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (c *Commander) ListDevices() error {
|
||||
devices, err := c.Client.PlayerDevices(c.Context)
|
||||
devices, err := c.Client().PlayerDevices(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -27,11 +27,11 @@ func PrintDevices(devices []spotify.PlayerDevice) error {
|
||||
}
|
||||
|
||||
func (c *Commander) SetDevice(device spotify.ID) error {
|
||||
err := c.Client.TransferPlayback(c.Context, device, true)
|
||||
err := c.Client().TransferPlayback(c.Context, device, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
devices, err := c.Client.PlayerDevices(c.Context)
|
||||
devices, err := c.Client().PlayerDevices(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ func (c *Commander) DownloadCover(path string) error {
|
||||
path = "cover.png"
|
||||
}
|
||||
destinationPath := filepath.Clean(path)
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) Like() error {
|
||||
playing, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
playing, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Client.AddTracksToLibrary(c.Context, playing.Item.ID)
|
||||
return c.Client().AddTracksToLibrary(c.Context, playing.Item.ID)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package commands
|
||||
import "fmt"
|
||||
|
||||
func (c *Commander) PrintLink() error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -12,7 +12,7 @@ func (c *Commander) PrintLink() error {
|
||||
}
|
||||
|
||||
func (c *Commander) PrintLinkContext() error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
func (c *Commander) Next(amt int, inqueue bool) error {
|
||||
if inqueue {
|
||||
for i := 0; i < amt; i++ {
|
||||
err := c.Client.Next(c.Context)
|
||||
err := c.Client().Next(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -17,15 +17,15 @@ func (c *Commander) Next(amt int, inqueue bool) error {
|
||||
return nil
|
||||
}
|
||||
if amt == 1 {
|
||||
err := c.Client.Next(c.Context)
|
||||
err := c.Client().Next(c.Context)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceId, err := c.activateDevice()
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.NextOpt(c.Context, &spotify.PlayOptions{
|
||||
DeviceID: &deviceId,
|
||||
err = c.Client().NextOpt(c.Context, &spotify.PlayOptions{
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -37,7 +37,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
|
||||
}
|
||||
// found := false
|
||||
// playingIndex := 0
|
||||
current, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
current, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -48,7 +48,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
|
||||
currentTrackIndex := 0
|
||||
page := 1
|
||||
for !found {
|
||||
playlist, err := c.Client.
|
||||
playlist, err := c.Client().
|
||||
GetPlaylistItems(
|
||||
c.Context,
|
||||
spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]),
|
||||
@ -68,7 +68,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
|
||||
page++
|
||||
}
|
||||
pos := currentTrackIndex + amt
|
||||
return c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
return c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: ¤t.PlaybackContext.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: &pos,
|
||||
@ -79,7 +79,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
|
||||
currentTrackIndex := 0
|
||||
page := 1
|
||||
for !found {
|
||||
playlist, err := c.Client.
|
||||
playlist, err := c.Client().
|
||||
GetAlbumTracks(
|
||||
c.Context,
|
||||
spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]),
|
||||
@ -99,7 +99,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
|
||||
page++
|
||||
}
|
||||
pos := currentTrackIndex + amt
|
||||
return c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
return c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: ¤t.PlaybackContext.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: &pos,
|
||||
@ -107,7 +107,7 @@ func (c *Commander) Next(amt int, inqueue bool) error {
|
||||
})
|
||||
default:
|
||||
for i := 0; i < amt; i++ {
|
||||
err := c.Client.Next(c.Context)
|
||||
err := c.Client().Next(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
func (c *Commander) NowPlaying(force bool) error {
|
||||
if force {
|
||||
current, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
current, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -19,7 +19,7 @@ func (c *Commander) NowPlaying(force bool) error {
|
||||
return err
|
||||
}
|
||||
song, err := c.Cache.GetOrDo("now_playing", func() (string, error) {
|
||||
current, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
current, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) Pause() error {
|
||||
return c.Client.Pause(c.Context)
|
||||
return c.Client().Pause(c.Context)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@ -8,14 +9,14 @@ import (
|
||||
)
|
||||
|
||||
func (c *Commander) Play() error {
|
||||
err := c.Client.Play(c.Context)
|
||||
err := c.Client().Play(c.Context)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
@ -29,6 +30,7 @@ func (c *Commander) Play() error {
|
||||
}
|
||||
|
||||
func (c *Commander) PlayLikedSongs(position int) error {
|
||||
c.Log.Debug("Playing liked songs")
|
||||
err := c.ClearRadio()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -37,28 +39,31 @@ func (c *Commander) PlayLikedSongs(position int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
songs, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(position))
|
||||
c.Log.Debug("got playlist", "id", playlist.ID)
|
||||
songs, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(position))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to_add := []spotify.ID{}
|
||||
toAdd := []spotify.ID{}
|
||||
for _, song := range songs.Tracks {
|
||||
to_add = append(to_add, song.ID)
|
||||
toAdd = append(toAdd, song.ID)
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, playlist.ID, to_add...)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, playlist.ID, toAdd...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
c.Log.Debug("added songs to playlist")
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &playlist.URI,
|
||||
})
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
c.Log.Debug("need to activate device")
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &playlist.URI,
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
@ -67,44 +72,50 @@ func (c *Commander) PlayLikedSongs(position int) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Log.Debug("starting loop")
|
||||
for page := 2; page <= 5; page++ {
|
||||
songs, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((50*(page-1))+position))
|
||||
c.Log.Debug("doing loop", "page", page)
|
||||
songs, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((50*(page-1))+position))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to_add := []spotify.ID{}
|
||||
toAdd := []spotify.ID{}
|
||||
for _, song := range songs.Tracks {
|
||||
to_add = append(to_add, song.ID)
|
||||
toAdd = append(toAdd, song.ID)
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, playlist.ID, to_add...)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, playlist.ID, toAdd...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.Log.Debug("done")
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commander) PlayUrl(urlString string) error {
|
||||
func (c *Commander) PlayURL(urlString string) error {
|
||||
url, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
track_id := strings.Split(url.Path, "/")[2]
|
||||
err = c.Client.QueueSong(c.Context, spotify.ID(track_id))
|
||||
splittUrl := strings.Split(url.Path, "/")
|
||||
if len(splittUrl) < 3 {
|
||||
return fmt.Errorf("invalid url")
|
||||
}
|
||||
trackID := splittUrl[2]
|
||||
err = c.Client().QueueSong(c.Context, spotify.ID(trackID))
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.QueueSongOpt(c.Context, spotify.ID(track_id), &spotify.PlayOptions{
|
||||
err = c.Client().QueueSongOpt(c.Context, spotify.ID(trackID), &spotify.PlayOptions{
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.NextOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().NextOpt(c.Context, &spotify.PlayOptions{
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
@ -115,7 +126,7 @@ func (c *Commander) PlayUrl(urlString string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = c.Client.Next(c.Context)
|
||||
err = c.Client().Next(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -123,7 +134,7 @@ func (c *Commander) PlayUrl(urlString string) error {
|
||||
}
|
||||
|
||||
func (c *Commander) PlaySongInPlaylist(context *spotify.URI, offset *int) error {
|
||||
e := c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
e := c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
|
||||
PlaybackContext: context,
|
||||
})
|
||||
@ -133,7 +144,7 @@ func (c *Commander) PlaySongInPlaylist(context *spotify.URI, offset *int) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
|
||||
PlaybackContext: context,
|
||||
DeviceID: &deviceID,
|
||||
@ -144,7 +155,7 @@ func (c *Commander) PlaySongInPlaylist(context *spotify.URI, offset *int) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
|
||||
PlaybackContext: context,
|
||||
DeviceID: &deviceID,
|
||||
@ -154,7 +165,7 @@ func (c *Commander) PlaySongInPlaylist(context *spotify.URI, offset *int) error
|
||||
}
|
||||
}
|
||||
}
|
||||
err = c.Client.Play(c.Context)
|
||||
err = c.Client().Play(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -3,15 +3,15 @@ package commands
|
||||
import "github.com/zmb3/spotify/v2"
|
||||
|
||||
func (c *Commander) Playlists(page int) (*spotify.SimplePlaylistPage, error) {
|
||||
return c.Client.CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
return c.Client().CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
||||
|
||||
func (c *Commander) PlaylistTracks(playlist spotify.ID, page int) (*spotify.PlaylistItemPage, error) {
|
||||
return c.Client.GetPlaylistItems(c.Context, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
return c.Client().GetPlaylistItems(c.Context, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
||||
|
||||
func (c *Commander) DeleteTracksFromPlaylist(tracks []spotify.ID, playlist spotify.ID) error {
|
||||
_, err := c.Client.RemoveTracksFromPlaylist(c.Context, playlist, tracks...)
|
||||
_, err := c.Client().RemoveTracksFromPlaylist(c.Context, playlist, tracks...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -19,5 +19,5 @@ func (c *Commander) DeleteTracksFromPlaylist(tracks []spotify.ID, playlist spoti
|
||||
}
|
||||
|
||||
func (c *Commander) TrackList(page int) (*spotify.SavedTrackPage, error) {
|
||||
return c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
return c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
||||
|
@ -5,15 +5,15 @@ import (
|
||||
)
|
||||
|
||||
func (c *Commander) Previous() error {
|
||||
err := c.Client.Previous(c.Context)
|
||||
err := c.Client().Previous(c.Context)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceId, err := c.activateDevice()
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PreviousOpt(c.Context, &spotify.PlayOptions{
|
||||
DeviceID: &deviceId,
|
||||
err = c.Client().PreviousOpt(c.Context, &spotify.PlayOptions{
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -3,14 +3,14 @@ package commands
|
||||
import "github.com/zmb3/spotify/v2"
|
||||
|
||||
func (c *Commander) QueueSong(id spotify.ID) error {
|
||||
err := c.Client.QueueSong(c.Context, id)
|
||||
err := c.Client().QueueSong(c.Context, id)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.QueueSongOpt(c.Context, id, &spotify.PlayOptions{
|
||||
err = c.Client().QueueSongOpt(c.Context, id, &spotify.PlayOptions{
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -6,55 +6,46 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gfx.cafe/util/go/frand"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func (c *Commander) Radio() error {
|
||||
current_song, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
currentSong, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if current_song.Item != nil {
|
||||
return c.RadioGivenSong(current_song.Item.SimpleTrack, current_song.Progress)
|
||||
if currentSong.Item != nil {
|
||||
return c.RadioGivenSong(currentSong.Item.SimpleTrack, currentSong.Progress)
|
||||
}
|
||||
_, err = c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tracks, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(10))
|
||||
tracks, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(10))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.RadioGivenSong(tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack, 0)
|
||||
return c.RadioGivenSong(tracks.Tracks[rand.Intn(len(tracks.Tracks))].SimpleTrack, 0)
|
||||
}
|
||||
|
||||
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,
|
||||
playlist.ID,
|
||||
spotify.Limit(50),
|
||||
spotify.Offset((randomPage-1)*50),
|
||||
spotify.Offset(0),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pageSongs := playlistPage.Items
|
||||
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
seedCount := 5
|
||||
if len(pageSongs) < seedCount {
|
||||
seedCount = len(pageSongs)
|
||||
@ -70,7 +61,7 @@ func (c *Commander) RadioFromPlaylist(playlist spotify.SimplePlaylist) error {
|
||||
}
|
||||
|
||||
func (c *Commander) RadioFromSavedTracks() error {
|
||||
savedSongs, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
savedSongs, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -80,14 +71,14 @@ func (c *Commander) RadioFromSavedTracks() error {
|
||||
pages := int(math.Ceil(float64(savedSongs.Total) / 50))
|
||||
randomPage := 1
|
||||
if pages > 1 {
|
||||
randomPage = frand.Intn(pages-1) + 1
|
||||
randomPage = rand.Intn(pages-1) + 1
|
||||
}
|
||||
trackPage, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(randomPage*50))
|
||||
trackPage, err := c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(randomPage*50))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pageSongs := trackPage.Tracks
|
||||
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
seedCount := 4
|
||||
seedIds := []spotify.ID{}
|
||||
for idx, song := range pageSongs {
|
||||
@ -104,7 +95,7 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
|
||||
seed := spotify.Seeds{
|
||||
Artists: []spotify.ID{artist.ID},
|
||||
}
|
||||
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -134,31 +125,32 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
|
||||
queue = append(queue, rec)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.Repeat(c.Context, "context")
|
||||
err = c.Client().Repeat(c.Context, "context")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
id := frand.Intn(len(recomendationIds)-2) + 1
|
||||
id := rand.Intn(len(recomendationIds)-2) + 1
|
||||
seed := spotify.Seeds{
|
||||
Tracks: []spotify.ID{recomendationIds[id]},
|
||||
}
|
||||
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
additionalRecs, err := c.Client().
|
||||
GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
additionalRecsIds := []spotify.ID{}
|
||||
for _, song := range additional_recs.Tracks {
|
||||
for _, song := range additionalRecs.Tracks {
|
||||
exists, err := c.SongExists(db, song.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -171,7 +163,7 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
|
||||
additionalRecsIds = append(additionalRecsIds, song.ID)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -179,12 +171,12 @@ func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
|
||||
func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos spotify.Numeric) error {
|
||||
start := time.Now().UnixMilli()
|
||||
seed := spotify.Seeds{
|
||||
Tracks: []spotify.ID{song.ID},
|
||||
}
|
||||
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
|
||||
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -218,15 +210,15 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
|
||||
queue = append(queue, rec)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delay := time.Now().UnixMilli() - start
|
||||
if pos != 0 {
|
||||
pos = pos + int(delay)
|
||||
pos = pos + spotify.Numeric(delay)
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
PositionMs: pos,
|
||||
})
|
||||
@ -236,7 +228,7 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
DeviceID: &deviceID,
|
||||
PositionMs: pos,
|
||||
@ -246,21 +238,22 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
err = c.Client.Repeat(c.Context, "context")
|
||||
err = c.Client().Repeat(c.Context, "context")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
id := frand.Intn(len(recomendationIds)-2) + 1
|
||||
id := rand.Intn(len(recomendationIds)-2) + 1
|
||||
seed := spotify.Seeds{
|
||||
Tracks: []spotify.ID{recomendationIds[id]},
|
||||
}
|
||||
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
additionalRecs, err := c.Client().
|
||||
GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
additionalRecsIds := []spotify.ID{}
|
||||
for _, song := range additional_recs.Tracks {
|
||||
for _, song := range additionalRecs.Tracks {
|
||||
exists, err := c.SongExists(db, song.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -273,7 +266,7 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
|
||||
additionalRecsIds = append(additionalRecsIds, song.ID)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -286,14 +279,14 @@ func (c *Commander) ClearRadio() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.UnfollowPlaylist(c.Context, radioPlaylist.ID)
|
||||
err = c.Client().UnfollowPlaylist(c.Context, radioPlaylist.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _ = db.Query("DROP TABLE IF EXISTS radio")
|
||||
configDir, _ := os.UserConfigDir()
|
||||
os.Remove(filepath.Join(configDir, "gspot/radio.json"))
|
||||
_ = c.Client.Pause(c.Context)
|
||||
_ = c.Client().Pause(c.Context)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -321,7 +314,7 @@ func (c *Commander) GetRadioPlaylist(name string) (*spotify.FullPlaylist, *sql.D
|
||||
func (c *Commander) CreateRadioPlaylist(name string) (*spotify.FullPlaylist, *sql.DB, error) {
|
||||
// private flag doesnt work
|
||||
configDir, _ := os.UserConfigDir()
|
||||
playlist, err := c.Client.
|
||||
playlist, err := c.Client().
|
||||
CreatePlaylistForUser(c.Context, c.User.ID, name+" - autoradio", "Automanaged radio playlist", false, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@ -344,9 +337,9 @@ func (c *Commander) CreateRadioPlaylist(name string) (*spotify.FullPlaylist, *sq
|
||||
}
|
||||
|
||||
func (c *Commander) SongExists(db *sql.DB, song spotify.ID) (bool, error) {
|
||||
song_id := string(song)
|
||||
songID := string(song)
|
||||
sqlStmt := `SELECT id FROM radio WHERE id = ?`
|
||||
err := db.QueryRow(sqlStmt, song_id).Scan(&song_id)
|
||||
err := db.QueryRow(sqlStmt, songID).Scan(&songID)
|
||||
if err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
return false, err
|
||||
@ -359,31 +352,32 @@ func (c *Commander) SongExists(db *sql.DB, song spotify.ID) (bool, error) {
|
||||
}
|
||||
|
||||
func (c *Commander) RefillRadio() error {
|
||||
status, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
status, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
paused := false
|
||||
if !status.Playing {
|
||||
return nil
|
||||
paused = true
|
||||
}
|
||||
to_remove := []spotify.ID{}
|
||||
toRemove := []spotify.ID{}
|
||||
radioPlaylist, db, err := c.GetRadioPlaylist("")
|
||||
if err != nil {
|
||||
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 {
|
||||
return fmt.Errorf("orig playlist items: %w", err)
|
||||
}
|
||||
|
||||
if status.PlaybackContext.URI != radioPlaylist.URI || paused {
|
||||
return c.RadioFromPlaylist(radioPlaylist.SimplePlaylist)
|
||||
}
|
||||
|
||||
page := 0
|
||||
for {
|
||||
tracks, err := c.Client.GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50))
|
||||
tracks, err := c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50))
|
||||
if err != nil {
|
||||
return fmt.Errorf("tracks: %w", err)
|
||||
}
|
||||
@ -394,15 +388,15 @@ func (c *Commander) RefillRadio() error {
|
||||
if track.Track.Track.ID == status.Item.ID {
|
||||
break
|
||||
}
|
||||
to_remove = append(to_remove, track.Track.Track.ID)
|
||||
toRemove = append(toRemove, track.Track.Track.ID)
|
||||
}
|
||||
page++
|
||||
}
|
||||
if len(to_remove) > 0 {
|
||||
if len(toRemove) > 0 {
|
||||
var trackGroups []spotify.ID
|
||||
for idx, item := range to_remove {
|
||||
if idx%100 == 0 {
|
||||
_, err = c.Client.RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
|
||||
for idx, item := range toRemove {
|
||||
if idx%100 == 0 && idx != 0 {
|
||||
_, err = c.Client().RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
|
||||
trackGroups = []spotify.ID{}
|
||||
}
|
||||
trackGroups = append(trackGroups, item)
|
||||
@ -410,14 +404,14 @@ func (c *Commander) RefillRadio() error {
|
||||
return fmt.Errorf("error clearing playlist: %w", err)
|
||||
}
|
||||
}
|
||||
_, err := c.Client.RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
|
||||
_, err := c.Client().RemoveTracksFromPlaylist(c.Context, radioPlaylist.ID, trackGroups...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
to_add := 500 - (playlistItems.Total - len(to_remove))
|
||||
playlistItems, err = c.Client.GetPlaylistItems(c.Context, radioPlaylist.ID)
|
||||
toAdd := 500 - (int(playlistItems.Total) - len(toRemove))
|
||||
playlistItems, err = c.Client().GetPlaylistItems(c.Context, radioPlaylist.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("playlist items: %w", err)
|
||||
}
|
||||
@ -425,15 +419,15 @@ func (c *Commander) RefillRadio() error {
|
||||
pages := int(math.Ceil(float64(total) / 50))
|
||||
randomPage := 1
|
||||
if pages > 1 {
|
||||
randomPage = frand.Intn(pages-1) + 1
|
||||
randomPage = rand.Intn(pages-1) + 1
|
||||
}
|
||||
playlistPage, err := c.Client.
|
||||
playlistPage, err := c.Client().
|
||||
GetPlaylistItems(c.Context, radioPlaylist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50))
|
||||
if err != nil {
|
||||
return fmt.Errorf("playlist page: %w", err)
|
||||
}
|
||||
pageSongs := playlistPage.Items
|
||||
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
seedCount := 5
|
||||
if len(pageSongs) < seedCount {
|
||||
seedCount = len(pageSongs)
|
||||
@ -448,7 +442,7 @@ func (c *Commander) RefillRadio() error {
|
||||
seed := spotify.Seeds{
|
||||
Tracks: seedIds,
|
||||
}
|
||||
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(95))
|
||||
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(95))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -464,7 +458,7 @@ func (c *Commander) RefillRadio() error {
|
||||
}
|
||||
queue := []spotify.ID{}
|
||||
for idx, rec := range recomendationIds {
|
||||
if idx > to_add {
|
||||
if idx > toAdd {
|
||||
break
|
||||
}
|
||||
_, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", rec.String()))
|
||||
@ -473,40 +467,41 @@ func (c *Commander) RefillRadio() error {
|
||||
}
|
||||
queue = append(queue, rec)
|
||||
}
|
||||
to_add -= len(queue)
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
toAdd -= len(queue)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add tracks: %w", err)
|
||||
}
|
||||
err = c.Client.Repeat(c.Context, "context")
|
||||
err = c.Client().Repeat(c.Context, "context")
|
||||
if err != nil {
|
||||
return fmt.Errorf("repeat: %w", err)
|
||||
}
|
||||
for to_add > 0 {
|
||||
id := frand.Intn(len(recomendationIds)-2) + 1
|
||||
for toAdd > 0 {
|
||||
id := rand.Intn(len(recomendationIds)-2) + 1
|
||||
seed := spotify.Seeds{
|
||||
Tracks: []spotify.ID{recomendationIds[id]},
|
||||
}
|
||||
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
additionalRecs, err := c.Client().
|
||||
GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get recs: %w", err)
|
||||
}
|
||||
additionalRecsIds := []spotify.ID{}
|
||||
for idx, song := range additional_recs.Tracks {
|
||||
for idx, song := range additionalRecs.Tracks {
|
||||
exists, err := c.SongExists(db, song.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check song existence: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
if idx > to_add {
|
||||
if idx > toAdd {
|
||||
break
|
||||
}
|
||||
additionalRecsIds = append(additionalRecsIds, song.ID)
|
||||
queue = append(queue, song.ID)
|
||||
}
|
||||
}
|
||||
to_add -= len(queue)
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
toAdd -= len(queue)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add tracks to playlist: %w", err)
|
||||
}
|
||||
@ -526,14 +521,14 @@ func (c *Commander) RadioFromAlbum(album spotify.SimpleAlbum) error {
|
||||
pages := int(math.Ceil(float64(total) / 50))
|
||||
randomPage := 1
|
||||
if pages > 1 {
|
||||
randomPage = frand.Intn(pages-1) + 1
|
||||
randomPage = rand.Intn(pages-1) + 1
|
||||
}
|
||||
albumTrackPage, err := c.AlbumTracks(album.ID, randomPage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pageSongs := albumTrackPage.Tracks
|
||||
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
rand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
seedCount := 5
|
||||
if len(pageSongs) < seedCount {
|
||||
seedCount = len(pageSongs)
|
||||
@ -548,11 +543,11 @@ func (c *Commander) RadioFromAlbum(album spotify.SimpleAlbum) error {
|
||||
return c.RadioGivenList(seedIds[:seedCount], album.Name)
|
||||
}
|
||||
|
||||
func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
|
||||
func (c *Commander) RadioGivenList(songs []spotify.ID, name string) error {
|
||||
seed := spotify.Seeds{
|
||||
Tracks: song_ids,
|
||||
Tracks: songs,
|
||||
}
|
||||
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
|
||||
recomendations, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -568,7 +563,7 @@ func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queue := []spotify.ID{song_ids[0]}
|
||||
queue := []spotify.ID{songs[0]}
|
||||
for _, rec := range recomendationIds {
|
||||
exists, err := c.SongExists(db, rec)
|
||||
if err != nil {
|
||||
@ -582,22 +577,22 @@ func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
|
||||
queue = append(queue, rec)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
})
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceId, err := c.activateDevice()
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
err = c.Client().PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
DeviceID: &deviceId,
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -605,16 +600,16 @@ func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
|
||||
}
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
id := frand.Intn(len(recomendationIds)-2) + 1
|
||||
id := rand.Intn(len(recomendationIds)-2) + 1
|
||||
seed := spotify.Seeds{
|
||||
Tracks: []spotify.ID{recomendationIds[id]},
|
||||
}
|
||||
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
additionalRecs, err := c.Client().GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
additionalRecsIds := []spotify.ID{}
|
||||
for _, song := range additional_recs.Tracks {
|
||||
for _, song := range additionalRecs.Tracks {
|
||||
exists, err := c.SongExists(db, song.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -627,7 +622,7 @@ func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
|
||||
additionalRecsIds = append(additionalRecsIds, song.ID)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
_, err = c.Client().AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) Repeat() error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -10,7 +10,7 @@ func (c *Commander) Repeat() error {
|
||||
newState = "context"
|
||||
}
|
||||
// spotifyd only supports binary value for repeat, context or off, change when/if spotifyd is better
|
||||
err = c.Client.Repeat(c.Context, newState)
|
||||
err = c.Client().Repeat(c.Context, newState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package commands
|
||||
import "github.com/zmb3/spotify/v2"
|
||||
|
||||
func (c *Commander) Search(search string, page int) (*spotify.SearchResult, error) {
|
||||
result, err := c.Client.
|
||||
result, err := c.Client().
|
||||
Search(c.Context, search, spotify.SearchTypeAlbum|spotify.SearchTypeArtist|spotify.SearchTypeTrack|spotify.SearchTypePlaylist, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -1,7 +1,7 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) Seek(fwd bool) error {
|
||||
current, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
current, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -9,7 +9,7 @@ func (c *Commander) Seek(fwd bool) error {
|
||||
if !fwd {
|
||||
newPos = current.Progress - 5000
|
||||
}
|
||||
err = c.Client.Seek(c.Context, newPos)
|
||||
err = c.Client().Seek(c.Context, int(newPos))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -17,7 +17,7 @@ func (c *Commander) Seek(fwd bool) error {
|
||||
}
|
||||
|
||||
func (c *Commander) SetPosition(pos int) error {
|
||||
err := c.Client.Seek(c.Context, pos)
|
||||
err := c.Client().Seek(c.Context, pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) Shuffle() error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.Shuffle(c.Context, !state.ShuffleState)
|
||||
err = c.Client().Shuffle(c.Context, !state.ShuffleState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import (
|
||||
|
||||
func (c *Commander) Status() error {
|
||||
state, err := c.Cache.GetOrDo("state", func() (string, error) {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) TogglePlay() error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
return c.Play()
|
||||
}
|
||||
if state.Playing {
|
||||
return c.Client.Pause(c.Context)
|
||||
return c.Pause()
|
||||
}
|
||||
return c.Client.Play(c.Context)
|
||||
return c.Play()
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) UnLike() error {
|
||||
playing, err := c.Client.PlayerCurrentlyPlaying(c.Context)
|
||||
playing, err := c.Client().PlayerCurrentlyPlaying(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Client.RemoveTracksFromLibrary(c.Context, playing.Item.ID)
|
||||
return c.Client().RemoveTracksFromLibrary(c.Context, playing.Item.ID)
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
package commands
|
||||
|
||||
func (c *Commander) ChangeVolume(amount int) error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newVolume := state.Device.Volume + amount
|
||||
newVolume := int(state.Device.Volume) + amount
|
||||
if newVolume > 100 {
|
||||
newVolume = 100
|
||||
}
|
||||
if newVolume < 0 {
|
||||
newVolume = 0
|
||||
}
|
||||
return c.Client.Volume(c.Context, newVolume)
|
||||
return c.Client().Volume(c.Context, newVolume)
|
||||
}
|
||||
|
||||
func (c *Commander) Mute() error {
|
||||
@ -24,7 +24,7 @@ func (c *Commander) UnMute() error {
|
||||
}
|
||||
|
||||
func (c *Commander) ToggleMute() error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func (c *Commander) PrintYoutubeLink() error {
|
||||
state, err := c.Client.PlayerState(c.Context)
|
||||
state, err := c.Client().PlayerState(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
67
src/components/logger/logger.go
Normal file
67
src/components/logger/logger.go
Normal file
@ -0,0 +1,67 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/lmittmann/tint"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gspot/src/config"
|
||||
)
|
||||
|
||||
type LoggerResult struct {
|
||||
fx.Out
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
type LoggerParams struct {
|
||||
fx.In
|
||||
|
||||
Config *config.Config
|
||||
}
|
||||
|
||||
func NewLogger(p LoggerParams) LoggerResult {
|
||||
lvl := slog.LevelInfo
|
||||
configLevel := strings.ToUpper(p.Config.LogLevel)
|
||||
switch configLevel {
|
||||
case "INFO":
|
||||
lvl = slog.LevelInfo
|
||||
case "WARN":
|
||||
lvl = slog.LevelWarn
|
||||
case "ERROR":
|
||||
lvl = slog.LevelError
|
||||
case "DEBUG":
|
||||
lvl = slog.LevelDebug
|
||||
}
|
||||
if strings.ToUpper(p.Config.LogOutput) == "FILE" {
|
||||
fp := ""
|
||||
p, err := os.UserConfigDir()
|
||||
if err != nil {
|
||||
p, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
fp = filepath.Join(p, ".config", "gspot", "gspot.log")
|
||||
} else {
|
||||
fp = filepath.Join(p, "gspot", "gspot.log")
|
||||
}
|
||||
f, err := os.Create(fp)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
return LoggerResult{
|
||||
Logger: slog.New(slog.NewJSONHandler(f, &slog.HandlerOptions{
|
||||
Level: lvl.Level(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
return LoggerResult{
|
||||
Logger: slog.New(tint.NewHandler(os.Stdout, &tint.Options{
|
||||
Level: lvl.Level(),
|
||||
TimeFormat: "[15:04:05.000]",
|
||||
})),
|
||||
}
|
||||
}
|
@ -32,10 +32,10 @@ func (m *mainModel) LoadMoreItems() {
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
mainUpdates <- m
|
||||
return
|
||||
case "artists":
|
||||
artists, err := m.commands.Client.CurrentUsersFollowedArtists(
|
||||
artists, err := m.commands.Client().CurrentUsersFollowedArtists(
|
||||
m.commands.Context,
|
||||
spotify.Limit(50),
|
||||
spotify.Offset((page)*50),
|
||||
@ -60,7 +60,7 @@ func (m *mainModel) LoadMoreItems() {
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
mainUpdates <- m
|
||||
return
|
||||
case "album":
|
||||
tracks, err := m.commands.AlbumTracks(m.album.ID, (page + 1))
|
||||
@ -80,7 +80,7 @@ func (m *mainModel) LoadMoreItems() {
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
mainUpdates <- m
|
||||
return
|
||||
case "albums":
|
||||
albums, err := m.commands.UserAlbums(page + 1)
|
||||
@ -99,7 +99,7 @@ func (m *mainModel) LoadMoreItems() {
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
mainUpdates <- m
|
||||
return
|
||||
case "main":
|
||||
playlists, err := m.commands.Playlists(page + 1)
|
||||
@ -117,7 +117,7 @@ func (m *mainModel) LoadMoreItems() {
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
mainUpdates <- m
|
||||
return
|
||||
case "playlist":
|
||||
playlistItems, err := m.commands.PlaylistTracks(m.playlist.ID, (page + 1))
|
||||
@ -139,7 +139,7 @@ func (m *mainModel) LoadMoreItems() {
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
mainUpdates <- m
|
||||
return
|
||||
case "tracks":
|
||||
tracks, err := m.commands.TrackList(page + 1)
|
||||
@ -159,7 +159,7 @@ func (m *mainModel) LoadMoreItems() {
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
mainUpdates <- m
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ var (
|
||||
DocStyle = lipgloss.NewStyle().Margin(0, 2).Border(lipgloss.DoubleBorder(), true, true, true, true)
|
||||
currentlyPlaying *spotify.CurrentlyPlaying
|
||||
playbackContext string
|
||||
main_updates chan *mainModel
|
||||
mainUpdates chan *mainModel
|
||||
page = 1
|
||||
loading = false
|
||||
showingMessage = false
|
||||
@ -190,32 +190,32 @@ func (m *mainModel) GoBack() (tea.Cmd, error) {
|
||||
return tea.Quit, nil
|
||||
case Albums, Artists, Tracks, Playlist, Devices, Search, Queue:
|
||||
m.mode = Main
|
||||
new_items, err := MainView(m.commands)
|
||||
newItems, err := MainView(m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
case Album:
|
||||
m.mode = Albums
|
||||
new_items, err := AlbumsView(m.commands)
|
||||
newItems, err := AlbumsView(m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
case Artist:
|
||||
m.mode = Artists
|
||||
new_items, err := ArtistsView(m.commands)
|
||||
newItems, err := ArtistsView(m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
case ArtistAlbum:
|
||||
m.mode = Artist
|
||||
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
newItems, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
case SearchArtists, SearchTracks, SearchAlbums, SearchPlaylists:
|
||||
m.mode = Search
|
||||
items, result, err := SearchView(m.commands, m.search)
|
||||
@ -226,39 +226,39 @@ func (m *mainModel) GoBack() (tea.Cmd, error) {
|
||||
m.list.SetItems(items)
|
||||
case SearchArtist:
|
||||
m.mode = SearchArtists
|
||||
new_items, err := SearchArtistsView(m.commands, m.searchResults.Artists)
|
||||
newItems, err := SearchArtistsView(m.commands, m.searchResults.Artists)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
case SearchArtistAlbum:
|
||||
m.mode = SearchArtist
|
||||
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
newItems, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
case SearchAlbum:
|
||||
m.mode = SearchAlbums
|
||||
new_items, err := SearchAlbumsView(m.commands, m.searchResults.Albums)
|
||||
newItems, err := SearchAlbumsView(m.commands, m.searchResults.Albums)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
case SearchPlaylist:
|
||||
m.mode = SearchPlaylists
|
||||
new_items, err := SearchPlaylistsView(m.commands, m.searchResults.Playlists)
|
||||
newItems, err := SearchPlaylistsView(m.commands, m.searchResults.Playlists)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
default:
|
||||
page = 0
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type SpotifyUrl struct {
|
||||
type SpotifyURL struct {
|
||||
ExternalURLs map[string]string
|
||||
}
|
||||
|
||||
@ -267,34 +267,34 @@ func (m *mainModel) CopyToClipboard() error {
|
||||
switch converted := item.(type) {
|
||||
case spotify.SimplePlaylist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullPlaylist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleAlbum:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullAlbum:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleArtist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullArtist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.PlaylistTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.Track.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.Track.ExternalURLs["spotify"])
|
||||
case spotify.SavedTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.FullTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
return clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -343,11 +343,11 @@ func (m *mainModel) QueueItem() error {
|
||||
}()
|
||||
if m.mode == Queue {
|
||||
go func() {
|
||||
new_items, err := QueueView(m.commands)
|
||||
newItems, err := QueueView(m.commands)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
@ -364,11 +364,11 @@ func (m *mainModel) DeleteTrackFromPlaylist() error {
|
||||
if err != nil {
|
||||
m.SendMessage(err.Error(), 5*time.Second)
|
||||
}
|
||||
new_items, err := PlaylistView(m.commands, m.playlist)
|
||||
newItems, err := PlaylistView(m.commands, m.playlist)
|
||||
if err != nil {
|
||||
m.SendMessage(err.Error(), 5*time.Second)
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
@ -382,11 +382,11 @@ func (m *mainModel) SelectItem() error {
|
||||
if err != nil {
|
||||
m.SendMessage(err.Error(), 5*time.Second)
|
||||
}
|
||||
new_items, err := QueueView(m.commands)
|
||||
newItems, err := QueueView(m.commands)
|
||||
if err != nil {
|
||||
m.SendMessage(err.Error(), 5*time.Second)
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
}()
|
||||
case Search:
|
||||
@ -394,150 +394,150 @@ func (m *mainModel) SelectItem() error {
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case *spotify.FullArtistPage:
|
||||
m.mode = SearchArtists
|
||||
new_items, err := SearchArtistsView(m.commands, item)
|
||||
newItems, err := SearchArtistsView(m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SimpleAlbumPage:
|
||||
m.mode = SearchAlbums
|
||||
new_items, err := SearchAlbumsView(m.commands, item)
|
||||
newItems, err := SearchAlbumsView(m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SimplePlaylistPage:
|
||||
m.mode = SearchPlaylists
|
||||
new_items, err := SearchPlaylistsView(m.commands, item)
|
||||
newItems, err := SearchPlaylistsView(m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.FullTrackPage:
|
||||
m.mode = SearchTracks
|
||||
new_items, err := SearchTracksView(item)
|
||||
newItems, err := SearchTracksView(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
}
|
||||
case SearchArtists:
|
||||
page = 1
|
||||
m.mode = SearchArtist
|
||||
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
|
||||
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
newItems, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case SearchArtist:
|
||||
page = 1
|
||||
m.mode = SearchArtistAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
newItems, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case SearchAlbums:
|
||||
page = 1
|
||||
m.mode = SearchAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
newItems, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case SearchPlaylists:
|
||||
page = 1
|
||||
m.mode = SearchPlaylist
|
||||
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
|
||||
m.playlist = playlist
|
||||
new_items, err := PlaylistView(m.commands, playlist)
|
||||
newItems, err := PlaylistView(m.commands, playlist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case Main:
|
||||
page = 1
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case spotify.Queue:
|
||||
m.mode = Queue
|
||||
new_items, err := QueueView(m.commands)
|
||||
newItems, err := QueueView(m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.FullArtistCursorPage:
|
||||
m.mode = Artists
|
||||
new_items, err := ArtistsView(m.commands)
|
||||
newItems, err := ArtistsView(m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SavedAlbumPage:
|
||||
m.mode = Albums
|
||||
new_items, err := AlbumsView(m.commands)
|
||||
newItems, err := AlbumsView(m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case spotify.SimplePlaylist:
|
||||
m.mode = Playlist
|
||||
m.playlist = item
|
||||
new_items, err := PlaylistView(m.commands, item)
|
||||
newItems, err := PlaylistView(m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SavedTrackPage:
|
||||
m.mode = Tracks
|
||||
new_items, err := SavedTracksView(m.commands)
|
||||
newItems, err := SavedTracksView(m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
}
|
||||
case Albums:
|
||||
page = 1
|
||||
m.mode = Album
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
newItems, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case Artist:
|
||||
m.mode = ArtistAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
newItems, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case Artists:
|
||||
m.mode = Artist
|
||||
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
|
||||
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
newItems, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
case Album, ArtistAlbum, SearchArtistAlbum, SearchAlbum:
|
||||
pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.TotalPages)
|
||||
@ -579,17 +579,17 @@ func (m *mainModel) SelectItem() error {
|
||||
}
|
||||
}()
|
||||
m.mode = "main"
|
||||
new_items, err := MainView(m.commands)
|
||||
newItems, err := MainView(m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mainModel) Init() tea.Cmd {
|
||||
main_updates = make(chan *mainModel)
|
||||
mainUpdates = make(chan *mainModel)
|
||||
return Tick()
|
||||
}
|
||||
|
||||
@ -602,10 +602,13 @@ func Tick() tea.Cmd {
|
||||
}
|
||||
|
||||
func (m *mainModel) TickPlayback() {
|
||||
playing, _ := m.commands.Client.PlayerCurrentlyPlaying(m.commands.Context)
|
||||
playing, _ := m.commands.Client().PlayerCurrentlyPlaying(m.commands.Context)
|
||||
if playing != nil && playing.Playing && playing.Item != nil {
|
||||
if currentlyPlaying == nil || currentlyPlaying.Item == nil ||
|
||||
currentlyPlaying.Item.ID != playing.Item.ID {
|
||||
playbackContext, _ = m.getContext(playing)
|
||||
}
|
||||
currentlyPlaying = playing
|
||||
playbackContext, _ = m.getContext(playing)
|
||||
}
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
quit := make(chan struct{})
|
||||
@ -613,10 +616,14 @@ func (m *mainModel) TickPlayback() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
playing, _ := m.commands.Client.PlayerCurrentlyPlaying(m.commands.Context)
|
||||
m.commands.Log.Debug("TICKING PLAYBACK")
|
||||
playing, _ := m.commands.Client().PlayerCurrentlyPlaying(m.commands.Context)
|
||||
if playing != nil && playing.Playing && playing.Item != nil {
|
||||
if currentlyPlaying == nil || currentlyPlaying.Item == nil ||
|
||||
currentlyPlaying.Item.ID != playing.Item.ID {
|
||||
playbackContext, _ = m.getContext(playing)
|
||||
}
|
||||
currentlyPlaying = playing
|
||||
playbackContext, _ = m.getContext(playing)
|
||||
}
|
||||
case <-quit:
|
||||
ticker.Stop()
|
||||
@ -658,26 +665,29 @@ func (m *mainModel) Typing(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
|
||||
func (m *mainModel) getContext(playing *spotify.CurrentlyPlaying) (string, error) {
|
||||
context := playing.PlaybackContext
|
||||
uri_split := strings.Split(string(context.URI), ":")
|
||||
if len(uri_split) < 3 {
|
||||
uriSplit := strings.Split(string(context.URI), ":")
|
||||
if len(uriSplit) < 3 {
|
||||
return "", fmt.Errorf("NO URI")
|
||||
}
|
||||
id := strings.Split(string(context.URI), ":")[2]
|
||||
switch context.Type {
|
||||
case "album":
|
||||
album, err := m.commands.Client.GetAlbum(m.commands.Context, spotify.ID(id))
|
||||
m.commands.Log.Debug("ALBUM CONTEXT")
|
||||
album, err := m.commands.Client().GetAlbum(m.commands.Context, spotify.ID(id))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return album.Name, nil
|
||||
case "playlist":
|
||||
playlist, err := m.commands.Client.GetPlaylist(m.commands.Context, spotify.ID(id))
|
||||
m.commands.Log.Debug("PLAYLIST CONTEXT")
|
||||
playlist, err := m.commands.Client().GetPlaylist(m.commands.Context, spotify.ID(id))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return playlist.Name, nil
|
||||
case "artist":
|
||||
artist, err := m.commands.Client.GetArtist(m.commands.Context, spotify.ID(id))
|
||||
m.commands.Log.Debug("ARTIST CONTEXT")
|
||||
artist, err := m.commands.Client().GetArtist(m.commands.Context, spotify.ID(id))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -689,7 +699,7 @@ func (m *mainModel) getContext(playing *spotify.CurrentlyPlaying) (string, error
|
||||
func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Update list items from LoadMore
|
||||
select {
|
||||
case update := <-main_updates:
|
||||
case update := <-mainUpdates:
|
||||
m.list.SetItems(update.list.Items())
|
||||
default:
|
||||
}
|
||||
@ -711,11 +721,11 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.mode == Queue && len(m.list.Items()) != 0 {
|
||||
if m.list.Items()[0].(mainItem).SpotifyItem.(spotify.FullTrack).Name != playing.Item.Name {
|
||||
go func() {
|
||||
new_items, err := QueueView(m.commands)
|
||||
newItems, err := QueueView(m.commands)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
}()
|
||||
}
|
||||
}
|
||||
@ -746,7 +756,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if msg.String() == "c" {
|
||||
err := m.CopyToClipboard()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
go m.SendMessage(err.Error(), 5*time.Second)
|
||||
}
|
||||
}
|
||||
if msg.String() == ">" {
|
||||
@ -796,11 +806,11 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// enter device selection
|
||||
if msg.String() == "d" {
|
||||
m.mode = Devices
|
||||
new_items, err := DeviceView(m.commands)
|
||||
newItems, err := DeviceView(m.commands)
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.SetItems(newItems)
|
||||
m.list.ResetSelected()
|
||||
}
|
||||
// go back
|
||||
@ -837,18 +847,19 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
// handle mouse
|
||||
case tea.MouseMsg:
|
||||
if msg.Type == 5 {
|
||||
case tea.MouseButton:
|
||||
if msg == 5 {
|
||||
m.list.CursorUp()
|
||||
}
|
||||
if msg.Type == 6 {
|
||||
m.list.CursorDown()
|
||||
if msg == 6 {
|
||||
m.list.CursorUp()
|
||||
}
|
||||
|
||||
// window size -1 to handle search bar
|
||||
case tea.WindowSizeMsg:
|
||||
h, v := DocStyle.GetFrameSize()
|
||||
m.list.SetSize(msg.Width-h, msg.Height-v-1)
|
||||
DocStyle.Width(msg.Width - h)
|
||||
}
|
||||
|
||||
// return
|
||||
|
@ -16,7 +16,7 @@ const regex = `<.*?>`
|
||||
|
||||
func DeviceView(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
devices, err := commands.Client.PlayerDevices(commands.Context)
|
||||
devices, err := commands.Client().PlayerDevices(commands.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -32,7 +32,7 @@ func DeviceView(commands *commands.Commander) ([]list.Item, error) {
|
||||
|
||||
func QueueView(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
tracks, err := commands.Client.GetQueue(commands.Context)
|
||||
tracks, err := commands.Client().GetQueue(commands.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -63,7 +63,7 @@ func QueueView(commands *commands.Commander) ([]list.Item, error) {
|
||||
|
||||
func PlaylistView(commands *commands.Commander, playlist spotify.SimplePlaylist) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
playlistItems, err := commands.Client.GetPlaylistItems(
|
||||
playlistItems, err := commands.Client().GetPlaylistItems(
|
||||
commands.Context,
|
||||
playlist.ID,
|
||||
spotify.Limit(50),
|
||||
@ -89,7 +89,7 @@ func PlaylistView(commands *commands.Commander, playlist spotify.SimplePlaylist)
|
||||
|
||||
func ArtistsView(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
artists, err := commands.Client.CurrentUsersFollowedArtists(commands.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
artists, err := commands.Client().CurrentUsersFollowedArtists(commands.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -145,7 +145,7 @@ func SearchView(commands *commands.Commander, search string) ([]list.Item, *Sear
|
||||
|
||||
func AlbumsView(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
albums, err := commands.Client.CurrentUsersAlbums(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
albums, err := commands.Client().CurrentUsersAlbums(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -171,7 +171,7 @@ func SearchPlaylistsView(commands *commands.Commander, playlists *spotify.Simple
|
||||
for _, playlist := range playlists.Playlists {
|
||||
items = append(items, mainItem{
|
||||
Name: playlist.Name,
|
||||
Desc: stripHtmlRegex(playlist.Description),
|
||||
Desc: stripHTMLRegex(playlist.Description),
|
||||
SpotifyItem: playlist,
|
||||
})
|
||||
}
|
||||
@ -249,7 +249,7 @@ func SearchTracksView(tracks *spotify.FullTrackPage) ([]list.Item, error) {
|
||||
|
||||
func SavedTracksView(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
tracks, err := commands.Client.CurrentUsersTracks(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
tracks, err := commands.Client().CurrentUsersTracks(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -267,29 +267,30 @@ func SavedTracksView(commands *commands.Commander) ([]list.Item, error) {
|
||||
}
|
||||
|
||||
func MainView(c *commands.Commander) ([]list.Item, error) {
|
||||
c.Log.Debug("SWITCHING TO MAIN VIEW")
|
||||
wg := errgroup.Group{}
|
||||
var saved_items *spotify.SavedTrackPage
|
||||
var savedItems *spotify.SavedTrackPage
|
||||
var playlists *spotify.SimplePlaylistPage
|
||||
var artists *spotify.FullArtistCursorPage
|
||||
var albums *spotify.SavedAlbumPage
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
saved_items, err = c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
savedItems, err = c.Client().CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
return
|
||||
})
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
playlists, err = c.Client.CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
playlists, err = c.Client().CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
return
|
||||
})
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
artists, err = c.Client.CurrentUsersFollowedArtists(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
artists, err = c.Client().CurrentUsersFollowedArtists(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
return
|
||||
})
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
albums, err = c.Client.CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
albums, err = c.Client().CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
return
|
||||
})
|
||||
|
||||
@ -299,11 +300,11 @@ func MainView(c *commands.Commander) ([]list.Item, error) {
|
||||
}
|
||||
|
||||
items := []list.Item{}
|
||||
if saved_items != nil && saved_items.Total != 0 {
|
||||
if savedItems != nil && savedItems.Total != 0 {
|
||||
items = append(items, mainItem{
|
||||
Name: "Saved Tracks",
|
||||
Desc: fmt.Sprintf("%d saved songs", saved_items.Total),
|
||||
SpotifyItem: saved_items,
|
||||
Desc: fmt.Sprintf("%d saved songs", savedItems.Total),
|
||||
SpotifyItem: savedItems,
|
||||
})
|
||||
}
|
||||
if albums != nil && albums.Total != 0 {
|
||||
@ -329,7 +330,7 @@ func MainView(c *commands.Commander) ([]list.Item, error) {
|
||||
for _, playlist := range playlists.Playlists {
|
||||
items = append(items, mainItem{
|
||||
Name: playlist.Name,
|
||||
Desc: stripHtmlRegex(playlist.Description),
|
||||
Desc: stripHTMLRegex(playlist.Description),
|
||||
SpotifyItem: playlist,
|
||||
})
|
||||
}
|
||||
@ -337,7 +338,7 @@ func MainView(c *commands.Commander) ([]list.Item, error) {
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func stripHtmlRegex(s string) string {
|
||||
func stripHTMLRegex(s string) string {
|
||||
r := regexp.MustCompile(regex)
|
||||
return r.ReplaceAllString(s, "")
|
||||
}
|
||||
|
102
src/components/tuitview/tuitview.go
Normal file
102
src/components/tuitview/tuitview.go
Normal file
@ -0,0 +1,102 @@
|
||||
package tuitview
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gspot/src/components/commands"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
tracksLoading = atomic.Bool{}
|
||||
playlistsLoading = atomic.Bool{}
|
||||
tracksPage = 1
|
||||
playlistsPage = 1
|
||||
)
|
||||
|
||||
func TuitView(cmd *commands.Commander) error {
|
||||
tracksLoading.Store(false)
|
||||
playlistsLoading.Store(false)
|
||||
playlistsList := tview.NewList().ShowSecondaryText(false)
|
||||
playlistsList.SetBorder(true).SetTitle("Playlists")
|
||||
savedTracksList := tview.NewList().ShowSecondaryText(false)
|
||||
savedTracksList.SetWrapAround(false)
|
||||
savedTracksList.SetBorder(true).SetTitle("Tracks")
|
||||
savedTracksList.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
|
||||
go cmd.PlayLikedSongs(index)
|
||||
})
|
||||
flex := tview.NewFlex().AddItem(playlistsList, 0, 1, false).AddItem(savedTracksList, 0, 2, true)
|
||||
playlists, err := cmd.Playlists(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, playlist := range playlists.Playlists {
|
||||
playlistsList.AddItem(playlist.Name, "", 0, func() {
|
||||
playlistTracksList := tview.NewList().ShowSecondaryText(false)
|
||||
playlistTracksList.SetBorder(true).SetTitle(playlist.Name)
|
||||
playlistTracksList.SetSelectedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
|
||||
go cmd.PlaySongInPlaylist((*spotify.URI)(&secondaryText), &index)
|
||||
})
|
||||
tracks, err := cmd.PlaylistTracks(playlist.ID, 1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, track := range tracks.Items {
|
||||
playlistTracksList.AddItem(track.Track.Track.Name+" - "+track.Track.Track.Artists[0].Name, string(playlist.URI), 0, nil)
|
||||
}
|
||||
flex.Clear()
|
||||
flex.AddItem(playlistsList, 0, 1, false)
|
||||
flex.AddItem(playlistTracksList, 0, 2, false)
|
||||
})
|
||||
}
|
||||
tracks, err := cmd.TrackList(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, track := range tracks.Tracks {
|
||||
savedTracksList.AddItem(track.Name+" - "+track.Artists[0].Name, "", 0, nil)
|
||||
}
|
||||
playlistsList.SetChangedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
|
||||
if playlistsList.GetItemCount()%50 != 0 {
|
||||
return
|
||||
}
|
||||
if playlistsList.GetItemCount()-index < 40 {
|
||||
go func() {
|
||||
if playlistsLoading.Load() {
|
||||
return
|
||||
}
|
||||
playlistsLoading.Store(true)
|
||||
defer playlistsLoading.Store(false)
|
||||
playlistsPage++
|
||||
newPlaylists, _ := cmd.Playlists(playlistsPage)
|
||||
for _, playlist := range newPlaylists.Playlists {
|
||||
savedTracksList.AddItem(playlist.Name, "", 0, nil)
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
savedTracksList.SetChangedFunc(func(index int, mainText string, secondaryText string, shortcut rune) {
|
||||
if savedTracksList.GetItemCount()%50 != 0 {
|
||||
return
|
||||
}
|
||||
if savedTracksList.GetItemCount()-index < 40 {
|
||||
go func() {
|
||||
if tracksLoading.Load() {
|
||||
return
|
||||
}
|
||||
tracksLoading.Store(true)
|
||||
defer tracksLoading.Store(false)
|
||||
tracksPage++
|
||||
tracks, _ := cmd.TrackList(tracksPage)
|
||||
for _, track := range tracks.Tracks {
|
||||
savedTracksList.AddItem(track.Name+" - "+track.Artists[0].Name, "", 0, nil)
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
if err := tview.NewApplication().EnableMouse(true).SetRoot(flex, true).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
ClientId string `yaml:"client_id"`
|
||||
ClientID string `yaml:"client_id"`
|
||||
ClientSecret string `yaml:"client_secret"`
|
||||
ClientSecretCmd string `yaml:"client_secret_cmd"`
|
||||
Port string `yaml:"port"`
|
||||
LogLevel string `yaml:"log_level" default:"info"`
|
||||
LogOutput string `yaml:"log_output" default:"stdout"`
|
||||
}
|
||||
|
@ -13,18 +13,12 @@ import (
|
||||
|
||||
"github.com/zmb3/spotify/v2"
|
||||
spotifyauth "github.com/zmb3/spotify/v2/auth"
|
||||
"go.uber.org/fx"
|
||||
"golang.org/x/exp/slog"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gspot/src/config"
|
||||
)
|
||||
|
||||
type SpotifyClientResult struct {
|
||||
fx.Out
|
||||
Client *spotify.Client
|
||||
}
|
||||
|
||||
var (
|
||||
auth *spotifyauth.Authenticator
|
||||
ch = make(chan *spotify.Client)
|
||||
@ -38,25 +32,25 @@ func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error)
|
||||
return fn(req)
|
||||
}
|
||||
|
||||
func NewSpotifyClient(conf *config.Config) (c SpotifyClientResult, err error) {
|
||||
if conf.ClientId == "" || (conf.ClientSecret == "" && conf.ClientSecretCmd == "") || conf.Port == "" {
|
||||
return SpotifyClientResult{}, fmt.Errorf("INVALID CONFIG")
|
||||
func GetClient(conf *config.Config) (c *spotify.Client, err error) {
|
||||
if conf.ClientID == "" || (conf.ClientSecret == "" && conf.ClientSecretCmd == "") || conf.Port == "" {
|
||||
return nil, fmt.Errorf("INVALID CONFIG")
|
||||
}
|
||||
if conf.ClientSecretCmd != "" {
|
||||
args := strings.Fields(conf.ClientSecretCmd)
|
||||
cmd := args[0]
|
||||
secret_command := exec.Command(cmd)
|
||||
secretCommand := exec.Command(cmd)
|
||||
if len(args) > 1 {
|
||||
secret_command.Args = args
|
||||
secretCommand.Args = args
|
||||
}
|
||||
secret, err := secret_command.Output()
|
||||
secret, err := secretCommand.Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
conf.ClientSecret = strings.TrimSpace(string(secret))
|
||||
}
|
||||
auth = spotifyauth.New(
|
||||
spotifyauth.WithClientID(conf.ClientId),
|
||||
spotifyauth.WithClientID(conf.ClientID),
|
||||
spotifyauth.WithClientSecret(conf.ClientSecret),
|
||||
spotifyauth.WithRedirectURL(fmt.Sprintf("http://localhost:%s/callback", conf.Port)),
|
||||
spotifyauth.WithScopes(
|
||||
@ -83,13 +77,13 @@ func NewSpotifyClient(conf *config.Config) (c SpotifyClientResult, err error) {
|
||||
authFilePath := filepath.Join(configDir, "gspot/auth.json")
|
||||
authFile, err := os.Open(authFilePath)
|
||||
if err != nil {
|
||||
return SpotifyClientResult{}, err
|
||||
return nil, err
|
||||
}
|
||||
defer authFile.Close()
|
||||
tok := &oauth2.Token{}
|
||||
err = json.NewDecoder(authFile).Decode(tok)
|
||||
if err != nil {
|
||||
return SpotifyClientResult{}, err
|
||||
return nil, err
|
||||
}
|
||||
authCtx := context.WithValue(context.Background(), oauth2.HTTPClient, &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
@ -98,20 +92,20 @@ func NewSpotifyClient(conf *config.Config) (c SpotifyClientResult, err error) {
|
||||
}),
|
||||
})
|
||||
authClient := auth.Client(authCtx, tok)
|
||||
client := spotify.New(authClient)
|
||||
new_token, err := client.Token()
|
||||
client := spotify.New(authClient, spotify.WithRetry(true))
|
||||
newToken, err := client.Token()
|
||||
if err != nil {
|
||||
return SpotifyClientResult{}, err
|
||||
return nil, err
|
||||
}
|
||||
out, err := json.MarshalIndent(new_token, "", " ")
|
||||
out, err := json.MarshalIndent(newToken, "", " ")
|
||||
if err != nil {
|
||||
return SpotifyClientResult{}, err
|
||||
return nil, err
|
||||
}
|
||||
err = os.WriteFile(authFilePath, out, 0o600)
|
||||
if err != nil {
|
||||
return SpotifyClientResult{}, fmt.Errorf("failed to save auth")
|
||||
return nil, fmt.Errorf("failed to save auth")
|
||||
}
|
||||
return SpotifyClientResult{Client: client}, nil
|
||||
return client, nil
|
||||
}
|
||||
// first start an HTTP server
|
||||
http.HandleFunc("/callback", completeAuth)
|
||||
@ -136,10 +130,10 @@ func NewSpotifyClient(conf *config.Config) (c SpotifyClientResult, err error) {
|
||||
// use the client to make calls that require authorization
|
||||
user, err := client.CurrentUser(context.Background())
|
||||
if err != nil {
|
||||
return SpotifyClientResult{}, err
|
||||
return nil, err
|
||||
}
|
||||
slog.Info("AUTH", "You are logged in as:", user.ID)
|
||||
return SpotifyClientResult{Client: client}, nil
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func completeAuth(w http.ResponseWriter, r *http.Request) {
|
||||
@ -162,7 +156,7 @@ func completeAuth(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Error("AUTHENTICATOR", "failed to save auth", err)
|
||||
}
|
||||
// use the token to get an authenticated client
|
||||
client := spotify.New(auth.Client(r.Context(), tok))
|
||||
client := spotify.New(auth.Client(r.Context(), tok), spotify.WithRetry(true))
|
||||
fmt.Fprintf(w, "Login Completed!")
|
||||
ch <- client
|
||||
}
|
||||
|
18
src/services/config.go
Normal file
18
src/services/config.go
Normal file
@ -0,0 +1,18 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"git.asdf.cafe/abs3nt/gunner"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gspot/src/config"
|
||||
)
|
||||
|
||||
var Config = fx.Options(
|
||||
fx.Provide(
|
||||
func() *config.Config {
|
||||
c := &config.Config{}
|
||||
gunner.LoadApp(c, "gspot")
|
||||
return c
|
||||
},
|
||||
),
|
||||
)
|
Loading…
Reference in New Issue
Block a user