all repos — fixyoutube-go @ dfabe54c50cb40c8ffbe27dd08c2c0da0d8055b4

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

invidious/invidious.go (view raw)

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