all repos — fixyoutube-go @ 636c962ec2a61c90f7451b365aa808a957a9082e

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/BiRabittoh/fixyoutube-go/volatile"
 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 Client struct {
 22	http     *http.Client
 23	timeouts *volatile.Volatile[string, error]
 24	Instance string
 25}
 26
 27type Video struct {
 28	VideoId     string   `json:"videoId"`
 29	Title       string   `json:"title"`
 30	Description string   `json:"description"`
 31	Uploader    string   `json:"author"`
 32	Duration    int      `json:"lengthSeconds"`
 33	Formats     []Format `json:"formatStreams"`
 34	Timestamp   time.Time
 35	Expire      time.Time
 36	Url         string
 37}
 38
 39func filter[T any](ss []T, test func(T) bool) (ret []T) {
 40	for _, s := range ss {
 41		if test(s) {
 42			ret = append(ret, s)
 43		}
 44	}
 45	return
 46}
 47
 48func parseOrZero(number string) int {
 49	res, err := strconv.Atoi(number)
 50	if err != nil {
 51		return 0
 52	}
 53	return res
 54}
 55
 56func (c *Client) GetVideo(videoId string, fromCache bool) (*Video, error) {
 57	logger.Info("Video https://youtu.be/", videoId, " was requested.")
 58
 59	var video *Video
 60	var err error
 61
 62	if fromCache {
 63		video, err = GetVideoDB(videoId)
 64		if err == nil {
 65			logger.Info("Found a valid cache entry.")
 66			return video, nil
 67		}
 68	}
 69
 70	video, httpErr := c.fetchVideo(videoId)
 71
 72	switch httpErr {
 73	case http.StatusOK:
 74		logger.Info("Retrieved by API.")
 75		break
 76	case http.StatusNotFound:
 77		logger.Debug("Video does not exist or can't be retrieved.")
 78		return nil, err
 79	default:
 80		err = c.NewInstance()
 81		if err != nil {
 82			logger.Error("Could not get a new instance: ", err)
 83			time.Sleep(10 * time.Second)
 84		}
 85		return c.GetVideo(videoId, true)
 86	}
 87
 88	err = CacheVideoDB(*video)
 89	if err != nil {
 90		logger.Warn("Could not cache video id: ", videoId)
 91		logger.Warn(err)
 92	}
 93	return video, nil
 94}
 95
 96func NewClient(httpClient *http.Client) *Client {
 97	InitDB()
 98	timeouts := volatile.NewVolatile[string, error](timeoutDuration)
 99	client := &Client{
100		http:     httpClient,
101		timeouts: timeouts,
102	}
103	err := client.NewInstance()
104	if err != nil {
105		logger.Fatal(err)
106	}
107	return client
108}