add cache
Marco Andronaco andronacomarco@gmail.com
Tue, 15 Oct 2024 22:50:46 +0200
10 files changed,
132 insertions(+),
41 deletions(-)
M
Dockerfile
→
Dockerfile
@@ -13,7 +13,7 @@ COPY src ./src
COPY *.go ./ # Build -RUN CGO_ENABLED=0 go build -trimpath -o /dist/fixyoutube +RUN CGO_ENABLED=0 go build -trimpath -o /dist/gopipe # Test FROM build-stage AS run-test-stage@@ -27,4 +27,4 @@ COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /dist . COPY templates ./templates -ENTRYPOINT ["./fixyoutube"] +ENTRYPOINT ["./gopipe"]
M
docker-compose.simple.yaml
→
docker-compose.simple.yaml
@@ -2,7 +2,7 @@ services:
app: build: . image: ghcr.io/birabittoh/gopipe:main - container_name: fixyoutube + container_name: gopipe restart: unless-stopped ports: - 3000:3000
M
docker-compose.yaml
→
docker-compose.yaml
@@ -2,7 +2,7 @@ services:
app: build: . image: ghcr.io/birabittoh/gopipe:main - container_name: fixyoutube + container_name: gopipe restart: unless-stopped env_file: - .env@@ -15,7 +15,7 @@ env_file:
- docker/swag.env volumes: #- /etc/config/swag:/config - - ./docker/fixyoutube.subdomain.conf:/config/nginx/proxy-confs/fixyoutube.subdomain.conf:ro + - ./docker/gopipe.subdomain.conf:/config/nginx/proxy-confs/gopipe.subdomain.conf:ro ports: - 443:443 - 80:80
M
src/app/handlers.go
→
src/app/handlers.go
@@ -10,8 +10,11 @@
g "github.com/birabittoh/gopipe/src/globals" ) -const err500 = "Internal Server Error" -const urlDuration = 6 * time.Hour +const ( + fmtYouTubeURL = "https://www.youtube.com/watch?v=%s" + err500 = "Internal Server Error" + urlDuration = 6 * time.Hour +) var ( templates = template.Must(template.ParseGlob("templates/*.html"))@@ -38,13 +41,11 @@ return
} } - url := "https://www.youtube.com/watch?v=" + videoID - if !userAgentRegex.MatchString(r.UserAgent()) { - log.Println("Regex did not match.") + log.Println("Regex did not match. UA: ", r.UserAgent()) if !g.Debug { - log.Println("Redirecting. UA:", r.UserAgent()) - http.Redirect(w, r, url, http.StatusFound) + log.Println("Redirecting.") + http.Redirect(w, r, getURL(videoID), http.StatusFound) return } }@@ -55,26 +56,15 @@ http.Error(w, "Invalid video ID.", http.StatusBadRequest)
return } - video, err := g.YT.GetVideo(url) + video, format, err := getVideo(videoID) if err != nil { - log.Println("videoHandler ERROR: ", err) http.Error(w, err500, http.StatusInternalServerError) return } - formats := video.Formats.WithAudioChannels() - if len(formats) == 0 { - log.Println("videoHandler ERROR: ", err) - http.Error(w, err500, http.StatusInternalServerError) - return - } - - // TODO: check formats[i].ContentLength - // g.KS.Set(videoID, formats[0].URL, urlDuration) - data := map[string]interface{}{ "VideoID": videoID, - "VideoURL": formats[0].URL, + "VideoURL": format.URL, "Uploader": video.Author, "Title": video.Title, "Description": video.Description,
A
src/app/video.go
@@ -0,0 +1,99 @@
+package app + +import ( + "errors" + "fmt" + "log" + "regexp" + "strconv" + "time" + + g "github.com/birabittoh/gopipe/src/globals" + "github.com/kkdai/youtube/v2" +) + +const ( + maxMB = 20 + maxContentLength = maxMB * 1048576 + defaultCacheDuration = 6 * time.Hour +) + +var ( + emptyVideo = youtube.Video{} + expireRegex = regexp.MustCompile(`(?i)expire=(\d+)`) +) + +func parseExpiration(url string) (time.Duration, error) { + expireString := expireRegex.FindStringSubmatch(url) + expireTimestamp, err := strconv.ParseInt(expireString[1], 10, 64) + if err != nil { + log.Println("parseExpiration ERROR: ", err) + return time.Duration(0), err + } + + return time.Until(time.Unix(expireTimestamp, 0)), nil +} + +func formatsSelectFn(f youtube.Format) bool { + return f.AudioChannels > 1 && f.ContentLength < maxContentLength +} + +func getURL(videoID string) string { + return fmt.Sprintf(fmtYouTubeURL, videoID) +} + +func getFromCache(videoID string) (video *youtube.Video, format youtube.Format, err error) { + video, err = g.KS.Get(videoID) + if err != nil { + return + } + + if video == nil { + err = errors.New("video should not be nil") + return + } + + if video.ID == emptyVideo.ID { + err = errors.New("no formats for this video") + return + } + + formats := video.Formats.Select(formatsSelectFn) + if len(formats) == 0 { + err = errors.New("no formats for this video") + return + } + + format = formats[0] + return +} + +func getFromYT(videoID string) (video *youtube.Video, format youtube.Format, err error) { + video, err = g.YT.GetVideo(getURL(videoID)) + if err != nil { + return + } + + formats := video.Formats.Select(formatsSelectFn) + if len(formats) == 0 { + g.KS.Set(videoID, emptyVideo, defaultCacheDuration) + return + } + + format = formats[0] + expiration, err := parseExpiration(format.URL) + if err != nil { + expiration = defaultCacheDuration + } + + g.KS.Set(videoID, *video, expiration) + return +} + +func getVideo(videoID string) (video *youtube.Video, format youtube.Format, err error) { + video, format, err = getFromCache(videoID) + if err != nil { + video, format, err = getFromYT(videoID) + } + return +}
M
src/globals/globals.go
→
src/globals/globals.go
@@ -1,6 +1,8 @@
package globals import ( + "time" + "github.com/birabittoh/myks" "github.com/kkdai/youtube/v2" )@@ -10,5 +12,5 @@ Debug bool
Port string YT = youtube.Client{} - KS = myks.New[string](0) + KS = myks.New[youtube.Video](3 * time.Hour) )
M
swag/fixyoutube.subdomain.conf
→
swag/fixyoutube.subdomain.conf
@@ -1,5 +1,5 @@
-# make sure that your app container is named fixyoutube -# make sure that your dns has a cname set for fixyoutube +# make sure that your app container is named gopipe +# make sure that your dns has a cname set for gopipe server { listen 443 ssl;@@ -15,7 +15,7 @@
location / { include /config/nginx/proxy.conf; include /config/nginx/resolver.conf; - set $upstream_app fixyoutube; + set $upstream_app gopipe; set $upstream_port 3000; set $upstream_proto http; proxy_pass $upstream_proto://$upstream_app:$upstream_port;
M
templates/index.html
→
templates/index.html
@@ -4,10 +4,10 @@
<head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <meta property="og:title" content="FixYouTube" /> - <meta property="og:site_name" content="FixYouTube" /> + <meta property="og:title" content="GoTube" /> + <meta property="og:site_name" content="GoTube" /> <meta property="og:description" content="Embed YouTube videos on Telegram, Discord and more!" /> - <title>FixYouTube</title> + <title>GoTube</title> <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text fill=%22white%22 y=%22.9em%22 font-size=%2290%22>🛠</text></svg>">@@ -17,10 +17,10 @@
<body> <main class="container" style="max-width: 35rem"> <hgroup> - <h1>FixYouTube</h1> + <h1>GoTube</h1> <h2>Embed YouTube videos on Telegram, Discord and more!</h2> </hgroup> - <p>FixYouTube serves fixed YouTube video embeds. Heavily inspired by <a href="https://fxtwitter.com">fxtwitter.com</a> and <a href="https://ddinstagram.com">ddinstagram.com</a>.</p> + <p>GoTube serves fixed YouTube video embeds. Heavily inspired by <a href="https://fxtwitter.com">fxtwitter.com</a> and <a href="https://ddinstagram.com">ddinstagram.com</a>.</p> <section> <header>
M
templates/video.html
→
templates/video.html
@@ -1,10 +1,10 @@
<!DOCTYPE html> <!-- -███████ ██ ██ ██ ██ ██ ███ ██ ██ ██████ ██ ██ ██████ ███████ -██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -█████ ██ ███ ████ ██ ██ ██ ██ ██ ██ ██ ██████ █████ -██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -██ ██ ██ ██ ██ ███ ███ ██ ███ ██████ ███████ +███████ ██ ██ ██ ██ ██ ███ ██ ██ ██████ ██ ██ ██████ ███████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +█████ ██ ███ ████ ██ ██ ██ ██ ██ ██ ██ ██████ █████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██ ███ ███ ██ ███ ██████ ███████ ██ ██ A better way to embed YouTube videos on Telegram (inspired by FixTweet). ██ -->@@ -21,7 +21,7 @@ <meta property="twitter:creator" content="{{ .Uploader }}" />
<meta property="twitter:title" content="{{ .Title }}" /> <meta property="og:title" content="{{ .Title }}" /> <meta property="og:description" content="{{ .Description }}" /> - <meta property="og:site_name" content="FixYouTube ({{ .Uploader }})" /> + <meta property="og:site_name" content="GoTube ({{ .Uploader }})" /> <meta property="twitter:image" content="0" /> <meta property="twitter:player:stream:content_type" content="video/mp4" /> {{ if .VideoURL }}