Commit Graph

158 Commits

Author SHA1 Message Date
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
d411a87161 chore: update lock file for activitypub 2.8.2 2026-03-13 18:59:18 +01:00
Sven
974d76b988 chore: switch activitypub to npm registry ^2.8.2
The github: source was pinned to 2.8.0 in the lock file but the repo
HEAD is now at 2.8.2, causing npm ci to fail. Switch to the versioned
npm package to keep the lock file stable.

Lock file needs regeneration: run npm install locally and commit
the updated package-lock.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 18:58: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
593665bcbd feat: switch activitypub endpoint to svemagie fork with DM support
Use svemagie/indiekit-endpoint-activitypub instead of the upstream npm
package to get direct message receive and native AP reply support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 06:33:35 +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
77dd85b507 fix: disable Mastodon external like/repost status posts
AP endpoint already sends native Like/Announce activities to fediverse
authors, making the "❤️ URL" / "🔁 URL" Mastodon status posts redundant.
Disabling syndicateExternalLikes + syndicateExternalReposts prevents the
"did not return a URL" error from the scope-limited Mastodon token.

Native same-instance favourites/reblogs still route through Mastodon
(requires write:favourites + write:statuses scope on the token).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 13:58:34 +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
270249bf83 feat: update blogroll + microsub forks with unified bookmark flow
Both plugins now implement the intended bookmark→feed pipeline:

Microsub:
- tag → find/create channel (no more fallback-only to "Bookmarks")
- stores micropubPostUrl on feed for update/delete tracking
- calls notifyBlogroll() after bookmark-import (blogroll via microsub, not independently)
- moves feed when tag changes (delete from old channel, create in new)
- handles micropub update action: detects tag change or bookmark-of removal

Blogroll:
- skips direct bookmark import when microsub is installed (avoids duplicates)
- acts as fallback importer when microsub is absent
- updates category in-place when existing entry's tag changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 09:35:46 +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
cb1418bae5 feat: replace @rmdes/indiekit-endpoint-microsub with svemagie fork
Switches to github:svemagie/indiekit-endpoint-microsub#bookmarks-import
which adds a contentNegotiationRoutes hook to auto-follow bookmarked
URLs as Microsub feed subscriptions when a bookmark-of post is created.

Mirrors the same pattern already in place for the blogroll fork.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 08:36:55 +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
6a2c38d798 fix: replace startup sleep with readiness check, clean up stale env/config
- start.example.sh: replace fixed sleep 30 with /status poll loop (up to 2min)
  so the webmention poller waits exactly until indiekit is ready, not longer
- indiekit.config.mjs: remove redundant webmentionIoMountPath variable and
  mountPath from webmention-io config (package default /webmentions is correct)
- .env.example: remove all stale proxy and unused WEBMENTION_SENDER_* vars
  (HOST, PORT, ENDPOINT, READY_TIMEOUT, STOP_TIMEOUT, AUTO_POLL) that were
  never read by start.example.sh; keep only WEBMENTION_SENDER_POLL_INTERVAL

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 20:30:55 +01:00
svemagie
c251650371 fix: add 30s startup delay before first webmention poll
The poller fired immediately after indiekit was backgrounded, before
the HTTP server finished initializing — causing curl error 52 (empty
reply). A 30s initial sleep gives indiekit time to become ready.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 20:12:11 +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
cdc437ea8c fix: remove webmentions-proxy, fix sender timing, explicit mountPath
- Remove @rmdes/indiekit-endpoint-webmentions-proxy (redundant with
  webmention-io which already provides a public JSON API from MongoDB)
- Remove proxy-related variables (webmentionsProxyMountPath, cacheTtl)
- Add explicit mountPath to webmention-io config to avoid future surprises
- Increase WEBMENTION_SENDER_POLL_INTERVAL to 600s so deploys complete
  before the poller fires, preventing silent data loss where posts with
  no stored content get marked webmention-sent before the page is live

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 20:00:12 +01:00
svemagie
9c90498cbc refactor: simplify start.sh to standard webmention-sender pattern
Remove complex PID tracking, readiness probe, graceful-shutdown and
daemon-parent machinery. Replace with the minimal polling loop from
the @rmdes/indiekit-endpoint-webmention-sender README: generate JWT,
POST to 127.0.0.1 (local), sleep 300s. Also drop the related tuning
vars from .env (WEBMENTION_SENDER_READY_TIMEOUT, STOP_TIMEOUT,
INDIEKIT_STOP_TIMEOUT, KILL_DAEMON_PARENT_ON_SHUTDOWN).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 19:47:46 +01:00
svemagie
fefd91dcf5 chore: commit all changes for deployment 2026-03-11 12:23:27 +01:00
svemagie
548450a12a chore: update webmentionSenderMountPath config to default /webmention-sender 2026-03-11 12:23:12 +01:00
svemagie
5036f9c7fd chore: empty commit for deployment sync 2026-03-11 12:01:20 +01:00
svemagie
be67f61792 feat: update webmention sender endpoint path to /webmentions-sender 2026-03-11 11:55:47 +01:00
svemagie
9c771df5d0 fix: wait for HTTP 200 and increase poller startup timeout to 180s 2026-03-11 11:50:12 +01:00
svemagie
720e23b771 fix: avoid double https:// in webmention sender endpoint construction 2026-03-11 11:46:06 +01:00
svemagie
b86a759b81 chore: update .env.example for production webmention sender host 2026-03-11 11:43:54 +01:00
svemagie
6520c13b8a fix: config for endpoint-auth 2026-03-10 20:57:19 +01:00
svemagie
c591b6fe37 feat(auth): add IndieAuth endpoint plugin and config for /auth 2026-03-10 20:47:20 +01:00
svemagie
7f830aa31f feat(blogroll): use bookmark post category in blogroll entry
Passes the micropub post's category (first tag) through to the blogroll
blog entry. Falls back to "bookmarks" when no category is set.

Handles all micropub body formats (form-encoded, JF2, MF2 JSON).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 19:54:20 +01:00
svemagie
c1a4eb2404 feat(blogroll): auto-import bookmark sites into blogroll
When a micropub bookmark post is created, the bookmarked site's
origin URL is discovered for RSS/Atom feeds and added to the blogroll
under category "bookmarks".

Integration is self-contained in the blogroll plugin via
contentNegotiationRoutes — no micropub patches needed.

Source: github:svemagie/indiekit-endpoint-blogroll#bookmark-import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 19:31:15 +01:00
svemagie
ae936b2e1a fix(deploy): explicitly re-run all patches after npm ci
Adds an explicit patch loop in the deploy step to ensure all
scripts/patch-*.mjs run even if npm ci postinstall was skipped.
This guarantees changelog categorization and other patches are
always applied on the server regardless of npm install mode.
2026-03-10 17:33:41 +01:00
svemagie
91c3778fdc fix: apply all patch scripts automatically on startup 2026-03-10 16:30:37 +01:00
svemagie
8dbe966a4c fix(posts): ensure post type fields and normalize AI taxonomy
Only display post types with defined fields; backend filters out types lacking fields.
Require fields for backend visibility, not just post path/url.
Add support for custom post form fields by requiring matching templates; missing templates are ignored.
Normalize legacy AI taxonomy values (3 → 2) in frontmatter.
Strip empty AI fields before jf2ToMf2 to avoid empty-string metadata in Micropub payloads.
2026-03-10 16:09:20 +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
771e2302b7 fix: updates 2026-03-10 15:44:25 +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
e98dba777c feat: add @indiekit/endpoint-micropub to plugins
Wires up the already-installed Micropub endpoint (aliased to
@rmdes/indiekit-endpoint-micropub) at /micropub.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:17:04 +01:00
svemagie
1be5baa60f feat: install and configure @rmdes/indiekit-endpoint-microsub
Adds Microsub social reader endpoint at /microsub, enabling feed
subscription and management via the Microsub protocol.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:17:04 +01:00
svemagie
038b94a606 feat: install and configure @rmdes/indiekit-endpoint-blogroll
Mounts blogroll API at /blogrollapi — syncs RSS/Atom feeds hourly,
caches 50 items per blog, deletes items after 7 days.
Public endpoints: /blogrollapi/api/blogs, /items, /categories, /status, /opml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 10:17:03 +01:00
svemagie
ab2c248344 feat: Add @rmdes/indiekit-syndicator-indienews plugin and config block 2026-03-10 10:17:02 +01:00
svemagie
bc83e2bbb7 feat: Add @rmdes/indiekit-syndicator-indienews plugin and config block 2026-03-10 10:17:01 +01:00