handlers.go (view raw)
1package main
2
3import (
4 "bytes"
5 "embed"
6 "io"
7 "net/http"
8 "net/url"
9 "regexp"
10 "strconv"
11 "text/template"
12
13 "github.com/BiRabittoh/fixyoutube-go/invidious"
14)
15
16const templatesDirectory = "templates/"
17
18var (
19 //go:embed templates/index.html templates/video.html
20 templates embed.FS
21 indexTemplate = template.Must(template.ParseFS(templates, templatesDirectory+"index.html"))
22 videoTemplate = template.Must(template.ParseFS(templates, templatesDirectory+"video.html"))
23 userAgentRegex = regexp.MustCompile(`(?i)bot|facebook|embed|got|firefox\/92|firefox\/38|curl|wget|go-http|yahoo|generator|whatsapp|preview|link|proxy|vkshare|images|analyzer|index|crawl|spider|python|cfnetwork|node`)
24 videoRegex = regexp.MustCompile(`(?i)^[a-z0-9_-]{11}$`)
25)
26
27func indexHandler(w http.ResponseWriter, r *http.Request) {
28 buf := &bytes.Buffer{}
29 err := indexTemplate.Execute(buf, nil)
30 if err != nil {
31 logger.Error("Failed to fill index template.")
32 http.Error(w, err.Error(), http.StatusInternalServerError)
33 return
34 }
35
36 buf.WriteTo(w)
37}
38
39func clearHandler(w http.ResponseWriter, r *http.Request) {
40 if r.Method != http.MethodPost {
41 http.Redirect(w, r, "/", http.StatusFound)
42 return
43 }
44
45 err := r.ParseForm()
46 if err != nil {
47 logger.Error("Failed to parse form in /clear.")
48 http.Error(w, err.Error(), http.StatusInternalServerError)
49 return
50 }
51
52 providedKey := r.PostForm.Get("apiKey")
53 if providedKey != apiKey {
54 logger.Debug("Wrong API key: ", providedKey)
55 http.Error(w, "Wrong or missing API key.", http.StatusForbidden)
56 return
57 }
58
59 invidious.ClearDB()
60 logger.Info("Cache cleared.")
61 http.Error(w, "Done.", http.StatusOK)
62}
63
64func videoHandler(videoId string, invidiousClient *invidious.Client, w http.ResponseWriter, r *http.Request) {
65 url := "https://www.youtube.com/watch?v=" + videoId
66 /*
67 userAgent := r.UserAgent()
68 res := userAgentRegex.MatchString(userAgent)
69 if !res {
70 logger.Debug("Regex did not match. Redirecting. UA:", userAgent)
71 http.Redirect(w, r, url, http.StatusFound)
72 return
73 }
74 */
75
76 if !videoRegex.MatchString(videoId) {
77 logger.Info("Invalid video ID: ", videoId)
78 http.Error(w, "Invalid video ID.", http.StatusBadRequest)
79 return
80 }
81
82 video, err := invidiousClient.GetVideo(videoId, true)
83 if err != nil {
84 logger.Info("Wrong video ID: ", videoId)
85 http.Error(w, "Wrong video ID.", http.StatusNotFound)
86 return
87 }
88
89 if video.Url == "" {
90 logger.Debug("No URL available. Redirecting.")
91 http.Redirect(w, r, url, http.StatusFound)
92 return
93 }
94
95 buf := &bytes.Buffer{}
96 err = videoTemplate.Execute(buf, video)
97 if err != nil {
98 logger.Error("Failed to fill video template.")
99 http.Error(w, err.Error(), http.StatusInternalServerError)
100 return
101 }
102 buf.WriteTo(w)
103}
104
105func watchHandler(invidiousClient *invidious.Client) http.HandlerFunc {
106 return func(w http.ResponseWriter, r *http.Request) {
107 u, err := url.Parse(r.URL.String())
108 if err != nil {
109 logger.Error("Failed to parse URL: ", r.URL.String())
110 http.Error(w, err.Error(), http.StatusInternalServerError)
111 return
112 }
113 q := u.Query()
114 videoId := q.Get("v")
115 videoHandler(videoId, invidiousClient, w, r)
116 }
117}
118
119func shortHandler(invidiousClient *invidious.Client) http.HandlerFunc {
120 return func(w http.ResponseWriter, r *http.Request) {
121 videoId := r.PathValue("videoId")
122 videoHandler(videoId, invidiousClient, w, r)
123 }
124}
125
126func proxyHandler(invidiousClient *invidious.Client) http.HandlerFunc {
127 return func(w http.ResponseWriter, r *http.Request) {
128 videoId := r.PathValue("videoId")
129
130 vb, s := invidiousClient.ProxyVideoId(videoId)
131 if s != http.StatusOK {
132 logger.Error("proxyHandler() failed. Final code: ", s)
133 http.Error(w, http.StatusText(s), s)
134 return
135 }
136 if !vb.ValidateLength() {
137 logger.Error("Buffer length is inconsistent.")
138 status := http.StatusInternalServerError
139 http.Error(w, http.StatusText(status), status)
140 return
141 }
142 h := w.Header()
143 h.Set("Status", "200")
144 h.Set("Content-Type", "video/mp4")
145 h.Set("Content-Length", strconv.FormatInt(vb.Length, 10))
146 io.Copy(w, vb.Buffer)
147 }
148}