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/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(middleware.Logger) 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 } } }