all repos — fixyoutube-go @ afe8a66478bca7add263eeae037e6f830dff0fe6

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(`(?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`)
 24var apiKey string
 25
 26func indexHandler(w http.ResponseWriter, r *http.Request) {
 27	buf := &bytes.Buffer{}
 28	err := indexTemplate.Execute(buf, nil)
 29	if err != nil {
 30		http.Error(w, err.Error(), http.StatusInternalServerError)
 31		return
 32	}
 33
 34	buf.WriteTo(w)
 35}
 36
 37func clearHandler(w http.ResponseWriter, r *http.Request) {
 38	if r.Method != http.MethodPost {
 39		http.Redirect(w, r, "/", http.StatusMovedPermanently)
 40		return
 41	}
 42
 43	err := r.ParseForm()
 44	if err != nil {
 45		http.Error(w, err.Error(), http.StatusInternalServerError)
 46		return
 47	}
 48
 49	providedKey := r.PostForm.Get("apiKey")
 50	if providedKey != apiKey {
 51		http.Error(w, "Wrong or missing API key.", http.StatusForbidden)
 52		return
 53	}
 54
 55	invidious.ClearDB()
 56	http.Error(w, "Done.", http.StatusOK)
 57}
 58
 59func videoHandler(videoId string, invidiousClient *invidious.Client, w http.ResponseWriter, r *http.Request) {
 60	userAgent := r.UserAgent()
 61	res := userAgentRegex.MatchString(userAgent)
 62	if !res {
 63		log.Println("Regex did not match. Redirecting. UA:", userAgent)
 64		url := "https://www.youtube.com/watch?v=" + videoId
 65		http.Redirect(w, r, url, http.StatusMovedPermanently)
 66		return
 67	}
 68
 69	video, err := invidiousClient.GetVideo(videoId)
 70	if err != nil {
 71		http.Error(w, err.Error(), http.StatusInternalServerError)
 72		return
 73	}
 74
 75	buf := &bytes.Buffer{}
 76	err = videoTemplate.Execute(buf, video)
 77	if err != nil {
 78		http.Error(w, err.Error(), http.StatusInternalServerError)
 79		return
 80	}
 81	buf.WriteTo(w)
 82}
 83
 84func watchHandler(invidiousClient *invidious.Client) http.HandlerFunc {
 85	return func(w http.ResponseWriter, r *http.Request) {
 86		u, err := url.Parse(r.URL.String())
 87		if err != nil {
 88			http.Error(w, err.Error(), http.StatusInternalServerError)
 89			return
 90		}
 91
 92		videoId := u.Query().Get("v")
 93		videoHandler(videoId, invidiousClient, w, r)
 94	}
 95}
 96
 97func shortHandler(invidiousClient *invidious.Client) http.HandlerFunc {
 98	return func(w http.ResponseWriter, r *http.Request) {
 99		videoId := mux.Vars(r)["videoId"]
100
101		if slices.Contains(blacklist, videoId) {
102			http.Error(w, "Not a valid ID.", http.StatusBadRequest)
103			return
104		}
105
106		videoHandler(videoId, invidiousClient, w, r)
107	}
108}
109
110func proxyHandler(invidiousClient *invidious.Client) http.HandlerFunc {
111	return func(w http.ResponseWriter, r *http.Request) {
112		videoId := mux.Vars(r)["videoId"]
113
114		invidiousClient.ProxyVideo(videoId, w)
115	}
116}
117
118func main() {
119	err := godotenv.Load()
120	if err != nil {
121		log.Println("No .env file provided.")
122	}
123
124	port := os.Getenv("PORT")
125	if port == "" {
126		port = "3000"
127	}
128
129	apiKey = os.Getenv("API_KEY")
130	if apiKey == "" {
131		apiKey = "itsme"
132	}
133
134	myClient := &http.Client{Timeout: 10 * time.Second}
135	videoapi := invidious.NewClient(myClient)
136
137	r := mux.NewRouter()
138	r.HandleFunc("/", indexHandler)
139	r.HandleFunc("/clear", clearHandler)
140	r.HandleFunc("/watch", watchHandler(videoapi))
141	r.HandleFunc("/{videoId}", shortHandler(videoapi))
142	r.HandleFunc("/proxy/{videoId}", proxyHandler(videoapi))
143	/*
144		// native go implementation (useless until february 2024)
145		r := http.NewServeMux()
146		r.HandleFunc("/watch", watchHandler(videoapi))
147		r.HandleFunc("/{videoId}/", shortHandler(videoapi))
148		r.HandleFunc("/", indexHandler)
149	*/
150	println("Serving on port", port)
151	http.ListenAndServe(":"+port, r)
152}