all repos — fixyoutube-go @ 29d827408340269e64205c65fe96873275948e65

A better way to embed YouTube videos everywhere (inspired by FixTweet).

invidious/api.go (view raw)

  1package invidious
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"io"
  7	"net/http"
  8	"net/url"
  9	"strconv"
 10	"time"
 11)
 12
 13type Format struct {
 14	Name      string `json:"qualityLabel"`
 15	Url       string `json:"url"`
 16	Container string `json:"container"`
 17	Size      string `json:"size"`
 18	Itag      string `json:"itag"`
 19}
 20
 21type VideoThumbnail struct {
 22	Quality string `json:"quality"`
 23	URL     string `json:"url"`
 24	Width   int    `json:"width"`
 25	Height  int    `json:"height"`
 26}
 27
 28func (c *Client) fetchVideo(videoId string) (*Video, int) {
 29	endpoint := fmt.Sprintf(videosEndpoint, c.Instance, url.QueryEscape(videoId))
 30	resp, err := c.http.Get(endpoint)
 31	if err != nil {
 32		logger.Error(err)
 33		return nil, http.StatusInternalServerError
 34	}
 35	defer resp.Body.Close()
 36
 37	if resp.StatusCode == http.StatusNotFound {
 38		return nil, http.StatusNotFound
 39	}
 40
 41	if resp.StatusCode != http.StatusOK {
 42		logger.Warn("Invidious gave the following status code: ", resp.StatusCode)
 43		return nil, resp.StatusCode
 44	}
 45
 46	body, err := io.ReadAll(resp.Body)
 47	if err != nil {
 48		logger.Error(err)
 49		return nil, http.StatusInternalServerError
 50	}
 51
 52	res := &Video{}
 53	err = json.Unmarshal(body, res)
 54	if err != nil {
 55		logger.Error(err)
 56		return nil, http.StatusInternalServerError
 57	}
 58
 59	if len(res.VideoThumbnails) > 0 {
 60		res.Thumbnail = res.VideoThumbnails[0].URL
 61	}
 62
 63	mp4Test := func(f Format) bool { return f.Itag == "18" }
 64	res.Formats = filter(res.Formats, mp4Test)
 65
 66	expireString := expireRegex.FindStringSubmatch(res.Formats[0].Url)
 67	expireTimestamp, err := strconv.ParseInt(expireString[1], 10, 64)
 68	if err != nil {
 69		logger.Error(err)
 70		return nil, http.StatusInternalServerError
 71	}
 72	res.Expire = time.Unix(expireTimestamp, 0)
 73
 74	vb, i := c.findCompatibleFormat(res)
 75	if i < 0 {
 76		logger.Warn("No compatible formats found for video.")
 77		res.Url = ""
 78	} else {
 79		videoBuffer := vb.Clone()
 80		c.buffers.Set(videoId, videoBuffer)
 81		res.Url = res.Formats[i].Url
 82	}
 83
 84	return res, http.StatusOK
 85}
 86
 87func (c *Client) NewInstance() error {
 88	if c.Instance != "" {
 89		err := fmt.Errorf("generic error")
 90		c.timeouts.Set(c.Instance, &err)
 91	}
 92
 93	resp, err := c.http.Get(instancesEndpoint)
 94	if err != nil {
 95		return err
 96	}
 97	defer resp.Body.Close()
 98
 99	body, err := io.ReadAll(resp.Body)
100	if err != nil {
101		return err
102	}
103
104	if resp.StatusCode != http.StatusOK {
105		return fmt.Errorf("HTTP error: %d", resp.StatusCode)
106	}
107
108	var jsonArray [][]interface{}
109	err = json.Unmarshal(body, &jsonArray)
110	if err != nil {
111		logger.Error("Could not unmarshal JSON response for instances.")
112		return err
113	}
114
115	for i := range jsonArray {
116		instance := jsonArray[i][0].(string)
117		if !c.timeouts.Has(instance) {
118			c.Instance = instance
119			logger.Info("Using new instance: ", c.Instance)
120			return nil
121		}
122	}
123
124	return fmt.Errorf("cannot find a valid instance")
125}