ytdl-web/app/router.go

168 lines
4.4 KiB
Go

package app
import (
"fmt"
"net/http"
"net/url"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/samber/lo"
"github.com/spf13/viper"
"go.fifitido.net/ytdl-web/pkg/httpx"
"go.fifitido.net/ytdl-web/pkg/middlewarex"
"go.fifitido.net/ytdl-web/pkg/view"
"go.fifitido.net/ytdl-web/version"
"go.fifitido.net/ytdl-web/ytdl"
"go.fifitido.net/ytdl-web/ytdl/metadata"
"golang.org/x/exp/slog"
)
func Router(ytdl ytdl.Ytdl) http.Handler {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middlewarex.SlogRequestLogger(slog.Default().With("module", "app")))
r.Use(middleware.Recoverer)
r.Get("/", indexHandler(ytdl))
r.Get("/home", homeIndex)
r.Route("/download", func(r chi.Router) {
r.Get("/", listDownloads(ytdl))
r.Head("/proxy", proxyDownload(ytdl))
r.Get("/proxy", proxyDownload(ytdl))
})
return r
}
func indexHandler(ytdl ytdl.Ytdl) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
views.Render(w, "index", view.Data{
"BasePath": viper.GetString("base_path"),
"Version": version.Version,
"Build": version.Build,
"BinaryVersion": ytdl.Version(),
}, "layouts/main")
}
}
func homeIndex(w http.ResponseWriter, r *http.Request) {
views.Render(w, "index", view.Data{
"BasePath": viper.GetString("base_path"),
})
}
func listDownloads(ytdl ytdl.Ytdl) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
urlBytes, err := url.QueryUnescape(r.URL.Query()["url"][0])
if err != nil {
views.Render(w, "index", view.Data{
"BasePath": viper.GetString("base_path"),
"Error": view.Data{
"Message": "Invalid URL",
},
})
return
}
url := string(urlBytes)
if len(url) < 1 {
views.Render(w, "index", view.Data{
"BasePath": viper.GetString("base_path"),
"Error": view.Data{
"Message": "Invalid URL",
},
})
return
}
meta, err := ytdl.GetMetadata(url)
if err != nil {
views.Render(w, "index", view.Data{
"BasePath": viper.GetString("base_path"),
"Error": view.Data{
"Message": "Could not find a video at that url, maybe try again?",
},
})
return
}
views.Render(w, "download", view.Data{
"BasePath": viper.GetString("base_path"),
"Url": url,
"Meta": meta,
"Videos": GetVideos(meta),
})
}
}
func proxyDownload(ytdl ytdl.Ytdl) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rawUrl, err := httpx.Query(r, "url")
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
urlBytes, err := url.QueryUnescape(rawUrl)
if err != nil {
slog.Error("Failed to decode url param", slog.String("error", err.Error()))
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
url := string(urlBytes)
if len(url) < 1 {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
formatId, err := httpx.Query(r, "format")
if err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
meta, err := ytdl.GetMetadata(url)
if err != nil {
slog.Error("Failed to get metadata", slog.String("error", err.Error()))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
videos := GetVideos(meta)
index, err := httpx.QueryInt(r, "index")
if err != nil || index < 0 || index >= len(videos) {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
video := videos[index]
format, ok := lo.Find(video.Formats, func(format metadata.Format) bool {
return format.FormatID == formatId
})
if !ok {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s-%s.%s\"", meta.ID, format.Resolution, format.Ext))
if format.Filesize != nil {
w.Header().Set("Content-Length", fmt.Sprint(*format.Filesize))
} else if format.FilesizeApprox != nil {
w.Header().Set("Content-Length", fmt.Sprint(*format.FilesizeApprox))
}
if len(videos) == 1 {
index = -1
}
if err := ytdl.Download(w, url, format.FormatID, index); err != nil {
slog.Error("Failed to download", slog.String("error", err.Error()))
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}
}