fix(mastodon-api): DM response "no data" — return full serialized status

The minimal bare JSON returned for visibility=direct DMs caused clients
(Phanpy, Elk) to show "no data" — they expect a full Mastodon Status
entity. Fix: build a proper ap_timeline document, store it with
visibility=direct (home/public timelines already exclude direct items),
and serialize it via serializeStatus() before returning. Also store the
DM in ap_notifications for the thread view as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
svemagie
2026-03-22 20:57:39 +01:00
parent 99964e9c3c
commit 481603391e
+46 -13
View File
@@ -284,12 +284,14 @@ router.post("/api/v1/statuses", async (req, res, next) => {
});
console.info(`[Mastodon API] Sent DM to ${recipientActorUrl}`);
// Store in ap_notifications so it appears in the DM thread view
const now = new Date().toISOString();
const hostname = new URL(publicationUrl).hostname;
const profile = await collections.ap_profile.findOne({});
// Store in ap_notifications for the DM thread view
try {
const ap_notifications = collections.ap_notifications;
if (ap_notifications) {
const hostname = new URL(publicationUrl).hostname;
const profile = await collections.ap_profile.findOne({});
await addNotification({ ap_notifications }, {
uid: noteId.href,
url: noteId.href,
@@ -303,23 +305,54 @@ router.post("/api/v1/statuses", async (req, res, next) => {
actorHandle: `@${handle}@${hostname}`,
inReplyTo: inReplyTo || null,
content: { text: (statusText || "").trim(), html: (statusText || "").trim() },
published: new Date().toISOString(),
createdAt: new Date().toISOString(),
published: now,
createdAt: now,
});
}
} catch (storeError) {
console.warn("[Mastodon API] Failed to store outbound DM:", storeError.message);
console.warn("[Mastodon API] Failed to store outbound DM in notifications:", storeError.message);
}
// Return a minimal status object so the client doesn't error
return res.json({
id: noteId.href,
created_at: new Date().toISOString(),
content: (statusText || "").trim(),
visibility: "direct",
// Store in ap_timeline with visibility=direct so serializeStatus can
// produce a full Mastodon status object. Home/public timelines already
// exclude direct-visibility items (visibility: { $nin: ["direct"] }).
const timelineItem = {
uid: noteId.href,
url: noteId.href,
account: { acct: handle },
type: "note",
visibility: "direct",
content: {
text: (statusText || "").trim(),
html: (statusText || "").trim(),
},
author: {
name: profile?.name || handle,
url: actorUri.href,
photo: profile?.icon || "",
handle: `@${handle}@${hostname}`,
},
published: now,
createdAt: now,
inReplyTo: inReplyTo || null,
category: [],
counts: { likes: 0, boosts: 0, replies: 0 },
};
try {
await addTimelineItem(collections.ap_timeline, timelineItem);
} catch (storeError) {
console.warn("[Mastodon API] Failed to store outbound DM in timeline:", storeError.message);
}
// Return a full serialized status so clients (Phanpy, Elk) can render it
const status = serializeStatus(timelineItem, {
baseUrl,
favouritedIds: new Set(),
rebloggedIds: new Set(),
bookmarkedIds: new Set(),
pinnedIds: new Set(),
});
return res.json(status);
}
// ── End DM path ───────────────────────────────────────────────────────────