src/app/handlers.go (view raw)
1package app
2
3import (
4 "fmt"
5 "html/template"
6 "log"
7 "net/http"
8 "regexp"
9 "strings"
10
11 g "github.com/birabittoh/gopipe/src/globals"
12 "github.com/kkdai/youtube/v2"
13 "golang.org/x/time/rate"
14)
15
16const (
17 fmtYouTubeURL = "https://www.youtube.com/watch?v=%s"
18 err400 = "Bad Request"
19 err401 = "Unauthorized"
20 err404 = "Not Found"
21 err500 = "Internal Server Error"
22 heading = `<!--
23 .d8888b. 8888888b. d8b
24d88P Y88b 888 Y88b Y8P
25888 888 888 888
26888 .d88b. 888 d88P 888 88888b. .d88b.
27888 88888 d88""88b 8888888P" 888 888 "88b d8P Y8b
28888 888 888 888 888 888 888 888 88888888
29Y88b d88P Y88..88P 888 888 888 d88P Y8b.
30 "Y8888P88 "Y88P" 888 888 88888P" "Y8888
31 888
32 888
33 888
34
35A better way to embed YouTube videos on Telegram.
36-->`
37)
38
39var (
40 videoRegex = regexp.MustCompile(`(?i)^[a-z0-9_-]{11}$`)
41)
42
43func limit(limiter *rate.Limiter, next http.Handler) http.Handler {
44 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
45 if !limiter.Allow() {
46 status := http.StatusTooManyRequests
47 http.Error(w, http.StatusText(status), status)
48 return
49 }
50 next.ServeHTTP(w, r)
51 })
52}
53
54func indexHandler(w http.ResponseWriter, r *http.Request) {
55 err := g.XT.ExecuteTemplate(w, "index.tmpl", nil)
56 if err != nil {
57 log.Println("indexHandler ERROR:", err)
58 http.Error(w, err500, http.StatusInternalServerError)
59 return
60 }
61}
62
63func redirectHandler(w http.ResponseWriter, r *http.Request) {
64 videoID := r.URL.Query().Get("v")
65 if videoID == "" {
66 videoID = r.PathValue("videoID")
67 if videoID == "" {
68 http.Error(w, "Missing video ID", http.StatusBadRequest)
69 return
70 }
71 }
72
73 if !videoRegex.MatchString(videoID) {
74 log.Println("Invalid video ID:", videoID)
75 http.Error(w, err404, http.StatusNotFound)
76 return
77 }
78
79 http.Redirect(w, r, "/"+videoID, http.StatusFound)
80}
81
82func videoHandler(w http.ResponseWriter, r *http.Request) {
83 videoID := r.PathValue("videoID")
84 if videoID == "" {
85 http.Error(w, "Missing video ID", http.StatusBadRequest)
86 return
87 }
88
89 if !videoRegex.MatchString(videoID) {
90 log.Println("Invalid video ID:", videoID)
91 http.Error(w, err404, http.StatusNotFound)
92 return
93 }
94
95 formatID := getFormatID(r.PathValue("formatID"))
96
97 video, format, err := getVideo(videoID, formatID)
98 if err != nil {
99 http.Error(w, err.Error(), http.StatusInternalServerError)
100 return
101 }
102
103 if video == nil || format == nil {
104 http.Error(w, err500, http.StatusInternalServerError)
105 return
106 }
107
108 var thumbnail string
109 if len(video.Thumbnails) > 0 {
110 thumbnail = video.Thumbnails[len(video.Thumbnails)-1].URL
111 }
112
113 videoURL := format.URL
114 if g.Proxy {
115 videoURL = fmt.Sprintf("/proxy/%s/%d", videoID, formatID)
116 }
117
118 splitResult := strings.Split(format.MimeType, ";")
119 var mimeType string
120 if len(splitResult) != 0 {
121 mimeType = splitResult[0]
122 }
123
124 data := map[string]interface{}{
125 "VideoID": videoID,
126 "VideoURL": videoURL,
127 "Author": video.Author,
128 "Title": video.Title,
129 "Description": video.Description,
130 "MimeType": mimeType,
131 "Thumbnail": thumbnail,
132 "Duration": video.Duration,
133 "Captions": getCaptions(*video),
134 "Heading": template.HTML(heading),
135 "AudioVideoFormats": video.Formats.Select(formatsSelectFnAudioVideo),
136 "VideoFormats": video.Formats.Select(formatsSelectFnVideo),
137 "AudioFormats": video.Formats.Select(formatsSelectFnAudio),
138 }
139
140 err = g.XT.ExecuteTemplate(w, "video.tmpl", data)
141 if err != nil {
142 log.Println("indexHandler ERROR:", err)
143 http.Error(w, err500, http.StatusInternalServerError)
144 return
145 }
146}
147
148func downloadHandler(w http.ResponseWriter, r *http.Request) {
149 videoID := r.FormValue("video")
150 if videoID == "" {
151 http.Error(w, "Missing video ID", http.StatusBadRequest)
152 return
153 }
154
155 if !videoRegex.MatchString(videoID) {
156 log.Println("Invalid video ID:", videoID)
157 http.Error(w, err404, http.StatusNotFound)
158 return
159 }
160
161 itagno := r.FormValue("itagno")
162 if itagno == "" {
163 http.Error(w, "Missing ItagNo", http.StatusBadRequest)
164 return
165 }
166
167 video, err := g.KS.Get(videoID)
168 if err != nil || video == nil {
169 http.Error(w, err404, http.StatusNotFound)
170 return
171 }
172
173 formats := video.Formats.Quality(itagno)
174 if len(formats) == 0 {
175 http.Error(w, err404, http.StatusNotFound)
176 return
177 }
178
179 http.Redirect(w, r, formats[0].URL, http.StatusFound)
180}
181
182func cacheHandler(w http.ResponseWriter, r *http.Request) {
183 username, password, ok := r.BasicAuth()
184 if !ok || username != g.AdminUser || password != g.AdminPass {
185 w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
186 http.Error(w, err401, http.StatusUnauthorized)
187 return
188 }
189
190 var videos []youtube.Video
191 for s := range g.KS.Keys() {
192 video, err := g.KS.Get(s)
193 if err != nil || video == nil {
194 continue
195 }
196 videos = append(videos, *video)
197 }
198
199 data := map[string]interface{}{"Videos": videos}
200 err := g.XT.ExecuteTemplate(w, "cache.tmpl", data)
201 if err != nil {
202 log.Println("cacheHandler ERROR:", err)
203 http.Error(w, err500, http.StatusInternalServerError)
204 return
205 }
206}
207
208func refreshHandler(w http.ResponseWriter, r *http.Request) {
209 videoID := r.PathValue("videoID")
210 if videoID == "" {
211 http.Error(w, err400, http.StatusBadRequest)
212 return
213 }
214
215 if !videoRegex.MatchString(videoID) {
216 http.Error(w, err400, http.StatusBadRequest)
217 return
218 }
219
220 video, err := g.KS.Get(videoID)
221 if err == nil && video != nil {
222 g.KS.Delete(videoID)
223 }
224
225 http.Redirect(w, r, "/"+videoID, http.StatusFound)
226}