all repos — gopipe @ e4ce8a08f0ac59bea5fc2fa4f4317272f73bfb43

Embed YouTube videos on Telegram, Discord and more!

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