all repos — fixyoutube-go @ main

A better way to embed YouTube videos everywhere (inspired by FixTweet).

handlers.go (view raw)

  1package main
  2
  3import (
  4	"bytes"
  5	"embed"
  6	"io"
  7	"net/http"
  8	"net/url"
  9	"regexp"
 10	"strconv"
 11	"text/template"
 12
 13	"github.com/BiRabittoh/fixyoutube-go/invidious"
 14)
 15
 16const templatesDirectory = "templates/"
 17
 18var (
 19	//go:embed templates/index.html templates/video.html
 20	templates      embed.FS
 21	indexTemplate  = template.Must(template.ParseFS(templates, templatesDirectory+"index.html"))
 22	videoTemplate  = template.Must(template.ParseFS(templates, templatesDirectory+"video.html"))
 23	userAgentRegex = regexp.MustCompile(`(?i)bot|facebook|embed|got|firefox\/92|firefox\/38|curl|wget|go-http|yahoo|generator|whatsapp|preview|link|proxy|vkshare|images|analyzer|index|crawl|spider|python|cfnetwork|node`)
 24	videoRegex     = regexp.MustCompile(`(?i)^[a-z0-9_-]{11}$`)
 25)
 26
 27func indexHandler(w http.ResponseWriter, r *http.Request) {
 28	buf := &bytes.Buffer{}
 29	err := indexTemplate.Execute(buf, nil)
 30	if err != nil {
 31		logger.Error("Failed to fill index template.")
 32		http.Error(w, err.Error(), http.StatusInternalServerError)
 33		return
 34	}
 35
 36	buf.WriteTo(w)
 37}
 38
 39func clearHandler(w http.ResponseWriter, r *http.Request) {
 40	if r.Method != http.MethodPost {
 41		http.Redirect(w, r, "/", http.StatusMovedPermanently)
 42		return
 43	}
 44
 45	err := r.ParseForm()
 46	if err != nil {
 47		logger.Error("Failed to parse form in /clear.")
 48		http.Error(w, err.Error(), http.StatusInternalServerError)
 49		return
 50	}
 51
 52	providedKey := r.PostForm.Get("apiKey")
 53	if providedKey != apiKey {
 54		logger.Debug("Wrong API key: ", providedKey)
 55		http.Error(w, "Wrong or missing API key.", http.StatusForbidden)
 56		return
 57	}
 58
 59	invidious.ClearDB()
 60	logger.Info("Cache cleared.")
 61	http.Error(w, "Done.", http.StatusOK)
 62}
 63
 64func videoHandler(videoId string, invidiousClient *invidious.Client, w http.ResponseWriter, r *http.Request) {
 65	url := "https://www.youtube.com/watch?v=" + videoId
 66	userAgent := r.UserAgent()
 67	res := userAgentRegex.MatchString(userAgent)
 68	if !res {
 69		logger.Debug("Regex did not match. Redirecting. UA:", userAgent)
 70		http.Redirect(w, r, url, http.StatusMovedPermanently)
 71		return
 72	}
 73
 74	if !videoRegex.MatchString(videoId) {
 75		logger.Info("Invalid video ID: ", videoId)
 76		http.Error(w, "Invalid video ID.", http.StatusBadRequest)
 77		return
 78	}
 79
 80	video, err := invidiousClient.GetVideo(videoId, true)
 81	if err != nil {
 82		logger.Info("Wrong video ID: ", videoId)
 83		http.Error(w, "Wrong video ID.", http.StatusNotFound)
 84		return
 85	}
 86
 87	if video.Url == "" {
 88		logger.Debug("No URL available. Redirecting.")
 89		http.Redirect(w, r, url, http.StatusMovedPermanently)
 90		return
 91	}
 92
 93	buf := &bytes.Buffer{}
 94	err = videoTemplate.Execute(buf, video)
 95	if err != nil {
 96		logger.Error("Failed to fill video template.")
 97		http.Error(w, err.Error(), http.StatusInternalServerError)
 98		return
 99	}
100	buf.WriteTo(w)
101}
102
103func watchHandler(invidiousClient *invidious.Client) http.HandlerFunc {
104	return func(w http.ResponseWriter, r *http.Request) {
105		u, err := url.Parse(r.URL.String())
106		if err != nil {
107			logger.Error("Failed to parse URL: ", r.URL.String())
108			http.Error(w, err.Error(), http.StatusInternalServerError)
109			return
110		}
111		q := u.Query()
112		videoId := q.Get("v")
113		videoHandler(videoId, invidiousClient, w, r)
114	}
115}
116
117func shortHandler(invidiousClient *invidious.Client) http.HandlerFunc {
118	return func(w http.ResponseWriter, r *http.Request) {
119		videoId := r.PathValue("videoId")
120		videoHandler(videoId, invidiousClient, w, r)
121	}
122}
123
124func proxyHandler(invidiousClient *invidious.Client) http.HandlerFunc {
125	return func(w http.ResponseWriter, r *http.Request) {
126		videoId := r.PathValue("videoId")
127
128		vb, s := invidiousClient.ProxyVideoId(videoId)
129		if s != http.StatusOK {
130			logger.Error("proxyHandler() failed. Final code: ", s)
131			http.Error(w, http.StatusText(s), s)
132			return
133		}
134		if !vb.ValidateLength() {
135			logger.Error("Buffer length is inconsistent.")
136			status := http.StatusInternalServerError
137			http.Error(w, http.StatusText(status), status)
138			return
139		}
140		h := w.Header()
141		h.Set("Status", "200")
142		h.Set("Content-Type", "video/mp4")
143		h.Set("Content-Length", strconv.FormatInt(vb.Length, 10))
144		io.Copy(w, vb.Buffer)
145	}
146}