Compare commits

...

37 Commits

Author SHA1 Message Date
abs3nt 6bbcd97b1e Merge pull request 'this should be println' (#20) from println into main
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: #20
2023-11-06 07:02:48 +00:00
a 2925b2c432
this should be print
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/pr/woodpecker Pipeline was successful Details
2023-11-05 20:42:20 -06:00
abs3nt 4cd781d5db
pipeline -> steps
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-10-20 10:55:20 -07:00
abs3nt d4f4b142f7
update
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-10-20 10:53:40 -07:00
abs3nt 2a942a3803
update, checks in format, rename alias for dl
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-10-20 10:52:55 -07:00
abs3nt 95c64ebd70
add force argument to now playing
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-10-14 09:47:03 -07:00
abs3nt e39355fb69
download_cover
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-10-14 09:27:58 -07:00
abs3nt 696ee7e263
remove print
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline failed Details
2023-09-26 15:41:02 -07:00
abs3nt 84b3bd2f90
add case for playlist itme
ci/woodpecker/manual/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline failed Details
2023-09-26 15:33:11 -07:00
abs3nt b569bd5bd6
uncomment 2023-07-25 21:35:38 -07:00
abs3nt e05573c2cf
remove file 2023-07-25 21:32:07 -07:00
abs3nt 232a1cd3b6
error handling, fix initial auth not erroring, remove depricated functions, update libraries 2023-07-25 21:31:43 -07:00
abs3nt a34633ec6d
fix pagination
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-04-23 15:10:35 -07:00
abs3nt 5081c44304
oopsie
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-04-16 11:31:54 -07:00
abs3nt 7e22c90311
better messages/feedback
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-16 11:30:37 -07:00
abs3nt efb03c1aad
fix skipping within queue while playing in a context
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-16 10:58:50 -07:00
abs3nt b5f76fb321
queue tracks, view queue, queue updates when open, hotkey clean up
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-16 10:47:26 -07:00
abs3nt 6ed3453dae
fix crash if no queue / not playing anything
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-16 00:12:50 -07:00
abs3nt a386f90ec5
queue wow
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-16 00:05:23 -07:00
abs3nt 81d5135d1a
sanitize playlist descriptions
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-15 23:21:48 -07:00
abs3nt 693b228194
make album info less stupid looking
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-15 23:04:18 -07:00
abs3nt c124ec4eb9
fix loadmoreitems being called a billion times
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-04-14 19:45:59 -07:00
abs3nt ec60177128
update
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-14 19:29:05 -07:00
abs3nt 8f179f0bc6
add experimental youtube-link command. fix pagination issues throughout tui 2023-04-14 19:28:31 -07:00
abs3nt ee9c2299dc
better toggle
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-04-07 09:25:10 -07:00
abs3nt 2fbeb6d21a
make code less bad
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-06 18:11:06 -07:00
abs3nt 55f9c73966
fix paging for playlists
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-04-05 19:40:24 -07:00
abs3nt 50d5cd18a8
version
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-03-11 21:39:46 -08:00
abs3nt 2b0ea8ed9d
updates
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-03-10 09:51:07 -08:00
abs3nt d222bc2e49
update urls 2023-03-09 23:48:02 -08:00
abs3nt 45ea45de40 README'
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-03-07 19:06:48 -08:00
a a1e9cc6dee adds trace log to http, fixes login flow (#14)
ci/woodpecker/push/woodpecker Pipeline was successful Details
Reviewed-on: https://gitea.asdf.cafe/abs3nt/gospt/pulls/14
Co-authored-by: a <a@tuxpa.in>
Co-committed-by: a <a@tuxpa.in>
2023-03-07 11:22:18 -08:00
abs3nt 7e65581333 fix: typo
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-03-05 15:15:56 -08:00
abs3nt c930490f34 ci: add clean arg to goreleaser
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline failed Details
2023-03-05 15:13:10 -08:00
abs3nt 9a0cd91fcd fix: remove binary
ci/woodpecker/push/woodpecker Pipeline was successful Details
2023-03-05 15:11:57 -08:00
abs3nt b02764ef33 ci: nevermind:
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details
2023-03-04 16:39:53 -08:00
abs3nt fe7f16b9ad ci: ?
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline failed Details
2023-03-04 16:24:54 -08:00
49 changed files with 1017 additions and 647 deletions

87
.golangci.yml Normal file
View File

@ -0,0 +1,87 @@
run:
deadline: 10m
skip-dirs:
- hack
linters:
disable-all: true
enable:
- gofmt
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- gocritic
- bodyclose
- gosec
- prealloc
- unconvert
- unused
linters-settings:
gocritic:
# Which checks should be enabled; can't be combined with 'disabled-checks';
# See https://go-critic.github.io/overview#checks-overview
# To check which checks are enabled run `GL_DEBUG=gocritic ./build/bin/golangci-lint run`
# By default list of stable checks is used.
enabled-checks:
- ruleguard
- truncateCmp
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
disabled-checks:
- captLocal
- assignOp
- paramTypeCombine
- importShadow
- commentFormatting
- rangeValCopy
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
enabled-tags:
- performance
- diagnostic
- opinionated
disabled-tags:
- experimental
settings:
hugeParam:
# size in bytes that makes the warning trigger (default 80)
sizeThreshold: 1000
rangeExprCopy:
# size in bytes that makes the warning trigger (default 512)
sizeThreshold: 512
# whether to check test functions (default true)
skipTestFuncs: true
truncateCmp:
# whether to skip int/uint/uintptr types (default true)
skipArchDependent: true
underef:
# whether to skip (*x).method() calls where x is a pointer receiver (default true)
skipRecvDeref: true
govet:
disable:
- deepequalerrors
- fieldalignment
- shadow
- unsafeptr
goconst:
min-len: 2
min-occurrences: 2
gofmt:
auto-fix: false
issues:
exclude-rules:
- linters:
- golint
text: "should be"
- linters:
- errcheck
text: "not checked"
- linters:
- staticcheck
text: "SA(1019|1029|5011)"

View File

@ -1,6 +1,6 @@
gitea_urls: gitea_urls:
api: https://gitea.asdf.cafe/api/v1 api: https://git.asdf.cafe/api/v1
download: https://gitea.asdf.cafe download: https://git.asdf.cafe
skip_tls_verify: false skip_tls_verify: false
before: before:
@ -17,6 +17,8 @@ builds:
ignore: ignore:
- goos: windows - goos: windows
goarch: "386" goarch: "386"
ldflags:
- -s -w -X git.asdf.cafe/abs3nt/gospt/src.cmd.Version={{.Version}}
archives: archives:
- format: tar.gz - format: tar.gz

View File

@ -1,117 +0,0 @@
# .goreleaser.yaml
aurs:
-
# The package name.
#
# Defaults to the Project Name with a -bin suffix.
#
# Note that since this integration does not create a PKGBUILD to build from
# source, per Arch's guidelines.
# That said, GoReleaser will enforce a `-bin` suffix if its not present.
name: gospt-bin
# Template of your app's description.
# Default is empty.
description: "Spotify TUI And CLI written in Go"
# The maintainers of the package.
# Defaults to empty.
maintainers:
- 'abs3nt <abs3nt@asdf.cafe>'
# The contributors of the package.
# Defaults to empty.
contributors:
- 'abs3nt <abs3nt@asdf.cafe>'
# SPDX identifier of your app's license.
# Default is empty.
license: "GPL"
# The SSH private key that should be used to commit to the Git repository.
# This can either be a path or the key contents.
#
# IMPORTANT: the key must not be password-protected.
#
# WARNING: do not expose your private key in the configuration file!
private_key: '{{ .Env.AUR_KEY }}'
# The AUR Git URL for this package.
# Defaults to empty
# Publish is skipped if empty.
git_url: 'ssh://aur@aur.archlinux.org/gospt-bin.git'
# Setting this will prevent goreleaser to actually try to commit the updated
# formula - instead, the formula file will be stored on the dist folder only,
# leaving the responsibility of publishing it to the user.
#
# If set to auto, the release will not be uploaded to the AUR repo
# in case there is an indicator for prerelease in the tag e.g. v1.0.0-rc1.
#
# Default is false.
skip_upload: false
# List of additional packages that the software provides the features of.
#
# Defaults to the project name.
provides:
- gospt
# List of packages that conflict with, or cause problems with the package.
#
# Defaults to the project name.
conflicts:
- gospt
# List of packages that are not needed for the software to function,
# but provide additional features.
#
# Must be in the format `package: short description of the extra functionality`.
#
# Defaults to empty.
optdepends:
- 'spotifyd: for handling streaming'
# Custom package instructions.
#
# Defaults to `install -Dm755 "./PROJECT_NAME" "${pkgdir}/usr/bin/PROJECT_NAME",
# which is not always correct.
#
# We recommend you override this, installing the binary, license and
# everything else your package needs.
package: |-
# bin
install -Dm755 "./gospt" "${pkgdir}/usr/bin/gospt"
# license
install -Dm644 "./LICENSE.md" "${pkgdir}/usr/share/licenses/gospt/LICENSE"
# completions
mkdir -p "${pkgdir}/usr/share/bash-completion/completions/"
mkdir -p "${pkgdir}/usr/share/zsh/site-functions/"
mkdir -p "${pkgdir}/usr/share/fish/vendor_completions.d/"
install -Dm644 "./completions/gospt.bash" "${pkgdir}/usr/share/bash-completion/completions/gospt"
install -Dm644 "./completions/gospt.zsh" "${pkgdir}/usr/share/zsh/site-functions/_gospt"
install -Dm644 "./completions/gospt.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/gospt.fish"
# Git author used to commit to the repository.
# Defaults are shown below.
commit_author:
name: abs3nt
email: abs3nt@asdf.cafe
# Commit message template.
# Defaults to `Update to {{ .Tag }}`.
commit_msg_template: "pkgbuild updates"
# If you build for multiple GOAMD64 versions, you may use this to choose which one to use.
# Defaults to `v1`.
goamd64: v2
# The value to be passed to `GIT_SSH_COMMAND`.
# This is mainly used to specify the SSH private key used to pull/push to
# the Git URL.
#
# Defaults to `ssh -i {{ .KeyPath }} -o StrictHostKeyChecking=accept-new -F /dev/null`.
git_ssh_command: 'ssh -i {{ .Env.KEY }} -o SomeOption=yes'

View File

@ -1,27 +0,0 @@
settings:
files:
errors:
status: false
path: ""
name: .r.logs.log
legacy:
force: false
interval: 100ms
server:
status: true
open: false
port: 5002
host: ""
schema:
- name: /home/abs3nt/Dev/gospt
path: /home/abs3nt/Dev/gospt
commands:
tidy:
status: true
run:
status: true
watcher:
extensions:
- go
paths:
- /

View File

@ -1,6 +1,6 @@
pipeline: steps:
build: build:
image: golang:1.19 image: golang:1.21
commands: commands:
- go mod tidy - go mod tidy
- go build -o gospt - go build -o gospt
@ -16,10 +16,3 @@ pipeline:
secrets: [ gitea_token ] secrets: [ gitea_token ]
when: when:
event: tag event: tag
publish_aur:
image: goreleaser/goreleaser
commands:
- goreleaser release --config .goreleaseraur.yaml --clean
secrets: [ gitea_token ]
when:
event: tag

View File

@ -1,7 +1,7 @@
IF YOU ARE ON GITHUB.COM GO HERE INSTEAD: https://gitea.asdf.cafe/abs3nt/gospt :) IF YOU ARE ON GITHUB.COM GO HERE INSTEAD: https://git.asdf.cafe/abs3nt/gospt :)
If you open an issue or PR on github I won't see it please use gitea. Register on asdf and open your PRs there If you open an issue or PR on github I won't see it please use git. Register on asdf and open your PRs there
This project is still under heavy development and some things might not work or not work as intended. Don't hesitate to open an issue to let me know. This project is still under heavy development and some things might not work or not work as intended. Don't hesitate to open an issue to let me know.
@ -12,17 +12,23 @@ This project is still under heavy development and some things might not work or
# To install (with a package manager): # To install (with a package manager):
## Archlinux ([AUR]) ## Archlinux ([AUR])
```yay -S gospt``` ```yay -S gospt```
or
```yay -S gospt-git```
## NetBSD ([Official repositories]) ## NetBSD ([Official repositories])
```pkgin install gospt``` ```pkgin install gospt```
# To build from source by pulling and building the binary # To build from source by pulling and building the binary
```git clone https://gitea.asdf.cafe/abs3nt/gospt```
```git clone https://git.asdf.cafe/abs3nt/gospt```
```cd gospt``` ```cd gospt```
```make build && sudo make install``` ```make build && sudo make install```
[AUR]: https://aur.archlinux.org/packages/gospt [AUR]: https://aur.archlinux.org/packages/gospt
@ -72,6 +78,6 @@ To view help:
Very open to contributations feel free to open a PR Very open to contributations feel free to open a PR
[tmux plugin](https://gitea.asdf.cafe/abs3nt/tmux-gospt) [tmux plugin](https://git.asdf.cafe/abs3nt/tmux-gospt)
[wiki](https://gitea.asdf.cafe/abs3nt/gospt/wiki) [wiki](https://git.asdf.cafe/abs3nt/gospt/wiki)

20
cmd/download_cover.go Normal file
View File

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

View File

@ -19,7 +19,7 @@ var linkCmd = &cobra.Command{
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
fmt.Print(link) fmt.Println(link)
}, },
} }

View File

@ -25,6 +25,6 @@ var nextCmd = &cobra.Command{
return err return err
} }
} }
return commands.Next(ctx, skipAmt) return commands.Next(ctx, skipAmt, false)
}, },
} }

View File

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

View File

@ -8,10 +8,10 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
cmds "gitea.asdf.cafe/abs3nt/gospt/src/commands" cmds "git.asdf.cafe/abs3nt/gospt/src/commands"
"gitea.asdf.cafe/abs3nt/gospt/src/config" "git.asdf.cafe/abs3nt/gospt/src/config"
"gitea.asdf.cafe/abs3nt/gospt/src/gctx" "git.asdf.cafe/abs3nt/gospt/src/gctx"
"tuxpa.in/a/zlog" "tuxpa.in/a/zlog"
"github.com/cristalhq/aconfig" "github.com/cristalhq/aconfig"
@ -21,11 +21,10 @@ import (
var ( var (
// Used for flags. // Used for flags.
ctx *gctx.Context ctx *gctx.Context
commands *cmds.Commands commands *cmds.Commands
cfgFile string cfgFile string
userLicense string verbose bool
verbose bool
rootCmd = &cobra.Command{ rootCmd = &cobra.Command{
Use: "gospt", Use: "gospt",
@ -90,7 +89,12 @@ func initConfig() {
} }
if config.Values.ClientSecretCmd != "" { if config.Values.ClientSecretCmd != "" {
args := strings.Fields(config.Values.ClientSecretCmd) args := strings.Fields(config.Values.ClientSecretCmd)
secret, err := exec.Command(args[0], args[1:]...).Output() cmd := args[0]
secret_command := exec.Command(cmd)
if len(args) > 1 {
secret_command.Args = args
}
secret, err := secret_command.Output()
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -1,7 +1,7 @@
package cmd package cmd
import ( import (
"gitea.asdf.cafe/abs3nt/gospt/src/tui" "git.asdf.cafe/abs3nt/gospt/src/tui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View File

@ -4,7 +4,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.asdf.cafe/abs3nt/gospt/src/tui" "git.asdf.cafe/abs3nt/gospt/src/tui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

View File

@ -4,7 +4,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.asdf.cafe/abs3nt/gospt/src/tui" "git.asdf.cafe/abs3nt/gospt/src/tui"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )

23
cmd/version.go Normal file
View File

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

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

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

93
go.mod
View File

@ -1,63 +1,74 @@
module gitea.asdf.cafe/abs3nt/gospt module git.asdf.cafe/abs3nt/gospt
go 1.19 go 1.21
require ( require (
gfx.cafe/util/go/frand v0.0.0-20230121041905-80dafb1e973e gfx.cafe/util/go/frand v0.0.0-20230721185457-c559e86c829c
github.com/atotto/clipboard v0.1.4 github.com/atotto/clipboard v0.1.4
github.com/charmbracelet/bubbles v0.14.0 github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.23.1 github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.6.0 github.com/charmbracelet/lipgloss v0.9.1
github.com/cristalhq/aconfig v0.18.3 github.com/cristalhq/aconfig v0.18.5
github.com/cristalhq/aconfig/aconfigyaml v0.17.1 github.com/cristalhq/aconfig/aconfigyaml v0.17.1
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.7.0
github.com/zmb3/spotify/v2 v2.3.1 github.com/zmb3/spotify/v2 v2.4.0
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 golang.org/x/net v0.17.0
modernc.org/sqlite v1.20.4 golang.org/x/oauth2 v0.13.0
tuxpa.in/a/zlog v1.60.0 golang.org/x/sync v0.4.0
google.golang.org/api v0.148.0
modernc.org/sqlite v1.26.0
tuxpa.in/a/zlog v1.61.0
) )
require ( require (
cloud.google.com/go/compute v1.23.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/containerd/console v1.0.3 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/uuid v1.3.0 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.13.0 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/rs/zerolog v1.28.0 // indirect github.com/rs/zerolog v1.31.0 // indirect
github.com/sahilm/fuzzy v0.1.0 // indirect github.com/sahilm/fuzzy v0.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/mod v0.3.0 // indirect go.opencensus.io v0.24.0 // indirect
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect golang.org/x/crypto v0.14.0 // indirect
golang.org/x/sys v0.1.0 // indirect golang.org/x/mod v0.13.0 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/term v0.13.0 // indirect
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect golang.org/x/text v0.13.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect golang.org/x/tools v0.14.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect lukechampine.com/uint128 v1.3.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/cc/v3 v3.41.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect modernc.org/ccgo/v3 v3.16.15 // indirect
modernc.org/libc v1.22.2 // indirect modernc.org/libc v1.25.0 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.4.0 // indirect modernc.org/memory v1.7.2 // indirect
modernc.org/opt v0.1.3 // indirect modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.0.1 // indirect modernc.org/token v1.1.0 // indirect
) )

233
go.sum
View File

@ -19,6 +19,10 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0=
cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@ -31,46 +35,45 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gfx.cafe/util/go/frand v0.0.0-20230121041905-80dafb1e973e h1:A62zlsu3HkEAVRIb+cCpRIpSTmd047+ABV1KC2RsI2U= gfx.cafe/util/go/frand v0.0.0-20230721185457-c559e86c829c h1:ZCCkzLPmcbw9tcON8DOx7YMZQIRzd/Z+5RH8bcvrNsA=
gfx.cafe/util/go/frand v0.0.0-20230121041905-80dafb1e973e/go.mod h1:LNHxMJl0WnIr5+OChYxlVopxk+j7qxZv0XvWCzB6uGE= gfx.cafe/util/go/frand v0.0.0-20230721185457-c559e86c829c/go.mod h1:LNHxMJl0WnIr5+OChYxlVopxk+j7qxZv0XvWCzB6uGE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og= github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc= github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4= github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck= github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs= github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY= github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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.4.0/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.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E= github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig v0.18.3 h1:Or12LIWIF+2mQpcGWA2PQnNc55+WiHFAqRjYh/pQNtM= github.com/cristalhq/aconfig v0.18.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
github.com/cristalhq/aconfig v0.18.3/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E= github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
github.com/cristalhq/aconfig/aconfigyaml v0.17.1 h1:xCCbRKVmKrft9gQj3gHOq6U5PduasvlXEIsxtyzmFZ0= github.com/cristalhq/aconfig/aconfigyaml v0.17.1 h1:xCCbRKVmKrft9gQj3gHOq6U5PduasvlXEIsxtyzmFZ0=
github.com/cristalhq/aconfig/aconfigyaml v0.17.1/go.mod h1:5DTsjHkvQ6hfbyxfG32roB1lF0U82rROtFaLxibL8V8= github.com/cristalhq/aconfig/aconfigyaml v0.17.1/go.mod h1:5DTsjHkvQ6hfbyxfG32roB1lF0U82rROtFaLxibL8V8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -83,6 +86,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -103,9 +108,11 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -115,8 +122,10 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -127,15 +136,22 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/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/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -156,68 +172,78 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 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-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= 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.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= 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.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zmb3/spotify/v2 v2.3.1 h1:aEyIPotROM3JJjHMCImFROgnPIUpzVo8wymYSaPSd9w= github.com/zmb3/spotify/v2 v2.4.0 h1:ZHdhBx/Qyn7rtVDP+onk/oSvtL5uVyJtb+VBLrNDC7Y=
github.com/zmb3/spotify/v2 v2.3.1/go.mod h1:+LVh9CafHu7SedyqYmEf12Rd01dIVlEL845yNhksW0E= github.com/zmb3/spotify/v2 v2.4.0/go.mod h1:m6c3mHgZSt1rTF76UfSfdn1Gb2Kx/B/ClCcr+2V1Scw=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -247,8 +273,10 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -275,16 +303,20 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o=
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -293,7 +325,9 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -321,28 +355,34 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-20220204135822-1c1b9b1eba6a/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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -386,12 +426,12 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@ -409,14 +449,17 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.148.0 h1:HBq4TZlN4/1pNcu0geJZ/Q50vIwIXT532UIMYoo0vOs=
google.golang.org/api v0.148.0/go.mod h1:8/TBgwaKjfqTdacOJrOv2+2Q6fBDU1uHKK06oGSkxzU=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -446,6 +489,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8=
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE=
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -458,6 +507,9 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -470,8 +522,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -487,32 +540,36 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/libc v1.25.0 h1:nbTrmtr6NSdZe59ut3MvXS4ZQQPfxhicocdlKbkmZkg=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/libc v1.25.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE= modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw=
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34= modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
tuxpa.in/a/zlog v1.60.0 h1:bU4wJk6nwvaFsKIvKKxgGM0uO+Z2kaE8LzgRiQ4NCRw= tuxpa.in/a/zlog v1.61.0 h1:7wrS6G4QwpnOmgHRQknrr7IgiMXrfGpekkU0PjM9FhE=
tuxpa.in/a/zlog v1.60.0/go.mod h1:1t8SX1a4zLy+p6ylGn6m1ZXnssTPr/2ErdPjjSP+C2k= tuxpa.in/a/zlog v1.61.0/go.mod h1:CNpMe8laDHLSypx/DyxfX1S0oyxUydeo3aGTEbtRBhg=

View File

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

View File

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

2
src/cache/cache.go vendored
View File

@ -42,7 +42,7 @@ func (c *Cache) save(m map[string]CacheEntry) error {
return err return err
} }
log.Trace().Str("tosave", string(payload)).Msg("saving cache") log.Trace().Str("tosave", string(payload)).Msg("saving cache")
err = os.WriteFile(c.Root, payload, 0640) err = os.WriteFile(c.Root, payload, 0o600)
if err != nil { if err != nil {
return err return err
} }

View File

@ -15,12 +15,13 @@ import (
"time" "time"
"gfx.cafe/util/go/frand" "gfx.cafe/util/go/frand"
"gitea.asdf.cafe/abs3nt/gospt/src/auth"
"gitea.asdf.cafe/abs3nt/gospt/src/cache"
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
"git.asdf.cafe/abs3nt/gospt/src/auth"
"git.asdf.cafe/abs3nt/gospt/src/cache"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
"git.asdf.cafe/abs3nt/gospt/src/youtube"
) )
type Commands struct { type Commands struct {
@ -147,7 +148,8 @@ func (c *Commands) UserArtists(ctx *gctx.Context, page int) (*spotify.FullArtist
} }
func (c *Commands) ArtistAlbums(ctx *gctx.Context, artist spotify.ID, page int) (*spotify.SimpleAlbumPage, error) { func (c *Commands) ArtistAlbums(ctx *gctx.Context, artist spotify.ID, page int) (*spotify.SimpleAlbumPage, error) {
albums, err := c.Client().GetArtistAlbums(ctx, artist, []spotify.AlbumType{1, 2, 3, 4}, spotify.Market(spotify.CountryUSA), spotify.Limit(50), spotify.Offset((page-1)*50)) albums, err := c.Client().
GetArtistAlbums(ctx, artist, []spotify.AlbumType{1, 2, 3, 4}, spotify.Market(spotify.CountryUSA), spotify.Limit(50), spotify.Offset((page-1)*50))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -155,7 +157,8 @@ func (c *Commands) ArtistAlbums(ctx *gctx.Context, artist spotify.ID, page int)
} }
func (c *Commands) Search(ctx *gctx.Context, search string, page int) (*spotify.SearchResult, error) { func (c *Commands) Search(ctx *gctx.Context, search string, page int) (*spotify.SearchResult, error) {
result, err := c.Client().Search(ctx, search, spotify.SearchTypeAlbum|spotify.SearchTypeArtist|spotify.SearchTypeTrack|spotify.SearchTypePlaylist, spotify.Limit(50), spotify.Offset((page-1)*50)) result, err := c.Client().
Search(ctx, search, spotify.SearchTypeAlbum|spotify.SearchTypeArtist|spotify.SearchTypeTrack|spotify.SearchTypePlaylist, spotify.Limit(50), spotify.Offset((page-1)*50))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -163,7 +166,8 @@ func (c *Commands) Search(ctx *gctx.Context, search string, page int) (*spotify.
} }
func (c *Commands) AlbumTracks(ctx *gctx.Context, album spotify.ID, page int) (*spotify.SimpleTrackPage, error) { func (c *Commands) AlbumTracks(ctx *gctx.Context, album spotify.ID, page int) (*spotify.SimpleTrackPage, error) {
tracks, err := c.Client().GetAlbumTracks(ctx, album, spotify.Limit(50), spotify.Offset((page-1)*50), spotify.Market(spotify.CountryUSA)) tracks, err := c.Client().
GetAlbumTracks(ctx, album, spotify.Limit(50), spotify.Offset((page-1)*50), spotify.Market(spotify.CountryUSA))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -171,11 +175,11 @@ func (c *Commands) AlbumTracks(ctx *gctx.Context, album spotify.ID, page int) (*
} }
func (c *Commands) UserAlbums(ctx *gctx.Context, page int) (*spotify.SavedAlbumPage, error) { func (c *Commands) UserAlbums(ctx *gctx.Context, page int) (*spotify.SavedAlbumPage, error) {
albums, err := c.Client().CurrentUsersAlbums(ctx, spotify.Limit(50), spotify.Offset((page-1)*50)) return c.Client().CurrentUsersAlbums(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
if err != nil { }
return nil, err
} func (c *Commands) UserQueue(ctx *gctx.Context) (*spotify.Queue, error) {
return albums, nil return c.Client().GetQueue(ctx)
} }
func (c *Commands) PlayUrl(ctx *gctx.Context, args []string) error { func (c *Commands) PlayUrl(ctx *gctx.Context, args []string) error {
@ -237,7 +241,7 @@ func (c *Commands) QueueSong(ctx *gctx.Context, id spotify.ID) error {
return nil return nil
} }
func (c *Commands) PlaySongInPlaylist(ctx *gctx.Context, context *spotify.URI, offset int) error { func (c *Commands) PlaySongInPlaylist(ctx *gctx.Context, context *spotify.URI, offset *int) error {
e := c.Client().PlayOpt(ctx, &spotify.PlayOptions{ e := c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackOffset: &spotify.PlaybackOffset{Position: offset}, PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
PlaybackContext: context, PlaybackContext: context,
@ -304,9 +308,6 @@ func (c *Commands) PlayLikedSongs(ctx *gctx.Context, position int) error {
} }
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{ err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &playlist.URI, PlaybackContext: &playlist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
}) })
if err != nil { if err != nil {
if isNoActiveError(err) { if isNoActiveError(err) {
@ -316,10 +317,7 @@ func (c *Commands) PlayLikedSongs(ctx *gctx.Context, position int) error {
} }
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{ err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &playlist.URI, PlaybackContext: &playlist.URI,
PlaybackOffset: &spotify.PlaybackOffset{ DeviceID: &deviceID,
Position: 0,
},
DeviceID: &deviceID,
}) })
if err != nil { if err != nil {
return err return err
@ -381,9 +379,6 @@ func (c *Commands) RadioGivenArtist(ctx *gctx.Context, artist spotify.SimpleArti
} }
c.Client().PlayOpt(ctx, &spotify.PlayOptions{ c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI, PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
}) })
err = c.Client().Repeat(ctx, "context") err = c.Client().Repeat(ctx, "context")
if err != nil { if err != nil {
@ -469,10 +464,7 @@ func (c *Commands) RadioGivenSong(ctx *gctx.Context, song spotify.SimpleTrack, p
} }
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{ err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI, PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{ PositionMs: pos,
Position: 0,
},
PositionMs: pos,
}) })
if err != nil { if err != nil {
if isNoActiveError(err) { if isNoActiveError(err) {
@ -482,11 +474,8 @@ func (c *Commands) RadioGivenSong(ctx *gctx.Context, song spotify.SimpleTrack, p
} }
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{ err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI, PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{ DeviceID: &deviceID,
Position: 0, PositionMs: pos,
},
DeviceID: &deviceID,
PositionMs: pos,
}) })
if err != nil { if err != nil {
return err return err
@ -528,10 +517,18 @@ func (c *Commands) RadioGivenSong(ctx *gctx.Context, song spotify.SimpleTrack, p
return nil return nil
} }
func (c *Commands) DeleteTracksFromPlaylist(ctx *gctx.Context, tracks []spotify.ID, playlist spotify.ID) error {
_, err := c.Client().RemoveTracksFromPlaylist(ctx, playlist, tracks...)
if err != nil {
return err
}
return nil
}
func (c *Commands) SongExists(db *sql.DB, song spotify.ID) (bool, error) { func (c *Commands) SongExists(db *sql.DB, song spotify.ID) (bool, error) {
song_id := string(song) song_id := string(song)
sqlStmt := `SELECT id FROM radio WHERE id = ?` sqlStmt := `SELECT id FROM radio WHERE id = ?`
err := db.QueryRow(sqlStmt, string(song_id)).Scan(&song_id) err := db.QueryRow(sqlStmt, song_id).Scan(&song_id)
if err != nil { if err != nil {
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
return false, err return false, err
@ -563,15 +560,12 @@ func (c *Commands) Radio(ctx *gctx.Context) error {
return err return err
} }
seed_song = tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack seed_song = tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack
} else { } else if !current_song.Playing {
if !current_song.Playing { tracks, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(10))
if err != nil {
tracks, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(10)) return err
if err != nil {
return err
}
seed_song = tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack
} }
seed_song = tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack
} }
return c.RadioGivenSong(ctx, seed_song, current_song.Progress) return c.RadioGivenSong(ctx, seed_song, current_song.Progress)
} }
@ -587,6 +581,7 @@ func (c *Commands) RefillRadio(ctx *gctx.Context) error {
to_remove := []spotify.ID{} to_remove := []spotify.ID{}
radioPlaylist, db, err := c.GetRadioPlaylist(ctx, "") radioPlaylist, db, err := c.GetRadioPlaylist(ctx, "")
if err != nil { if err != nil {
return err
} }
if status.PlaybackContext.URI != radioPlaylist.URI { if status.PlaybackContext.URI != radioPlaylist.URI {
@ -598,20 +593,17 @@ func (c *Commands) RefillRadio(ctx *gctx.Context) error {
return fmt.Errorf("orig playlist items: %w", err) return fmt.Errorf("orig playlist items: %w", err)
} }
found := false
page := 0 page := 0
for !found { for {
tracks, err := c.Client().GetPlaylistItems(ctx, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50)) tracks, err := c.Client().GetPlaylistItems(ctx, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50))
if err != nil { if err != nil {
return fmt.Errorf("tracks: %w", err) return fmt.Errorf("tracks: %w", err)
} }
if len(tracks.Items) == 0 { if len(tracks.Items) == 0 {
found = true
break break
} }
for _, track := range tracks.Items { for _, track := range tracks.Items {
if track.Track.Track.ID == status.Item.ID { if track.Track.Track.ID == status.Item.ID {
found = true
break break
} }
to_remove = append(to_remove, track.Track.Track.ID) to_remove = append(to_remove, track.Track.Track.ID)
@ -630,7 +622,7 @@ func (c *Commands) RefillRadio(ctx *gctx.Context) error {
return fmt.Errorf("error clearing playlist: %w", err) return fmt.Errorf("error clearing playlist: %w", err)
} }
} }
_, err = c.Client().RemoveTracksFromPlaylist(ctx, radioPlaylist.ID, trackGroups...) c.Client().RemoveTracksFromPlaylist(ctx, radioPlaylist.ID, trackGroups...)
} }
to_add := 500 - (playlistItems.Total - len(to_remove)) to_add := 500 - (playlistItems.Total - len(to_remove))
@ -642,9 +634,10 @@ func (c *Commands) RefillRadio(ctx *gctx.Context) error {
pages := int(math.Ceil(float64(total) / 50)) pages := int(math.Ceil(float64(total) / 50))
randomPage := 1 randomPage := 1
if pages > 1 { if pages > 1 {
randomPage = frand.Intn(int(pages-1)) + 1 randomPage = frand.Intn(pages-1) + 1
} }
playlistPage, err := c.Client().GetPlaylistItems(ctx, radioPlaylist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50)) playlistPage, err := c.Client().
GetPlaylistItems(ctx, radioPlaylist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50))
if err != nil { if err != nil {
return fmt.Errorf("playlist page: %w", err) return fmt.Errorf("playlist page: %w", err)
} }
@ -684,6 +677,9 @@ func (c *Commands) RefillRadio(ctx *gctx.Context) error {
break break
} }
_, err = db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", rec.String())) _, err = db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", rec.String()))
if err != nil {
return err
}
queue = append(queue, rec) queue = append(queue, rec)
} }
to_add -= len(queue) to_add -= len(queue)
@ -762,7 +758,7 @@ func (c *Commands) Pause(ctx *gctx.Context) error {
func (c *Commands) TogglePlay(ctx *gctx.Context) error { func (c *Commands) TogglePlay(ctx *gctx.Context) error {
current, err := c.Client().PlayerCurrentlyPlaying(ctx) current, err := c.Client().PlayerCurrentlyPlaying(ctx)
if err != nil { if err != nil {
return err return c.Play(ctx)
} }
if !current.Playing { if !current.Playing {
return c.Play(ctx) return c.Play(ctx)
@ -794,7 +790,13 @@ func (c *Commands) Unlike(ctx *gctx.Context) error {
return nil return nil
} }
func (c *Commands) Next(ctx *gctx.Context, amt int) error { func (c *Commands) Next(ctx *gctx.Context, amt int, inqueue bool) error {
if inqueue {
for i := 0; i < amt; i++ {
c.Client().Next(ctx)
}
return nil
}
if amt == 1 { if amt == 1 {
err := c.Client().Next(ctx) err := c.Client().Next(ctx)
if err != nil { if err != nil {
@ -825,9 +827,10 @@ func (c *Commands) Next(ctx *gctx.Context, amt int) error {
case "playlist": case "playlist":
found := false found := false
currentTrackIndex := 0 currentTrackIndex := 0
page := 1
for !found { for !found {
page := 1 playlist, err := c.Client().
playlist, err := c.Client().GetPlaylistItems(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50)) GetPlaylistItems(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50))
if err != nil { if err != nil {
return err return err
} }
@ -840,19 +843,21 @@ func (c *Commands) Next(ctx *gctx.Context, amt int) error {
} }
page++ page++
} }
pos := currentTrackIndex + amt
c.Client().PlayOpt(ctx, &spotify.PlayOptions{ c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &current.PlaybackContext.URI, PlaybackContext: &current.PlaybackContext.URI,
PlaybackOffset: &spotify.PlaybackOffset{ PlaybackOffset: &spotify.PlaybackOffset{
Position: currentTrackIndex + amt, Position: &pos,
}, },
}) })
return nil return nil
case "album": case "album":
found := false found := false
currentTrackIndex := 0 currentTrackIndex := 0
page := 1
for !found { for !found {
page := 1 playlist, err := c.Client().
playlist, err := c.Client().GetAlbumTracks(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50)) GetAlbumTracks(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50))
if err != nil { if err != nil {
return err return err
} }
@ -865,10 +870,11 @@ func (c *Commands) Next(ctx *gctx.Context, amt int) error {
} }
page++ page++
} }
pos := currentTrackIndex + amt
c.Client().PlayOpt(ctx, &spotify.PlayOptions{ c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &current.PlaybackContext.URI, PlaybackContext: &current.PlaybackContext.URI,
PlaybackOffset: &spotify.PlaybackOffset{ PlaybackOffset: &spotify.PlaybackOffset{
Position: currentTrackIndex + amt, Position: &pos,
}, },
}) })
return nil return nil
@ -903,10 +909,24 @@ func (c *Commands) Status(ctx *gctx.Context) error {
if err != nil { if err != nil {
return err return err
} }
fmt.Println(state)
return nil
}
func (c *Commands) DownloadCover(ctx *gctx.Context, args []string) error {
destinationPath := filepath.Clean(args[0])
state, err := c.Client().PlayerState(ctx)
if err != nil {
return err
}
f, err := os.OpenFile(destinationPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755)
if err != nil {
return err
}
err = state.Item.Album.Images[0].Download(f)
if err != nil { if err != nil {
return err return err
} }
fmt.Println(state)
return nil return nil
} }
@ -918,15 +938,35 @@ func (c *Commands) Link(ctx *gctx.Context) (string, error) {
return state.Item.ExternalURLs["spotify"], nil return state.Item.ExternalURLs["spotify"], nil
} }
func (c *Commands) YoutubeLink(ctx *gctx.Context) (string, error) {
state, err := c.Client().PlayerState(ctx)
if err != nil {
return "", err
}
link := youtube.Search(state.Item.Artists[0].Name + state.Item.Name)
return link, nil
}
func (c *Commands) LinkContext(ctx *gctx.Context) (string, error) { func (c *Commands) LinkContext(ctx *gctx.Context) (string, error) {
state, err := c.Client().PlayerState(ctx) state, err := c.Client().PlayerState(ctx)
if err != nil { if err != nil {
return "", err return "", err
} }
return string(state.PlaybackContext.ExternalURLs["spotify"]), nil return state.PlaybackContext.ExternalURLs["spotify"], nil
} }
func (c *Commands) NowPlaying(ctx *gctx.Context) error { func (c *Commands) NowPlaying(ctx *gctx.Context, args []string) error {
if len(args) > 0 {
if args[0] == "force" {
current, err := c.Client().PlayerCurrentlyPlaying(ctx)
if err != nil {
return err
}
str := FormatSong(current)
fmt.Println(str)
return nil
}
}
song, err := cache.DefaultCache().GetOrDo("now_playing", func() (string, error) { song, err := cache.DefaultCache().GetOrDo("now_playing", func() (string, error) {
current, err := c.Client().PlayerCurrentlyPlaying(ctx) current, err := c.Client().PlayerCurrentlyPlaying(ctx)
if err != nil { if err != nil {
@ -943,17 +983,25 @@ func (c *Commands) NowPlaying(ctx *gctx.Context) error {
} }
func FormatSong(current *spotify.CurrentlyPlaying) string { func FormatSong(current *spotify.CurrentlyPlaying) string {
icon := "▶" out := "▶"
if !current.Playing { if !current.Playing {
icon = "⏸" out = "⏸"
} }
return fmt.Sprintf("%s %s - %s", icon, current.Item.Name, current.Item.Artists[0].Name) if current != nil {
if current.Item != nil {
out += fmt.Sprintf(" %s", current.Item.Name)
if len(current.Item.Artists) > 0 {
out += fmt.Sprintf(" - %s", current.Item.Artists[0].Name)
}
}
}
return out
} }
func (c *Commands) Shuffle(ctx *gctx.Context) error { func (c *Commands) Shuffle(ctx *gctx.Context) error {
state, err := c.Client().PlayerState(ctx) state, err := c.Client().PlayerState(ctx)
if err != nil { if err != nil {
return fmt.Errorf("Failed to get current playstate") return fmt.Errorf("failed to get current playstate")
} }
err = c.Client().Shuffle(ctx, !state.ShuffleState) err = c.Client().Shuffle(ctx, !state.ShuffleState)
if err != nil { if err != nil {
@ -966,7 +1014,7 @@ func (c *Commands) Shuffle(ctx *gctx.Context) error {
func (c *Commands) Repeat(ctx *gctx.Context) error { func (c *Commands) Repeat(ctx *gctx.Context) error {
state, err := c.Client().PlayerState(ctx) state, err := c.Client().PlayerState(ctx)
if err != nil { if err != nil {
return fmt.Errorf("Failed to get current playstate") return fmt.Errorf("failed to get current playstate")
} }
newState := "off" newState := "off"
if state.RepeatState == "off" { if state.RepeatState == "off" {
@ -985,16 +1033,12 @@ func (c *Commands) TrackList(ctx *gctx.Context, page int) (*spotify.SavedTrackPa
return c.Client().CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset((page-1)*50)) return c.Client().CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
} }
func (c *Commands) GetQueue(ctx *gctx.Context) (*spotify.Queue, error) {
return c.Client().GetQueue(ctx)
}
func (c *Commands) Playlists(ctx *gctx.Context, page int) (*spotify.SimplePlaylistPage, error) { func (c *Commands) Playlists(ctx *gctx.Context, page int) (*spotify.SimplePlaylistPage, error) {
return c.Client().CurrentUsersPlaylists(ctx, spotify.Limit(50), spotify.Offset((page-1)*50)) return c.Client().CurrentUsersPlaylists(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
} }
func (c *Commands) PlaylistTracks(ctx *gctx.Context, playlist spotify.ID, page int) (*spotify.PlaylistTrackPage, error) { func (c *Commands) PlaylistTracks(ctx *gctx.Context, playlist spotify.ID, page int) (*spotify.PlaylistItemPage, error) {
return c.Client().GetPlaylistTracks(ctx, playlist, spotify.Limit(50), spotify.Offset((page-1)*50)) return c.Client().GetPlaylistItems(ctx, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
} }
func (c *Commands) FormatState(state *spotify.PlayerState) (string, error) { func (c *Commands) FormatState(state *spotify.PlayerState) (string, error) {
@ -1012,7 +1056,7 @@ func (c *Commands) PrintPlaying(current *spotify.CurrentlyPlaying) error {
if !current.Playing { if !current.Playing {
icon = "⏸" icon = "⏸"
} }
fmt.Println(fmt.Sprintf("%s %s - %s", icon, current.Item.Name, current.Item.Artists[0].Name)) fmt.Printf("%s %s - %s\n", icon, current.Item.Name, current.Item.Artists[0].Name)
return nil return nil
} }
@ -1031,7 +1075,7 @@ func (c *Commands) SetDevice(ctx *gctx.Context, device spotify.PlayerDevice) err
return err return err
} }
configDir, _ := os.UserConfigDir() configDir, _ := os.UserConfigDir()
err = os.WriteFile(filepath.Join(configDir, "gospt/device.json"), out, 0o644) err = os.WriteFile(filepath.Join(configDir, "gospt/device.json"), out, 0o600)
if err != nil { if err != nil {
return err return err
} }
@ -1049,12 +1093,12 @@ func isNoActiveError(err error) bool {
func (c *Commands) RadioFromPlaylist(ctx *gctx.Context, playlist spotify.SimplePlaylist) error { func (c *Commands) RadioFromPlaylist(ctx *gctx.Context, playlist spotify.SimplePlaylist) error {
total := playlist.Tracks.Total total := playlist.Tracks.Total
if total == 0 { if total == 0 {
return fmt.Errorf("This playlist is empty") return fmt.Errorf("this playlist is empty")
} }
pages := int(math.Ceil(float64(total) / 50)) pages := int(math.Ceil(float64(total) / 50))
randomPage := 1 randomPage := 1
if pages > 1 { if pages > 1 {
randomPage = frand.Intn(int(pages-1)) + 1 randomPage = frand.Intn(pages-1) + 1
} }
playlistPage, err := c.Client().GetPlaylistItems(ctx, playlist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50)) playlistPage, err := c.Client().GetPlaylistItems(ctx, playlist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50))
if err != nil { if err != nil {
@ -1083,12 +1127,12 @@ func (c *Commands) RadioFromAlbum(ctx *gctx.Context, album spotify.SimpleAlbum)
} }
total := tracks.Total total := tracks.Total
if total == 0 { if total == 0 {
return fmt.Errorf("This playlist is empty") return fmt.Errorf("this playlist is empty")
} }
pages := int(math.Ceil(float64(total) / 50)) pages := int(math.Ceil(float64(total) / 50))
randomPage := 1 randomPage := 1
if pages > 1 { if pages > 1 {
randomPage = frand.Intn(int(pages-1)) + 1 randomPage = frand.Intn(pages-1) + 1
} }
albumTrackPage, err := c.AlbumTracks(ctx, album.ID, randomPage) albumTrackPage, err := c.AlbumTracks(ctx, album.ID, randomPage)
if err != nil { if err != nil {
@ -1116,12 +1160,12 @@ func (c *Commands) RadioFromSavedTracks(ctx *gctx.Context) error {
return err return err
} }
if savedSongs.Total == 0 { if savedSongs.Total == 0 {
return fmt.Errorf("You have no saved songs") return fmt.Errorf("you have no saved songs")
} }
pages := int(math.Ceil(float64(savedSongs.Total) / 50)) pages := int(math.Ceil(float64(savedSongs.Total) / 50))
randomPage := 1 randomPage := 1
if pages > 1 { if pages > 1 {
randomPage = frand.Intn(int(pages-1)) + 1 randomPage = frand.Intn(pages-1) + 1
} }
trackPage, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset(randomPage*50)) trackPage, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(50), spotify.Offset(randomPage*50))
if err != nil { if err != nil {
@ -1181,9 +1225,6 @@ func (c *Commands) RadioGivenList(ctx *gctx.Context, song_ids []spotify.ID, name
} }
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{ err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI, PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{
Position: 0,
},
}) })
if err != nil { if err != nil {
if isNoActiveError(err) { if isNoActiveError(err) {
@ -1193,10 +1234,7 @@ func (c *Commands) RadioGivenList(ctx *gctx.Context, song_ids []spotify.ID, name
} }
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{ err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
PlaybackContext: &radioPlaylist.URI, PlaybackContext: &radioPlaylist.URI,
PlaybackOffset: &spotify.PlaybackOffset{ DeviceID: &deviceId,
Position: 0,
},
DeviceID: &deviceId,
}) })
if err != nil { if err != nil {
return err return err
@ -1261,29 +1299,6 @@ func (c *Commands) activateDevice(ctx *gctx.Context) (spotify.ID, error) {
return device.ID, nil return device.ID, nil
} }
func (c *Commands) getDefaultDevice(ctx *gctx.Context) (spotify.ID, error) {
configDir, _ := os.UserConfigDir()
if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err == nil {
deviceFile, err := os.Open(filepath.Join(configDir, "gospt/device.json"))
if err != nil {
return "", err
}
defer deviceFile.Close()
deviceValue, err := io.ReadAll(deviceFile)
if err != nil {
return "", err
}
var device *spotify.PlayerDevice
err = json.Unmarshal(deviceValue, &device)
if err != nil {
return "", err
}
return device.ID, nil
} else {
return "", err
}
}
func (c *Commands) GetRadioPlaylist(ctx *gctx.Context, name string) (*spotify.FullPlaylist, *sql.DB, error) { func (c *Commands) GetRadioPlaylist(ctx *gctx.Context, name string) (*spotify.FullPlaylist, *sql.DB, error) {
configDir, _ := os.UserConfigDir() configDir, _ := os.UserConfigDir()
playlistFile, err := os.ReadFile(filepath.Join(configDir, "gospt/radio.json")) playlistFile, err := os.ReadFile(filepath.Join(configDir, "gospt/radio.json"))
@ -1299,13 +1314,17 @@ func (c *Commands) GetRadioPlaylist(ctx *gctx.Context, name string) (*spotify.Fu
return nil, nil, err return nil, nil, err
} }
db, err := sql.Open("sqlite", filepath.Join(configDir, "gospt/radio.db")) db, err := sql.Open("sqlite", filepath.Join(configDir, "gospt/radio.db"))
if err != nil {
return nil, nil, err
}
return playlist, db, nil return playlist, db, nil
} }
func (c *Commands) CreateRadioPlaylist(ctx *gctx.Context, name string) (*spotify.FullPlaylist, *sql.DB, error) { func (c *Commands) CreateRadioPlaylist(ctx *gctx.Context, name string) (*spotify.FullPlaylist, *sql.DB, error) {
// private flag doesnt work // private flag doesnt work
configDir, _ := os.UserConfigDir() configDir, _ := os.UserConfigDir()
playlist, err := c.Client().CreatePlaylistForUser(ctx, c.User(), name+" - autoradio", "Automanaged radio playlist", false, false) playlist, err := c.Client().
CreatePlaylistForUser(ctx, c.User(), name+" - autoradio", "Automanaged radio playlist", false, false)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -1313,11 +1332,14 @@ func (c *Commands) CreateRadioPlaylist(ctx *gctx.Context, name string) (*spotify
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
err = os.WriteFile(filepath.Join(configDir, "gospt/radio.json"), raw, 0o644) err = os.WriteFile(filepath.Join(configDir, "gospt/radio.json"), raw, 0o600)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
db, err := sql.Open("sqlite", filepath.Join(configDir, "gospt/radio.db")) db, err := sql.Open("sqlite", filepath.Join(configDir, "gospt/radio.db"))
if err != nil {
return nil, nil, err
}
db.QueryContext(ctx, "DROP TABLE IF EXISTS radio") db.QueryContext(ctx, "DROP TABLE IF EXISTS radio")
db.QueryContext(ctx, "CREATE TABLE IF NOT EXISTS radio (id string PRIMARY KEY)") db.QueryContext(ctx, "CREATE TABLE IF NOT EXISTS radio (id string PRIMARY KEY)")
return playlist, db, nil return playlist, db, nil

View File

@ -1,15 +1,14 @@
package tui package tui
import ( import (
"gitea.asdf.cafe/abs3nt/gospt/src/commands"
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
"git.asdf.cafe/abs3nt/gospt/src/commands"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
) )
func HandlePlayWithContext(ctx *gctx.Context, commands *commands.Commands, uri *spotify.URI, pos int) { func HandlePlayWithContext(ctx *gctx.Context, commands *commands.Commands, uri *spotify.URI, pos *int) {
var err error err := commands.PlaySongInPlaylist(ctx, uri, pos)
err = commands.PlaySongInPlaylist(ctx, uri, pos)
if err != nil { if err != nil {
return return
} }
@ -87,15 +86,35 @@ func HandlePlayTrack(ctx *gctx.Context, commands *commands.Commands, track spoti
if err != nil { if err != nil {
return return
} }
err = commands.Next(ctx, 1) err = commands.Next(ctx, 1, false)
if err != nil {
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 { if err != nil {
return return
} }
} }
func HandleSetDevice(ctx *gctx.Context, commands *commands.Commands, player spotify.PlayerDevice) { func HandleSetDevice(ctx *gctx.Context, commands *commands.Commands, player spotify.PlayerDevice) {
var err error err := commands.SetDevice(ctx, player)
err = commands.SetDevice(ctx, player)
if err != nil { if err != nil {
return return
} }

View File

@ -8,10 +8,14 @@ import (
) )
func (m *mainModel) LoadMoreItems() { func (m *mainModel) LoadMoreItems() {
loading = true
defer func() {
page++
loading = false
}()
switch m.mode { switch m.mode {
case "artist": case "artist":
albums, err := m.commands.ArtistAlbums(m.ctx, m.artist.ID, (page + 1)) albums, err := m.commands.ArtistAlbums(m.ctx, m.artist.ID, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -31,7 +35,6 @@ func (m *mainModel) LoadMoreItems() {
return return
case "artists": case "artists":
artists, err := m.commands.UserArtists(m.ctx, (page + 1)) artists, err := m.commands.UserArtists(m.ctx, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -51,7 +54,6 @@ func (m *mainModel) LoadMoreItems() {
return return
case "album": case "album":
tracks, err := m.commands.AlbumTracks(m.ctx, m.album.ID, (page + 1)) tracks, err := m.commands.AlbumTracks(m.ctx, m.album.ID, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -72,7 +74,6 @@ func (m *mainModel) LoadMoreItems() {
return return
case "albums": case "albums":
albums, err := m.commands.UserAlbums(m.ctx, (page + 1)) albums, err := m.commands.UserAlbums(m.ctx, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -92,7 +93,6 @@ func (m *mainModel) LoadMoreItems() {
return return
case "main": case "main":
playlists, err := m.commands.Playlists(m.ctx, (page + 1)) playlists, err := m.commands.Playlists(m.ctx, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
@ -110,19 +110,18 @@ func (m *mainModel) LoadMoreItems() {
main_updates <- m main_updates <- m
return return
case "playlist": case "playlist":
tracks, err := m.commands.PlaylistTracks(m.ctx, m.playlist.ID, (page + 1)) playlistItems, err := m.commands.PlaylistTracks(m.ctx, m.playlist.ID, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
items := []mainItem{} items := []mainItem{}
for _, track := range tracks.Tracks { for _, item := range playlistItems.Items {
items = append(items, mainItem{ items = append(items, mainItem{
Name: track.Track.Name, Name: item.Track.Track.Name,
Artist: track.Track.Artists[0], Artist: item.Track.Track.Artists[0],
Duration: track.Track.TimeDuration().Round(time.Second).String(), Duration: item.Track.Track.TimeDuration().Round(time.Second).String(),
ID: track.Track.ID, ID: item.Track.Track.ID,
Desc: track.Track.Artists[0].Name + " - " + track.Track.TimeDuration().Round(time.Second).String(), Desc: item.Track.Track.Artists[0].Name + " - " + item.Track.Track.TimeDuration().Round(time.Second).String(),
}) })
} }
for _, item := range items { for _, item := range items {
@ -132,11 +131,9 @@ func (m *mainModel) LoadMoreItems() {
return return
case "tracks": case "tracks":
tracks, err := m.commands.TrackList(m.ctx, (page + 1)) tracks, err := m.commands.TrackList(m.ctx, (page + 1))
page++
if err != nil { if err != nil {
return return
} }
page++
items := []list.Item{} items := []list.Item{}
for _, track := range tracks.Tracks { for _, track := range tracks.Tracks {
items = append(items, mainItem{ items = append(items, mainItem{

View File

@ -5,9 +5,6 @@ import (
"strings" "strings"
"time" "time"
"gitea.asdf.cafe/abs3nt/gospt/src/commands"
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
"github.com/atotto/clipboard" "github.com/atotto/clipboard"
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
@ -16,6 +13,9 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
"git.asdf.cafe/abs3nt/gospt/src/commands"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
) )
var ( var (
@ -25,30 +25,33 @@ var (
playbackContext string playbackContext string
main_updates chan *mainModel main_updates chan *mainModel
page = 1 page = 1
loading = false
showingMessage = false
) )
type Mode string type Mode string
const ( const (
Album Mode = "album" Album Mode = "album"
ArtistAlbum = "artistalbum" ArtistAlbum Mode = "artistalbum"
Artist = "artist" Artist Mode = "artist"
Artists = "artists" Artists Mode = "artists"
Tracks = "tracks" Queue Mode = "queue"
Albums = "albums" Tracks Mode = "tracks"
Main = "main" Albums Mode = "albums"
Playlists = "playlists" Main Mode = "main"
Playlist = "playlist" Playlists Mode = "playlists"
Devices = "devices" Playlist Mode = "playlist"
Search = "search" Devices Mode = "devices"
SearchAlbums = "searchalbums" Search Mode = "search"
SearchAlbum = "searchalbum" SearchAlbums Mode = "searchalbums"
SearchArtists = "searchartists" SearchAlbum Mode = "searchalbum"
SearchArtist = "searchartist" SearchArtists Mode = "searchartists"
SearchArtistAlbum = "searchartistalbum" SearchArtist Mode = "searchartist"
SearchTracks = "searchtracks" SearchArtistAlbum Mode = "searchartistalbum"
SearchPlaylists = "searchplaylsits" SearchTracks Mode = "searchtracks"
SearchPlaylist = "searchplaylist" SearchPlaylists Mode = "searchplaylsits"
SearchPlaylist Mode = "searchplaylist"
) )
type mainItem struct { type mainItem struct {
@ -88,50 +91,55 @@ type mainModel struct {
} }
func (m *mainModel) PlayRadio() { func (m *mainModel) PlayRadio() {
m.list.NewStatusMessage("Starting radio for " + m.list.SelectedItem().(mainItem).Title()) go m.SendMessage("Starting radio for "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
selectedItem := m.list.SelectedItem().(mainItem).SpotifyItem selectedItem := m.list.SelectedItem().(mainItem).SpotifyItem
switch selectedItem.(type) { switch item := selectedItem.(type) {
case spotify.SimplePlaylist: case spotify.SimplePlaylist:
go HandlePlaylistRadio(m.ctx, m.commands, selectedItem.(spotify.SimplePlaylist)) go HandlePlaylistRadio(m.ctx, m.commands, item)
return return
case *spotify.SavedTrackPage: case *spotify.SavedTrackPage:
go HandleLibraryRadio(m.ctx, m.commands) go HandleLibraryRadio(m.ctx, m.commands)
return return
case spotify.SimpleAlbum: case spotify.SimpleAlbum:
go HandleAlbumRadio(m.ctx, m.commands, selectedItem.(spotify.SimpleAlbum)) go HandleAlbumRadio(m.ctx, m.commands, item)
return return
case spotify.FullAlbum: case spotify.FullAlbum:
go HandleAlbumRadio(m.ctx, m.commands, selectedItem.(spotify.FullAlbum).SimpleAlbum) go HandleAlbumRadio(m.ctx, m.commands, item.SimpleAlbum)
return return
case spotify.SimpleArtist: case spotify.SimpleArtist:
go HandleArtistRadio(m.ctx, m.commands, selectedItem.(spotify.SimpleArtist)) go HandleArtistRadio(m.ctx, m.commands, item)
return return
case spotify.FullArtist: case spotify.FullArtist:
go HandleArtistRadio(m.ctx, m.commands, selectedItem.(spotify.FullArtist).SimpleArtist) go HandleArtistRadio(m.ctx, m.commands, item.SimpleArtist)
return return
case spotify.SimpleTrack: case spotify.SimpleTrack:
go HandleRadio(m.ctx, m.commands, selectedItem.(spotify.SimpleTrack)) go HandleRadio(m.ctx, m.commands, item)
return return
case spotify.FullTrack: case spotify.FullTrack:
go HandleRadio(m.ctx, m.commands, selectedItem.(spotify.FullTrack).SimpleTrack) go HandleRadio(m.ctx, m.commands, item.SimpleTrack)
return return
case spotify.PlaylistTrack: case spotify.PlaylistTrack:
go HandleRadio(m.ctx, m.commands, selectedItem.(spotify.PlaylistTrack).Track.SimpleTrack) go HandleRadio(m.ctx, m.commands, item.Track.SimpleTrack)
return
case spotify.PlaylistItem:
go HandleRadio(m.ctx, m.commands, item.Track.Track.SimpleTrack)
return return
case spotify.SavedTrack: case spotify.SavedTrack:
go HandleRadio(m.ctx, m.commands, selectedItem.(spotify.SavedTrack).SimpleTrack) go HandleRadio(m.ctx, m.commands, item.SimpleTrack)
return return
} }
} }
func (m *mainModel) GoBack() (tea.Cmd, error) { func (m *mainModel) GoBack() (tea.Cmd, error) {
page = 1
switch m.mode { switch m.mode {
case Main: case Main:
return tea.Quit, nil return tea.Quit, nil
case Albums, Artists, Tracks, Playlist, Devices, Search: case Albums, Artists, Tracks, Playlist, Devices, Search, Queue:
m.mode = Main m.mode = Main
new_items, err := MainView(m.ctx, m.commands) new_items, err := MainView(m.ctx, m.commands)
if err != nil { if err != nil {
return nil, err
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
case Album: case Album:
@ -203,48 +211,122 @@ type SpotifyUrl struct {
func (m *mainModel) CopyToClipboard() error { func (m *mainModel) CopyToClipboard() error {
item := m.list.SelectedItem().(mainItem).SpotifyItem item := m.list.SelectedItem().(mainItem).SpotifyItem
switch item.(type) { switch converted := item.(type) {
case spotify.SimplePlaylist: case spotify.SimplePlaylist:
clipboard.WriteAll(item.(spotify.SimplePlaylist).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.ExternalURLs["spotify"])
case *spotify.FullPlaylist: case *spotify.FullPlaylist:
clipboard.WriteAll(item.(*spotify.FullPlaylist).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.SimpleAlbum: case spotify.SimpleAlbum:
clipboard.WriteAll(item.(spotify.SimpleAlbum).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.ExternalURLs["spotify"])
case *spotify.FullAlbum: case *spotify.FullAlbum:
clipboard.WriteAll(item.(*spotify.FullAlbum).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.SimpleArtist: case spotify.SimpleArtist:
clipboard.WriteAll(item.(spotify.SimpleArtist).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.ExternalURLs["spotify"])
case *spotify.FullArtist: case *spotify.FullArtist:
clipboard.WriteAll(item.(*spotify.FullArtist).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.SimpleTrack: case spotify.SimpleTrack:
clipboard.WriteAll(item.(spotify.SimpleTrack).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.PlaylistTrack: case spotify.PlaylistTrack:
clipboard.WriteAll(item.(spotify.PlaylistTrack).Track.ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.Track.ExternalURLs["spotify"])
case spotify.SavedTrack: case spotify.SavedTrack:
clipboard.WriteAll(item.(spotify.SavedTrack).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.ExternalURLs["spotify"])
case spotify.FullTrack: case spotify.FullTrack:
clipboard.WriteAll(item.(spotify.FullTrack).ExternalURLs["spotify"]) go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title()) clipboard.WriteAll(converted.ExternalURLs["spotify"])
} }
return nil return nil
} }
func (m *mainModel) SendMessage(msg string, duration time.Duration) {
showingMessage = true
defer func() {
showingMessage = false
}()
m.list.NewStatusMessage(msg)
time.Sleep(duration)
}
func (m *mainModel) QueueItem() error {
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
case spotify.PlaylistTrack:
go m.SendMessage("Adding "+item.Track.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.Track.ID)
case spotify.SavedTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case spotify.SimpleTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case spotify.FullTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case *spotify.FullTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case *spotify.SimpleTrack:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
case *spotify.SimplePlaylist:
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
go HandleQueueItem(m.ctx, m.commands, item.ID)
}
if m.mode == Queue {
go func() {
new_items, err := QueueView(m.ctx, m.commands)
if err != nil {
return
}
m.list.SetItems(new_items)
}()
}
return nil
}
func (m *mainModel) DeleteTrackFromPlaylist() error {
if m.mode != Playlist {
return nil
}
track := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlaylistTrack).Track
go m.SendMessage("Deleteing "+track.Name+" from "+m.playlist.Name, 2*time.Second)
go func() {
HandleDeleteTrackFromPlaylist(m.ctx, m.commands, track.ID, m.playlist.ID)
new_items, err := PlaylistView(m.ctx, m.commands, m.playlist)
if err != nil {
return
}
m.list.SetItems(new_items)
}()
return nil
}
func (m *mainModel) SelectItem() error { func (m *mainModel) SelectItem() error {
switch m.mode { switch m.mode {
case Queue:
page = 1
go func() {
HandleNextInQueue(m.ctx, m.commands, m.list.Index())
new_items, err := QueueView(m.ctx, m.commands)
if err != nil {
return
}
m.list.SetItems(new_items)
m.list.ResetSelected()
}()
case Search: case Search:
switch m.list.SelectedItem().(mainItem).SpotifyItem.(type) { page = 1
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
case *spotify.FullArtistPage: case *spotify.FullArtistPage:
m.mode = SearchArtists m.mode = SearchArtists
new_items, err := SearchArtistsView(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.FullArtistPage)) new_items, err := SearchArtistsView(m.ctx, m.commands, item)
if err != nil { if err != nil {
return err return err
} }
@ -252,7 +334,7 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case *spotify.SimpleAlbumPage: case *spotify.SimpleAlbumPage:
m.mode = SearchAlbums m.mode = SearchAlbums
new_items, err := SearchAlbumsView(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.SimpleAlbumPage)) new_items, err := SearchAlbumsView(m.ctx, m.commands, item)
if err != nil { if err != nil {
return err return err
} }
@ -260,8 +342,7 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case *spotify.SimplePlaylistPage: case *spotify.SimplePlaylistPage:
m.mode = SearchPlaylists m.mode = SearchPlaylists
playlists := m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.SimplePlaylistPage) new_items, err := SearchPlaylistsView(m.ctx, m.commands, item)
new_items, err := SearchPlaylistsView(m.ctx, m.commands, playlists)
if err != nil { if err != nil {
return err return err
} }
@ -269,7 +350,7 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case *spotify.FullTrackPage: case *spotify.FullTrackPage:
m.mode = SearchTracks m.mode = SearchTracks
new_items, err := SearchTracksView(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.FullTrackPage)) new_items, err := SearchTracksView(m.ctx, m.commands, item)
if err != nil { if err != nil {
return err return err
} }
@ -277,6 +358,7 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
} }
case SearchArtists: case SearchArtists:
page = 1
m.mode = SearchArtist m.mode = SearchArtist
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist) m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands) new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
@ -286,6 +368,7 @@ func (m *mainModel) SelectItem() error {
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case SearchArtist: case SearchArtist:
page = 1
m.mode = SearchArtistAlbum m.mode = SearchArtistAlbum
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum) m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands) new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
@ -295,6 +378,7 @@ func (m *mainModel) SelectItem() error {
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case SearchAlbums: case SearchAlbums:
page = 1
m.mode = SearchAlbum m.mode = SearchAlbum
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum) m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands) new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
@ -304,6 +388,7 @@ func (m *mainModel) SelectItem() error {
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case SearchPlaylists: case SearchPlaylists:
page = 1
m.mode = SearchPlaylist m.mode = SearchPlaylist
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist) playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
m.playlist = playlist m.playlist = playlist
@ -314,7 +399,16 @@ func (m *mainModel) SelectItem() error {
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case Main: case Main:
switch m.list.SelectedItem().(mainItem).SpotifyItem.(type) { page = 1
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
case spotify.Queue:
m.mode = Queue
new_items, err := QueueView(m.ctx, m.commands)
if err != nil {
return err
}
m.list.SetItems(new_items)
m.list.ResetSelected()
case *spotify.FullArtistCursorPage: case *spotify.FullArtistCursorPage:
m.mode = Artists m.mode = Artists
new_items, err := ArtistsView(m.ctx, m.commands) new_items, err := ArtistsView(m.ctx, m.commands)
@ -333,9 +427,8 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
case spotify.SimplePlaylist: case spotify.SimplePlaylist:
m.mode = Playlist m.mode = Playlist
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist) m.playlist = item
m.playlist = playlist new_items, err := PlaylistView(m.ctx, m.commands, item)
new_items, err := PlaylistView(m.ctx, m.commands, playlist)
if err != nil { if err != nil {
return err return err
} }
@ -351,6 +444,7 @@ func (m *mainModel) SelectItem() error {
m.list.ResetSelected() m.list.ResetSelected()
} }
case Albums: case Albums:
page = 1
m.mode = Album m.mode = Album
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum) m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands) new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
@ -378,18 +472,19 @@ func (m *mainModel) SelectItem() error {
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
case Album, ArtistAlbum, SearchArtistAlbum, SearchAlbum: case Album, ArtistAlbum, SearchArtistAlbum, SearchAlbum:
go HandlePlayWithContext(m.ctx, m.commands, &m.album.URI, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.TotalPages)) pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.TotalPages)
go HandlePlayWithContext(m.ctx, m.commands, &m.album.URI, &pos)
case Playlist, SearchPlaylist: case Playlist, SearchPlaylist:
go HandlePlayWithContext(m.ctx, m.commands, &m.playlist.URI, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage)) pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.PerPage)
go HandlePlayWithContext(m.ctx, m.commands, &m.playlist.URI, &pos)
case Tracks: case Tracks:
go HandlePlayLikedSong(m.ctx, m.commands, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage)) go HandlePlayLikedSong(m.ctx, m.commands, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage))
case SearchTracks: case SearchTracks:
go HandlePlayTrack(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.FullTrack).ID) go HandlePlayTrack(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.FullTrack).ID)
case Devices: case Devices:
go HandleSetDevice(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlayerDevice)) go HandleSetDevice(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlayerDevice))
m.list.NewStatusMessage("Setting device to " + m.list.SelectedItem().FilterValue()) go m.SendMessage("Setting device to "+m.list.SelectedItem().FilterValue(), 2*time.Second)
m.mode = "main" m.mode = "main"
m.list.NewStatusMessage("Setting view to main")
new_items, err := MainView(m.ctx, m.commands) new_items, err := MainView(m.ctx, m.commands)
if err != nil { if err != nil {
return err return err
@ -399,7 +494,7 @@ func (m *mainModel) SelectItem() error {
return nil return nil
} }
func (m mainModel) Init() tea.Cmd { func (m *mainModel) Init() tea.Cmd {
main_updates = make(chan *mainModel) main_updates = make(chan *mainModel)
return Tick() return Tick()
} }
@ -437,7 +532,7 @@ func (m *mainModel) TickPlayback() {
}() }()
} }
func (m mainModel) View() string { func (m *mainModel) View() string {
if m.input.Focused() { if m.input.Focused() {
return DocStyle.Render(m.list.View() + "\n" + m.input.View()) return DocStyle.Render(m.list.View() + "\n" + m.input.View())
} }
@ -497,7 +592,7 @@ func (m *mainModel) getContext(playing *spotify.CurrentlyPlaying) (string, error
return "", 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 list items from LoadMore // Update list items from LoadMore
select { select {
case update := <-main_updates: case update := <-main_updates:
@ -505,7 +600,7 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
default: default:
} }
// Call for more items if needed // Call for more items if needed
if m.list.Paginator.Page == m.list.Paginator.TotalPages-2 && m.list.Cursor() == 0 { if m.list.Paginator.Page == m.list.Paginator.TotalPages-1 && m.list.Cursor() == 0 && !loading {
// if last request was still full request more // if last request was still full request more
if len(m.list.Items())%50 == 0 { if len(m.list.Items())%50 == 0 {
go m.LoadMoreItems() go m.LoadMoreItems()
@ -519,6 +614,17 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmd := m.progress.SetPercent(float64(playing.Progress) / float64(playing.Item.Duration)) cmd := m.progress.SetPercent(float64(playing.Progress) / float64(playing.Item.Duration))
m.playing = playing m.playing = playing
m.playbackContext = playbackContext m.playbackContext = playbackContext
if m.mode == Queue && len(m.list.Items()) != 0 {
if m.list.Items()[0].(mainItem).SpotifyItem.(spotify.FullTrack).Name != playing.Item.Name {
go func() {
new_items, err := QueueView(m.ctx, m.commands)
if err != nil {
return
}
m.list.SetItems(new_items)
}()
}
}
return m, tea.Batch(Tick(), cmd) return m, tea.Batch(Tick(), cmd)
} }
return m, Tick() return m, Tick()
@ -526,15 +632,17 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case progress.FrameMsg: case progress.FrameMsg:
progressModel, cmd := m.progress.Update(msg) progressModel, cmd := m.progress.Update(msg)
m.progress = progressModel.(progress.Model) m.progress = progressModel.(progress.Model)
m.list.NewStatusMessage( if !showingMessage {
fmt.Sprintf("Now playing %s by %s - %s %s/%s : %s", m.list.NewStatusMessage(
m.playing.Item.Name, fmt.Sprintf("Now playing %s by %s - %s %s/%s : %s",
m.playing.Item.Artists[0].Name, m.playing.Item.Name,
m.progress.View(), m.playing.Item.Artists[0].Name,
(time.Duration(m.playing.Progress) * time.Millisecond).Round(time.Second), m.progress.View(),
(time.Duration(m.playing.Item.Duration) * time.Millisecond).Round(time.Second), (time.Duration(m.playing.Progress) * time.Millisecond).Round(time.Second),
m.playbackContext), (time.Duration(m.playing.Item.Duration) * time.Millisecond).Round(time.Second),
) m.playbackContext),
)
}
return m, cmd return m, cmd
case tea.KeyMsg: case tea.KeyMsg:
// quit // quit
@ -580,7 +688,6 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
m.list.SetItems(new_items) m.list.SetItems(new_items)
m.list.ResetSelected() m.list.ResetSelected()
m.list.NewStatusMessage("Setting view to devices")
} }
// go back // go back
if msg.String() == "backspace" || msg.String() == "esc" || msg.String() == "q" { if msg.String() == "backspace" || msg.String() == "esc" || msg.String() == "q" {
@ -591,9 +698,20 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.list.ResetSelected() m.list.ResetSelected()
return m, msg return m, msg
} }
if msg.String() == "ctrl+d" {
err := m.DeleteTrackFromPlaylist()
if err != nil {
return m, tea.Quit
}
}
if msg.String() == "ctrl+@" || msg.String() == "ctrl+p" {
err := m.QueueItem()
if err != nil {
return m, tea.Quit
}
}
// select item // select item
if msg.String() == "enter" || msg.String() == "spacebar" { if msg.String() == "enter" || msg.String() == " " || msg.String() == "p" {
err := m.SelectItem() err := m.SelectItem()
if err != nil { if err != nil {
return m, tea.Quit return m, tea.Quit
@ -647,7 +765,7 @@ func InitMain(ctx *gctx.Context, c *commands.Commands, mode Mode) (tea.Model, er
return nil, err return nil, err
} }
} }
m := mainModel{ m := &mainModel{
list: list.New(items, list.NewDefaultDelegate(), 0, 0), list: list.New(items, list.NewDefaultDelegate(), 0, 0),
ctx: ctx, ctx: ctx,
commands: c, commands: c,
@ -658,27 +776,30 @@ func InitMain(ctx *gctx.Context, c *commands.Commands, mode Mode) (tea.Model, er
go m.TickPlayback() go m.TickPlayback()
Tick() Tick()
m.list.DisableQuitKeybindings() m.list.DisableQuitKeybindings()
m.list.SetFilteringEnabled(false)
m.list.AdditionalShortHelpKeys = func() []key.Binding { m.list.AdditionalShortHelpKeys = func() []key.Binding {
return []key.Binding{ return []key.Binding{
key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")), key.NewBinding(key.WithKeys("q"), key.WithHelp("q", "back")),
key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "search")), key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")), key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "search")),
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "start radio")), key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "radio")),
key.NewBinding(key.WithKeys("ctrl"+"shift"+"c"), key.WithHelp("ctrl+shift+c", "copy url")), key.NewBinding(key.WithKeys("ctrl"+"p"), key.WithHelp("ctrl+p", "queue")),
key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")), key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")),
} }
} }
m.list.AdditionalFullHelpKeys = func() []key.Binding { m.list.AdditionalFullHelpKeys = func() []key.Binding {
return []key.Binding{ return []key.Binding{
key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")), key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")),
key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "search")), key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "search")),
key.NewBinding(key.WithKeys(">"), key.WithHelp(">", "seek forward")), key.NewBinding(key.WithKeys(">"), key.WithHelp(">", "seek forward")),
key.NewBinding(key.WithKeys("<"), key.WithHelp("<", "seek backward")), key.NewBinding(key.WithKeys("<"), key.WithHelp("<", "seek backward")),
key.NewBinding(key.WithKeys("+"), key.WithHelp("+", "volume up")), key.NewBinding(key.WithKeys("+"), key.WithHelp("+", "volume up")),
key.NewBinding(key.WithKeys("-"), key.WithHelp("-", "volume down")), key.NewBinding(key.WithKeys("-"), key.WithHelp("-", "volume down")),
key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "copy link to item")),
key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")), key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")),
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "start radio")), key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "start radio")),
key.NewBinding(key.WithKeys("ctrl"+"shift"+"c"), key.WithHelp("ctrl+shift+c", "copy url")), key.NewBinding(key.WithKeys("ctrl"+"p"), key.WithHelp("ctrl+p", "queue song")),
key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")), key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")),
} }
} }

View File

@ -1,10 +1,8 @@
package tui package tui
import ( import (
"fmt" "git.asdf.cafe/abs3nt/gospt/src/commands"
"git.asdf.cafe/abs3nt/gospt/src/gctx"
"gitea.asdf.cafe/abs3nt/gospt/src/commands"
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
) )
@ -13,10 +11,10 @@ import (
func StartTea(ctx *gctx.Context, cmd *commands.Commands, mode string) error { func StartTea(ctx *gctx.Context, cmd *commands.Commands, mode string) error {
m, err := InitMain(ctx, cmd, Mode(mode)) m, err := InitMain(ctx, cmd, Mode(mode))
if err != nil { if err != nil {
fmt.Println("UH OH") return err
} }
P = tea.NewProgram(m, tea.WithAltScreen()) P = tea.NewProgram(m, tea.WithAltScreen())
if err := P.Start(); err != nil { if _, err := P.Run(); err != nil {
return err return err
} }
return nil return nil

View File

@ -2,16 +2,19 @@ package tui
import ( import (
"fmt" "fmt"
"sync" "regexp"
"time" "time"
"gitea.asdf.cafe/abs3nt/gospt/src/commands" "git.asdf.cafe/abs3nt/gospt/src/commands"
"gitea.asdf.cafe/abs3nt/gospt/src/gctx" "git.asdf.cafe/abs3nt/gospt/src/gctx"
"golang.org/x/sync/errgroup"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
"github.com/zmb3/spotify/v2" "github.com/zmb3/spotify/v2"
) )
const regex = `<.*?>`
func DeviceView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) { func DeviceView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
devices, err := commands.Client().PlayerDevices(ctx) devices, err := commands.Client().PlayerDevices(ctx)
@ -28,25 +31,54 @@ func DeviceView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, er
return items, nil return items, nil
} }
func PlaylistView(ctx *gctx.Context, commands *commands.Commands, playlist spotify.SimplePlaylist) ([]list.Item, error) { func QueueView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
tracks, err := commands.PlaylistTracks(ctx, playlist.ID, 1) tracks, err := commands.UserQueue(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, track := range tracks.Tracks { if tracks.CurrentlyPlaying.Name != "" {
items = append(items, mainItem{ items = append(items, mainItem{
Name: track.Track.Name, Name: tracks.CurrentlyPlaying.Name,
Artist: track.Track.Artists[0], Artist: tracks.CurrentlyPlaying.Artists[0],
Duration: track.Track.TimeDuration().Round(time.Second).String(), Duration: tracks.CurrentlyPlaying.TimeDuration().Round(time.Second).String(),
ID: track.Track.ID, ID: tracks.CurrentlyPlaying.ID,
Desc: track.Track.Artists[0].Name + " - " + track.Track.TimeDuration().Round(time.Second).String(), Desc: tracks.CurrentlyPlaying.Artists[0].Name + " - " + tracks.CurrentlyPlaying.TimeDuration().Round(time.Second).String(),
SpotifyItem: tracks.CurrentlyPlaying,
})
}
for _, track := range tracks.Items {
items = append(items, mainItem{
Name: track.Name,
Artist: track.Artists[0],
Duration: track.TimeDuration().Round(time.Second).String(),
ID: track.ID,
Desc: track.Artists[0].Name + " - " + track.TimeDuration().Round(time.Second).String(),
SpotifyItem: track, SpotifyItem: track,
}) })
} }
return items, nil return items, nil
} }
func PlaylistView(ctx *gctx.Context, commands *commands.Commands, playlist spotify.SimplePlaylist) ([]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) { func ArtistsView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
items := []list.Item{} items := []list.Item{}
artists, err := commands.UserArtists(ctx, 1) artists, err := commands.UserArtists(ctx, 1)
@ -57,7 +89,7 @@ func ArtistsView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, e
items = append(items, mainItem{ items = append(items, mainItem{
Name: artist.Name, Name: artist.Name,
ID: artist.ID, ID: artist.ID,
Desc: fmt.Sprintf("%d followers, genres: %s, popularity: %d", artist.Followers.Count, artist.Genres, artist.Popularity), Desc: fmt.Sprintf("%d followers", artist.Followers.Count),
SpotifyItem: artist.SimpleArtist, SpotifyItem: artist.SimpleArtist,
}) })
} }
@ -70,7 +102,7 @@ func SearchArtistsView(ctx *gctx.Context, commands *commands.Commands, artists *
items = append(items, mainItem{ items = append(items, mainItem{
Name: artist.Name, Name: artist.Name,
ID: artist.ID, ID: artist.ID,
Desc: fmt.Sprintf("%d followers, genres: %s, popularity: %d", artist.Followers.Count, artist.Genres, artist.Popularity), Desc: fmt.Sprintf("%d followers", artist.Followers.Count),
SpotifyItem: artist.SimpleArtist, SpotifyItem: artist.SimpleArtist,
}) })
} }
@ -84,27 +116,13 @@ func SearchView(ctx *gctx.Context, commands *commands.Commands, search string) (
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
items = append(items, mainItem{ items = append(
Name: "Tracks", items,
Desc: "Search results", mainItem{Name: "Tracks", Desc: "Search results", SpotifyItem: result.Tracks},
SpotifyItem: result.Tracks, mainItem{Name: "Albums", Desc: "Search results", SpotifyItem: result.Albums},
}) mainItem{Name: "Artists", Desc: "Search results", SpotifyItem: result.Artists},
items = append(items, mainItem{ mainItem{Name: "Playlists", Desc: "Search results", SpotifyItem: result.Playlists},
Name: "Albums", )
Desc: "Search results",
SpotifyItem: result.Albums,
})
items = append(items, mainItem{
Name: "Artists",
Desc: "Search results",
SpotifyItem: result.Artists,
})
items = append(items, mainItem{
Name: "Playlists",
Desc: "Search results",
SpotifyItem: result.Playlists,
})
results := &SearchResults{ results := &SearchResults{
Tracks: result.Tracks, Tracks: result.Tracks,
Playlists: result.Playlists, Playlists: result.Playlists,
@ -124,7 +142,7 @@ func AlbumsView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, er
items = append(items, mainItem{ items = append(items, mainItem{
Name: album.Name, Name: album.Name,
ID: album.ID, ID: album.ID,
Desc: fmt.Sprintf("%s, %d tracks", album.Artists[0].Name, album.Tracks.Total), Desc: fmt.Sprintf("%s by %s, %d tracks, released %d", album.AlbumType, album.Artists[0].Name, album.Tracks.Total, album.ReleaseDateTime().Year()),
SpotifyItem: album.SimpleAlbum, SpotifyItem: album.SimpleAlbum,
}) })
} }
@ -136,7 +154,7 @@ func SearchPlaylistsView(ctx *gctx.Context, commands *commands.Commands, playlis
for _, playlist := range playlists.Playlists { for _, playlist := range playlists.Playlists {
items = append(items, mainItem{ items = append(items, mainItem{
Name: playlist.Name, Name: playlist.Name,
Desc: playlist.Description, Desc: stripHtmlRegex(playlist.Description),
SpotifyItem: playlist, SpotifyItem: playlist,
}) })
} }
@ -149,7 +167,7 @@ func SearchAlbumsView(ctx *gctx.Context, commands *commands.Commands, albums *sp
items = append(items, mainItem{ items = append(items, mainItem{
Name: album.Name, Name: album.Name,
ID: album.ID, ID: album.ID,
Desc: fmt.Sprintf("%s, %d", album.Artists[0].Name, album.ReleaseDateTime()), Desc: fmt.Sprintf("%s by %s, released %d", album.AlbumType, album.Artists[0].Name, album.ReleaseDateTime().Year()),
SpotifyItem: album, SpotifyItem: album,
}) })
} }
@ -227,57 +245,36 @@ func SavedTracksView(ctx *gctx.Context, commands *commands.Commands) ([]list.Ite
} }
func MainView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) { func MainView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
var wg sync.WaitGroup wg := errgroup.Group{}
var saved_items *spotify.SavedTrackPage var saved_items *spotify.SavedTrackPage
var playlists *spotify.SimplePlaylistPage var playlists *spotify.SimplePlaylistPage
var artists *spotify.FullArtistCursorPage var artists *spotify.FullArtistCursorPage
var albums *spotify.SavedAlbumPage var albums *spotify.SavedAlbumPage
wg.Add(1) wg.Go(func() (err error) {
go func() {
defer wg.Done()
var err error
saved_items, err = commands.TrackList(ctx, 1) saved_items, err = commands.TrackList(ctx, 1)
if err != nil { return
fmt.Println(err.Error()) })
return
}
}()
wg.Add(1) wg.Go(func() (err error) {
go func() {
defer wg.Done()
var err error
playlists, err = commands.Playlists(ctx, 1) playlists, err = commands.Playlists(ctx, 1)
if err != nil { return
fmt.Println(err.Error()) })
return
}
}()
wg.Add(1) wg.Go(func() (err error) {
go func() {
defer wg.Done()
var err error
artists, err = commands.UserArtists(ctx, 1) artists, err = commands.UserArtists(ctx, 1)
if err != nil { return
fmt.Println(err.Error()) })
return
}
}()
wg.Add(1) wg.Go(func() (err error) {
go func() {
defer wg.Done()
var err error
albums, err = commands.UserAlbums(ctx, 1) albums, err = commands.UserAlbums(ctx, 1)
if err != nil { return
fmt.Println(err.Error()) })
return
}
}()
wg.Wait() err := wg.Wait()
if err != nil {
return nil, err
}
items := []list.Item{} items := []list.Item{}
if saved_items != nil && saved_items.Total != 0 { if saved_items != nil && saved_items.Total != 0 {
@ -301,14 +298,24 @@ func MainView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, erro
SpotifyItem: artists, SpotifyItem: artists,
}) })
} }
items = append(items, mainItem{
Name: "Queue",
Desc: "Your Current Queue",
SpotifyItem: spotify.Queue{},
})
if playlists != nil && playlists.Total != 0 { if playlists != nil && playlists.Total != 0 {
for _, playlist := range playlists.Playlists { for _, playlist := range playlists.Playlists {
items = append(items, mainItem{ items = append(items, mainItem{
Name: playlist.Name, Name: playlist.Name,
Desc: playlist.Description, Desc: stripHtmlRegex(playlist.Description),
SpotifyItem: playlist, SpotifyItem: playlist,
}) })
} }
} }
return items, nil return items, nil
} }
func stripHtmlRegex(s string) string {
r := regexp.MustCompile(regex)
return r.ReplaceAllString(s, "")
}

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

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

BIN
tmp/main

Binary file not shown.