invidious/invidious.go (view raw)
1package invidious
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "net/http"
8 "net/url"
9 "strconv"
10 "strings"
11)
12
13type Client struct {
14 http *http.Client
15 Instance string
16}
17
18type Format struct {
19 Url string `json:"url"`
20 Container string `json:"container"`
21 Size string `json:"size"`
22}
23
24type Video struct {
25 VideoId string `json:"videoId"`
26 Title string `json:"title"`
27 Description string `json:"description"`
28 Uploader string `json:"author"`
29 Duration int `json:"lengthSeconds"`
30 FormatStreams []Format `json:"formatStreams"`
31 Url string
32 Height int
33 Width int
34}
35
36func filter[T any](ss []T, test func(T) bool) (ret []T) {
37 for _, s := range ss {
38 if test(s) {
39 ret = append(ret, s)
40 }
41 }
42 return
43}
44
45func parseOrZero(number string) int {
46 res, err := strconv.Atoi(number)
47 if err != nil {
48 return 0
49 }
50 return res
51}
52
53func (c *Client) FetchEverything(videoId string) (*Video, error) {
54 endpoint := fmt.Sprintf("https://%s/api/v1/videos/%s?fields=videoId,title,description,author,lengthSeconds,size,formatStreams", c.Instance, url.QueryEscape(videoId))
55 resp, err := c.http.Get(endpoint)
56 if err != nil {
57 return nil, err
58 }
59
60 defer resp.Body.Close()
61
62 body, err := io.ReadAll(resp.Body)
63 if err != nil {
64 return nil, err
65 }
66
67 if resp.StatusCode != http.StatusOK {
68 return nil, fmt.Errorf(string(body))
69 }
70
71 res := &Video{}
72 err = json.Unmarshal(body, res)
73 if err != nil {
74 return nil, err
75 }
76
77 mp4Test := func(f Format) bool { return f.Container == "mp4" }
78 mp4Formats := filter(res.FormatStreams, mp4Test)
79 myFormat := mp4Formats[len(mp4Formats)-1]
80 mySize := strings.Split(myFormat.Size, "x")
81
82 res.Url = myFormat.Url
83 res.Width = parseOrZero(mySize[0])
84 res.Height = parseOrZero(mySize[1])
85
86 return res, err
87}
88
89func NewClient(httpClient *http.Client, instance string) *Client {
90 return &Client{httpClient, instance}
91}