all repos — fixyoutube-go @ ca543233e814d5ac68644a39d8de37ee43f2119f

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}
 19
 20func (c *Client) fetchVideo(videoId string) (*Video, int) {
 21	endpoint := fmt.Sprintf(videosEndpoint, c.Instance, url.QueryEscape(videoId))
 22	resp, err := c.http.Get(endpoint)
 23	if err != nil {
 24		logger.Error(err)
 25		return nil, http.StatusInternalServerError
 26	}
 27	defer resp.Body.Close()
 28
 29	if resp.StatusCode != http.StatusOK {
 30		logger.Warn("Invidious gave the following status code: ", resp.StatusCode)
 31		if resp.StatusCode == http.StatusForbidden {
 32			return nil, http.StatusInternalServerError
 33		}
 34		return nil, http.StatusNotFound
 35	}
 36
 37	body, err := io.ReadAll(resp.Body)
 38	if err != nil {
 39		logger.Error(err)
 40		return nil, http.StatusInternalServerError
 41	}
 42
 43	res := &Video{}
 44	err = json.Unmarshal(body, res)
 45	if err != nil {
 46		logger.Error(err)
 47		return nil, http.StatusInternalServerError
 48	}
 49
 50	mp4Test := func(f Format) bool { return f.Container == "mp4" }
 51	res.Formats = filter(res.Formats, mp4Test)
 52
 53	expireString := expireRegex.FindStringSubmatch(res.Formats[0].Url)
 54	expireTimestamp, err := strconv.ParseInt(expireString[1], 10, 64)
 55	if err != nil {
 56		logger.Error(err)
 57		return nil, http.StatusInternalServerError
 58	}
 59	res.Expire = time.Unix(expireTimestamp, 0)
 60
 61	vb, i := c.findCompatibleFormat(res)
 62	if i < 0 {
 63		logger.Warn("No compatible formats found for video.")
 64		res.Url = ""
 65	} else {
 66		videoBuffer := vb.Clone()
 67		c.buffers.Set(videoId, videoBuffer)
 68		res.Url = res.Formats[i].Url
 69	}
 70
 71	return res, http.StatusOK
 72}
 73
 74func (c *Client) NewInstance() error {
 75	if c.Instance != "" {
 76		err := fmt.Errorf("generic error")
 77		c.timeouts.Set(c.Instance, &err)
 78	}
 79
 80	resp, err := c.http.Get(instancesEndpoint)
 81	if err != nil {
 82		return err
 83	}
 84	defer resp.Body.Close()
 85
 86	body, err := io.ReadAll(resp.Body)
 87	if err != nil {
 88		return err
 89	}
 90
 91	if resp.StatusCode != http.StatusOK {
 92		return fmt.Errorf("HTTP error: %d", resp.StatusCode)
 93	}
 94
 95	var jsonArray [][]interface{}
 96	err = json.Unmarshal(body, &jsonArray)
 97	if err != nil {
 98		logger.Error("Could not unmarshal JSON response for instances.")
 99		return err
100	}
101
102	for i := range jsonArray {
103		instance := jsonArray[i][0].(string)
104		if !c.timeouts.Has(instance) {
105			c.Instance = instance
106			logger.Info("Using new instance: ", c.Instance)
107			return nil
108		}
109	}
110
111	return fmt.Errorf("cannot find a valid instance")
112}