all repos — gopipe @ 4dee9cbfacb6ffa25a5915196d7cfeeacbd15999

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