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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
/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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>