Compare commits
No commits in common. "main" and "v0.0.27" have entirely different histories.
34
.drone.yml
Normal file
34
.drone.yml
Normal file
@ -0,0 +1,34 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: golang:1.19
|
||||
commands:
|
||||
- go build -o gospt
|
||||
- ./gospt completion zsh > gospt_zsh
|
||||
- ./gospt completion bash > gospt_bash
|
||||
- ./gospt completion fish > gospt_fish
|
||||
- name: gitea_release
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
api_key:
|
||||
from_secret:
|
||||
drone_releases
|
||||
base_url: https://gitea.asdf.cafe
|
||||
files:
|
||||
- gospt
|
||||
- gospt_zsh
|
||||
- gospt_bash
|
||||
- gospt_fish
|
||||
checksum: sha256
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
ref:
|
||||
- refs/tags/v*
|
17
.gitignore
vendored
17
.gitignore
vendored
@ -1,17 +1,2 @@
|
||||
bin/
|
||||
bin/*
|
||||
/gospt
|
||||
gospt_zsh
|
||||
gospt_bash
|
||||
gospt_fish
|
||||
completions
|
||||
|
||||
.idea/*
|
||||
.idea
|
||||
|
||||
*.log
|
||||
*.out
|
||||
|
||||
*.tmp
|
||||
|
||||
dist/
|
||||
gospt
|
||||
|
@ -1,87 +0,0 @@
|
||||
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)"
|
@ -1,61 +0,0 @@
|
||||
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
|
@ -1,18 +0,0 @@
|
||||
steps:
|
||||
build:
|
||||
image: golang:1.22
|
||||
commands:
|
||||
- go mod tidy
|
||||
- go build -o gospt
|
||||
- mkdir completions
|
||||
- ./gospt completion zsh > completions/gospt_zsh
|
||||
- ./gospt completion bash > completions/gospt_bash
|
||||
- ./gospt completion fish > completions/gospt_fish
|
||||
|
||||
publish:
|
||||
image: goreleaser/goreleaser
|
||||
commands:
|
||||
- goreleaser release --clean
|
||||
secrets: [ gitea_token ]
|
||||
when:
|
||||
event: tag
|
27
Makefile
27
Makefile
@ -1,13 +1,12 @@
|
||||
build: gospt
|
||||
|
||||
gospt: $(shell find . -name '*.go')
|
||||
go build -o gospt .
|
||||
build:
|
||||
mkdir -p bin
|
||||
go build -o ./bin/gospt .
|
||||
|
||||
completions:
|
||||
mkdir -p completions
|
||||
gospt completion zsh > completions/_gospt
|
||||
gospt completion bash > completions/gospt
|
||||
gospt completion fish > completions/gospt.fish
|
||||
bin/gospt completion zsh > completions/_gospt
|
||||
bin/gospt completion bash > completions/gospt
|
||||
bin/gospt completion fish > completions/gospt.fish
|
||||
|
||||
run:
|
||||
go run main.go
|
||||
@ -16,17 +15,15 @@ tidy:
|
||||
go mod tidy
|
||||
|
||||
clean:
|
||||
rm -f gospt
|
||||
rm -rf bin
|
||||
rm -rf completions
|
||||
|
||||
uninstall:
|
||||
rm -f /usr/bin/gospt
|
||||
rm -f /usr/share/zsh/site-functions/_gospt
|
||||
rm -f /usr/share/bash-completion/completions/gospt
|
||||
rm -f /usr/share/fish/vendor_completions.d/gospt.fish
|
||||
rm /usr/share/zsh/site-functions/_gospt
|
||||
|
||||
install:
|
||||
cp gospt /usr/bin
|
||||
gospt completion zsh > /usr/share/zsh/site-functions/_gospt
|
||||
gospt completion bash > /usr/share/bash-completion/completions/gospt
|
||||
gospt completion fish > /usr/share/fish/vendor_completions.d/gospt.fish
|
||||
cp bin/gospt /usr/bin
|
||||
cp completions/_gospt /usr/share/zsh/site-functions/_gospt
|
||||
cp completions/gospt /usr/share/bash-completion/completions/gospt
|
||||
cp completions/gospt.fish /usr/share/fish/vendor_completions.d/gospt.fish
|
||||
|
39
README.md
39
README.md
@ -1,40 +1,29 @@
|
||||
IF YOU ARE ON GITHUB.COM GO HERE INSTEAD: https://git.asdf.cafe/abs3nt/gospt :)
|
||||
IF YOU ARE ON GITHUB.COM GO HERE INSTEAD: https://gitea.asdf.cafe/abs3nt/gospt :)
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
Project Discord: [Join Here](https://discord.gg/nWEEK6HrUD)
|
||||
---
|
||||
[![status-badge](https://ci.asdf.cafe/api/badges/abs3nt/gospt/status.svg)](https://ci.asdf.cafe/abs3nt/gospt)
|
||||
![video](/assets/gospt.gif)
|
||||
![picture](/assets/gospt.png)
|
||||
![video](/assets/gost.webm)
|
||||
|
||||
# To install (with a package manager):
|
||||
to install:
|
||||
|
||||
## Archlinux ([AUR])
|
||||
```yay -S gospt```
|
||||
```yay -S gospt```
|
||||
|
||||
or
|
||||
or to build from source by pulling and building the binary
|
||||
|
||||
```yay -S gospt-git```
|
||||
|
||||
## NetBSD ([Official repositories])
|
||||
```pkgin install gospt```
|
||||
|
||||
# To build from source by pulling and building the binary
|
||||
|
||||
|
||||
```git clone https://git.asdf.cafe/abs3nt/gospt```
|
||||
```git clone https://gitea.asdf.cafe/abs3nt/gospt```
|
||||
|
||||
```cd gospt```
|
||||
|
||||
```make build```
|
||||
then
|
||||
|
||||
```make build && sudo make install```
|
||||
```sudo make install```
|
||||
|
||||
[AUR]: https://aur.archlinux.org/packages/gospt
|
||||
[Official repositories]: http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/audio/gospt/
|
||||
|
||||
# Configuration
|
||||
go here https://developer.spotify.com/dashboard/applications to make a spotify application. you will need a client ID and a client secret. Set your redirect uri like this:
|
||||
|
||||
```http://localhost:8888/callback```
|
||||
@ -77,7 +66,3 @@ To view help:
|
||||
```gospt --help```
|
||||
|
||||
Very open to contributations feel free to open a PR
|
||||
|
||||
[tmux plugin](https://git.asdf.cafe/abs3nt/tmux-gospt)
|
||||
|
||||
[wiki](https://git.asdf.cafe/abs3nt/gospt/wiki)
|
||||
|
BIN
assets/gospt.gif
BIN
assets/gospt.gif
Binary file not shown.
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 670 KiB |
BIN
assets/gospt.mp4
Normal file
BIN
assets/gospt.mp4
Normal file
Binary file not shown.
BIN
assets/gost.webm
Normal file
BIN
assets/gost.webm
Normal file
Binary file not shown.
@ -1,20 +0,0 @@
|
||||
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)
|
||||
},
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// linkCmd represents the link command
|
||||
var linkContextCmd = &cobra.Command{
|
||||
Use: "linkcontext",
|
||||
Aliases: []string{"lc"},
|
||||
Short: "Get url to current context(album, playlist)",
|
||||
Long: `Get url to current context(album, playlist)`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
link, err := commands.LinkContext(ctx)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Print(link)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(linkContextCmd)
|
||||
}
|
30
cmd/next.go
30
cmd/next.go
@ -1,30 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(nextCmd)
|
||||
}
|
||||
|
||||
var nextCmd = &cobra.Command{
|
||||
Use: "next {amount}",
|
||||
Aliases: []string{"n", "skip"},
|
||||
Args: cobra.MatchAll(cobra.RangeArgs(0, 1)),
|
||||
Short: "Skip to next song or skip the specified number of tracks",
|
||||
Long: `Skip to next song of skip the specified number of tracks`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
skipAmt := 1
|
||||
if len(args) >= 1 {
|
||||
var err error
|
||||
skipAmt, err = strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return commands.Next(ctx, skipAmt, false)
|
||||
},
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(nowPlayingCmd)
|
||||
}
|
||||
|
||||
var nowPlayingCmd = &cobra.Command{
|
||||
Use: "nowplaying",
|
||||
Aliases: []string{"now"},
|
||||
Short: "Shows song and artist of currently playing song",
|
||||
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) {
|
||||
commands.NowPlaying(ctx, args)
|
||||
},
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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)
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
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)
|
||||
}
|
93
go.mod
93
go.mod
@ -1,83 +1,40 @@
|
||||
module git.asdf.cafe/abs3nt/gospt
|
||||
module gospt
|
||||
|
||||
go 1.21
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.26.6
|
||||
github.com/charmbracelet/lipgloss v0.12.1
|
||||
github.com/cristalhq/aconfig v0.18.5
|
||||
github.com/charmbracelet/bubbles v0.14.0
|
||||
github.com/charmbracelet/bubbletea v0.23.1
|
||||
github.com/charmbracelet/lipgloss v0.6.0
|
||||
github.com/cristalhq/aconfig v0.18.3
|
||||
github.com/cristalhq/aconfig/aconfigyaml v0.17.1
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/zmb3/spotify/v2 v2.4.2
|
||||
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
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/zmb3/spotify/v2 v2.3.1
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5
|
||||
)
|
||||
|
||||
require (
|
||||
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/x/ansi v0.1.4 // 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/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/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/muesli/termenv v0.13.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // 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
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.27.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
|
||||
)
|
||||
|
334
go.sum
334
go.sum
@ -13,29 +13,12 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go 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.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v1.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.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
@ -52,82 +35,44 @@ 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/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/charmbracelet/bubbletea v0.26.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/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og=
|
||||
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
|
||||
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
|
||||
github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck=
|
||||
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
|
||||
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
|
||||
github.com/charmbracelet/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/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
|
||||
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
|
||||
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/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/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
github.com/cristalhq/aconfig v0.18.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
|
||||
github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
github.com/cristalhq/aconfig v0.18.3 h1:Or12LIWIF+2mQpcGWA2PQnNc55+WiHFAqRjYh/pQNtM=
|
||||
github.com/cristalhq/aconfig v0.18.3/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/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.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.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/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/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.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/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-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.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
@ -148,13 +93,9 @@ 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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
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.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@ -164,10 +105,8 @@ 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.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.3/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.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/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=
|
||||
@ -177,29 +116,13 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/s2a-go v0.1.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.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.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/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/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
@ -214,99 +137,61 @@ 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/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-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.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.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
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/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/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
|
||||
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
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/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/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
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/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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
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.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zmb3/spotify/v2 v2.4.2 h1:j3yNN5lKVEMZQItJF4MHCSZbfNWmXO+KaC+3RFaLlLc=
|
||||
github.com/zmb3/spotify/v2 v2.4.2/go.mod h1:XOV7BrThayFYB9AAfB+L0Q0wyxBuLCARk4fI/ZXCBW8=
|
||||
github.com/zmb3/spotify/v2 v2.3.1 h1:aEyIPotROM3JJjHMCImFROgnPIUpzVo8wymYSaPSd9w=
|
||||
github.com/zmb3/spotify/v2 v2.3.1/go.mod h1:+LVh9CafHu7SedyqYmEf12Rd01dIVlEL845yNhksW0E=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -337,10 +222,6 @@ 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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -367,28 +248,15 @@ 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-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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-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/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 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.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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -397,12 +265,6 @@ 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-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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -428,53 +290,26 @@ 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-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-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-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-20210630005230-0f9fa26af87c/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-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/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-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
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-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.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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -518,13 +353,10 @@ 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-20200804011535-6c149bb5ef0d/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-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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
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.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
@ -542,21 +374,14 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.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.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
@ -586,16 +411,6 @@ 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-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-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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@ -608,13 +423,6 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -627,10 +435,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
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=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@ -646,36 +452,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
modernc.org/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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
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=
|
||||
|
2
main.go
2
main.go
@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.asdf.cafe/abs3nt/gospt/cmd"
|
||||
"gospt/src/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["config:recommended"]
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"tuxpa.in/a/zlog/log"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt/src/config"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"gospt/src/config"
|
||||
"gospt/src/gctx"
|
||||
|
||||
"github.com/zmb3/spotify/v2"
|
||||
spotifyauth "github.com/zmb3/spotify/v2/auth"
|
||||
@ -27,14 +25,9 @@ var (
|
||||
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) {
|
||||
if config.Values.ClientId == "" || config.Values.ClientSecret == "" || config.Values.Port == "" {
|
||||
configDir, _ := os.UserConfigDir()
|
||||
fmt.Println("PLEASE WRITE YOUR CONFIG FILE IN", filepath.Join(configDir, "gospt/client.yml"))
|
||||
fmt.Println("GO HERE TO AND MAKE AN APPLICATION: https://developer.spotify.com/dashboard/applications")
|
||||
fmt.Print("\nclient_id: \"idgoesherelikethis\"\nclient_secret: \"secretgoesherelikethis\"\nport:\"8888\"\n\n")
|
||||
@ -65,36 +58,42 @@ func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
|
||||
),
|
||||
)
|
||||
if _, err := os.Stat(filepath.Join(configDir, "gospt/auth.json")); err == nil {
|
||||
authFilePath := filepath.Join(configDir, "gospt/auth.json")
|
||||
authFile, err := os.Open(authFilePath)
|
||||
authFile, err := os.Open(filepath.Join(configDir, "gospt/auth.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer authFile.Close()
|
||||
tok := &oauth2.Token{}
|
||||
err = json.NewDecoder(authFile).Decode(tok)
|
||||
authValue, err := ioutil.ReadAll(authFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx.Context = context.WithValue(ctx.Context, oauth2.HTTPClient, &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
log.Trace().Interface("path", r.URL.Path).Msg("request")
|
||||
return http.DefaultTransport.RoundTrip(r)
|
||||
}),
|
||||
})
|
||||
authClient := auth.Client(ctx, tok)
|
||||
client := spotify.New(authClient)
|
||||
var tok *oauth2.Token
|
||||
err = json.Unmarshal(authValue, &tok)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := spotify.New(auth.Client(ctx, tok))
|
||||
new_token, err := client.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := json.MarshalIndent(new_token, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if new_token != tok {
|
||||
out, err := json.MarshalIndent(new_token, "", " ")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o644)
|
||||
if err != nil {
|
||||
panic("FAILED TO SAVE AUTH")
|
||||
}
|
||||
}
|
||||
err = os.WriteFile(authFilePath, out, 0o600)
|
||||
out, err := json.MarshalIndent(tok, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to save auth")
|
||||
panic(err.Error())
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o644)
|
||||
if err != nil {
|
||||
panic("FAILED TO SAVE AUTH")
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
@ -103,12 +102,11 @@ func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("Got request for:", r.URL.String())
|
||||
})
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%s", config.Values.Port),
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
}
|
||||
go func() {
|
||||
server.ListenAndServe()
|
||||
err := http.ListenAndServe(fmt.Sprintf(":%s", config.Values.Port), nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
url := auth.AuthURL(state)
|
||||
fmt.Println(url)
|
||||
@ -117,7 +115,6 @@ func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
|
||||
// wait for auth to complete
|
||||
client := <-ch
|
||||
|
||||
server.Shutdown(ctx)
|
||||
// use the client to make calls that require authorization
|
||||
user, err := client.CurrentUser(ctx)
|
||||
if err != nil {
|
||||
@ -131,6 +128,7 @@ func completeAuth(w http.ResponseWriter, r *http.Request) {
|
||||
tok, err := auth.Token(r.Context(), state, r)
|
||||
if err != nil {
|
||||
http.Error(w, "Couldn't get token", http.StatusForbidden)
|
||||
log.Fatal(err)
|
||||
}
|
||||
if st := r.FormValue("state"); st != state {
|
||||
http.NotFound(w, r)
|
||||
@ -140,7 +138,7 @@ func completeAuth(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o600)
|
||||
err = ioutil.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o644)
|
||||
if err != nil {
|
||||
panic("FAILED TO SAVE AUTH")
|
||||
}
|
||||
|
98
src/cache/cache.go
vendored
98
src/cache/cache.go
vendored
@ -1,98 +0,0 @@
|
||||
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)
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -13,6 +15,6 @@ var clearRadioCmd = &cobra.Command{
|
||||
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`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.ClearRadio(ctx)
|
||||
commands.ClearRadio(ctx, client)
|
||||
},
|
||||
}
|
@ -43,7 +43,7 @@ fish:
|
||||
$ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
|
||||
`, rootCmd.Name()),
|
||||
DisableFlagsInUseLine: true,
|
||||
ValidArgs: []string{"bash", "zsh", "fish"},
|
||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
switch args[0] {
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -13,6 +15,6 @@ var devicesCmd = &cobra.Command{
|
||||
Short: "Prints out devices",
|
||||
Long: `Prints out devices`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Devices(ctx)
|
||||
commands.Devices(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -14,6 +16,6 @@ var likeCmd = &cobra.Command{
|
||||
Short: "Likes song",
|
||||
Long: `Likes song`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Like(ctx)
|
||||
commands.Like(ctx, client)
|
||||
},
|
||||
}
|
@ -4,6 +4,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -14,12 +16,12 @@ var linkCmd = &cobra.Command{
|
||||
Short: "Print link to currently playing song",
|
||||
Long: `Print link to currently playing song`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
link, err := commands.Link(ctx)
|
||||
link, err := commands.Link(ctx, client)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println(link)
|
||||
fmt.Print(link)
|
||||
},
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -13,7 +15,7 @@ var muteCmd = &cobra.Command{
|
||||
Short: "mutes playback",
|
||||
Long: `Mutes the spotify device, playback will continue`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := commands.SetVolume(ctx, 0)
|
||||
err := commands.SetVolume(ctx, client, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
21
src/cmd/next.go
Normal file
21
src/cmd/next.go
Normal file
@ -0,0 +1,21 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(nextCmd)
|
||||
}
|
||||
|
||||
var nextCmd = &cobra.Command{
|
||||
Use: "next",
|
||||
Aliases: []string{"n"},
|
||||
Short: "Skip to next song",
|
||||
Long: `Skip to next song`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Next(ctx, client)
|
||||
},
|
||||
}
|
20
src/cmd/nowplaying.go
Normal file
20
src/cmd/nowplaying.go
Normal file
@ -0,0 +1,20 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(nowPlayingCmd)
|
||||
}
|
||||
|
||||
var nowPlayingCmd = &cobra.Command{
|
||||
Use: "nowplaying",
|
||||
Short: "Shows song and artist of currently playing song",
|
||||
Long: `Shows song and artist of currently playing song, useful for scripting`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.NowPlaying(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -9,11 +11,10 @@ func init() {
|
||||
}
|
||||
|
||||
var pauseCmd = &cobra.Command{
|
||||
Use: "pause",
|
||||
Short: "Pauses spotify",
|
||||
Aliases: []string{"pa"},
|
||||
Long: `Pauses currently playing song on spotify`,
|
||||
Use: "pause",
|
||||
Short: "Pauses spotify",
|
||||
Long: `Pauses currently playing song on spotify`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Pause(ctx)
|
||||
commands.Pause(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -10,10 +12,10 @@ func init() {
|
||||
|
||||
var playCmd = &cobra.Command{
|
||||
Use: "play",
|
||||
Aliases: []string{"pl", "start", "s"},
|
||||
Aliases: []string{"p"},
|
||||
Short: "Plays spotify",
|
||||
Long: `Plays queued song on spotify, uses last used device and activates it if needed`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Play(ctx)
|
||||
commands.Play(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -14,6 +16,6 @@ var playurlCmd = &cobra.Command{
|
||||
Args: cobra.MatchAll(cobra.ExactArgs(1)),
|
||||
Long: `Plays song from provided url`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.PlayUrl(ctx, args)
|
||||
commands.PlayUrl(ctx, client, args)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -10,10 +12,10 @@ func init() {
|
||||
|
||||
var previousCmd = &cobra.Command{
|
||||
Use: "previous",
|
||||
Aliases: []string{"b", "prev", "back"},
|
||||
Aliases: []string{"b"},
|
||||
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`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Previous(ctx)
|
||||
commands.Previous(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -14,6 +16,6 @@ var radioCmd = &cobra.Command{
|
||||
Short: "Starts radio",
|
||||
Long: `Starts radio`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return commands.Radio(ctx)
|
||||
return commands.Radio(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -13,7 +15,7 @@ var refillRadioCmd = &cobra.Command{
|
||||
Aliases: []string{"rr"},
|
||||
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`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return commands.RefillRadio(ctx)
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.RefillRadio(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -13,6 +15,6 @@ var repeatCmd = &cobra.Command{
|
||||
Short: "Toggles repeat",
|
||||
Long: `Switches between repeating your current context or not, spotifyd does not support single track loops`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Repeat(ctx)
|
||||
commands.Repeat(ctx, client)
|
||||
},
|
||||
}
|
@ -8,23 +8,22 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
cmds "git.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt/src/config"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"tuxpa.in/a/zlog"
|
||||
"gospt/src/auth"
|
||||
"gospt/src/config"
|
||||
"gospt/src/gctx"
|
||||
|
||||
"github.com/cristalhq/aconfig"
|
||||
"github.com/cristalhq/aconfig/aconfigyaml"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// Used for flags.
|
||||
ctx *gctx.Context
|
||||
commands *cmds.Commands
|
||||
cfgFile string
|
||||
verbose bool
|
||||
ctx *gctx.Context
|
||||
client *spotify.Client
|
||||
cfgFile string
|
||||
userLicense string
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "gospt",
|
||||
@ -46,21 +45,27 @@ func Execute(defCmd string) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
zlog.SetGlobalLevel(zlog.DebugLevel)
|
||||
if len(os.Args) > 1 {
|
||||
if os.Args[1] == "completion" || os.Args[1] == "__complete" {
|
||||
return
|
||||
}
|
||||
}
|
||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "enable verbose logging")
|
||||
cobra.OnInitialize(func() {
|
||||
if verbose {
|
||||
zlog.SetGlobalLevel(zlog.TraceLevel)
|
||||
}
|
||||
})
|
||||
ctx = gctx.NewContext(context.Background())
|
||||
commands = &cmds.Commands{Context: ctx}
|
||||
cobra.OnInitialize(initConfig)
|
||||
cobra.OnInitialize(initClient)
|
||||
}
|
||||
|
||||
func initClient() {
|
||||
var err error
|
||||
ctx = gctx.NewContext(context.Background())
|
||||
client, err = auth.GetClient(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
currentUser, err := client.CurrentUser(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx.UserId = currentUser.ID
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
@ -89,12 +94,7 @@ func initConfig() {
|
||||
}
|
||||
if config.Values.ClientSecretCmd != "" {
|
||||
args := strings.Fields(config.Values.ClientSecretCmd)
|
||||
cmd := args[0]
|
||||
secret_command := exec.Command(cmd)
|
||||
if len(args) > 1 {
|
||||
secret_command.Args = args
|
||||
}
|
||||
secret, err := secret_command.Output()
|
||||
secret, err := exec.Command(args[0], args[1:]...).Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
@ -3,6 +3,8 @@ package cmd
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -18,7 +20,7 @@ var seekCmd = &cobra.Command{
|
||||
Long: `Seeks forward or backward, or seeks to a given position in seconds`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if args[0] == "forward" || args[0] == "f" {
|
||||
err := commands.Seek(ctx, true)
|
||||
err := commands.Seek(ctx, client, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -26,7 +28,7 @@ var seekCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if args[0] == "backward" || args[0] == "b" {
|
||||
err := commands.Seek(ctx, false)
|
||||
err := commands.Seek(ctx, client, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -38,7 +40,7 @@ var seekCmd = &cobra.Command{
|
||||
return err
|
||||
}
|
||||
pos = pos * 1000
|
||||
err = commands.SetPosition(ctx, pos)
|
||||
err = commands.SetPosition(ctx, client, pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"git.asdf.cafe/abs3nt/gospt/src/tui"
|
||||
"gospt/src/tui"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -15,6 +15,6 @@ var setDeviceCmd = &cobra.Command{
|
||||
Short: "Shows tui to pick active device",
|
||||
Long: `Allows setting or changing the active spotify device, shown in a tui`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
tui.StartTea(ctx, commands, "devices")
|
||||
tui.StartTea(ctx, client, "devices")
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -13,6 +15,6 @@ var shuffleCmd = &cobra.Command{
|
||||
Short: "Toggles shuffle",
|
||||
Long: `Enables shuffle if it is currently disabled or disables it if it is currently active`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Shuffle(ctx)
|
||||
commands.Shuffle(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -13,6 +15,6 @@ var statusCmd = &cobra.Command{
|
||||
Short: "Returns player status in json",
|
||||
Long: `Returns all player status in json, useful for scripting`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Status(ctx)
|
||||
commands.Status(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -14,6 +16,6 @@ var togglePlayCmd = &cobra.Command{
|
||||
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`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.TogglePlay(ctx)
|
||||
commands.TogglePlay(ctx, client)
|
||||
},
|
||||
}
|
@ -4,7 +4,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt/src/tui"
|
||||
"gospt/src/commands"
|
||||
"gospt/src/tui"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -19,12 +20,12 @@ var tracksCmd = &cobra.Command{
|
||||
Long: `Uses TUI to open a list of saved tracks`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
configDir, _ := os.UserConfigDir()
|
||||
if commands.ActiveDeviceExists(ctx) {
|
||||
return tui.StartTea(ctx, commands, "tracks")
|
||||
if commands.ActiveDeviceExists(ctx, client) {
|
||||
return tui.StartTea(ctx, client, "tracks")
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err != nil {
|
||||
return tui.StartTea(ctx, commands, "devices")
|
||||
return tui.StartTea(ctx, client, "devices")
|
||||
}
|
||||
return tui.StartTea(ctx, commands, "tracks")
|
||||
return tui.StartTea(ctx, client, "tracks")
|
||||
},
|
||||
}
|
@ -4,7 +4,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt/src/tui"
|
||||
"gospt/src/commands"
|
||||
"gospt/src/tui"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -19,12 +20,12 @@ var tuiCmd = &cobra.Command{
|
||||
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 {
|
||||
configDir, _ := os.UserConfigDir()
|
||||
if commands.ActiveDeviceExists(ctx) {
|
||||
return tui.StartTea(ctx, commands, "main")
|
||||
if commands.ActiveDeviceExists(ctx, client) {
|
||||
return tui.StartTea(ctx, client, "main")
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err != nil {
|
||||
return tui.StartTea(ctx, commands, "devices")
|
||||
return tui.StartTea(ctx, client, "devices")
|
||||
}
|
||||
return tui.StartTea(ctx, commands, "main")
|
||||
return tui.StartTea(ctx, client, "main")
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -14,6 +16,6 @@ var unlikeCmd = &cobra.Command{
|
||||
Short: "unlikes song",
|
||||
Long: `unlikes song`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.Unlike(ctx)
|
||||
commands.Unlike(ctx, client)
|
||||
},
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -13,7 +15,7 @@ var unmuteCmd = &cobra.Command{
|
||||
Short: "unmutes playback",
|
||||
Long: `unmutes the spotify device, playback will continue`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := commands.SetVolume(ctx, 100)
|
||||
err := commands.SetVolume(ctx, client, 100)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
@ -3,6 +3,8 @@ package cmd
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -18,7 +20,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`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if args[0] == "up" {
|
||||
err := commands.ChangeVolume(ctx, 5)
|
||||
err := commands.ChangeVolume(ctx, client, 5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -26,7 +28,7 @@ var volumeCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
if args[0] == "down" {
|
||||
err := commands.ChangeVolume(ctx, -5)
|
||||
err := commands.ChangeVolume(ctx, client, -5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -37,7 +39,7 @@ var volumeCmd = &cobra.Command{
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = commands.SetVolume(ctx, vol)
|
||||
err = commands.SetVolume(ctx, client, vol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -2,32 +2,23 @@ package gctx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"tuxpa.in/a/zlog"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
zlog.Logger
|
||||
Debug zlog.Logger
|
||||
|
||||
context.Context
|
||||
}
|
||||
|
||||
func (c *Context) Err() error {
|
||||
return c.Context.Err()
|
||||
}
|
||||
|
||||
func (c *Context) Println(args ...any) {
|
||||
c.Info().Msg(fmt.Sprint(args...))
|
||||
*log.Logger
|
||||
Debug *log.Logger
|
||||
UserId string
|
||||
}
|
||||
|
||||
func NewContext(ctx context.Context) *Context {
|
||||
out := &Context{
|
||||
Context: ctx,
|
||||
Logger: zlog.New(os.Stderr),
|
||||
Debug: zlog.New(os.Stderr),
|
||||
ctx,
|
||||
log.New(os.Stdout, "LOG:", 0),
|
||||
log.New(os.Stdout, "DEBUG:", 1),
|
||||
"",
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
@ -1,121 +1,109 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/zmb3/spotify/v2"
|
||||
"fmt"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"gospt/src/commands"
|
||||
"gospt/src/gctx"
|
||||
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
func HandlePlayWithContext(ctx *gctx.Context, commands *commands.Commands, uri *spotify.URI, pos *int) {
|
||||
err := commands.PlaySongInPlaylist(ctx, uri, pos)
|
||||
func HandlePlayWithContext(ctx *gctx.Context, client *spotify.Client, uri *spotify.URI, pos int) {
|
||||
var err error
|
||||
err = commands.PlaySongInPlaylist(ctx, client, uri, pos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleRadio(ctx *gctx.Context, commands *commands.Commands, song spotify.SimpleTrack) {
|
||||
err := commands.RadioGivenSong(ctx, song, 0)
|
||||
func HandleRadio(ctx *gctx.Context, client *spotify.Client, id spotify.ID) {
|
||||
err := commands.RadioGivenSong(ctx, client, id, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleAlbumRadio(ctx *gctx.Context, commands *commands.Commands, album spotify.SimpleAlbum) {
|
||||
err := commands.RadioFromAlbum(ctx, album)
|
||||
func HandleAlbumRadio(ctx *gctx.Context, client *spotify.Client, id spotify.ID) {
|
||||
err := commands.RadioFromAlbum(ctx, client, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSeek(ctx *gctx.Context, commands *commands.Commands, fwd bool) {
|
||||
err := commands.Seek(ctx, fwd)
|
||||
func HandleSeek(ctx *gctx.Context, client *spotify.Client, fwd bool) {
|
||||
err := commands.Seek(ctx, client, fwd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleVolume(ctx *gctx.Context, commands *commands.Commands, up bool) {
|
||||
func HandleVolume(ctx *gctx.Context, client *spotify.Client, up bool) {
|
||||
vol := 10
|
||||
if !up {
|
||||
vol = -10
|
||||
}
|
||||
err := commands.ChangeVolume(ctx, vol)
|
||||
err := commands.ChangeVolume(ctx, client, vol)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleArtistRadio(ctx *gctx.Context, commands *commands.Commands, artist spotify.SimpleArtist) {
|
||||
err := commands.RadioGivenArtist(ctx, artist)
|
||||
func HandleArtistRadio(ctx *gctx.Context, client *spotify.Client, id spotify.ID) {
|
||||
err := commands.RadioGivenArtist(ctx, client, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleAlbumArtist(ctx *gctx.Context, commands *commands.Commands, artist spotify.SimpleArtist) {
|
||||
err := commands.RadioGivenArtist(ctx, artist)
|
||||
func HandleAlbumArtist(ctx *gctx.Context, client *spotify.Client, id spotify.ID) {
|
||||
err := commands.RadioGivenArtist(ctx, client, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandlePlaylistRadio(ctx *gctx.Context, commands *commands.Commands, playlist spotify.SimplePlaylist) {
|
||||
err := commands.RadioFromPlaylist(ctx, playlist)
|
||||
func HandlePlaylistRadio(ctx *gctx.Context, client *spotify.Client, playlist spotify.SimplePlaylist) {
|
||||
err := commands.RadioFromPlaylist(ctx, client, playlist)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleLibraryRadio(ctx *gctx.Context, commands *commands.Commands) {
|
||||
err := commands.RadioFromSavedTracks(ctx)
|
||||
func HandleLibraryRadio(ctx *gctx.Context, client *spotify.Client) {
|
||||
err := commands.RadioFromSavedTracks(ctx, client)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandlePlayLikedSong(ctx *gctx.Context, commands *commands.Commands, position int) {
|
||||
err := commands.PlayLikedSongs(ctx, position)
|
||||
func HandlePlayLikedSong(ctx *gctx.Context, client *spotify.Client, position int) {
|
||||
err := commands.PlayLikedSongs(ctx, client, position)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandlePlayTrack(ctx *gctx.Context, commands *commands.Commands, track spotify.ID) {
|
||||
err := commands.QueueSong(ctx, track)
|
||||
func HandlePlayTrack(ctx *gctx.Context, client *spotify.Client, track spotify.ID) {
|
||||
err := commands.QueueSong(ctx, client, track)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
err = commands.Next(ctx, 1, false)
|
||||
err = commands.Next(ctx, client)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleNextInQueue(ctx *gctx.Context, commands *commands.Commands, amt int) {
|
||||
err := commands.Next(ctx, amt, true)
|
||||
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)
|
||||
func HandleSetDevice(ctx *gctx.Context, client *spotify.Client, player spotify.PlayerDevice) {
|
||||
var err error
|
||||
err = commands.SetDevice(ctx, client, player)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -4,18 +4,16 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gospt/src/commands"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
)
|
||||
|
||||
func (m *mainModel) LoadMoreItems() {
|
||||
loading = true
|
||||
defer func() {
|
||||
page++
|
||||
loading = false
|
||||
}()
|
||||
switch m.mode {
|
||||
case "artist":
|
||||
albums, err := m.commands.ArtistAlbums(m.ctx, m.artist.ID, (page + 1))
|
||||
albums, err := commands.ArtistAlbums(m.ctx, m.client, m.artist.ID, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -34,7 +32,8 @@ func (m *mainModel) LoadMoreItems() {
|
||||
main_updates <- m
|
||||
return
|
||||
case "artists":
|
||||
artists, err := m.commands.UserArtists(m.ctx, (page + 1))
|
||||
artists, err := commands.UserArtists(m.ctx, m.client, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -53,7 +52,8 @@ func (m *mainModel) LoadMoreItems() {
|
||||
main_updates <- m
|
||||
return
|
||||
case "album":
|
||||
tracks, err := m.commands.AlbumTracks(m.ctx, m.album.ID, (page + 1))
|
||||
tracks, err := commands.AlbumTracks(m.ctx, m.client, m.album.ID, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -73,7 +73,8 @@ func (m *mainModel) LoadMoreItems() {
|
||||
main_updates <- m
|
||||
return
|
||||
case "albums":
|
||||
albums, err := m.commands.UserAlbums(m.ctx, (page + 1))
|
||||
albums, err := commands.UserAlbums(m.ctx, m.client, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -92,7 +93,8 @@ func (m *mainModel) LoadMoreItems() {
|
||||
main_updates <- m
|
||||
return
|
||||
case "main":
|
||||
playlists, err := m.commands.Playlists(m.ctx, (page + 1))
|
||||
playlists, err := commands.Playlists(m.ctx, m.client, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -110,18 +112,19 @@ func (m *mainModel) LoadMoreItems() {
|
||||
main_updates <- m
|
||||
return
|
||||
case "playlist":
|
||||
playlistItems, err := m.commands.PlaylistTracks(m.ctx, m.playlist.ID, (page + 1))
|
||||
tracks, err := commands.PlaylistTracks(m.ctx, m.client, m.playlist.ID, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := []mainItem{}
|
||||
for _, item := range playlistItems.Items {
|
||||
for _, track := range tracks.Tracks {
|
||||
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(),
|
||||
Name: track.Track.Name,
|
||||
Artist: track.Track.Artists[0],
|
||||
Duration: track.Track.TimeDuration().Round(time.Second).String(),
|
||||
ID: track.Track.ID,
|
||||
Desc: track.Track.Artists[0].Name + " - " + track.Track.TimeDuration().Round(time.Second).String(),
|
||||
})
|
||||
}
|
||||
for _, item := range items {
|
||||
@ -130,10 +133,12 @@ func (m *mainModel) LoadMoreItems() {
|
||||
main_updates <- m
|
||||
return
|
||||
case "tracks":
|
||||
tracks, err := m.commands.TrackList(m.ctx, (page + 1))
|
||||
tracks, err := commands.TrackList(m.ctx, m.client, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
page++
|
||||
items := []list.Item{}
|
||||
for _, track := range tracks.Tracks {
|
||||
items = append(items, mainItem{
|
||||
|
540
src/tui/main.go
540
src/tui/main.go
@ -2,56 +2,48 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"gospt/src/gctx"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/progress"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
)
|
||||
|
||||
var (
|
||||
P *tea.Program
|
||||
DocStyle = lipgloss.NewStyle().Margin(0, 2).Border(lipgloss.DoubleBorder(), true, true, true, true)
|
||||
currentlyPlaying *spotify.CurrentlyPlaying
|
||||
playbackContext string
|
||||
DocStyle = lipgloss.NewStyle().Margin(0, 2)
|
||||
currentlyPlaying string
|
||||
main_updates chan *mainModel
|
||||
page = 1
|
||||
loading = false
|
||||
showingMessage = false
|
||||
)
|
||||
|
||||
type Mode string
|
||||
|
||||
const (
|
||||
Album Mode = "album"
|
||||
ArtistAlbum Mode = "artistalbum"
|
||||
Artist Mode = "artist"
|
||||
Artists Mode = "artists"
|
||||
Queue Mode = "queue"
|
||||
Tracks Mode = "tracks"
|
||||
Albums Mode = "albums"
|
||||
Main Mode = "main"
|
||||
Playlists Mode = "playlists"
|
||||
Playlist Mode = "playlist"
|
||||
Devices Mode = "devices"
|
||||
Search Mode = "search"
|
||||
SearchAlbums Mode = "searchalbums"
|
||||
SearchAlbum Mode = "searchalbum"
|
||||
SearchArtists Mode = "searchartists"
|
||||
SearchArtist Mode = "searchartist"
|
||||
SearchArtistAlbum Mode = "searchartistalbum"
|
||||
SearchTracks Mode = "searchtracks"
|
||||
SearchPlaylists Mode = "searchplaylsits"
|
||||
SearchPlaylist Mode = "searchplaylist"
|
||||
ArtistAlbum = "artistalbum"
|
||||
Artist = "artist"
|
||||
Artists = "artists"
|
||||
Tracks = "tracks"
|
||||
Albums = "albums"
|
||||
Main = "main"
|
||||
Playlists = "playlists"
|
||||
Playlist = "playlist"
|
||||
Devices = "devices"
|
||||
Search = "search"
|
||||
SearchAlbums = "searchalbums"
|
||||
SearchAlbum = "searchalbum"
|
||||
SearchArtists = "searchartists"
|
||||
SearchArtist = "searchartist"
|
||||
SearchArtistAlbum = "searchartistalbum"
|
||||
SearchTracks = "searchtracks"
|
||||
SearchPlaylists = "searchplaylsits"
|
||||
SearchPlaylist = "searchplaylist"
|
||||
)
|
||||
|
||||
type mainItem struct {
|
||||
@ -75,97 +67,88 @@ func (i mainItem) Description() string { return i.Desc }
|
||||
func (i mainItem) FilterValue() string { return i.Title() + i.Desc }
|
||||
|
||||
type mainModel struct {
|
||||
list list.Model
|
||||
input textinput.Model
|
||||
ctx *gctx.Context
|
||||
commands *commands.Commands
|
||||
mode Mode
|
||||
playlist spotify.SimplePlaylist
|
||||
artist spotify.SimpleArtist
|
||||
album spotify.SimpleAlbum
|
||||
searchResults *SearchResults
|
||||
progress progress.Model
|
||||
playing *spotify.CurrentlyPlaying
|
||||
playbackContext string
|
||||
search string
|
||||
list list.Model
|
||||
input textinput.Model
|
||||
ctx *gctx.Context
|
||||
client *spotify.Client
|
||||
mode Mode
|
||||
playlist spotify.SimplePlaylist
|
||||
artist spotify.SimpleArtist
|
||||
album spotify.SimpleAlbum
|
||||
searchResults *SearchResults
|
||||
search string
|
||||
}
|
||||
|
||||
func (m *mainModel) PlayRadio() {
|
||||
go m.SendMessage("Starting radio for "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
currentlyPlaying = m.list.SelectedItem().FilterValue()
|
||||
m.list.NewStatusMessage("Playing " + currentlyPlaying)
|
||||
selectedItem := m.list.SelectedItem().(mainItem).SpotifyItem
|
||||
switch item := selectedItem.(type) {
|
||||
switch selectedItem.(type) {
|
||||
case spotify.SimplePlaylist:
|
||||
go HandlePlaylistRadio(m.ctx, m.commands, item)
|
||||
go HandlePlaylistRadio(m.ctx, m.client, selectedItem.(spotify.SimplePlaylist))
|
||||
return
|
||||
case *spotify.SavedTrackPage:
|
||||
go HandleLibraryRadio(m.ctx, m.commands)
|
||||
case spotify.SavedTrackPage:
|
||||
go HandleLibraryRadio(m.ctx, m.client)
|
||||
return
|
||||
case spotify.SimpleAlbum:
|
||||
go HandleAlbumRadio(m.ctx, m.commands, item)
|
||||
go HandleAlbumRadio(m.ctx, m.client, selectedItem.(spotify.SimpleAlbum).ID)
|
||||
return
|
||||
case spotify.FullAlbum:
|
||||
go HandleAlbumRadio(m.ctx, m.commands, item.SimpleAlbum)
|
||||
go HandleAlbumRadio(m.ctx, m.client, selectedItem.(spotify.FullAlbum).ID)
|
||||
return
|
||||
case spotify.SimpleArtist:
|
||||
go HandleArtistRadio(m.ctx, m.commands, item)
|
||||
go HandleArtistRadio(m.ctx, m.client, selectedItem.(spotify.SimpleArtist).ID)
|
||||
return
|
||||
case spotify.FullArtist:
|
||||
go HandleArtistRadio(m.ctx, m.commands, item.SimpleArtist)
|
||||
go HandleArtistRadio(m.ctx, m.client, selectedItem.(spotify.FullArtist).ID)
|
||||
return
|
||||
case spotify.SimpleTrack:
|
||||
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)
|
||||
default:
|
||||
go HandleRadio(m.ctx, m.client, m.list.SelectedItem().(mainItem).ID)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mainModel) GoBack() (tea.Cmd, error) {
|
||||
page = 1
|
||||
switch m.mode {
|
||||
case Main:
|
||||
return tea.Quit, nil
|
||||
case Albums, Artists, Tracks, Playlist, Devices, Search, Queue:
|
||||
case Albums, Artists, Tracks, Playlist, Devices, Search:
|
||||
m.mode = Main
|
||||
new_items, err := MainView(m.ctx, m.commands)
|
||||
m.list.NewStatusMessage("Setting view to main")
|
||||
new_items, err := MainView(m.ctx, m.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case Album:
|
||||
m.mode = Albums
|
||||
new_items, err := AlbumsView(m.ctx, m.commands)
|
||||
m.list.NewStatusMessage("Setting view to albums")
|
||||
new_items, err := AlbumsView(m.ctx, m.client)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case Artist:
|
||||
m.mode = Artists
|
||||
new_items, err := ArtistsView(m.ctx, m.commands)
|
||||
m.list.NewStatusMessage("Setting view to artists")
|
||||
new_items, err := ArtistsView(m.ctx, m.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case ArtistAlbum:
|
||||
m.mode = Artist
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
|
||||
m.list.NewStatusMessage("Opening " + m.artist.Name)
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case SearchArtists, SearchTracks, SearchAlbums, SearchPlaylists:
|
||||
m.mode = Search
|
||||
items, result, err := SearchView(m.ctx, m.commands, m.search)
|
||||
m.list.NewStatusMessage("Setting view to search for " + m.input.Value())
|
||||
items, result, err := SearchView(m.ctx, m.client, m.search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -173,28 +156,32 @@ func (m *mainModel) GoBack() (tea.Cmd, error) {
|
||||
m.list.SetItems(items)
|
||||
case SearchArtist:
|
||||
m.mode = SearchArtists
|
||||
new_items, err := SearchArtistsView(m.ctx, m.commands, m.searchResults.Artists)
|
||||
m.list.NewStatusMessage("Setting view to artists")
|
||||
new_items, err := SearchArtistsView(m.ctx, m.client, m.searchResults.Artists)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case SearchArtistAlbum:
|
||||
m.mode = SearchArtist
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
|
||||
m.list.NewStatusMessage("Opening " + m.artist.Name)
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case SearchAlbum:
|
||||
m.mode = SearchAlbums
|
||||
new_items, err := SearchAlbumsView(m.ctx, m.commands, m.searchResults.Albums)
|
||||
m.list.NewStatusMessage("Setting view to albums")
|
||||
new_items, err := SearchAlbumsView(m.ctx, m.client, m.searchResults.Albums)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case SearchPlaylist:
|
||||
m.mode = SearchPlaylists
|
||||
new_items, err := SearchPlaylistsView(m.ctx, m.commands, m.searchResults.Playlists)
|
||||
m.list.NewStatusMessage("Setting view to playlists")
|
||||
new_items, err := SearchPlaylistsView(m.ctx, m.client, m.searchResults.Playlists)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -205,128 +192,14 @@ func (m *mainModel) GoBack() (tea.Cmd, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type SpotifyUrl struct {
|
||||
ExternalURLs map[string]string
|
||||
}
|
||||
|
||||
func (m *mainModel) CopyToClipboard() error {
|
||||
item := m.list.SelectedItem().(mainItem).SpotifyItem
|
||||
switch converted := item.(type) {
|
||||
case spotify.SimplePlaylist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullPlaylist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleAlbum:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullAlbum:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleArtist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullArtist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.PlaylistTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.Track.ExternalURLs["spotify"])
|
||||
case spotify.SavedTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.FullTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
}
|
||||
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 {
|
||||
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:
|
||||
page = 1
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
switch m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case *spotify.FullArtistPage:
|
||||
m.mode = SearchArtists
|
||||
new_items, err := SearchArtistsView(m.ctx, m.commands, item)
|
||||
m.list.NewStatusMessage("Setting view to artists")
|
||||
new_items, err := SearchArtistsView(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.FullArtistPage))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -334,7 +207,8 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SimpleAlbumPage:
|
||||
m.mode = SearchAlbums
|
||||
new_items, err := SearchAlbumsView(m.ctx, m.commands, item)
|
||||
m.list.NewStatusMessage("Setting view to albums")
|
||||
new_items, err := SearchAlbumsView(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.SimpleAlbumPage))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -342,7 +216,9 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SimplePlaylistPage:
|
||||
m.mode = SearchPlaylists
|
||||
new_items, err := SearchPlaylistsView(m.ctx, m.commands, item)
|
||||
playlists := m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.SimplePlaylistPage)
|
||||
m.list.NewStatusMessage("Setting view to playlist")
|
||||
new_items, err := SearchPlaylistsView(m.ctx, m.client, playlists)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -350,68 +226,62 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.ResetSelected()
|
||||
case *spotify.FullTrackPage:
|
||||
m.mode = SearchTracks
|
||||
new_items, err := SearchTracksView(m.ctx, m.commands, item)
|
||||
m.list.NewStatusMessage("Setting view to tracks")
|
||||
new_items, err := SearchTracksView(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.FullTrackPage))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
m.list.NewStatusMessage("Setting view to tracks")
|
||||
}
|
||||
case SearchArtists:
|
||||
page = 1
|
||||
m.mode = SearchArtist
|
||||
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
|
||||
m.list.NewStatusMessage("Opening " + m.artist.Name)
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchArtist:
|
||||
page = 1
|
||||
m.mode = SearchArtistAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
m.list.NewStatusMessage("Opening " + m.album.Name)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchAlbums:
|
||||
page = 1
|
||||
m.mode = SearchAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
m.list.NewStatusMessage("Opening " + m.album.Name)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchPlaylists:
|
||||
page = 1
|
||||
m.mode = SearchPlaylist
|
||||
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
|
||||
m.playlist = playlist
|
||||
new_items, err := PlaylistView(m.ctx, m.commands, playlist)
|
||||
m.list.NewStatusMessage("Setting view to playlist " + playlist.Name)
|
||||
new_items, err := PlaylistView(m.ctx, m.client, playlist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case Main:
|
||||
page = 1
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case spotify.Queue:
|
||||
m.mode = Queue
|
||||
new_items, err := QueueView(m.ctx, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
switch m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case *spotify.FullArtistCursorPage:
|
||||
m.mode = Artists
|
||||
new_items, err := ArtistsView(m.ctx, m.commands)
|
||||
m.list.NewStatusMessage("Setting view to artists")
|
||||
new_items, err := ArtistsView(m.ctx, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -419,7 +289,8 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SavedAlbumPage:
|
||||
m.mode = Albums
|
||||
new_items, err := AlbumsView(m.ctx, m.commands)
|
||||
m.list.NewStatusMessage("Setting view to albums")
|
||||
new_items, err := AlbumsView(m.ctx, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -427,8 +298,10 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.ResetSelected()
|
||||
case spotify.SimplePlaylist:
|
||||
m.mode = Playlist
|
||||
m.playlist = item
|
||||
new_items, err := PlaylistView(m.ctx, m.commands, item)
|
||||
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
|
||||
m.playlist = playlist
|
||||
m.list.NewStatusMessage("Setting view to playlist " + playlist.Name)
|
||||
new_items, err := PlaylistView(m.ctx, m.client, playlist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -436,18 +309,20 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SavedTrackPage:
|
||||
m.mode = Tracks
|
||||
new_items, err := SavedTracksView(m.ctx, m.commands)
|
||||
m.list.NewStatusMessage("Setting view to saved tracks")
|
||||
new_items, err := SavedTracksView(m.ctx, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
m.list.NewStatusMessage("Setting view to tracks")
|
||||
}
|
||||
case Albums:
|
||||
page = 1
|
||||
m.mode = Album
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
m.list.NewStatusMessage("Opening " + m.album.Name)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -456,7 +331,8 @@ func (m *mainModel) SelectItem() error {
|
||||
case Artist:
|
||||
m.mode = ArtistAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
m.list.NewStatusMessage("Opening " + m.album.Name)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -465,64 +341,58 @@ func (m *mainModel) SelectItem() error {
|
||||
case Artists:
|
||||
m.mode = Artist
|
||||
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
|
||||
m.list.NewStatusMessage("Opening " + m.artist.Name)
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case Album, ArtistAlbum, SearchArtistAlbum, SearchAlbum:
|
||||
pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.TotalPages)
|
||||
go HandlePlayWithContext(m.ctx, m.commands, &m.album.URI, &pos)
|
||||
currentlyPlaying = m.list.SelectedItem().FilterValue()
|
||||
m.list.NewStatusMessage("Playing " + currentlyPlaying)
|
||||
go HandlePlayWithContext(m.ctx, m.client, &m.album.URI, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.TotalPages))
|
||||
case Playlist, SearchPlaylist:
|
||||
pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.PerPage)
|
||||
go HandlePlayWithContext(m.ctx, m.commands, &m.playlist.URI, &pos)
|
||||
currentlyPlaying = m.list.SelectedItem().FilterValue()
|
||||
m.list.NewStatusMessage("Playing " + currentlyPlaying)
|
||||
go HandlePlayWithContext(m.ctx, m.client, &m.playlist.URI, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage))
|
||||
case Tracks:
|
||||
go HandlePlayLikedSong(m.ctx, m.commands, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage))
|
||||
currentlyPlaying = m.list.SelectedItem().FilterValue()
|
||||
m.list.NewStatusMessage("Playing " + currentlyPlaying)
|
||||
go HandlePlayLikedSong(m.ctx, m.client, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage))
|
||||
case SearchTracks:
|
||||
go HandlePlayTrack(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.FullTrack).ID)
|
||||
currentlyPlaying = m.list.SelectedItem().FilterValue()
|
||||
m.list.NewStatusMessage("Playing " + currentlyPlaying)
|
||||
go HandlePlayTrack(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.FullTrack).ID)
|
||||
case Devices:
|
||||
go HandleSetDevice(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlayerDevice))
|
||||
go m.SendMessage("Setting device to "+m.list.SelectedItem().FilterValue(), 2*time.Second)
|
||||
go HandleSetDevice(m.ctx, m.client, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlayerDevice))
|
||||
m.list.NewStatusMessage("Setting device to " + m.list.SelectedItem().FilterValue())
|
||||
m.mode = "main"
|
||||
new_items, err := MainView(m.ctx, m.commands)
|
||||
m.list.NewStatusMessage("Setting view to main")
|
||||
new_items, err := MainView(m.ctx, m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mainModel) Init() tea.Cmd {
|
||||
func (m mainModel) Init() tea.Cmd {
|
||||
main_updates = make(chan *mainModel)
|
||||
return Tick()
|
||||
return nil
|
||||
}
|
||||
|
||||
type tickMsg time.Time
|
||||
|
||||
func Tick() tea.Cmd {
|
||||
return tea.Tick(time.Second*1, func(t time.Time) tea.Msg {
|
||||
return tickMsg(t)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
func (m *mainModel) Tick() {
|
||||
ticker := time.NewTicker(5 * 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)
|
||||
playing, _ := m.client.PlayerCurrentlyPlaying(m.ctx)
|
||||
if playing != nil && playing.Playing {
|
||||
currentlyPlaying = "Now playing " + playing.Item.Name + " by " + playing.Item.Artists[0].Name
|
||||
}
|
||||
case <-quit:
|
||||
ticker.Stop()
|
||||
@ -532,7 +402,7 @@ func (m *mainModel) TickPlayback() {
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *mainModel) View() string {
|
||||
func (m mainModel) View() string {
|
||||
if m.input.Focused() {
|
||||
return DocStyle.Render(m.list.View() + "\n" + m.input.View())
|
||||
}
|
||||
@ -541,8 +411,10 @@ func (m *mainModel) View() string {
|
||||
|
||||
func (m *mainModel) Typing(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
if msg.String() == "enter" {
|
||||
items, result, err := SearchView(m.ctx, m.commands, m.input.Value())
|
||||
m.list.NewStatusMessage("Setting view to search for " + m.input.Value())
|
||||
items, result, err := SearchView(m.ctx, m.client, m.input.Value())
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return false, tea.Quit
|
||||
}
|
||||
m.searchResults = result
|
||||
@ -562,37 +434,9 @@ func (m *mainModel) Typing(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *mainModel) getContext(playing *spotify.CurrentlyPlaying) (string, error) {
|
||||
context := playing.PlaybackContext
|
||||
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) {
|
||||
func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Update Now Playing
|
||||
m.list.NewStatusMessage(currentlyPlaying)
|
||||
// Update list items from LoadMore
|
||||
select {
|
||||
case update := <-main_updates:
|
||||
@ -600,7 +444,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
default:
|
||||
}
|
||||
// Call for more items if needed
|
||||
if m.list.Paginator.Page == m.list.Paginator.TotalPages-1 && m.list.Cursor() == 0 && !loading {
|
||||
if m.list.Paginator.Page == m.list.Paginator.TotalPages-2 && m.list.Cursor() == 0 {
|
||||
// if last request was still full request more
|
||||
if len(m.list.Items())%50 == 0 {
|
||||
go m.LoadMoreItems()
|
||||
@ -608,64 +452,22 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
// Handle user input
|
||||
switch msg := msg.(type) {
|
||||
case tickMsg:
|
||||
playing := currentlyPlaying
|
||||
if playing != nil && playing.Playing && playing.Item != nil {
|
||||
cmd := m.progress.SetPercent(float64(playing.Progress) / float64(playing.Item.Duration))
|
||||
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, Tick()
|
||||
|
||||
case progress.FrameMsg:
|
||||
progressModel, cmd := m.progress.Update(msg)
|
||||
m.progress = progressModel.(progress.Model)
|
||||
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
|
||||
case tea.KeyMsg:
|
||||
// quit
|
||||
if msg.String() == "ctrl+c" {
|
||||
return m, tea.Quit
|
||||
}
|
||||
if msg.String() == "c" {
|
||||
err := m.CopyToClipboard()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
if msg.String() == ">" {
|
||||
go HandleSeek(m.ctx, m.commands, true)
|
||||
go HandleSeek(m.ctx, m.client, true)
|
||||
}
|
||||
if msg.String() == "<" {
|
||||
go HandleSeek(m.ctx, m.commands, false)
|
||||
go HandleSeek(m.ctx, m.client, false)
|
||||
}
|
||||
if msg.String() == "+" {
|
||||
go HandleVolume(m.ctx, m.commands, true)
|
||||
go HandleVolume(m.ctx, m.client, true)
|
||||
}
|
||||
if msg.String() == "-" {
|
||||
go HandleVolume(m.ctx, m.commands, false)
|
||||
go HandleVolume(m.ctx, m.client, false)
|
||||
}
|
||||
// search input
|
||||
if m.input.Focused() {
|
||||
@ -682,36 +484,27 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// enter device selection
|
||||
if msg.String() == "d" {
|
||||
m.mode = Devices
|
||||
new_items, err := DeviceView(m.ctx, m.commands)
|
||||
new_items, err := DeviceView(m.ctx, m.client)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
m.list.NewStatusMessage("Setting view to devices")
|
||||
}
|
||||
// go back
|
||||
if msg.String() == "backspace" || msg.String() == "esc" || msg.String() == "q" {
|
||||
msg, err := m.GoBack()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
fmt.Println(err)
|
||||
}
|
||||
m.list.ResetSelected()
|
||||
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
|
||||
if msg.String() == "enter" || msg.String() == " " || msg.String() == "p" {
|
||||
if msg.String() == "enter" || msg.String() == "spacebar" {
|
||||
err := m.SelectItem()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
@ -743,63 +536,62 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func InitMain(ctx *gctx.Context, c *commands.Commands, mode Mode) (tea.Model, error) {
|
||||
prog := progress.New(progress.WithColorProfile(2), progress.WithoutPercentage())
|
||||
func InitMain(ctx *gctx.Context, client *spotify.Client, mode Mode) (tea.Model, error) {
|
||||
var err error
|
||||
lipgloss.SetColorProfile(2)
|
||||
playing, err := client.PlayerCurrentlyPlaying(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if playing != nil && playing.Playing {
|
||||
currentlyPlaying = "Now playing " + playing.Item.Name + " by " + playing.Item.Artists[0].Name
|
||||
}
|
||||
items := []list.Item{}
|
||||
switch mode {
|
||||
case Main:
|
||||
items, err = MainView(ctx, c)
|
||||
items, err = MainView(ctx, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case Devices:
|
||||
items, err = DeviceView(ctx, c)
|
||||
items, err = DeviceView(ctx, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case Tracks:
|
||||
items, err = SavedTracksView(ctx, c)
|
||||
items, err = SavedTracksView(ctx, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
m := &mainModel{
|
||||
list: list.New(items, list.NewDefaultDelegate(), 0, 0),
|
||||
ctx: ctx,
|
||||
commands: c,
|
||||
mode: mode,
|
||||
progress: prog,
|
||||
m := mainModel{
|
||||
list: list.New(items, list.NewDefaultDelegate(), 0, 0),
|
||||
ctx: ctx,
|
||||
client: client,
|
||||
mode: mode,
|
||||
}
|
||||
m.list.Title = "GOSPT"
|
||||
go m.TickPlayback()
|
||||
Tick()
|
||||
go m.Tick()
|
||||
m.list.DisableQuitKeybindings()
|
||||
m.list.SetFilteringEnabled(false)
|
||||
m.list.AdditionalShortHelpKeys = func() []key.Binding {
|
||||
return []key.Binding{
|
||||
key.NewBinding(key.WithKeys("q"), key.WithHelp("q", "back")),
|
||||
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
|
||||
key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "search")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "radio")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"p"), key.WithHelp("ctrl+p", "queue")),
|
||||
key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")),
|
||||
key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "search")),
|
||||
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("d"), key.WithHelp("d", "select device")),
|
||||
}
|
||||
}
|
||||
m.list.AdditionalFullHelpKeys = func() []key.Binding {
|
||||
return []key.Binding{
|
||||
key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")),
|
||||
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
|
||||
key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "search")),
|
||||
key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "search")),
|
||||
key.NewBinding(key.WithKeys(">"), key.WithHelp(">", "seek forward")),
|
||||
key.NewBinding(key.WithKeys("<"), key.WithHelp("<", "seek backward")),
|
||||
key.NewBinding(key.WithKeys("+"), key.WithHelp("+", "volume up")),
|
||||
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"+"r"), key.WithHelp("ctrl+r", "start radio")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"p"), key.WithHelp("ctrl+p", "queue song")),
|
||||
key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")),
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,22 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"git.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"fmt"
|
||||
|
||||
"gospt/src/gctx"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
// StartTea the entry point for the UI. Initializes the model.
|
||||
func StartTea(ctx *gctx.Context, cmd *commands.Commands, mode string) error {
|
||||
m, err := InitMain(ctx, cmd, Mode(mode))
|
||||
func StartTea(ctx *gctx.Context, client *spotify.Client, mode string) error {
|
||||
m, err := InitMain(ctx, client, Mode(mode))
|
||||
if err != nil {
|
||||
return err
|
||||
fmt.Println("UH OH")
|
||||
}
|
||||
P = tea.NewProgram(m, tea.WithAltScreen())
|
||||
if _, err := P.Run(); err != nil {
|
||||
if err := P.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
205
src/tui/views.go
205
src/tui/views.go
@ -2,22 +2,19 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gospt/src/commands"
|
||||
"gospt/src/gctx"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
const regex = `<.*?>`
|
||||
|
||||
func DeviceView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
|
||||
func DeviceView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
devices, err := commands.Client().PlayerDevices(ctx)
|
||||
devices, err := client.PlayerDevices(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -31,57 +28,27 @@ func DeviceView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, er
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func QueueView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
|
||||
func PlaylistView(ctx *gctx.Context, client *spotify.Client, playlist spotify.SimplePlaylist) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
tracks, err := commands.UserQueue(ctx)
|
||||
tracks, err := commands.PlaylistTracks(ctx, client, playlist.ID, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tracks.CurrentlyPlaying.Name != "" {
|
||||
for _, track := range tracks.Tracks {
|
||||
items = append(items, mainItem{
|
||||
Name: tracks.CurrentlyPlaying.Name,
|
||||
Artist: tracks.CurrentlyPlaying.Artists[0],
|
||||
Duration: tracks.CurrentlyPlaying.TimeDuration().Round(time.Second).String(),
|
||||
ID: tracks.CurrentlyPlaying.ID,
|
||||
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,
|
||||
Name: track.Track.Name,
|
||||
Artist: track.Track.Artists[0],
|
||||
Duration: track.Track.TimeDuration().Round(time.Second).String(),
|
||||
ID: track.Track.ID,
|
||||
Desc: track.Track.Artists[0].Name + " - " + track.Track.TimeDuration().Round(time.Second).String(),
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func PlaylistView(ctx *gctx.Context, commands *commands.Commands, playlist spotify.SimplePlaylist) ([]list.Item, error) {
|
||||
func ArtistsView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
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)
|
||||
artists, err := commands.UserArtists(ctx, client, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -89,40 +56,53 @@ func ArtistsView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, e
|
||||
items = append(items, mainItem{
|
||||
Name: artist.Name,
|
||||
ID: artist.ID,
|
||||
Desc: fmt.Sprintf("%d followers", artist.Followers.Count),
|
||||
Desc: fmt.Sprintf("%d followers, genres: %s, popularity: %d", artist.Followers.Count, artist.Genres, artist.Popularity),
|
||||
SpotifyItem: artist.SimpleArtist,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SearchArtistsView(ctx *gctx.Context, commands *commands.Commands, artists *spotify.FullArtistPage) ([]list.Item, error) {
|
||||
func SearchArtistsView(ctx *gctx.Context, client *spotify.Client, artists *spotify.FullArtistPage) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
for _, artist := range artists.Artists {
|
||||
items = append(items, mainItem{
|
||||
Name: artist.Name,
|
||||
ID: artist.ID,
|
||||
Desc: fmt.Sprintf("%d followers", artist.Followers.Count),
|
||||
Desc: fmt.Sprintf("%d followers, genres: %s, popularity: %d", artist.Followers.Count, artist.Genres, artist.Popularity),
|
||||
SpotifyItem: artist.SimpleArtist,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SearchView(ctx *gctx.Context, commands *commands.Commands, search string) ([]list.Item, *SearchResults, error) {
|
||||
func SearchView(ctx *gctx.Context, client *spotify.Client, search string) ([]list.Item, *SearchResults, error) {
|
||||
items := []list.Item{}
|
||||
|
||||
result, err := commands.Search(ctx, search, 1)
|
||||
result, err := commands.Search(ctx, client, search, 1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
items = append(
|
||||
items,
|
||||
mainItem{Name: "Tracks", Desc: "Search results", SpotifyItem: result.Tracks},
|
||||
mainItem{Name: "Albums", Desc: "Search results", SpotifyItem: result.Albums},
|
||||
mainItem{Name: "Artists", Desc: "Search results", SpotifyItem: result.Artists},
|
||||
mainItem{Name: "Playlists", Desc: "Search results", SpotifyItem: result.Playlists},
|
||||
)
|
||||
items = append(items, mainItem{
|
||||
Name: "Tracks",
|
||||
Desc: "Search results",
|
||||
SpotifyItem: result.Tracks,
|
||||
})
|
||||
items = append(items, mainItem{
|
||||
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{
|
||||
Tracks: result.Tracks,
|
||||
Playlists: result.Playlists,
|
||||
@ -132,9 +112,9 @@ func SearchView(ctx *gctx.Context, commands *commands.Commands, search string) (
|
||||
return items, results, nil
|
||||
}
|
||||
|
||||
func AlbumsView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
|
||||
func AlbumsView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
albums, err := commands.UserAlbums(ctx, 1)
|
||||
albums, err := commands.UserAlbums(ctx, client, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -142,41 +122,41 @@ func AlbumsView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, er
|
||||
items = append(items, mainItem{
|
||||
Name: album.Name,
|
||||
ID: album.ID,
|
||||
Desc: fmt.Sprintf("%s by %s, %d tracks, released %d", album.AlbumType, album.Artists[0].Name, album.Tracks.Total, album.ReleaseDateTime().Year()),
|
||||
Desc: fmt.Sprintf("%s, %d tracks", album.Artists[0].Name, album.Tracks.Total),
|
||||
SpotifyItem: album.SimpleAlbum,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SearchPlaylistsView(ctx *gctx.Context, commands *commands.Commands, playlists *spotify.SimplePlaylistPage) ([]list.Item, error) {
|
||||
func SearchPlaylistsView(ctx *gctx.Context, client *spotify.Client, playlists *spotify.SimplePlaylistPage) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
for _, playlist := range playlists.Playlists {
|
||||
items = append(items, mainItem{
|
||||
Name: playlist.Name,
|
||||
Desc: stripHtmlRegex(playlist.Description),
|
||||
Desc: playlist.Description,
|
||||
SpotifyItem: playlist,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SearchAlbumsView(ctx *gctx.Context, commands *commands.Commands, albums *spotify.SimpleAlbumPage) ([]list.Item, error) {
|
||||
func SearchAlbumsView(ctx *gctx.Context, client *spotify.Client, albums *spotify.SimpleAlbumPage) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
for _, album := range albums.Albums {
|
||||
items = append(items, mainItem{
|
||||
Name: album.Name,
|
||||
ID: album.ID,
|
||||
Desc: fmt.Sprintf("%s by %s, released %d", album.AlbumType, album.Artists[0].Name, album.ReleaseDateTime().Year()),
|
||||
Desc: fmt.Sprintf("%s, %d", album.Artists[0].Name, album.ReleaseDateTime()),
|
||||
SpotifyItem: album,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func ArtistAlbumsView(ctx *gctx.Context, album spotify.ID, commands *commands.Commands) ([]list.Item, error) {
|
||||
func ArtistAlbumsView(ctx *gctx.Context, album spotify.ID, client *spotify.Client) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
albums, err := commands.ArtistAlbums(ctx, album, 1)
|
||||
albums, err := commands.ArtistAlbums(ctx, client, album, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -191,9 +171,9 @@ func ArtistAlbumsView(ctx *gctx.Context, album spotify.ID, commands *commands.Co
|
||||
return items, err
|
||||
}
|
||||
|
||||
func AlbumTracksView(ctx *gctx.Context, album spotify.ID, commands *commands.Commands) ([]list.Item, error) {
|
||||
func AlbumTracksView(ctx *gctx.Context, album spotify.ID, client *spotify.Client) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
tracks, err := commands.AlbumTracks(ctx, album, 1)
|
||||
tracks, err := commands.AlbumTracks(ctx, client, album, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -210,7 +190,7 @@ func AlbumTracksView(ctx *gctx.Context, album spotify.ID, commands *commands.Com
|
||||
return items, err
|
||||
}
|
||||
|
||||
func SearchTracksView(ctx *gctx.Context, commands *commands.Commands, tracks *spotify.FullTrackPage) ([]list.Item, error) {
|
||||
func SearchTracksView(ctx *gctx.Context, client *spotify.Client, tracks *spotify.FullTrackPage) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
for _, track := range tracks.Tracks {
|
||||
items = append(items, mainItem{
|
||||
@ -225,9 +205,9 @@ func SearchTracksView(ctx *gctx.Context, commands *commands.Commands, tracks *sp
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SavedTracksView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
|
||||
func SavedTracksView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
tracks, err := commands.TrackList(ctx, 1)
|
||||
tracks, err := commands.TrackList(ctx, client, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -244,37 +224,58 @@ func SavedTracksView(ctx *gctx.Context, commands *commands.Commands) ([]list.Ite
|
||||
return items, err
|
||||
}
|
||||
|
||||
func MainView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
|
||||
wg := errgroup.Group{}
|
||||
func MainView(ctx *gctx.Context, client *spotify.Client) ([]list.Item, error) {
|
||||
var wg sync.WaitGroup
|
||||
var saved_items *spotify.SavedTrackPage
|
||||
var playlists *spotify.SimplePlaylistPage
|
||||
var artists *spotify.FullArtistCursorPage
|
||||
var albums *spotify.SavedAlbumPage
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
saved_items, err = commands.TrackList(ctx, 1)
|
||||
return
|
||||
})
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
saved_items, err = commands.TrackList(ctx, client, 1)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
playlists, err = commands.Playlists(ctx, 1)
|
||||
return
|
||||
})
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
playlists, err = commands.Playlists(ctx, client, 1)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
artists, err = commands.UserArtists(ctx, 1)
|
||||
return
|
||||
})
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
artists, err = commands.UserArtists(ctx, client, 1)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
albums, err = commands.UserAlbums(ctx, 1)
|
||||
return
|
||||
})
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
albums, err = commands.UserAlbums(ctx, client, 1)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
err := wg.Wait()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
items := []list.Item{}
|
||||
if saved_items != nil && saved_items.Total != 0 {
|
||||
@ -298,24 +299,14 @@ func MainView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, erro
|
||||
SpotifyItem: artists,
|
||||
})
|
||||
}
|
||||
items = append(items, mainItem{
|
||||
Name: "Queue",
|
||||
Desc: "Your Current Queue",
|
||||
SpotifyItem: spotify.Queue{},
|
||||
})
|
||||
if playlists != nil && playlists.Total != 0 {
|
||||
for _, playlist := range playlists.Playlists {
|
||||
items = append(items, mainItem{
|
||||
Name: playlist.Name,
|
||||
Desc: stripHtmlRegex(playlist.Description),
|
||||
Desc: playlist.Description,
|
||||
SpotifyItem: playlist,
|
||||
})
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func stripHtmlRegex(s string) string {
|
||||
r := regexp.MustCompile(regex)
|
||||
return r.ReplaceAllString(s, "")
|
||||
}
|
||||
|
@ -1,115 +0,0 @@
|
||||
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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user