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 "VideoFormats": video.Formats.Select(formatsSelectFnVideo),
108 "AudioFormats": video.Formats.Select(formatsSelectFnAudio),
109 }
110
111 err = g.XT.ExecuteTemplate(w, "video.tmpl", data)
112 if err != nil {
113 log.Println("indexHandler ERROR:", err)
114 http.Error(w, err500, http.StatusInternalServerError)
115 return
116 }
117}
118
119func downloadHandler(w http.ResponseWriter, r *http.Request) {
120 videoID := r.FormValue("video")
121 if videoID == "" {
122 http.Error(w, "Missing video ID", http.StatusBadRequest)
123 return
124 }
125
126 if !videoRegex.MatchString(videoID) {
127 log.Println("Invalid video ID:", videoID)
128 http.Error(w, err404, http.StatusNotFound)
129 return
130 }
131
132 itagno := r.FormValue("itagno")
133 if itagno == "" {
134 http.Error(w, "Missing ItagNo", http.StatusBadRequest)
135 return
136 }
137
138 video, err := g.KS.Get(videoID)
139 if err != nil || video == nil {
140 http.Error(w, err404, http.StatusNotFound)
141 return
142 }
143
144 formats := video.Formats.Quality(itagno)
145 if len(formats) == 0 {
146 http.Error(w, err404, http.StatusNotFound)
147 return
148 }
149
150 http.Redirect(w, r, formats[0].URL, http.StatusFound)
151}
152
153func cacheHandler(w http.ResponseWriter, r *http.Request) {
154 username, password, ok := r.BasicAuth()
155 if !ok || username != g.AdminUser || password != g.AdminPass {
156 w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
157 http.Error(w, err401, http.StatusUnauthorized)
158 return
159 }
160
161 var videos []youtube.Video
162 for s := range g.KS.Keys() {
163 video, err := g.KS.Get(s)
164 if err != nil || video == nil {
165 continue
166 }
167 videos = append(videos, *video)
168 }
169
170 data := map[string]interface{}{"Videos": videos}
171 err := g.XT.ExecuteTemplate(w, "cache.tmpl", data)
172 if err != nil {
173 log.Println("cacheHandler ERROR:", err)
174 http.Error(w, err500, http.StatusInternalServerError)
175 return
176 }
177}