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 videoHandler(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 formatID := getFormatID(r.PathValue("formatID"))
75
76 video, format, err := getVideo(videoID, formatID)
77 if err != nil {
78 http.Error(w, err500, http.StatusInternalServerError)
79 return
80 }
81
82 if video == nil || format == nil {
83 http.Error(w, err500, http.StatusInternalServerError)
84 return
85 }
86
87 var thumbnail string
88 if len(video.Thumbnails) > 0 {
89 thumbnail = video.Thumbnails[len(video.Thumbnails)-1].URL
90 }
91
92 videoURL := format.URL
93 if g.Proxy {
94 videoURL = fmt.Sprintf("/proxy/%s/%d", videoID, formatID)
95 }
96
97 data := map[string]interface{}{
98 "VideoID": videoID,
99 "VideoURL": videoURL,
100 "Author": video.Author,
101 "Title": video.Title,
102 "Description": video.Description,
103 "Thumbnail": thumbnail,
104 "Duration": video.Duration,
105 "Captions": getCaptions(*video),
106 "Heading": template.HTML(heading),
107 "AudioVideoFormats": video.Formats.Select(formatsSelectFnAudioVideo),
108 "VideoFormats": video.Formats.Select(formatsSelectFnVideo),
109 "AudioFormats": video.Formats.Select(formatsSelectFnAudio),
110 }
111
112 err = g.XT.ExecuteTemplate(w, "video.tmpl", data)
113 if err != nil {
114 log.Println("indexHandler ERROR:", err)
115 http.Error(w, err500, http.StatusInternalServerError)
116 return
117 }
118}
119
120func downloadHandler(w http.ResponseWriter, r *http.Request) {
121 videoID := r.FormValue("video")
122 if videoID == "" {
123 http.Error(w, "Missing video ID", http.StatusBadRequest)
124 return
125 }
126
127 if !videoRegex.MatchString(videoID) {
128 log.Println("Invalid video ID:", videoID)
129 http.Error(w, err404, http.StatusNotFound)
130 return
131 }
132
133 itagno := r.FormValue("itagno")
134 if itagno == "" {
135 http.Error(w, "Missing ItagNo", http.StatusBadRequest)
136 return
137 }
138
139 video, err := g.KS.Get(videoID)
140 if err != nil || video == nil {
141 http.Error(w, err404, http.StatusNotFound)
142 return
143 }
144
145 formats := video.Formats.Quality(itagno)
146 if len(formats) == 0 {
147 http.Error(w, err404, http.StatusNotFound)
148 return
149 }
150
151 http.Redirect(w, r, formats[0].URL, http.StatusFound)
152}
153
154func cacheHandler(w http.ResponseWriter, r *http.Request) {
155 username, password, ok := r.BasicAuth()
156 if !ok || username != g.AdminUser || password != g.AdminPass {
157 w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
158 http.Error(w, err401, http.StatusUnauthorized)
159 return
160 }
161
162 var videos []youtube.Video
163 for s := range g.KS.Keys() {
164 video, err := g.KS.Get(s)
165 if err != nil || video == nil {
166 continue
167 }
168 videos = append(videos, *video)
169 }
170
171 data := map[string]interface{}{"Videos": videos}
172 err := g.XT.ExecuteTemplate(w, "cache.tmpl", data)
173 if err != nil {
174 log.Println("cacheHandler ERROR:", err)
175 http.Error(w, err500, http.StatusInternalServerError)
176 return
177 }
178}