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.StatusMovedPermanently)
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 userAgent := r.UserAgent()
67 res := userAgentRegex.MatchString(userAgent)
68 if !res {
69 logger.Debug("Regex did not match. Redirecting. UA:", userAgent)
70 http.Redirect(w, r, url, http.StatusMovedPermanently)
71 return
72 }
73
74 if !videoRegex.MatchString(videoId) {
75 logger.Info("Invalid video ID: ", videoId)
76 http.Error(w, "Invalid video ID.", http.StatusBadRequest)
77 return
78 }
79
80 video, err := invidiousClient.GetVideo(videoId, true)
81 if err != nil {
82 logger.Info("Wrong video ID: ", videoId)
83 http.Error(w, "Wrong video ID.", http.StatusNotFound)
84 return
85 }
86
87 if video.Url == "" {
88 logger.Debug("No URL available. Redirecting.")
89 http.Redirect(w, r, url, http.StatusMovedPermanently)
90 return
91 }
92
93 buf := &bytes.Buffer{}
94 err = videoTemplate.Execute(buf, video)
95 if err != nil {
96 logger.Error("Failed to fill video template.")
97 http.Error(w, err.Error(), http.StatusInternalServerError)
98 return
99 }
100 buf.WriteTo(w)
101}
102
103func watchHandler(invidiousClient *invidious.Client) http.HandlerFunc {
104 return func(w http.ResponseWriter, r *http.Request) {
105 u, err := url.Parse(r.URL.String())
106 if err != nil {
107 logger.Error("Failed to parse URL: ", r.URL.String())
108 http.Error(w, err.Error(), http.StatusInternalServerError)
109 return
110 }
111 q := u.Query()
112 videoId := q.Get("v")
113 videoHandler(videoId, invidiousClient, w, r)
114 }
115}
116
117func shortHandler(invidiousClient *invidious.Client) http.HandlerFunc {
118 return func(w http.ResponseWriter, r *http.Request) {
119 videoId := r.PathValue("videoId")
120 videoHandler(videoId, invidiousClient, w, r)
121 }
122}
123
124func proxyHandler(invidiousClient *invidious.Client) http.HandlerFunc {
125 return func(w http.ResponseWriter, r *http.Request) {
126 videoId := r.PathValue("videoId")
127
128 vb, s := invidiousClient.ProxyVideoId(videoId)
129 if s != http.StatusOK {
130 logger.Error("proxyHandler() failed. Final code: ", s)
131 http.Error(w, http.StatusText(s), s)
132 return
133 }
134 if !vb.ValidateLength() {
135 logger.Error("Buffer length is inconsistent.")
136 status := http.StatusInternalServerError
137 http.Error(w, http.StatusText(status), status)
138 return
139 }
140 h := w.Header()
141 h.Set("Status", "200")
142 h.Set("Content-Type", "video/mp4")
143 h.Set("Content-Length", strconv.FormatInt(vb.Length, 10))
144 io.Copy(w, vb.Buffer)
145 }
146}