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