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 // rabbitpipe.ClearDB()
60 logger.Info("Cache cleared.")
61 http.Error(w, "Done.", http.StatusOK)
62}
63
64func videoHandler(videoID string, w http.ResponseWriter, r *http.Request) {
65 url := "https://www.youtube.com/watch?v=" + videoID
66
67 if !videoRegex.MatchString(videoID) {
68 logger.Info("Invalid video ID: ", videoID)
69 http.Error(w, "Invalid video ID.", http.StatusBadRequest)
70 return
71 }
72
73 video, err := invidious.RP.GetVideo(videoID)
74 if err != nil || video == nil {
75 logger.Info("Wrong video ID: ", videoID)
76 http.Error(w, "Wrong video ID.", http.StatusNotFound)
77 return
78 }
79
80 if invidious.GetVideoURL(*video) == "" {
81 logger.Debug("No URL available. Redirecting.")
82 http.Redirect(w, r, url, http.StatusFound)
83 return
84 }
85
86 buf := &bytes.Buffer{}
87 err = videoTemplate.Execute(buf, video)
88 if err != nil {
89 logger.Error("Failed to fill video template.")
90 http.Error(w, err.Error(), http.StatusInternalServerError)
91 return
92 }
93 buf.WriteTo(w)
94}
95
96func watchHandler(w http.ResponseWriter, r *http.Request) {
97 u, err := url.Parse(r.URL.String())
98 if err != nil {
99 logger.Error("Failed to parse URL: ", r.URL.String())
100 http.Error(w, err.Error(), http.StatusInternalServerError)
101 return
102 }
103
104 q := u.Query()
105 videoId := q.Get("v")
106 videoHandler(videoId, w, r)
107}
108
109func shortHandler(w http.ResponseWriter, r *http.Request) {
110 videoId := r.PathValue("videoId")
111 videoHandler(videoId, w, r)
112 return
113}
114
115func proxyHandler(w http.ResponseWriter, r *http.Request) {
116 videoId := r.PathValue("videoId")
117
118 vb, s := invidious.ProxyVideoId(videoId)
119 if s != http.StatusOK {
120 logger.Error("proxyHandler() failed. Final code: ", s)
121 http.Error(w, http.StatusText(s), s)
122 return
123 }
124 if !vb.ValidateLength() {
125 logger.Error("Buffer length is inconsistent.")
126 status := http.StatusInternalServerError
127 http.Error(w, http.StatusText(status), status)
128 return
129 }
130 h := w.Header()
131 h.Set("Status", "200")
132 h.Set("Content-Type", "video/mp4")
133 h.Set("Content-Length", strconv.FormatInt(vb.Length, 10))
134 io.Copy(w, vb.Buffer)
135}