add experimental youtube-link command. fix pagination issues throughout tui
This commit is contained in:
parent
ee9c2299dc
commit
8f179f0bc6
9
go.mod
9
go.mod
@ -12,19 +12,24 @@ require (
|
||||
github.com/cristalhq/aconfig/aconfigyaml v0.17.1
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/zmb3/spotify/v2 v2.3.1
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5
|
||||
google.golang.org/api v0.30.0
|
||||
modernc.org/sqlite v1.20.4
|
||||
tuxpa.in/a/zlog v1.60.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.65.0 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3 // indirect
|
||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
@ -41,14 +46,16 @@ require (
|
||||
github.com/rs/zerolog v1.28.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
go.opencensus.io v0.22.4 // indirect
|
||||
golang.org/x/mod v0.3.0 // indirect
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
|
||||
google.golang.org/grpc v1.31.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
|
7
go.sum
7
go.sum
@ -12,6 +12,7 @@ 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=
|
||||
@ -82,6 +83,7 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
|
||||
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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@ -131,6 +133,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
||||
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/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/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=
|
||||
@ -212,6 +215,7 @@ 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=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -408,6 +412,7 @@ 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/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@ -445,6 +450,7 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY
|
||||
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/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
@ -457,6 +463,7 @@ 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/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=
|
||||
|
28
src/cmd/youtube-link.go
Normal file
28
src/cmd/youtube-link.go
Normal file
@ -0,0 +1,28 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// youtubeLinkCmd represents the youtube-link command
|
||||
var youtubeLinkCmd = &cobra.Command{
|
||||
Use: "youtube-link",
|
||||
Aliases: []string{"yl"},
|
||||
Short: "Print youtube link to currently playing song",
|
||||
Long: `Print youtube link to currently playing song`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
link, err := commands.YoutubeLink(ctx)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Print(link)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(youtubeLinkCmd)
|
||||
}
|
@ -18,6 +18,7 @@ import (
|
||||
"git.asdf.cafe/abs3nt/gospt/src/auth"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/cache"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/gctx"
|
||||
"git.asdf.cafe/abs3nt/gospt/src/youtube"
|
||||
|
||||
"github.com/zmb3/spotify/v2"
|
||||
_ "modernc.org/sqlite"
|
||||
@ -913,6 +914,15 @@ func (c *Commands) Link(ctx *gctx.Context) (string, error) {
|
||||
return state.Item.ExternalURLs["spotify"], nil
|
||||
}
|
||||
|
||||
func (c *Commands) YoutubeLink(ctx *gctx.Context) (string, error) {
|
||||
state, err := c.Client().PlayerState(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
link := youtube.Search(state.Item.Artists[0].Name + state.Item.Name)
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func (c *Commands) LinkContext(ctx *gctx.Context) (string, error) {
|
||||
state, err := c.Client().PlayerState(ctx)
|
||||
if err != nil {
|
||||
|
@ -125,6 +125,7 @@ func (m *mainModel) PlayRadio() {
|
||||
}
|
||||
|
||||
func (m *mainModel) GoBack() (tea.Cmd, error) {
|
||||
page = 1
|
||||
switch m.mode {
|
||||
case Main:
|
||||
return tea.Quit, nil
|
||||
@ -242,6 +243,7 @@ func (m *mainModel) CopyToClipboard() error {
|
||||
func (m *mainModel) SelectItem() error {
|
||||
switch m.mode {
|
||||
case Search:
|
||||
page = 1
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case *spotify.FullArtistPage:
|
||||
m.mode = SearchArtists
|
||||
@ -277,6 +279,7 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.ResetSelected()
|
||||
}
|
||||
case SearchArtists:
|
||||
page = 1
|
||||
m.mode = SearchArtist
|
||||
m.artist = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleArtist)
|
||||
new_items, err := ArtistAlbumsView(m.ctx, m.artist.ID, m.commands)
|
||||
@ -286,6 +289,7 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchArtist:
|
||||
page = 1
|
||||
m.mode = SearchArtistAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
@ -295,6 +299,7 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchAlbums:
|
||||
page = 1
|
||||
m.mode = SearchAlbum
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
@ -304,6 +309,7 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case SearchPlaylists:
|
||||
page = 1
|
||||
m.mode = SearchPlaylist
|
||||
playlist := m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimplePlaylist)
|
||||
m.playlist = playlist
|
||||
@ -314,6 +320,7 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.SetItems(new_items)
|
||||
m.list.ResetSelected()
|
||||
case Main:
|
||||
page = 1
|
||||
switch item := m.list.SelectedItem().(mainItem).SpotifyItem.(type) {
|
||||
case *spotify.FullArtistCursorPage:
|
||||
m.mode = Artists
|
||||
@ -350,6 +357,7 @@ func (m *mainModel) SelectItem() error {
|
||||
m.list.ResetSelected()
|
||||
}
|
||||
case Albums:
|
||||
page = 1
|
||||
m.mode = Album
|
||||
m.album = m.list.SelectedItem().(mainItem).SpotifyItem.(spotify.SimpleAlbum)
|
||||
new_items, err := AlbumTracksView(m.ctx, m.album.ID, m.commands)
|
||||
@ -504,7 +512,7 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
default:
|
||||
}
|
||||
// Call for more items if needed
|
||||
if m.list.Paginator.Page == m.list.Paginator.TotalPages-2 && m.list.Cursor() == 0 {
|
||||
if m.list.Paginator.Page == m.list.Paginator.TotalPages-1 && m.list.Cursor() == 0 {
|
||||
// if last request was still full request more
|
||||
if len(m.list.Items())%50 == 0 {
|
||||
go m.LoadMoreItems()
|
||||
|
125
src/youtube/youtube.go
Normal file
125
src/youtube/youtube.go
Normal file
@ -0,0 +1,125 @@
|
||||
package youtube
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/option"
|
||||
"google.golang.org/api/youtube/v3"
|
||||
)
|
||||
|
||||
// getClient uses a Context and Config to retrieve a Token
|
||||
// then generate a Client. It returns the generated Client.
|
||||
func getClient(ctx context.Context, config *oauth2.Config) *http.Client {
|
||||
cacheFile, err := tokenCacheFile()
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get path to cached credential file. %v", err)
|
||||
}
|
||||
tok, err := tokenFromFile(cacheFile)
|
||||
if err != nil {
|
||||
tok = getTokenFromWeb(config)
|
||||
saveToken(cacheFile, tok)
|
||||
}
|
||||
return config.Client(ctx, tok)
|
||||
}
|
||||
|
||||
// getTokenFromWeb uses Config to request a Token.
|
||||
// It returns the retrieved Token.
|
||||
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
|
||||
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
|
||||
fmt.Printf("Go to the following link in your browser then type the "+
|
||||
"authorization code: \n%v\n", authURL)
|
||||
|
||||
var code string
|
||||
if _, err := fmt.Scan(&code); err != nil {
|
||||
log.Fatalf("Unable to read authorization code %v", err)
|
||||
}
|
||||
|
||||
tok, err := config.Exchange(context.Background(), code)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to retrieve token from web %v", err)
|
||||
}
|
||||
return tok
|
||||
}
|
||||
|
||||
// tokenCacheFile generates credential file path/filename.
|
||||
// It returns the generated credential path/filename.
|
||||
func tokenCacheFile() (string, error) {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials")
|
||||
err = os.MkdirAll(tokenCacheDir, 0o700)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(tokenCacheDir,
|
||||
url.QueryEscape("youtube-go-quickstart.json")), err
|
||||
}
|
||||
|
||||
// tokenFromFile retrieves a Token from a given file path.
|
||||
// It returns the retrieved Token and any read error encountered.
|
||||
func tokenFromFile(file string) (*oauth2.Token, error) {
|
||||
f, err := os.Open(file)
|
||||
handleError(err, "Error opening file")
|
||||
t := &oauth2.Token{}
|
||||
err = json.NewDecoder(f).Decode(t)
|
||||
defer f.Close()
|
||||
return t, err
|
||||
}
|
||||
|
||||
// saveToken uses a file path to create a file and store the
|
||||
// token in it.
|
||||
func saveToken(file string, token *oauth2.Token) {
|
||||
fmt.Printf("Saving credential file to: %s\n", file)
|
||||
f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to cache oauth token: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
err = json.NewEncoder(f).Encode(token)
|
||||
handleError(err, "Error encoding token")
|
||||
}
|
||||
|
||||
func handleError(err error, message string) {
|
||||
if message == "" {
|
||||
message = "Error making API call"
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf(message+": %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func Search(query string) string {
|
||||
ctx := context.Background()
|
||||
|
||||
confDir, _ := os.UserConfigDir()
|
||||
b, err := os.ReadFile(filepath.Join(confDir, "gospt", "client_secret.json"))
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to read client secret file: %v", err)
|
||||
}
|
||||
|
||||
config, err := google.ConfigFromJSON(b, youtube.YoutubeReadonlyScope)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to parse client secret file to config: %v", err)
|
||||
}
|
||||
client := getClient(ctx, config)
|
||||
service, err := youtube.NewService(ctx, option.WithHTTPClient(client))
|
||||
|
||||
handleError(err, "Error creating YouTube client")
|
||||
call := service.Search.List([]string{"snippet"})
|
||||
call.Q(query)
|
||||
response, err := call.Do()
|
||||
handleError(err, "")
|
||||
return fmt.Sprintf("https://www.youtube.com/watch?v=%s", response.Items[0].Id.VideoId)
|
||||
}
|
Loading…
Reference in New Issue
Block a user