all repos — fixyoutube-go @ 29d827408340269e64205c65fe96873275948e65

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	"time"
  8
  9	"github.com/BiRabittoh/fixyoutube-go/volatile"
 10	"github.com/sirupsen/logrus"
 11)
 12
 13const instancesEndpoint = "https://api.invidious.io/instances.json?sort_by=api,type"
 14const videosEndpoint = "https://%s/api/v1/videos/%s?fields=videoId,title,description,author,lengthSeconds,size,formatStreams"
 15
 16var expireRegex = regexp.MustCompile(`(?i)expire=(\d+)`)
 17var logger = logrus.New()
 18
 19type ClientOptions struct {
 20	CacheDuration   time.Duration
 21	TimeoutDuration time.Duration
 22	CleanupInterval time.Duration
 23	MaxSizeBytes    int64
 24}
 25
 26type VideoBuffer struct {
 27	Buffer *bytes.Buffer
 28	Length int64
 29}
 30
 31type Client struct {
 32	http     *http.Client
 33	timeouts *volatile.Volatile[string, error]
 34	buffers  *volatile.Volatile[string, VideoBuffer]
 35	Instance string
 36	Options  ClientOptions
 37}
 38
 39type Video struct {
 40	VideoId         string           `json:"videoId"`
 41	Title           string           `json:"title"`
 42	Description     string           `json:"description"`
 43	Uploader        string           `json:"author"`
 44	VideoThumbnails []VideoThumbnail `json:"videoThumbnails"`
 45	Duration        int              `json:"lengthSeconds"`
 46	Formats         []Format         `json:"formatStreams"`
 47	Expire          time.Time
 48	Url             string
 49	Thumbnail       string
 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 NewVideoBuffer(b *bytes.Buffer, l int64) *VideoBuffer {
 62	d := new(bytes.Buffer)
 63	d.Write(b.Bytes())
 64
 65	return &VideoBuffer{
 66		Buffer: d,
 67		Length: l,
 68	}
 69}
 70
 71func (vb *VideoBuffer) Clone() *VideoBuffer {
 72	return NewVideoBuffer(vb.Buffer, vb.Length)
 73}
 74
 75func (vb *VideoBuffer) ValidateLength() bool {
 76	return vb.Length > 0 && vb.Length == int64(vb.Buffer.Len())
 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	case http.StatusNotFound:
 99		logger.Debug("Video does not exist or can't be retrieved.")
100		return nil, err
101	default:
102		err = c.NewInstance()
103		if err != nil {
104			logger.Error("Could not get a new instance: ", err)
105			time.Sleep(10 * time.Second)
106		}
107		return c.GetVideo(videoId, true)
108	}
109
110	err = CacheVideoDB(*video)
111	if err != nil {
112		logger.Warn("Could not cache video id: ", videoId)
113		logger.Warn(err)
114	}
115	return video, nil
116}
117
118func NewClient(httpClient *http.Client, options ClientOptions) *Client {
119	InitDB()
120	timeouts := volatile.NewVolatile[string, error](options.TimeoutDuration, options.CleanupInterval)
121	buffers := volatile.NewVolatile[string, VideoBuffer](options.CacheDuration, options.CleanupInterval)
122	client := &Client{
123		http:     httpClient,
124		timeouts: timeouts,
125		buffers:  buffers,
126		Options:  options,
127	}
128	err := client.NewInstance()
129	if err != nil {
130		logger.Fatal(err)
131	}
132	return client
133}