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}