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

@ -10,3 +10,5 @@ steps:
environment:
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
Imgur *ImgurInfoApi
Youtube *YoutubeInfoApi
linkHandlers []*LinkHandler
}
@ -38,6 +39,8 @@ func New(opts ...Option) *LinkInfoApi {
switch opt.(type) {
case *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{
{Hosts: youtubeHosts, Handler: i.YoutubeLinkHandler},
{Hosts: twitterHosts, Handler: i.TwitterLinkHandler},
if i.Youtube != nil {
i.linkHandlers = append(i.linkHandlers, &LinkHandler{
Hosts: youtubeHosts,
Handler: i.Youtube.Handler,
})
}
}

View File

@ -1,9 +1,147 @@
package linkinfo
import (
"encoding/json"
"errors"
"fmt"
"math"
"net/http"
"net/url"
"regexp"
"strings"
"time"
)
const youtubeApiUrl = "https://www.googleapis.com/youtube/v3/videos"
var (
youtubeHosts = []string{"youtube.com", "youtu.be"}
durationRegexp = regexp.MustCompile("^00:")
)
func (i *LinkInfoApi) YoutubeLinkHandler(link string) (*LinkInfo, error) {
return nil, nil
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)
}
}