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