Local mode, Makefile, image fixes, Pete avatar

- Add -local flag: web/RSS-only mode that skips Matrix login and posting,
  so the web UI can be exercised against live feeds without credentials
- Add Makefile (build/local/seed/test/clean) that handles Tailwind +
  go build in one shot
- Fix Guardian thumbnails: NormalizeImageURL was rewriting width=1200
  onto signed i.guim.co.uk URLs, invalidating the s= signature and
  returning 401. Leave signed URLs alone and pick the widest
  media:content variant up front instead
- Use pete.avif as the header logo, favicon, and footer mark; drop the
  unused leaf.svg
This commit is contained in:
prosolis
2026-05-24 22:43:03 -07:00
parent 9d5db63c56
commit 0111a1b06d
10 changed files with 115 additions and 16 deletions

View File

@@ -6,10 +6,12 @@ import (
"log/slog"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"github.com/mmcdole/gofeed"
ext "github.com/mmcdole/gofeed/extensions"
)
const userAgent = "Pete/1.0 (newsbot; +https://github.com/reala-misaki/pete)"
@@ -88,21 +90,19 @@ func extractLede(desc string) string {
// scraped from content:encoded / description (catches feeds like Ars that
// embed the lead image in the article body but not as a media:* element).
func extractImageURL(item *gofeed.Item) string {
// Check media content (common in RSS 2.0 with media namespace)
// Check media content (common in RSS 2.0 with media namespace).
// When multiple media:content variants are advertised (Guardian RSS lists
// 140/460/700/1200…) we want the widest signed URL we can find.
if item.Extensions != nil {
if media, ok := item.Extensions["media"]; ok {
if contents, ok := media["content"]; ok {
for _, c := range contents {
if url := c.Attrs["url"]; url != "" {
return url
}
if url := widestURL(contents); url != "" {
return url
}
}
if thumbs, ok := media["thumbnail"]; ok {
for _, t := range thumbs {
if url := t.Attrs["url"]; url != "" {
return url
}
if url := widestURL(thumbs); url != "" {
return url
}
}
// media:group wraps nested media:content / media:thumbnail in some feeds
@@ -143,6 +143,25 @@ func extractImageURL(item *gofeed.Item) string {
return ""
}
// widestURL picks the entry with the largest declared width attribute, falling
// back to the first URL if none have a parsable width.
func widestURL(entries []ext.Extension) string {
best := ""
bestW := -1
for _, e := range entries {
url := e.Attrs["url"]
if url == "" {
continue
}
w, _ := strconv.Atoi(e.Attrs["width"])
if w > bestW {
best = url
bestW = w
}
}
return best
}
func firstImgSrc(htmlBody string) string {
if htmlBody == "" {
return ""