This commit is contained in:
parent
fa06934f31
commit
3b8a51db39
48
go.mod
48
go.mod
@ -5,48 +5,73 @@ go 1.22.0
|
||||
require (
|
||||
gfx.cafe/util/go/frand v0.0.0-20231226111635-bc00a6a250fb
|
||||
gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb
|
||||
git.asdf.cafe/abs3nt/gospt v0.0.52
|
||||
git.asdf.cafe/abs3nt/gunner v0.0.1
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.25.0
|
||||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/lmittmann/tint v1.0.4
|
||||
github.com/urfave/cli/v2 v2.27.1
|
||||
github.com/zmb3/spotify/v2 v2.4.1
|
||||
go.uber.org/fx v1.20.1
|
||||
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5
|
||||
google.golang.org/api v0.30.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/sync v0.3.0
|
||||
google.golang.org/api v0.133.0
|
||||
modernc.org/sqlite v1.29.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.65.0 // indirect
|
||||
cloud.google.com/go/compute v1.22.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cristalhq/aconfig v0.18.5 // indirect
|
||||
github.com/cristalhq/aconfig/aconfigdotenv v0.17.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/s2a-go v0.1.4 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/joho/godotenv v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // 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.19 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2 // indirect
|
||||
github.com/rs/zerolog v1.29.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
go.opencensus.io v0.22.4 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/dig v1.17.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.23.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/sys v0.16.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
|
||||
google.golang.org/grpc v1.31.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895 // indirect
|
||||
google.golang.org/grpc v1.56.2 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
|
||||
modernc.org/libc v1.41.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
@ -54,4 +79,5 @@ require (
|
||||
modernc.org/strutil v1.2.0 // indirect
|
||||
modernc.org/token v1.1.0 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
tuxpa.in/a/zlog v1.61.0 // indirect
|
||||
)
|
||||
|
143
go.sum
143
go.sum
@ -12,7 +12,6 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
@ -20,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.22.0 h1:cB8R6FtUtT1TYGl5R3xuxnW6OUIc/DrT2aiR16TTG7Y=
|
||||
cloud.google.com/go/compute v1.22.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
cloud.google.com/go/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=
|
||||
@ -36,20 +39,45 @@ gfx.cafe/util/go/frand v0.0.0-20231226111635-bc00a6a250fb h1:sBsE/GNN43F9a9/FQjb
|
||||
gfx.cafe/util/go/frand v0.0.0-20231226111635-bc00a6a250fb/go.mod h1:LNHxMJl0WnIr5+OChYxlVopxk+j7qxZv0XvWCzB6uGE=
|
||||
gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb h1:JL2ZB1wCxGS/mBIsTwDrgGEndHVQtZuba9dgSk+OsSE=
|
||||
gfx.cafe/util/go/fxplus v0.0.0-20231226111635-bc00a6a250fb/go.mod h1:qcgf/NcKZwJCETErwNtofMa10hQtP28ec1bN2nl8ahA=
|
||||
git.asdf.cafe/abs3nt/gospt v0.0.52 h1:DlVUXT/KXV9HW2dJyFvijhjr7Ak8zIC2/4rUvjESmpA=
|
||||
git.asdf.cafe/abs3nt/gospt v0.0.52/go.mod h1:8IqF8S5+ZjDfbmBrXqff542kYu/1t1h+Qin89i1ZNSA=
|
||||
git.asdf.cafe/abs3nt/gunner v0.0.1 h1:N6kCe7fH83kzm1Sjp/5uZbl8FM5s7KoYCfmhO8qyQbA=
|
||||
git.asdf.cafe/abs3nt/gunner v0.0.1/go.mod h1:Q4zhiPfmffCVAb5xIzZn6Momm91uf/deqRVd2/vdjd4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cristalhq/aconfig v0.17.0/go.mod h1:NXaRp+1e6bkO4dJn+wZ71xyaihMDYPtCSvEhMTm/H3E=
|
||||
@ -65,15 +93,20 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
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=
|
||||
@ -94,9 +127,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=
|
||||
@ -106,6 +141,7 @@ 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.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@ -121,11 +157,18 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
|
||||
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/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
@ -141,28 +184,67 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
|
||||
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2 h1:tcc3ZFBvjydcgrAxavZRYqFqCKzy0FJ+UY4ATq4QVXk=
|
||||
github.com/rivo/uniseg v0.4.7-0.20240127222946-601bbb3750c2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/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/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
|
||||
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y=
|
||||
github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
|
||||
@ -179,8 +261,10 @@ 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 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/dig v1.17.0 h1:5Chju+tUvcC+N7N6EV08BJz41UZuO3BmHcN4A287ZLI=
|
||||
@ -199,6 +283,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/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=
|
||||
@ -262,7 +348,9 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
@ -273,8 +361,9 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr
|
||||
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.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
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=
|
||||
@ -285,6 +374,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/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=
|
||||
@ -310,12 +401,19 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-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-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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
@ -324,13 +422,16 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
@ -401,8 +502,9 @@ google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.133.0 h1:N7Ym5Hl0Dpn0I0o7R1z4UpVA1GCDyS8vbPu1/ObV73A=
|
||||
google.golang.org/api v0.133.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk=
|
||||
google.golang.org/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=
|
||||
@ -434,13 +536,19 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130 h1:Au6te5hbKUV8pIYWHqOUZ1pva5qK/rwbIhoXEUB9Lu8=
|
||||
google.golang.org/genproto v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:O9kGHb51iE/nOGvQaDUuadVYqovW56s5emA88lQnj6Y=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130 h1:XVeBY8d/FaK4848myy41HBqnDwvxeV3zMZhwN1TvAMU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230706204954-ccb25ca9f130/go.mod h1:mPBs5jNgx2GuQGvFwUvVKqtn6HsUw9nP64BedgvqEsQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895 h1:co8AMhI481nhd3WBfW2mq5msyQHNBcGn7G9GCEqz45k=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230725213213-b022f6e96895/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||
google.golang.org/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=
|
||||
@ -452,8 +560,13 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
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 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.56.2 h1:fVRFRnXvU+x6C4IlHZewvJOVHoOv1TUuQyoRsYnB4bI=
|
||||
google.golang.org/grpc v1.56.2/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
||||
google.golang.org/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=
|
||||
@ -466,13 +579,15 @@ 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=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@ -502,3 +617,5 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
tuxpa.in/a/zlog v1.61.0 h1:7wrS6G4QwpnOmgHRQknrr7IgiMXrfGpekkU0PjM9FhE=
|
||||
tuxpa.in/a/zlog v1.61.0/go.mod h1:CNpMe8laDHLSypx/DyxfX1S0oyxUydeo3aGTEbtRBhg=
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/components/commands"
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/components/tui"
|
||||
)
|
||||
|
||||
var Version = "dev"
|
||||
@ -291,6 +292,13 @@ func Run(c *commands.Commander, s fx.Shutdowner) {
|
||||
},
|
||||
Category: "Playback",
|
||||
},
|
||||
{
|
||||
Name: "tui",
|
||||
Usage: "Starts the TUI",
|
||||
Action: func(ctx *cli.Context) error {
|
||||
return tui.StartTea(c, "main")
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "seek",
|
||||
Usage: "Seek to a position in the song",
|
||||
|
@ -1,7 +1,6 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
@ -10,7 +9,7 @@ import (
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
func (c *Commander) activateDevice(ctx context.Context) (spotify.ID, error) {
|
||||
func (c *Commander) activateDevice() (spotify.ID, error) {
|
||||
var device *spotify.PlayerDevice
|
||||
configDir, _ := os.UserConfigDir()
|
||||
if _, err := os.Stat(filepath.Join(configDir, "gospt/device.json")); err == nil {
|
||||
@ -27,7 +26,7 @@ func (c *Commander) activateDevice(ctx context.Context) (spotify.ID, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = c.Client.TransferPlayback(ctx, device.ID, true)
|
||||
err = c.Client.TransferPlayback(c.Context, device.ID, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
18
src/components/commands/album.go
Normal file
18
src/components/commands/album.go
Normal file
@ -0,0 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
func (c *Commander) AlbumTracks(album spotify.ID, page int) (*spotify.SimpleTrackPage, error) {
|
||||
tracks, err := c.Client.
|
||||
GetAlbumTracks(c.Context, album, spotify.Limit(50), spotify.Offset((page-1)*50), spotify.Market(spotify.CountryUSA))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tracks, nil
|
||||
}
|
||||
|
||||
func (c *Commander) UserAlbums(page int) (*spotify.SavedAlbumPage, error) {
|
||||
return c.Client.CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
12
src/components/commands/artist.go
Normal file
12
src/components/commands/artist.go
Normal file
@ -0,0 +1,12 @@
|
||||
package commands
|
||||
|
||||
import "github.com/zmb3/spotify/v2"
|
||||
|
||||
func (c *Commander) ArtistAlbums(artist spotify.ID, page int) (*spotify.SimpleAlbumPage, error) {
|
||||
albums, err := c.Client.
|
||||
GetArtistAlbums(c.Context, artist, []spotify.AlbumType{1, 2, 3, 4}, spotify.Market(spotify.CountryUSA), spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return albums, nil
|
||||
}
|
@ -8,7 +8,7 @@ func (c *Commander) Next() error {
|
||||
err := c.Client.Next(c.Context)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceId, err := c.activateDevice(c.Context)
|
||||
deviceId, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ func (c *Commander) Play() error {
|
||||
err := c.Client.Play(c.Context)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice(c.Context)
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -28,6 +28,63 @@ func (c *Commander) Play() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commander) PlayLikedSongs(position int) error {
|
||||
err := c.ClearRadio()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
playlist, _, err := c.GetRadioPlaylist("Saved Songs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
songs, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(position))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to_add := []spotify.ID{}
|
||||
for _, song := range songs.Tracks {
|
||||
to_add = append(to_add, song.ID)
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, playlist.ID, to_add...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &playlist.URI,
|
||||
})
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &playlist.URI,
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for page := 2; page <= 5; page++ {
|
||||
songs, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((50*(page-1))+position))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to_add := []spotify.ID{}
|
||||
for _, song := range songs.Tracks {
|
||||
to_add = append(to_add, song.ID)
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, playlist.ID, to_add...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commander) PlayUrl(urlString string) error {
|
||||
url, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
@ -37,7 +94,7 @@ func (c *Commander) PlayUrl(urlString string) error {
|
||||
err = c.Client.QueueSong(c.Context, spotify.ID(track_id))
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice(c.Context)
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -64,3 +121,46 @@ func (c *Commander) PlayUrl(urlString string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commander) PlaySongInPlaylist(context *spotify.URI, offset *int) error {
|
||||
e := c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
|
||||
PlaybackContext: context,
|
||||
})
|
||||
if e != nil {
|
||||
if isNoActiveError(e) {
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
|
||||
PlaybackContext: context,
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackOffset: &spotify.PlaybackOffset{Position: offset},
|
||||
PlaybackContext: context,
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
err = c.Client.Play(c.Context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
23
src/components/commands/playlist.go
Normal file
23
src/components/commands/playlist.go
Normal file
@ -0,0 +1,23 @@
|
||||
package commands
|
||||
|
||||
import "github.com/zmb3/spotify/v2"
|
||||
|
||||
func (c *Commander) Playlists(page int) (*spotify.SimplePlaylistPage, error) {
|
||||
return c.Client.CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
||||
|
||||
func (c *Commander) PlaylistTracks(playlist spotify.ID, page int) (*spotify.PlaylistItemPage, error) {
|
||||
return c.Client.GetPlaylistItems(c.Context, playlist, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
||||
|
||||
func (c *Commander) DeleteTracksFromPlaylist(tracks []spotify.ID, playlist spotify.ID) error {
|
||||
_, err := c.Client.RemoveTracksFromPlaylist(c.Context, playlist, tracks...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commander) TrackList(page int) (*spotify.SavedTrackPage, error) {
|
||||
return c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
}
|
@ -8,7 +8,7 @@ func (c *Commander) Previous() error {
|
||||
err := c.Client.Previous(c.Context)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceId, err := c.activateDevice(c.Context)
|
||||
deviceId, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
25
src/components/commands/queue.go
Normal file
25
src/components/commands/queue.go
Normal file
@ -0,0 +1,25 @@
|
||||
package commands
|
||||
|
||||
import "github.com/zmb3/spotify/v2"
|
||||
|
||||
func (c *Commander) QueueSong(id spotify.ID) error {
|
||||
err := c.Client.QueueSong(c.Context, id)
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.QueueSongOpt(c.Context, id, &spotify.PlayOptions{
|
||||
DeviceID: &deviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -23,7 +23,7 @@ func (c *Commander) Radio() error {
|
||||
if current_song.Item != nil {
|
||||
return c.RadioGivenSong(current_song.Item.SimpleTrack, current_song.Progress)
|
||||
}
|
||||
_, err = c.activateDevice(c.Context)
|
||||
_, err = c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -34,6 +34,146 @@ func (c *Commander) Radio() error {
|
||||
return c.RadioGivenSong(tracks.Tracks[frand.Intn(len(tracks.Tracks))].SimpleTrack, 0)
|
||||
}
|
||||
|
||||
func (c *Commander) RadioFromPlaylist(playlist spotify.SimplePlaylist) error {
|
||||
total := playlist.Tracks.Total
|
||||
if total == 0 {
|
||||
return fmt.Errorf("this playlist is empty")
|
||||
}
|
||||
pages := int(math.Ceil(float64(total) / 50))
|
||||
randomPage := 1
|
||||
if pages > 1 {
|
||||
randomPage = frand.Intn(pages-1) + 1
|
||||
}
|
||||
playlistPage, err := c.Client.GetPlaylistItems(c.Context, playlist.ID, spotify.Limit(50), spotify.Offset((randomPage-1)*50))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pageSongs := playlistPage.Items
|
||||
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
seedCount := 5
|
||||
if len(pageSongs) < seedCount {
|
||||
seedCount = len(pageSongs)
|
||||
}
|
||||
seedIds := []spotify.ID{}
|
||||
for idx, song := range pageSongs {
|
||||
if idx >= seedCount {
|
||||
break
|
||||
}
|
||||
seedIds = append(seedIds, song.Track.Track.ID)
|
||||
}
|
||||
return c.RadioGivenList(seedIds[:seedCount], playlist.Name)
|
||||
}
|
||||
|
||||
func (c *Commander) RadioFromSavedTracks() error {
|
||||
savedSongs, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if savedSongs.Total == 0 {
|
||||
return fmt.Errorf("you have no saved songs")
|
||||
}
|
||||
pages := int(math.Ceil(float64(savedSongs.Total) / 50))
|
||||
randomPage := 1
|
||||
if pages > 1 {
|
||||
randomPage = frand.Intn(pages-1) + 1
|
||||
}
|
||||
trackPage, err := c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(randomPage*50))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pageSongs := trackPage.Tracks
|
||||
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
seedCount := 4
|
||||
seedIds := []spotify.ID{}
|
||||
for idx, song := range pageSongs {
|
||||
if idx >= seedCount {
|
||||
break
|
||||
}
|
||||
seedIds = append(seedIds, song.ID)
|
||||
}
|
||||
seedIds = append(seedIds, savedSongs.Tracks[0].ID)
|
||||
return c.RadioGivenList(seedIds, "Saved Tracks")
|
||||
}
|
||||
|
||||
func (c *Commander) RadioGivenArtist(artist spotify.SimpleArtist) error {
|
||||
seed := spotify.Seeds{
|
||||
Artists: []spotify.ID{artist.ID},
|
||||
}
|
||||
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
recomendationIds := []spotify.ID{}
|
||||
for _, song := range recomendations.Tracks {
|
||||
recomendationIds = append(recomendationIds, song.ID)
|
||||
}
|
||||
err = c.ClearRadio()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
radioPlaylist, db, err := c.GetRadioPlaylist(artist.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queue := []spotify.ID{}
|
||||
for _, rec := range recomendationIds {
|
||||
exists, err := c.SongExists(db, rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
_, err := db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(rec)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queue = append(queue, rec)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.Repeat(c.Context, "context")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
id := frand.Intn(len(recomendationIds)-2) + 1
|
||||
seed := spotify.Seeds{
|
||||
Tracks: []spotify.ID{recomendationIds[id]},
|
||||
}
|
||||
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
additionalRecsIds := []spotify.ID{}
|
||||
for _, song := range additional_recs.Tracks {
|
||||
exists, err := c.SongExists(db, song.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
_, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
additionalRecsIds = append(additionalRecsIds, song.ID)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
|
||||
start := time.Now().UnixMilli()
|
||||
seed := spotify.Seeds{
|
||||
@ -87,7 +227,7 @@ func (c *Commander) RadioGivenSong(song spotify.SimpleTrack, pos int) error {
|
||||
})
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceID, err := c.activateDevice(c.Context)
|
||||
deviceID, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -368,3 +508,124 @@ func (c *Commander) RefillRadio() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commander) RadioFromAlbum(album spotify.SimpleAlbum) error {
|
||||
tracks, err := c.AlbumTracks(album.ID, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
total := tracks.Total
|
||||
if total == 0 {
|
||||
return fmt.Errorf("this playlist is empty")
|
||||
}
|
||||
pages := int(math.Ceil(float64(total) / 50))
|
||||
randomPage := 1
|
||||
if pages > 1 {
|
||||
randomPage = frand.Intn(pages-1) + 1
|
||||
}
|
||||
albumTrackPage, err := c.AlbumTracks(album.ID, randomPage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pageSongs := albumTrackPage.Tracks
|
||||
frand.Shuffle(len(pageSongs), func(i, j int) { pageSongs[i], pageSongs[j] = pageSongs[j], pageSongs[i] })
|
||||
seedCount := 5
|
||||
if len(pageSongs) < seedCount {
|
||||
seedCount = len(pageSongs)
|
||||
}
|
||||
seedIds := []spotify.ID{}
|
||||
for idx, song := range pageSongs {
|
||||
if idx >= seedCount {
|
||||
break
|
||||
}
|
||||
seedIds = append(seedIds, song.ID)
|
||||
}
|
||||
return c.RadioGivenList(seedIds[:seedCount], album.Name)
|
||||
}
|
||||
|
||||
func (c *Commander) RadioGivenList(song_ids []spotify.ID, name string) error {
|
||||
seed := spotify.Seeds{
|
||||
Tracks: song_ids,
|
||||
}
|
||||
recomendations, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(99))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
recomendationIds := []spotify.ID{}
|
||||
for _, song := range recomendations.Tracks {
|
||||
recomendationIds = append(recomendationIds, song.ID)
|
||||
}
|
||||
err = c.ClearRadio()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
radioPlaylist, db, err := c.GetRadioPlaylist(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queue := []spotify.ID{song_ids[0]}
|
||||
for _, rec := range recomendationIds {
|
||||
exists, err := c.SongExists(db, rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
_, err := db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(rec)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
queue = append(queue, rec)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, queue...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
})
|
||||
if err != nil {
|
||||
if isNoActiveError(err) {
|
||||
deviceId, err := c.activateDevice()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.PlayOpt(c.Context, &spotify.PlayOptions{
|
||||
PlaybackContext: &radioPlaylist.URI,
|
||||
DeviceID: &deviceId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
id := frand.Intn(len(recomendationIds)-2) + 1
|
||||
seed := spotify.Seeds{
|
||||
Tracks: []spotify.ID{recomendationIds[id]},
|
||||
}
|
||||
additional_recs, err := c.Client.GetRecommendations(c.Context, seed, &spotify.TrackAttributes{}, spotify.Limit(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
additionalRecsIds := []spotify.ID{}
|
||||
for _, song := range additional_recs.Tracks {
|
||||
exists, err := c.SongExists(db, song.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
_, err = db.QueryContext(c.Context, fmt.Sprintf("INSERT INTO radio (id) VALUES('%s')", string(song.ID)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
additionalRecsIds = append(additionalRecsIds, song.ID)
|
||||
}
|
||||
}
|
||||
_, err = c.Client.AddTracksToPlaylist(c.Context, radioPlaylist.ID, additionalRecsIds...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
12
src/components/commands/search.go
Normal file
12
src/components/commands/search.go
Normal file
@ -0,0 +1,12 @@
|
||||
package commands
|
||||
|
||||
import "github.com/zmb3/spotify/v2"
|
||||
|
||||
func (c *Commander) Search(search string, page int) (*spotify.SearchResult, error) {
|
||||
result, err := c.Client.
|
||||
Search(c.Context, search, spotify.SearchTypeAlbum|spotify.SearchTypeArtist|spotify.SearchTypeTrack|spotify.SearchTypePlaylist, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
120
src/components/tui/handlers.go
Normal file
120
src/components/tui/handlers.go
Normal file
@ -0,0 +1,120 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/zmb3/spotify/v2"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/components/commands"
|
||||
)
|
||||
|
||||
func HandlePlayWithContext(commands *commands.Commander, uri *spotify.URI, pos *int) {
|
||||
err := commands.PlaySongInPlaylist(uri, pos)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleRadio(commands *commands.Commander, song spotify.SimpleTrack) {
|
||||
err := commands.RadioGivenSong(song, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleAlbumRadio(commands *commands.Commander, album spotify.SimpleAlbum) {
|
||||
err := commands.RadioFromAlbum(album)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSeek(commands *commands.Commander, fwd bool) {
|
||||
err := commands.Seek(fwd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleVolume(commands *commands.Commander, up bool) {
|
||||
vol := 10
|
||||
if !up {
|
||||
vol = -10
|
||||
}
|
||||
err := commands.ChangeVolume(vol)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleArtistRadio(commands *commands.Commander, artist spotify.SimpleArtist) {
|
||||
err := commands.RadioGivenArtist(artist)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleAlbumArtist(commands *commands.Commander, artist spotify.SimpleArtist) {
|
||||
err := commands.RadioGivenArtist(artist)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandlePlaylistRadio(commands *commands.Commander, playlist spotify.SimplePlaylist) {
|
||||
err := commands.RadioFromPlaylist(playlist)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleLibraryRadio(commands *commands.Commander) {
|
||||
err := commands.RadioFromSavedTracks()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandlePlayLikedSong(commands *commands.Commander, position int) {
|
||||
err := commands.PlayLikedSongs(position)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandlePlayTrack(commands *commands.Commander, track spotify.ID) {
|
||||
err := commands.QueueSong(track)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = commands.Next()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleNextInQueue(commands *commands.Commander, amt int) {
|
||||
err := commands.Next()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleQueueItem(commands *commands.Commander, item spotify.ID) {
|
||||
err := commands.QueueSong(item)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleDeleteTrackFromPlaylist(commands *commands.Commander, item, playlist spotify.ID) {
|
||||
err := commands.DeleteTracksFromPlaylist([]spotify.ID{item}, playlist)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func HandleSetDevice(commands *commands.Commander, player spotify.PlayerDevice) {
|
||||
err := commands.SetDevice(player.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
165
src/components/tui/loader.go
Normal file
165
src/components/tui/loader.go
Normal file
@ -0,0 +1,165 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
)
|
||||
|
||||
func (m *mainModel) LoadMoreItems() {
|
||||
loading = true
|
||||
defer func() {
|
||||
page++
|
||||
loading = false
|
||||
}()
|
||||
switch m.mode {
|
||||
case "artist":
|
||||
albums, err := m.commands.ArtistAlbums(m.artist.ID, (page + 1))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := []list.Item{}
|
||||
for _, album := range albums.Albums {
|
||||
items = append(items, mainItem{
|
||||
Name: album.Name,
|
||||
ID: album.ID,
|
||||
Desc: fmt.Sprintf("%s by %s", album.AlbumType, album.Artists[0].Name),
|
||||
SpotifyItem: album,
|
||||
})
|
||||
}
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
return
|
||||
case "artists":
|
||||
artists, err := m.commands.Client.CurrentUsersFollowedArtists(
|
||||
m.commands.Context,
|
||||
spotify.Limit(50),
|
||||
spotify.Offset((page)*50),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := []list.Item{}
|
||||
for _, artist := range artists.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,
|
||||
),
|
||||
SpotifyItem: artist.SimpleArtist,
|
||||
})
|
||||
}
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
return
|
||||
case "album":
|
||||
tracks, err := m.commands.AlbumTracks(m.album.ID, (page + 1))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := []mainItem{}
|
||||
for _, track := range tracks.Tracks {
|
||||
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(),
|
||||
})
|
||||
}
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
return
|
||||
case "albums":
|
||||
albums, err := m.commands.UserAlbums(page + 1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := []list.Item{}
|
||||
for _, album := range albums.Albums {
|
||||
items = append(items, mainItem{
|
||||
Name: album.Name,
|
||||
ID: album.ID,
|
||||
Desc: fmt.Sprintf("%s, %d tracks", album.Artists[0].Name, album.Tracks.Total),
|
||||
SpotifyItem: album.SimpleAlbum,
|
||||
})
|
||||
}
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
return
|
||||
case "main":
|
||||
playlists, err := m.commands.Playlists(page + 1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := []list.Item{}
|
||||
for _, playlist := range playlists.Playlists {
|
||||
items = append(items, mainItem{
|
||||
Name: playlist.Name,
|
||||
Desc: playlist.Description,
|
||||
SpotifyItem: playlist,
|
||||
})
|
||||
}
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
return
|
||||
case "playlist":
|
||||
playlistItems, err := m.commands.PlaylistTracks(m.playlist.ID, (page + 1))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := []mainItem{}
|
||||
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(),
|
||||
})
|
||||
}
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
return
|
||||
case "tracks":
|
||||
tracks, err := m.commands.TrackList(page + 1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
items := []list.Item{}
|
||||
for _, track := range tracks.Tracks {
|
||||
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(),
|
||||
})
|
||||
}
|
||||
for _, item := range items {
|
||||
m.list.InsertItem(len(m.list.Items())+1, item)
|
||||
}
|
||||
main_updates <- m
|
||||
return
|
||||
}
|
||||
}
|
810
src/components/tui/main.go
Normal file
810
src/components/tui/main.go
Normal file
@ -0,0 +1,810 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/progress"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/components/commands"
|
||||
)
|
||||
|
||||
var (
|
||||
P *tea.Program
|
||||
DocStyle = lipgloss.NewStyle().Margin(0, 2).Border(lipgloss.DoubleBorder(), true, true, true, true)
|
||||
currentlyPlaying *spotify.CurrentlyPlaying
|
||||
playbackContext string
|
||||
main_updates chan *mainModel
|
||||
page = 1
|
||||
loading = false
|
||||
showingMessage = false
|
||||
)
|
||||
|
||||
type Mode string
|
||||
|
||||
const (
|
||||
Album Mode = "album"
|
||||
ArtistAlbum Mode = "artistalbum"
|
||||
Artist Mode = "artist"
|
||||
Artists Mode = "artists"
|
||||
Queue Mode = "queue"
|
||||
Tracks Mode = "tracks"
|
||||
Albums Mode = "albums"
|
||||
Main Mode = "main"
|
||||
Playlists Mode = "playlists"
|
||||
Playlist Mode = "playlist"
|
||||
Devices Mode = "devices"
|
||||
Search Mode = "search"
|
||||
SearchAlbums Mode = "searchalbums"
|
||||
SearchAlbum Mode = "searchalbum"
|
||||
SearchArtists Mode = "searchartists"
|
||||
SearchArtist Mode = "searchartist"
|
||||
SearchArtistAlbum Mode = "searchartistalbum"
|
||||
SearchTracks Mode = "searchtracks"
|
||||
SearchPlaylists Mode = "searchplaylsits"
|
||||
SearchPlaylist Mode = "searchplaylist"
|
||||
)
|
||||
|
||||
type mainItem struct {
|
||||
Name string
|
||||
Duration string
|
||||
Artist spotify.SimpleArtist
|
||||
ID spotify.ID
|
||||
Desc string
|
||||
SpotifyItem any
|
||||
}
|
||||
|
||||
type SearchResults struct {
|
||||
Tracks *spotify.FullTrackPage
|
||||
Artists *spotify.FullArtistPage
|
||||
Playlists *spotify.SimplePlaylistPage
|
||||
Albums *spotify.SimpleAlbumPage
|
||||
}
|
||||
|
||||
func (i mainItem) Title() string { return i.Name }
|
||||
func (i mainItem) Description() string { return i.Desc }
|
||||
func (i mainItem) FilterValue() string { return i.Title() + i.Desc }
|
||||
|
||||
type mainModel struct {
|
||||
list list.Model
|
||||
input textinput.Model
|
||||
commands *commands.Commander
|
||||
mode Mode
|
||||
playlist spotify.SimplePlaylist
|
||||
artist spotify.SimpleArtist
|
||||
album spotify.SimpleAlbum
|
||||
searchResults *SearchResults
|
||||
progress progress.Model
|
||||
playing *spotify.CurrentlyPlaying
|
||||
playbackContext string
|
||||
search string
|
||||
}
|
||||
|
||||
func (m *mainModel) PlayRadio() {
|
||||
go m.SendMessage("Starting radio for "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
selectedItem := m.list.SelectedItem().(mainItem).SpotifyItem
|
||||
switch item := selectedItem.(type) {
|
||||
case spotify.SimplePlaylist:
|
||||
go HandlePlaylistRadio(m.commands, item)
|
||||
return
|
||||
case *spotify.SavedTrackPage:
|
||||
go HandleLibraryRadio(m.commands)
|
||||
return
|
||||
case spotify.SimpleAlbum:
|
||||
go HandleAlbumRadio(m.commands, item)
|
||||
return
|
||||
case spotify.FullAlbum:
|
||||
go HandleAlbumRadio(m.commands, item.SimpleAlbum)
|
||||
return
|
||||
case spotify.SimpleArtist:
|
||||
go HandleArtistRadio(m.commands, item)
|
||||
return
|
||||
case spotify.FullArtist:
|
||||
go HandleArtistRadio(m.commands, item.SimpleArtist)
|
||||
return
|
||||
case spotify.SimpleTrack:
|
||||
go HandleRadio(m.commands, item)
|
||||
return
|
||||
case spotify.FullTrack:
|
||||
go HandleRadio(m.commands, item.SimpleTrack)
|
||||
return
|
||||
case spotify.PlaylistTrack:
|
||||
go HandleRadio(m.commands, item.Track.SimpleTrack)
|
||||
return
|
||||
case spotify.PlaylistItem:
|
||||
go HandleRadio(m.commands, item.Track.Track.SimpleTrack)
|
||||
return
|
||||
case spotify.SavedTrack:
|
||||
go HandleRadio(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, Queue:
|
||||
m.mode = Main
|
||||
new_items, err := MainView(m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case Album:
|
||||
m.mode = Albums
|
||||
new_items, err := AlbumsView(m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case Artist:
|
||||
m.mode = Artists
|
||||
new_items, err := ArtistsView(m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case ArtistAlbum:
|
||||
m.mode = Artist
|
||||
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case SearchArtists, SearchTracks, SearchAlbums, SearchPlaylists:
|
||||
m.mode = Search
|
||||
items, result, err := SearchView(m.commands, m.search)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.searchResults = result
|
||||
m.list.SetItems(items)
|
||||
case SearchArtist:
|
||||
m.mode = SearchArtists
|
||||
new_items, err := SearchArtistsView(m.commands, m.searchResults.Artists)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case SearchArtistAlbum:
|
||||
m.mode = SearchArtist
|
||||
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case SearchAlbum:
|
||||
m.mode = SearchAlbums
|
||||
new_items, err := SearchAlbumsView(m.commands, m.searchResults.Albums)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
case SearchPlaylist:
|
||||
m.mode = SearchPlaylists
|
||||
new_items, err := SearchPlaylistsView(m.commands, m.searchResults.Playlists)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
default:
|
||||
page = 0
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type SpotifyUrl struct {
|
||||
ExternalURLs map[string]string
|
||||
}
|
||||
|
||||
func (m *mainModel) CopyToClipboard() error {
|
||||
item := m.list.SelectedItem().(mainItem).SpotifyItem
|
||||
switch converted := item.(type) {
|
||||
case spotify.SimplePlaylist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullPlaylist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleAlbum:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullAlbum:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleArtist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case *spotify.FullArtist:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.SimpleTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.PlaylistTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.Track.ExternalURLs["spotify"])
|
||||
case spotify.SavedTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
case spotify.FullTrack:
|
||||
go m.SendMessage("Copying link to "+m.list.SelectedItem().(mainItem).Title(), 2*time.Second)
|
||||
clipboard.WriteAll(converted.ExternalURLs["spotify"])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mainModel) SendMessage(msg string, duration time.Duration) {
|
||||
showingMessage = true
|
||||
defer func() {
|
||||
showingMessage = false
|
||||
}()
|
||||
m.list.NewStatusMessage(msg)
|
||||
time.Sleep(duration)
|
||||
}
|
||||
|
||||
func (m *mainModel) QueueItem() error {
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case spotify.PlaylistTrack:
|
||||
go m.SendMessage("Adding "+item.Track.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.commands, item.Track.ID)
|
||||
case spotify.SavedTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.commands, item.ID)
|
||||
case spotify.SimpleTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.commands, item.ID)
|
||||
case spotify.FullTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.commands, item.ID)
|
||||
case *spotify.FullTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.commands, item.ID)
|
||||
case *spotify.SimpleTrack:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.commands, item.ID)
|
||||
case *spotify.SimplePlaylist:
|
||||
go m.SendMessage("Adding "+item.Name+" to queue", 2*time.Second)
|
||||
go HandleQueueItem(m.commands, item.ID)
|
||||
}
|
||||
if m.mode == Queue {
|
||||
go func() {
|
||||
new_items, err := QueueView(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.commands, track.ID, m.playlist.ID)
|
||||
new_items, err := PlaylistView(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.commands, m.list.Index())
|
||||
new_items, err := QueueView(m.commands)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
}()
|
||||
case Search:
|
||||
page = 1
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case *spotify.FullArtistPage:
|
||||
m.mode = SearchArtists
|
||||
new_items, err := SearchArtistsView(m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SimpleAlbumPage:
|
||||
m.mode = SearchAlbums
|
||||
new_items, err := SearchAlbumsView(m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SimplePlaylistPage:
|
||||
m.mode = SearchPlaylists
|
||||
new_items, err := SearchPlaylistsView(m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.FullTrackPage:
|
||||
m.mode = SearchTracks
|
||||
new_items, err := SearchTracksView(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
}
|
||||
case SearchArtists:
|
||||
page = 1
|
||||
m.mode = SearchArtist
|
||||
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
|
||||
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchArtist:
|
||||
page = 1
|
||||
m.mode = SearchArtistAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchAlbums:
|
||||
page = 1
|
||||
m.mode = SearchAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchPlaylists:
|
||||
page = 1
|
||||
m.mode = SearchPlaylist
|
||||
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
|
||||
m.playlist = playlist
|
||||
new_items, err := PlaylistView(m.commands, playlist)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case Main:
|
||||
page = 1
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case spotify.Queue:
|
||||
m.mode = Queue
|
||||
new_items, err := QueueView(m.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.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SavedAlbumPage:
|
||||
m.mode = Albums
|
||||
new_items, err := AlbumsView(m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case spotify.SimplePlaylist:
|
||||
m.mode = Playlist
|
||||
m.playlist = item
|
||||
new_items, err := PlaylistView(m.commands, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case *spotify.SavedTrackPage:
|
||||
m.mode = Tracks
|
||||
new_items, err := SavedTracksView(m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
}
|
||||
case Albums:
|
||||
page = 1
|
||||
m.mode = Album
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case Artist:
|
||||
m.mode = ArtistAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.album.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case Artists:
|
||||
m.mode = Artist
|
||||
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
|
||||
new_items, err := ArtistAlbumsView(m.artist.ID, m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case Album, ArtistAlbum, SearchArtistAlbum, SearchAlbum:
|
||||
pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.TotalPages)
|
||||
go HandlePlayWithContext(m.commands, &m.album.URI, &pos)
|
||||
case Playlist, SearchPlaylist:
|
||||
pos := m.list.Cursor() + (m.list.Paginator.Page * m.list.Paginator.PerPage)
|
||||
go HandlePlayWithContext(m.commands, &m.playlist.URI, &pos)
|
||||
case Tracks:
|
||||
go HandlePlayLikedSong(m.commands, m.list.Cursor()+(m.list.Paginator.Page*m.list.Paginator.PerPage))
|
||||
case SearchTracks:
|
||||
go HandlePlayTrack(m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.FullTrack).ID)
|
||||
case Devices:
|
||||
go HandleSetDevice(m.commands, m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.PlayerDevice))
|
||||
go m.SendMessage("Setting device to "+m.list.SelectedItem().FilterValue(), 2*time.Second)
|
||||
m.mode = "main"
|
||||
new_items, err := MainView(m.commands)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mainModel) Init() tea.Cmd {
|
||||
main_updates = make(chan *mainModel)
|
||||
return Tick()
|
||||
}
|
||||
|
||||
type tickMsg time.Time
|
||||
|
||||
func Tick() tea.Cmd {
|
||||
return tea.Tick(time.Second*1, func(t time.Time) tea.Msg {
|
||||
return tickMsg(t)
|
||||
})
|
||||
}
|
||||
|
||||
func (m *mainModel) TickPlayback() {
|
||||
playing, _ := m.commands.Client.PlayerCurrentlyPlaying(m.commands.Context)
|
||||
if playing != nil && playing.Playing && playing.Item != nil {
|
||||
currentlyPlaying = playing
|
||||
playbackContext, _ = m.getContext(playing)
|
||||
}
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
quit := make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
playing, _ := m.commands.Client.PlayerCurrentlyPlaying(m.commands.Context)
|
||||
if playing != nil && playing.Playing && playing.Item != nil {
|
||||
currentlyPlaying = playing
|
||||
playbackContext, _ = m.getContext(playing)
|
||||
}
|
||||
case <-quit:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *mainModel) View() string {
|
||||
if m.input.Focused() {
|
||||
return DocStyle.Render(m.list.View() + "\n" + m.input.View())
|
||||
}
|
||||
return DocStyle.Render(m.list.View() + "\n")
|
||||
}
|
||||
|
||||
func (m *mainModel) Typing(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
if msg.String() == "enter" {
|
||||
items, result, err := SearchView(m.commands, m.input.Value())
|
||||
if err != nil {
|
||||
return false, tea.Quit
|
||||
}
|
||||
m.searchResults = result
|
||||
m.search = m.input.Value()
|
||||
m.list.SetItems(items)
|
||||
m.list.ResetSelected()
|
||||
m.input.SetValue("")
|
||||
m.input.Blur()
|
||||
return true, nil
|
||||
}
|
||||
if msg.String() == "esc" {
|
||||
m.input.SetValue("")
|
||||
m.input.Blur()
|
||||
return false, nil
|
||||
}
|
||||
m.input, _ = m.input.Update(msg)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (m *mainModel) getContext(playing *spotify.CurrentlyPlaying) (string, error) {
|
||||
context := playing.PlaybackContext
|
||||
uri_split := strings.Split(string(context.URI), ":")
|
||||
if len(uri_split) < 3 {
|
||||
return "", fmt.Errorf("NO URI")
|
||||
}
|
||||
id := strings.Split(string(context.URI), ":")[2]
|
||||
switch context.Type {
|
||||
case "album":
|
||||
album, err := m.commands.Client.GetAlbum(m.commands.Context, spotify.ID(id))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return album.Name, nil
|
||||
case "playlist":
|
||||
playlist, err := m.commands.Client.GetPlaylist(m.commands.Context, spotify.ID(id))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return playlist.Name, nil
|
||||
case "artist":
|
||||
artist, err := m.commands.Client.GetArtist(m.commands.Context, spotify.ID(id))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return artist.Name, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Update list items from LoadMore
|
||||
select {
|
||||
case update := <-main_updates:
|
||||
m.list.SetItems(update.list.Items())
|
||||
default:
|
||||
}
|
||||
// Call for more items if needed
|
||||
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()
|
||||
}
|
||||
}
|
||||
// Handle user input
|
||||
switch msg := msg.(type) {
|
||||
case tickMsg:
|
||||
playing := currentlyPlaying
|
||||
if playing != nil && playing.Playing && playing.Item != nil {
|
||||
cmd := m.progress.SetPercent(float64(playing.Progress) / float64(playing.Item.Duration))
|
||||
m.playing = playing
|
||||
m.playbackContext = playbackContext
|
||||
if m.mode == Queue && len(m.list.Items()) != 0 {
|
||||
if m.list.Items()[0].(mainItem).SpotifyItem.(spotify.FullTrack).Name != playing.Item.Name {
|
||||
go func() {
|
||||
new_items, err := QueueView(m.commands)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
}()
|
||||
}
|
||||
}
|
||||
return m, tea.Batch(Tick(), cmd)
|
||||
}
|
||||
return m, Tick()
|
||||
|
||||
case progress.FrameMsg:
|
||||
progressModel, cmd := m.progress.Update(msg)
|
||||
m.progress = progressModel.(progress.Model)
|
||||
if !showingMessage {
|
||||
m.list.NewStatusMessage(
|
||||
fmt.Sprintf("Now playing %s by %s - %s %s/%s : %s",
|
||||
m.playing.Item.Name,
|
||||
m.playing.Item.Artists[0].Name,
|
||||
m.progress.View(),
|
||||
(time.Duration(m.playing.Progress) * time.Millisecond).Round(time.Second),
|
||||
(time.Duration(m.playing.Item.Duration) * time.Millisecond).Round(time.Second),
|
||||
m.playbackContext),
|
||||
)
|
||||
}
|
||||
return m, cmd
|
||||
case tea.KeyMsg:
|
||||
// quit
|
||||
if msg.String() == "ctrl+c" {
|
||||
return m, tea.Quit
|
||||
}
|
||||
if msg.String() == "c" {
|
||||
err := m.CopyToClipboard()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
if msg.String() == ">" {
|
||||
go HandleSeek(m.commands, true)
|
||||
}
|
||||
if msg.String() == "<" {
|
||||
go HandleSeek(m.commands, false)
|
||||
}
|
||||
if msg.String() == "+" {
|
||||
go HandleVolume(m.commands, true)
|
||||
}
|
||||
if msg.String() == "-" {
|
||||
go HandleVolume(m.commands, false)
|
||||
}
|
||||
// search input
|
||||
if m.input.Focused() {
|
||||
search, cmd := m.Typing(msg)
|
||||
if search {
|
||||
m.mode = "search"
|
||||
}
|
||||
return m, cmd
|
||||
}
|
||||
// start search
|
||||
if msg.String() == "s" || msg.String() == "/" {
|
||||
m.input.Focus()
|
||||
}
|
||||
// enter device selection
|
||||
if msg.String() == "d" {
|
||||
m.mode = Devices
|
||||
new_items, err := DeviceView(m.commands)
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
}
|
||||
// go back
|
||||
if msg.String() == "backspace" || msg.String() == "esc" || msg.String() == "q" {
|
||||
msg, err := m.GoBack()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.list.ResetSelected()
|
||||
return m, msg
|
||||
}
|
||||
if msg.String() == "ctrl+d" {
|
||||
err := m.DeleteTrackFromPlaylist()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
if msg.String() == "ctrl+@" || msg.String() == "ctrl+p" {
|
||||
err := m.QueueItem()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
// select item
|
||||
if msg.String() == "enter" || msg.String() == " " || msg.String() == "p" {
|
||||
err := m.SelectItem()
|
||||
if err != nil {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
// start radio
|
||||
if msg.String() == "ctrl+r" {
|
||||
m.PlayRadio()
|
||||
}
|
||||
|
||||
// handle mouse
|
||||
case tea.MouseMsg:
|
||||
if msg.Type == 5 {
|
||||
m.list.CursorUp()
|
||||
}
|
||||
if msg.Type == 6 {
|
||||
m.list.CursorDown()
|
||||
}
|
||||
|
||||
// window size -1 to handle search bar
|
||||
case tea.WindowSizeMsg:
|
||||
h, v := DocStyle.GetFrameSize()
|
||||
m.list.SetSize(msg.Width-h, msg.Height-v-1)
|
||||
}
|
||||
|
||||
// return
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func InitMain(c *commands.Commander, mode Mode) (tea.Model, error) {
|
||||
prog := progress.New(progress.WithColorProfile(2), progress.WithoutPercentage())
|
||||
var err error
|
||||
lipgloss.SetColorProfile(2)
|
||||
items := []list.Item{}
|
||||
switch mode {
|
||||
case Main:
|
||||
items, err = MainView(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case Devices:
|
||||
items, err = DeviceView(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case Tracks:
|
||||
items, err = SavedTracksView(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
m := &mainModel{
|
||||
list: list.New(items, list.NewDefaultDelegate(), 0, 0),
|
||||
commands: c,
|
||||
mode: mode,
|
||||
progress: prog,
|
||||
}
|
||||
m.list.Title = "GOSPT"
|
||||
go m.TickPlayback()
|
||||
Tick()
|
||||
m.list.DisableQuitKeybindings()
|
||||
m.list.SetFilteringEnabled(false)
|
||||
m.list.AdditionalShortHelpKeys = func() []key.Binding {
|
||||
return []key.Binding{
|
||||
key.NewBinding(key.WithKeys("q"), key.WithHelp("q", "back")),
|
||||
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
|
||||
key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "search")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "radio")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"p"), key.WithHelp("ctrl+p", "queue")),
|
||||
key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")),
|
||||
}
|
||||
}
|
||||
m.list.AdditionalFullHelpKeys = func() []key.Binding {
|
||||
return []key.Binding{
|
||||
key.NewBinding(key.WithKeys("esc"), key.WithHelp("esc", "back")),
|
||||
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select")),
|
||||
key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "search")),
|
||||
key.NewBinding(key.WithKeys(">"), key.WithHelp(">", "seek forward")),
|
||||
key.NewBinding(key.WithKeys("<"), key.WithHelp("<", "seek backward")),
|
||||
key.NewBinding(key.WithKeys("+"), key.WithHelp("+", "volume up")),
|
||||
key.NewBinding(key.WithKeys("-"), key.WithHelp("-", "volume down")),
|
||||
key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "copy link to item")),
|
||||
key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl+c", "quit")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"r"), key.WithHelp("ctrl+r", "start radio")),
|
||||
key.NewBinding(key.WithKeys("ctrl"+"p"), key.WithHelp("ctrl+p", "queue song")),
|
||||
key.NewBinding(key.WithKeys("d"), key.WithHelp("d", "select device")),
|
||||
}
|
||||
}
|
||||
input := textinput.New()
|
||||
input.Prompt = "$ "
|
||||
input.Placeholder = "Search..."
|
||||
input.CharLimit = 250
|
||||
input.Width = 50
|
||||
m.input = input
|
||||
return m, nil
|
||||
}
|
20
src/components/tui/tui.go
Normal file
20
src/components/tui/tui.go
Normal file
@ -0,0 +1,20 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/components/commands"
|
||||
)
|
||||
|
||||
// StartTea the entry point for the UI. Initializes the model.
|
||||
func StartTea(cmd *commands.Commander, mode string) error {
|
||||
m, err := InitMain(cmd, Mode(mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
P = tea.NewProgram(m, tea.WithAltScreen())
|
||||
if _, err := P.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
343
src/components/tui/views.go
Normal file
343
src/components/tui/views.go
Normal file
@ -0,0 +1,343 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/zmb3/spotify/v2"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"git.asdf.cafe/abs3nt/gospt-ng/src/components/commands"
|
||||
)
|
||||
|
||||
const regex = `<.*?>`
|
||||
|
||||
func DeviceView(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
devices, err := commands.Client.PlayerDevices(commands.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, device := range devices {
|
||||
items = append(items, mainItem{
|
||||
Name: device.Name,
|
||||
Desc: fmt.Sprintf("%s - active: %t", device.ID, device.Active),
|
||||
SpotifyItem: device,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func QueueView(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
tracks, err := commands.Client.GetQueue(commands.Context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tracks.CurrentlyPlaying.Name != "" {
|
||||
items = append(items, mainItem{
|
||||
Name: tracks.CurrentlyPlaying.Name,
|
||||
Artist: tracks.CurrentlyPlaying.Artists[0],
|
||||
Duration: tracks.CurrentlyPlaying.TimeDuration().Round(time.Second).String(),
|
||||
ID: tracks.CurrentlyPlaying.ID,
|
||||
Desc: tracks.CurrentlyPlaying.Artists[0].Name + " - " + tracks.CurrentlyPlaying.TimeDuration().
|
||||
Round(time.Second).
|
||||
String(),
|
||||
SpotifyItem: tracks.CurrentlyPlaying,
|
||||
})
|
||||
}
|
||||
for _, track := range tracks.Items {
|
||||
items = append(items, mainItem{
|
||||
Name: track.Name,
|
||||
Artist: track.Artists[0],
|
||||
Duration: track.TimeDuration().Round(time.Second).String(),
|
||||
ID: track.ID,
|
||||
Desc: track.Artists[0].Name + " - " + track.TimeDuration().Round(time.Second).String(),
|
||||
SpotifyItem: track,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func PlaylistView(commands *commands.Commander, playlist spotify.SimplePlaylist) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
playlistItems, err := commands.Client.GetPlaylistItems(
|
||||
commands.Context,
|
||||
playlist.ID,
|
||||
spotify.Limit(50),
|
||||
spotify.Offset(0),
|
||||
)
|
||||
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(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
artists, err := commands.Client.CurrentUsersFollowedArtists(commands.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, artist := range artists.Artists {
|
||||
items = append(items, mainItem{
|
||||
Name: artist.Name,
|
||||
ID: artist.ID,
|
||||
Desc: fmt.Sprintf("%d followers", artist.Followers.Count),
|
||||
SpotifyItem: artist.SimpleArtist,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SearchArtistsView(
|
||||
commands *commands.Commander,
|
||||
artists *spotify.FullArtistPage,
|
||||
) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
for _, artist := range artists.Artists {
|
||||
items = append(items, mainItem{
|
||||
Name: artist.Name,
|
||||
ID: artist.ID,
|
||||
Desc: fmt.Sprintf("%d followers", artist.Followers.Count),
|
||||
SpotifyItem: artist.SimpleArtist,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SearchView(commands *commands.Commander, search string) ([]list.Item, *SearchResults, error) {
|
||||
items := []list.Item{}
|
||||
|
||||
result, err := commands.Search(search, 1)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
items = append(
|
||||
items,
|
||||
mainItem{Name: "Tracks", Desc: "Search results", SpotifyItem: result.Tracks},
|
||||
mainItem{Name: "Albums", Desc: "Search results", SpotifyItem: result.Albums},
|
||||
mainItem{Name: "Artists", Desc: "Search results", SpotifyItem: result.Artists},
|
||||
mainItem{Name: "Playlists", Desc: "Search results", SpotifyItem: result.Playlists},
|
||||
)
|
||||
results := &SearchResults{
|
||||
Tracks: result.Tracks,
|
||||
Playlists: result.Playlists,
|
||||
Albums: result.Albums,
|
||||
Artists: result.Artists,
|
||||
}
|
||||
return items, results, nil
|
||||
}
|
||||
|
||||
func AlbumsView(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
albums, err := commands.Client.CurrentUsersAlbums(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, album := range albums.Albums {
|
||||
items = append(items, mainItem{
|
||||
Name: album.Name,
|
||||
ID: album.ID,
|
||||
Desc: fmt.Sprintf(
|
||||
"%s by %s, %d tracks, released %d",
|
||||
album.AlbumType,
|
||||
album.Artists[0].Name,
|
||||
album.Tracks.Total,
|
||||
album.ReleaseDateTime().Year(),
|
||||
),
|
||||
SpotifyItem: album.SimpleAlbum,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SearchPlaylistsView(commands *commands.Commander, playlists *spotify.SimplePlaylistPage) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
for _, playlist := range playlists.Playlists {
|
||||
items = append(items, mainItem{
|
||||
Name: playlist.Name,
|
||||
Desc: stripHtmlRegex(playlist.Description),
|
||||
SpotifyItem: playlist,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SearchAlbumsView(commands *commands.Commander, albums *spotify.SimpleAlbumPage) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
for _, album := range albums.Albums {
|
||||
items = append(items, mainItem{
|
||||
Name: album.Name,
|
||||
ID: album.ID,
|
||||
Desc: fmt.Sprintf(
|
||||
"%s by %s, released %d",
|
||||
album.AlbumType,
|
||||
album.Artists[0].Name,
|
||||
album.ReleaseDateTime().Year(),
|
||||
),
|
||||
SpotifyItem: album,
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func ArtistAlbumsView(album spotify.ID, commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
albums, err := commands.ArtistAlbums(album, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, album := range albums.Albums {
|
||||
items = append(items, mainItem{
|
||||
Name: album.Name,
|
||||
ID: album.ID,
|
||||
Desc: fmt.Sprintf("%s by %s", album.AlbumType, album.Artists[0].Name),
|
||||
SpotifyItem: album,
|
||||
})
|
||||
}
|
||||
return items, err
|
||||
}
|
||||
|
||||
func AlbumTracksView(album spotify.ID, commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
tracks, err := commands.AlbumTracks(album, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, track := range tracks.Tracks {
|
||||
items = append(items, mainItem{
|
||||
Name: track.Name,
|
||||
Artist: track.Artists[0],
|
||||
Duration: track.TimeDuration().Round(time.Second).String(),
|
||||
ID: track.ID,
|
||||
SpotifyItem: track,
|
||||
Desc: track.Artists[0].Name + " - " + track.TimeDuration().Round(time.Second).String(),
|
||||
})
|
||||
}
|
||||
return items, err
|
||||
}
|
||||
|
||||
func SearchTracksView(tracks *spotify.FullTrackPage) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
for _, track := range tracks.Tracks {
|
||||
items = append(items, mainItem{
|
||||
Name: track.Name,
|
||||
Artist: track.Artists[0],
|
||||
Duration: track.TimeDuration().Round(time.Second).String(),
|
||||
ID: track.ID,
|
||||
SpotifyItem: track,
|
||||
Desc: track.Artists[0].Name + " - " + track.TimeDuration().Round(time.Second).String(),
|
||||
})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func SavedTracksView(commands *commands.Commander) ([]list.Item, error) {
|
||||
items := []list.Item{}
|
||||
tracks, err := commands.Client.CurrentUsersTracks(commands.Context, spotify.Limit(50), spotify.Offset((page-1)*50))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, track := range tracks.Tracks {
|
||||
items = append(items, mainItem{
|
||||
Name: track.Name,
|
||||
Artist: track.Artists[0],
|
||||
Duration: track.TimeDuration().Round(time.Second).String(),
|
||||
ID: track.ID,
|
||||
SpotifyItem: track,
|
||||
Desc: track.Artists[0].Name + " - " + track.TimeDuration().Round(time.Second).String(),
|
||||
})
|
||||
}
|
||||
return items, err
|
||||
}
|
||||
|
||||
func MainView(c *commands.Commander) ([]list.Item, error) {
|
||||
wg := errgroup.Group{}
|
||||
var saved_items *spotify.SavedTrackPage
|
||||
var playlists *spotify.SimplePlaylistPage
|
||||
var artists *spotify.FullArtistCursorPage
|
||||
var albums *spotify.SavedAlbumPage
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
saved_items, err = c.Client.CurrentUsersTracks(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
return
|
||||
})
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
playlists, err = c.Client.CurrentUsersPlaylists(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
return
|
||||
})
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
artists, err = c.Client.CurrentUsersFollowedArtists(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
return
|
||||
})
|
||||
|
||||
wg.Go(func() (err error) {
|
||||
albums, err = c.Client.CurrentUsersAlbums(c.Context, spotify.Limit(50), spotify.Offset(0))
|
||||
return
|
||||
})
|
||||
|
||||
err := wg.Wait()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
items := []list.Item{}
|
||||
if saved_items != nil && saved_items.Total != 0 {
|
||||
items = append(items, mainItem{
|
||||
Name: "Saved Tracks",
|
||||
Desc: fmt.Sprintf("%d saved songs", saved_items.Total),
|
||||
SpotifyItem: saved_items,
|
||||
})
|
||||
}
|
||||
if albums != nil && albums.Total != 0 {
|
||||
items = append(items, mainItem{
|
||||
Name: "Albums",
|
||||
Desc: fmt.Sprintf("%d albums", albums.Total),
|
||||
SpotifyItem: albums,
|
||||
})
|
||||
}
|
||||
if artists != nil && artists.Total != 0 {
|
||||
items = append(items, mainItem{
|
||||
Name: "Artists",
|
||||
Desc: fmt.Sprintf("%d artists", artists.Total),
|
||||
SpotifyItem: artists,
|
||||
})
|
||||
}
|
||||
items = append(items, mainItem{
|
||||
Name: "Queue",
|
||||
Desc: "Your Current Queue",
|
||||
SpotifyItem: spotify.Queue{},
|
||||
})
|
||||
if playlists != nil && playlists.Total != 0 {
|
||||
for _, playlist := range playlists.Playlists {
|
||||
items = append(items, mainItem{
|
||||
Name: playlist.Name,
|
||||
Desc: stripHtmlRegex(playlist.Description),
|
||||
SpotifyItem: playlist,
|
||||
})
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func stripHtmlRegex(s string) string {
|
||||
r := regexp.MustCompile(regex)
|
||||
return r.ReplaceAllString(s, "")
|
||||
}
|
Loading…
Reference in New Issue
Block a user