Youtube integration
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Tyler 2019-10-04 00:08:03 -04:00
parent ef026cf138
commit a5f2d06861
4 changed files with 175 additions and 8 deletions

View File

@ -9,4 +9,6 @@ steps:
- go test -v - go test -v
environment: environment:
IMGUR_CLIENT_ID: IMGUR_CLIENT_ID:
from_secret: imgur_client_id from_secret: imgur_client_id
YOUTUBE_KEY:
from_secret: youtube_key

View File

@ -15,6 +15,7 @@ type LinkInfoApi struct {
Client *http.Client Client *http.Client
Imgur *ImgurInfoApi Imgur *ImgurInfoApi
Youtube *YoutubeInfoApi
linkHandlers []*LinkHandler linkHandlers []*LinkHandler
} }
@ -38,6 +39,8 @@ func New(opts ...Option) *LinkInfoApi {
switch opt.(type) { switch opt.(type) {
case *ImgurOptions: case *ImgurOptions:
api.Imgur = &ImgurInfoApi{api, opt.(*ImgurOptions)} api.Imgur = &ImgurInfoApi{api, opt.(*ImgurOptions)}
case *YoutubeOptions:
api.Youtube = &YoutubeInfoApi{api, opt.(*YoutubeOptions)}
} }
} }
@ -56,9 +59,11 @@ func (i *LinkInfoApi) registerDefaultHandlers() {
}) })
} }
i.linkHandlers = []*LinkHandler{ if i.Youtube != nil {
{Hosts: youtubeHosts, Handler: i.YoutubeLinkHandler}, i.linkHandlers = append(i.linkHandlers, &LinkHandler{
{Hosts: twitterHosts, Handler: i.TwitterLinkHandler}, Hosts: youtubeHosts,
Handler: i.Youtube.Handler,
})
} }
} }

View File

@ -1,9 +1,147 @@
package linkinfo package linkinfo
var ( import (
youtubeHosts = []string{"youtube.com", "youtu.be"} "encoding/json"
"errors"
"fmt"
"math"
"net/http"
"net/url"
"regexp"
"strings"
"time"
) )
func (i *LinkInfoApi) YoutubeLinkHandler(link string) (*LinkInfo, error) { const youtubeApiUrl = "https://www.googleapis.com/youtube/v3/videos"
return nil, nil
var (
youtubeHosts = []string{"youtube.com", "youtu.be"}
durationRegexp = regexp.MustCompile("^00:")
)
type YoutubeOptions struct {
Option
Key string
}
type YoutubeInfoApi struct {
api *LinkInfoApi
opts *YoutubeOptions
}
type youtubeResponse struct {
Error *youtubeError `json:"error"`
Items []*youtubeItem `json:"items"`
}
type youtubeError struct {
Code int `json:"code"`
Message string `json:"message"`
}
type youtubeItem struct {
Snippet *youtubeSnippet `json:"snippet"`
ContentDetails *youtubeContentDetails `json:"contentDetails"`
}
type youtubeSnippet struct {
Title string `json:"title"`
Description string `json:"description"`
}
type youtubeContentDetails struct {
Duration string `json:"duration"`
}
func (i *YoutubeInfoApi) Handler(link string) (*LinkInfo, error) {
u, err := url.Parse(link)
if err != nil {
return nil, err
}
var id string
query := u.Query()
if query != nil && query.Get("v") != "" {
id = query.Get("v")
} else if u.Host == "youtu.be" {
id = u.Path[1:]
}
if id == "" {
return i.api.DefaultLinkHandler(link)
}
params := &url.Values{}
params.Set("id", id)
params.Set("part", "id, snippet, contentDetails")
params.Set("key", i.opts.Key)
req, err := http.NewRequest("GET", youtubeApiUrl+"?"+params.Encode(), nil)
if err != nil {
return nil, err
}
res, err := i.api.Client.Do(req)
if err != nil {
return nil, err
}
var response youtubeResponse
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
return nil, err
}
if response.Error != nil {
return nil, errors.New(response.Error.Message)
}
if len(response.Items) < 1 {
return nil, errors.New("video not found")
}
item := response.Items[0]
duration, err := time.ParseDuration(strings.ToLower(item.ContentDetails.Duration[2:]))
trimmedDuration := durationRegexp.ReplaceAllString(humanizeDuration(duration), "")
ret := &LinkInfo{
Title: item.Snippet.Title + " [" + trimmedDuration + "]",
Description: item.Snippet.Description,
Duration: item.ContentDetails.Duration,
}
return ret, nil
}
// https://gist.github.com/harshavardhana/327e0577c4fed9211f65#gistcomment-2366908
func humanizeDuration(duration time.Duration) string {
hours := int64(duration.Hours())
minutes := int64(math.Mod(duration.Minutes(), 60))
seconds := int64(math.Mod(duration.Seconds(), 60))
chunks := []struct {
singularName string
amount int64
}{
{"hour", hours},
{"minute", minutes},
{"second", seconds},
}
parts := make([]string, 0)
for _, chunk := range chunks {
parts = append(parts, fmt.Sprintf("%02d", chunk.amount))
}
return strings.Join(parts, ":")
} }

22
youtube_test.go Normal file
View File

@ -0,0 +1,22 @@
package linkinfo
import (
"os"
"testing"
)
func TestYoutubeInfoApi_Handler(t *testing.T) {
api := New(&YoutubeOptions{
Key: os.Getenv("YOUTUBE_KEY"),
})
info, err := api.Youtube.Handler("https://www.youtube.com/watch?v=1r4Md5WKaqs")
if err != nil {
t.Fatal("Error getting youtube info:", err)
}
if info.Title != "CHVRCHES - Graves [04:46]" {
t.Fatal("Unexpected title", info.Title)
}
}