all repos — fixyoutube-go @ f4aee526df2825f57da964fff12ea902c5979782

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

better logging
Marco Andronaco andronacomarco@gmail.com
Thu, 11 Jan 2024 13:35:05 +0100
commit

f4aee526df2825f57da964fff12ea902c5979782

parent

1ed34f89fd58b854daee23c3e8188e071ff5a852

5 files changed, 63 insertions(+), 45 deletions(-)

jump to
M fixyoutube.gofixyoutube.go

@@ -3,25 +3,24 @@

import ( "bytes" "html/template" - "log" "net/http" "net/url" "os" "regexp" - "slices" "strconv" "time" "github.com/BiRabittoh/fixyoutube-go/invidious" "github.com/gorilla/mux" "github.com/joho/godotenv" + "github.com/sirupsen/logrus" ) const templatesDirectory = "templates/" +var logger = logrus.New() 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", "proxy"} var 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`) var videoRegex = regexp.MustCompile(`^(?i)[a-z0-9_-]{11}$`)

@@ -30,7 +29,7 @@

func parseFormatIndex(formatIndexString string) int { formatIndex, err := strconv.Atoi(formatIndexString) if err != nil || formatIndex < 0 { - log.Println("Error: could not parse formatIndex.") + logger.Debug("Could not parse formatIndex.") return 0 } return formatIndex

@@ -40,6 +39,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {

buf := &bytes.Buffer{} err := indexTemplate.Execute(buf, nil) if err != nil { + logger.Error("Failed to fill index template.") http.Error(w, err.Error(), http.StatusInternalServerError) return }

@@ -55,17 +55,20 @@ }

err := r.ParseForm() if err != nil { + logger.Error("Failed to parse form in /clear.") http.Error(w, err.Error(), http.StatusInternalServerError) return } providedKey := r.PostForm.Get("apiKey") if providedKey != apiKey { + logger.Debug("Wrong API key: ", providedKey) http.Error(w, "Wrong or missing API key.", http.StatusForbidden) return } invidious.ClearDB() + logger.Info("Cache cleared.") http.Error(w, "Done.", http.StatusOK) }

@@ -73,20 +76,22 @@ func videoHandler(videoId string, formatIndex int, invidiousClient *invidious.Client, w http.ResponseWriter, r *http.Request) {

userAgent := r.UserAgent() res := userAgentRegex.MatchString(userAgent) if !res { - log.Println("Regex did not match. Redirecting. UA:", userAgent) + logger.Debug("Regex did not match. Redirecting. UA:", userAgent) url := "https://www.youtube.com/watch?v=" + videoId http.Redirect(w, r, url, http.StatusMovedPermanently) return } if !videoRegex.MatchString(videoId) { - http.Error(w, "Bad Video ID.", http.StatusBadRequest) + logger.Info("Invalid video ID: ", videoId) + http.Error(w, "Invalid video ID.", http.StatusBadRequest) return } video, err := invidiousClient.GetVideo(videoId) if err != nil { - http.Error(w, "Wrong Video ID.", http.StatusNotFound) + logger.Info("Wrong video ID: ", videoId) + http.Error(w, "Wrong video ID.", http.StatusNotFound) return }

@@ -95,6 +100,7 @@

buf := &bytes.Buffer{} err = videoTemplate.Execute(buf, video) if err != nil { + logger.Error("Failed to fill video template.") http.Error(w, err.Error(), http.StatusInternalServerError) return }

@@ -105,6 +111,7 @@ func watchHandler(invidiousClient *invidious.Client) http.HandlerFunc {

return func(w http.ResponseWriter, r *http.Request) { u, err := url.Parse(r.URL.String()) if err != nil { + logger.Error("Failed to parse URL: ", r.URL.String()) http.Error(w, err.Error(), http.StatusInternalServerError) return }

@@ -120,12 +127,6 @@ return func(w http.ResponseWriter, r *http.Request) {

vars := mux.Vars(r) videoId := vars["videoId"] formatIndex := parseFormatIndex(vars["formatIndex"]) - - if slices.Contains(blacklist, videoId) { - http.Error(w, "Not a valid ID.", http.StatusBadRequest) - return - } - videoHandler(videoId, formatIndex, invidiousClient, w, r) } }

@@ -142,7 +143,7 @@

func main() { err := godotenv.Load() if err != nil { - log.Println("No .env file provided.") + logger.Info("No .env file provided.") } port := os.Getenv("PORT")

@@ -173,6 +174,6 @@ r.HandleFunc("/watch", watchHandler(videoapi))

r.HandleFunc("/{videoId}/", shortHandler(videoapi)) r.HandleFunc("/", indexHandler) */ - println("Serving on port", port) + logger.Info("Serving on port ", port) http.ListenAndServe(":"+port, r) }
M go.modgo.mod

@@ -6,4 +6,9 @@ require github.com/joho/godotenv v1.5.1

require github.com/gorilla/mux v1.8.1 -require github.com/mattn/go-sqlite3 v1.14.19 +require github.com/mattn/go-sqlite3 v1.14.19 + +require ( + github.com/sirupsen/logrus v1.9.3 // indirect + golang.org/x/sys v0.16.0 // indirect +)
M go.sumgo.sum

@@ -1,6 +1,18 @@

+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
M invidious/cache.goinvidious/cache.go

@@ -3,7 +3,6 @@

import ( "database/sql" "fmt" - "log" _ "github.com/mattn/go-sqlite3" )

@@ -13,7 +12,7 @@

func getDb(mode string) *sql.DB { db, err := sql.Open("sqlite3", dbConnectionString+mode) if err != nil { - log.Println("Could not open DB:", err) + logger.Error("Could not open DB:", err) return nil } db.SetMaxOpenConns(1)

@@ -26,12 +25,12 @@ defer db.Close()

_, err := db.Exec(createQueryVideos) if err != nil { - log.Printf("%q: %s\n", err, createQueryVideos) + logger.Errorf("%q: %s\n", err, createQueryVideos) return } _, err = db.Exec(createQueryFormats) if err != nil { - log.Printf("%q: %s\n", err, createQueryFormats) + logger.Errorf("%q: %s\n", err, createQueryFormats) return } }

@@ -42,28 +41,28 @@ defer db.Close()

cacheVideo, err := db.Prepare(cacheVideoQuery) if err != nil { - log.Println("Could not cache video:", err) + logger.Error("Could not cache video:", err) return } defer cacheVideo.Close() _, err = cacheVideo.Exec(v.VideoId, v.Title, v.Description, v.Uploader, v.Duration, v.Expire) if err != nil { - log.Println("Could not cache video:", err) + logger.Error("Could not cache video:", err) return } for _, f := range v.Formats { cacheFormat, err := db.Prepare(cacheFormatQuery) if err != nil { - log.Println("Could not cache format:", err) + logger.Error("Could not cache format:", err) return } defer cacheVideo.Close() _, err = cacheFormat.Exec(v.VideoId, f.Name, f.Height, f.Width, f.Url) if err != nil { - log.Println("Could not cache format:", err) + logger.Error("Could not cache format:", err) return } }

@@ -75,7 +74,7 @@ defer db.Close()

getVideo, err := db.Prepare(getVideoQuery) if err != nil { - log.Println("Could not get video:", err) + logger.Error("Could not get video:", err) return nil, err } defer getVideo.Close()

@@ -83,25 +82,25 @@

v := &Video{} err = getVideo.QueryRow(videoId).Scan(&v.VideoId, &v.Title, &v.Description, &v.Uploader, &v.Duration, &v.Timestamp, &v.Expire) if err != nil { - log.Println("Could not get video:", err) + logger.Debug("Could not get video:", err) return nil, err } if v.Timestamp.After(v.Expire) { - log.Println("Video has expired.") + logger.Info("Video has expired.") return nil, fmt.Errorf("expired") } getFormat, err := db.Prepare(getFormatQuery) if err != nil { - log.Println("Could not get video:", err) + logger.Error("Could not get format:", err) return nil, err } defer getFormat.Close() response, err := getFormat.Query(videoId) if err != nil { - log.Println("Could not get formats:", err) + logger.Error("Could not get formats:", err) return nil, err } defer response.Close()

@@ -110,7 +109,7 @@ for response.Next() {

f := Format{} err := response.Scan(&f.VideoId, &f.Name, &f.Height, &f.Width, &f.Url) if err != nil { - log.Println("Could not get formats:", err) + logger.Error("Could not get formats:", err) return nil, err } v.Formats = append(v.Formats, f)

@@ -125,14 +124,14 @@ defer db.Close()

stmt, err := db.Prepare(clearQuery) if err != nil { - log.Println("Could not clear DB:", err) + logger.Error("Could not clear DB:", err) return } defer stmt.Close() _, err = stmt.Exec() if err != nil { - log.Println("Could not clear DB:", err) + logger.Error("Could not clear DB:", err) return } }
M invidious/invidious.goinvidious/invidious.go

@@ -5,13 +5,13 @@ "bytes"

"encoding/json" "fmt" "io" - "log" "net/http" "net/url" - "os" "regexp" "strconv" "time" + + "github.com/sirupsen/logrus" ) const maxSizeMB = 50

@@ -19,6 +19,7 @@ const instancesEndpoint = "https://api.invidious.io/instances.json?sort_by=api,type"

const videosEndpoint = "https://%s/api/v1/videos/%s?fields=videoId,title,description,author,lengthSeconds,size,formatStreams" var expireRegex = regexp.MustCompile(`(?i)expire=(\d+)`) +var logger = logrus.New() type Client struct { http *http.Client

@@ -68,8 +69,7 @@ func (c *Client) fetchVideo(videoId string) (*Video, error) {

if c.Instance == "" { err := c.NewInstance() if err != nil { - log.Fatal(err, "Could not get a new instance.") - os.Exit(1) + logger.Fatal(err, "Could not get a new instance.") } } endpoint := fmt.Sprintf(videosEndpoint, c.Instance, url.QueryEscape(videoId))

@@ -109,11 +109,11 @@ return res, err

} func (c *Client) GetVideo(videoId string) (*Video, error) { - log.Println("Video", videoId, "was requested.") + logger.Info("Video https://youtu.be/", videoId, " was requested.") video, err := GetVideoDB(videoId) if err == nil { - log.Println("Found a valid cache entry.") + logger.Info("Found a valid cache entry.") return video, nil }

@@ -123,15 +123,15 @@ if err != nil {

if err.Error() == "{}" { return nil, err } - log.Println(err) + logger.Error(err) err = c.NewInstance() if err != nil { - log.Fatal("Could not get a new instance: ", err) + logger.Error("Could not get a new instance: ", err) time.Sleep(10 * time.Second) } return c.GetVideo(videoId) } - log.Println("Retrieved by API.") + logger.Info("Retrieved by API.") CacheVideoDB(*video) return video, nil

@@ -160,13 +160,14 @@ return err

} c.Instance = jsonArray[0][0].(string) - log.Println("Using new instance:", c.Instance) + logger.Info("Using new instance:", c.Instance) return nil } func (c *Client) ProxyVideo(w http.ResponseWriter, videoId string, formatIndex int) error { video, err := GetVideoDB(videoId) if err != nil { + logger.Debug("Cannot proxy a video that is not cached: https://youtu.be/", videoId) http.Error(w, "Bad Request", http.StatusBadRequest) return err }

@@ -176,10 +177,10 @@ idx := formatIndex % fmtAmount

url := video.Formats[fmtAmount-1-idx].Url req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { - log.Fatal(err) + logger.Error(err) new_video, err := c.fetchVideo(videoId) if err != nil { - log.Fatal("Url for", videoId, "expired:", err) + logger.Error("Url for", videoId, "expired:", err) return err } return c.ProxyVideo(w, new_video.VideoId, formatIndex)

@@ -188,7 +189,7 @@

req.Header.Add("Range", fmt.Sprintf("bytes=0-%d000000", maxSizeMB)) resp, err := c.http.Do(req) if err != nil { - log.Fatal(err) + logger.Error(err) http.Error(w, err.Error(), http.StatusInternalServerError) return err }