diff --git a/.envrc b/.envrc
index fdef198..cc5c18b 100644
--- a/.envrc
+++ b/.envrc
@@ -1,11 +1,12 @@
-if ! has nix_direnv_version || ! nix_direnv_version 2.2.1; then
- source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.2.1/direnvrc" "sha256-zelF0vLbEl5uaqrfIzbgNzJWGmLzCmYAkInj/LNxvKs="
-fi
+#!/usr/bin/env bash
-nix_direnv_watch_file nix/devenv.nix
-nix_direnv_watch_file devenv.lock
-nix_direnv_watch_file devenv.yaml
-if ! use flake . --impure
-then
- echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
-fi
\ No newline at end of file
+export DIRENV_WARN_TIMEOUT=20s
+
+eval "$(devenv direnvrc)"
+
+# `use devenv` supports the same options as the `devenv shell` command.
+#
+# To silence all output, use `--quiet`.
+#
+# Example usage: use devenv --quiet --impure --option services.postgres.enable:bool true
+use devenv
diff --git a/.gitignore b/.gitignore
index d36b53e..9c4a351 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,25 @@ devenv.local.nix
.direnv
# pre-commit
-.pre-commit-config.yaml
\ No newline at end of file
+.pre-commit-config.yaml
+# Devenv
+.devenv*
+devenv.local.nix
+devenv.local.yaml
+
+# direnv
+.direnv
+
+# pre-commit
+.pre-commit-config.yaml
+
+# Devenv
+.devenv*
+devenv.local.nix
+devenv.local.yaml
+
+# direnv
+.direnv
+
+# pre-commit
+.pre-commit-config.yaml
diff --git a/app/controllers/download.go b/app/controllers/download.go
deleted file mode 100644
index 56d6a55..0000000
--- a/app/controllers/download.go
+++ /dev/null
@@ -1,180 +0,0 @@
-package controllers
-
-import (
- "fmt"
- "io"
- "net/http"
- "net/url"
-
- "github.com/go-chi/chi/v5"
- "github.com/spf13/viper"
- "go.fifitido.net/ytdl-web/app"
- "go.fifitido.net/ytdl-web/app/models"
- "go.fifitido.net/ytdl-web/pkg/htmx"
- "go.fifitido.net/ytdl-web/pkg/httpx"
- "go.fifitido.net/ytdl-web/pkg/server"
- "go.fifitido.net/ytdl-web/pkg/view"
- "go.fifitido.net/ytdl-web/pkg/ytdl"
- "go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
- "go.fifitido.net/ytdl-web/version"
- "golang.org/x/exp/slog"
-)
-
-type DownloadController struct {
- ytdl ytdl.Ytdl
-}
-
-var _ server.Controller = (*DownloadController)(nil)
-
-func NewDownloadController(ytdl ytdl.Ytdl) *DownloadController {
- return &DownloadController{
- ytdl: ytdl,
- }
-}
-
-func (c *DownloadController) Router(r chi.Router) {
- r.Get("/", c.ListDownloadLinks)
- r.Head("/proxy", c.ProxyDownload)
- r.Get("/proxy", c.ProxyDownload)
-}
-
-func (c *DownloadController) getUrlParam(r *http.Request) (string, bool) {
- urlRaw, err := httpx.Query(r, "url")
- if err != nil {
- return "", false
- }
-
- urlBytes, err := url.QueryUnescape(urlRaw)
- if err != nil || len(urlBytes) < 1 {
- return "", false
- }
-
- return urlBytes, true
-}
-
-func (c *DownloadController) ListDownloadLinks(w http.ResponseWriter, r *http.Request) {
- hx := htmx.New(w, r)
- var layout []string
- if !hx.IsHtmxRequest() {
- layout = append(layout, "layouts/main")
- }
-
- isSecure := r.URL.Scheme == "https" || r.Header.Get("X-Forwarded-Proto") == "https"
-
- videoUrl, ok := c.getUrlParam(r)
- if !ok {
- app.Views.Render(w, "index", view.Data{
- "BasePath": viper.GetString("base_path"),
- "Version": version.Version,
- "Build": version.Build,
- "BinaryVersion": c.ytdl.Version(),
- "IsSecure": isSecure,
- "Error": view.Data{
- "Message": "Invalid URL",
- },
- }, layout...)
- return
- }
-
- meta, err := c.ytdl.GetMetadata(videoUrl)
- if err != nil {
- app.Views.Render(w, "index", view.Data{
- "BasePath": viper.GetString("base_path"),
- "Version": version.Version,
- "Build": version.Build,
- "BinaryVersion": c.ytdl.Version(),
- "IsSecure": isSecure,
- "Error": view.Data{
- "Message": "Could not find a video at that url",
- "RetryUrl": videoUrl,
- },
- }, layout...)
- return
- }
-
- if hx.IsHtmxRequest() {
- hx.PushUrl("/download?url=" + url.QueryEscape(videoUrl))
- }
-
- app.Views.Render(w, "download", view.Data{
- "BasePath": viper.GetString("base_path"),
- "Version": version.Version,
- "Build": version.Build,
- "BinaryVersion": c.ytdl.Version(),
- "IsSecure": isSecure,
- "Url": videoUrl,
- "Meta": meta,
- "Videos": models.GetVideosFromMetadata(meta),
- }, layout...)
-}
-
-var (
- BUF_LEN = 1024
-)
-
-func (c *DownloadController) ProxyDownload(w http.ResponseWriter, r *http.Request) {
- videoUrl, ok := c.getUrlParam(r)
- if !ok {
- 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 := c.ytdl.GetMetadata(videoUrl)
- 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 := models.GetVideosFromMetadata(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]
-
- var format *metadata.Format
- for _, f := range video.Formats {
- if f.FormatID == formatId {
- format = &f
- break
- }
- }
- if format == nil {
- 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))
- }
-
- if len(videos) == 1 {
- index = -1
- }
-
- read, write := io.Pipe()
-
- go func() {
- _, err := io.Copy(w, read)
- if err != nil {
- slog.Error("Failed to copy", slog.String("error", err.Error()))
- }
- }()
-
- if err := c.ytdl.Download(write, videoUrl, format.FormatID, index); err != nil {
- slog.Error("Failed to download", slog.String("error", err.Error()))
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- }
-
- write.Close()
-}
diff --git a/app/controllers/home.go b/app/controllers/home.go
deleted file mode 100644
index 5b7128c..0000000
--- a/app/controllers/home.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package controllers
-
-import (
- "net/http"
-
- "github.com/go-chi/chi/v5"
- "github.com/spf13/viper"
- "go.fifitido.net/ytdl-web/app"
- "go.fifitido.net/ytdl-web/pkg/htmx"
- "go.fifitido.net/ytdl-web/pkg/server"
- "go.fifitido.net/ytdl-web/pkg/view"
- "go.fifitido.net/ytdl-web/pkg/ytdl"
- "go.fifitido.net/ytdl-web/version"
-)
-
-type HomeController struct {
- ytdl ytdl.Ytdl
-}
-
-var _ server.Controller = (*HomeController)(nil)
-
-func NewHomeController(ytdl ytdl.Ytdl) *HomeController {
- return &HomeController{
- ytdl: ytdl,
- }
-}
-
-func (c *HomeController) Router(r chi.Router) {
- r.Get("/", c.Index)
-}
-
-func (c *HomeController) Index(w http.ResponseWriter, r *http.Request) {
- hx := htmx.New(w, r)
- isSecure := r.URL.Scheme == "https" || r.Header.Get("X-Forwarded-Proto") == "https"
-
- if hx.IsHtmxRequest() {
- hx.PushUrl("/")
-
- app.Views.Render(w, "index", view.Data{
- "BasePath": viper.GetString("base_path"),
- "Version": version.Version,
- "Build": version.Build,
- "BinaryVersion": c.ytdl.Version(),
- "IsSecure": isSecure,
- })
- } else {
- app.Views.Render(w, "index", view.Data{
- "BasePath": viper.GetString("base_path"),
- "Version": version.Version,
- "Build": version.Build,
- "BinaryVersion": c.ytdl.Version(),
- "IsSecure": isSecure,
- }, "layouts/main")
- }
-}
diff --git a/app/views.go b/app/views.go
deleted file mode 100644
index d779f67..0000000
--- a/app/views.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package app
-
-import (
- "embed"
- "encoding/json"
- "fmt"
- "html/template"
- "net/url"
-
- "github.com/htfy96/reformism"
- "go.fifitido.net/ytdl-web/pkg/view/html"
- "go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
-)
-
-//go:embed views/*
-var viewsfs embed.FS
-
-var Views = html.New(
- viewsfs,
- html.DefaultOptions().
- WithBaseDir("views").
- WithFunction("unsafe", func(s string) template.HTML {
- return template.HTML(s)
- }).
- WithFunction("queryEscape", func(s string) string {
- return url.QueryEscape(s)
- }).
- WithFunction("jsonMarshal", func(s any) (string, error) {
- j, err := json.MarshalIndent(s, "", " ")
- if err != nil {
- return "", err
- }
- return string(j), nil
- }).
- WithFunction("downloadContext", func(meta metadata.Metadata, url, basePath string, format metadata.Format) map[string]any {
- return map[string]any{
- "Meta": meta,
- "Url": url,
- "BasePath": basePath,
- "Format": format,
- }
- }).
- WithFunction("sprintf", func(format string, args ...any) string {
- return fmt.Sprintf(format, args...)
- }).
- WithFunction("filesize", func(size *int) string {
- if size == nil {
- return "unknown"
- }
- const unit = 1000
- if *size < unit {
- return fmt.Sprintf("%d B", *size)
- }
- div, exp := int64(unit), 0
- for n := *size / unit; n >= unit; n /= unit {
- div *= unit
- exp++
- }
- return fmt.Sprintf("%.1f %cB",
- float64(*size)/float64(div), "kMGTPE"[exp])
- }).
- WithFunctions(reformism.FuncsHTML),
-)
-
-func init() {
- if err := Views.Load(); err != nil {
- panic(err)
- }
-}
diff --git a/app/views/download.html b/app/views/download.html
deleted file mode 100644
index 86ef67c..0000000
--- a/app/views/download.html
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
-{{$root := .}}
-{{range $vidIndex, $video := .Videos}}
-{{if not (eq $vidIndex 0)}}
-
-{{end}}
-
-
-
-
-
- {{range $index, $format := $video.Formats}}
- {{template "format" (map "root" $root "format" $format "label" $format.Format "vidIndex" $vidIndex)}}
- {{end}}
-
-
-{{if gt (len $video.OtherFormats) 0}}
-
-
- See More Formats
-
-
-
-{{end}}
-{{end}}
-
-{{define "format"}}
-{{.label}}
-
-{{end}}
\ No newline at end of file
diff --git a/app/views/index.html b/app/views/index.html
deleted file mode 100644
index 6e0f4fa..0000000
--- a/app/views/index.html
+++ /dev/null
@@ -1,52 +0,0 @@
-YTDL Web
-
- Download videos from over a thousand websites with the help of
- yt-dlp , a fork of youtube-dl
- with more features and fixes.
-
- View a complete list of supported websites
- here .
-
-
-
-
-{{if .Error}}
-
-
{{.Error.Message}}
-
- {{if .Error.RetryUrl}}
-
- Try Again
-
- Loading...
-
-
- {{end}}
-
-{{end}}
\ No newline at end of file
diff --git a/app/views/layouts/main.html b/app/views/layouts/main.html
deleted file mode 100644
index dcdb982..0000000
--- a/app/views/layouts/main.html
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
-
-
-
- YTDL Web
-
-
-
-
-
-
-
-
-
-
-
- {{template "partials/navbar" .}}
- {{yield}}
-
- {{template "partials/footer" .}}
-
-
-
-
\ No newline at end of file
diff --git a/app/views/partials/footer.html b/app/views/partials/footer.html
deleted file mode 100644
index 19aad37..0000000
--- a/app/views/partials/footer.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
- Version:
- {{.Version}}
- (Build: {{.Build}})
-
-
- yt-dlp version: {{.BinaryVersion}}
-
-
-
-
Git Repository
-
© 2024 FiFiTiDo
-
-
-
-
\ No newline at end of file
diff --git a/app/views/partials/navbar.html b/app/views/partials/navbar.html
deleted file mode 100644
index d9cfd5e..0000000
--- a/app/views/partials/navbar.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/cmd/completion.go b/cmd/completion.go
deleted file mode 100644
index b4669b2..0000000
--- a/cmd/completion.go
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
-Copyright © 2024 NAME HERE
-*/
-package cmd
-
-import (
- "fmt"
- "os"
-
- "github.com/spf13/cobra"
-)
-
-// completionCmd represents the completion command
-var completionCmd = &cobra.Command{
- Use: "completion [bash|zsh|fish|powershell]",
- Short: "Generate completion script",
- Long: fmt.Sprintf(`To load completions:
-
-Bash:
-
- $ source <(%[1]s completion bash)
-
- # To load completions for each session, execute once:
- # Linux:
- $ %[1]s completion bash > /etc/bash_completion.d/%[1]s
- # macOS:
- $ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
-
-Zsh:
-
- # If shell completion is not already enabled in your environment,
- # you will need to enable it. You can execute the following once:
-
- $ echo "autoload -U compinit; compinit" >> ~/.zshrc
-
- # To load completions for each session, execute once:
- $ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
-
- # You will need to start a new shell for this setup to take effect.
-
-fish:
-
- $ %[1]s completion fish | source
-
- # To load completions for each session, execute once:
- $ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
-
-PowerShell:
-
- PS> %[1]s completion powershell | Out-String | Invoke-Expression
-
- # To load completions for every new session, run:
- PS> %[1]s completion powershell > %[1]s.ps1
- # and source this file from your PowerShell profile.
-`, rootCmd.Root().Name()),
- DisableFlagsInUseLine: true,
- ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
- Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
- Run: func(cmd *cobra.Command, args []string) {
- switch args[0] {
- case "bash":
- cmd.Root().GenBashCompletion(os.Stdout)
- case "zsh":
- cmd.Root().GenZshCompletion(os.Stdout)
- case "fish":
- cmd.Root().GenFishCompletion(os.Stdout, true)
- case "powershell":
- cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
- }
- },
-}
-
-func init() {
- rootCmd.AddCommand(completionCmd)
-
- // Here you will define your flags and configuration settings.
-
- // Cobra supports Persistent Flags which will work for this command
- // and all subcommands, e.g.:
- // completionCmd.PersistentFlags().String("foo", "", "A help for foo")
-
- // Cobra supports local flags which will only run when this command
- // is called directly, e.g.:
- // completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}
diff --git a/cmd/root.go b/cmd/root.go
index 7ca3bbf..55db51b 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -6,12 +6,17 @@ package cmd
import (
"errors"
"fmt"
+ "log/slog"
"os"
+ "github.com/dgraph-io/badger/v2"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.fifitido.net/ytdl-web/config"
- "golang.org/x/exp/slog"
+ "go.fifitido.net/ytdl-web/pkg/server"
+ "go.fifitido.net/ytdl-web/pkg/utils"
+ "go.fifitido.net/ytdl-web/pkg/ytdl"
+ "go.fifitido.net/ytdl-web/pkg/ytdl/cache"
)
var (
@@ -23,6 +28,30 @@ var (
Short: "A web frontend for yt-dlp",
Long: `YTDL Web is a web application that grabs the links to videos
from over a thousand websites using the yt-dlp project under the hood.`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ initLogging()
+ logger := slog.Default()
+
+ db, err := badger.Open(
+ badger.
+ DefaultOptions(cfg.Cache.DirPath).
+ WithLogger(utils.NewBadgerLogger(logger.With("module", "badger"))),
+ )
+ if err != nil {
+ return err
+ }
+ defer db.Close()
+
+ cache := cache.NewDefaultMetadataCache(db)
+ yt := ytdl.NewYtdl(cfg, slog.Default(), cache)
+ ytdl.SetDefault(yt)
+
+ return server.ListenAndServe(
+ server.WithListenAddr(viper.GetString("http.listen")),
+ server.WithListenPort(viper.GetInt("http.port")),
+ server.WithLogger(logger.With("module", "server")),
+ )
+ },
}
)
@@ -42,14 +71,12 @@ func init() {
rootCmd.PersistentFlags().BoolP("cookies-enabled", "C", false, "whether cookies are enabled")
rootCmd.PersistentFlags().StringP("cookies", "c", "", "the path to the cookies file")
- // trunk-ignore-begin(golangci-lint/errcheck): Ignoring errors
viper.BindPFlag("http.port", rootCmd.PersistentFlags().Lookup("port"))
viper.BindPFlag("http.listen", rootCmd.PersistentFlags().Lookup("listen"))
viper.BindPFlag("http.basePath", rootCmd.PersistentFlags().Lookup("base-path"))
viper.BindPFlag("ytdlp.binaryPath", rootCmd.PersistentFlags().Lookup("ytdlp-path"))
viper.BindPFlag("cookies.enabled", rootCmd.PersistentFlags().Lookup("cookies-enabled"))
viper.BindPFlag("cookies.filePath", rootCmd.PersistentFlags().Lookup("cookies"))
- // trunk-ignore-end(golangci-lint/errcheck)
}
func initConfig() {
@@ -73,19 +100,15 @@ func initConfig() {
}
func initLogging() {
- var handler slog.Handler
-
if cfg.IsProduction() {
- handler = slog.HandlerOptions{
+ slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelInfo,
- }.NewJSONHandler(os.Stdout)
+ })))
} else {
- handler = slog.HandlerOptions{
+ slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: slog.LevelDebug,
- }.NewTextHandler(os.Stdout)
+ })))
}
-
- slog.SetDefault(slog.New(handler))
}
diff --git a/cmd/serve.go b/cmd/serve.go
deleted file mode 100644
index 0b3a634..0000000
--- a/cmd/serve.go
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-Copyright © 2024 Evan Fiordeliso
-*/
-package cmd
-
-import (
- "github.com/dgraph-io/badger/v2"
- "github.com/spf13/cobra"
- "github.com/spf13/viper"
- "go.fifitido.net/ytdl-web/app/controllers"
- "go.fifitido.net/ytdl-web/pkg/server"
- "go.fifitido.net/ytdl-web/pkg/utils"
- "go.fifitido.net/ytdl-web/pkg/ytdl"
- "go.fifitido.net/ytdl-web/pkg/ytdl/cache"
- "golang.org/x/exp/slog"
-)
-
-var (
- serveCmd = &cobra.Command{
- Use: "serve",
- Short: "Serve the ytdl-web application",
- Long: `Serve the ytdl-web application`,
- RunE: func(cmd *cobra.Command, args []string) error {
- initLogging()
- logger := slog.Default()
-
- db, err := badger.Open(
- badger.
- DefaultOptions(cfg.Cache.DirPath).
- WithLogger(utils.NewBadgerLogger(logger.With("module", "badger"))),
- )
- if err != nil {
- return err
- }
- defer db.Close()
-
- cache := cache.NewDefaultMetadataCache(db)
- ytdl := ytdl.NewYtdl(cfg, slog.Default(), cache)
-
- s := server.New(
- server.
- DefaultOptions().
- WithListenAddr(viper.GetString("http.listen")).
- WithListenPort(viper.GetInt("http.port")).
- WithLogger(logger.With("module", "server")),
- )
-
- s.MountController("/", controllers.NewHomeController(ytdl))
- s.MountController("/download", controllers.NewDownloadController(ytdl))
-
- return s.ListenAndServe()
- },
- }
-)
-
-func init() {
- rootCmd.AddCommand(serveCmd)
-
- // Here you will define your flags and configuration settings.
-
- // Cobra supports Persistent Flags which will work for this command
- // and all subcommands, e.g.:
- // completionCmd.PersistentFlags().String("foo", "", "A help for foo")
-
- // Cobra supports local flags which will only run when this command
- // is called directly, e.g.:
- // completionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}
diff --git a/config.example.yaml b/config.example.yaml
index de92c02..fb9ff06 100644
--- a/config.example.yaml
+++ b/config.example.yaml
@@ -4,9 +4,10 @@
# For prod environments use Production
# For staging envronments use Staging
env: Production
-# The path to the yt-dlp binary
-# If it is already in your $PATH just yt-dlp will work.
-binaryPath: yt-dlp
+ytdlp:
+ # The path to the yt-dlp binary
+ # If it is already in your $PATH just yt-dlp will work.
+ binaryPath: yt-dlp
http:
# The port to listen on
port: 8080
diff --git a/config/config.go b/config/config.go
index a225eb4..5c4385a 100644
--- a/config/config.go
+++ b/config/config.go
@@ -1,6 +1,7 @@
package config
import (
+ "fmt"
"os"
"path"
"strings"
@@ -122,5 +123,7 @@ func LoadConfig(paths ...string) (*Config, error) {
return nil, err
}
+ fmt.Printf("%#v\n", config)
+
return config, nil
}
diff --git a/devenv.lock b/devenv.lock
index 97c9f18..a6b5931 100644
--- a/devenv.lock
+++ b/devenv.lock
@@ -3,11 +3,10 @@
"devenv": {
"locked": {
"dir": "src/modules",
- "lastModified": 1708141957,
- "narHash": "sha256-IWkw+jsVpu7HFNPbOTJaQeMYQ5/eh7ZVScPvtlSo8vc=",
+ "lastModified": 1762791812,
"owner": "cachix",
"repo": "devenv",
- "rev": "40b567388381137a3c49acdff5f4b6946d645a5f",
+ "rev": "1faab0d28c573f2a8dba2cf457b9d383adba252a",
"type": "github"
},
"original": {
@@ -20,11 +19,10 @@
"flake-compat": {
"flake": false,
"locked": {
- "lastModified": 1696426674,
- "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "lastModified": 1761588595,
"owner": "edolstra",
"repo": "flake-compat",
- "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
@@ -33,37 +31,39 @@
"type": "github"
}
},
- "flake-utils": {
+ "git-hooks": {
"inputs": {
- "systems": "systems"
+ "flake-compat": "flake-compat",
+ "gitignore": "gitignore",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
},
"locked": {
- "lastModified": 1701680307,
- "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+ "lastModified": 1762441963,
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
+ "rev": "8e7576e79b88c16d7ee3bbd112c8d90070832885",
"type": "github"
},
"original": {
- "owner": "numtide",
- "repo": "flake-utils",
+ "owner": "cachix",
+ "repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
- "pre-commit-hooks",
+ "git-hooks",
"nixpkgs"
]
},
"locked": {
- "lastModified": 1703887061,
- "narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
+ "lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
- "rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
+ "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
@@ -74,80 +74,27 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1708151420,
- "narHash": "sha256-MGT/4aGCWQPQiu6COqJdCj9kSpLPiShgbwpbC38YXC8=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "6e2f00c83911461438301db0dba5281197fe4b3a",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs-stable": {
- "locked": {
- "lastModified": 1704874635,
- "narHash": "sha256-YWuCrtsty5vVZvu+7BchAxmcYzTMfolSPP5io8+WYCg=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "3dc440faeee9e889fe2d1b4d25ad0f430d449356",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixos-23.11",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "pre-commit-hooks": {
- "inputs": {
- "flake-compat": "flake-compat",
- "flake-utils": "flake-utils",
- "gitignore": "gitignore",
- "nixpkgs": [
- "nixpkgs"
- ],
- "nixpkgs-stable": "nixpkgs-stable"
- },
- "locked": {
- "lastModified": 1708018599,
- "narHash": "sha256-M+Ng6+SePmA8g06CmUZWi1AjG2tFBX9WCXElBHEKnyM=",
+ "lastModified": 1761313199,
"owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "rev": "5df5a70ad7575f6601d91f0efec95dd9bc619431",
+ "repo": "devenv-nixpkgs",
+ "rev": "d1c30452ebecfc55185ae6d1c983c09da0c274ff",
"type": "github"
},
"original": {
"owner": "cachix",
- "repo": "pre-commit-hooks.nix",
+ "ref": "rolling",
+ "repo": "devenv-nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
+ "git-hooks": "git-hooks",
"nixpkgs": "nixpkgs",
- "pre-commit-hooks": "pre-commit-hooks"
- }
- },
- "systems": {
- "locked": {
- "lastModified": 1681028828,
- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
- "owner": "nix-systems",
- "repo": "default",
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default",
- "type": "github"
+ "pre-commit-hooks": [
+ "git-hooks"
+ ]
}
}
},
diff --git a/devenv.nix b/devenv.nix
new file mode 100644
index 0000000..c030849
--- /dev/null
+++ b/devenv.nix
@@ -0,0 +1,113 @@
+{ pkgs, lib, config, inputs, ... }:
+
+{
+ # https://devenv.sh/basics/
+ env = {
+ NAME = "ytdl-web";
+ BINARY_OUT = "./out/ytdl-web";
+ VERSION = "v1.2.3";
+ VERSION_PKG = "go.fifitido.net/ytdl-web/version";
+ DOCKER_REGISTRY = "git.fifitido.net";
+ DOCKER_ORG = "apps";
+ DOCKER_PLATFORMS = "linux/amd64,linux/arm64";
+
+ YTDL_ENV = "Development";
+ };
+
+ # https://devenv.sh/packages/
+ packages = with pkgs; [
+ git
+ air
+ goreleaser
+ docker
+ docker-compose
+ buildkit
+ docker-buildx
+ yt-dlp
+ cobra-cli
+ ];
+
+ # https://devenv.sh/scripts/
+ scripts = {
+ deps.exec = "go mod download";
+ tidy.exec = "go mod tidy";
+ check.exec = "goreleaser check";
+
+ build-id.exec = "git rev-parse --short HEAD";
+ build-date.exec = "date -Iseconds";
+
+ build.exec = ''
+ go build -ldflags="
+ -X $VERSION_PKG.Version=$VERSION
+ -X $VERSION_PKG.Build=$(build-id)
+ -X $VERSION_PKG.BuildDate=$(build-date)
+ -X $VERSION_PKG.BuiltBy=manual
+ " -o $BINARY_OUT .
+ '';
+ clean.exec = ''
+ rm -rf ./dist/ ./out/ ./tmp/
+ go clean
+ '';
+ release.exec = ''
+ clean
+ goreleaser release
+ docker-build-release
+ '';
+ lint.exec = "trunk check";
+ fmt.exec = "trunk fmt";
+
+ docker-image.exec = "echo $DOCKER_REGISTRY/$DOCKER_ORG/$NAME";
+
+ docker-init.exec = ''
+ docker buildx create \
+ --name $NAME \
+ --platform $DOCKER_PLATFORMS
+ '';
+
+ docker-login.exec = "docker login $DOCKER_REGISTRY";
+
+ docker-build.exec = ''
+ platform=''${1:-linux}
+ output=''${2:-type=docker}
+
+ docker-init
+ PROGRESS_NO_TRUNC=1 docker buildx build . \
+ --tag $(docker-image):$VERSION \
+ --tag $(docker-image):latest \
+ --platform $platform \
+ --builder $NAME \
+ --build-arg VERSION=$VERSION \
+ --build-arg BUILD=$(build-id) \
+ --build-arg BUILD_DATE=$(build-date) \
+ --build-arg BUILT_BY="Devenv Script" \
+ --progress plain \
+ --output $output
+ '';
+
+ docker-build-release.exec = "docker-build $DOCKER_PLATFORMS type=image,push=true";
+ };
+
+ enterShell = ''
+ echo "Welcome to the $NAME development environment."
+ echo -n "Golang version: $(go version | cut -d ' ' -f 3), "
+ echo -n "Goreleaser version: $(goreleaser --version | tail -n 9 | head -n 1 | cut -c 16-), "
+ echo -n "Docker CLI version: $(docker version -f {{.Client.Version}}), "
+ echo -n "Buildx version: $(docker buildx version | cut -d ' ' -f 2), "
+ echo "yt-dlp version: $(yt-dlp --version)"
+ '';
+
+ # https://devenv.sh/languages/
+ languages.go.enable = true;
+
+ # https://devenv.sh/pre-commit-hooks/
+ git-hooks.hooks = {
+ staticcheck.enable = true;
+ hadolint.enable = true;
+ yamllint.enable = true;
+ };
+
+ # https://devenv.sh/processes/
+ processes.web.exec = "air";
+
+ # See full reference at https://devenv.sh/reference/options/
+}
diff --git a/devenv.yaml b/devenv.yaml
index b8609f7..72ad323 100644
--- a/devenv.yaml
+++ b/devenv.yaml
@@ -1,5 +1,15 @@
+# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
---
-allowUnfree: true
inputs:
nixpkgs:
- url: github:NixOS/nixpkgs/nixpkgs-unstable
+ url: github:cachix/devenv-nixpkgs/rolling
+# If you're using non-OSS software, you can set allowUnfree to true.
+# allowUnfree: true
+
+# If you're willing to use a package that's vulnerable
+# permittedInsecurePackages:
+# - "openssl-1.1.1w"
+
+# If you have more than one devenv you can merge them
+# imports:
+# - ./backend
diff --git a/flake.lock b/flake.lock
deleted file mode 100644
index 07264de..0000000
--- a/flake.lock
+++ /dev/null
@@ -1,757 +0,0 @@
-{
- "nodes": {
- "cachix": {
- "inputs": {
- "devenv": "devenv_2",
- "flake-compat": [
- "devenv",
- "flake-compat"
- ],
- "git-hooks": [
- "devenv",
- "pre-commit-hooks"
- ],
- "nixpkgs": [
- "devenv",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1726520618,
- "narHash": "sha256-jOsaBmJ/EtX5t/vbylCdS7pWYcKGmWOKg4QKUzKr6dA=",
- "owner": "cachix",
- "repo": "cachix",
- "rev": "695525f9086542dfb09fde0871dbf4174abbf634",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "cachix",
- "type": "github"
- }
- },
- "cachix_2": {
- "inputs": {
- "devenv": "devenv_3",
- "flake-compat": [
- "devenv",
- "cachix",
- "devenv",
- "flake-compat"
- ],
- "nixpkgs": [
- "devenv",
- "cachix",
- "devenv",
- "nixpkgs"
- ],
- "pre-commit-hooks": [
- "devenv",
- "cachix",
- "devenv",
- "pre-commit-hooks"
- ]
- },
- "locked": {
- "lastModified": 1712055811,
- "narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
- "owner": "cachix",
- "repo": "cachix",
- "rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "cachix",
- "type": "github"
- }
- },
- "devenv": {
- "inputs": {
- "cachix": "cachix",
- "flake-compat": "flake-compat_2",
- "nix": "nix_3",
- "nixpkgs": "nixpkgs_3",
- "pre-commit-hooks": "pre-commit-hooks_2"
- },
- "locked": {
- "lastModified": 1730890834,
- "narHash": "sha256-ogmpmsPOlX4qeWVW4NZkTd0Lx8V4rvnjwlgOX7WnTZo=",
- "owner": "cachix",
- "repo": "devenv",
- "rev": "c5353d1a0483b8f0dc15933de91c6b1b9a892831",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "devenv",
- "type": "github"
- }
- },
- "devenv_2": {
- "inputs": {
- "cachix": "cachix_2",
- "flake-compat": [
- "devenv",
- "cachix",
- "flake-compat"
- ],
- "nix": "nix_2",
- "nixpkgs": [
- "devenv",
- "cachix",
- "nixpkgs"
- ],
- "pre-commit-hooks": [
- "devenv",
- "cachix",
- "git-hooks"
- ]
- },
- "locked": {
- "lastModified": 1723156315,
- "narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=",
- "owner": "cachix",
- "repo": "devenv",
- "rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "devenv",
- "type": "github"
- }
- },
- "devenv_3": {
- "inputs": {
- "flake-compat": [
- "devenv",
- "cachix",
- "devenv",
- "cachix",
- "flake-compat"
- ],
- "nix": "nix",
- "nixpkgs": "nixpkgs",
- "poetry2nix": "poetry2nix",
- "pre-commit-hooks": [
- "devenv",
- "cachix",
- "devenv",
- "cachix",
- "pre-commit-hooks"
- ]
- },
- "locked": {
- "lastModified": 1708704632,
- "narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
- "owner": "cachix",
- "repo": "devenv",
- "rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "ref": "python-rewrite",
- "repo": "devenv",
- "type": "github"
- }
- },
- "flake-compat": {
- "flake": false,
- "locked": {
- "lastModified": 1673956053,
- "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
- "owner": "edolstra",
- "repo": "flake-compat",
- "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
- "type": "github"
- },
- "original": {
- "owner": "edolstra",
- "repo": "flake-compat",
- "type": "github"
- }
- },
- "flake-compat_2": {
- "flake": false,
- "locked": {
- "lastModified": 1696426674,
- "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
- "owner": "edolstra",
- "repo": "flake-compat",
- "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
- "type": "github"
- },
- "original": {
- "owner": "edolstra",
- "repo": "flake-compat",
- "type": "github"
- }
- },
- "flake-parts": {
- "inputs": {
- "nixpkgs-lib": [
- "devenv",
- "nix",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1712014858,
- "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "type": "github"
- }
- },
- "flake-parts_2": {
- "inputs": {
- "nixpkgs-lib": "nixpkgs-lib"
- },
- "locked": {
- "lastModified": 1730504689,
- "narHash": "sha256-hgmguH29K2fvs9szpq2r3pz2/8cJd2LPS+b4tfNFCwE=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "506278e768c2a08bec68eb62932193e341f55c90",
- "type": "github"
- },
- "original": {
- "id": "flake-parts",
- "type": "indirect"
- }
- },
- "flake-utils": {
- "inputs": {
- "systems": "systems"
- },
- "locked": {
- "lastModified": 1689068808,
- "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
- "flake-utils_2": {
- "locked": {
- "lastModified": 1667395993,
- "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
- "flake-utils_3": {
- "inputs": {
- "systems": "systems_2"
- },
- "locked": {
- "lastModified": 1710146030,
- "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
- "gitignore": {
- "inputs": {
- "nixpkgs": [
- "devenv",
- "pre-commit-hooks",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1709087332,
- "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "type": "github"
- }
- },
- "libgit2": {
- "flake": false,
- "locked": {
- "lastModified": 1697646580,
- "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
- "owner": "libgit2",
- "repo": "libgit2",
- "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
- "type": "github"
- },
- "original": {
- "owner": "libgit2",
- "repo": "libgit2",
- "type": "github"
- }
- },
- "mk-shell-bin": {
- "locked": {
- "lastModified": 1677004959,
- "narHash": "sha256-/uEkr1UkJrh11vD02aqufCxtbF5YnhRTIKlx5kyvf+I=",
- "owner": "rrbutani",
- "repo": "nix-mk-shell-bin",
- "rev": "ff5d8bd4d68a347be5042e2f16caee391cd75887",
- "type": "github"
- },
- "original": {
- "owner": "rrbutani",
- "repo": "nix-mk-shell-bin",
- "type": "github"
- }
- },
- "nix": {
- "inputs": {
- "flake-compat": "flake-compat",
- "nixpkgs": [
- "devenv",
- "cachix",
- "devenv",
- "cachix",
- "devenv",
- "nixpkgs"
- ],
- "nixpkgs-regression": "nixpkgs-regression"
- },
- "locked": {
- "lastModified": 1712911606,
- "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
- "owner": "domenkozar",
- "repo": "nix",
- "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
- "type": "github"
- },
- "original": {
- "owner": "domenkozar",
- "ref": "devenv-2.21",
- "repo": "nix",
- "type": "github"
- }
- },
- "nix-github-actions": {
- "inputs": {
- "nixpkgs": [
- "devenv",
- "cachix",
- "devenv",
- "cachix",
- "devenv",
- "poetry2nix",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1688870561,
- "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
- "owner": "nix-community",
- "repo": "nix-github-actions",
- "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "nix-github-actions",
- "type": "github"
- }
- },
- "nix2container": {
- "inputs": {
- "flake-utils": "flake-utils_3",
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1730479402,
- "narHash": "sha256-79NLeNjpCa4mSasmFsE3QA6obURezF0TUO5Pm+1daog=",
- "owner": "nlewo",
- "repo": "nix2container",
- "rev": "5fb215a1564baa74ce04ad7f903d94ad6617e17a",
- "type": "github"
- },
- "original": {
- "owner": "nlewo",
- "repo": "nix2container",
- "type": "github"
- }
- },
- "nix_2": {
- "inputs": {
- "flake-compat": [
- "devenv",
- "cachix",
- "devenv",
- "flake-compat"
- ],
- "nixpkgs": [
- "devenv",
- "cachix",
- "devenv",
- "nixpkgs"
- ],
- "nixpkgs-regression": "nixpkgs-regression_2"
- },
- "locked": {
- "lastModified": 1712911606,
- "narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
- "owner": "domenkozar",
- "repo": "nix",
- "rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
- "type": "github"
- },
- "original": {
- "owner": "domenkozar",
- "ref": "devenv-2.21",
- "repo": "nix",
- "type": "github"
- }
- },
- "nix_3": {
- "inputs": {
- "flake-compat": [
- "devenv",
- "flake-compat"
- ],
- "flake-parts": "flake-parts",
- "libgit2": "libgit2",
- "nixpkgs": "nixpkgs_2",
- "nixpkgs-23-11": "nixpkgs-23-11",
- "nixpkgs-regression": "nixpkgs-regression_3",
- "pre-commit-hooks": "pre-commit-hooks"
- },
- "locked": {
- "lastModified": 1727438425,
- "narHash": "sha256-X8ES7I1cfNhR9oKp06F6ir4Np70WGZU5sfCOuNBEwMg=",
- "owner": "domenkozar",
- "repo": "nix",
- "rev": "f6c5ae4c1b2e411e6b1e6a8181cc84363d6a7546",
- "type": "github"
- },
- "original": {
- "owner": "domenkozar",
- "ref": "devenv-2.24",
- "repo": "nix",
- "type": "github"
- }
- },
- "nixpkgs": {
- "locked": {
- "lastModified": 1692808169,
- "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs-23-11": {
- "locked": {
- "lastModified": 1717159533,
- "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446",
- "type": "github"
- }
- },
- "nixpkgs-lib": {
- "locked": {
- "lastModified": 1730504152,
- "narHash": "sha256-lXvH/vOfb4aGYyvFmZK/HlsNsr/0CVWlwYvo2rxJk3s=",
- "type": "tarball",
- "url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz"
- },
- "original": {
- "type": "tarball",
- "url": "https://github.com/NixOS/nixpkgs/archive/cc2f28000298e1269cea6612cd06ec9979dd5d7f.tar.gz"
- }
- },
- "nixpkgs-regression": {
- "locked": {
- "lastModified": 1643052045,
- "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
- "type": "github"
- }
- },
- "nixpkgs-regression_2": {
- "locked": {
- "lastModified": 1643052045,
- "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
- "type": "github"
- }
- },
- "nixpkgs-regression_3": {
- "locked": {
- "lastModified": 1643052045,
- "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
- "type": "github"
- }
- },
- "nixpkgs-stable": {
- "locked": {
- "lastModified": 1720386169,
- "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixos-24.05",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs_2": {
- "locked": {
- "lastModified": 1717432640,
- "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "release-24.05",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs_3": {
- "locked": {
- "lastModified": 1716977621,
- "narHash": "sha256-Q1UQzYcMJH4RscmpTkjlgqQDX5yi1tZL0O345Ri6vXQ=",
- "owner": "cachix",
- "repo": "devenv-nixpkgs",
- "rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "ref": "rolling",
- "repo": "devenv-nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs_4": {
- "locked": {
- "lastModified": 1716977621,
- "narHash": "sha256-Q1UQzYcMJH4RscmpTkjlgqQDX5yi1tZL0O345Ri6vXQ=",
- "owner": "cachix",
- "repo": "devenv-nixpkgs",
- "rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "ref": "rolling",
- "repo": "devenv-nixpkgs",
- "type": "github"
- }
- },
- "poetry2nix": {
- "inputs": {
- "flake-utils": "flake-utils",
- "nix-github-actions": "nix-github-actions",
- "nixpkgs": [
- "devenv",
- "cachix",
- "devenv",
- "cachix",
- "devenv",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1692876271,
- "narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
- "owner": "nix-community",
- "repo": "poetry2nix",
- "rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "poetry2nix",
- "type": "github"
- }
- },
- "pre-commit-hooks": {
- "inputs": {
- "flake-compat": [
- "devenv",
- "nix"
- ],
- "flake-utils": "flake-utils_2",
- "gitignore": [
- "devenv",
- "nix"
- ],
- "nixpkgs": [
- "devenv",
- "nix",
- "nixpkgs"
- ],
- "nixpkgs-stable": [
- "devenv",
- "nix",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1712897695,
- "narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=",
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "type": "github"
- }
- },
- "pre-commit-hooks_2": {
- "inputs": {
- "flake-compat": [
- "devenv",
- "flake-compat"
- ],
- "gitignore": "gitignore",
- "nixpkgs": [
- "devenv",
- "nixpkgs"
- ],
- "nixpkgs-stable": "nixpkgs-stable"
- },
- "locked": {
- "lastModified": 1726745158,
- "narHash": "sha256-D5AegvGoEjt4rkKedmxlSEmC+nNLMBPWFxvmYnVLhjk=",
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "rev": "4e743a6920eab45e8ba0fbe49dc459f1423a4b74",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "devenv": "devenv",
- "flake-parts": "flake-parts_2",
- "mk-shell-bin": "mk-shell-bin",
- "nix2container": "nix2container",
- "nixpkgs": "nixpkgs_4"
- }
- },
- "systems": {
- "locked": {
- "lastModified": 1681028828,
- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
- "owner": "nix-systems",
- "repo": "default",
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default",
- "type": "github"
- }
- },
- "systems_2": {
- "locked": {
- "lastModified": 1681028828,
- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
- "owner": "nix-systems",
- "repo": "default",
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default",
- "type": "github"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/flake.nix b/flake.nix
deleted file mode 100644
index 45c7c72..0000000
--- a/flake.nix
+++ /dev/null
@@ -1,56 +0,0 @@
-{
- description = "Description for the project";
-
- inputs = {
- nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling";
- devenv.url = "github:cachix/devenv";
- nix2container.url = "github:nlewo/nix2container";
- nix2container.inputs.nixpkgs.follows = "nixpkgs";
- mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin";
- };
-
- nixConfig = {
- extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=";
- extra-substituters = "https://devenv.cachix.org";
- };
-
- outputs = inputs@{ self, flake-parts, ... }:
- let
- version = "1.2.0";
- rev = if (self ? rev) then self.rev else self.dirtyRev;
- in
- flake-parts.lib.mkFlake { inherit inputs; } {
- imports = [
- inputs.devenv.flakeModule
- ];
- systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
-
- perSystem = { config, self', inputs', pkgs, system, ... }: {
- # Per-system attributes can be defined here. The self' and inputs'
- # module parameters provide easy access to attributes of the same
- # system.
-
- # needed for devenv up
- packages.devenv-up = self'.devShells.default.config.procfileScript;
-
- packages.default = pkgs.callPackage ./nix/package.nix { inherit rev version; };
-
- devenv.shells.default = {
- name = "ytdl-web";
-
- imports = [
- ./nix/devenv.nix
- ];
- };
-
- };
-
- flake = {
- # The usual flake attributes can be defined here, including system-
- # agnostic ones like nixosModule and system-enumerating ones, although
- # those are more easily expressed in perSystem.
-
- nixosModules.default = import ./nix/module.nix;
- };
- };
-}
diff --git a/go.mod b/go.mod
index 8fcf5f0..ac0f230 100644
--- a/go.mod
+++ b/go.mod
@@ -1,44 +1,52 @@
module go.fifitido.net/ytdl-web
-go 1.22
+go 1.25.2
require (
- github.com/adrg/xdg v0.4.0
+ github.com/a-h/templ v0.3.960
+ github.com/adrg/xdg v0.5.3
github.com/dgraph-io/badger/v2 v2.2007.4
- github.com/go-chi/chi/v5 v5.0.10
- github.com/htfy96/reformism v0.0.0-20160819020323-e5bfca398e73
- github.com/spf13/cobra v1.7.0
- github.com/spf13/viper v1.10.0
- golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
+ github.com/go-chi/chi/v5 v5.2.3
+ github.com/samber/slog-chi v1.17.0
+ github.com/spf13/cobra v1.10.1
+ github.com/spf13/viper v1.21.0
)
require (
github.com/cespare/xxhash v1.1.0 // indirect
- github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de // indirect
- github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 // indirect
- github.com/dustin/go-humanize v1.0.0 // indirect
- github.com/fsnotify/fsnotify v1.5.1 // indirect
- github.com/golang/protobuf v1.5.2 // indirect
- github.com/golang/snappy v0.0.3 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/dgraph-io/ristretto v0.2.0 // indirect
+ github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/fsnotify/fsnotify v1.9.0 // indirect
+ github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
+ github.com/golang/protobuf v1.5.4 // indirect
+ github.com/golang/snappy v1.0.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/klauspost/compress v1.16.3 // indirect
+ github.com/klauspost/compress v1.18.1 // indirect
github.com/kr/text v0.2.0 // indirect
- github.com/magiconair/properties v1.8.5 // indirect
- github.com/mitchellh/mapstructure v1.4.3 // indirect
+ github.com/magiconair/properties v1.8.10 // indirect
+ github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
- github.com/pelletier/go-toml v1.9.4 // indirect
- github.com/pkg/errors v0.8.1 // indirect
- github.com/spf13/afero v1.6.0 // indirect
- github.com/spf13/cast v1.4.1 // indirect
+ github.com/pelletier/go-toml v1.9.5 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.4 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/sagikazarmark/locafero v0.12.0 // indirect
+ github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
+ github.com/spf13/afero v1.15.0 // indirect
+ github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
- github.com/spf13/pflag v1.0.5 // indirect
- github.com/subosito/gotenv v1.2.0 // indirect
- golang.org/x/net v0.8.0 // indirect
- golang.org/x/sys v0.7.0 // indirect
- golang.org/x/text v0.8.0 // indirect
- google.golang.org/protobuf v1.27.1 // indirect
+ github.com/spf13/pflag v1.0.10 // indirect
+ github.com/subosito/gotenv v1.6.0 // indirect
+ go.opentelemetry.io/otel v1.38.0 // indirect
+ go.opentelemetry.io/otel/trace v1.38.0 // indirect
+ go.yaml.in/yaml/v3 v3.0.4 // indirect
+ golang.org/x/net v0.46.0 // indirect
+ golang.org/x/sys v0.38.0 // indirect
+ golang.org/x/text v0.30.0 // indirect
+ google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
- gopkg.in/ini.v1 v1.66.2 // indirect
+ gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
diff --git a/go.sum b/go.sum
index d7734ad..0b66dad 100644
--- a/go.sum
+++ b/go.sum
@@ -1,16 +1,23 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/a-h/templ v0.3.960 h1:trshEpGa8clF5cdI39iY4ZrZG8Z/QixyzEyUnA7feTM=
+github.com/a-h/templ v0.3.960/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
+github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
+github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -19,34 +26,49 @@ github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdw
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
+github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
+github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
+github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
-github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
-github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
-github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
+github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
+github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
+github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
+github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
+github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
+github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/htfy96/reformism v0.0.0-20160819020323-e5bfca398e73 h1:Shcv21tstWAyUkKxbn5bTARYej9sgEgFgTRxUPk1J8o=
-github.com/htfy96/reformism v0.0.0-20160819020323-e5bfca398e73/go.mod h1:i2jduFeVras6pm8GnBWdfVKj97mGEXJogjMHzyJhukY=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
+github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -56,85 +78,133 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
+github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
+github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
+github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
+github.com/samber/slog-chi v1.17.0 h1:zP66fV4LGF1y1Dg/+uHNY9Uxkmw1YNWaZGws6M6mPCk=
+github.com/samber/slog-chi v1.17.0/go.mod h1:a1iIuofF2gS1ii8aXIQhC6TEguLOhOvSM958fY5hToU=
+github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
+github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
+github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
+github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
+github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
+github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
+github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.10.0 h1:mXH0UwHS4D2HwWZa75im4xIQynLfblmWV7qcWpfv0yk=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
+github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
+github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
+github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
+go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
+go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
-golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
+golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
+golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
+golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
+golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
+golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
+google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/pkg/components/footer.templ b/pkg/components/footer.templ
new file mode 100644
index 0000000..4c1e8b5
--- /dev/null
+++ b/pkg/components/footer.templ
@@ -0,0 +1,29 @@
+package components
+
+import (
+ "go.fifitido.net/ytdl-web/pkg/ytdl"
+ "go.fifitido.net/ytdl-web/version"
+)
+
+templ Footer() {
+
+
+
+
+
+ Version:
+ { version.Version }
+ (Build: { version.Build })
+
+
+ yt-dlp version: { ytdl.Default().Version() }
+
+
+
+
Git Repository
+
© 2024 FiFiTiDo
+
+
+
+
+}
diff --git a/pkg/components/footer_templ.go b/pkg/components/footer_templ.go
new file mode 100644
index 0000000..0a640d2
--- /dev/null
+++ b/pkg/components/footer_templ.go
@@ -0,0 +1,84 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.960
+package components
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import (
+ "go.fifitido.net/ytdl-web/pkg/ytdl"
+ "go.fifitido.net/ytdl-web/version"
+)
+
+func Footer() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/components/navbar.templ b/pkg/components/navbar.templ
new file mode 100644
index 0000000..46c13e2
--- /dev/null
+++ b/pkg/components/navbar.templ
@@ -0,0 +1,9 @@
+package components
+
+templ Navbar() {
+
+
+
+}
diff --git a/pkg/components/navbar_templ.go b/pkg/components/navbar_templ.go
new file mode 100644
index 0000000..2044adc
--- /dev/null
+++ b/pkg/components/navbar_templ.go
@@ -0,0 +1,40 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.960
+package components
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+func Navbar() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/components/paste.templ b/pkg/components/paste.templ
new file mode 100644
index 0000000..9b03658
--- /dev/null
+++ b/pkg/components/paste.templ
@@ -0,0 +1,36 @@
+package components
+
+import "go.fifitido.net/ytdl-web/pkg/serverctx"
+
+script pasteClipboard() {
+ const pasteButton = content.querySelector("#paste-button");
+ const urlField = content.querySelector("#url");
+
+ try {
+ const text = await navigator.clipboard.readText();
+ urlField.value = text;
+ } catch (error) {
+ toastr.error("Failed to paste url from clipboard.");
+ }
+}
+
+templ PasteButton() {
+ if serverctx.IsHTTPS(ctx) {
+
+
+
+
+
+ }
+}
diff --git a/pkg/components/paste_templ.go b/pkg/components/paste_templ.go
new file mode 100644
index 0000000..a5259b3
--- /dev/null
+++ b/pkg/components/paste_templ.go
@@ -0,0 +1,75 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.960
+package components
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "go.fifitido.net/ytdl-web/pkg/serverctx"
+
+func pasteClipboard() templ.ComponentScript {
+ return templ.ComponentScript{
+ Name: `__templ_pasteClipboard_2c01`,
+ Function: `function __templ_pasteClipboard_2c01(){const pasteButton = content.querySelector("#paste-button");
+ const urlField = content.querySelector("#url");
+
+ try {
+ const text = await navigator.clipboard.readText();
+ urlField.value = text;
+ } catch (error) {
+ toastr.error("Failed to paste url from clipboard.");
+ }
+}`,
+ Call: templ.SafeScript(`__templ_pasteClipboard_2c01`),
+ CallInline: templ.SafeScriptInline(`__templ_pasteClipboard_2c01`),
+ }
+}
+
+func PasteButton() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ if serverctx.IsHTTPS(ctx) {
+ templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, pasteClipboard())
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/htmx/htmx.go b/pkg/htmx/htmx.go
deleted file mode 100644
index b242ab8..0000000
--- a/pkg/htmx/htmx.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package htmx
-
-import "net/http"
-
-type HTMX struct {
- w http.ResponseWriter
- r *http.Request
-}
-
-func New(w http.ResponseWriter, r *http.Request) *HTMX {
- return &HTMX{
- w: w,
- r: r,
- }
-}
-
-//
-// Request header methods
-//
-
-// True when the request is an HTMX request
-func (h *HTMX) IsHtmxRequest() bool {
- return h.r.Header.Get("HX-Request") == "true"
-}
-
-// Indicates that the request is via an element using hx-boost
-func (h *HTMX) IsBoosted() bool {
- return h.r.Header.Get("HX-Boosted") == "true"
-}
-
-// The current URL of the browser
-func (h *HTMX) CurrentUrl() string {
- return h.r.Header.Get("HX-Current-URL")
-}
-
-// If the request is for history restoration after a miss in the local history cache
-func (h *HTMX) IsHistoryRestore() bool {
- return h.r.Header.Get("HX-History-Restore") == "true"
-}
-
-// The id of the element that triggered the request
-func (h *HTMX) TriggerId() string {
- return h.r.Header.Get("HX-Trigger")
-}
-
-// The name of the element that triggered the request
-func (h *HTMX) TriggerName() string {
- return h.r.Header.Get("HX-Trigger-Name")
-}
-
-// The id of the target element
-func (h *HTMX) TargetId() string {
- return h.r.Header.Get("HX-Target")
-}
-
-// The value entered by the user when prompted via hx-prompt
-func (h *HTMX) Prompt() string {
- return h.r.Header.Get("HX-Prompt")
-}
-
-//
-// Response header methods
-//
-
-// Pushe a new url into the history stack
-func (h *HTMX) PushUrl(url string) {
- h.w.Header().Set("HX-Push", url)
-}
-
-// Trigger a client-side redirect to a new location
-func (h *HTMX) RedirectTo(url string) {
- h.w.Header().Set("HX-Redirect", url)
-}
-
-// Triggers a client-side redirect to a new location that acts as a swap
-func (h *HTMX) Location(url string) {
- h.w.Header().Set("Location", url)
-}
-
-// Replace the current URL in the location bar
-func (h *HTMX) ReplaceUrl(url string) {
- h.w.Header().Set("HX-Replace-Url", url)
-}
-
-// Sets refresh to true causing the client side to do a full refresh of the page
-func (h *HTMX) Refresh() {
- h.w.Header().Set("HX-Refresh", "true")
-}
-
-// Trigger client side events
-func (h *HTMX) Trigger(name string) {
- h.w.Header().Set("HX-Trigger", name)
-}
-
-// Trigger client side events after the swap step
-func (h *HTMX) TriggerAfterSwap(name string) {
- h.w.Header().Set("HX-Trigger-After-Swap", name)
-}
-
-// Trigger client side events after the settle step
-func (h *HTMX) TriggerAfterSettle(name string) {
- h.w.Header().Set("HX-Trigger-After-Settle", name)
-}
-
-// Specify how the response will be swapped. See hx-swap for possible values
-func (h *HTMX) Reswap(value string) {
- h.w.Header().Set("HX-Reswap", value)
-}
-
-// Update the target of the content update to a different element on the page
-// Value should be a CSS selector.
-func (h *HTMX) Retarget(value string) {
- h.w.Header().Set("HX-Retarget", value)
-}
-
-// Choose which part of the response is used to be swapped in. Overrides an existing hx-select on the triggering element.
-// Value should be a CSS selector.
-func (h *HTMX) Reselect(value string) {
- h.w.Header().Set("HX-Reselect", value)
-}
diff --git a/pkg/httpx/query.go b/pkg/httpx/query.go
deleted file mode 100644
index 3f0511d..0000000
--- a/pkg/httpx/query.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package httpx
-
-import (
- "errors"
- "net/http"
- "strconv"
-)
-
-var (
- ErrQueryKeyNotFound = errors.New("query key not found")
-)
-
-func Query(r *http.Request, key string) (string, error) {
- values, ok := r.URL.Query()[key]
- if !ok || len(values) == 0 {
- return "", ErrQueryKeyNotFound
- }
-
- return values[0], nil
-}
-
-func QueryInt(r *http.Request, key string) (int, error) {
- value, err := Query(r, key)
- if err != nil {
- return 0, err
- }
-
- return strconv.Atoi(value)
-}
diff --git a/pkg/middlewarex/slog.go b/pkg/middlewarex/slog.go
deleted file mode 100644
index 795c79e..0000000
--- a/pkg/middlewarex/slog.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package middlewarex
-
-import (
- "net/http"
- "time"
-
- "github.com/go-chi/chi/v5/middleware"
- "golang.org/x/exp/slog"
-)
-
-func SlogRequestLogger(logger *slog.Logger) func(next http.Handler) http.Handler {
- if logger == nil {
- return func(next http.Handler) http.Handler { return next }
- }
-
- return func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)
- ti := time.Now()
-
- defer func() {
- reqLogger := logger.With(
- slog.String("proto", r.Proto),
- slog.String("path", r.URL.Path),
- slog.String("reqId", middleware.GetReqID(r.Context())),
- slog.Duration("lat", time.Since(ti)),
- slog.Int("status", ww.Status()),
- slog.Int("size", ww.BytesWritten()),
- )
-
- reqLogger.Info("Served")
- }()
-
- next.ServeHTTP(ww, r)
- })
- }
-}
diff --git a/app/models/video.go b/pkg/models/video.go
similarity index 100%
rename from app/models/video.go
rename to pkg/models/video.go
diff --git a/pkg/server/controller.go b/pkg/server/controller.go
deleted file mode 100644
index d1cb610..0000000
--- a/pkg/server/controller.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package server
-
-import "github.com/go-chi/chi/v5"
-
-type Controller interface {
- Router(r chi.Router)
-}
diff --git a/pkg/server/options.go b/pkg/server/options.go
index 71fa427..5cb1c51 100644
--- a/pkg/server/options.go
+++ b/pkg/server/options.go
@@ -1,6 +1,6 @@
package server
-import "golang.org/x/exp/slog"
+import "log/slog"
type Options struct {
ListenAddr string
@@ -16,17 +16,22 @@ func DefaultOptions() *Options {
}
}
-func (o *Options) WithListenAddr(addr string) *Options {
- o.ListenAddr = addr
- return o
+type Option func(*Options)
+
+func WithListenAddr(addr string) Option {
+ return func(o *Options) {
+ o.ListenAddr = addr
+ }
}
-func (o *Options) WithListenPort(port int) *Options {
- o.ListenPort = port
- return o
+func WithListenPort(port int) Option {
+ return func(o *Options) {
+ o.ListenPort = port
+ }
}
-func (o *Options) WithLogger(logger *slog.Logger) *Options {
- o.Logger = logger
- return o
+func WithLogger(logger *slog.Logger) Option {
+ return func(o *Options) {
+ o.Logger = logger
+ }
}
diff --git a/pkg/server/routes.go b/pkg/server/routes.go
new file mode 100644
index 0000000..5b9318c
--- /dev/null
+++ b/pkg/server/routes.go
@@ -0,0 +1,144 @@
+package server
+
+import (
+ "fmt"
+ "io"
+ "log/slog"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/a-h/templ"
+ "github.com/go-chi/chi/v5"
+ "go.fifitido.net/ytdl-web/pkg/models"
+ "go.fifitido.net/ytdl-web/pkg/views"
+ "go.fifitido.net/ytdl-web/pkg/ytdl"
+ "go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
+)
+
+func Routes(r chi.Router) {
+ r.Get("/", home)
+ r.Route("/download", func(r chi.Router) {
+ r.Get("/", download)
+ r.Head("/proxy", proxyDownload)
+ r.Get("/proxy", proxyDownload)
+ })
+}
+
+func renderPage(w http.ResponseWriter, r *http.Request, component templ.Component) {
+ isHtmx := r.Header.Get("HX-Request") == "true"
+
+ if isHtmx {
+ if err := templ.RenderFragments(r.Context(), w, component, "main-content"); err != nil {
+ slog.ErrorContext(r.Context(), "failed to render page", slog.Any("error", err))
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ }
+ return
+ }
+
+ component.Render(r.Context(), w)
+}
+
+func home(w http.ResponseWriter, r *http.Request) {
+ renderPage(w, r, views.Home(nil))
+}
+
+func getUrlParam(r *http.Request) (string, bool) {
+ urlRaw := r.URL.Query().Get("url")
+ if urlRaw == "" {
+ return "", false
+ }
+
+ urlUnescaped, err := url.QueryUnescape(urlRaw)
+ if err != nil || len(urlUnescaped) < 1 {
+ return "", false
+ }
+
+ return urlUnescaped, true
+}
+
+func download(w http.ResponseWriter, r *http.Request) {
+ ytdl := ytdl.Default()
+
+ videoUrl, ok := getUrlParam(r)
+ if !ok {
+ renderPage(w, r, views.Home(&views.Error{Message: "Invalid URL"}))
+ return
+ }
+
+ meta, err := ytdl.GetMetadata(videoUrl)
+ if err != nil {
+ renderPage(w, r, views.Home(&views.Error{Message: "Could not find a video at that url", RetryUrl: &videoUrl}))
+ return
+ }
+
+ renderPage(w, r, views.Downloads(&views.DownloadsViewModel{Url: videoUrl, Meta: meta}))
+}
+
+func proxyDownload(w http.ResponseWriter, r *http.Request) {
+ ytdl := ytdl.Default()
+ videoUrl, ok := getUrlParam(r)
+ if !ok {
+ http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+ return
+ }
+
+ formatId := r.URL.Query().Get("format")
+ if formatId == "" {
+ http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+ return
+ }
+
+ meta, err := ytdl.GetMetadata(videoUrl)
+ 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 := models.GetVideosFromMetadata(meta)
+
+ index, err := strconv.Atoi(r.URL.Query().Get("index"))
+ if err != nil || index < 0 || index >= len(videos) {
+ http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+ return
+ }
+
+ video := videos[index]
+
+ var format *metadata.Format
+ for _, f := range video.Formats {
+ if f.FormatID == formatId {
+ format = &f
+ break
+ }
+ }
+ if format == nil {
+ http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
+ return
+ }
+
+ w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.%s\"", meta.ID, format.Ext))
+ if format.Filesize != nil {
+ w.Header().Set("Content-Length", fmt.Sprint(*format.Filesize))
+ }
+
+ if len(videos) == 1 {
+ index = -1
+ }
+
+ read, write := io.Pipe()
+
+ go func() {
+ _, err := io.Copy(w, read)
+ if err != nil {
+ slog.Error("Failed to copy", slog.String("error", err.Error()))
+ }
+ }()
+
+ if err := ytdl.Download(write, videoUrl, format.FormatID, index); err != nil {
+ slog.Error("Failed to download", slog.String("error", err.Error()))
+ http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
+ }
+
+ write.Close()
+}
diff --git a/pkg/server/server.go b/pkg/server/server.go
index 857fa7a..f7bb343 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -2,53 +2,34 @@ package server
import (
"fmt"
+ "log/slog"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
- "go.fifitido.net/ytdl-web/pkg/middlewarex"
- "golang.org/x/exp/slog"
+ slogchi "github.com/samber/slog-chi"
+ "go.fifitido.net/ytdl-web/pkg/serverctx"
)
-type Server interface {
- MountController(path string, controller Controller)
- ListenAndServe() error
-}
-
-type DefaultServer struct {
- r chi.Router
- opts *Options
-}
-
-var _ Server = (*DefaultServer)(nil)
-
-func New(options ...*Options) *DefaultServer {
- var opts *Options
- if len(options) > 0 {
- opts = options[0]
- } else {
- opts = DefaultOptions()
+func ListenAndServe(options ...Option) error {
+ opts := DefaultOptions()
+ for _, opt := range options {
+ opt(opts)
}
r := chi.NewRouter()
- r.Use(middleware.RequestID)
- r.Use(middleware.RealIP)
- r.Use(middlewarex.SlogRequestLogger(opts.Logger))
- r.Use(middleware.Recoverer)
+ r.Use(
+ serverctx.Middleware,
+ middleware.RequestID,
+ middleware.RealIP,
+ slogchi.New(opts.Logger),
+ middleware.Recoverer,
+ )
- return &DefaultServer{
- r: r,
- opts: opts,
- }
-}
+ r.Group(Routes)
-func (s *DefaultServer) MountController(path string, controller Controller) {
- s.r.Route(path, controller.Router)
-}
-
-func (s *DefaultServer) ListenAndServe() error {
- listenAddr := fmt.Sprintf("%s:%d", s.opts.ListenAddr, s.opts.ListenPort)
- s.opts.Logger.Info("Starting HTTP server", slog.String("addr", s.opts.ListenAddr), slog.Int("port", s.opts.ListenPort))
- return http.ListenAndServe(listenAddr, s.r)
+ listenAddr := fmt.Sprintf("%s:%d", opts.ListenAddr, opts.ListenPort)
+ opts.Logger.Info("Starting HTTP server", slog.String("addr", opts.ListenAddr), slog.Int("port", opts.ListenPort))
+ return http.ListenAndServe(listenAddr, r)
}
diff --git a/pkg/serverctx/context.go b/pkg/serverctx/context.go
new file mode 100644
index 0000000..902c743
--- /dev/null
+++ b/pkg/serverctx/context.go
@@ -0,0 +1,25 @@
+package serverctx
+
+import (
+ "context"
+ "net/http"
+)
+
+type contextKey string
+
+const (
+ isHTTPS contextKey = "isHTTPS"
+)
+
+func Middleware(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ isHttps := r.URL.Scheme == "https" || r.Header.Get("X-Forwarded-Proto") == "https"
+ ctx := context.WithValue(r.Context(), isHTTPS, isHttps)
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+}
+
+func IsHTTPS(ctx context.Context) bool {
+ isHttps, ok := ctx.Value(isHTTPS).(bool)
+ return ok && isHttps
+}
diff --git a/pkg/utils/badgerlogger.go b/pkg/utils/badgerlogger.go
index 9a5a9bb..ea3493e 100644
--- a/pkg/utils/badgerlogger.go
+++ b/pkg/utils/badgerlogger.go
@@ -3,8 +3,9 @@ package utils
import (
"fmt"
+ "log/slog"
+
"github.com/dgraph-io/badger/v2"
- "golang.org/x/exp/slog"
)
type SlogLogger struct {
diff --git a/pkg/utils/logwriter.go b/pkg/utils/logwriter.go
index 0b98df8..14138e4 100644
--- a/pkg/utils/logwriter.go
+++ b/pkg/utils/logwriter.go
@@ -3,8 +3,7 @@ package utils
import (
"context"
"io"
-
- "golang.org/x/exp/slog"
+ "log/slog"
)
type loggerWriter struct {
diff --git a/pkg/view/engine.go b/pkg/view/engine.go
deleted file mode 100644
index 3730b8e..0000000
--- a/pkg/view/engine.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package view
-
-import "net/http"
-
-type Data map[string]interface{}
-
-type Engine interface {
- Load() error
- Render(w http.ResponseWriter, view string, data Data, layout ...string)
-}
diff --git a/pkg/view/html/engine.go b/pkg/view/html/engine.go
deleted file mode 100644
index 9a754ad..0000000
--- a/pkg/view/html/engine.go
+++ /dev/null
@@ -1,125 +0,0 @@
-package html
-
-import (
- "embed"
- "html/template"
- "io/fs"
- "net/http"
- "path/filepath"
- "strings"
- "sync"
-
- "go.fifitido.net/ytdl-web/pkg/view"
-)
-
-type Engine struct {
- fs embed.FS
- tpl *template.Template
- opts *Options
-
- loadOnce sync.Once
- mu sync.Mutex
-}
-
-var _ view.Engine = (*Engine)(nil)
-
-func New(fs embed.FS, options ...*Options) *Engine {
- var opts *Options
- if len(options) > 0 && options[0] != nil {
- opts = options[0]
- } else {
- opts = DefaultOptions()
- }
-
- tpl := template.New("/")
- tpl.Delims(opts.LeftDelim, opts.RightDelim)
- tpl.Funcs(opts.FuncMap)
-
- return &Engine{
- fs: fs,
- tpl: tpl,
- opts: opts,
- }
-}
-
-func (e *Engine) Load() error {
- var err error
- e.loadOnce.Do(func() {
- err = e.doLoad()
- })
- return err
-}
-
-func (e *Engine) doLoad() error {
- e.mu.Lock()
- defer e.mu.Unlock()
-
- return fs.WalkDir(e.fs, ".", func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
-
- if d == nil || d.IsDir() {
- return nil
- }
-
- if len(path) < len(e.opts.Extension) || path[len(path)-len(e.opts.Extension):] != e.opts.Extension {
- return nil
- }
-
- rel, err := filepath.Rel(e.opts.BaseDir, path)
- if err != nil {
- return err
- }
-
- name := filepath.ToSlash(rel)
- name = strings.TrimSuffix(name, e.opts.Extension)
-
- buf, err := e.fs.ReadFile(path)
- if err != nil {
- return err
- }
-
- _, err = e.tpl.New(name).Parse(string(buf))
- if err != nil {
- return err
- }
-
- return nil
- })
-}
-
-func (e *Engine) Render(w http.ResponseWriter, view string, data view.Data, layout ...string) {
- tmpl := e.tpl.Lookup(view)
- if tmpl == nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
- return
- }
-
- if len(layout) > 0 && layout[0] != "" {
- lay := e.tpl.Lookup(layout[0])
- if lay == nil {
- http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
- return
- }
-
- e.mu.Lock()
- defer e.mu.Unlock()
-
- lay.Funcs(map[string]interface{}{
- "yield": func() error {
- return tmpl.Execute(w, data)
- },
- })
-
- if err := lay.Execute(w, data); err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- }
- return
- }
-
- if err := tmpl.Execute(w, data); err != nil {
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
-}
diff --git a/pkg/view/html/options.go b/pkg/view/html/options.go
deleted file mode 100644
index 99a035d..0000000
--- a/pkg/view/html/options.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package html
-
-import (
- "fmt"
- "html/template"
-)
-
-type Options struct {
- LeftDelim string
- RightDelim string
- Extension string
- BaseDir string
- FuncMap template.FuncMap
-}
-
-func DefaultOptions() *Options {
- return &Options{
- LeftDelim: "{{",
- RightDelim: "}}",
- Extension: ".html",
- BaseDir: "/",
- FuncMap: template.FuncMap{
- "yield": func() error { return fmt.Errorf("yield called outside of a layout") },
- },
- }
-}
-
-func (o *Options) WithDelimiters(left, right string) *Options {
- o.LeftDelim = left
- o.RightDelim = right
- return o
-}
-
-func (o *Options) WithExtension(ext string) *Options {
- o.Extension = ext
- return o
-}
-
-func (o *Options) WithBaseDir(dir string) *Options {
- o.BaseDir = dir
- return o
-}
-
-func (o *Options) WithFunction(name string, fn interface{}) *Options {
- o.FuncMap[name] = fn
- return o
-}
-
-func (o *Options) WithFunctions(funcs template.FuncMap) *Options {
- for k, v := range funcs {
- o.FuncMap[k] = v
- }
- return o
-}
diff --git a/pkg/views/downloads.templ b/pkg/views/downloads.templ
new file mode 100644
index 0000000..2c5297e
--- /dev/null
+++ b/pkg/views/downloads.templ
@@ -0,0 +1,122 @@
+package views
+
+import (
+ "fmt"
+ "go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
+)
+
+type DownloadsViewModel struct {
+ Url string
+ Meta *metadata.Metadata
+}
+
+templ videoFormatView(vm *DownloadsViewModel, vidIndex int, format metadata.Format, label string) {
+ {{ filename := fmt.Sprintf("%s-%s.%s", vm.Meta.ID, format.Resolution, format.Ext) }}
+ { label }
+
+}
+
+templ videoView(vm *DownloadsViewModel, index int, video *VideoViewModel) {
+ if index != 0 {
+
+ }
+
+ {{
+ thumbnail := video.Meta.Thumbnail
+ thumbnailAlt := video.Meta.ID
+ if video.Meta.Title != nil {
+ thumbnailAlt = *video.Meta.Title
+ }
+ }}
+ if thumbnail != nil {
+
+
+
+ }
+
+ for _, format := range video.Formats {
+ @videoFormatView(vm, index, format, format.Format)
+ }
+
+
+ if len(video.OtherFormats) > 0 {
+ {{ collapseId := fmt.Sprintf("collapse-%s-%d", vm.Meta.ID, index) }}
+
+
+ See More Formats
+
+
+
+
+ for _, format := range video.OtherFormats {
+ {{ label := fmt.Sprintf("ext: %s, resolution: %s, filesize: %d, note: %s", format.Ext, format.Resolution, format.Filesize, format.FormatNote) }}
+ @videoFormatView(vm, index, format, label)
+ }
+
+
+ }
+}
+
+css downloads() {
+ display: grid;
+ grid-template-columns: minmax(auto, max-content) auto;
+ gap: 1.5rem;
+ align-items: center;
+}
+
+css fullWidth() {
+ grid-column: span 2;
+}
+
+templ Downloads(vm *DownloadsViewModel) {
+ {{ videos := GetVideosFromMetadata(vm.Meta) }}
+ @Layout() {
+
+ for index, video := range videos {
+ @videoView(vm, index, video)
+ }
+ }
+}
diff --git a/pkg/views/downloads_templ.go b/pkg/views/downloads_templ.go
new file mode 100644
index 0000000..001f06a
--- /dev/null
+++ b/pkg/views/downloads_templ.go
@@ -0,0 +1,463 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.960
+package views
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import (
+ "fmt"
+ "go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
+)
+
+type DownloadsViewModel struct {
+ Url string
+ Meta *metadata.Metadata
+}
+
+func videoFormatView(vm *DownloadsViewModel, vidIndex int, format metadata.Format, label string) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ filename := fmt.Sprintf("%s-%s.%s", vm.Meta.ID, format.Resolution, format.Ext)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var2 string
+ templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(label)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 15, Col: 40}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func videoView(vm *DownloadsViewModel, index int, video *VideoViewModel) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var8 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var8 == nil {
+ templ_7745c5c3_Var8 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ if index != 0 {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ thumbnail := video.Meta.Thumbnail
+ thumbnailAlt := video.Meta.ID
+ if video.Meta.Title != nil {
+ thumbnailAlt = *video.Meta.Title
+ }
+ if thumbnail != nil {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ var templ_7745c5c3_Var11 = []any{downloads(), "flex-lg-grow-1"}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, format := range video.Formats {
+ templ_7745c5c3_Err = videoFormatView(vm, index, format, format.Format).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if len(video.OtherFormats) > 0 {
+ collapseId := fmt.Sprintf("collapse-%s-%d", vm.Meta.ID, index)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "See More Formats
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var15 = []any{fullWidth(), "collapse"}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var18 = []any{downloads(), "d-flex d-md-grid flex-column"}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var18...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, format := range video.OtherFormats {
+ label := fmt.Sprintf("ext: %s, resolution: %s, filesize: %d, note: %s", format.Ext, format.Resolution, format.Filesize, format.FormatNote)
+ templ_7745c5c3_Err = videoFormatView(vm, index, format, label).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ return nil
+ })
+}
+
+func downloads() templ.CSSClass {
+ templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
+ templ_7745c5c3_CSSBuilder.WriteString(`display:grid;`)
+ templ_7745c5c3_CSSBuilder.WriteString(`grid-template-columns:minmax(auto, max-content) auto;`)
+ templ_7745c5c3_CSSBuilder.WriteString(`gap:1.5rem;`)
+ templ_7745c5c3_CSSBuilder.WriteString(`align-items:center;`)
+ templ_7745c5c3_CSSID := templ.CSSID(`downloads`, templ_7745c5c3_CSSBuilder.String())
+ return templ.ComponentCSSClass{
+ ID: templ_7745c5c3_CSSID,
+ Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
+ }
+}
+
+func fullWidth() templ.CSSClass {
+ templ_7745c5c3_CSSBuilder := templruntime.GetBuilder()
+ templ_7745c5c3_CSSBuilder.WriteString(`grid-column:span 2;`)
+ templ_7745c5c3_CSSID := templ.CSSID(`fullWidth`, templ_7745c5c3_CSSBuilder.String())
+ return templ.ComponentCSSClass{
+ ID: templ_7745c5c3_CSSID,
+ Class: templ.SafeCSS(`.` + templ_7745c5c3_CSSID + `{` + templ_7745c5c3_CSSBuilder.String() + `}`),
+ }
+}
+
+func Downloads(vm *DownloadsViewModel) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var20 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var20 == nil {
+ templ_7745c5c3_Var20 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ videos := GetVideosFromMetadata(vm.Meta)
+ templ_7745c5c3_Var21 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "Download Video ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var22 string
+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(*vm.Meta.Title)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 106, Col: 59}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var23 string
+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(vm.Url)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 107, Col: 41}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
Download Another Video ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for index, video := range videos {
+ templ_7745c5c3_Err = videoView(vm, index, video).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/views/helpers.go b/pkg/views/helpers.go
new file mode 100644
index 0000000..1d12666
--- /dev/null
+++ b/pkg/views/helpers.go
@@ -0,0 +1,12 @@
+package views
+
+import (
+ "strings"
+
+ "github.com/a-h/templ"
+ "github.com/spf13/viper"
+)
+
+func pathTo(path ...string) templ.SafeURL {
+ return templ.SafeURL(viper.GetString("base_path") + "/" + strings.TrimPrefix(strings.Join(path, "/"), "/"))
+}
diff --git a/pkg/views/home.templ b/pkg/views/home.templ
new file mode 100644
index 0000000..492714b
--- /dev/null
+++ b/pkg/views/home.templ
@@ -0,0 +1,66 @@
+package views
+
+import "go.fifitido.net/ytdl-web/pkg/components"
+
+type Error struct {
+ Message string
+ RetryUrl *string
+}
+
+templ Home(err *Error) {
+ @Layout() {
+ YTDL Web
+
+ Download videos from over a thousand websites with the help of
+ yt-dlp , a fork of youtube-dl
+ with more features and fixes.
+
+ View a complete list of supported websites
+ here .
+
+
+ if err != nil {
+
+
{ err.Message }
+ if err.RetryUrl != nil {
+
+ Try Again
+
+ Loading...
+
+
+ }
+
+ }
+ }
+}
diff --git a/pkg/views/home_templ.go b/pkg/views/home_templ.go
new file mode 100644
index 0000000..6ea0697
--- /dev/null
+++ b/pkg/views/home_templ.go
@@ -0,0 +1,141 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.960
+package views
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "go.fifitido.net/ytdl-web/pkg/components"
+
+type Error struct {
+ Message string
+ RetryUrl *string
+}
+
+func Home(err *Error) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "YTDL Web Download videos from over a thousand websites with the help of yt-dlp , a fork of youtube-dl with more features and fixes. View a complete list of supported websites here .
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if err != nil {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(err.Message)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/home.templ`, Line: 47, Col: 23}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if err.RetryUrl != nil {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
Try Again Loading...
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = Layout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/views/layout.templ b/pkg/views/layout.templ
new file mode 100644
index 0000000..1691dc9
--- /dev/null
+++ b/pkg/views/layout.templ
@@ -0,0 +1,52 @@
+package views
+
+import "go.fifitido.net/ytdl-web/pkg/components"
+
+templ Layout() {
+
+
+
+
+
+
+ YTDL Web
+
+
+
+
+
+
+
+
+
+
+ @components.Navbar()
+ @templ.Fragment("main-content") {
+
+ { children... }
+
+ }
+
+ @components.Footer()
+
+
+}
diff --git a/pkg/views/layout_templ.go b/pkg/views/layout_templ.go
new file mode 100644
index 0000000..f068748
--- /dev/null
+++ b/pkg/views/layout_templ.go
@@ -0,0 +1,88 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.960
+package views
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "go.fifitido.net/ytdl-web/pkg/components"
+
+func Layout() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "YTDL Web ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = components.Navbar().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+ templ_7745c5c3_Err = templ.Fragment("main-content").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = components.Footer().Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/views/video.go b/pkg/views/video.go
new file mode 100644
index 0000000..0df815d
--- /dev/null
+++ b/pkg/views/video.go
@@ -0,0 +1,46 @@
+package views
+
+import (
+ "go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
+)
+
+type VideoViewModel struct {
+ Meta *metadata.Metadata
+ Formats []metadata.Format
+ OtherFormats []metadata.Format
+}
+
+func GetVideosFromMetadata(meta *metadata.Metadata) []*VideoViewModel {
+ if meta.IsPlaylist() {
+ videos := make([]*VideoViewModel, 0, len(meta.Entries))
+
+ for _, entry := range meta.Entries {
+ videos = append(videos, GetVideosFromMetadata(&entry)...)
+ }
+
+ return videos
+ }
+
+ formats := []metadata.Format{}
+ otherFormats := []metadata.Format{}
+
+ for _, format := range meta.Formats {
+ if format.ACodec != "none" && format.VCodec != "none" && format.Protocol != "m3u8_native" {
+ formats = append(formats, format)
+ } else {
+ otherFormats = append(otherFormats, format)
+ }
+ }
+
+ for i, j := 0, len(formats)-1; i < j; i, j = i+1, j-1 {
+ formats[i], formats[j] = formats[j], formats[i]
+ }
+
+ return []*VideoViewModel{
+ {
+ Meta: meta,
+ Formats: formats,
+ OtherFormats: otherFormats,
+ },
+ }
+}
diff --git a/pkg/ytdl/ytdl.go b/pkg/ytdl/ytdl.go
index 03b8c47..150d809 100644
--- a/pkg/ytdl/ytdl.go
+++ b/pkg/ytdl/ytdl.go
@@ -3,15 +3,17 @@ package ytdl
import (
"bytes"
"encoding/json"
+ "errors"
"fmt"
"io"
"os/exec"
"strings"
+ "log/slog"
+
"go.fifitido.net/ytdl-web/config"
"go.fifitido.net/ytdl-web/pkg/ytdl/cache"
"go.fifitido.net/ytdl-web/pkg/ytdl/metadata"
- "golang.org/x/exp/slog"
)
type Ytdl interface {
@@ -27,6 +29,16 @@ type ytdlImpl struct {
version string
}
+var defaultYtdl Ytdl = &ytdlImpl{}
+
+func SetDefault(y Ytdl) {
+ defaultYtdl = y
+}
+
+func Default() Ytdl {
+ return defaultYtdl
+}
+
func NewYtdl(cfg *config.Config, logger *slog.Logger, cache cache.MetadataCache) Ytdl {
cmd := exec.Command(
cfg.Ytdlp.BinaryPath,
@@ -102,13 +114,23 @@ func (y *ytdlImpl) GetMetadata(url string) (*metadata.Metadata, error) {
args = y.appendCookieArgs(args)
+ fmt.Printf("ytdlp args: %#v\n", args)
+
cmd := exec.Command(y.cfg.Ytdlp.BinaryPath, args...)
out, err := cmd.Output()
if err != nil {
attrs := []any{
slog.String("url", url),
- slog.String("error", err.Error()),
+ }
+
+ exiterr := &exec.ExitError{}
+ if errors.As(err, &exiterr) {
+ attrs = append(attrs, slog.Int("code", exiterr.ExitCode()))
+ attrs = append(attrs, slog.String("stderr", string(exiterr.Stderr)))
+ attrs = append(attrs, slog.String("error", exiterr.Error()))
+ } else {
+ attrs = append(attrs, slog.String("error", err.Error()))
}
y.logger.Error("failed to get metadata", attrs...)