all repos — gopipe @ 2c659855b1c3bb20d334660e1f0c2aee94652db3

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
 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}