invidious/api.go (view raw)
1package invidious
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "net/http"
8 "net/url"
9 "strconv"
10 "time"
11)
12
13type Format struct {
14 Name string `json:"qualityLabel"`
15 Url string `json:"url"`
16 Container string `json:"container"`
17 Size string `json:"size"`
18 Itag string `json:"itag"`
19}
20
21type VideoThumbnail struct {
22 Quality string `json:"quality"`
23 URL string `json:"url"`
24 Width int `json:"width"`
25 Height int `json:"height"`
26}
27
28func (c *Client) fetchVideo(videoId string) (*Video, int) {
29 endpoint := fmt.Sprintf(videosEndpoint, c.Instance, url.QueryEscape(videoId))
30 resp, err := c.http.Get(endpoint)
31 if err != nil {
32 logger.Error(err)
33 return nil, http.StatusInternalServerError
34 }
35 defer resp.Body.Close()
36
37 if resp.StatusCode == http.StatusNotFound {
38 return nil, http.StatusNotFound
39 }
40
41 if resp.StatusCode != http.StatusOK {
42 logger.Warn("Invidious gave the following status code: ", resp.StatusCode)
43 return nil, resp.StatusCode
44 }
45
46 body, err := io.ReadAll(resp.Body)
47 if err != nil {
48 logger.Error(err)
49 return nil, http.StatusInternalServerError
50 }
51
52 res := &Video{}
53 err = json.Unmarshal(body, res)
54 if err != nil {
55 logger.Error(err)
56 return nil, http.StatusInternalServerError
57 }
58
59 if len(res.VideoThumbnails) > 0 {
60 res.Thumbnail = res.VideoThumbnails[0].URL
61 }
62
63 mp4Test := func(f Format) bool { return f.Itag == "18" }
64 res.Formats = filter(res.Formats, mp4Test)
65
66 expireString := expireRegex.FindStringSubmatch(res.Formats[0].Url)
67 expireTimestamp, err := strconv.ParseInt(expireString[1], 10, 64)
68 if err != nil {
69 logger.Error(err)
70 return nil, http.StatusInternalServerError
71 }
72 res.Expire = time.Unix(expireTimestamp, 0)
73
74 vb, i := c.findCompatibleFormat(res)
75 if i < 0 {
76 logger.Warn("No compatible formats found for video.")
77 res.Url = ""
78 } else {
79 videoBuffer := vb.Clone()
80 c.buffers.Set(videoId, videoBuffer)
81 res.Url = res.Formats[i].Url
82 }
83
84 return res, http.StatusOK
85}
86
87func (c *Client) NewInstance() error {
88 if c.Instance != "" {
89 err := fmt.Errorf("generic error")
90 c.timeouts.Set(c.Instance, &err)
91 }
92
93 resp, err := c.http.Get(instancesEndpoint)
94 if err != nil {
95 return err
96 }
97 defer resp.Body.Close()
98
99 body, err := io.ReadAll(resp.Body)
100 if err != nil {
101 return err
102 }
103
104 if resp.StatusCode != http.StatusOK {
105 return fmt.Errorf("HTTP error: %d", resp.StatusCode)
106 }
107
108 var jsonArray [][]interface{}
109 err = json.Unmarshal(body, &jsonArray)
110 if err != nil {
111 logger.Error("Could not unmarshal JSON response for instances.")
112 return err
113 }
114
115 for i := range jsonArray {
116 instance := jsonArray[i][0].(string)
117 if !c.timeouts.Has(instance) {
118 c.Instance = instance
119 logger.Info("Using new instance: ", c.Instance)
120 return nil
121 }
122 }
123
124 return fmt.Errorf("cannot find a valid instance")
125}