Switch to templ for views and simplify routing
This commit is contained in:
parent
b871ccdd7c
commit
668b36ad04
21
.envrc
21
.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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -14,3 +14,24 @@ devenv.local.nix
|
|||
|
||||
# pre-commit
|
||||
.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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
69
app/views.go
69
app/views.go
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
<div class="d-flex flex-column align-items-center">
|
||||
<h1>Download Video</h1>
|
||||
<h2 class="fs-4 text-muted text-center">{{.Meta.Title}}</h2>
|
||||
<p style="font-size: 0.85rem">{{.Url}}</p>
|
||||
<a href="{{.BasePath}}/" hx-get="{{.BasePath}}/" hx-trigger="click" hx-target="#main-content"
|
||||
class="btn btn-secondary btn-sm mt-3" style="width: 30rem; max-width: 100%">
|
||||
Download Another Video
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{$root := .}}
|
||||
{{range $vidIndex, $video := .Videos}}
|
||||
{{if not (eq $vidIndex 0)}}
|
||||
<hr class="mt-5" />
|
||||
{{end}}
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-center gap-5 mt-5">
|
||||
<div class="d-flex justify-content-center">
|
||||
<img src="{{$video.Meta.Thumbnail}}" alt="{{$video.Meta.Title}}" style="max-height: 25rem; max-width: 100%; margin: 0 auto" />
|
||||
</div>
|
||||
<div class="downloads flex-lg-grow-1">
|
||||
{{range $index, $format := $video.Formats}}
|
||||
{{template "format" (map "root" $root "format" $format "label" $format.Format "vidIndex" $vidIndex)}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if gt (len $video.OtherFormats) 0}}
|
||||
<div class="d-grid my-3">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapse-{{$root.Meta.ID}}-{{$vidIndex}}"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapse-{{$root.Meta.ID}}-{{$vidIndex}}"
|
||||
>
|
||||
See More Formats
|
||||
</button>
|
||||
</div>
|
||||
<div id="collapse-{{$root.Meta.ID}}-{{$vidIndex}}" class="collapse">
|
||||
<div class="downloads d-flex d-md-grid flex-column">
|
||||
{{range $index, $format := $video.OtherFormats}}
|
||||
{{$label := sprintf "ext: %s, resolution: %s, filesize: %s, note: %s" $format.Ext $format.Resolution
|
||||
(filesize $format.Filesize) $format.FormatNote}}
|
||||
{{template "format" (map "root" $root "format" $format "label" $label "vidIndex" $vidIndex)}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "format"}}
|
||||
<div style="font-size: smaller">{{.label}}</div>
|
||||
<div class="flex-grow-1 d-flex gap-3">
|
||||
<a class="btn btn-primary flex-grow-1" download="{{.root.Meta.ID}}-{{.format.Resolution}}.{{.format.Ext}}" P
|
||||
href="{{.format.Url}}">
|
||||
Download (direct)
|
||||
</a>
|
||||
<a class="btn btn-primary flex-grow-1" download="{{.root.Meta.ID}}-{{.format.Resolution}}.{{.format.Ext}}"
|
||||
href="{{.root.BasePath}}/download/proxy?url={{queryEscape .root.Url}}&format={{.format.FormatID}}&index={{.vidIndex}}">
|
||||
Download (proxied)
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<h1 class="text-center">YTDL Web</h1>
|
||||
<p class="text-center mb-5">
|
||||
Download videos from over a thousand websites with the help of
|
||||
<a href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a>, a fork of youtube-dl
|
||||
with more features and fixes.
|
||||
<br />
|
||||
View a complete list of supported websites
|
||||
<a href="https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md">here</a>.
|
||||
</p>
|
||||
|
||||
<form hx-get="{{.BasePath}}/download" hx-trigger="submit" hx-target="#main-content" hx-swap="innerHTML">
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label visually-hidden">Url</label>
|
||||
<div class="input-group">
|
||||
<input type="url" name="url" id="url" class="form-control" required
|
||||
placeholder="Enter url here then click download" />
|
||||
{{if .IsSecure}}
|
||||
<button id="paste-button" class="btn btn-outline-secondary" type="button" title="Paste">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
style="width: 1.5rem; height: 1.5rem">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184" />
|
||||
</svg>
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Download
|
||||
<div class="spinner-border spinner-border-sm htmx-indicator ms-1" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{{if .Error}}
|
||||
<div class="alert alert-danger mt-4" role="alert">
|
||||
<span>{{.Error.Message}}</span>
|
||||
|
||||
{{if .Error.RetryUrl}}
|
||||
<button class="btn btn-link btn-sm pt-0 lh-base text-decoration-none" hx-get="/download" hx-trigger="click"
|
||||
hx-target="#main-content" hx-swap="innerHTML" hx-vals='{"url": "{{.Error.RetryUrl}}"}'>
|
||||
<span class="text-decoration-underline">Try Again</span>
|
||||
<div class="spinner-border spinner-border-sm htmx-indicator ms-1" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en" class="h-100">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>YTDL Web</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.css"
|
||||
integrity="sha256-R91pD48xW+oHbpJYGn5xR0Q7tMhH4xOrWn1QqMRINtA=" crossorigin="anonymous" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"
|
||||
defer></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.js"
|
||||
integrity="sha256-Hgwq1OBpJ276HUP9H3VJkSv9ZCGRGQN+JldPJ8pNcUM=" crossorigin="anonymous" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@1.9.4/dist/htmx.min.js"
|
||||
integrity="sha256-XIivRAE99i/eil5P31JNihaDSiix0V40rgmUrCfNTH4=" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
#toast-container>div {
|
||||
-moz-box-shadow: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
-ms-filter: none;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.downloads {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(auto, max-content) auto;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.see-more-btn,
|
||||
.collapse {
|
||||
grid-column: span 2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="d-flex flex-column h-100" data-bs-theme="dark">
|
||||
<div class="flex-shrink-0">
|
||||
{{template "partials/navbar" .}}
|
||||
<main id="main-content" class="container my-5">{{yield}}</main>
|
||||
</div>
|
||||
{{template "partials/footer" .}}
|
||||
<script>
|
||||
/**
|
||||
* @param content {Element}
|
||||
*/
|
||||
function setupPaste(content) {
|
||||
const pasteButton = content.querySelector("#paste-button");
|
||||
const urlField = content.querySelector("#url");
|
||||
|
||||
if (pasteButton) {
|
||||
pasteButton.addEventListener("click", async () => {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
urlField.value = text;
|
||||
} catch (error) {
|
||||
toastr.error("Failed to paste url from clipboard.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
htmx.onLoad(setupPaste);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<footer class="footer mt-auto py-3 bg-body-tertiary" style="font-size: 0.95rem">
|
||||
<div class="container">
|
||||
<div class="row gap-2 gap-md-0">
|
||||
<div class="d-flex gap-4 col-md justify-content-center">
|
||||
<div class="d-flex gap-1 align-items-baseline">
|
||||
Version:
|
||||
<span class="text-muted">{{.Version}}</span>
|
||||
<span class="text-muted text-nowrap" style="font-size: smaller">(Build: {{.Build}})</span>
|
||||
</div>
|
||||
<div class="d-flex gap-1 align-items-baseline text-nowrap">
|
||||
yt-dlp version: <span class="text-muted">{{.BinaryVersion}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2 col-md justify-content-center">
|
||||
<a href="https://git.fifitido.net/apps/ytdl-web">Git Repository</a>
|
||||
<span class="text-muted">© 2024 FiFiTiDo</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<nav class="navbar navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a href="/" class="navbar-brand">YTDL Web</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
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")
|
||||
}
|
||||
45
cmd/root.go
45
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))
|
||||
}
|
||||
|
|
|
|||
68
cmd/serve.go
68
cmd/serve.go
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
Copyright © 2024 Evan Fiordeliso <evan.fiordeliso@gmail.com>
|
||||
*/
|
||||
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")
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
109
devenv.lock
109
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"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
}
|
||||
14
devenv.yaml
14
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
|
||||
|
|
|
|||
757
flake.lock
757
flake.lock
|
|
@ -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
|
||||
}
|
||||
56
flake.nix
56
flake.nix
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
62
go.mod
62
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
|
||||
)
|
||||
|
|
|
|||
106
go.sum
106
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=
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"go.fifitido.net/ytdl-web/pkg/ytdl"
|
||||
"go.fifitido.net/ytdl-web/version"
|
||||
)
|
||||
|
||||
templ Footer() {
|
||||
<footer class="footer mt-auto py-3 bg-body-tertiary" style="font-size: 0.95rem">
|
||||
<div class="container">
|
||||
<div class="row gap-2 gap-md-0">
|
||||
<div class="d-flex gap-4 col-md justify-content-center">
|
||||
<div class="d-flex gap-1 align-items-baseline">
|
||||
Version:
|
||||
<span class="text-muted">{ version.Version }</span>
|
||||
<span class="text-muted text-nowrap" style="font-size: smaller">(Build: { version.Build })</span>
|
||||
</div>
|
||||
<div class="d-flex gap-1 align-items-baseline text-nowrap">
|
||||
yt-dlp version: <span class="text-muted">{ ytdl.Default().Version() }</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2 col-md justify-content-center">
|
||||
<a href="https://git.fifitido.net/apps/ytdl-web">Git Repository</a>
|
||||
<span class="text-muted">© 2024 FiFiTiDo</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
}
|
||||
|
|
@ -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, "<footer class=\"footer mt-auto py-3 bg-body-tertiary\" style=\"font-size: 0.95rem\"><div class=\"container\"><div class=\"row gap-2 gap-md-0\"><div class=\"d-flex gap-4 col-md justify-content-center\"><div class=\"d-flex gap-1 align-items-baseline\">Version: <span class=\"text-muted\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(version.Version)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/footer.templ`, Line: 15, Col: 48}
|
||||
}
|
||||
_, 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, "</span> <span class=\"text-muted text-nowrap\" style=\"font-size: smaller\">(Build: ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(version.Build)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/footer.templ`, Line: 16, Col: 93}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, ")</span></div><div class=\"d-flex gap-1 align-items-baseline text-nowrap\">yt-dlp version: <span class=\"text-muted\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(ytdl.Default().Version())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/components/footer.templ`, Line: 19, Col: 73}
|
||||
}
|
||||
_, 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, 4, "</span></div></div><div class=\"d-flex gap-2 col-md justify-content-center\"><a href=\"https://git.fifitido.net/apps/ytdl-web\">Git Repository</a> <span class=\"text-muted\">© 2024 FiFiTiDo</span></div></div></div></footer>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package components
|
||||
|
||||
templ Navbar() {
|
||||
<nav class="navbar navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a href="/" class="navbar-brand">YTDL Web</a>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
|
|
@ -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, "<nav class=\"navbar navbar-dark bg-primary\"><div class=\"container\"><a href=\"/\" class=\"navbar-brand\">YTDL Web</a></div></nav>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
|
|
@ -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) {
|
||||
<button id="paste-button" class="btn btn-outline-secondary" type="button" title="Paste" onclick={ pasteClipboard() }>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
style="width: 1.5rem; height: 1.5rem"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
|
@ -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, "<button id=\"paste-button\" class=\"btn btn-outline-secondary\" type=\"button\" title=\"Paste\" onclick=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 templ.ComponentScript = pasteClipboard()
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2.Call)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\" style=\"width: 1.5rem; height: 1.5rem\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184\"></path></svg></button>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
120
pkg/htmx/htmx.go
120
pkg/htmx/htmx.go
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package server
|
||||
|
||||
import "github.com/go-chi/chi/v5"
|
||||
|
||||
type Controller interface {
|
||||
Router(r chi.Router)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -3,8 +3,9 @@ package utils
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/dgraph-io/badger/v2"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
type SlogLogger struct {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ package utils
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
type loggerWriter struct {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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) }}
|
||||
<div style="font-size: smaller">{ label }</div>
|
||||
<div class="flex-grow-1 d-flex gap-3">
|
||||
<style>
|
||||
.dl-direct:after {
|
||||
content: 'Download (direct)';
|
||||
}
|
||||
</style>
|
||||
<a
|
||||
class="btn btn-primary flex-grow-1 dl-direct"
|
||||
download={ filename }
|
||||
href={ templ.SafeURL(format.Url) }
|
||||
aria-label="Direct Download"
|
||||
>
|
||||
// Workaround for browsers that use the content of the link as the filename
|
||||
<span class="d-none">{ filename }</span>
|
||||
</a>
|
||||
<a
|
||||
class="btn btn-primary flex-grow-1"
|
||||
download={ filename }
|
||||
href={ pathTo(fmt.Sprintf("download/proxy?url=%s&format=%s&index=%d", vm.Url, format.FormatID, vidIndex)) }
|
||||
aria-label="Proxied Download"
|
||||
>
|
||||
Download (proxied)
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ videoView(vm *DownloadsViewModel, index int, video *VideoViewModel) {
|
||||
if index != 0 {
|
||||
<hr class="mt-5"/>
|
||||
}
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-center gap-5 mt-5">
|
||||
{{
|
||||
thumbnail := video.Meta.Thumbnail
|
||||
thumbnailAlt := video.Meta.ID
|
||||
if video.Meta.Title != nil {
|
||||
thumbnailAlt = *video.Meta.Title
|
||||
}
|
||||
}}
|
||||
if thumbnail != nil {
|
||||
<div class="d-flex justify-content-center">
|
||||
<img src={ *thumbnail } alt={ thumbnailAlt } style="max-height: 25rem; max-width: 100%; margin: 0 auto"/>
|
||||
</div>
|
||||
}
|
||||
<div class={ downloads(), "flex-lg-grow-1" }>
|
||||
for _, format := range video.Formats {
|
||||
@videoFormatView(vm, index, format, format.Format)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
if len(video.OtherFormats) > 0 {
|
||||
{{ collapseId := fmt.Sprintf("collapse-%s-%d", vm.Meta.ID, index) }}
|
||||
<div class="d-grid my-3">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target={ "#" + collapseId }
|
||||
aria-expanded="false"
|
||||
aria-controls={ collapseId }
|
||||
>
|
||||
See More Formats
|
||||
</button>
|
||||
</div>
|
||||
<div id={ collapseId } class={ fullWidth(), "collapse" }>
|
||||
<div class={ downloads(), "d-flex d-md-grid flex-column" }>
|
||||
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)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
<div class="d-flex flex-column align-items-center">
|
||||
<h1>Download Video</h1>
|
||||
<h2 class="fs-4 text-muted text-center">{ *vm.Meta.Title }</h2>
|
||||
<p style="font-size: 0.85rem">{ vm.Url }</p>
|
||||
<a
|
||||
href={ pathTo() }
|
||||
hx-get={ pathTo() }
|
||||
hx-trigger="click"
|
||||
class="btn btn-secondary btn-sm mt-3"
|
||||
style="width: 30rem; max-width: 100%"
|
||||
>
|
||||
Download Another Video
|
||||
</a>
|
||||
</div>
|
||||
for index, video := range videos {
|
||||
@videoView(vm, index, video)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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, "<div style=\"font-size: smaller\">")
|
||||
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, "</div><div class=\"flex-grow-1 d-flex gap-3\"><style>\n\t\t .dl-direct:after {\n\t\t content: 'Download (direct)';\n\t\t }\n\t\t</style><a class=\"btn btn-primary flex-grow-1 dl-direct\" download=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(filename)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 24, Col: 22}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 templ.SafeURL
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(format.Url))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 25, Col: 35}
|
||||
}
|
||||
_, 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, 4, "\" aria-label=\"Direct Download\"><span class=\"d-none\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(filename)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 29, Col: 34}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span></a> <a class=\"btn btn-primary flex-grow-1\" download=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(filename)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 33, Col: 22}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 templ.SafeURL
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(pathTo(fmt.Sprintf("download/proxy?url=%s&format=%s&index=%d", vm.Url, format.FormatID, vidIndex)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 34, Col: 108}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" aria-label=\"Proxied Download\">Download (proxied)</a></div>")
|
||||
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, "<hr class=\"mt-5\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"d-flex flex-column flex-lg-row justify-content-center gap-5 mt-5\">")
|
||||
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, "<div class=\"d-flex justify-content-center\"><img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(*thumbnail)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 56, Col: 25}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" alt=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(thumbnailAlt)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 56, Col: 46}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" style=\"max-height: 25rem; max-width: 100%; margin: 0 auto\"></div>")
|
||||
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, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var11).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
|
||||
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, "</div></div>")
|
||||
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, "<div class=\"d-grid my-3\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-toggle=\"collapse\" data-bs-target=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs("#" + collapseId)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 72, Col: 37}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" aria-expanded=\"false\" aria-controls=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(collapseId)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 74, Col: 30}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\">See More Formats</button></div>")
|
||||
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, "<div id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(collapseId)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 79, Col: 22}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 string
|
||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var15).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\">")
|
||||
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, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var19 string
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var18).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\">")
|
||||
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, "</div></div>")
|
||||
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, "<div class=\"d-flex flex-column align-items-center\"><h1>Download Video</h1><h2 class=\"fs-4 text-muted text-center\">")
|
||||
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, "</h2><p style=\"font-size: 0.85rem\">")
|
||||
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, "</p><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var24 templ.SafeURL
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinURLErrs(pathTo())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 109, Col: 19}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\" hx-get=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var25 string
|
||||
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(pathTo())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/downloads.templ`, Line: 110, Col: 21}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\" hx-trigger=\"click\" class=\"btn btn-secondary btn-sm mt-3\" style=\"width: 30rem; max-width: 100%\">Download Another Video</a></div>")
|
||||
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
|
||||
|
|
@ -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, "/"), "/"))
|
||||
}
|
||||
|
|
@ -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() {
|
||||
<h1 class="text-center">YTDL Web</h1>
|
||||
<p class="text-center mb-5">
|
||||
Download videos from over a thousand websites with the help of
|
||||
<a href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a>, a fork of youtube-dl
|
||||
with more features and fixes.
|
||||
<br/>
|
||||
View a complete list of supported websites
|
||||
<a href="https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md">here</a>.
|
||||
</p>
|
||||
<form hx-get={ pathTo("/download") } hx-trigger="submit">
|
||||
<div class="mb-3">
|
||||
<label for="url" class="form-label visually-hidden">Url</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="url"
|
||||
name="url"
|
||||
id="url"
|
||||
class="form-control"
|
||||
required
|
||||
placeholder="Enter url here then click download"
|
||||
/>
|
||||
@components.PasteButton()
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Download
|
||||
<div class="spinner-border spinner-border-sm htmx-indicator ms-1" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
if err != nil {
|
||||
<div class="alert alert-danger mt-4" role="alert">
|
||||
<span>{ err.Message }</span>
|
||||
if err.RetryUrl != nil {
|
||||
<button
|
||||
class="btn btn-link btn-sm pt-0 lh-base text-decoration-none"
|
||||
hx-get={ pathTo("/download") }
|
||||
hx-trigger="click"
|
||||
hx-target="#main-content"
|
||||
hx-swap="innerHTML"
|
||||
hx-vals={ templ.JSONString(map[string]any{"url": *err.RetryUrl}) }
|
||||
>
|
||||
<span class="text-decoration-underline">Try Again</span>
|
||||
<div class="spinner-border spinner-border-sm htmx-indicator ms-1" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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, "<h1 class=\"text-center\">YTDL Web</h1><p class=\"text-center mb-5\">Download videos from over a thousand websites with the help of <a href=\"https://github.com/yt-dlp/yt-dlp\">yt-dlp</a>, a fork of youtube-dl with more features and fixes.<br>View a complete list of supported websites <a href=\"https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md\">here</a>.</p><form hx-get=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(pathTo("/download"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/home.templ`, Line: 21, Col: 36}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" hx-trigger=\"submit\"><div class=\"mb-3\"><label for=\"url\" class=\"form-label visually-hidden\">Url</label><div class=\"input-group\"><input type=\"url\" name=\"url\" id=\"url\" class=\"form-control\" required placeholder=\"Enter url here then click download\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = components.PasteButton().Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></div><div class=\"d-grid\"><button type=\"submit\" class=\"btn btn-primary\">Download<div class=\"spinner-border spinner-border-sm htmx-indicator ms-1\" role=\"status\"><span class=\"visually-hidden\">Loading...</span></div></button></div></form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if err != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"alert alert-danger mt-4\" role=\"alert\"><span>")
|
||||
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, "</span> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if err.RetryUrl != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<button class=\"btn btn-link btn-sm pt-0 lh-base text-decoration-none\" hx-get=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(pathTo("/download"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/home.templ`, Line: 51, Col: 34}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" hx-trigger=\"click\" hx-target=\"#main-content\" hx-swap=\"innerHTML\" hx-vals=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(templ.JSONString(map[string]any{"url": *err.RetryUrl}))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/views/home.templ`, Line: 55, Col: 70}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><span class=\"text-decoration-underline\">Try Again</span><div class=\"spinner-border spinner-border-sm htmx-indicator ms-1\" role=\"status\"><span class=\"visually-hidden\">Loading...</span></div></button>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div>")
|
||||
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
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package views
|
||||
|
||||
import "go.fifitido.net/ytdl-web/pkg/components"
|
||||
|
||||
templ Layout() {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-100">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>YTDL Web</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.css"
|
||||
integrity="sha256-R91pD48xW+oHbpJYGn5xR0Q7tMhH4xOrWn1QqMRINtA="
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous" defer></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.js" integrity="sha256-Hgwq1OBpJ276HUP9H3VJkSv9ZCGRGQN+JldPJ8pNcUM=" crossorigin="anonymous" defer></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@1.9.4/dist/htmx.min.js" integrity="sha256-XIivRAE99i/eil5P31JNihaDSiix0V40rgmUrCfNTH4=" crossorigin="anonymous"></script>
|
||||
<style>
|
||||
#toast-container>div {
|
||||
-moz-box-shadow: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
-ms-filter: none;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex flex-column h-100" data-bs-theme="dark">
|
||||
<div class="flex-shrink-0">
|
||||
@components.Navbar()
|
||||
@templ.Fragment("main-content") {
|
||||
<main id="main-content" class="container my-5" hx-target="#main-content" hx-swap="outerHTML">
|
||||
{ children... }
|
||||
</main>
|
||||
}
|
||||
</div>
|
||||
@components.Footer()
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
|
@ -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, "<!doctype html><html lang=\"en\" class=\"h-100\"><head><meta charset=\"UTF-8\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>YTDL Web</title><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN\" crossorigin=\"anonymous\"><link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.css\" integrity=\"sha256-R91pD48xW+oHbpJYGn5xR0Q7tMhH4xOrWn1QqMRINtA=\" crossorigin=\"anonymous\"><script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js\" integrity=\"sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL\" crossorigin=\"anonymous\" defer></script><script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js\" defer></script><script src=\"https://cdn.jsdelivr.net/npm/toastr@2.1.4/build/toastr.min.js\" integrity=\"sha256-Hgwq1OBpJ276HUP9H3VJkSv9ZCGRGQN+JldPJ8pNcUM=\" crossorigin=\"anonymous\" defer></script><script src=\"https://cdn.jsdelivr.net/npm/htmx.org@1.9.4/dist/htmx.min.js\" integrity=\"sha256-XIivRAE99i/eil5P31JNihaDSiix0V40rgmUrCfNTH4=\" crossorigin=\"anonymous\"></script><style>\n \t #toast-container>div {\n -moz-box-shadow: none !important;\n -webkit-box-shadow: none !important;\n box-shadow: none !important;\n -ms-filter: none;\n filter: none;\n opacity: 1;\n \t }\n </style></head><body class=\"d-flex flex-column h-100\" data-bs-theme=\"dark\"><div class=\"flex-shrink-0\">")
|
||||
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, "<main id=\"main-content\" class=\"container my-5\" hx-target=\"#main-content\" hx-swap=\"outerHTML\">")
|
||||
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, "</main>")
|
||||
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, "</div>")
|
||||
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, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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...)
|
||||
|
|
|
|||
Loading…
Reference in New Issue