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}