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}