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}
19
20func (c *Client) fetchVideo(videoId string) (*Video, int) {
21 endpoint := fmt.Sprintf(videosEndpoint, c.Instance, url.QueryEscape(videoId))
22 resp, err := c.http.Get(endpoint)
23 if err != nil {
24 logger.Error(err)
25 return nil, http.StatusInternalServerError
26 }
27 defer resp.Body.Close()
28
29 if resp.StatusCode != http.StatusOK {
30 logger.Warn("Invidious gave the following status code: ", resp.StatusCode)
31 if resp.StatusCode == http.StatusForbidden {
32 return nil, http.StatusInternalServerError
33 }
34 return nil, http.StatusNotFound
35 }
36
37 body, err := io.ReadAll(resp.Body)
38 if err != nil {
39 logger.Error(err)
40 return nil, http.StatusInternalServerError
41 }
42
43 res := &Video{}
44 err = json.Unmarshal(body, res)
45 if err != nil {
46 logger.Error(err)
47 return nil, http.StatusInternalServerError
48 }
49
50 mp4Test := func(f Format) bool { return f.Container == "mp4" }
51 res.Formats = filter(res.Formats, mp4Test)
52
53 expireString := expireRegex.FindStringSubmatch(res.Formats[0].Url)
54 expireTimestamp, err := strconv.ParseInt(expireString[1], 10, 64)
55 if err != nil {
56 logger.Error(err)
57 return nil, http.StatusInternalServerError
58 }
59 res.Expire = time.Unix(expireTimestamp, 0)
60
61 vb, i := c.findCompatibleFormat(res)
62 if i < 0 {
63 logger.Warn("No compatible formats found for video.")
64 res.Url = ""
65 } else {
66 videoBuffer := vb.Clone()
67 c.buffers.Set(videoId, videoBuffer)
68 res.Url = res.Formats[i].Url
69 }
70
71 return res, http.StatusOK
72}
73
74func (c *Client) NewInstance() error {
75 if c.Instance != "" {
76 err := fmt.Errorf("generic error")
77 c.timeouts.Set(c.Instance, &err)
78 }
79
80 resp, err := c.http.Get(instancesEndpoint)
81 if err != nil {
82 return err
83 }
84 defer resp.Body.Close()
85
86 body, err := io.ReadAll(resp.Body)
87 if err != nil {
88 return err
89 }
90
91 if resp.StatusCode != http.StatusOK {
92 return fmt.Errorf("HTTP error: %d", resp.StatusCode)
93 }
94
95 var jsonArray [][]interface{}
96 err = json.Unmarshal(body, &jsonArray)
97 if err != nil {
98 logger.Error("Could not unmarshal JSON response for instances.")
99 return err
100 }
101
102 for i := range jsonArray {
103 instance := jsonArray[i][0].(string)
104 if !c.timeouts.Has(instance) {
105 c.Instance = instance
106 logger.Info("Using new instance: ", c.Instance)
107 return nil
108 }
109 }
110
111 return fmt.Errorf("cannot find a valid instance")
112}