all repos — fixyoutube-go @ ff7eee7343ccb174a5e966f88533d101e0a7e5dc

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

add proxy and apiKey check
Marco Andronaco andronacomarco@gmail.com
Tue, 19 Dec 2023 11:43:41 +0100
commit

ff7eee7343ccb174a5e966f88533d101e0a7e5dc

parent

c3cd124eaa5b5d6f42157998309dafff53bab713

3 files changed, 73 insertions(+), 13 deletions(-)

jump to
A .env.example

@@ -0,0 +1,2 @@

+PORT=3000 +API_KEY=changeme
M fixyoutube.gofixyoutube.go

@@ -21,6 +21,7 @@ var indexTemplate = template.Must(template.ParseFiles(templatesDirectory + "index.html"))

var videoTemplate = template.Must(template.ParseFiles(templatesDirectory + "video.html")) var blacklist = []string{"favicon.ico", "robots.txt"} var 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`) +var apiKey string func indexHandler(w http.ResponseWriter, r *http.Request) { buf := &bytes.Buffer{}

@@ -34,9 +35,25 @@ buf.WriteTo(w)

} func clearHandler(w http.ResponseWriter, r *http.Request) { - //TODO: check with some secret API key before clearing cache. + if r.Method != http.MethodPost { + http.Redirect(w, r, "/", http.StatusMovedPermanently) + return + } + + err := r.ParseForm() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + providedKey := r.PostForm.Get("apiKey") + if providedKey != apiKey { + http.Error(w, "Wrong or missing API key.", http.StatusForbidden) + return + } + invidious.ClearDB() - //TODO: return a message + http.Error(w, "Done.", http.StatusOK) } func videoHandler(videoId string, invidiousClient *invidious.Client, w http.ResponseWriter, r *http.Request) {

@@ -89,6 +106,14 @@ videoHandler(videoId, invidiousClient, w, r)

} } +func proxyHandler(invidiousClient *invidious.Client) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + videoId := mux.Vars(r)["videoId"] + + invidiousClient.ProxyVideo(videoId, w) + } +} + func main() { err := godotenv.Load() if err != nil {

@@ -100,6 +125,11 @@ if port == "" {

port = "3000" } + apiKey = os.Getenv("API_KEY") + if apiKey == "" { + apiKey = "itsme" + } + myClient := &http.Client{Timeout: 10 * time.Second} videoapi := invidious.NewClient(myClient)

@@ -108,15 +138,7 @@ r.HandleFunc("/", indexHandler)

r.HandleFunc("/clear", clearHandler) r.HandleFunc("/watch", watchHandler(videoapi)) r.HandleFunc("/{videoId}", shortHandler(videoapi)) - //TODO: r.HandleFunc("/proxy/{videoId}", proxyHandler) - //TODO: check user agent before serving templates. - - /* - 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" - PROXY_HEADERS_REQUEST = { "Range": f"bytes=0-{MAX_SIZE_MB}000000" } - PROXY_HEADERS_RESPONSE = { "Content-Type": "video/mp4" } - */ - + r.HandleFunc("/proxy/{videoId}", proxyHandler(videoapi)) /* // native go implementation (useless until february 2024) r := http.NewServeMux()
M invidious/invidious.goinvidious/invidious.go

@@ -16,6 +16,7 @@

var instancesEndpoint = "https://api.invidious.io/instances.json?sort_by=api,type" var videosEndpoint = "https://%s/api/v1/videos/%s?fields=videoId,title,description,author,lengthSeconds,size,formatStreams" var timeToLive, _ = time.ParseDuration("6h") +var maxSizeMB = 50 type Client struct { http *http.Client

@@ -58,7 +59,7 @@ }

return res } -func (c *Client) FetchVideo(videoId string) (*Video, error) { +func (c *Client) fetchVideo(videoId string) (*Video, error) { if c.Instance == "" { err := c.NewInstance() if err != nil {

@@ -113,7 +114,7 @@ return video, nil

} } - video, err = c.FetchVideo(videoId) + video, err = c.fetchVideo(videoId) if err != nil { log.Println(err) err = c.NewInstance()

@@ -154,6 +155,41 @@

c.Instance = jsonArray[0][0].(string) log.Println("Using new instance:", c.Instance) return nil +} + +func (c *Client) ProxyVideo(videoId string, w http.ResponseWriter) error { + video, err := GetVideoDB(videoId) + if err != nil { + http.Error(w, "Bad Request", http.StatusBadRequest) + return err + } + + req, err := http.NewRequest(http.MethodGet, video.Url, nil) + if err != nil { + log.Fatal(err) + new_video, err := c.fetchVideo(videoId) + if err != nil { + log.Fatal("Url for", videoId, "expired:", err) + return err + } + return c.ProxyVideo(new_video.VideoId, w) + } + + req.Header.Add("Range", fmt.Sprintf("bytes=0-%d000000", maxSizeMB)) + resp, err := c.http.Do(req) + if err != nil { + log.Fatal(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return err + } + defer resp.Body.Close() + + w.Header().Set("content-type", "video/mp4") + w.Header().Set("Status", "206") // Partial Content + + i, err := io.Copy(w, resp.Body) + fmt.Println(i, err) + return err } func NewClient(httpClient *http.Client) *Client {