all repos — fixyoutube-go @ c3cd124eaa5b5d6f42157998309dafff53bab713

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

fixyoutube.go (view raw)

  1package main
  2
  3import (
  4	"bytes"
  5	"html/template"
  6	"log"
  7	"net/http"
  8	"net/url"
  9	"os"
 10	"regexp"
 11	"slices"
 12	"time"
 13
 14	"github.com/BiRabittoh/fixyoutube-go/invidious"
 15	"github.com/gorilla/mux"
 16	"github.com/joho/godotenv"
 17)
 18
 19var templatesDirectory = "templates/"
 20var indexTemplate = template.Must(template.ParseFiles(templatesDirectory + "index.html"))
 21var videoTemplate = template.Must(template.ParseFiles(templatesDirectory + "video.html"))
 22var blacklist = []string{"favicon.ico", "robots.txt"}
 23var userAgentRegex = regexp.MustCompile(`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
 25func indexHandler(w http.ResponseWriter, r *http.Request) {
 26	buf := &bytes.Buffer{}
 27	err := indexTemplate.Execute(buf, nil)
 28	if err != nil {
 29		http.Error(w, err.Error(), http.StatusInternalServerError)
 30		return
 31	}
 32
 33	buf.WriteTo(w)
 34}
 35
 36func clearHandler(w http.ResponseWriter, r *http.Request) {
 37	//TODO: check with some secret API key before clearing cache.
 38	invidious.ClearDB()
 39	//TODO: return a message
 40}
 41
 42func videoHandler(videoId string, invidiousClient *invidious.Client, w http.ResponseWriter, r *http.Request) {
 43
 44	res := userAgentRegex.MatchString(r.UserAgent())
 45	if !res {
 46		url := "https://www.youtube.com/watch?v=" + videoId
 47		http.Redirect(w, r, url, http.StatusMovedPermanently)
 48		return
 49	}
 50
 51	video, err := invidiousClient.GetVideo(videoId)
 52	if err != nil {
 53		http.Error(w, err.Error(), http.StatusInternalServerError)
 54		return
 55	}
 56
 57	buf := &bytes.Buffer{}
 58	err = videoTemplate.Execute(buf, video)
 59	if err != nil {
 60		http.Error(w, err.Error(), http.StatusInternalServerError)
 61		return
 62	}
 63	buf.WriteTo(w)
 64}
 65
 66func watchHandler(invidiousClient *invidious.Client) http.HandlerFunc {
 67	return func(w http.ResponseWriter, r *http.Request) {
 68		u, err := url.Parse(r.URL.String())
 69		if err != nil {
 70			http.Error(w, err.Error(), http.StatusInternalServerError)
 71			return
 72		}
 73
 74		videoId := u.Query().Get("v")
 75		videoHandler(videoId, invidiousClient, w, r)
 76	}
 77}
 78
 79func shortHandler(invidiousClient *invidious.Client) http.HandlerFunc {
 80	return func(w http.ResponseWriter, r *http.Request) {
 81		videoId := mux.Vars(r)["videoId"]
 82
 83		if slices.Contains(blacklist, videoId) {
 84			http.Error(w, "Not a valid ID.", http.StatusBadRequest)
 85			return
 86		}
 87
 88		videoHandler(videoId, invidiousClient, w, r)
 89	}
 90}
 91
 92func main() {
 93	err := godotenv.Load()
 94	if err != nil {
 95		log.Println("Error loading .env file")
 96	}
 97
 98	port := os.Getenv("PORT")
 99	if port == "" {
100		port = "3000"
101	}
102
103	myClient := &http.Client{Timeout: 10 * time.Second}
104	videoapi := invidious.NewClient(myClient)
105
106	r := mux.NewRouter()
107	r.HandleFunc("/", indexHandler)
108	r.HandleFunc("/clear", clearHandler)
109	r.HandleFunc("/watch", watchHandler(videoapi))
110	r.HandleFunc("/{videoId}", shortHandler(videoapi))
111	//TODO: r.HandleFunc("/proxy/{videoId}", proxyHandler)
112	//TODO: check user agent before serving templates.
113
114	/*
115		UA_REGEX = r"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"
116		PROXY_HEADERS_REQUEST = { "Range": f"bytes=0-{MAX_SIZE_MB}000000" }
117		PROXY_HEADERS_RESPONSE = { "Content-Type": "video/mp4" }
118	*/
119
120	/*
121		// native go implementation (useless until february 2024)
122		r := http.NewServeMux()
123		r.HandleFunc("/watch", watchHandler(videoapi))
124		r.HandleFunc("/{videoId}/", shortHandler(videoapi))
125		r.HandleFunc("/", indexHandler)
126	*/
127	println("Serving on port", port)
128	http.ListenAndServe(":"+port, r)
129}