diff --git a/lib/controllers/compose.js b/lib/controllers/compose.js index 1c1ec28..4655588 100644 --- a/lib/controllers/compose.js +++ b/lib/controllers/compose.js @@ -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, + ); } } diff --git a/package.json b/package.json index 61ad296..1800bff 100644 --- a/package.json +++ b/package.json @@ -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",