Compare commits

...

4 Commits

Author SHA1 Message Date
bel
bf23d6a9cf wish i could preview items but too lazy 2025-05-05 22:10:04 -06:00
bel
0271f84948 ui has multi-page edit vs new even tho they the same page but templates a lil diff 2025-05-05 21:33:31 -06:00
bel
0e3e6c54de fix tests vs running 2025-05-05 21:32:37 -06:00
bel
352eff2691 webhooks can be vpntor:///outdir 2025-05-05 21:26:19 -06:00
9 changed files with 112 additions and 47 deletions

View File

@@ -70,17 +70,19 @@ func oneItem(ctx context.Context, feed feeds.Feed, item feeds.Item) error {
Item: item, Item: item,
} }
method, err := render(feed.Version.WebhookMethod, arg) wmethod, wurl, wbody := feed.Webhook()
method, err := render(wmethod, arg)
if err != nil { if err != nil {
return err return err
} }
wurl, err := render(feed.Version.WebhookURL, arg) wurl, err = render(wurl, arg)
if err != nil { if err != nil {
return err return err
} }
body, err := render(feed.Version.WebhookBody, arg) body, err := render(wbody, arg)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -29,9 +29,9 @@ func TestOne(t *testing.T) {
}, },
"feeds": func(t *testing.T) context.Context { "feeds": func(t *testing.T) context.Context {
gets := []string{} gets := []string{}
sURL := "http://localhost:10000/" sURL := "http://localhost:10001/"
s := &http.Server{ s := &http.Server{
Addr: ":10000", Addr: ":10001",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gets = append(gets, r.URL.String()) gets = append(gets, r.URL.String())
rb, _ := io.ReadAll(r.Body) rb, _ := io.ReadAll(r.Body)
@@ -43,6 +43,7 @@ func TestOne(t *testing.T) {
case "10": case "10":
case "11": case "11":
default: default:
t.Logf("%s => 404", r.URL)
http.NotFound(w, r) http.NotFound(w, r)
} }

View File

@@ -24,12 +24,12 @@ func (h Handler) feeds(w http.ResponseWriter, r *http.Request) error {
func (h Handler) feedsPost(ctx context.Context, form url.Values) error { func (h Handler) feedsPost(ctx context.Context, form url.Values) error {
var req feeds.Version var req feeds.Version
for k, v := range map[string]*string{ for k, v := range map[string]*string{
"URL": &req.URL,
"Cron": &req.Cron, "Cron": &req.Cron,
"Pattern": &req.Pattern, "Pattern": &req.Pattern,
"URL": &req.URL,
"WebhookBody": &req.WebhookBody,
"WebhookMethod": &req.WebhookMethod, "WebhookMethod": &req.WebhookMethod,
"WebhookURL": &req.WebhookURL, "WebhookURL": &req.WebhookURL,
"WebhookBody": &req.WebhookBody,
} { } {
if *v = form.Get(k); *v == "" { if *v = form.Get(k); *v == "" {
return fmt.Errorf("no ?%s in %s", k, form.Encode()) return fmt.Errorf("no ?%s in %s", k, form.Encode())

View File

@@ -1,11 +1,6 @@
<html> <html>
<header> <header>
<link rel="stylesheet" href="/experimental/ui/dark.css"> <link rel="stylesheet" href="/experimental/ui/dark.css">
<script>
fill(elem) {
}
</script>
</header> </header>
<body> <body>
<h2>Feeds</h2> <h2>Feeds</h2>
@@ -13,24 +8,37 @@
<div> <div>
{{ range feeds }} {{ range feeds }}
<div> <div>
<h4>(<button onclick="fill(this)">{{ namespan "entry.id" .Entry.ID}}</button>) {{ namespan "version.url" .Version.URL }}</h4> <h3><code><a href="?edit={{.Entry.ID}}">{{ .Version.URL }}</a></code></h3>
<div>{{ .Version.Created }} (last {{ .Execution.Executed }})</div> <div>@<code>{{ .Version.Cron }}</code> ~<code>{{ .Version.Pattern }}</code></div>
<div>@{{ .Version.Cron }} ~"{{ .Version.Pattern }}"</div> <div><code>{{ .Version.WebhookMethod }} {{ .Version.WebhookURL }} | {{ .Version.WebhookBody }}</code></div>
<div>{{ .Version.WebhookMethod }} {{ .Version.WebhookURL }} | {{ .Version.WebhookBody }}</div> <div>(last run {{ ago .Execution.Executed }} ago)</div>
</div> </div>
{{ end }} {{ end }}
</div> </div>
<br> <br><hr><br>
<div> <div>
<h3>New</h3> <h3>
{{ if eq "" .editing.ID }}
New
{{ else }}
Update <code><a target="_blank" href="{{ .editing_url }}">{{ .editing.URL }}</a></code> (<a href="?">clear</a>)
{{ end }}
</h3>
<form method="POST" action="/v1/feeds"> <form method="POST" action="/v1/feeds">
{{ range feedsVersionFields }} {{ range $k, $v := .editing }}
{{ if ne . "Created" }} {{ if not (in $k "Created" "Deleted" "Updated" "ID") }}
<div> <div>
<label for="{{ . }}">{{ . }}</label> <label for="{{ $k }}">
<input name="{{ . }}" type="text" /> {{ $k }}
{{- if eq $k "URL" }}
(hint: nyaa://?q=show)
{{ else if eq $k "WebhookURL" }}
(hint: vpntor:///outdir)
{{ end }}
</label>
<input name="{{ $k }}" type="text" value="{{ $v }}"/>
</div> </div>
{{ end }} {{ end }}
{{ end }} {{ end }}

View File

@@ -2,13 +2,13 @@ package handler
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"os" "os"
"path" "path"
"show-rss/src/feeds" "show-rss/src/feeds"
"slices" "slices"
"text/template" "text/template"
"time"
) )
var dir = func() string { var dir = func() string {
@@ -30,16 +30,27 @@ func (h Handler) ui(w http.ResponseWriter, r *http.Request) error {
func (h Handler) uiIndex(w http.ResponseWriter, r *http.Request) error { func (h Handler) uiIndex(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context() ctx := r.Context()
var editing struct {
feeds.Entry `json:",inline"`
feeds.Version `json:",inline"`
}
all := []feeds.Feed{}
if err := feeds.ForEach(ctx, func(f feeds.Feed) error {
all = append(all, f)
if f.Entry.ID == r.URL.Query().Get("edit") {
editing.Entry = f.Entry
editing.Version = f.Version
}
return ctx.Err()
}); err != nil {
return err
}
b, _ := os.ReadFile(path.Join(dir, "index.tmpl")) b, _ := os.ReadFile(path.Join(dir, "index.tmpl"))
tmpl := template.New(r.URL.Path).Funcs(template.FuncMap{ tmpl := template.New(r.URL.Path).Funcs(template.FuncMap{
"feeds": func() ([]feeds.Feed, error) { "feeds": func() []feeds.Feed {
all := []feeds.Feed{} return all
err := feeds.ForEach(ctx, func(f feeds.Feed) error {
all = append(all, f)
return ctx.Err()
})
return all, err
}, },
"feedsVersionFields": func() []string { "feedsVersionFields": func() []string {
b, _ := json.Marshal(feeds.Version{}) b, _ := json.Marshal(feeds.Version{})
@@ -52,8 +63,11 @@ func (h Handler) uiIndex(w http.ResponseWriter, r *http.Request) error {
slices.Sort(ks) slices.Sort(ks)
return ks return ks
}, },
"namespan": func(k string, v any) string { "in": func(k string, v ...string) bool {
return fmt.Sprintf("<span name=%q>%s</span>", k, v) return slices.Contains(v, k)
},
"ago": func(t time.Time) time.Duration {
return time.Since(t)
}, },
}) })
@@ -62,5 +76,16 @@ func (h Handler) uiIndex(w http.ResponseWriter, r *http.Request) error {
return err return err
} }
return tmpl.Execute(w, nil) args := map[string]any{}
{
b, _ := json.Marshal(editing)
var m map[string]any
json.Unmarshal(b, &m)
args["editing"] = m
args["editing_url"], _ = editing.FetchURL()
}
return tmpl.Execute(w, args)
} }

View File

@@ -5,18 +5,12 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"os"
"show-rss/src/cmd/server/handler" "show-rss/src/cmd/server/handler"
"strconv" "show-rss/src/server"
) )
func Main(ctx context.Context) error { func Main(ctx context.Context) error {
port, _ := strconv.Atoi(os.Getenv("PORT")) return Run(ctx, fmt.Sprintf(":%d", server.Port))
if port == 0 {
port = 10000
}
return Run(ctx, fmt.Sprintf(":%d", port))
} }
func Run(ctx context.Context, listen string) error { func Run(ctx context.Context, listen string) error {

View File

@@ -2,8 +2,11 @@ package feeds
import ( import (
"context" "context"
"fmt"
"io" "io"
"net/url"
"show-rss/src/db" "show-rss/src/db"
"show-rss/src/server"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@@ -230,3 +233,17 @@ func initDB(ctx context.Context) error {
)`, )`,
}) })
} }
func (feed Feed) Webhook() (string, string, string) {
u, _ := url.Parse(feed.Version.WebhookURL)
switch u.Scheme {
case "vpntor":
return "POST", fmt.Sprintf("http://localhost:%d/v1/vpntor", server.Port), fmt.Sprintf(`{
"Magnet": "{{ .Item.Link }}",
"Dir": %q,
"URL": "https://vpntor.int.bel.blue/transmission/rpc"
}`, u.Path)
default:
return feed.Version.WebhookMethod, feed.Version.WebhookURL, feed.Version.WebhookBody
}
}

View File

@@ -132,14 +132,18 @@ func proxyFetch(ctx context.Context, u string) (string, error) {
b, _ := io.ReadAll(resp.Body) b, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed fetch: (%d) %s", resp.StatusCode, b) return "", fmt.Errorf("failed fetch %s %s: (%d) %s", req.Method, req.URL.String(), resp.StatusCode, b)
} }
return string(b), nil return string(b), nil
} }
func (feed Feed) FetchURL() (*url.URL, error) { func (feed Feed) FetchURL() (*url.URL, error) {
u, err := url.Parse(feed.Version.URL) return feed.Version.FetchURL()
}
func (version Version) FetchURL() (*url.URL, error) {
u, err := url.Parse(version.URL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -148,7 +152,7 @@ func (feed Feed) FetchURL() (*url.URL, error) {
case "nyaa": // `nyaa://?q=A B` to `https://nyaa.si/?page=rss&q=A%20B&c=0_0&f=0` case "nyaa": // `nyaa://?q=A B` to `https://nyaa.si/?page=rss&q=A%20B&c=0_0&f=0`
q := u.Query() q := u.Query()
if q.Get("q") == "" { if q.Get("q") == "" {
return nil, fmt.Errorf("invalid nyaa:// (%s): no ?q", feed.Version.URL) return nil, fmt.Errorf("invalid nyaa:// (%s): no ?q", version.URL)
} }
q.Set("page", "rss") q.Set("page", "rss")

14
src/server/config.go Normal file
View File

@@ -0,0 +1,14 @@
package server
import (
"os"
"strconv"
)
var Port = func() int {
port, _ := strconv.Atoi(os.Getenv("PORT"))
if port == 0 {
port = 10000
}
return port
}()