fix: include reply author in cc and log delivery failures

Quick replies only sent to followers, never directly to the
replied-to author's server. The author was also missing from
the Note's cc field, so Mastodon couldn't thread or notify.

Now resolves the author before constructing the Note, includes
them in ccs, sends directly to their inbox, and logs failures
instead of silently swallowing them.
This commit is contained in:
Ricardo
2026-02-22 21:21:55 +01:00
parent 4a553da94e
commit 77aad65947
2 changed files with 61 additions and 40 deletions
+60 -39
View File
@@ -204,32 +204,10 @@ export function submitComposeController(mountPath, plugin) {
"https://www.w3.org/ns/activitystreams#Public",
);
const followersUri = ctx.getFollowersUri(handle);
const note = new Note({
id: new URL(noteId),
attribution: actorUri,
content: content.trim(),
replyTarget: inReplyTo ? new URL(inReplyTo) : undefined,
published: Temporal.Now.instant(),
to: publicAddress,
cc: followersUri,
});
const create = new Create({
id: new URL(`${noteId}#activity`),
actor: actorUri,
object: note,
to: publicAddress,
cc: followersUri,
});
// Send to followers
await ctx.sendActivity({ identifier: handle }, "followers", create, {
preferSharedInbox: true,
syncCollection: true,
orderingKey: noteId,
});
// If replying, also send to the original author
// Resolve the original author BEFORE constructing the Note,
// so we can include them in cc (required for threading/notification)
let recipient = null;
if (inReplyTo) {
try {
const documentLoader = await ctx.getDocumentLoader({
@@ -246,21 +224,64 @@ export function submitComposeController(mountPath, plugin) {
const author = await remoteObject.getAttributedTo({
documentLoader,
});
const recipient = Array.isArray(author)
? author[0]
: author;
if (recipient) {
await ctx.sendActivity(
{ identifier: handle },
recipient,
create,
{ orderingKey: noteId },
);
}
recipient = Array.isArray(author) ? author[0] : author;
}
} catch {
// Non-critical — followers still got it
} catch (error) {
console.warn(
`[ActivityPub] lookupObject failed for ${inReplyTo} (quick reply):`,
error.message,
);
}
}
// Build cc list: always include followers, add original author for replies
const ccList = [followersUri];
if (recipient?.id) {
ccList.push(recipient.id);
}
const note = new Note({
id: new URL(noteId),
attribution: actorUri,
content: content.trim(),
replyTarget: inReplyTo ? new URL(inReplyTo) : undefined,
published: Temporal.Now.instant(),
to: publicAddress,
ccs: ccList,
});
const create = new Create({
id: new URL(`${noteId}#activity`),
actor: actorUri,
object: note,
to: publicAddress,
ccs: ccList,
});
// Send to followers
await ctx.sendActivity({ identifier: handle }, "followers", create, {
preferSharedInbox: true,
syncCollection: true,
orderingKey: noteId,
});
// Also send directly to the original author's inbox
if (recipient) {
try {
await ctx.sendActivity(
{ identifier: handle },
recipient,
create,
{ orderingKey: noteId },
);
console.info(
`[ActivityPub] Sent quick reply directly to ${recipient.id?.href || "author"}`,
);
} catch (error) {
console.warn(
`[ActivityPub] Direct delivery to author failed (quick reply):`,
error.message,
);
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@rmdes/indiekit-endpoint-activitypub",
"version": "2.0.6",
"version": "2.0.7",
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
"keywords": [
"indiekit",