all repos — gopipe @ main

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 redirectHandler(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	http.Redirect(w, r, "/"+videoID, http.StatusFound)
 75}
 76
 77func videoHandler(w http.ResponseWriter, r *http.Request) {
 78	videoID := r.PathValue("videoID")
 79	if videoID == "" {
 80		http.Error(w, "Missing video ID", http.StatusBadRequest)
 81		return
 82	}
 83
 84	if !videoRegex.MatchString(videoID) {
 85		log.Println("Invalid video ID:", videoID)
 86		http.Error(w, err404, http.StatusNotFound)
 87		return
 88	}
 89
 90	formatID := getFormatID(r.PathValue("formatID"))
 91
 92	video, format, err := getVideo(videoID, formatID)
 93	if err != nil {
 94		http.Error(w, err500, http.StatusInternalServerError)
 95		return
 96	}
 97
 98	if video == nil || format == nil {
 99		http.Error(w, err500, http.StatusInternalServerError)
100		return
101	}
102
103	var thumbnail string
104	if len(video.Thumbnails) > 0 {
105		thumbnail = video.Thumbnails[len(video.Thumbnails)-1].URL
106	}
107
108	videoURL := format.URL
109	if g.Proxy {
110		videoURL = fmt.Sprintf("/proxy/%s/%d", videoID, formatID)
111	}
112
113	data := map[string]interface{}{
114		"VideoID":           videoID,
115		"VideoURL":          videoURL,
116		"Author":            video.Author,
117		"Title":             video.Title,
118		"Description":       video.Description,
119		"Thumbnail":         thumbnail,
120		"Duration":          video.Duration,
121		"Captions":          getCaptions(*video),
122		"Heading":           template.HTML(heading),
123		"AudioVideoFormats": video.Formats.Select(formatsSelectFnAudioVideo),
124		"VideoFormats":      video.Formats.Select(formatsSelectFnVideo),
125		"AudioFormats":      video.Formats.Select(formatsSelectFnAudio),
126	}
127
128	err = g.XT.ExecuteTemplate(w, "video.tmpl", data)
129	if err != nil {
130		log.Println("indexHandler ERROR:", err)
131		http.Error(w, err500, http.StatusInternalServerError)
132		return
133	}
134}
135
136func downloadHandler(w http.ResponseWriter, r *http.Request) {
137	videoID := r.FormValue("video")
138	if videoID == "" {
139		http.Error(w, "Missing video ID", http.StatusBadRequest)
140		return
141	}
142
143	if !videoRegex.MatchString(videoID) {
144		log.Println("Invalid video ID:", videoID)
145		http.Error(w, err404, http.StatusNotFound)
146		return
147	}
148
149	itagno := r.FormValue("itagno")
150	if itagno == "" {
151		http.Error(w, "Missing ItagNo", http.StatusBadRequest)
152		return
153	}
154
155	video, err := g.KS.Get(videoID)
156	if err != nil || video == nil {
157		http.Error(w, err404, http.StatusNotFound)
158		return
159	}
160
161	formats := video.Formats.Quality(itagno)
162	if len(formats) == 0 {
163		http.Error(w, err404, http.StatusNotFound)
164		return
165	}
166
167	http.Redirect(w, r, formats[0].URL, http.StatusFound)
168}
169
170func cacheHandler(w http.ResponseWriter, r *http.Request) {
171	username, password, ok := r.BasicAuth()
172	if !ok || username != g.AdminUser || password != g.AdminPass {
173		w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
174		http.Error(w, err401, http.StatusUnauthorized)
175		return
176	}
177
178	var videos []youtube.Video
179	for s := range g.KS.Keys() {
180		video, err := g.KS.Get(s)
181		if err != nil || video == nil {
182			continue
183		}
184		videos = append(videos, *video)
185	}
186
187	data := map[string]interface{}{"Videos": videos}
188	err := g.XT.ExecuteTemplate(w, "cache.tmpl", data)
189	if err != nil {
190		log.Println("cacheHandler ERROR:", err)
191		http.Error(w, err500, http.StatusInternalServerError)
192		return
193	}
194}