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