Pulls the merged upstream feat/mastodon-client-api branch into our fork
(svemagie/indiekit-endpoint-activitypub@f029c31). The fork now tracks
upstream v3.6.8 while retaining our custom additions.
What's new in the installed package:
- lib/mastodon/ — full Mastodon Client API compatibility layer (entities,
middleware, routes, helpers, backfill-timeline, router)
- 13 additional locale files (es, fr, de, hi, id, it, nl, pt, sr, sv, zh, …)
- signatureTimeWindow and allowPrivateAddress built into federation-setup.js
(patch-ap-allow-private-address now cleanly detects "already up to date")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On fresh installs where the old wrong patch was never applied, Like was
absent from the @fedify/fedify/vocab imports, causing a ReferenceError
at startup. The dispatcher patch now adds Like to the import block if
missing, making it self-contained and install-order independent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the semantically incorrect fake-Note approach with strict AP protocol compliance:
- patch-ap-like-note-dispatcher: rewritten to revert the fake-Note block
- patch-ap-like-activity-id: adds canonical id URI to Like activities (AP §6.2.1)
- patch-ap-like-activity-dispatcher: registers setObjectDispatcher(Like, ...) so
/activitypub/activities/like/{id} is dereferenceable (AP §3.1)
- patch-ap-url-lookup-api-like: /api/ap-url now returns the likeOf URL for AP-likes
so the "Also on: Fediverse" widget's authorize_interaction flow opens the
original Mastodon post on the remote instance
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Micropub's replaceEntries() stores single-value arrays as plain strings
(JF2 normalization). Spreading a string into [...str] gives individual
characters, so hasSyndicationUrl() never matches existing syndication URLs
and alreadySyndicated is always false — causing re-syndication on every
webhook trigger.
Fix: use [].concat() which safely handles both string and array values.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: blog.giersig.eu DNS resolves internally to 10.100.0.10 (the
indiekit admin nginx), which returns the login page for post URLs of
certain types (notes, photos, replies). Live page fetching is inherently
unreliable in this split-DNS / jailed setup.
The fix: indiekit already stores all microformat target URLs in MongoDB
(in-reply-to, like-of, bookmark-of, repost-of) and content.html has
inline links. We can build a synthetic h-entry HTML snippet directly
from post.properties — no network fetch required for the source post.
Bumps livefetch patch to v5:
- Replace live page fetch with synthetic HTML built from post.properties
- Handles string values, mf2 objects ({properties.url[0]}), and plain
value strings for each microformat property
- Simplifies patch script: single full-block replacement handles all
prior versions (v1–v4) via marker detection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: INTERNAL_FETCH_URL (10.100.0.10) points to the nginx reverse
proxy in front of indiekit's admin interface. Post URLs like /bookmarks/…
require authentication there, so the fetch returned the login page
("Anmelden - Indiekit") which has no .h-entry.
The blog HTML is served by an external host (GitHub Pages), reachable
from the jail over the public URL. INTERNAL_FETCH_URL should only be
used for indiekit API calls, not for fetching blog post pages.
Bumps livefetch patch to v4:
- Remove INTERNAL_FETCH_URL rewrite for live page fetches
- Fetch from postUrl (public URL) directly by default
- Add WEBMENTION_LIVEFETCH_URL env var as opt-in override for setups
where a local static server can serve blog pages faster
- Add v3→v4 in-place upgrade logic to the patch script
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without a Host header, nginx routes internal fetches to the wrong vhost
(sees the internal IP as Host), returning a page with no .h-entry and
causing all posts to retry indefinitely.
Bumps livefetch patch to v3:
- Sends `Host: blog.giersig.eu` when fetching via internal URL so nginx
routes to the correct virtual host
- Logs the actual fetchUrl on every internal fetch
- Logs the first 200 chars of the response body when h-entry check fails
so the root cause (wrong vhost, indiekit page, etc.) is visible
- Adds v2→v3 in-place upgrade logic to the patch script
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove patch-preset-eleventy-ai-frontmatter: upstream now writes AI
frontmatter natively using hyphenated keys (ai-text-level etc.)
- Remove patch-endpoint-posts-ai-cleanup: upstream beta.44 natively
removes empty ai-text-level/ai-code-level/ai-tools/ai-description fields
- Remove patch-endpoint-posts-ai-fields: upstream beta.44 has AI form UI
inline in post-form.njk; our separate templates would have duplicated fields
- Remove patch-micropub-ai-block-resync: one-time stale-block migration,
no longer relevant
- Remove patch-endpoint-posts-prefill-url: upstream beta.44 has native
prefill from query params; our patch would have conflicted
- Remove patch-endpoint-posts-search-tags: upstream beta.44 has native
search/filter/sort UI; patch already detected this and was a no-op
- Bump @rmdes/indiekit-endpoint-posts beta.25→beta.44,
override beta.41→beta.44
- Update indiekit.config.mjs: remove camelCase ai field names from
all postTypes.fields (ai-* fields now rendered inline by upstream)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reposts with a body (commentary) were silently broken in two ways:
1. jf2ToAS2Activity() always emitted a bare Announce pointing at the
external URL (e.g. fromjason.xyz). That URL doesn't serve AP JSON,
so Mastodon couldn't fetch the object and dropped the activity from
followers' timelines — the post only appeared when explicitly searched.
2. jf2ToActivityStreams() (content negotiation / search) hard-coded the
Note content to just '🔁 <url>', completely ignoring properties.content.
Fix via patch-ap-repost-commentary.mjs (4 targeted replacements):
- jf2ToAS2Activity(): skip the Announce early-return when commentary is
present and fall through to the existing Create(Note) path instead.
Pure reposts (no body) keep the Announce behaviour unchanged.
- jf2ToAS2Activity() content block: add a repost branch that formats
the Note as '<commentary><br><br>🔁 <url>' (mirrors bookmark/like).
- jf2ToActivityStreams(): extract commentary and prepend it to the Note
content when present.
Patch registered in both postinstall and serve chains.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds patch-webmention-sender-hentry-syntax.mjs to fix a typo in
@rmdes/indiekit-endpoint-webmention-sender@1.0.8 that prevents the
module from loading:
_html.includes("h-entry"") → _html.includes("h-entry")
The extra closing quote causes a SyntaxError at startup, which means
the webmention sender never runs and the background sync never starts.
Patch runs before the other webmention-sender patches in both
postinstall and serve so the file is valid JS before further transforms.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- retry: silently skip when livefetch:v2 marker is present instead of
logging a misleading "target snippet not found (package updated?)"
warning on every startup
- livefetch: match `h-entry"` or `h-entry ` instead of bare `h-entry`
to avoid false positives from body text containing the string
- reset-stale: update comment to reference livefetch v2 (not retry)
as the patch that prevents recurrence
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: when the livefetch got a 200 response that was actually an
error page (nginx 502 HTML, login redirect, error template), it had no
.h-entry so extractLinks found 0 links — permanently marking the post
as sent with empty results.
Changes:
- livefetch v2: check fetched HTML contains "h-entry" before using it;
if missing, skip and retry next poll instead of falling back to stored
content (which also lacks microformat links for likes/reposts/bookmarks)
- livefetch v2: can detect and upgrade from v1 patch in-place
- reset-stale v9: also matches the v1.0.6+ detail format (empty arrays)
to catch posts stuck by the error-page bug
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pulls AP like detection — likes of Mastodon/AP URLs are now sent as
proper Like activities; likes of regular URLs remain bookmark-style.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update README: likes delivered as bookmarks, announces use upstream
addressing, OG images added to AP objects
- Update fork reference to 45f8ba9
- Remove unused patch-ap-like-announce-addressing.mjs (now in fork)
- Update package-lock.json for new fork commit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Like and Announce activities were missing the followers collection in
their to/cc addressing. Mastodon shared inboxes silently drop activities
without cc:followers, so likes and reposts were delivered but never
appeared on remote instances.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update sync flow diagram to show store.createFile step
- Note draft status and "Title - Author" content format
- Add reset route to routes table
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switch @rmdes/indiekit-endpoint-youtube to forked repo with OAuth 2.0
liked-videos sync. Add OAuth client config and likes sync settings.
Also document outgoing webmentions architecture in README.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
OwnYourSwarm sends visibility:"private" which Indiekit doesn't recognise,
causing checkin posts to slip through syndication and AP federation guards.
Remove the !hasVisibility condition so checkins are always forced to
"unlisted" regardless of what OwnYourSwarm sends.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The signatureTimeWindow patch was deleted in e52e98c5c (assumed fixed
in fork), but the lockfile still pins the fork to v2.10.1 which lacks
it. This broke the patch-ap-allow-private-address patch chain: it
expected signatureTimeWindow in its OLD_SNIPPET, never matched, and
silently skipped — leaving the server without both signatureTimeWindow
AND allowPrivateAddress. Without allowPrivateAddress, Fedify's SSRF
guard blocks own-site URL resolution (blog.giersig.eu → 10.100.0.10),
breaking federation delivery.
- Fix patch-ap-allow-private-address to handle fresh v2.10.1 (adds
both signatureTimeWindow and allowPrivateAddress in one step)
- Restore patch-ap-object-url-trailing-slash (also lost in e52e98c5c)
- Add both patches to postinstall and serve scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The static Eleventy CV page at /cv was being served by nginx try_files
before the request could fall through to the @indiekit proxy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The inbox View-skip patch buffers the request stream but was not saving
the original bytes as req._rawBody. Without them, fromExpressRequest
falls back to JSON.stringify(req.body) which can alter byte layout,
breaking the Digest check in Fedify's HTTP Signature verification.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The root /status responds before MongoDB collections and plugin routes
are fully initialised, causing 502 on the first poll. Now probes the
plugin's own /api/status which only responds once everything is ready.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The /status endpoint becomes available before all plugin routes are
fully mounted, causing a 502 on the first webmention-sender POST.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Posts were marked as webmention-sent with 0/0/0 results during the
SyntaxError period. The v7 migration already ran before those posts
were processed, so bump to v8 to reset them for retry now that the
livefetch patch is fixed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The nested template literal in the patch output had over-escaped regex
and backticks (\\\\/ instead of \\/, \\\` instead of \`), producing
invalid JS that caused a SyntaxError at runtime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The livefetch patch was fetching postUrl (the public HTTPS URL) which
fails behind the nginx jail. Now rewrites to INTERNAL_FETCH_URL so the
live page fetch goes through nginx on the internal network. Without
this, likes/reposts/bookmarks fall back to stored content which has no
microformat links, resulting in 0 webmentions sent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The retry and livefetch patches both target the same upstream code block.
If retry runs first (current postinstall order), it transforms the code
into a variant that livefetch couldn't match — silently losing the
"always fetch live page" behavior. Now livefetch detects both the
original upstream code and the retry-patched variant.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Retry behavior is already covered by the livefetch patch, but keeping
this patch ensures the skip-on-failure guard applies even if livefetch
is removed or the upstream code changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The public URL (blog.giersig.eu) is reachable from inside the jail, so
the INTERNAL_FETCH_URL rewriting approach was unnecessary and caused 400/502
errors because 10.100.0.10 does not serve the static blog pages.
Simplify the livefetch patch to fetch postUrl directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When rewriting the public URL to INTERNAL_FETCH_URL for jailed setups,
the Host header becomes the internal IP (e.g. 10.100.0.10) instead of
the public hostname (blog.giersig.eu). nginx uses the Host header for
virtual host routing and returns 400 without the correct value.
Fix: extract the host from the original public postUrl and set it as the
Host header on the internal fetch, so nginx routes to the correct vhost.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The livefetch patch used `process.env.PUBLICATION_URL || process.env.SITE_URL`
to determine the public base URL for rewriting to INTERNAL_FETCH_URL. Neither
env var is set — the publication URL comes from indiekit.config.mjs which has
a hardcoded fallback. Without a publicBase, URL rewriting never fired, the
public HTTPS URL was fetched directly, failed inside the FreeBSD jail, and fell
back to stored content.
For interaction posts (repost/bookmark/reply/like), stored content is just the
body text — the target URL (repostOf, bookmarkOf, inReplyTo, likeOf) is only
rendered in the live page via reply-context.njk. So 0 webmentions were sent.
Fix: add `|| siteUrl` as fallback. siteUrl is already in scope (derived from
`publication.me`) and is the correct value when env vars are absent.
Also bump reset-stale migration to v7 so interaction posts incorrectly marked
with 0 results (e.g. 342a5 repost) are reset and retried on next startup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Posts processed before the livefetch patch was applied were re-processed
without live page fetching, leaving them with empty results again.
Bumping the migration ID triggers another reset on next startup so the
poller retries them with livefetch active.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a post was processed but had no discoverable external links, the
expanded detail row rendered completely blank — result.details was
truthy ({}) so the 'noDetails' fallback never fired, but all three
arrays were empty so no tables rendered either.
Adds a patch script for the template that shows "No external links
discovered in this post." in that case, and wires it into both
postinstall and serve scripts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>