Compare commits

..

99 Commits

Author SHA1 Message Date
bbd0b48f0b Merge pull request 'Update module modernc.org/sqlite to v1.30.2' (#38) from renovate/modernc.org-sqlite-1.x into main
Reviewed-on: #38
2024-07-14 19:35:32 +00:00
d91bdea878 Merge pull request 'Update module github.com/charmbracelet/lipgloss to v0.12.1' (#37) from renovate/github.com-charmbracelet-lipgloss-0.x into main
Reviewed-on: #37
2024-07-14 19:35:23 +00:00
Renovate Bot
b5555f778d Update module github.com/charmbracelet/lipgloss to v0.12.1 2024-07-12 09:47:57 -07:00
Renovate Bot
2a7ceb342d Update module modernc.org/sqlite to v1.30.2 2024-07-12 08:47:56 -07:00
4563d03770 Merge pull request 'Update module github.com/charmbracelet/lipgloss to v0.11.1' (#36) from renovate/github.com-charmbracelet-lipgloss-0.x into main
Reviewed-on: #36
2024-07-10 19:50:20 +00:00
Renovate Bot
a8f2671a44 Update module github.com/charmbracelet/lipgloss to v0.11.1 2024-07-10 07:48:04 -07:00
9f92321ca5 Merge pull request 'Update module google.golang.org/api to v0.188.0' (#35) from renovate/google.golang.org-api-0.x into main
Reviewed-on: #35
2024-07-10 00:42:53 +00:00
Renovate Bot
e8ff2971ca Update module google.golang.org/api to v0.188.0 2024-07-09 10:48:06 -07:00
4a9c315526 Merge pull request 'Update module golang.org/x/net to v0.27.0' (#29) from renovate/golang.org-x-net-0.x into main
Reviewed-on: #29
2024-07-08 02:09:50 +00:00
41792410e7 Merge pull request 'Update module github.com/charmbracelet/bubbletea to v0.26.6' (#26) from renovate/github.com-charmbracelet-bubbletea-0.x into main
Reviewed-on: #26
2024-07-08 02:09:45 +00:00
Renovate Bot
3044be396c Update module golang.org/x/net to v0.27.0 2024-07-07 09:23:51 -07:00
Renovate Bot
4b5237f381 Update module github.com/charmbracelet/bubbletea to v0.26.6 2024-07-07 09:23:46 -07:00
302f60ac4f Merge pull request 'Update module github.com/charmbracelet/lipgloss to v0.11.0' (#27) from renovate/github.com-charmbracelet-lipgloss-0.x into main
Reviewed-on: #27
2024-07-07 16:13:08 +00:00
d7de010442 Merge pull request 'Update module google.golang.org/api to v0.187.0' (#32) from renovate/google.golang.org-api-0.x into main
Reviewed-on: #32
2024-07-07 16:12:55 +00:00
Renovate Bot
5a90fb228d Update module google.golang.org/api to v0.187.0 2024-07-06 14:22:02 -07:00
Renovate Bot
18e355d36c Update module github.com/charmbracelet/lipgloss to v0.11.0 2024-07-06 14:21:47 -07:00
f740e96003
bye frand 2024-07-06 14:08:49 -07:00
6a6a1ada21 Merge pull request 'Update module github.com/charmbracelet/bubbles to v0.18.0' (#25) from renovate/github.com-charmbracelet-bubbles-0.x into main
Reviewed-on: #25
2024-07-06 21:07:20 +00:00
c2352547c7 Merge pull request 'Update module github.com/spf13/cobra to v1.8.1' (#28) from renovate/github.com-spf13-cobra-1.x into main
Reviewed-on: #28
2024-07-06 21:07:12 +00:00
ae9d766246 Merge pull request 'Update module modernc.org/sqlite to v1.30.1' (#33) from renovate/modernc.org-sqlite-1.x into main
Reviewed-on: #33
2024-07-06 21:06:53 +00:00
Renovate Bot
eb39b4fb05 Update module modernc.org/sqlite to v1.30.1 2024-07-06 13:47:47 -07:00
Renovate Bot
0d974324ca Update module github.com/spf13/cobra to v1.8.1 2024-07-06 13:47:22 -07:00
Renovate Bot
9ea787f1ae Update module github.com/charmbracelet/bubbles to v0.18.0 2024-07-06 13:47:05 -07:00
d8fb4da2a7 Update renovate.json 2024-07-06 20:35:50 +00:00
c2a4655cdd Merge pull request 'Update golang Docker tag to v1.22' (#23) from renovate/golang-1.x into main
Reviewed-on: #23
2024-07-06 20:26:19 +00:00
d98a7e82dc Merge pull request 'Update module github.com/zmb3/spotify/v2 to v2.4.2' (#22) from renovate/github.com-zmb3-spotify-v2-2.x into main
Reviewed-on: #22
2024-07-06 20:26:04 +00:00
Renovate Bot
7092c3d520 Update golang Docker tag to v1.22 2024-07-06 13:18:09 -07:00
Renovate Bot
e7e9eedb3b Update module github.com/zmb3/spotify/v2 to v2.4.2 2024-07-06 13:18:07 -07:00
ad529e7aab Merge pull request 'Configure Renovate' (#21) from renovate/configure into main
Reviewed-on: #21
2024-07-06 20:11:05 +00:00
Renovate Bot
43300710ef Add renovate.json 2024-07-06 13:08:20 -07:00
6bbcd97b1e Merge pull request 'this should be println' (#20) from println into main
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: #20
2023-11-06 07:02:48 +00:00
a
2925b2c432
this should be print
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-11-05 20:42:20 -06:00
4cd781d5db
pipeline -> steps
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-20 10:55:20 -07:00
d4f4b142f7
update
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-20 10:53:40 -07:00
2a942a3803
update, checks in format, rename alias for dl
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-10-20 10:52:55 -07:00
95c64ebd70
add force argument to now playing
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-14 09:47:03 -07:00
e39355fb69
download_cover
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-10-14 09:27:58 -07:00
696ee7e263
remove print
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-09-26 15:41:02 -07:00
84b3bd2f90
add case for playlist itme
Some checks failed
ci/woodpecker/manual/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-09-26 15:33:11 -07:00
b569bd5bd6
uncomment 2023-07-25 21:35:38 -07:00
e05573c2cf
remove file 2023-07-25 21:32:07 -07:00
232a1cd3b6
error handling, fix initial auth not erroring, remove depricated functions, update libraries 2023-07-25 21:31:43 -07:00
a34633ec6d
fix pagination
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-04-23 15:10:35 -07:00
5081c44304
oopsie
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-04-16 11:31:54 -07:00
7e22c90311
better messages/feedback
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-16 11:30:37 -07:00
efb03c1aad
fix skipping within queue while playing in a context
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-16 10:58:50 -07:00
b5f76fb321
queue tracks, view queue, queue updates when open, hotkey clean up
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-16 10:47:26 -07:00
6ed3453dae
fix crash if no queue / not playing anything
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-16 00:12:50 -07:00
a386f90ec5
queue wow
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-16 00:05:23 -07:00
81d5135d1a
sanitize playlist descriptions
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-15 23:21:48 -07:00
693b228194
make album info less stupid looking
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-15 23:04:18 -07:00
c124ec4eb9
fix loadmoreitems being called a billion times
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-04-14 19:45:59 -07:00
ec60177128
update
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-14 19:29:05 -07:00
8f179f0bc6
add experimental youtube-link command. fix pagination issues throughout tui 2023-04-14 19:28:31 -07:00
ee9c2299dc
better toggle
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-04-07 09:25:10 -07:00
2fbeb6d21a
make code less bad
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-06 18:11:06 -07:00
55f9c73966
fix paging for playlists
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-05 19:40:24 -07:00
50d5cd18a8
version
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-03-11 21:39:46 -08:00
2b0ea8ed9d
updates
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-03-10 09:51:07 -08:00
d222bc2e49
update urls 2023-03-09 23:48:02 -08:00
45ea45de40 README'
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-03-07 19:06:48 -08:00
a
a1e9cc6dee adds trace log to http, fixes login flow (#14)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: https://gitea.asdf.cafe/abs3nt/gospt/pulls/14
Co-authored-by: a <a@tuxpa.in>
Co-committed-by: a <a@tuxpa.in>
2023-03-07 11:22:18 -08:00
7e65581333 fix: typo
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-03-05 15:15:56 -08:00
c930490f34 ci: add clean arg to goreleaser
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-03-05 15:13:10 -08:00
9a0cd91fcd fix: remove binary
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-03-05 15:11:57 -08:00
b02764ef33 ci: nevermind:
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-03-04 16:39:53 -08:00
fe7f16b9ad ci: ?
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-03-04 16:24:54 -08:00
cc05ab7042 ci: clean
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-03-04 16:20:48 -08:00
23f85fa1ac ci: aur
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-03-04 16:17:55 -08:00
d39ca73b74 ci: aur
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-03-04 16:14:32 -08:00
f57e6cecc9 ci: aur 2023-03-04 16:14:16 -08:00
7ea37f8061 IMPROVED: add status to cache (#12)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
Reviewed-on: https://gitea.asdf.cafe/abs3nt/gospt/pulls/12
2023-02-28 10:53:58 -08:00
9625551767 CI: rlcp
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-28 09:08:48 -08:00
b6d2988bc4 CI: fix completions
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-28 09:03:34 -08:00
5c409f4314 CI: add completions
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline failed
2023-02-28 09:02:16 -08:00
5e4a8c3799 CI: add gitea urls
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2023-02-28 08:49:31 -08:00
14e230985a CI: remove windows 386 build
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-02-28 08:46:11 -08:00
e1bc6ea65d CI: update goreleaser
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-02-28 08:40:16 -08:00
58474e3507 woodpecker update
Some checks failed
ci/woodpecker/tag/woodpecker Pipeline failed
2023-02-28 08:18:08 -08:00
f12a47c5ca woodpecker update
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline failed
2023-02-28 08:11:32 -08:00
c3d907e87f Merge pull request 'fix bug in refill radio and add logging' (#11) from increase-limit into main
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: https://gitea.asdf.cafe/abs3nt/gospt/pulls/11
2023-02-20 15:51:42 -08:00
a
2fd2af1292 fix bug
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-02-20 17:31:12 -06:00
ab4c9d4214 Merge pull request 'cache' (#10) from cache into main
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: https://gitea.asdf.cafe/abs3nt/gospt/pulls/10
2023-02-17 14:29:13 -08:00
a
8a4ac17743 works maybe
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-02-17 16:08:25 -06:00
a
6abd74ea1c resolve
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-02-17 15:52:19 -06:00
a
67e67d32eb changes 2023-02-17 15:45:24 -06:00
a
e58674ce17 cache 2023-02-17 15:31:19 -06:00
a
a8bfc458ef cache 2023-02-17 15:05:06 -06:00
bd1fbc8b27 Merge pull request 'fix' (#9) from fix into main
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: https://gitea.asdf.cafe/abs3nt/gospt/pulls/9
2023-02-09 07:30:17 -08:00
a
9feea44400 fix
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
2023-02-09 01:29:23 -06:00
a
b9b8b4e9f0 a 2023-02-09 01:28:41 -06:00
7bd2b8c3db fix crash on start if no playback context
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-02-05 15:54:48 -08:00
5804607c42 remove debug prints. do logging later
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-31 21:37:10 -08:00
148b93f0f8 dont remove from sqlite and border
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-31 14:27:36 -08:00
1fe232cb96 experimental sqlite tracking for radios
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-30 10:55:31 -08:00
f1a0e8bb3c Merge branch 'main' of gitea.asdf.cafe:abs3nt/gospt
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-01-30 09:13:29 -08:00
b8c736ee11 fix for copy urls, update to playback bar 2023-01-30 09:13:23 -08:00
57b8840d3e Merge pull request 'Update URL for NetBSD package' (#8) from pin/gospt:main into main
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: https://gitea.asdf.cafe/abs3nt/gospt/pulls/8
2023-01-30 07:47:09 -08:00
pin
74cb9d0872 Update URL for NetBSD package
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
pkgsrc.se is no more :(
After over 20 years it says goodbye.
Follow the current state on the official cvs repository. Not as good looking but, it does the job.
2023-01-30 15:13:55 +01:00
49 changed files with 1952 additions and 925 deletions

14
.gitignore vendored
View File

@ -1,3 +1,17 @@
bin/ bin/
bin/* bin/*
/gospt /gospt
gospt_zsh
gospt_bash
gospt_fish
completions
.idea/*
.idea
*.log
*.out
*.tmp
dist/

87
.golangci.yml Normal file
View File

@ -0,0 +1,87 @@
run:
deadline: 10m
skip-dirs:
- hack
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
- rangeValCopy
# 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
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
gofmt:
auto-fix: false
issues:
exclude-rules:
- linters:
- golint
text: "should be"
- linters:
- errcheck
text: "not checked"
- linters:
- staticcheck
text: "SA(1019|1029|5011)"

61
.goreleaser.yaml Normal file
View File

@ -0,0 +1,61 @@
gitea_urls:
api: https://git.asdf.cafe/api/v1
download: https://git.asdf.cafe
skip_tls_verify: false
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
ignore:
- goos: windows
goarch: "386"
ldflags:
- -s -w -X git.asdf.cafe/abs3nt/gospt/src.cmd.Version={{.Version}}
archives:
- format: tar.gz
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
format_overrides:
- goos: windows
format: zip
files:
- completions/*
rlcp: true
checksum:
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

View File

@ -1,24 +1,18 @@
pipeline: steps:
build: build:
image: golang:1.19 image: golang:1.22
commands: commands:
- go mod tidy
- go build -o gospt - go build -o gospt
- ./gospt completion zsh > gospt_zsh - mkdir completions
- ./gospt completion bash > gospt_bash - ./gospt completion zsh > completions/gospt_zsh
- ./gospt completion fish > gospt_fish - ./gospt completion bash > completions/gospt_bash
- ./gospt completion fish > completions/gospt_fish
publish: publish:
image: woodpeckerci/plugin-gitea-release image: goreleaser/goreleaser
settings: commands:
skip_verify: true - goreleaser release --clean
base_url: https://gitea.asdf.cafe secrets: [ gitea_token ]
files:
- gospt
- gospt_zsh
- gospt_bash
- gospt_fish
api_key:
from_secret: GITEA_KEY
checksum: sha256
when: when:
event: tag event: tag

View File

@ -1,7 +1,7 @@
IF YOU ARE ON GITHUB.COM GO HERE INSTEAD: https://gitea.asdf.cafe/abs3nt/gospt :) IF YOU ARE ON GITHUB.COM GO HERE INSTEAD: https://git.asdf.cafe/abs3nt/gospt :)
If you open an issue or PR on github I won't see it please use gitea. Register on asdf and open your PRs there If you open an issue or PR on github I won't see it please use git. 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. 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.
@ -14,19 +14,25 @@ This project is still under heavy development and some things might not work or
## Archlinux ([AUR]) ## Archlinux ([AUR])
```yay -S gospt``` ```yay -S gospt```
or
```yay -S gospt-git```
## NetBSD ([Official repositories]) ## NetBSD ([Official repositories])
```pkgin install gospt``` ```pkgin install gospt```
# To build from source by pulling and building the binary # To build from source by pulling and building the binary
```git clone https://gitea.asdf.cafe/abs3nt/gospt```
```git clone https://git.asdf.cafe/abs3nt/gospt```
```cd gospt``` ```cd gospt```
```make build && sudo make install``` ```make build && sudo make install```
[AUR]: https://aur.archlinux.org/packages/gospt [AUR]: https://aur.archlinux.org/packages/gospt
[Official repositories]: https://pkgsrc.se/audio/gospt/ [Official repositories]: http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/audio/gospt/
# Configuration # 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: 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:
@ -72,6 +78,6 @@ To view help:
Very open to contributations feel free to open a PR Very open to contributations feel free to open a PR
[tmux plugin](https://gitea.asdf.cafe/abs3nt/tmux-gospt) [tmux plugin](https://git.asdf.cafe/abs3nt/tmux-gospt)
[wiki](https://gitea.asdf.cafe/abs3nt/gospt/wiki) [wiki](https://git.asdf.cafe/abs3nt/gospt/wiki)

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,6 +13,6 @@ var clearRadioCmd = &cobra.Command{
Short: "Wipes the radio playlist and creates an empty one", Short: "Wipes the radio playlist and creates an empty one",
Long: `Wipes the radio playlist and creates an empty one, mostly for debugging or if something goes wrong`, Long: `Wipes the radio playlist and creates an empty one, mostly for debugging or if something goes wrong`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.ClearRadio(ctx, client) commands.ClearRadio(ctx)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,6 +13,6 @@ var devicesCmd = &cobra.Command{
Short: "Prints out devices", Short: "Prints out devices",
Long: `Prints out devices`, Long: `Prints out devices`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.Devices(ctx, client) commands.Devices(ctx)
}, },
} }

20
cmd/download_cover.go Normal file
View File

@ -0,0 +1,20 @@
package cmd
import (
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(downloadCoverCmd)
}
var downloadCoverCmd = &cobra.Command{
Use: "download_cover",
Aliases: []string{"dl"},
Short: "Returns url for currently playing song art",
Long: `Returns url for currently playing song art`,
Args: cobra.MatchAll(cobra.ExactArgs(1)),
Run: func(cmd *cobra.Command, args []string) {
commands.DownloadCover(ctx, args)
},
}

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,6 +14,6 @@ var likeCmd = &cobra.Command{
Short: "Likes song", Short: "Likes song",
Long: `Likes song`, Long: `Likes song`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.Like(ctx, client) commands.Like(ctx)
}, },
} }

View File

@ -4,8 +4,6 @@ import (
"fmt" "fmt"
"os" "os"
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,12 +14,12 @@ var linkCmd = &cobra.Command{
Short: "Print link to currently playing song", Short: "Print link to currently playing song",
Long: `Print link to currently playing song`, Long: `Print link to currently playing song`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
link, err := commands.Link(ctx, client) link, err := commands.Link(ctx)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
fmt.Print(link) fmt.Println(link)
}, },
} }

View File

@ -4,8 +4,6 @@ import (
"fmt" "fmt"
"os" "os"
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,7 +14,7 @@ var linkContextCmd = &cobra.Command{
Short: "Get url to current context(album, playlist)", Short: "Get url to current context(album, playlist)",
Long: `Get url to current context(album, playlist)`, Long: `Get url to current context(album, playlist)`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
link, err := commands.LinkContext(ctx, client) link, err := commands.LinkContext(ctx)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,7 +13,7 @@ var muteCmd = &cobra.Command{
Short: "mutes playback", Short: "mutes playback",
Long: `Mutes the spotify device, playback will continue`, Long: `Mutes the spotify device, playback will continue`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := commands.SetVolume(ctx, client, 0) err := commands.SetVolume(ctx, 0)
if err != nil { if err != nil {
return err return err
} }

View File

@ -3,8 +3,6 @@ package cmd
import ( import (
"strconv" "strconv"
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -27,6 +25,6 @@ var nextCmd = &cobra.Command{
return err return err
} }
} }
return commands.Next(ctx, client, skipAmt) return commands.Next(ctx, skipAmt, false)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,7 +13,8 @@ var nowPlayingCmd = &cobra.Command{
Aliases: []string{"now"}, Aliases: []string{"now"},
Short: "Shows song and artist of currently playing song", Short: "Shows song and artist of currently playing song",
Long: `Shows song and artist of currently playing song, useful for scripting`, Long: `Shows song and artist of currently playing song, useful for scripting`,
Args: cobra.MatchAll(cobra.RangeArgs(0, 1)),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.NowPlaying(ctx, client) commands.NowPlaying(ctx, args)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,6 +14,6 @@ var pauseCmd = &cobra.Command{
Aliases: []string{"pa"}, Aliases: []string{"pa"},
Long: `Pauses currently playing song on spotify`, Long: `Pauses currently playing song on spotify`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.Pause(ctx, client) commands.Pause(ctx)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,6 +14,6 @@ var playCmd = &cobra.Command{
Short: "Plays spotify", Short: "Plays spotify",
Long: `Plays queued song on spotify, uses last used device and activates it if needed`, Long: `Plays queued song on spotify, uses last used device and activates it if needed`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.Play(ctx, client) commands.Play(ctx)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,6 +14,6 @@ var playurlCmd = &cobra.Command{
Args: cobra.MatchAll(cobra.ExactArgs(1)), Args: cobra.MatchAll(cobra.ExactArgs(1)),
Long: `Plays song from provided url`, Long: `Plays song from provided url`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.PlayUrl(ctx, client, args) commands.PlayUrl(ctx, args)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,6 +14,6 @@ var previousCmd = &cobra.Command{
Short: "goes to previous song", Short: "goes to previous song",
Long: `if song is playing it will start over, if close to begining of song it will go to previous song`, Long: `if song is playing it will start over, if close to begining of song it will go to previous song`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.Previous(ctx, client) commands.Previous(ctx)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,6 +14,6 @@ var radioCmd = &cobra.Command{
Short: "Starts radio", Short: "Starts radio",
Long: `Starts radio`, Long: `Starts radio`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return commands.Radio(ctx, client) return commands.Radio(ctx)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,6 +14,6 @@ var refillRadioCmd = &cobra.Command{
Short: "Refills the radio", Short: "Refills the radio",
Long: `Deletes all songs up to your position in the radio and adds that many songs to the end of the radio`, Long: `Deletes all songs up to your position in the radio and adds that many songs to the end of the radio`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return commands.RefillRadio(ctx, client) return commands.RefillRadio(ctx)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,6 +13,6 @@ var repeatCmd = &cobra.Command{
Short: "Toggles repeat", Short: "Toggles repeat",
Long: `Switches between repeating your current context or not, spotifyd does not support single track loops`, Long: `Switches between repeating your current context or not, spotifyd does not support single track loops`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.Repeat(ctx, client) commands.Repeat(ctx)
}, },
} }

View File

@ -8,22 +8,23 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"gospt/src/auth" cmds "git.asdf.cafe/abs3nt/gospt/src/commands"
"gospt/src/config"
"gospt/src/gctx" "git.asdf.cafe/abs3nt/gospt/src/config"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
"tuxpa.in/a/zlog"
"github.com/cristalhq/aconfig" "github.com/cristalhq/aconfig"
"github.com/cristalhq/aconfig/aconfigyaml" "github.com/cristalhq/aconfig/aconfigyaml"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/zmb3/spotify/v2"
) )
var ( var (
// Used for flags. // Used for flags.
ctx *gctx.Context ctx *gctx.Context
client *spotify.Client commands *cmds.Commands
cfgFile string cfgFile string
userLicense string verbose bool
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "gospt", Use: "gospt",
@ -45,27 +46,21 @@ func Execute(defCmd string) {
} }
func init() { func init() {
zlog.SetGlobalLevel(zlog.DebugLevel)
if len(os.Args) > 1 { if len(os.Args) > 1 {
if os.Args[1] == "completion" || os.Args[1] == "__complete" { if os.Args[1] == "completion" || os.Args[1] == "__complete" {
return return
} }
} }
cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose logging")
cobra.OnInitialize(initClient) cobra.OnInitialize(func() {
} if verbose {
zlog.SetGlobalLevel(zlog.TraceLevel)
func initClient() { }
var err error })
ctx = gctx.NewContext(context.Background()) ctx = gctx.NewContext(context.Background())
client, err = auth.GetClient(ctx) commands = &cmds.Commands{Context: ctx}
if err != nil { cobra.OnInitialize(initConfig)
panic(err)
}
currentUser, err := client.CurrentUser(ctx)
if err != nil {
panic(err)
}
ctx.UserId = currentUser.ID
} }
func initConfig() { func initConfig() {
@ -94,7 +89,12 @@ func initConfig() {
} }
if config.Values.ClientSecretCmd != "" { if config.Values.ClientSecretCmd != "" {
args := strings.Fields(config.Values.ClientSecretCmd) args := strings.Fields(config.Values.ClientSecretCmd)
secret, err := exec.Command(args[0], args[1:]...).Output() cmd := args[0]
secret_command := exec.Command(cmd)
if len(args) > 1 {
secret_command.Args = args
}
secret, err := secret_command.Output()
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -3,8 +3,6 @@ package cmd
import ( import (
"strconv" "strconv"
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -20,7 +18,7 @@ var seekCmd = &cobra.Command{
Long: `Seeks forward or backward, or seeks to a given position in seconds`, Long: `Seeks forward or backward, or seeks to a given position in seconds`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if args[0] == "forward" || args[0] == "f" { if args[0] == "forward" || args[0] == "f" {
err := commands.Seek(ctx, client, true) err := commands.Seek(ctx, true)
if err != nil { if err != nil {
return err return err
} }
@ -28,7 +26,7 @@ var seekCmd = &cobra.Command{
} }
if args[0] == "backward" || args[0] == "b" { if args[0] == "backward" || args[0] == "b" {
err := commands.Seek(ctx, client, false) err := commands.Seek(ctx, false)
if err != nil { if err != nil {
return err return err
} }
@ -40,7 +38,7 @@ var seekCmd = &cobra.Command{
return err return err
} }
pos = pos * 1000 pos = pos * 1000
err = commands.SetPosition(ctx, client, pos) err = commands.SetPosition(ctx, pos)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,7 +1,7 @@
package cmd package cmd
import ( import (
"gospt/src/tui" "git.asdf.cafe/abs3nt/gospt/src/tui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,6 +15,6 @@ var setDeviceCmd = &cobra.Command{
Short: "Shows tui to pick active device", Short: "Shows tui to pick active device",
Long: `Allows setting or changing the active spotify device, shown in a tui`, Long: `Allows setting or changing the active spotify device, shown in a tui`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
tui.StartTea(ctx, client, "devices") tui.StartTea(ctx, commands, "devices")
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,6 +13,6 @@ var shuffleCmd = &cobra.Command{
Short: "Toggles shuffle", Short: "Toggles shuffle",
Long: `Enables shuffle if it is currently disabled or disables it if it is currently active`, Long: `Enables shuffle if it is currently disabled or disables it if it is currently active`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.Shuffle(ctx, client) commands.Shuffle(ctx)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,6 +13,6 @@ var statusCmd = &cobra.Command{
Short: "Returns player status in json", Short: "Returns player status in json",
Long: `Returns all player status in json, useful for scripting`, Long: `Returns all player status in json, useful for scripting`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.Status(ctx, client) commands.Status(ctx)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,6 +14,6 @@ var togglePlayCmd = &cobra.Command{
Short: "Toggles the play state of spotify", Short: "Toggles the play state of spotify",
Long: `If you are playing a song it will pause and if a song is paused it will play`, Long: `If you are playing a song it will pause and if a song is paused it will play`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.TogglePlay(ctx, client) commands.TogglePlay(ctx)
}, },
} }

View File

@ -4,8 +4,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gospt/src/commands" "git.asdf.cafe/abs3nt/gospt/src/tui"
"gospt/src/tui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -20,12 +19,12 @@ var tracksCmd = &cobra.Command{
Long: `Uses TUI to open a list of saved tracks`, Long: `Uses TUI to open a list of saved tracks`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
configDir, _ := os.UserConfigDir() configDir, _ := os.UserConfigDir()
if commands.ActiveDeviceExists(ctx, client) { if commands.ActiveDeviceExists(ctx) {
return tui.StartTea(ctx, client, "tracks") return tui.StartTea(ctx, commands, "tracks")
} }
if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err != nil { if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err != nil {
return tui.StartTea(ctx, client, "devices") return tui.StartTea(ctx, commands, "devices")
} }
return tui.StartTea(ctx, client, "tracks") return tui.StartTea(ctx, commands, "tracks")
}, },
} }

View File

@ -4,8 +4,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gospt/src/commands" "git.asdf.cafe/abs3nt/gospt/src/tui"
"gospt/src/tui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -20,12 +19,12 @@ var tuiCmd = &cobra.Command{
Long: `Default command. this is what will run if no other commands are present. Shows the main menu.`, Long: `Default command. this is what will run if no other commands are present. Shows the main menu.`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
configDir, _ := os.UserConfigDir() configDir, _ := os.UserConfigDir()
if commands.ActiveDeviceExists(ctx, client) { if commands.ActiveDeviceExists(ctx) {
return tui.StartTea(ctx, client, "main") return tui.StartTea(ctx, commands, "main")
} }
if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err != nil { if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err != nil {
return tui.StartTea(ctx, client, "devices") return tui.StartTea(ctx, commands, "devices")
} }
return tui.StartTea(ctx, client, "main") return tui.StartTea(ctx, commands, "main")
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -16,6 +14,6 @@ var unlikeCmd = &cobra.Command{
Short: "unlikes song", Short: "unlikes song",
Long: `unlikes song`, Long: `unlikes song`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
commands.Unlike(ctx, client) commands.Unlike(ctx)
}, },
} }

View File

@ -1,8 +1,6 @@
package cmd package cmd
import ( import (
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -15,7 +13,7 @@ var unmuteCmd = &cobra.Command{
Short: "unmutes playback", Short: "unmutes playback",
Long: `unmutes the spotify device, playback will continue`, Long: `unmutes the spotify device, playback will continue`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
err := commands.SetVolume(ctx, client, 100) err := commands.SetVolume(ctx, 100)
if err != nil { if err != nil {
return err return err
} }

23
cmd/version.go Normal file
View File

@ -0,0 +1,23 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var Version = "v0.0.47"
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Prints current verison",
Run: version,
}
func version(cmd *cobra.Command, args []string) {
fmt.Printf("Gospt: %s\n", Version)
}

View File

@ -3,8 +3,6 @@ package cmd
import ( import (
"strconv" "strconv"
"gospt/src/commands"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -20,7 +18,7 @@ var volumeCmd = &cobra.Command{
Long: `Sets the volume to the given percent [0-100] or increases/decreases by 5 percent if you say up or down`, Long: `Sets the volume to the given percent [0-100] or increases/decreases by 5 percent if you say up or down`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if args[0] == "up" { if args[0] == "up" {
err := commands.ChangeVolume(ctx, client, 5) err := commands.ChangeVolume(ctx, 5)
if err != nil { if err != nil {
return err return err
} }
@ -28,7 +26,7 @@ var volumeCmd = &cobra.Command{
} }
if args[0] == "down" { if args[0] == "down" {
err := commands.ChangeVolume(ctx, client, -5) err := commands.ChangeVolume(ctx, -5)
if err != nil { if err != nil {
return err return err
} }
@ -39,7 +37,7 @@ var volumeCmd = &cobra.Command{
if err != nil { if err != nil {
return err return err
} }
err = commands.SetVolume(ctx, client, vol) err = commands.SetVolume(ctx, vol)
if err != nil { if err != nil {
return err return err
} }

28
cmd/youtube-link.go Normal file
View File

@ -0,0 +1,28 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
// youtubeLinkCmd represents the youtube-link command
var youtubeLinkCmd = &cobra.Command{
Use: "youtube-link",
Aliases: []string{"yl"},
Short: "Print youtube link to currently playing song",
Long: `Print youtube link to currently playing song`,
Run: func(cmd *cobra.Command, args []string) {
link, err := commands.YoutubeLink(ctx)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Print(link)
},
}
func init() {
rootCmd.AddCommand(youtubeLinkCmd)
}

90
go.mod
View File

@ -1,41 +1,83 @@
module gospt module git.asdf.cafe/abs3nt/gospt
go 1.19 go 1.21
require ( require (
github.com/atotto/clipboard v0.1.4 github.com/atotto/clipboard v0.1.4
github.com/charmbracelet/bubbles v0.14.0 github.com/charmbracelet/bubbles v0.18.0
github.com/charmbracelet/bubbletea v0.23.1 github.com/charmbracelet/bubbletea v0.26.6
github.com/charmbracelet/lipgloss v0.6.0 github.com/charmbracelet/lipgloss v0.12.1
github.com/cristalhq/aconfig v0.18.3 github.com/cristalhq/aconfig v0.18.5
github.com/cristalhq/aconfig/aconfigyaml v0.17.1 github.com/cristalhq/aconfig/aconfigyaml v0.17.1
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.8.1
github.com/zmb3/spotify/v2 v2.3.1 github.com/zmb3/spotify/v2 v2.4.2
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 golang.org/x/net v0.27.0
golang.org/x/oauth2 v0.21.0
golang.org/x/sync v0.7.0
google.golang.org/api v0.188.0
modernc.org/sqlite v1.30.2
tuxpa.in/a/zlog v1.61.0
) )
require ( require (
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect cloud.google.com/go/auth v0.7.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect
cloud.google.com/go/compute v1.25.1 // indirect
cloud.google.com/go/compute/metadata v0.4.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/containerd/console v1.0.3 // indirect github.com/charmbracelet/x/ansi v0.1.4 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/charmbracelet/x/input v0.1.0 // indirect
github.com/charmbracelet/x/term v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.1.0 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // 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/go-logr/logr v1.4.1 // 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.4 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.5 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.13.0 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/sahilm/fuzzy v0.1.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/zerolog v1.31.0 // indirect
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect go.opencensus.io v0.24.0 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
golang.org/x/text v0.3.7 // indirect go.opentelemetry.io/otel v1.24.0 // indirect
google.golang.org/appengine v1.6.7 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect
google.golang.org/grpc v1.64.1 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.52.1 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
) )

333
go.sum
View File

@ -13,12 +13,29 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go/auth v0.6.1 h1:T0Zw1XM5c1GlpN2HYr2s+m3vr1p2wy+8VN+Z1FKxW38=
cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4=
cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts=
cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw=
cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4=
cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0=
cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78=
cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU=
cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c=
cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@ -35,45 +52,82 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og= github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU= github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s=
github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
github.com/charmbracelet/lipgloss v0.11.1 h1:a8KgVPHa7kOoP95vm2tQQrjD2AKhbWmfr4uJ2RW6kNk=
github.com/charmbracelet/lipgloss v0.11.1/go.mod h1:beLlcmkF7MWA+5UrKKIRo/VJ21xGXr7YJ9miWfdMRIU=
github.com/charmbracelet/lipgloss v0.12.1 h1:/gmzszl+pedQpjCOH+wFkZr/N90Snz40J/NR7A0zQcs=
github.com/charmbracelet/lipgloss v0.12.1/go.mod h1:V2CiwIuhx9S1S1ZlADfOj9HmxeMAORuz5izHb0zGbB8=
github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk=
github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/ansi v0.1.3 h1:RBh/eleNWML5R524mjUF0yVRePTwqN9tPtV+DPgO5Lw=
github.com/charmbracelet/x/ansi v0.1.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ=
github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28=
github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI=
github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw=
github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4=
github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E= github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig v0.18.3 h1:Or12LIWIF+2mQpcGWA2PQnNc55+WiHFAqRjYh/pQNtM= github.com/cristalhq/aconfig v0.18.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
github.com/cristalhq/aconfig v0.18.3/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E= github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig/aconfigyaml v0.17.1 h1:xCCbRKVmKrft9gQj3gHOq6U5PduasvlXEIsxtyzmFZ0= github.com/cristalhq/aconfig/aconfigyaml v0.17.1 h1:xCCbRKVmKrft9gQj3gHOq6U5PduasvlXEIsxtyzmFZ0=
github.com/cristalhq/aconfig/aconfigyaml v0.17.1/go.mod h1:5DTsjHkvQ6hfbyxfG32roB1lF0U82rROtFaLxibL8V8= github.com/cristalhq/aconfig/aconfigyaml v0.17.1/go.mod h1:5DTsjHkvQ6hfbyxfG32roB1lF0U82rROtFaLxibL8V8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/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/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -94,9 +148,13 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -106,8 +164,10 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -117,13 +177,29 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.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.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ=
github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA=
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -138,61 +214,99 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
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 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 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/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
github.com/rivo/uniseg v0.4.6/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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zmb3/spotify/v2 v2.3.1 h1:aEyIPotROM3JJjHMCImFROgnPIUpzVo8wymYSaPSd9w= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zmb3/spotify/v2 v2.3.1/go.mod h1:+LVh9CafHu7SedyqYmEf12Rd01dIVlEL845yNhksW0E= 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.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -223,6 +337,10 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -249,15 +367,28 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o=
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.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-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -266,6 +397,12 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -291,26 +428,53 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -354,10 +518,13 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@ -375,14 +542,21 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.148.0 h1:HBq4TZlN4/1pNcu0geJZ/Q50vIwIXT532UIMYoo0vOs=
google.golang.org/api v0.148.0/go.mod h1:8/TBgwaKjfqTdacOJrOv2+2Q6fBDU1uHKK06oGSkxzU=
google.golang.org/api v0.187.0 h1:Mxs7VATVC2v7CY+7Xwm4ndkX71hpElcvx0D1Ji/p1eo=
google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk=
google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw=
google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -412,6 +586,16 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8=
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE=
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -424,6 +608,13 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -436,8 +627,10 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -453,6 +646,36 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.17.10 h1:6wrtRozgrhCxieCeJh85QsxkX/2FFrT9hdaWPlbn4Zo=
modernc.org/ccgo/v4 v4.17.10/go.mod h1:0NBHgsqTTpm9cA5z2ccErvGZmtntSM9qD2kFAs6pjXM=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.52.1 h1:uau0VoiT5hnR+SpoWekCKbLqm7v6dhRL3hI+NQhgN3M=
modernc.org/libc v1.52.1/go.mod h1:HR4nVzFDSDizP620zcMCgjb1/8xk2lg5p/8yjfGv1IQ=
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.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.30.1 h1:YFhPVfu2iIgUf9kuA1CR7iiHdcEEsI2i+yjRYHscyxk=
modernc.org/sqlite v1.30.1/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
modernc.org/sqlite v1.30.2 h1:IPVVkhLu5mMVnS1dQgh3h0SAACRWcVk7aoLP9Us3UCk=
modernc.org/sqlite v1.30.2/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
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=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
tuxpa.in/a/zlog v1.61.0 h1:7wrS6G4QwpnOmgHRQknrr7IgiMXrfGpekkU0PjM9FhE=
tuxpa.in/a/zlog v1.61.0/go.mod h1:CNpMe8laDHLSypx/DyxfX1S0oyxUydeo3aGTEbtRBhg=

View File

@ -1,7 +1,7 @@
package main package main
import ( import (
"gospt/src/cmd" "git.asdf.cafe/abs3nt/gospt/cmd"
) )
func main() { func main() {

3
renovate.json Normal file
View File

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

View File

@ -1,17 +1,19 @@
package auth package auth
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"log"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"time"
"gospt/src/config" "tuxpa.in/a/zlog/log"
"gospt/src/gctx"
"git.asdf.cafe/abs3nt/gospt/src/config"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
spotifyauth "github.com/zmb3/spotify/v2/auth" spotifyauth "github.com/zmb3/spotify/v2/auth"
@ -25,6 +27,12 @@ var (
configDir, _ = os.UserConfigDir() configDir, _ = os.UserConfigDir()
) )
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return fn(req)
}
func GetClient(ctx *gctx.Context) (*spotify.Client, error) { func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
if config.Values.ClientId == "" || config.Values.ClientSecret == "" || config.Values.Port == "" { if config.Values.ClientId == "" || config.Values.ClientSecret == "" || config.Values.Port == "" {
fmt.Println("PLEASE WRITE YOUR CONFIG FILE IN", filepath.Join(configDir, "gospt/client.yml")) fmt.Println("PLEASE WRITE YOUR CONFIG FILE IN", filepath.Join(configDir, "gospt/client.yml"))
@ -57,42 +65,36 @@ func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
), ),
) )
if _, err := os.Stat(filepath.Join(configDir, "gospt/auth.json")); err == nil { if _, err := os.Stat(filepath.Join(configDir, "gospt/auth.json")); err == nil {
authFile, err := os.Open(filepath.Join(configDir, "gospt/auth.json")) authFilePath := filepath.Join(configDir, "gospt/auth.json")
authFile, err := os.Open(authFilePath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer authFile.Close() defer authFile.Close()
authValue, err := io.ReadAll(authFile) tok := &oauth2.Token{}
err = json.NewDecoder(authFile).Decode(tok)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var tok *oauth2.Token ctx.Context = context.WithValue(ctx.Context, oauth2.HTTPClient, &http.Client{
err = json.Unmarshal(authValue, &tok) Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
if err != nil { log.Trace().Interface("path", r.URL.Path).Msg("request")
return nil, err return http.DefaultTransport.RoundTrip(r)
} }),
client := spotify.New(auth.Client(ctx, tok)) })
authClient := auth.Client(ctx, tok)
client := spotify.New(authClient)
new_token, err := client.Token() new_token, err := client.Token()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if new_token != tok {
out, err := json.MarshalIndent(new_token, "", " ") out, err := json.MarshalIndent(new_token, "", " ")
if err != nil { if err != nil {
panic(err.Error()) return nil, err
} }
err = os.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o644) err = os.WriteFile(authFilePath, out, 0o600)
if err != nil { if err != nil {
panic("FAILED TO SAVE AUTH") return nil, fmt.Errorf("failed to save auth")
}
}
out, err := json.MarshalIndent(tok, "", " ")
if err != nil {
panic(err.Error())
}
err = os.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o644)
if err != nil {
panic("FAILED TO SAVE AUTH")
} }
return client, nil return client, nil
} }
@ -101,11 +103,12 @@ func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("Got request for:", r.URL.String()) log.Println("Got request for:", r.URL.String())
}) })
go func() { server := &http.Server{
err := http.ListenAndServe(fmt.Sprintf(":%s", config.Values.Port), nil) Addr: fmt.Sprintf(":%s", config.Values.Port),
if err != nil { ReadHeaderTimeout: 5 * time.Second,
log.Fatal(err)
} }
go func() {
server.ListenAndServe()
}() }()
url := auth.AuthURL(state) url := auth.AuthURL(state)
fmt.Println(url) fmt.Println(url)
@ -114,6 +117,7 @@ func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
// wait for auth to complete // wait for auth to complete
client := <-ch client := <-ch
server.Shutdown(ctx)
// use the client to make calls that require authorization // use the client to make calls that require authorization
user, err := client.CurrentUser(ctx) user, err := client.CurrentUser(ctx)
if err != nil { if err != nil {
@ -127,7 +131,6 @@ func completeAuth(w http.ResponseWriter, r *http.Request) {
tok, err := auth.Token(r.Context(), state, r) tok, err := auth.Token(r.Context(), state, r)
if err != nil { if err != nil {
http.Error(w, "Couldn't get token", http.StatusForbidden) http.Error(w, "Couldn't get token", http.StatusForbidden)
log.Fatal(err)
} }
if st := r.FormValue("state"); st != state { if st := r.FormValue("state"); st != state {
http.NotFound(w, r) http.NotFound(w, r)
@ -137,7 +140,7 @@ func completeAuth(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }
err = os.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o644) err = os.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o600)
if err != nil { if err != nil {
panic("FAILED TO SAVE AUTH") panic("FAILED TO SAVE AUTH")
} }

98
src/cache/cache.go vendored Normal file
View File

@ -0,0 +1,98 @@
package cache
import (
"encoding/json"
"os"
"path/filepath"
"time"
"tuxpa.in/a/zlog/log"
)
type Cache struct {
Root string
}
type CacheEntry struct {
Expire time.Time `json:"e"`
Value string `json:"v"`
}
func DefaultCache() *Cache {
return &Cache{
Root: filepath.Join(os.TempDir(), "gospt.cache"),
}
}
func (c *Cache) load() (map[string]CacheEntry, error) {
out := map[string]CacheEntry{}
cache, err := os.Open(c.Root)
if err != nil {
return nil, err
}
if err := json.NewDecoder(cache).Decode(&out); err != nil {
return nil, err
}
return out, nil
}
func (c *Cache) save(m map[string]CacheEntry) error {
payload, err := json.Marshal(m)
if err != nil {
return err
}
log.Trace().Str("tosave", string(payload)).Msg("saving cache")
err = os.WriteFile(c.Root, payload, 0o600)
if err != nil {
return err
}
return nil
}
func (c *Cache) GetOrDo(key string, do func() (string, error), ttl time.Duration) (string, error) {
conf, err := c.load()
if err != nil {
log.Trace().Err(err).Msg("cache failed read")
return c.Do(key, do, ttl)
}
val, ok := conf[key]
if !ok {
return c.Do(key, do, ttl)
}
if time.Now().After(val.Expire) {
return c.Do(key, do, ttl)
}
return val.Value, nil
}
func (c *Cache) Do(key string, do func() (string, error), ttl time.Duration) (string, error) {
if do == nil {
return "", nil
}
res, err := do()
if err != nil {
return "", err
}
return c.Put(key, res, ttl)
}
func (c *Cache) Put(key string, value string, ttl time.Duration) (string, error) {
conf, err := c.load()
if err != nil {
conf = map[string]CacheEntry{}
}
conf[key] = CacheEntry{
Expire: time.Now().Add(ttl),
Value: value,
}
log.Trace().Str("key", key).Str("val", value).Msg("saving new cache key")
err = c.save(conf)
if err != nil {
log.Trace().Err(err).Msg("cache failed save")
}
return value, nil
}
func (c *Cache) Clear() error {
return os.Remove(c.Root)
}

File diff suppressed because it is too large Load Diff

View File

@ -2,23 +2,32 @@ package gctx
import ( import (
"context" "context"
"log" "fmt"
"os" "os"
"tuxpa.in/a/zlog"
) )
type Context struct { type Context struct {
zlog.Logger
Debug zlog.Logger
context.Context context.Context
*log.Logger }
Debug *log.Logger
UserId string func (c *Context) Err() error {
return c.Context.Err()
}
func (c *Context) Println(args ...any) {
c.Info().Msg(fmt.Sprint(args...))
} }
func NewContext(ctx context.Context) *Context { func NewContext(ctx context.Context) *Context {
out := &Context{ out := &Context{
ctx, Context: ctx,
log.New(os.Stdout, "LOG:", 0), Logger: zlog.New(os.Stderr),
log.New(os.Stdout, "DEBUG:", 1), Debug: zlog.New(os.Stderr),
"",
} }
return out return out
} }

View File

@ -1,109 +1,121 @@
package tui package tui
import ( import (
"fmt"
"gospt/src/commands"
"gospt/src/gctx"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
"git.asdf.cafe/abs3nt/gospt/src/commands"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
) )
func HandlePlayWithContext(ctx *gctx.Context, client *spotify.Client, uri *spotify.URI, pos int) { func HandlePlayWithContext(ctx *gctx.Context, commands *commands.Commands, uri *spotify.URI, pos *int) {
var err error err := commands.PlaySongInPlaylist(ctx, uri, pos)
err = commands.PlaySongInPlaylist(ctx, client, uri, pos)
if err != nil { if err != nil {
return return
} }
} }
func HandleRadio(ctx *gctx.Context, client *spotify.Client, id spotify.ID) { func HandleRadio(ctx *gctx.Context, commands *commands.Commands, song spotify.SimpleTrack) {
err := commands.RadioGivenSong(ctx, client, id, 0) err := commands.RadioGivenSong(ctx, song, 0)
if err != nil { if err != nil {
return return
} }
} }
func HandleAlbumRadio(ctx *gctx.Context, client *spotify.Client, id spotify.ID) { func HandleAlbumRadio(ctx *gctx.Context, commands *commands.Commands, album spotify.SimpleAlbum) {
err := commands.RadioFromAlbum(ctx, client, id) err := commands.RadioFromAlbum(ctx, album)
if err != nil { if err != nil {
return return
} }
} }
func HandleSeek(ctx *gctx.Context, client *spotify.Client, fwd bool) { func HandleSeek(ctx *gctx.Context, commands *commands.Commands, fwd bool) {
err := commands.Seek(ctx, client, fwd) err := commands.Seek(ctx, fwd)
if err != nil { if err != nil {
return return
} }
} }
func HandleVolume(ctx *gctx.Context, client *spotify.Client, up bool) { func HandleVolume(ctx *gctx.Context, commands *commands.Commands, up bool) {
vol := 10 vol := 10
if !up { if !up {
vol = -10 vol = -10
} }
err := commands.ChangeVolume(ctx, client, vol) err := commands.ChangeVolume(ctx, vol)
if err != nil { if err != nil {
return return
} }
} }
func HandleArtistRadio(ctx *gctx.Context, client *spotify.Client, id spotify.ID) { func HandleArtistRadio(ctx *gctx.Context, commands *commands.Commands, artist spotify.SimpleArtist) {
err := commands.RadioGivenArtist(ctx, client, id) err := commands.RadioGivenArtist(ctx, artist)
if err != nil { if err != nil {
return return
} }
} }
func HandleAlbumArtist(ctx *gctx.Context, client *spotify.Client, id spotify.ID) { func HandleAlbumArtist(ctx *gctx.Context, commands *commands.Commands, artist spotify.SimpleArtist) {
err := commands.RadioGivenArtist(ctx, client, id) err := commands.RadioGivenArtist(ctx, artist)
if err != nil { if err != nil {
return return
} }
} }
func HandlePlaylistRadio(ctx *gctx.Context, client *spotify.Client, playlist spotify.SimplePlaylist) { func HandlePlaylistRadio(ctx *gctx.Context, commands *commands.Commands, playlist spotify.SimplePlaylist) {
err := commands.RadioFromPlaylist(ctx, client, playlist) err := commands.RadioFromPlaylist(ctx, playlist)
if err != nil { if err != nil {
return return
} }
} }
func HandleLibraryRadio(ctx *gctx.Context, client *spotify.Client) { func HandleLibraryRadio(ctx *gctx.Context, commands *commands.Commands) {
err := commands.RadioFromSavedTracks(ctx, client) err := commands.RadioFromSavedTracks(ctx)
if err != nil { if err != nil {
fmt.Println(err.Error())
return return
} }
} }
func HandlePlayLikedSong(ctx *gctx.Context, client *spotify.Client, position int) { func HandlePlayLikedSong(ctx *gctx.Context, commands *commands.Commands, position int) {
err := commands.PlayLikedSongs(ctx, client, position) err := commands.PlayLikedSongs(ctx, position)
if err != nil { if err != nil {
fmt.Println(err.Error())
return return
} }
} }
func HandlePlayTrack(ctx *gctx.Context, client *spotify.Client, track spotify.ID) { func HandlePlayTrack(ctx *gctx.Context, commands *commands.Commands, track spotify.ID) {
err := commands.QueueSong(ctx, client, track) err := commands.QueueSong(ctx, track)
if err != nil { if err != nil {
fmt.Println(err.Error())
return return
} }
err = commands.Next(ctx, client, 1) err = commands.Next(ctx, 1, false)
if err != nil { if err != nil {
fmt.Println(err.Error())
return return
} }
} }
func HandleSetDevice(ctx *gctx.Context, client *spotify.Client, player spotify.PlayerDevice) { func HandleNextInQueue(ctx *gctx.Context, commands *commands.Commands, amt int) {
var err error err := commands.Next(ctx, amt, true)
err = commands.SetDevice(ctx, client, player) if err != nil {
return
}
}
func HandleQueueItem(ctx *gctx.Context, commands *commands.Commands, item spotify.ID) {
err := commands.QueueSong(ctx, item)
if err != nil {
return
}
}
func HandleDeleteTrackFromPlaylist(ctx *gctx.Context, commands *commands.Commands, item, playlist spotify.ID) {
err := commands.DeleteTracksFromPlaylist(ctx, []spotify.ID{item}, playlist)
if err != nil {
return
}
}
func HandleSetDevice(ctx *gctx.Context, commands *commands.Commands, player spotify.PlayerDevice) {
err := commands.SetDevice(ctx, player)
if err != nil { if err != nil {
fmt.Println(err.Error())
return return
} }
} }

View File

@ -4,16 +4,18 @@ import (
"fmt" "fmt"
"time" "time"
"gospt/src/commands"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
) )
func (m *mainModel) LoadMoreItems() { func (m *mainModel) LoadMoreItems() {
loading = true
defer func() {
page++
loading = false
}()
switch m.mode { switch m.mode {
case "artist": case "artist":
albums, err := commands.ArtistAlbums(m.ctx, m.client, m.artist.ID, (page + 1)) albums, err := m.commands.ArtistAlbums(m.ctx, m.artist.ID, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -32,8 +34,7 @@ func (m *mainModel) LoadMoreItems() {
main_updates <- m main_updates <- m
return return
case "artists": case "artists":
artists, err := commands.UserArtists(m.ctx, m.client, (page + 1)) artists, err := m.commands.UserArtists(m.ctx, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -52,8 +53,7 @@ func (m *mainModel) LoadMoreItems() {
main_updates <- m main_updates <- m
return return
case "album": case "album":
tracks, err := commands.AlbumTracks(m.ctx, m.client, m.album.ID, (page + 1)) tracks, err := m.commands.AlbumTracks(m.ctx, m.album.ID, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -73,8 +73,7 @@ func (m *mainModel) LoadMoreItems() {
main_updates <- m main_updates <- m
return return
case "albums": case "albums":
albums, err := commands.UserAlbums(m.ctx, m.client, (page + 1)) albums, err := m.commands.UserAlbums(m.ctx, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -93,8 +92,7 @@ func (m *mainModel) LoadMoreItems() {
main_updates <- m main_updates <- m
return return
case "main": case "main":
playlists, err := commands.Playlists(m.ctx, m.client, (page + 1)) playlists, err := m.commands.Playlists(m.ctx, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -112,19 +110,18 @@ func (m *mainModel) LoadMoreItems() {
main_updates <- m main_updates <- m
return return
case "playlist": case "playlist":
tracks, err := commands.PlaylistTracks(m.ctx, m.client, m.playlist.ID, (page + 1)) playlistItems, err := m.commands.PlaylistTracks(m.ctx, m.playlist.ID, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
items := []mainItem{} items := []mainItem{}
for _, track := range tracks.Tracks { for _, item := range playlistItems.Items {
items = append(items, mainItem{ items = append(items, mainItem{
Name: track.Track.Name, Name: item.Track.Track.Name,
Artist: track.Track.Artists[0], Artist: item.Track.Track.Artists[0],
Duration: track.Track.TimeDuration().Round(time.Second).String(), Duration: item.Track.Track.TimeDuration().Round(time.Second).String(),
ID: track.Track.ID, ID: item.Track.Track.ID,
Desc: track.Track.Artists[0].Name + " - " + track.Track.TimeDuration().Round(time.Second).String(), Desc: item.Track.Track.Artists[0].Name + " - " + item.Track.Track.TimeDuration().Round(time.Second).String(),
}) })
} }
for _, item := range items { for _, item := range items {
@ -133,12 +130,10 @@ func (m *mainModel) LoadMoreItems() {
main_updates <- m main_updates <- m
return return
case "tracks": case "tracks":
tracks, err := commands.TrackList(m.ctx, m.client, (page + 1)) tracks, err := m.commands.TrackList(m.ctx, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
page++
items := []list.Item{} items := []list.Item{}
for _, track := range tracks.Tracks { for _, track := range tracks.Tracks {
items = append(items, mainItem{ items = append(items, mainItem{

View File

@ -2,10 +2,9 @@ package tui
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"gospt/src/gctx"
"github.com/atotto/clipboard" "github.com/atotto/clipboard"
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
@ -14,38 +13,45 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
"git.asdf.cafe/abs3nt/gospt/src/commands"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
) )
var ( var (
P *tea.Program P *tea.Program
DocStyle = lipgloss.NewStyle().Margin(0, 2) DocStyle = lipgloss.NewStyle().Margin(0, 2).Border(lipgloss.DoubleBorder(), true, true, true, true)
currentlyPlaying string currentlyPlaying *spotify.CurrentlyPlaying
playbackContext string
main_updates chan *mainModel main_updates chan *mainModel
page = 1 page = 1
loading = false
showingMessage = false
) )
type Mode string type Mode string
const ( const (
Album Mode = "album" Album Mode = "album"
ArtistAlbum = "artistalbum" ArtistAlbum Mode = "artistalbum"
Artist = "artist" Artist Mode = "artist"
Artists = "artists" Artists Mode = "artists"
Tracks = "tracks" Queue Mode = "queue"
Albums = "albums" Tracks Mode = "tracks"
Main = "main" Albums Mode = "albums"
Playlists = "playlists" Main Mode = "main"
Playlist = "playlist" Playlists Mode = "playlists"
Devices = "devices" Playlist Mode = "playlist"
Search = "search" Devices Mode = "devices"
SearchAlbums = "searchalbums" Search Mode = "search"
SearchAlbum = "searchalbum" SearchAlbums Mode = "searchalbums"
SearchArtists = "searchartists" SearchAlbum Mode = "searchalbum"
SearchArtist = "searchartist" SearchArtists Mode = "searchartists"
SearchArtistAlbum = "searchartistalbum" SearchArtist Mode = "searchartist"
SearchTracks = "searchtracks" SearchArtistAlbum Mode = "searchartistalbum"
SearchPlaylists = "searchplaylsits" SearchTracks Mode = "searchtracks"
SearchPlaylist = "searchplaylist" SearchPlaylists Mode = "searchplaylsits"
SearchPlaylist Mode = "searchplaylist"
) )
type mainItem struct { type mainItem struct {
@ -72,7 +78,7 @@ type mainModel struct {
list list.Model list list.Model
input textinput.Model input textinput.Model
ctx *gctx.Context ctx *gctx.Context
client *spotify.Client commands *commands.Commands
mode Mode mode Mode
playlist spotify.SimplePlaylist playlist spotify.SimplePlaylist
artist spotify.SimpleArtist artist spotify.SimpleArtist
@ -80,79 +86,86 @@ type mainModel struct {
searchResults *SearchResults searchResults *SearchResults
progress progress.Model progress progress.Model
playing *spotify.CurrentlyPlaying playing *spotify.CurrentlyPlaying
playbackContext string
search string search string
} }
func (m *mainModel) PlayRadio() { func (m *mainModel) PlayRadio() {
currentlyPlaying = m.list.SelectedItem().(mainItem).Title() go m.SendMessage("Starting radio for "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Starting radio for " + currentlyPlaying)
selectedItem := m.list.SelectedItem().(mainItem).SpotifyItem selectedItem := m.list.SelectedItem().(mainItem).SpotifyItem
switch selectedItem.(type) { switch item := selectedItem.(type) {
case spotify.SimplePlaylist: case spotify.SimplePlaylist:
go HandlePlaylistRadio(m.ctx, m.client, selectedItem.(spotify.SimplePlaylist)) go HandlePlaylistRadio(m.ctx, m.commands, item)
return return
case *spotify.SavedTrackPage: case *spotify.SavedTrackPage:
go HandleLibraryRadio(m.ctx, m.client) go HandleLibraryRadio(m.ctx, m.commands)
return return
case spotify.SimpleAlbum: case spotify.SimpleAlbum:
go HandleAlbumRadio(m.ctx, m.client, selectedItem.(spotify.SimpleAlbum).ID) go HandleAlbumRadio(m.ctx, m.commands, item)
return return
case spotify.FullAlbum: case spotify.FullAlbum:
go HandleAlbumRadio(m.ctx, m.client, selectedItem.(spotify.FullAlbum).ID) go HandleAlbumRadio(m.ctx, m.commands, item.SimpleAlbum)
return return
case spotify.SimpleArtist: case spotify.SimpleArtist:
go HandleArtistRadio(m.ctx, m.client, selectedItem.(spotify.SimpleArtist).ID) go HandleArtistRadio(m.ctx, m.commands, item)
return return
case spotify.FullArtist: case spotify.FullArtist:
go HandleArtistRadio(m.ctx, m.client, selectedItem.(spotify.FullArtist).ID) go HandleArtistRadio(m.ctx, m.commands, item.SimpleArtist)
return return
default: case spotify.SimpleTrack:
go HandleRadio(m.ctx, m.client, m.list.SelectedItem().(mainItem).ID) go HandleRadio(m.ctx, m.commands, item)
return
case spotify.FullTrack:
go HandleRadio(m.ctx, m.commands, item.SimpleTrack)
return
case spotify.PlaylistTrack:
go HandleRadio(m.ctx, m.commands, item.Track.SimpleTrack)
return
case spotify.PlaylistItem:
go HandleRadio(m.ctx, m.commands, item.Track.Track.SimpleTrack)
return
case spotify.SavedTrack:
go HandleRadio(m.ctx, m.commands, item.SimpleTrack)
return return
} }
} }
func (m *mainModel) GoBack() (tea.Cmd, error) { func (m *mainModel) GoBack() (tea.Cmd, error) {
page = 1
switch m.mode { switch m.mode {
case Main: case Main:
return tea.Quit, nil return tea.Quit, nil
case Albums, Artists, Tracks, Playlist, Devices, Search: case Albums, Artists, Tracks, Playlist, Devices, Search, Queue:
m.mode = Main m.mode = Main
m.list.NewStatusMessage("Setting view to main") new_items, err := MainView(m.ctx, m.commands)
new_items, err := MainView(m.ctx, m.client)
if err != nil { if err != nil {
fmt.Println(err.Error()) return nil, err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
case Album: case Album:
m.mode = Albums m.mode = Albums
m.list.NewStatusMessage("Setting view to albums") new_items, err := AlbumsView(m.ctx, m.commands)
new_items, err := AlbumsView(m.ctx, m.client)
if err != nil { if err != nil {
fmt.Println(err.Error())
return nil, err return nil, err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
case Artist: case Artist:
m.mode = Artists m.mode = Artists
m.list.NewStatusMessage("Setting view to artists") new_items, err := ArtistsView(m.ctx, m.commands)
new_items, err := ArtistsView(m.ctx, m.client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
case ArtistAlbum: case ArtistAlbum:
m.mode = Artist m.mode = Artist
m.list.NewStatusMessage("Opening " + m.artist.Name) new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
case SearchArtists, SearchTracks, SearchAlbums, SearchPlaylists: case SearchArtists, SearchTracks, SearchAlbums, SearchPlaylists:
m.mode = Search m.mode = Search
m.list.NewStatusMessage("Setting view to search for " + m.input.Value()) items, result, err := SearchView(m.ctx, m.commands, m.search)
items, result, err := SearchView(m.ctx, m.client, m.search)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -160,32 +173,28 @@ func (m *mainModel) GoBack() (tea.Cmd, error) {
m.list.SetItems(items) m.list.SetItems(items)
case SearchArtist: case SearchArtist:
m.mode = SearchArtists m.mode = SearchArtists
m.list.NewStatusMessage("Setting view to artists") new_items, err := SearchArtistsView(m.ctx, m.commands, m.searchResults.Artists)
new_items, err := SearchArtistsView(m.ctx, m.client, m.searchResults.Artists)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
case SearchArtistAlbum: case SearchArtistAlbum:
m.mode = SearchArtist m.mode = SearchArtist
m.list.NewStatusMessage("Opening " + m.artist.Name) new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.client)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
case SearchAlbum: case SearchAlbum:
m.mode = SearchAlbums m.mode = SearchAlbums
m.list.NewStatusMessage("Setting view to albums") new_items, err := SearchAlbumsView(m.ctx, m.commands, m.searchResults.Albums)
new_items, err := SearchAlbumsView(m.ctx, m.client, m.searchResults.Albums)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
case SearchPlaylist: case SearchPlaylist:
m.mode = SearchPlaylists m.mode = SearchPlaylists
m.list.NewStatusMessage("Setting view to playlists") new_items, err := SearchPlaylistsView(m.ctx, m.commands, m.searchResults.Playlists)
new_items, err := SearchPlaylistsView(m.ctx, m.client, m.searchResults.Playlists)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -202,40 +211,122 @@ type SpotifyUrl struct {
func (m *mainModel) CopyToClipboard() error { func (m *mainModel) CopyToClipboard() error {
item := m.list.SelectedItem().(mainItem).SpotifyItem item := m.list.SelectedItem().(mainItem).SpotifyItem
m.list.NewStatusMessage("Copying link to " + item.(mainItem).Title()) switch converted := item.(type) {
switch item.(type) {
case spotify.SimplePlaylist: case spotify.SimplePlaylist:
clipboard.WriteAll(item.(spotify.SimplePlaylist).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.ExternalURLs["spotify"])
case *spotify.FullPlaylist: case *spotify.FullPlaylist:
clipboard.WriteAll(item.(*spotify.FullPlaylist).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.SimpleAlbum: case spotify.SimpleAlbum:
clipboard.WriteAll(item.(spotify.SimpleAlbum).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.ExternalURLs["spotify"])
case *spotify.FullAlbum: case *spotify.FullAlbum:
clipboard.WriteAll(item.(*spotify.FullAlbum).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.SimpleArtist: case spotify.SimpleArtist:
clipboard.WriteAll(item.(spotify.SimpleArtist).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.ExternalURLs["spotify"])
case *spotify.FullArtist: case *spotify.FullArtist:
clipboard.WriteAll(item.(*spotify.FullArtist).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.SimpleTrack: case spotify.SimpleTrack:
clipboard.WriteAll(item.(spotify.SimpleTrack).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.PlaylistTrack: case spotify.PlaylistTrack:
clipboard.WriteAll(item.(spotify.PlaylistTrack).Track.ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.Track.ExternalURLs["spotify"])
case spotify.SavedTrack: case spotify.SavedTrack:
clipboard.WriteAll(item.(spotify.SavedTrack).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.FullTrack: case spotify.FullTrack:
clipboard.WriteAll(item.(spotify.FullTrack).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
clipboard.WriteAll(converted.ExternalURLs["spotify"])
} }
return nil return nil
} }
func (m *mainModel) SendMessage(msg string, duration time.Duration) {
showingMessage = true
defer func() {
showingMessage = false
}()
m.list.NewStatusMessage(msg)
time.Sleep(duration)
}
func (m *mainModel) QueueItem() error {
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
case spotify.PlaylistTrack:
go m.SendMessage("Adding "+item.Track.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.Track.ID)
case spotify.SavedTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case spotify.SimpleTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case spotify.FullTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case *spotify.FullTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case *spotify.SimpleTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case *spotify.SimplePlaylist:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
}
if m.mode == Queue {
go func() {
new_items, err := QueueView(m.ctx, m.commands)
if err != nil {
return
}
m.list.SetItems(new_items)
}()
}
return nil
}
func (m *mainModel) DeleteTrackFromPlaylist() error {
if m.mode != Playlist {
return nil
}
track := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlaylistTrack).Track
go m.SendMessage("Deleteing "+track.Name+" from "+m.playlist.Name, 2*time.Second)
go func() {
HandleDeleteTrackFromPlaylist(m.ctx, m.commands, track.ID, m.playlist.ID)
new_items, err := PlaylistView(m.ctx, m.commands, m.playlist)
if err != nil {
return
}
m.list.SetItems(new_items)
}()
return nil
}
func (m *mainModel) SelectItem() error { func (m *mainModel) SelectItem() error {
switch m.mode { switch m.mode {
case Queue:
page = 1
go func() {
HandleNextInQueue(m.ctx, m.commands, m.list.Index())
new_items, err := QueueView(m.ctx, m.commands)
if err != nil {
return
}
m.list.SetItems(new_items)
m.list.ResetSelected()
}()
case Search: case Search:
switch m.list.SelectedItem().(mainItem).SpotifyItem.(type) { page = 1
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
case *spotify.FullArtistPage: case *spotify.FullArtistPage:
m.mode = SearchArtists m.mode = SearchArtists
m.list.NewStatusMessage("Setting view to artists") new_items, err := SearchArtistsView(m.ctx, m.commands, item)
new_items, err := SearchArtistsView(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.FullArtistPage))
if err != nil { if err != nil {
return err return err
} }
@ -243,8 +334,7 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case *spotify.SimpleAlbumPage: case *spotify.SimpleAlbumPage:
m.mode = SearchAlbums m.mode = SearchAlbums
m.list.NewStatusMessage("Setting view to albums") new_items, err := SearchAlbumsView(m.ctx, m.commands, item)
new_items, err := SearchAlbumsView(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.SimpleAlbumPage))
if err != nil { if err != nil {
return err return err
} }
@ -252,9 +342,7 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case *spotify.SimplePlaylistPage: case *spotify.SimplePlaylistPage:
m.mode = SearchPlaylists m.mode = SearchPlaylists
playlists := m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.SimplePlaylistPage) new_items, err := SearchPlaylistsView(m.ctx, m.commands, item)
m.list.NewStatusMessage("Setting view to playlist")
new_items, err := SearchPlaylistsView(m.ctx, m.client, playlists)
if err != nil { if err != nil {
return err return err
} }
@ -262,62 +350,68 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case *spotify.FullTrackPage: case *spotify.FullTrackPage:
m.mode = SearchTracks m.mode = SearchTracks
m.list.NewStatusMessage("Setting view to tracks") new_items, err := SearchTracksView(m.ctx, m.commands, item)
new_items, err := SearchTracksView(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.FullTrackPage))
if err != nil { if err != nil {
return err return err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
m.list.NewStatusMessage("Setting view to tracks")
} }
case SearchArtists: case SearchArtists:
page = 1
m.mode = SearchArtist m.mode = SearchArtist
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist) m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
m.list.NewStatusMessage("Opening " + m.artist.Name) new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.client)
if err != nil { if err != nil {
return err return err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case SearchArtist: case SearchArtist:
page = 1
m.mode = SearchArtistAlbum m.mode = SearchArtistAlbum
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum) m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
m.list.NewStatusMessage("Opening " + m.album.Name) new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.client)
if err != nil { if err != nil {
return err return err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case SearchAlbums: case SearchAlbums:
page = 1
m.mode = SearchAlbum m.mode = SearchAlbum
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum) m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
m.list.NewStatusMessage("Opening " + m.album.Name) new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.client)
if err != nil { if err != nil {
return err return err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case SearchPlaylists: case SearchPlaylists:
page = 1
m.mode = SearchPlaylist m.mode = SearchPlaylist
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist) playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
m.playlist = playlist m.playlist = playlist
m.list.NewStatusMessage("Setting view to playlist " + playlist.Name) new_items, err := PlaylistView(m.ctx, m.commands, playlist)
new_items, err := PlaylistView(m.ctx, m.client, playlist)
if err != nil { if err != nil {
return err return err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case Main: case Main:
switch m.list.SelectedItem().(mainItem).SpotifyItem.(type) { page = 1
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
case spotify.Queue:
m.mode = Queue
new_items, err := QueueView(m.ctx, m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.ResetSelected()
case *spotify.FullArtistCursorPage: case *spotify.FullArtistCursorPage:
m.mode = Artists m.mode = Artists
m.list.NewStatusMessage("Setting view to artists") new_items, err := ArtistsView(m.ctx, m.commands)
new_items, err := ArtistsView(m.ctx, m.client)
if err != nil { if err != nil {
return err return err
} }
@ -325,8 +419,7 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case *spotify.SavedAlbumPage: case *spotify.SavedAlbumPage:
m.mode = Albums m.mode = Albums
m.list.NewStatusMessage("Setting view to albums") new_items, err := AlbumsView(m.ctx, m.commands)
new_items, err := AlbumsView(m.ctx, m.client)
if err != nil { if err != nil {
return err return err
} }
@ -334,10 +427,8 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case spotify.SimplePlaylist: case spotify.SimplePlaylist:
m.mode = Playlist m.mode = Playlist
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist) m.playlist = item
m.playlist = playlist new_items, err := PlaylistView(m.ctx, m.commands, item)
m.list.NewStatusMessage("Setting view to playlist " + playlist.Name)
new_items, err := PlaylistView(m.ctx, m.client, playlist)
if err != nil { if err != nil {
return err return err
} }
@ -345,20 +436,18 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case *spotify.SavedTrackPage: case *spotify.SavedTrackPage:
m.mode = Tracks m.mode = Tracks
m.list.NewStatusMessage("Setting view to saved tracks") new_items, err := SavedTracksView(m.ctx, m.commands)
new_items, err := SavedTracksView(m.ctx, m.client)
if err != nil { if err != nil {
return err return err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
m.list.NewStatusMessage("Setting view to tracks")
} }
case Albums: case Albums:
page = 1
m.mode = Album m.mode = Album
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum) m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
m.list.NewStatusMessage("Opening " + m.album.Name) new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.client)
if err != nil { if err != nil {
return err return err
} }
@ -367,8 +456,7 @@ func (m *mainModel) SelectItem() error {
case Artist: case Artist:
m.mode = ArtistAlbum m.mode = ArtistAlbum
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum) m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
m.list.NewStatusMessage("Opening " + m.album.Name) new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.client)
if err != nil { if err != nil {
return err return err
} }
@ -377,44 +465,36 @@ func (m *mainModel) SelectItem() error {
case Artists: case Artists:
m.mode = Artist m.mode = Artist
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist) m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
m.list.NewStatusMessage("Opening " + m.artist.Name) new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.client)
if err != nil { if err != nil {
return err return err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case Album, ArtistAlbum, SearchArtistAlbum, SearchAlbum: case Album, ArtistAlbum, SearchArtistAlbum, SearchAlbum:
currentlyPlaying = m.list.SelectedItem().FilterValue() pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.TotalPages)
m.list.NewStatusMessage("Playing " + currentlyPlaying) go HandlePlayWithContext(m.ctx, m.commands, &m.album.URI, &pos)
go HandlePlayWithContext(m.ctx, m.client, &m.album.URI, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.TotalPages))
case Playlist, SearchPlaylist: case Playlist, SearchPlaylist:
currentlyPlaying = m.list.SelectedItem().FilterValue() pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.PerPage)
m.list.NewStatusMessage("Playing " + currentlyPlaying) go HandlePlayWithContext(m.ctx, m.commands, &m.playlist.URI, &pos)
go HandlePlayWithContext(m.ctx, m.client, &m.playlist.URI, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage))
case Tracks: case Tracks:
currentlyPlaying = m.list.SelectedItem().FilterValue() go HandlePlayLikedSong(m.ctx, m.commands, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage))
m.list.NewStatusMessage("Playing " + currentlyPlaying)
go HandlePlayLikedSong(m.ctx, m.client, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage))
case SearchTracks: case SearchTracks:
currentlyPlaying = m.list.SelectedItem().FilterValue() go HandlePlayTrack(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.FullTrack).ID)
m.list.NewStatusMessage("Playing " + currentlyPlaying)
go HandlePlayTrack(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.FullTrack).ID)
case Devices: case Devices:
go HandleSetDevice(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlayerDevice)) go HandleSetDevice(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlayerDevice))
m.list.NewStatusMessage("Setting device to " + m.list.SelectedItem().FilterValue()) go m.SendMessage("Setting device to "+m.list.SelectedItem().FilterValue(), 2*time.Second)
m.mode = "main" m.mode = "main"
m.list.NewStatusMessage("Setting view to main") new_items, err := MainView(m.ctx, m.commands)
new_items, err := MainView(m.ctx, m.client)
if err != nil { if err != nil {
fmt.Println(err.Error()) return err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
} }
return nil return nil
} }
func (m mainModel) Init() tea.Cmd { func (m *mainModel) Init() tea.Cmd {
main_updates = make(chan *mainModel) main_updates = make(chan *mainModel)
return Tick() return Tick()
} }
@ -427,7 +507,32 @@ func Tick() tea.Cmd {
}) })
} }
func (m mainModel) View() string { func (m *mainModel) TickPlayback() {
playing, _ := m.commands.Client().PlayerCurrentlyPlaying(m.ctx)
if playing != nil && playing.Playing && playing.Item != nil {
currentlyPlaying = playing
playbackContext, _ = m.getContext(playing)
}
ticker := time.NewTicker(1 * time.Second)
quit := make(chan struct{})
go func() {
for {
select {
case <-ticker.C:
playing, _ := m.commands.Client().PlayerCurrentlyPlaying(m.ctx)
if playing != nil && playing.Playing && playing.Item != nil {
currentlyPlaying = playing
playbackContext, _ = m.getContext(playing)
}
case <-quit:
ticker.Stop()
return
}
}
}()
}
func (m *mainModel) View() string {
if m.input.Focused() { if m.input.Focused() {
return DocStyle.Render(m.list.View() + "\n" + m.input.View()) return DocStyle.Render(m.list.View() + "\n" + m.input.View())
} }
@ -436,10 +541,8 @@ func (m mainModel) View() string {
func (m *mainModel) Typing(msg tea.KeyMsg) (bool, tea.Cmd) { func (m *mainModel) Typing(msg tea.KeyMsg) (bool, tea.Cmd) {
if msg.String() == "enter" { if msg.String() == "enter" {
m.list.NewStatusMessage("Setting view to search for " + m.input.Value()) items, result, err := SearchView(m.ctx, m.commands, m.input.Value())
items, result, err := SearchView(m.ctx, m.client, m.input.Value())
if err != nil { if err != nil {
fmt.Println(err.Error())
return false, tea.Quit return false, tea.Quit
} }
m.searchResults = result m.searchResults = result
@ -459,9 +562,37 @@ func (m *mainModel) Typing(msg tea.KeyMsg) (bool, tea.Cmd) {
return false, nil return false, nil
} }
func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *mainModel) getContext(playing *spotify.CurrentlyPlaying) (string, error) {
// Update Now Playing context := playing.PlaybackContext
m.list.NewStatusMessage(currentlyPlaying) uri_split := strings.Split(string(context.URI), ":")
if len(uri_split) < 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.ctx, spotify.ID(id))
if err != nil {
return "", err
}
return album.Name, nil
case "playlist":
playlist, err := m.commands.Client().GetPlaylist(m.ctx, spotify.ID(id))
if err != nil {
return "", err
}
return playlist.Name, nil
case "artist":
artist, err := m.commands.Client().GetArtist(m.ctx, spotify.ID(id))
if err != nil {
return "", err
}
return artist.Name, nil
}
return "", nil
}
func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Update list items from LoadMore // Update list items from LoadMore
select { select {
case update := <-main_updates: case update := <-main_updates:
@ -469,7 +600,7 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
default: default:
} }
// Call for more items if needed // Call for more items if needed
if m.list.Paginator.Page == m.list.Paginator.TotalPages-2 && m.list.Cursor() == 0 { if m.list.Paginator.Page == m.list.Paginator.TotalPages-1 && m.list.Cursor() == 0 && !loading {
// if last request was still full request more // if last request was still full request more
if len(m.list.Items())%50 == 0 { if len(m.list.Items())%50 == 0 {
go m.LoadMoreItems() go m.LoadMoreItems()
@ -478,20 +609,40 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Handle user input // Handle user input
switch msg := msg.(type) { switch msg := msg.(type) {
case tickMsg: case tickMsg:
playing, err := m.client.PlayerCurrentlyPlaying(m.ctx) playing := currentlyPlaying
if err != nil {
return nil, nil
}
if playing != nil && playing.Playing && playing.Item != nil { if playing != nil && playing.Playing && playing.Item != nil {
cmd := m.progress.SetPercent(float64(playing.Progress) / float64(playing.Item.Duration)) cmd := m.progress.SetPercent(float64(playing.Progress) / float64(playing.Item.Duration))
m.playing = playing m.playing = playing
m.playbackContext = playbackContext
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.ctx, m.commands)
if err != nil {
return
}
m.list.SetItems(new_items)
}()
}
}
return m, tea.Batch(Tick(), cmd) return m, tea.Batch(Tick(), cmd)
} }
return m, Tick() return m, Tick()
case progress.FrameMsg: case progress.FrameMsg:
progressModel, cmd := m.progress.Update(msg) progressModel, cmd := m.progress.Update(msg)
m.progress = progressModel.(progress.Model) m.progress = progressModel.(progress.Model)
currentlyPlaying = fmt.Sprintf("Now playing %s by %s - %s %s/%s ", m.playing.Item.Name, m.playing.Item.Artists[0].Name, m.progress.View(), (time.Duration(m.playing.Progress) * time.Millisecond).Round(time.Second), (time.Duration(m.playing.Item.Duration) * time.Millisecond).Round(time.Second)) if !showingMessage {
m.list.NewStatusMessage(
fmt.Sprintf("Now playing %s by %s - %s %s/%s : %s",
m.playing.Item.Name,
m.playing.Item.Artists[0].Name,
m.progress.View(),
(time.Duration(m.playing.Progress) * time.Millisecond).Round(time.Second),
(time.Duration(m.playing.Item.Duration) * time.Millisecond).Round(time.Second),
m.playbackContext),
)
}
return m, cmd return m, cmd
case tea.KeyMsg: case tea.KeyMsg:
// quit // quit
@ -505,16 +656,16 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
} }
if msg.String() == ">" { if msg.String() == ">" {
go HandleSeek(m.ctx, m.client, true) go HandleSeek(m.ctx, m.commands, true)
} }
if msg.String() == "<" { if msg.String() == "<" {
go HandleSeek(m.ctx, m.client, false) go HandleSeek(m.ctx, m.commands, false)
} }
if msg.String() == "+" { if msg.String() == "+" {
go HandleVolume(m.ctx, m.client, true) go HandleVolume(m.ctx, m.commands, true)
} }
if msg.String() == "-" { if msg.String() == "-" {
go HandleVolume(m.ctx, m.client, false) go HandleVolume(m.ctx, m.commands, false)
} }
// search input // search input
if m.input.Focused() { if m.input.Focused() {
@ -531,27 +682,36 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// enter device selection // enter device selection
if msg.String() == "d" { if msg.String() == "d" {
m.mode = Devices m.mode = Devices
new_items, err := DeviceView(m.ctx, m.client) new_items, err := DeviceView(m.ctx, m.commands)
if err != nil { if err != nil {
fmt.Println(err.Error())
return m, tea.Quit return m, tea.Quit
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
m.list.NewStatusMessage("Setting view to devices")
} }
// go back // go back
if msg.String() == "backspace" || msg.String() == "esc" || msg.String() == "q" { if msg.String() == "backspace" || msg.String() == "esc" || msg.String() == "q" {
msg, err := m.GoBack() msg, err := m.GoBack()
if err != nil { if err != nil {
fmt.Println(err) return m, tea.Quit
} }
m.list.ResetSelected() m.list.ResetSelected()
return m, msg return m, msg
} }
if msg.String() == "ctrl+d" {
err := m.DeleteTrackFromPlaylist()
if err != nil {
return m, tea.Quit
}
}
if msg.String() == "ctrl+@" || msg.String() == "ctrl+p" {
err := m.QueueItem()
if err != nil {
return m, tea.Quit
}
}
// select item // select item
if msg.String() == "enter" || msg.String() == "spacebar" { if msg.String() == "enter" || msg.String() == " " || msg.String() == "p" {
err := m.SelectItem() err := m.SelectItem()
if err != nil { if err != nil {
return m, tea.Quit return m, tea.Quit
@ -583,59 +743,63 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd return m, cmd
} }
func InitMain(ctx *gctx.Context, client *spotify.Client, mode Mode) (tea.Model, error) { func InitMain(ctx *gctx.Context, c *commands.Commands, mode Mode) (tea.Model, error) {
prog := progress.New(progress.WithColorProfile(2), progress.WithoutPercentage()) prog := progress.New(progress.WithColorProfile(2), progress.WithoutPercentage())
var err error var err error
lipgloss.SetColorProfile(2) lipgloss.SetColorProfile(2)
items := []list.Item{} items := []list.Item{}
switch mode { switch mode {
case Main: case Main:
items, err = MainView(ctx, client) items, err = MainView(ctx, c)
if err != nil { if err != nil {
return nil, err return nil, err
} }
case Devices: case Devices:
items, err = DeviceView(ctx, client) items, err = DeviceView(ctx, c)
if err != nil { if err != nil {
return nil, err return nil, err
} }
case Tracks: case Tracks:
items, err = SavedTracksView(ctx, client) items, err = SavedTracksView(ctx, c)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
m := mainModel{ m := &mainModel{
list: list.New(items, list.NewDefaultDelegate(), 0, 0), list: list.New(items, list.NewDefaultDelegate(), 0, 0),
ctx: ctx, ctx: ctx,
client: client, commands: c,
mode: mode, mode: mode,
progress: prog, progress: prog,
} }
m.list.Title = "GOSPT" m.list.Title = "GOSPT"
go m.TickPlayback()
Tick() Tick()
m.list.DisableQuitKeybindings() m.list.DisableQuitKeybindings()
m.list.SetFilteringEnabled(false)
m.list.AdditionalShortHelpKeys = func() []key.Binding { m.list.AdditionalShortHelpKeys = func() []key.Binding {
return []key.Binding{ return []key.Binding{
key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")), key.NewBinding(key.WithKeys("q"), key.WithHelp("q", "back")),
key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "search")), key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")), key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "search")),
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "start radio")), key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "radio")),
key.NewBinding(key.WithKeys("ctrl"+"shift"+"c"), key.WithHelp("ctrl+shift+c", "copy url")), key.NewBinding(key.WithKeys("ctrl"+"p"), key.WithHelp("ctrl+p", "queue")),
key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")), key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")),
} }
} }
m.list.AdditionalFullHelpKeys = func() []key.Binding { m.list.AdditionalFullHelpKeys = func() []key.Binding {
return []key.Binding{ return []key.Binding{
key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")), key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")),
key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "search")), key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "search")),
key.NewBinding(key.WithKeys(">"), key.WithHelp(">", "seek forward")), key.NewBinding(key.WithKeys(">"), key.WithHelp(">", "seek forward")),
key.NewBinding(key.WithKeys("<"), key.WithHelp("<", "seek backward")), key.NewBinding(key.WithKeys("<"), key.WithHelp("<", "seek backward")),
key.NewBinding(key.WithKeys("+"), key.WithHelp("+", "volume up")), key.NewBinding(key.WithKeys("+"), key.WithHelp("+", "volume up")),
key.NewBinding(key.WithKeys("-"), key.WithHelp("-", "volume down")), key.NewBinding(key.WithKeys("-"), key.WithHelp("-", "volume down")),
key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "copy link to item")),
key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")), key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")),
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "start radio")), key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "start radio")),
key.NewBinding(key.WithKeys("ctrl"+"shift"+"c"), key.WithHelp("ctrl+shift+c", "copy url")), key.NewBinding(key.WithKeys("ctrl"+"p"), key.WithHelp("ctrl+p", "queue song")),
key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")), key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")),
} }
} }

View File

@ -1,22 +1,20 @@
package tui package tui
import ( import (
"fmt" "git.asdf.cafe/abs3nt/gospt/src/commands"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
"gospt/src/gctx"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/zmb3/spotify/v2"
) )
// StartTea the entry point for the UI. Initializes the model. // StartTea the entry point for the UI. Initializes the model.
func StartTea(ctx *gctx.Context, client *spotify.Client, mode string) error { func StartTea(ctx *gctx.Context, cmd *commands.Commands, mode string) error {
m, err := InitMain(ctx, client, Mode(mode)) m, err := InitMain(ctx, cmd, Mode(mode))
if err != nil { if err != nil {
fmt.Println("UH OH") return err
} }
P = tea.NewProgram(m, tea.WithAltScreen()) P = tea.NewProgram(m, tea.WithAltScreen())
if err := P.Start(); err != nil { if _, err := P.Run(); err != nil {
return err return err
} }
return nil return nil

View File

@ -2,19 +2,22 @@ package tui
import ( import (
"fmt" "fmt"
"sync" "regexp"
"time" "time"
"gospt/src/commands" "git.asdf.cafe/abs3nt/gospt/src/commands"
"gospt/src/gctx" "git.asdf.cafe/abs3nt/gospt/src/gctx"
"golang.org/x/sync/errgroup"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
) )
func DeviceView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) { const regex = `<.*?>`
func DeviceView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
devices, err := client.PlayerDevices(ctx) devices, err := commands.Client().PlayerDevices(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -28,28 +31,57 @@ func DeviceView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error)
return items, nil return items, nil
} }
func PlaylistView(ctx *gctx.Context, client *spotify.Client, playlist spotify.SimplePlaylist) ([]list.Item, error) { func QueueView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
tracks, err := commands.PlaylistTracks(ctx, client, playlist.ID, 1) tracks, err := commands.UserQueue(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, track := range tracks.Tracks { if tracks.CurrentlyPlaying.Name != "" {
items = append(items, mainItem{ items = append(items, mainItem{
Name: track.Track.Name, Name: tracks.CurrentlyPlaying.Name,
Artist: track.Track.Artists[0], Artist: tracks.CurrentlyPlaying.Artists[0],
Duration: track.Track.TimeDuration().Round(time.Second).String(), Duration: tracks.CurrentlyPlaying.TimeDuration().Round(time.Second).String(),
ID: track.Track.ID, ID: tracks.CurrentlyPlaying.ID,
Desc: track.Track.Artists[0].Name + " - " + track.Track.TimeDuration().Round(time.Second).String(), Desc: tracks.CurrentlyPlaying.Artists[0].Name + " - " + tracks.CurrentlyPlaying.TimeDuration().Round(time.Second).String(),
SpotifyItem: tracks.CurrentlyPlaying,
})
}
for _, track := range tracks.Items {
items = append(items, mainItem{
Name: track.Name,
Artist: track.Artists[0],
Duration: track.TimeDuration().Round(time.Second).String(),
ID: track.ID,
Desc: track.Artists[0].Name + " - " + track.TimeDuration().Round(time.Second).String(),
SpotifyItem: track, SpotifyItem: track,
}) })
} }
return items, nil return items, nil
} }
func ArtistsView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) { func PlaylistView(ctx *gctx.Context, commands *commands.Commands, playlist spotify.SimplePlaylist) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
artists, err := commands.UserArtists(ctx, client, 1) playlistItems, err := commands.PlaylistTracks(ctx, playlist.ID, 1)
if err != nil {
return nil, err
}
for _, item := range playlistItems.Items {
items = append(items, mainItem{
Name: item.Track.Track.Name,
Artist: item.Track.Track.Artists[0],
Duration: item.Track.Track.TimeDuration().Round(time.Second).String(),
ID: item.Track.Track.ID,
Desc: item.Track.Track.Artists[0].Name + " - " + item.Track.Track.TimeDuration().Round(time.Second).String(),
SpotifyItem: item,
})
}
return items, nil
}
func ArtistsView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{}
artists, err := commands.UserArtists(ctx, 1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -57,54 +89,40 @@ func ArtistsView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error)
items = append(items, mainItem{ items = append(items, mainItem{
Name: artist.Name, Name: artist.Name,
ID: artist.ID, ID: artist.ID,
Desc: fmt.Sprintf("%d followers, genres: %s, popularity: %d", artist.Followers.Count, artist.Genres, artist.Popularity), Desc: fmt.Sprintf("%d followers", artist.Followers.Count),
SpotifyItem: artist.SimpleArtist, SpotifyItem: artist.SimpleArtist,
}) })
} }
return items, nil return items, nil
} }
func SearchArtistsView(ctx *gctx.Context, client *spotify.Client, artists *spotify.FullArtistPage) ([]list.Item, error) { func SearchArtistsView(ctx *gctx.Context, commands *commands.Commands, artists *spotify.FullArtistPage) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
for _, artist := range artists.Artists { for _, artist := range artists.Artists {
items = append(items, mainItem{ items = append(items, mainItem{
Name: artist.Name, Name: artist.Name,
ID: artist.ID, ID: artist.ID,
Desc: fmt.Sprintf("%d followers, genres: %s, popularity: %d", artist.Followers.Count, artist.Genres, artist.Popularity), Desc: fmt.Sprintf("%d followers", artist.Followers.Count),
SpotifyItem: artist.SimpleArtist, SpotifyItem: artist.SimpleArtist,
}) })
} }
return items, nil return items, nil
} }
func SearchView(ctx *gctx.Context, client *spotify.Client, search string) ([]list.Item, *SearchResults, error) { func SearchView(ctx *gctx.Context, commands *commands.Commands, search string) ([]list.Item, *SearchResults, error) {
items := []list.Item{} items := []list.Item{}
result, err := commands.Search(ctx, client, search, 1) result, err := commands.Search(ctx, search, 1)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
items = append(items, mainItem{ items = append(
Name: "Tracks", items,
Desc: "Search results", mainItem{Name: "Tracks", Desc: "Search results", SpotifyItem: result.Tracks},
SpotifyItem: result.Tracks, mainItem{Name: "Albums", Desc: "Search results", SpotifyItem: result.Albums},
}) mainItem{Name: "Artists", Desc: "Search results", SpotifyItem: result.Artists},
items = append(items, mainItem{ mainItem{Name: "Playlists", Desc: "Search results", SpotifyItem: result.Playlists},
Name: "Albums", )
Desc: "Search results",
SpotifyItem: result.Albums,
})
items = append(items, mainItem{
Name: "Artists",
Desc: "Search results",
SpotifyItem: result.Artists,
})
items = append(items, mainItem{
Name: "Playlists",
Desc: "Search results",
SpotifyItem: result.Playlists,
})
results := &SearchResults{ results := &SearchResults{
Tracks: result.Tracks, Tracks: result.Tracks,
Playlists: result.Playlists, Playlists: result.Playlists,
@ -114,9 +132,9 @@ func SearchView(ctx *gctx.Context, client *spotify.Client, search string) ([]lis
return items, results, nil return items, results, nil
} }
func AlbumsView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) { func AlbumsView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
albums, err := commands.UserAlbums(ctx, client, 1) albums, err := commands.UserAlbums(ctx, 1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -124,41 +142,41 @@ func AlbumsView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error)
items = append(items, mainItem{ items = append(items, mainItem{
Name: album.Name, Name: album.Name,
ID: album.ID, ID: album.ID,
Desc: fmt.Sprintf("%s, %d tracks", album.Artists[0].Name, album.Tracks.Total), Desc: fmt.Sprintf("%s by %s, %d tracks, released %d", album.AlbumType, album.Artists[0].Name, album.Tracks.Total, album.ReleaseDateTime().Year()),
SpotifyItem: album.SimpleAlbum, SpotifyItem: album.SimpleAlbum,
}) })
} }
return items, nil return items, nil
} }
func SearchPlaylistsView(ctx *gctx.Context, client *spotify.Client, playlists *spotify.SimplePlaylistPage) ([]list.Item, error) { func SearchPlaylistsView(ctx *gctx.Context, commands *commands.Commands, playlists *spotify.SimplePlaylistPage) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
for _, playlist := range playlists.Playlists { for _, playlist := range playlists.Playlists {
items = append(items, mainItem{ items = append(items, mainItem{
Name: playlist.Name, Name: playlist.Name,
Desc: playlist.Description, Desc: stripHtmlRegex(playlist.Description),
SpotifyItem: playlist, SpotifyItem: playlist,
}) })
} }
return items, nil return items, nil
} }
func SearchAlbumsView(ctx *gctx.Context, client *spotify.Client, albums *spotify.SimpleAlbumPage) ([]list.Item, error) { func SearchAlbumsView(ctx *gctx.Context, commands *commands.Commands, albums *spotify.SimpleAlbumPage) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
for _, album := range albums.Albums { for _, album := range albums.Albums {
items = append(items, mainItem{ items = append(items, mainItem{
Name: album.Name, Name: album.Name,
ID: album.ID, ID: album.ID,
Desc: fmt.Sprintf("%s, %d", album.Artists[0].Name, album.ReleaseDateTime()), Desc: fmt.Sprintf("%s by %s, released %d", album.AlbumType, album.Artists[0].Name, album.ReleaseDateTime().Year()),
SpotifyItem: album, SpotifyItem: album,
}) })
} }
return items, nil return items, nil
} }
func ArtistAlbumsView(ctx *gctx.Context, album spotify.ID, client *spotify.Client) ([]list.Item, error) { func ArtistAlbumsView(ctx *gctx.Context, album spotify.ID, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
albums, err := commands.ArtistAlbums(ctx, client, album, 1) albums, err := commands.ArtistAlbums(ctx, album, 1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -173,9 +191,9 @@ func ArtistAlbumsView(ctx *gctx.Context, album spotify.ID, client *spotify.Clien
return items, err return items, err
} }
func AlbumTracksView(ctx *gctx.Context, album spotify.ID, client *spotify.Client) ([]list.Item, error) { func AlbumTracksView(ctx *gctx.Context, album spotify.ID, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
tracks, err := commands.AlbumTracks(ctx, client, album, 1) tracks, err := commands.AlbumTracks(ctx, album, 1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -192,7 +210,7 @@ func AlbumTracksView(ctx *gctx.Context, album spotify.ID, client *spotify.Client
return items, err return items, err
} }
func SearchTracksView(ctx *gctx.Context, client *spotify.Client, tracks *spotify.FullTrackPage) ([]list.Item, error) { func SearchTracksView(ctx *gctx.Context, commands *commands.Commands, tracks *spotify.FullTrackPage) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
for _, track := range tracks.Tracks { for _, track := range tracks.Tracks {
items = append(items, mainItem{ items = append(items, mainItem{
@ -207,9 +225,9 @@ func SearchTracksView(ctx *gctx.Context, client *spotify.Client, tracks *spotify
return items, nil return items, nil
} }
func SavedTracksView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) { func SavedTracksView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
tracks, err := commands.TrackList(ctx, client, 1) tracks, err := commands.TrackList(ctx, 1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -226,58 +244,37 @@ func SavedTracksView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, er
return items, err return items, err
} }
func MainView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) { func MainView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
var wg sync.WaitGroup wg := errgroup.Group{}
var saved_items *spotify.SavedTrackPage var saved_items *spotify.SavedTrackPage
var playlists *spotify.SimplePlaylistPage var playlists *spotify.SimplePlaylistPage
var artists *spotify.FullArtistCursorPage var artists *spotify.FullArtistCursorPage
var albums *spotify.SavedAlbumPage var albums *spotify.SavedAlbumPage
wg.Add(1) wg.Go(func() (err error) {
go func() { saved_items, err = commands.TrackList(ctx, 1)
defer wg.Done()
var err error
saved_items, err = commands.TrackList(ctx, client, 1)
if err != nil {
fmt.Println(err.Error())
return return
} })
}()
wg.Add(1) wg.Go(func() (err error) {
go func() { playlists, err = commands.Playlists(ctx, 1)
defer wg.Done()
var err error
playlists, err = commands.Playlists(ctx, client, 1)
if err != nil {
fmt.Println(err.Error())
return return
} })
}()
wg.Add(1) wg.Go(func() (err error) {
go func() { artists, err = commands.UserArtists(ctx, 1)
defer wg.Done()
var err error
artists, err = commands.UserArtists(ctx, client, 1)
if err != nil {
fmt.Println(err.Error())
return return
} })
}()
wg.Add(1) wg.Go(func() (err error) {
go func() { albums, err = commands.UserAlbums(ctx, 1)
defer wg.Done()
var err error
albums, err = commands.UserAlbums(ctx, client, 1)
if err != nil {
fmt.Println(err.Error())
return return
} })
}()
wg.Wait() err := wg.Wait()
if err != nil {
return nil, err
}
items := []list.Item{} items := []list.Item{}
if saved_items != nil && saved_items.Total != 0 { if saved_items != nil && saved_items.Total != 0 {
@ -301,14 +298,24 @@ func MainView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) {
SpotifyItem: artists, SpotifyItem: artists,
}) })
} }
items = append(items, mainItem{
Name: "Queue",
Desc: "Your Current Queue",
SpotifyItem: spotify.Queue{},
})
if playlists != nil && playlists.Total != 0 { if playlists != nil && playlists.Total != 0 {
for _, playlist := range playlists.Playlists { for _, playlist := range playlists.Playlists {
items = append(items, mainItem{ items = append(items, mainItem{
Name: playlist.Name, Name: playlist.Name,
Desc: playlist.Description, Desc: stripHtmlRegex(playlist.Description),
SpotifyItem: playlist, SpotifyItem: playlist,
}) })
} }
} }
return items, nil return items, nil
} }
func stripHtmlRegex(s string) string {
r := regexp.MustCompile(regex)
return r.ReplaceAllString(s, "")
}

115
src/youtube/youtube.go Normal file
View File

@ -0,0 +1,115 @@
package youtube
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
"os/user"
"path/filepath"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"
"google.golang.org/api/youtube/v3"
)
func getClient(ctx context.Context, config *oauth2.Config) *http.Client {
cacheFile, err := tokenCacheFile()
if err != nil {
log.Fatalf("Unable to get path to cached credential file. %v", err)
}
tok, err := tokenFromFile(cacheFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(cacheFile, tok)
}
return config.Client(ctx, tok)
}
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser then type the "+
"authorization code: \n%v\n", authURL)
var code string
if _, err := fmt.Scan(&code); err != nil {
log.Fatalf("Unable to read authorization code %v", err)
}
tok, err := config.Exchange(context.Background(), code)
if err != nil {
log.Fatalf("Unable to retrieve token from web %v", err)
}
return tok
}
func tokenCacheFile() (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
}
tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials")
err = os.MkdirAll(tokenCacheDir, 0o700)
if err != nil {
return "", err
}
return filepath.Join(tokenCacheDir,
url.QueryEscape("youtube-go-quickstart.json")), err
}
func tokenFromFile(file string) (*oauth2.Token, error) {
f, err := os.Open(file)
handleError(err, "Error opening file")
t := &oauth2.Token{}
err = json.NewDecoder(f).Decode(t)
defer f.Close()
return t, err
}
func saveToken(file string, token *oauth2.Token) {
fmt.Printf("Saving credential file to: %s\n", file)
f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
if err != nil {
log.Fatalf("Unable to cache oauth token: %v", err)
}
defer f.Close()
err = json.NewEncoder(f).Encode(token)
handleError(err, "Error encoding token")
}
func handleError(err error, message string) {
if message == "" {
message = "Error making API call"
}
if err != nil {
log.Fatalf(message+": %v", err.Error())
}
}
func Search(query string) string {
ctx := context.Background()
confDir, _ := os.UserConfigDir()
b, err := os.ReadFile(filepath.Join(confDir, "gospt", "client_secret.json"))
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
config, err := google.ConfigFromJSON(b, youtube.YoutubeReadonlyScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(ctx, config)
service, err := youtube.NewService(ctx, option.WithHTTPClient(client))
handleError(err, "Error creating YouTube client")
call := service.Search.List([]string{"snippet"})
call.Q(query)
response, err := call.Do()
handleError(err, "")
return fmt.Sprintf("https://www.youtube.com/watch?v=%s", response.Items[0].Id.VideoId)
}