172 Commits

Author SHA1 Message Date
Sven
314a08542b fix: buffer ActivityPub body before checking for PeerTube View activities
The previous patch checked req.body?.type === "View" but Express's JSON
body parser ignores application/activity+json, so req.body was always
undefined and the guard never fired.

Fix in two parts:
1. In createFedifyMiddleware: manually buffer and JSON-parse the raw
   request stream for activity+json/ld+json POSTs, storing the result on
   req.body before the type check.
2. In fromExpressRequest: extend the content-type check to include
   activity+json/ld+json so non-View activities are correctly forwarded
   to Fedify with the buffered body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:40:54 +01:00
Sven
3708dd92c3 chore: remove Mastodon syndicator and related patches
Blog is now a native ActivityPub actor; Mastodon syndication via
troet.cafe is no longer needed. Removes the syndicator package,
config vars, patch script, and env example entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:31:37 +01:00
Sven
296745fcf8 fix: skip PeerTube View activities before Fedify JSON-LD parse
The .on(View) inbox handler from the previous commit is never reached
because Fedify crashes parsing PeerTube's View activity before dispatch —
its JSON-LD deserialiser doesn't recognise Schema.org InteractionCounter,
throwing "Failed to parse activity: TypeError: Invalid type".

Add a guard at the top of createFedifyMiddleware that short-circuits any
POST with body.type === "View" and returns 200 immediately, bypassing
federation.fetch() entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:21:25 +01:00
Sven
f004ecdbcc fix: silently ignore PeerTube View activities in ActivityPub inbox
PeerTube broadcasts non-standard View (WatchAction) activities to all
followers on every video watch. Fedify has no handler for this type,
causing noisy "Unsupported activity type" errors in the federation log.

Adds a no-op .on(View, ...) handler at the end of the inbox listener
chain via the existing patch script mechanism.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:12:38 +01:00
svemagie
655bc736f2 fix: clear stale Bluesky polling cursor to restore interaction ingestion 2026-03-14 14:46:43 +01:00
svemagie
4f1440a634 fix: filter out self-interactions from own Bluesky account 2026-03-14 14:01:44 +01:00
Sven
d3c094f91f update: activitypub 2026-03-14 13:53:40 +01:00
Sven
b632af9564 fix(webmention): scope link extraction to .h-entry not .e-content
u-in-reply-to, u-like-of, u-repost-of etc. are rendered in an aside
before the .e-content div, not inside it. Scoping to .h-entry .e-content
caused these microformat links to be missed entirely.

Also bump reset-stale migration to v3 so posts already marked sent with
zero results (like /replies/88feb/) are retried with the corrected scope.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 09:40:48 +01:00
Sven
3ca920089b fix: improve microsub feed discovery via <link rel="alternate"> tags
When a bookmarked URL is an HTML page whose feed is not at a common
path (/feed, /rss.xml etc.), fetchAndParseFeed would throw and store
no items in microsub_items.  Sites like econsoc.mpifg.de or signal.org
post pages advertise their feed via a standard
  <link rel="alternate" type="application/rss+xml" href="...">
element, which discoverFeeds() already parses but was never called
from the fetch/parse pipeline.

Now, before probing common paths, fetchAndParseFeed calls discoverFeeds()
on the fetched HTML and uses any typed RSS/Atom/JSONFeed link it finds.
Common-path probing remains as the final fallback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 09:27:22 +01:00
Sven
0dc71d1922 fix: pre-fill reference URL when creating post from /news entry
share-post.js opens /posts/create?type=like&url=<link>&name=<title>
but postData.create only reads request.body, ignoring the query params.

Patch postData.create: when properties is empty and ?url= is present,
seed properties with the correct field name per post type:
  like     → like-of
  bookmark → bookmark-of  (also seeds name from ?name=)
  reply    → in-reply-to
  repost   → repost-of

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 07:23:36 +01:00
Sven
1d28df8d04 fix: post edit 404 — query micropub source by _id not paginated scan
getPostProperties queries ?q=source (no filter, default limit=40) then
scans items for a uid match. Posts outside the 40 most recent return
undefined → IndiekitError.notFound (404).

Fix:
- Patch micropub query controller: when ?q=source&uid=<objectId> is
  present, findOne({ _id: getObjectId(uid) }) directly and return
  { items: [mf2] } so it is compatible with getPostProperties.
- Patch getPostProperties to append uid= to the micropub source URL,
  so any post can be fetched regardless of recency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 20:45:12 +01:00
Sven
3979e12e8b fix: use express.Router() not require() in ESM patch
The blogroll index.js is an ES module so require() is not defined.
Replace `const { Router } = require("express")` with `express.Router()`
which is already in scope from the module's own top-level import.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 19:20:53 +01:00
Sven
4cde4a28ba fix: transform /rssapi response shape to match /news static page
The /news page template uses item.link, item.feedId, item.sourceUrl,
and feedsRes.feeds — but the blogroll API returns item.url, item.blog.id,
item.blog.siteUrl, and {items:[...]} respectively.

Add a response-transformer middleware to the /rssapi alias router that:
- maps url -> link on each item
- maps blog.id -> feedId, blog.siteUrl -> sourceUrl on each item
- renames items -> feeds for the /api/feeds endpoint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 19:16:56 +01:00
Sven
c4299b73b1 fix: dual-mount blogroll at /blogrollapi and /rssapi
/blogroll static page hardcodes /blogrollapi/api/* calls.
/news static page hardcodes /rssapi/api/* calls.
Both must work simultaneously.

- Revert mountPath to /blogrollapi (restores /blogroll page)
- Patch init() to also register publicRouter at /rssapi via a
  thin Indiekit.addEndpoint() alias (fixes /news page)
- /api/feeds alias retained for /news page (maps to listBlogs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 19:09:48 +01:00
Sven
64e5f99526 fix: mount blogroll at /rssapi and add /api/feeds alias
The /news/ page fetches /rssapi/api/items, /rssapi/api/feeds and
/rssapi/api/status. The blogroll endpoint was mounted at /blogrollapi,
so all three requests returned a 404 HTML page — causing the
"Unexpected token '<'" JSON parse error.

- Change blogroll mountPath from /blogrollapi to /rssapi
- Add patch-endpoint-blogroll-feeds-alias.mjs: injects a /api/feeds
  route alias pointing to listBlogs (page expects /feeds, endpoint
  only had /blogs)
- Wire new patch into postinstall and serve scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 18:54:24 +01:00
svemagie
bf1991d82c fix: scope webmention link extraction to post content only
Adds patch-webmention-sender-content-scope.mjs to restrict extractLinks()
to .h-entry .e-content / .e-content / article / main — preventing sidebar,
nav, and footer links from the live-fetched full page from being included.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 08:34:05 +01:00
svemagie
5df7314f8d chore: integrate AP patches into fork — remove 3 scripts, trim federation-unlisted-guards
Deleted scripts (logic now built into the fork):
- patch-endpoint-activitypub-docloader-loglevel.mjs
- patch-endpoint-activitypub-migrate-alias-clear.mjs
- patch-endpoint-activitypub-like-boost-methods.mjs

Trimmed patch-federation-unlisted-guards.mjs to only cover
endpoint-syndicate (separate package). AP unlisted guards are
now in the fork's federation-setup.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 08:07:36 +01:00
svemagie
630edf2b77 fix: drop compose.js docloader patch — now in fork
createPublicationAwareDocumentLoader and rawDocumentLoader wrapping
are built into the fork's compose.js; the patch was re-injecting the
function and causing a duplicate-declaration SyntaxError at startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 07:56:41 +01:00
svemagie
d9566919fc chore: remove like/repost content-negotiation patch (now in fork)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 07:51:45 +01:00
svemagie
d35e7b7b28 fix: disable Mastodon external like/repost status posts
Serve like/repost posts as Note objects for AP content negotiation.
Returning a bare Like/Announce activity broke Mastodon's
authorize_interaction because it expects a content object (Note/Article).
Now like posts are served as ❤️ Note and reposts as 🔁 Note.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 14:59:04 +01:00
svemagie
71ee8672f4 fix: never auto-check Mastodon syndicator for like/repost compose actions
The site is its own fediverse actor (@svemagie@blog.giersig.eu). Likes and
reposts send native AP Like/Announce activities directly — troet.cafe has
no role in site-level interaction actions. Mastodon is now only auto-selected
in the compose form for fediverse *replies*, not likes or reposts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 14:03:37 +01:00
svemagie
ad0492ef64 feat: dispatch native AP Like/Announce from fediverse identity on like/repost
- Add likePost() and boostPost() programmatic methods to the ActivityPub
  plugin, sending native Like and Announce activities as @svemagie@blog.giersig.eu
- Wire up AP dispatch in submitCompose (microsub reader) after a successful
  Micropub post creation — fire-and-forget, non-blocking
- Fix detectProtocol to recognise troet.cafe, hachyderm.io, infosec.exchange,
  chaos.social and other fediverse domains not in the original pattern list
- Fix Mastodon syndication target auto-selection to match by service.name
  ("mastodon") and by configured instance hostname, not just uid string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 13:44:09 +01:00
svemagie
3f367610f5 fix: force one-time ai: block resync for posts with stale files
The v3 patch bug allowed Micropub to update MongoDB with aiTextLevel/
aiCodeLevel values but write post files without the ai: frontmatter
block (supportsAiDisclosure was always false). Re-saving with the same
values correctly returned "no properties changed" — but the file on disk
remained stale.

New patch (patch-micropub-ai-block-resync.mjs) adds _aiBlockVersion to
each post document in MongoDB. On update, if a post has AI fields but
_aiBlockVersion != "v4", the no-change check is bypassed and the file
is force-rewritten exactly once. Subsequent no-change saves behave
normally.

Also adds AI transparency section to README documenting the full
implementation, patch chain, v4 root cause, and re-save instructions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 09:03:22 +01:00
svemagie
5f4d8ca5e8 fix: detect article/note post type via permalink for AI frontmatter
Indiekit's getPostTemplateProperties() explicitly removes the post-type
property before passing JF2 to postTemplate(). The v3 patch relied on
post-type to set supportsAiDisclosure, which was therefore always false —
causing the ai: frontmatter block to never be written regardless of what
was selected in the backend form.

v4 patch falls back to permalink URL pattern (/articles/, /notes/) to
correctly detect the post type when post-type is absent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 08:11:28 +01:00
svemagie
0862a266e6 fix: always fetch live page for webmention link extraction
The core bug: stored post content (post.properties.content.html) is only
the post body text. Template-rendered microformat links (u-in-reply-to,
u-like-of, u-bookmark-of, u-repost-of) live in the 11ty HTML output, not
in MongoDB. So replies, likes, bookmarks and reposts never had their target
URLs extracted — webmentions were silently skipped.

- patch-webmention-sender-livefetch: always fetch the live page; fall back
  to stored content only if the page is unavailable; skip (don't mark sent)
  when no content is available so the next poll retries it. Handles both
  original upstream code and the older retry-patch variant.
- patch-webmention-sender-reset-stale: bump to v2 so posts incorrectly
  marked as sent today (empty results due to the content bug) get reset
  and retried on next deploy.
- Remove patch-webmention-sender-retry: superseded by livefetch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 20:41:30 +01:00
svemagie
881976f821 fix: prevent silent webmention-sent data loss on fetch failure
Two startup scripts:

patch-webmention-sender-retry: patches the package controller so that
when a post has no stored content and the live page fetch fails (page not
yet deployed), the post is skipped instead of being permanently marked as
webmention-sent with zero results. On the next poll the page will be live
and webmentions will be sent correctly.

patch-webmention-sender-reset-stale: one-time migration (guarded by a
migrations collection entry) that resets all posts already incorrectly
marked as webmention-sent with all-zero results, so they are retried on
the next poll.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 20:08:39 +01:00
svemagie
a8f7625897 feat(changelog): categorize perf, a11y, docs commits in endpoint-github changelog patch 2026-03-10 16:09:20 +01:00
svemagie
617a8f92b2 feat: patch changelog endpoint to categorize by commit message
Replaces repo-name-based category logic with commit-message parsing:
- feat: / feat(...): → Features
- fix: / fix(...): / Fix... → Fixes
- everything else → Other

Drops unused categories (Deployment, Theme, Endpoints, Syndicators,
Post Types, Presets). Patch runs on postinstall and serve.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:17:05 +01:00
svemagie
13d662cff6 Add comments endpoint locale patch and translations 2026-03-09 22:13:39 +01:00
svemagie
4eb7c07eb3 Fix homepage SITE_SOCIAL identity defaults parsing 2026-03-09 21:23:50 +01:00
svemagie
ae583a8e35 Require token for Funkwhale scope=me sync 2026-03-09 19:51:57 +01:00
svemagie
55682087a0 Scope Funkwhale history sync to authenticated user 2026-03-09 19:48:23 +01:00
svemagie
aeae212553 Harden Funkwhale sync against malformed records 2026-03-09 19:45:06 +01:00
svemagie
1365f696f0 fix(activitypub): handle publication host private DNS lookups 2026-03-09 19:39:04 +01:00
svemagie
d237dac539 Optimize AI metadata pipeline for editor and frontmatter 2026-03-09 18:50:24 +01:00
svemagie
8375e62ad1 Scope AI disclosure to article/note and extend note AI fields 2026-03-09 18:38:02 +01:00
svemagie
ad17b697c7 Allow clearing Mastodon migration alias 2026-03-09 18:15:46 +01:00
svemagie
77e015ca13 Fix Funkwhale sync keying and legacy stats backfill 2026-03-09 18:07:57 +01:00
svemagie
5836d05d86 Add Podroll OPML file upload support 2026-03-09 16:13:27 +01:00
svemagie
c2a24b9162 Expose AI fields in /posts article editor 2026-03-09 15:24:00 +01:00
svemagie
f8aa318342 Harden listening endpoint sync, stats, and runtime guards 2026-03-09 15:04:49 +01:00
svemagie
37c22291ea Harden Funkwhale stats/trends fallback on listening page 2026-03-09 13:27:22 +01:00
svemagie
795ccace28 Patch Eleventy preset with default AI frontmatter 2026-03-09 13:12:49 +01:00
svemagie
66c77f604f fix(activitypub): suppress noisy fedify docloader 410 logs 2026-03-08 18:47:11 +01:00
svemagie
12de20913d fix(homepage): add de locale patch and robust contentDir for saves 2026-03-08 18:34:07 +01:00
svemagie
ddae0276c7 chore(patches): add mastodon disconnect flow and runtime guards 2026-03-08 17:33:47 +01:00
svemagie
1dedd763a6 feat(federation): block unlisted posts from syndication queue 2026-03-08 17:21:07 +01:00
svemagie
21262c31a9 feat(where): unlist OwnYourSwarm checkin notes by default 2026-03-08 16:04:18 +01:00
svemagie
d918e4cd52 Ensure ActivityPub outbox requests are RSA-signed 2026-03-08 11:23:54 +01:00
svemagie
9919b1decc Normalize ActivityPub profile URLs to fix WebFinger invalid URL 2026-03-08 11:16:17 +01:00