linkinfo/youtube.go

148 lines
2.8 KiB
Go

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:")
)
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, ":")
}