all repos — fixyoutube-go @ e57b55acea1bb8e55a8620c62c41cd23984e0c46

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

invidious/invidious.go (view raw)

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