Compare commits
37 Commits
Author | SHA1 | Date |
---|---|---|
abs3nt | 6bbcd97b1e | |
a | 2925b2c432 | |
abs3nt | 4cd781d5db | |
abs3nt | d4f4b142f7 | |
abs3nt | 2a942a3803 | |
abs3nt | 95c64ebd70 | |
abs3nt | e39355fb69 | |
abs3nt | 696ee7e263 | |
abs3nt | 84b3bd2f90 | |
abs3nt | b569bd5bd6 | |
abs3nt | e05573c2cf | |
abs3nt | 232a1cd3b6 | |
abs3nt | a34633ec6d | |
abs3nt | 5081c44304 | |
abs3nt | 7e22c90311 | |
abs3nt | efb03c1aad | |
abs3nt | b5f76fb321 | |
abs3nt | 6ed3453dae | |
abs3nt | a386f90ec5 | |
abs3nt | 81d5135d1a | |
abs3nt | 693b228194 | |
abs3nt | c124ec4eb9 | |
abs3nt | ec60177128 | |
abs3nt | 8f179f0bc6 | |
abs3nt | ee9c2299dc | |
abs3nt | 2fbeb6d21a | |
abs3nt | 55f9c73966 | |
abs3nt | 50d5cd18a8 | |
abs3nt | 2b0ea8ed9d | |
abs3nt | d222bc2e49 | |
abs3nt | 45ea45de40 | |
a | a1e9cc6dee | |
abs3nt | 7e65581333 | |
abs3nt | c930490f34 | |
abs3nt | 9a0cd91fcd | |
abs3nt | b02764ef33 | |
abs3nt | fe7f16b9ad |
|
@ -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)"
|
|
@ -1,6 +1,6 @@
|
|||
gitea_urls:
|
||||
api: https://gitea.asdf.cafe/api/v1
|
||||
download: https://gitea.asdf.cafe
|
||||
api: https://git.asdf.cafe/api/v1
|
||||
download: https://git.asdf.cafe
|
||||
skip_tls_verify: false
|
||||
|
||||
before:
|
||||
|
@ -17,6 +17,8 @@ builds:
|
|||
ignore:
|
||||
- goos: windows
|
||||
goarch: "386"
|
||||
ldflags:
|
||||
- -s -w -X git.asdf.cafe/abs3nt/gospt/src.cmd.Version={{.Version}}
|
||||
|
||||
archives:
|
||||
- format: tar.gz
|
||||
|
|
|
@ -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'
|
||||
|
|
@ -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:
|
||||
- /
|
|
@ -1,6 +1,6 @@
|
|||
pipeline:
|
||||
steps:
|
||||
build:
|
||||
image: golang:1.19
|
||||
image: golang:1.21
|
||||
commands:
|
||||
- go mod tidy
|
||||
- go build -o gospt
|
||||
|
@ -16,10 +16,3 @@ pipeline:
|
|||
secrets: [ gitea_token ]
|
||||
when:
|
||||
event: tag
|
||||
publish_aur:
|
||||
image: goreleaser/goreleaser
|
||||
commands:
|
||||
- goreleaser release --config .goreleaseraur.yaml --clean
|
||||
secrets: [ gitea_token ]
|
||||
when:
|
||||
event: tag
|
||||
|
|
18
README.md
18
README.md
|
@ -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.
|
||||
|
||||
|
@ -12,17 +12,23 @@ This project is still under heavy development and some things might not work or
|
|||
# To install (with a package manager):
|
||||
|
||||
## Archlinux ([AUR])
|
||||
```yay -S gospt```
|
||||
```yay -S gospt```
|
||||
|
||||
or
|
||||
|
||||
```yay -S gospt-git```
|
||||
|
||||
## NetBSD ([Official repositories])
|
||||
```pkgin install gospt```
|
||||
|
||||
# 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```
|
||||
|
||||
|
||||
```make build && sudo make install```
|
||||
|
||||
[AUR]: https://aur.archlinux.org/packages/gospt
|
||||
|
@ -72,6 +78,6 @@ To view help:
|
|||
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
}
|
|
@ -19,7 +19,7 @@ var linkCmd = &cobra.Command{
|
|||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Print(link)
|
||||
fmt.Println(link)
|
||||
},
|
||||
}
|
||||
|
|
@ -25,6 +25,6 @@ var nextCmd = &cobra.Command{
|
|||
return err
|
||||
}
|
||||
}
|
||||
return commands.Next(ctx, skipAmt)
|
||||
return commands.Next(ctx, skipAmt, false)
|
||||
},
|
||||
}
|
|
@ -13,7 +13,8 @@ var nowPlayingCmd = &cobra.Command{
|
|||
Aliases: []string{"now"},
|
||||
Short: "Shows song and artist of currently playing song",
|
||||
Long: `Shows song and artist of currently playing song, useful for scripting`,
|
||||
Args: cobra.MatchAll(cobra.RangeArgs(0, 1)),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
commands.NowPlaying(ctx)
|
||||
commands.NowPlaying(ctx, args)
|
||||
},
|
||||
}
|
|
@ -8,10 +8,10 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
cmds "gitea.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
cmds "git.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/config"
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/config"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"tuxpa.in/a/zlog"
|
||||
|
||||
"github.com/cristalhq/aconfig"
|
||||
|
@ -21,11 +21,10 @@ import (
|
|||
|
||||
var (
|
||||
// Used for flags.
|
||||
ctx *gctx.Context
|
||||
commands *cmds.Commands
|
||||
cfgFile string
|
||||
userLicense string
|
||||
verbose bool
|
||||
ctx *gctx.Context
|
||||
commands *cmds.Commands
|
||||
cfgFile string
|
||||
verbose bool
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "gospt",
|
||||
|
@ -90,7 +89,12 @@ func initConfig() {
|
|||
}
|
||||
if 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 {
|
||||
panic(err)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/tui"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/tui"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
|
@ -4,7 +4,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/tui"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/tui"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
|
@ -4,7 +4,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/tui"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/tui"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
|
@ -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)
|
||||
}
|
|
@ -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
93
go.mod
|
@ -1,63 +1,74 @@
|
|||
module gitea.asdf.cafe/abs3nt/gospt
|
||||
module git.asdf.cafe/abs3nt/gospt
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
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/charmbracelet/bubbles v0.14.0
|
||||
github.com/charmbracelet/bubbletea v0.23.1
|
||||
github.com/charmbracelet/lipgloss v0.6.0
|
||||
github.com/cristalhq/aconfig v0.18.3
|
||||
github.com/charmbracelet/bubbles v0.16.1
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/cristalhq/aconfig v0.18.5
|
||||
github.com/cristalhq/aconfig/aconfigyaml v0.17.1
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/zmb3/spotify/v2 v2.3.1
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5
|
||||
modernc.org/sqlite v1.20.4
|
||||
tuxpa.in/a/zlog v1.60.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/zmb3/spotify/v2 v2.4.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.13.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 (
|
||||
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/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/containerd/console v1.0.3 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/s2a-go v0.1.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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.13.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rs/zerolog v1.28.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rs/zerolog v1.31.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/mod v0.3.0 // indirect
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // 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
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.2 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.4.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
modernc.org/cc/v3 v3.41.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.15 // indirect
|
||||
modernc.org/libc v1.25.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
)
|
||||
|
|
233
go.sum
233
go.sum
|
@ -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.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0=
|
||||
cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78=
|
||||
cloud.google.com/go/compute/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.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
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.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
gfx.cafe/util/go/frand v0.0.0-20230121041905-80dafb1e973e h1:A62zlsu3HkEAVRIb+cCpRIpSTmd047+ABV1KC2RsI2U=
|
||||
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 h1:ZCCkzLPmcbw9tcON8DOx7YMZQIRzd/Z+5RH8bcvrNsA=
|
||||
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/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
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 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/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.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
|
||||
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
|
||||
github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck=
|
||||
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
|
||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
||||
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
|
||||
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
|
||||
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.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/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.3/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
github.com/cristalhq/aconfig v0.18.5 h1:QqXH/Gy2c4QUQJTV2BN8UAuL/rqZ3IwhvxeC8OgzquA=
|
||||
github.com/cristalhq/aconfig v0.18.5/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
github.com/cristalhq/aconfig/aconfigyaml v0.17.1 h1:xCCbRKVmKrft9gQj3gHOq6U5PduasvlXEIsxtyzmFZ0=
|
||||
github.com/cristalhq/aconfig/aconfigyaml v0.17.1/go.mod h1:5DTsjHkvQ6hfbyxfG32roB1lF0U82rROtFaLxibL8V8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
@ -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-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
|
@ -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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -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.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/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/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=
|
||||
|
@ -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-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.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.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.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/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
|
@ -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/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
|
||||
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.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/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.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
|
||||
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
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/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zmb3/spotify/v2 v2.3.1 h1:aEyIPotROM3JJjHMCImFROgnPIUpzVo8wymYSaPSd9w=
|
||||
github.com/zmb3/spotify/v2 v2.3.1/go.mod h1:+LVh9CafHu7SedyqYmEf12Rd01dIVlEL845yNhksW0E=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zmb3/spotify/v2 v2.4.0 h1:ZHdhBx/Qyn7rtVDP+onk/oSvtL5uVyJtb+VBLrNDC7Y=
|
||||
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.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
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-20180826012351-8a410e7b638d/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-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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o=
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
|
||||
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -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-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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220915200043-7b5979e65e41/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
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-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.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-20190308202827-9d24e82272b4/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-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-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
|
@ -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.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.148.0 h1:HBq4TZlN4/1pNcu0geJZ/Q50vIwIXT532UIMYoo0vOs=
|
||||
google.golang.org/api v0.148.0/go.mod h1:8/TBgwaKjfqTdacOJrOv2+2Q6fBDU1uHKK06oGSkxzU=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
|
@ -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-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8=
|
||||
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
@ -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.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
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 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
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-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=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q=
|
||||
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
|
||||
modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0=
|
||||
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/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
||||
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
|
||||
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v1.25.0 h1:nbTrmtr6NSdZe59ut3MvXS4ZQQPfxhicocdlKbkmZkg=
|
||||
modernc.org/libc v1.25.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
|
||||
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
||||
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
|
||||
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||
modernc.org/sqlite v1.26.0 h1:SocQdLRSYlA8W99V8YH0NES75thx19d9sB/aFc4R8Lw=
|
||||
modernc.org/sqlite v1.26.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
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/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
tuxpa.in/a/zlog v1.60.0 h1:bU4wJk6nwvaFsKIvKKxgGM0uO+Z2kaE8LzgRiQ4NCRw=
|
||||
tuxpa.in/a/zlog v1.60.0/go.mod h1:1t8SX1a4zLy+p6ylGn6m1ZXnssTPr/2ErdPjjSP+C2k=
|
||||
tuxpa.in/a/zlog v1.61.0 h1:7wrS6G4QwpnOmgHRQknrr7IgiMXrfGpekkU0PjM9FhE=
|
||||
tuxpa.in/a/zlog v1.61.0/go.mod h1:CNpMe8laDHLSypx/DyxfX1S0oyxUydeo3aGTEbtRBhg=
|
||||
|
|
2
main.go
2
main.go
|
@ -1,7 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/cmd"
|
||||
"git.asdf.cafe/abs3nt/gospt/cmd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/config"
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"tuxpa.in/a/zlog/log"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt/src/config"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
|
||||
"github.com/zmb3/spotify/v2"
|
||||
spotifyauth "github.com/zmb3/spotify/v2/auth"
|
||||
|
@ -25,6 +27,12 @@ var (
|
|||
configDir, _ = os.UserConfigDir()
|
||||
)
|
||||
|
||||
type roundTripperFunc func(*http.Request) (*http.Response, error)
|
||||
|
||||
func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return fn(req)
|
||||
}
|
||||
|
||||
func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
|
||||
if config.Values.ClientId == "" || config.Values.ClientSecret == "" || config.Values.Port == "" {
|
||||
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 {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
defer authFile.Close()
|
||||
authValue, err := io.ReadAll(authFile)
|
||||
tok := &oauth2.Token{}
|
||||
err = json.NewDecoder(authFile).Decode(tok)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tok *oauth2.Token
|
||||
err = json.Unmarshal(authValue, &tok)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := spotify.New(auth.Client(ctx, tok))
|
||||
ctx.Context = context.WithValue(ctx.Context, oauth2.HTTPClient, &http.Client{
|
||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||
log.Trace().Interface("path", r.URL.Path).Msg("request")
|
||||
return http.DefaultTransport.RoundTrip(r)
|
||||
}),
|
||||
})
|
||||
authClient := auth.Client(ctx, tok)
|
||||
client := spotify.New(authClient)
|
||||
new_token, err := client.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if new_token != tok {
|
||||
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, "", " ")
|
||||
out, err := json.MarshalIndent(new_token, "", " ")
|
||||
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 {
|
||||
panic("FAILED TO SAVE AUTH")
|
||||
return nil, fmt.Errorf("failed to save auth")
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
@ -101,11 +103,12 @@ func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
|
|||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("Got request for:", r.URL.String())
|
||||
})
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%s", config.Values.Port),
|
||||
ReadHeaderTimeout: 5 * time.Second,
|
||||
}
|
||||
go func() {
|
||||
err := http.ListenAndServe(fmt.Sprintf(":%s", config.Values.Port), nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
server.ListenAndServe()
|
||||
}()
|
||||
url := auth.AuthURL(state)
|
||||
fmt.Println(url)
|
||||
|
@ -114,6 +117,7 @@ func GetClient(ctx *gctx.Context) (*spotify.Client, error) {
|
|||
// wait for auth to complete
|
||||
client := <-ch
|
||||
|
||||
server.Shutdown(ctx)
|
||||
// use the client to make calls that require authorization
|
||||
user, err := client.CurrentUser(ctx)
|
||||
if err != nil {
|
||||
|
@ -127,7 +131,6 @@ func completeAuth(w http.ResponseWriter, r *http.Request) {
|
|||
tok, err := auth.Token(r.Context(), state, r)
|
||||
if err != nil {
|
||||
http.Error(w, "Couldn't get token", http.StatusForbidden)
|
||||
log.Fatal(err)
|
||||
}
|
||||
if st := r.FormValue("state"); st != state {
|
||||
http.NotFound(w, r)
|
||||
|
@ -137,7 +140,7 @@ func completeAuth(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
err = os.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o644)
|
||||
err = os.WriteFile(filepath.Join(configDir, "gospt/auth.json"), out, 0o600)
|
||||
if err != nil {
|
||||
panic("FAILED TO SAVE AUTH")
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func (c *Cache) save(m map[string]CacheEntry) error {
|
|||
return err
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -15,12 +15,13 @@ import (
|
|||
"time"
|
||||
|
||||
"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"
|
||||
_ "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 {
|
||||
|
@ -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) {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
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 {
|
||||
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) {
|
||||
albums, err := c.Client().CurrentUsersAlbums(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return albums, nil
|
||||
return c.Client().CurrentUsersAlbums(ctx, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
||||
|
||||
func (c *Commands) UserQueue(ctx *gctx.Context) (*spotify.Queue, error) {
|
||||
return c.Client().GetQueue(ctx)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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{
|
||||
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
|
||||
PlaybackContext: context,
|
||||
|
@ -304,9 +308,6 @@ func (c *Commands) PlayLikedSongs(ctx *gctx.Context, position int) error {
|
|||
}
|
||||
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
|
||||
PlaybackContext: &playlist.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: 0,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
|
@ -316,10 +317,7 @@ func (c *Commands) PlayLikedSongs(ctx *gctx.Context, position int) error {
|
|||
}
|
||||
err = c.Client().PlayOpt(ctx, &spotify.PlayOptions{
|
||||
PlaybackContext: &playlist.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: 0,
|
||||
},
|
||||
DeviceID: &deviceID,
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -381,9 +379,6 @@ func (c *Commands) RadioGivenArtist(ctx *gctx.Context, artist spotify.SimpleArti
|
|||
}
|
||||
c.Client().PlayOpt(ctx, &spotify.PlayOptions{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: 0,
|
||||
},
|
||||
})
|
||||
err = c.Client().Repeat(ctx, "context")
|
||||
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{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: 0,
|
||||
},
|
||||
PositionMs: pos,
|
||||
PositionMs: pos,
|
||||
})
|
||||
if err != nil {
|
||||
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{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: 0,
|
||||
},
|
||||
DeviceID: &deviceID,
|
||||
PositionMs: pos,
|
||||
DeviceID: &deviceID,
|
||||
PositionMs: pos,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -528,10 +517,18 @@ func (c *Commands) RadioGivenSong(ctx *gctx.Context, song spotify.SimpleTrack, p
|
|||
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) {
|
||||
song_id := string(song)
|
||||
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 != sql.ErrNoRows {
|
||||
return false, err
|
||||
|
@ -563,15 +560,12 @@ func (c *Commands) Radio(ctx *gctx.Context) error {
|
|||
return err
|
||||
}
|
||||
seed_song = tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack
|
||||
} else {
|
||||
if !current_song.Playing {
|
||||
|
||||
tracks, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(10))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seed_song = tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack
|
||||
} else if !current_song.Playing {
|
||||
tracks, err := c.Client().CurrentUsersTracks(ctx, spotify.Limit(10))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seed_song = tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack
|
||||
}
|
||||
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{}
|
||||
radioPlaylist, db, err := c.GetRadioPlaylist(ctx, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
found := false
|
||||
page := 0
|
||||
for !found {
|
||||
for {
|
||||
tracks, err := c.Client().GetPlaylistItems(ctx, radioPlaylist.ID, spotify.Limit(50), spotify.Offset(page*50))
|
||||
if err != nil {
|
||||
return fmt.Errorf("tracks: %w", err)
|
||||
}
|
||||
if len(tracks.Items) == 0 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
for _, track := range tracks.Items {
|
||||
if track.Track.Track.ID == status.Item.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
_, err = c.Client().RemoveTracksFromPlaylist(ctx, radioPlaylist.ID, trackGroups...)
|
||||
c.Client().RemoveTracksFromPlaylist(ctx, radioPlaylist.ID, trackGroups...)
|
||||
}
|
||||
|
||||
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))
|
||||
randomPage := 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 {
|
||||
return fmt.Errorf("playlist page: %w", err)
|
||||
}
|
||||
|
@ -684,6 +677,9 @@ func (c *Commands) RefillRadio(ctx *gctx.Context) error {
|
|||
break
|
||||
}
|
||||
_, err = db.QueryContext(ctx, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", rec.String()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queue = append(queue, rec)
|
||||
}
|
||||
to_add -= len(queue)
|
||||
|
@ -762,7 +758,7 @@ func (c *Commands) Pause(ctx *gctx.Context) error {
|
|||
func (c *Commands) TogglePlay(ctx *gctx.Context) error {
|
||||
current, err := c.Client().PlayerCurrentlyPlaying(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return c.Play(ctx)
|
||||
}
|
||||
if !current.Playing {
|
||||
return c.Play(ctx)
|
||||
|
@ -794,7 +790,13 @@ func (c *Commands) Unlike(ctx *gctx.Context) error {
|
|||
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 {
|
||||
err := c.Client().Next(ctx)
|
||||
if err != nil {
|
||||
|
@ -825,9 +827,10 @@ func (c *Commands) Next(ctx *gctx.Context, amt int) error {
|
|||
case "playlist":
|
||||
found := false
|
||||
currentTrackIndex := 0
|
||||
page := 1
|
||||
for !found {
|
||||
page := 1
|
||||
playlist, err := c.Client().GetPlaylistItems(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
playlist, err := c.Client().
|
||||
GetPlaylistItems(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -840,19 +843,21 @@ func (c *Commands) Next(ctx *gctx.Context, amt int) error {
|
|||
}
|
||||
page++
|
||||
}
|
||||
pos := currentTrackIndex + amt
|
||||
c.Client().PlayOpt(ctx, &spotify.PlayOptions{
|
||||
PlaybackContext: ¤t.PlaybackContext.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: currentTrackIndex + amt,
|
||||
Position: &pos,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
case "album":
|
||||
found := false
|
||||
currentTrackIndex := 0
|
||||
page := 1
|
||||
for !found {
|
||||
page := 1
|
||||
playlist, err := c.Client().GetAlbumTracks(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
playlist, err := c.Client().
|
||||
GetAlbumTracks(ctx, spotify.ID(strings.Split(string(current.PlaybackContext.URI), ":")[2]), spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -865,10 +870,11 @@ func (c *Commands) Next(ctx *gctx.Context, amt int) error {
|
|||
}
|
||||
page++
|
||||
}
|
||||
pos := currentTrackIndex + amt
|
||||
c.Client().PlayOpt(ctx, &spotify.PlayOptions{
|
||||
PlaybackContext: ¤t.PlaybackContext.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: currentTrackIndex + amt,
|
||||
Position: &pos,
|
||||
},
|
||||
})
|
||||
return nil
|
||||
|
@ -903,10 +909,24 @@ func (c *Commands) Status(ctx *gctx.Context) error {
|
|||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
fmt.Println(state)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -918,15 +938,35 @@ func (c *Commands) Link(ctx *gctx.Context) (string, error) {
|
|||
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) {
|
||||
state, err := c.Client().PlayerState(ctx)
|
||||
if err != nil {
|
||||
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) {
|
||||
current, err := c.Client().PlayerCurrentlyPlaying(ctx)
|
||||
if err != nil {
|
||||
|
@ -943,17 +983,25 @@ func (c *Commands) NowPlaying(ctx *gctx.Context) error {
|
|||
}
|
||||
|
||||
func FormatSong(current *spotify.CurrentlyPlaying) string {
|
||||
icon := "▶"
|
||||
out := "▶"
|
||||
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 {
|
||||
state, err := c.Client().PlayerState(ctx)
|
||||
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)
|
||||
if err != nil {
|
||||
|
@ -966,7 +1014,7 @@ func (c *Commands) Shuffle(ctx *gctx.Context) error {
|
|||
func (c *Commands) Repeat(ctx *gctx.Context) error {
|
||||
state, err := c.Client().PlayerState(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to get current playstate")
|
||||
return fmt.Errorf("failed to get current playstate")
|
||||
}
|
||||
newState := "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))
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
return c.Client().GetPlaylistTracks(ctx, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
func (c *Commands) PlaylistTracks(ctx *gctx.Context, playlist spotify.ID, page int) (*spotify.PlaylistItemPage, error) {
|
||||
return c.Client().GetPlaylistItems(ctx, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
||||
|
||||
func (c *Commands) FormatState(state *spotify.PlayerState) (string, error) {
|
||||
|
@ -1012,7 +1056,7 @@ func (c *Commands) PrintPlaying(current *spotify.CurrentlyPlaying) error {
|
|||
if !current.Playing {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -1031,7 +1075,7 @@ func (c *Commands) SetDevice(ctx *gctx.Context, device spotify.PlayerDevice) err
|
|||
return err
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -1049,12 +1093,12 @@ func isNoActiveError(err error) bool {
|
|||
func (c *Commands) RadioFromPlaylist(ctx *gctx.Context, playlist spotify.SimplePlaylist) error {
|
||||
total := playlist.Tracks.Total
|
||||
if total == 0 {
|
||||
return fmt.Errorf("This playlist is empty")
|
||||
return fmt.Errorf("this playlist is empty")
|
||||
}
|
||||
pages := int(math.Ceil(float64(total) / 50))
|
||||
randomPage := 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))
|
||||
if err != nil {
|
||||
|
@ -1083,12 +1127,12 @@ func (c *Commands) RadioFromAlbum(ctx *gctx.Context, album spotify.SimpleAlbum)
|
|||
}
|
||||
total := tracks.Total
|
||||
if total == 0 {
|
||||
return fmt.Errorf("This playlist is empty")
|
||||
return fmt.Errorf("this playlist is empty")
|
||||
}
|
||||
pages := int(math.Ceil(float64(total) / 50))
|
||||
randomPage := 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)
|
||||
if err != nil {
|
||||
|
@ -1116,12 +1160,12 @@ func (c *Commands) RadioFromSavedTracks(ctx *gctx.Context) error {
|
|||
return err
|
||||
}
|
||||
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))
|
||||
randomPage := 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))
|
||||
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{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: 0,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
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{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
PlaybackOffset: &spotify.PlaybackOffset{
|
||||
Position: 0,
|
||||
},
|
||||
DeviceID: &deviceId,
|
||||
DeviceID: &deviceId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1261,29 +1299,6 @@ func (c *Commands) activateDevice(ctx *gctx.Context) (spotify.ID, error) {
|
|||
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) {
|
||||
configDir, _ := os.UserConfigDir()
|
||||
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
|
||||
}
|
||||
db, err := sql.Open("sqlite", filepath.Join(configDir, "gospt/radio.db"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return playlist, db, nil
|
||||
}
|
||||
|
||||
func (c *Commands) CreateRadioPlaylist(ctx *gctx.Context, name string) (*spotify.FullPlaylist, *sql.DB, error) {
|
||||
// private flag doesnt work
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -1313,11 +1332,14 @@ func (c *Commands) CreateRadioPlaylist(ctx *gctx.Context, name string) (*spotify
|
|||
if err != nil {
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
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, "CREATE TABLE IF NOT EXISTS radio (id string PRIMARY KEY)")
|
||||
return playlist, db, nil
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
|
||||
"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) {
|
||||
var err error
|
||||
err = commands.PlaySongInPlaylist(ctx, uri, pos)
|
||||
func HandlePlayWithContext(ctx *gctx.Context, commands *commands.Commands, uri *spotify.URI, pos *int) {
|
||||
err := commands.PlaySongInPlaylist(ctx, uri, pos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -87,15 +86,35 @@ func HandlePlayTrack(ctx *gctx.Context, commands *commands.Commands, track spoti
|
|||
if err != nil {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -8,10 +8,14 @@ import (
|
|||
)
|
||||
|
||||
func (m *mainModel) LoadMoreItems() {
|
||||
loading = true
|
||||
defer func() {
|
||||
page++
|
||||
loading = false
|
||||
}()
|
||||
switch m.mode {
|
||||
case "artist":
|
||||
albums, err := m.commands.ArtistAlbums(m.ctx, m.artist.ID, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -31,7 +35,6 @@ func (m *mainModel) LoadMoreItems() {
|
|||
return
|
||||
case "artists":
|
||||
artists, err := m.commands.UserArtists(m.ctx, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -51,7 +54,6 @@ func (m *mainModel) LoadMoreItems() {
|
|||
return
|
||||
case "album":
|
||||
tracks, err := m.commands.AlbumTracks(m.ctx, m.album.ID, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -72,7 +74,6 @@ func (m *mainModel) LoadMoreItems() {
|
|||
return
|
||||
case "albums":
|
||||
albums, err := m.commands.UserAlbums(m.ctx, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -92,7 +93,6 @@ func (m *mainModel) LoadMoreItems() {
|
|||
return
|
||||
case "main":
|
||||
playlists, err := m.commands.Playlists(m.ctx, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -110,19 +110,18 @@ func (m *mainModel) LoadMoreItems() {
|
|||
main_updates <- m
|
||||
return
|
||||
case "playlist":
|
||||
tracks, err := m.commands.PlaylistTracks(m.ctx, m.playlist.ID, (page + 1))
|
||||
page++
|
||||
playlistItems, err := m.commands.PlaylistTracks(m.ctx, m.playlist.ID, (page + 1))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := []mainItem{}
|
||||
for _, track := range tracks.Tracks {
|
||||
for _, item := range playlistItems.Items {
|
||||
items = append(items, mainItem{
|
||||
Name: track.Track.Name,
|
||||
Artist: track.Track.Artists[0],
|
||||
Duration: track.Track.TimeDuration().Round(time.Second).String(),
|
||||
ID: track.Track.ID,
|
||||
Desc: track.Track.Artists[0].Name + " - " + track.Track.TimeDuration().Round(time.Second).String(),
|
||||
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(),
|
||||
})
|
||||
}
|
||||
for _, item := range items {
|
||||
|
@ -132,11 +131,9 @@ func (m *mainModel) LoadMoreItems() {
|
|||
return
|
||||
case "tracks":
|
||||
tracks, err := m.commands.TrackList(m.ctx, (page + 1))
|
||||
page++
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
page++
|
||||
items := []list.Item{}
|
||||
for _, track := range tracks.Tracks {
|
||||
items = append(items, mainItem{
|
||||
|
|
305
src/tui/main.go
305
src/tui/main.go
|
@ -5,9 +5,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
|
@ -16,6 +13,9 @@ import (
|
|||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -25,30 +25,33 @@ var (
|
|||
playbackContext string
|
||||
main_updates chan *mainModel
|
||||
page = 1
|
||||
loading = false
|
||||
showingMessage = false
|
||||
)
|
||||
|
||||
type Mode string
|
||||
|
||||
const (
|
||||
Album Mode = "album"
|
||||
ArtistAlbum = "artistalbum"
|
||||
Artist = "artist"
|
||||
Artists = "artists"
|
||||
Tracks = "tracks"
|
||||
Albums = "albums"
|
||||
Main = "main"
|
||||
Playlists = "playlists"
|
||||
Playlist = "playlist"
|
||||
Devices = "devices"
|
||||
Search = "search"
|
||||
SearchAlbums = "searchalbums"
|
||||
SearchAlbum = "searchalbum"
|
||||
SearchArtists = "searchartists"
|
||||
SearchArtist = "searchartist"
|
||||
SearchArtistAlbum = "searchartistalbum"
|
||||
SearchTracks = "searchtracks"
|
||||
SearchPlaylists = "searchplaylsits"
|
||||
SearchPlaylist = "searchplaylist"
|
||||
ArtistAlbum Mode = "artistalbum"
|
||||
Artist Mode = "artist"
|
||||
Artists Mode = "artists"
|
||||
Queue Mode = "queue"
|
||||
Tracks Mode = "tracks"
|
||||
Albums Mode = "albums"
|
||||
Main Mode = "main"
|
||||
Playlists Mode = "playlists"
|
||||
Playlist Mode = "playlist"
|
||||
Devices Mode = "devices"
|
||||
Search Mode = "search"
|
||||
SearchAlbums Mode = "searchalbums"
|
||||
SearchAlbum Mode = "searchalbum"
|
||||
SearchArtists Mode = "searchartists"
|
||||
SearchArtist Mode = "searchartist"
|
||||
SearchArtistAlbum Mode = "searchartistalbum"
|
||||
SearchTracks Mode = "searchtracks"
|
||||
SearchPlaylists Mode = "searchplaylsits"
|
||||
SearchPlaylist Mode = "searchplaylist"
|
||||
)
|
||||
|
||||
type mainItem struct {
|
||||
|
@ -88,50 +91,55 @@ type mainModel struct {
|
|||
}
|
||||
|
||||
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
|
||||
switch selectedItem.(type) {
|
||||
switch item := selectedItem.(type) {
|
||||
case spotify.SimplePlaylist:
|
||||
go HandlePlaylistRadio(m.ctx, m.commands, selectedItem.(spotify.SimplePlaylist))
|
||||
go HandlePlaylistRadio(m.ctx, m.commands, item)
|
||||
return
|
||||
case *spotify.SavedTrackPage:
|
||||
go HandleLibraryRadio(m.ctx, m.commands)
|
||||
return
|
||||
case spotify.SimpleAlbum:
|
||||
go HandleAlbumRadio(m.ctx, m.commands, selectedItem.(spotify.SimpleAlbum))
|
||||
go HandleAlbumRadio(m.ctx, m.commands, item)
|
||||
return
|
||||
case spotify.FullAlbum:
|
||||
go HandleAlbumRadio(m.ctx, m.commands, selectedItem.(spotify.FullAlbum).SimpleAlbum)
|
||||
go HandleAlbumRadio(m.ctx, m.commands, item.SimpleAlbum)
|
||||
return
|
||||
case spotify.SimpleArtist:
|
||||
go HandleArtistRadio(m.ctx, m.commands, selectedItem.(spotify.SimpleArtist))
|
||||
go HandleArtistRadio(m.ctx, m.commands, item)
|
||||
return
|
||||
case spotify.FullArtist:
|
||||
go HandleArtistRadio(m.ctx, m.commands, selectedItem.(spotify.FullArtist).SimpleArtist)
|
||||
go HandleArtistRadio(m.ctx, m.commands, item.SimpleArtist)
|
||||
return
|
||||
case spotify.SimpleTrack:
|
||||
go HandleRadio(m.ctx, m.commands, selectedItem.(spotify.SimpleTrack))
|
||||
go HandleRadio(m.ctx, m.commands, item)
|
||||
return
|
||||
case spotify.FullTrack:
|
||||
go HandleRadio(m.ctx, m.commands, selectedItem.(spotify.FullTrack).SimpleTrack)
|
||||
go HandleRadio(m.ctx, m.commands, item.SimpleTrack)
|
||||
return
|
||||
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
|
||||
case spotify.SavedTrack:
|
||||
go HandleRadio(m.ctx, m.commands, selectedItem.(spotify.SavedTrack).SimpleTrack)
|
||||
go HandleRadio(m.ctx, m.commands, item.SimpleTrack)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mainModel) GoBack() (tea.Cmd, error) {
|
||||
page = 1
|
||||
switch m.mode {
|
||||
case Main:
|
||||
return tea.Quit, nil
|
||||
case Albums, Artists, Tracks, Playlist, Devices, Search:
|
||||
case Albums, Artists, Tracks, Playlist, Devices, Search, Queue:
|
||||
m.mode = Main
|
||||
new_items, err := MainView(m.ctx, m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case Album:
|
||||
|
@ -203,48 +211,122 @@ type SpotifyUrl struct {
|
|||
|
||||
func (m *mainModel) CopyToClipboard() error {
|
||||
item := m.list.SelectedItem().(mainItem).SpotifyItem
|
||||
switch item.(type) {
|
||||
switch converted := item.(type) {
|
||||
case spotify.SimplePlaylist:
|
||||
clipboard.WriteAll(item.(spotify.SimplePlaylist).ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullPlaylist:
|
||||
clipboard.WriteAll(item.(*spotify.FullPlaylist).ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleAlbum:
|
||||
clipboard.WriteAll(item.(spotify.SimpleAlbum).ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullAlbum:
|
||||
clipboard.WriteAll(item.(*spotify.FullAlbum).ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleArtist:
|
||||
clipboard.WriteAll(item.(spotify.SimpleArtist).ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullArtist:
|
||||
clipboard.WriteAll(item.(*spotify.FullArtist).ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleTrack:
|
||||
clipboard.WriteAll(item.(spotify.SimpleTrack).ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.PlaylistTrack:
|
||||
clipboard.WriteAll(item.(spotify.PlaylistTrack).Track.ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.Track.ExternalURLs["spotify"])
|
||||
case spotify.SavedTrack:
|
||||
clipboard.WriteAll(item.(spotify.SavedTrack).ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.FullTrack:
|
||||
clipboard.WriteAll(item.(spotify.FullTrack).ExternalURLs["spotify"])
|
||||
m.list.NewStatusMessage("Copying link to " + m.list.SelectedItem().(mainItem).Title())
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mainModel) SendMessage(msg string, duration time.Duration) {
|
||||
showingMessage = true
|
||||
defer func() {
|
||||
showingMessage = false
|
||||
}()
|
||||
m.list.NewStatusMessage(msg)
|
||||
time.Sleep(duration)
|
||||
}
|
||||
|
||||
func (m *mainModel) QueueItem() error {
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case spotify.PlaylistTrack:
|
||||
go m.SendMessage("Adding "+item.Track.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.ctx, m.commands, item.Track.ID)
|
||||
case spotify.SavedTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.ctx, m.commands, item.ID)
|
||||
case spotify.SimpleTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.ctx, m.commands, item.ID)
|
||||
case spotify.FullTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.ctx, m.commands, item.ID)
|
||||
case *spotify.FullTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.ctx, m.commands, item.ID)
|
||||
case *spotify.SimpleTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.ctx, m.commands, item.ID)
|
||||
case *spotify.SimplePlaylist:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.ctx, m.commands, item.ID)
|
||||
}
|
||||
if m.mode == Queue {
|
||||
go func() {
|
||||
new_items, err := QueueView(m.ctx, m.commands)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mainModel) DeleteTrackFromPlaylist() error {
|
||||
if m.mode != Playlist {
|
||||
return nil
|
||||
}
|
||||
track := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlaylistTrack).Track
|
||||
go m.SendMessage("Deleteing "+track.Name+" from "+m.playlist.Name, 2*time.Second)
|
||||
go func() {
|
||||
HandleDeleteTrackFromPlaylist(m.ctx, m.commands, track.ID, m.playlist.ID)
|
||||
new_items, err := PlaylistView(m.ctx, m.commands, m.playlist)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mainModel) SelectItem() error {
|
||||
switch m.mode {
|
||||
case Queue:
|
||||
page = 1
|
||||
go func() {
|
||||
HandleNextInQueue(m.ctx, m.commands, m.list.Index())
|
||||
new_items, err := QueueView(m.ctx, m.commands)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
}()
|
||||
case Search:
|
||||
switch m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
page = 1
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case *spotify.FullArtistPage:
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -252,7 +334,7 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.ResetSelected()
|
||||
case *spotify.SimpleAlbumPage:
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -260,8 +342,7 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.ResetSelected()
|
||||
case *spotify.SimplePlaylistPage:
|
||||
m.mode = SearchPlaylists
|
||||
playlists := m.list.SelectedItem().(mainItem).SpotifyItem.(*spotify.SimplePlaylistPage)
|
||||
new_items, err := SearchPlaylistsView(m.ctx, m.commands, playlists)
|
||||
new_items, err := SearchPlaylistsView(m.ctx, m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -269,7 +350,7 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.ResetSelected()
|
||||
case *spotify.FullTrackPage:
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -277,6 +358,7 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.ResetSelected()
|
||||
}
|
||||
case SearchArtists:
|
||||
page = 1
|
||||
m.mode = SearchArtist
|
||||
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
|
||||
|
@ -286,6 +368,7 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchArtist:
|
||||
page = 1
|
||||
m.mode = SearchArtistAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
|
@ -295,6 +378,7 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchAlbums:
|
||||
page = 1
|
||||
m.mode = SearchAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
|
@ -304,6 +388,7 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchPlaylists:
|
||||
page = 1
|
||||
m.mode = SearchPlaylist
|
||||
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
|
||||
m.playlist = playlist
|
||||
|
@ -314,7 +399,16 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
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:
|
||||
m.mode = Artists
|
||||
new_items, err := ArtistsView(m.ctx, m.commands)
|
||||
|
@ -333,9 +427,8 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.ResetSelected()
|
||||
case spotify.SimplePlaylist:
|
||||
m.mode = Playlist
|
||||
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
|
||||
m.playlist = playlist
|
||||
new_items, err := PlaylistView(m.ctx, m.commands, playlist)
|
||||
m.playlist = item
|
||||
new_items, err := PlaylistView(m.ctx, m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -351,6 +444,7 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.ResetSelected()
|
||||
}
|
||||
case Albums:
|
||||
page = 1
|
||||
m.mode = Album
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
|
@ -378,18 +472,19 @@ func (m *mainModel) SelectItem() error {
|
|||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
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:
|
||||
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:
|
||||
go HandlePlayLikedSong(m.ctx, m.commands, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage))
|
||||
case SearchTracks:
|
||||
go HandlePlayTrack(m.ctx, m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.FullTrack).ID)
|
||||
case Devices:
|
||||
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.list.NewStatusMessage("Setting view to main")
|
||||
new_items, err := MainView(m.ctx, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -399,7 +494,7 @@ func (m *mainModel) SelectItem() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m mainModel) Init() tea.Cmd {
|
||||
func (m *mainModel) Init() tea.Cmd {
|
||||
main_updates = make(chan *mainModel)
|
||||
return Tick()
|
||||
}
|
||||
|
@ -437,7 +532,7 @@ func (m *mainModel) TickPlayback() {
|
|||
}()
|
||||
}
|
||||
|
||||
func (m mainModel) View() string {
|
||||
func (m *mainModel) View() string {
|
||||
if m.input.Focused() {
|
||||
return DocStyle.Render(m.list.View() + "\n" + m.input.View())
|
||||
}
|
||||
|
@ -497,7 +592,7 @@ func (m *mainModel) getContext(playing *spotify.CurrentlyPlaying) (string, error
|
|||
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
|
||||
select {
|
||||
case update := <-main_updates:
|
||||
|
@ -505,7 +600,7 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
default:
|
||||
}
|
||||
// Call for more items if needed
|
||||
if m.list.Paginator.Page == m.list.Paginator.TotalPages-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 len(m.list.Items())%50 == 0 {
|
||||
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))
|
||||
m.playing = playing
|
||||
m.playbackContext = playbackContext
|
||||
if m.mode == Queue && len(m.list.Items()) != 0 {
|
||||
if m.list.Items()[0].(mainItem).SpotifyItem.(spotify.FullTrack).Name != playing.Item.Name {
|
||||
go func() {
|
||||
new_items, err := QueueView(m.ctx, m.commands)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
}()
|
||||
}
|
||||
}
|
||||
return m, tea.Batch(Tick(), cmd)
|
||||
}
|
||||
return m, Tick()
|
||||
|
@ -526,15 +632,17 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
case progress.FrameMsg:
|
||||
progressModel, cmd := m.progress.Update(msg)
|
||||
m.progress = progressModel.(progress.Model)
|
||||
m.list.NewStatusMessage(
|
||||
fmt.Sprintf("Now playing %s by %s - %s %s/%s : %s",
|
||||
m.playing.Item.Name,
|
||||
m.playing.Item.Artists[0].Name,
|
||||
m.progress.View(),
|
||||
(time.Duration(m.playing.Progress) * time.Millisecond).Round(time.Second),
|
||||
(time.Duration(m.playing.Item.Duration) * time.Millisecond).Round(time.Second),
|
||||
m.playbackContext),
|
||||
)
|
||||
if !showingMessage {
|
||||
m.list.NewStatusMessage(
|
||||
fmt.Sprintf("Now playing %s by %s - %s %s/%s : %s",
|
||||
m.playing.Item.Name,
|
||||
m.playing.Item.Artists[0].Name,
|
||||
m.progress.View(),
|
||||
(time.Duration(m.playing.Progress) * time.Millisecond).Round(time.Second),
|
||||
(time.Duration(m.playing.Item.Duration) * time.Millisecond).Round(time.Second),
|
||||
m.playbackContext),
|
||||
)
|
||||
}
|
||||
return m, cmd
|
||||
case tea.KeyMsg:
|
||||
// quit
|
||||
|
@ -580,7 +688,6 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
m.list.NewStatusMessage("Setting view to devices")
|
||||
}
|
||||
// go back
|
||||
if msg.String() == "backspace" || msg.String() == "esc" || msg.String() == "q" {
|
||||
|
@ -591,9 +698,20 @@ func (m mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
m.list.ResetSelected()
|
||||
return m, msg
|
||||
}
|
||||
|
||||
if msg.String() == "ctrl+d" {
|
||||
err := m.DeleteTrackFromPlaylist()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
if msg.String() == "ctrl+@" || msg.String() == "ctrl+p" {
|
||||
err := m.QueueItem()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
// select item
|
||||
if msg.String() == "enter" || msg.String() == "spacebar" {
|
||||
if msg.String() == "enter" || msg.String() == " " || msg.String() == "p" {
|
||||
err := m.SelectItem()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
|
@ -647,7 +765,7 @@ func InitMain(ctx *gctx.Context, c *commands.Commands, mode Mode) (tea.Model, er
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
m := mainModel{
|
||||
m := &mainModel{
|
||||
list: list.New(items, list.NewDefaultDelegate(), 0, 0),
|
||||
ctx: ctx,
|
||||
commands: c,
|
||||
|
@ -658,27 +776,30 @@ func InitMain(ctx *gctx.Context, c *commands.Commands, mode Mode) (tea.Model, er
|
|||
go m.TickPlayback()
|
||||
Tick()
|
||||
m.list.DisableQuitKeybindings()
|
||||
m.list.SetFilteringEnabled(false)
|
||||
m.list.AdditionalShortHelpKeys = func() []key.Binding {
|
||||
return []key.Binding{
|
||||
key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")),
|
||||
key.NewBinding(key.WithKeys("s"), key.WithHelp("s", "search")),
|
||||
key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "start radio")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"shift"+"c"), key.WithHelp("ctrl+shift+c", "copy url")),
|
||||
key.NewBinding(key.WithKeys("q"), key.WithHelp("q", "back")),
|
||||
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
|
||||
key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "search")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "radio")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"p"), key.WithHelp("ctrl+p", "queue")),
|
||||
key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")),
|
||||
}
|
||||
}
|
||||
m.list.AdditionalFullHelpKeys = func() []key.Binding {
|
||||
return []key.Binding{
|
||||
key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")),
|
||||
key.NewBinding(key.WithKeys("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 backward")),
|
||||
key.NewBinding(key.WithKeys("+"), key.WithHelp("+", "volume up")),
|
||||
key.NewBinding(key.WithKeys("-"), key.WithHelp("-", "volume down")),
|
||||
key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "copy link to item")),
|
||||
key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "start radio")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"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")),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
@ -13,10 +11,10 @@ import (
|
|||
func StartTea(ctx *gctx.Context, cmd *commands.Commands, mode string) error {
|
||||
m, err := InitMain(ctx, cmd, Mode(mode))
|
||||
if err != nil {
|
||||
fmt.Println("UH OH")
|
||||
return err
|
||||
}
|
||||
P = tea.NewProgram(m, tea.WithAltScreen())
|
||||
if err := P.Start(); err != nil {
|
||||
if _, err := P.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
159
src/tui/views.go
159
src/tui/views.go
|
@ -2,16 +2,19 @@ package tui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"gitea.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/commands"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
const regex = `<.*?>`
|
||||
|
||||
func DeviceView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
devices, err := commands.Client().PlayerDevices(ctx)
|
||||
|
@ -28,25 +31,54 @@ func DeviceView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, er
|
|||
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{}
|
||||
tracks, err := commands.PlaylistTracks(ctx, playlist.ID, 1)
|
||||
tracks, err := commands.UserQueue(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, track := range tracks.Tracks {
|
||||
if tracks.CurrentlyPlaying.Name != "" {
|
||||
items = append(items, mainItem{
|
||||
Name: track.Track.Name,
|
||||
Artist: track.Track.Artists[0],
|
||||
Duration: track.Track.TimeDuration().Round(time.Second).String(),
|
||||
ID: track.Track.ID,
|
||||
Desc: track.Track.Artists[0].Name + " - " + track.Track.TimeDuration().Round(time.Second).String(),
|
||||
Name: tracks.CurrentlyPlaying.Name,
|
||||
Artist: tracks.CurrentlyPlaying.Artists[0],
|
||||
Duration: tracks.CurrentlyPlaying.TimeDuration().Round(time.Second).String(),
|
||||
ID: tracks.CurrentlyPlaying.ID,
|
||||
Desc: tracks.CurrentlyPlaying.Artists[0].Name + " - " + tracks.CurrentlyPlaying.TimeDuration().Round(time.Second).String(),
|
||||
SpotifyItem: tracks.CurrentlyPlaying,
|
||||
})
|
||||
}
|
||||
for _, track := range tracks.Items {
|
||||
items = append(items, mainItem{
|
||||
Name: track.Name,
|
||||
Artist: track.Artists[0],
|
||||
Duration: track.TimeDuration().Round(time.Second).String(),
|
||||
ID: track.ID,
|
||||
Desc: track.Artists[0].Name + " - " + track.TimeDuration().Round(time.Second).String(),
|
||||
SpotifyItem: track,
|
||||
})
|
||||
}
|
||||
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) {
|
||||
items := []list.Item{}
|
||||
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{
|
||||
Name: artist.Name,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
@ -70,7 +102,7 @@ func SearchArtistsView(ctx *gctx.Context, commands *commands.Commands, artists *
|
|||
items = append(items, mainItem{
|
||||
Name: artist.Name,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
@ -84,27 +116,13 @@ func SearchView(ctx *gctx.Context, commands *commands.Commands, search string) (
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
items = append(items, mainItem{
|
||||
Name: "Tracks",
|
||||
Desc: "Search results",
|
||||
SpotifyItem: result.Tracks,
|
||||
})
|
||||
items = append(items, mainItem{
|
||||
Name: "Albums",
|
||||
Desc: "Search results",
|
||||
SpotifyItem: result.Albums,
|
||||
})
|
||||
items = append(items, mainItem{
|
||||
Name: "Artists",
|
||||
Desc: "Search results",
|
||||
SpotifyItem: result.Artists,
|
||||
})
|
||||
|
||||
items = append(items, mainItem{
|
||||
Name: "Playlists",
|
||||
Desc: "Search results",
|
||||
SpotifyItem: result.Playlists,
|
||||
})
|
||||
items = append(
|
||||
items,
|
||||
mainItem{Name: "Tracks", Desc: "Search results", SpotifyItem: result.Tracks},
|
||||
mainItem{Name: "Albums", Desc: "Search results", SpotifyItem: result.Albums},
|
||||
mainItem{Name: "Artists", Desc: "Search results", SpotifyItem: result.Artists},
|
||||
mainItem{Name: "Playlists", Desc: "Search results", SpotifyItem: result.Playlists},
|
||||
)
|
||||
results := &SearchResults{
|
||||
Tracks: result.Tracks,
|
||||
Playlists: result.Playlists,
|
||||
|
@ -124,7 +142,7 @@ func AlbumsView(ctx *gctx.Context, commands *commands.Commands) ([]list.Item, er
|
|||
items = append(items, mainItem{
|
||||
Name: album.Name,
|
||||
ID: album.ID,
|
||||
Desc: fmt.Sprintf("%s, %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,
|
||||
})
|
||||
}
|
||||
|
@ -136,7 +154,7 @@ func SearchPlaylistsView(ctx *gctx.Context, commands *commands.Commands, playlis
|
|||
for _, playlist := range playlists.Playlists {
|
||||
items = append(items, mainItem{
|
||||
Name: playlist.Name,
|
||||
Desc: playlist.Description,
|
||||
Desc: stripHtmlRegex(playlist.Description),
|
||||
SpotifyItem: playlist,
|
||||
})
|
||||
}
|
||||
|
@ -149,7 +167,7 @@ func SearchAlbumsView(ctx *gctx.Context, commands *commands.Commands, albums *sp
|
|||
items = append(items, mainItem{
|
||||
Name: album.Name,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
@ -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) {
|
||||
var wg sync.WaitGroup
|
||||
wg := errgroup.Group{}
|
||||
var saved_items *spotify.SavedTrackPage
|
||||
var playlists *spotify.SimplePlaylistPage
|
||||
var artists *spotify.FullArtistCursorPage
|
||||
var albums *spotify.SavedAlbumPage
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
wg.Go(func() (err error) {
|
||||
saved_items, err = commands.TrackList(ctx, 1)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
return
|
||||
})
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
wg.Go(func() (err error) {
|
||||
playlists, err = commands.Playlists(ctx, 1)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
return
|
||||
})
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
wg.Go(func() (err error) {
|
||||
artists, err = commands.UserArtists(ctx, 1)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
return
|
||||
})
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var err error
|
||||
wg.Go(func() (err error) {
|
||||
albums, err = commands.UserAlbums(ctx, 1)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
return
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
err := wg.Wait()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := []list.Item{}
|
||||
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,
|
||||
})
|
||||
}
|
||||
items = append(items, mainItem{
|
||||
Name: "Queue",
|
||||
Desc: "Your Current Queue",
|
||||
SpotifyItem: spotify.Queue{},
|
||||
})
|
||||
if playlists != nil && playlists.Total != 0 {
|
||||
for _, playlist := range playlists.Playlists {
|
||||
items = append(items, mainItem{
|
||||
Name: playlist.Name,
|
||||
Desc: playlist.Description,
|
||||
Desc: stripHtmlRegex(playlist.Description),
|
||||
SpotifyItem: playlist,
|
||||
})
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func stripHtmlRegex(s string) string {
|
||||
r := regexp.MustCompile(regex)
|
||||
return r.ReplaceAllString(s, "")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue