all repos — gopipe @ 8e4f97e36220a7548c4bc0b8f16b6cd4bdd1ad83

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