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