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