all repos — fixyoutube-go @ a98db242719bb1b183676dd9eca524cbf3000361

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

invidious/invidious.go (view raw)

  1package invidious
  2
  3import (
  4	"net/http"
  5	"regexp"
  6	"strconv"
  7	"time"
  8
  9	"github.com/sirupsen/logrus"
 10)
 11
 12const timeoutDuration = 10 * time.Minute
 13const maxSizeBytes = 20000000 // 20 MB
 14const instancesEndpoint = "https://api.invidious.io/instances.json?sort_by=api,type"
 15const videosEndpoint = "https://%s/api/v1/videos/%s?fields=videoId,title,description,author,lengthSeconds,size,formatStreams"
 16
 17var expireRegex = regexp.MustCompile(`(?i)expire=(\d+)`)
 18var logger = logrus.New()
 19
 20type Timeout struct {
 21	Instance  string
 22	Timestamp time.Time
 23}
 24
 25type Client struct {
 26	http     *http.Client
 27	timeouts []Timeout
 28	Instance string
 29}
 30
 31type Format struct {
 32	VideoId   string
 33	Name      string `json:"qualityLabel"`
 34	Height    int
 35	Width     int
 36	Url       string `json:"url"`
 37	Container string `json:"container"`
 38	Size      string `json:"size"`
 39}
 40
 41type Video struct {
 42	VideoId     string   `json:"videoId"`
 43	Title       string   `json:"title"`
 44	Description string   `json:"description"`
 45	Uploader    string   `json:"author"`
 46	Duration    int      `json:"lengthSeconds"`
 47	Formats     []Format `json:"formatStreams"`
 48	Timestamp   time.Time
 49	Expire      time.Time
 50}
 51
 52func filter[T any](ss []T, test func(T) bool) (ret []T) {
 53	for _, s := range ss {
 54		if test(s) {
 55			ret = append(ret, s)
 56		}
 57	}
 58	return
 59}
 60
 61func parseOrZero(number string) int {
 62	res, err := strconv.Atoi(number)
 63	if err != nil {
 64		return 0
 65	}
 66	return res
 67}
 68
 69func (c *Client) GetVideo(videoId string, fromCache bool) (*Video, error) {
 70	logger.Info("Video https://youtu.be/", videoId, " was requested.")
 71
 72	var video *Video
 73	var err error
 74
 75	if fromCache {
 76		video, err = GetVideoDB(videoId)
 77		if err == nil {
 78			logger.Info("Found a valid cache entry.")
 79			return video, nil
 80		}
 81	}
 82
 83	video, httpErr := c.fetchVideo(videoId)
 84
 85	switch httpErr {
 86	case http.StatusOK:
 87		logger.Info("Retrieved by API.")
 88		break
 89	case http.StatusNotFound:
 90		logger.Debug("Video does not exist or can't be retrieved.")
 91		return nil, err
 92	default:
 93		fallthrough
 94	case http.StatusInternalServerError:
 95		err = c.NewInstance()
 96		if err != nil {
 97			logger.Error("Could not get a new instance: ", err)
 98			time.Sleep(10 * time.Second)
 99		}
100		return c.GetVideo(videoId, true)
101	}
102
103	err = CacheVideoDB(*video)
104	if err != nil {
105		logger.Warn("Could not cache video id: ", videoId)
106		logger.Warn(err)
107	}
108	return video, nil
109}
110
111func NewClient(httpClient *http.Client) *Client {
112	InitDB()
113	client := &Client{
114		http:     httpClient,
115		timeouts: []Timeout{},
116		Instance: "",
117	}
118	err := client.NewInstance()
119	if err != nil {
120		logger.Fatal(err)
121	}
122	return client
123}