168 lines
4.4 KiB
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
|
|
}
|
|
}
|
|
}
|