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}`); 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 { try {
const ap_notifications = collections.ap_notifications; const ap_notifications = collections.ap_notifications;
if (ap_notifications) { if (ap_notifications) {
const hostname = new URL(publicationUrl).hostname;
const profile = await collections.ap_profile.findOne({});
await addNotification({ ap_notifications }, { await addNotification({ ap_notifications }, {
uid: noteId.href, uid: noteId.href,
url: noteId.href, url: noteId.href,
@@ -303,23 +305,54 @@ router.post("/api/v1/statuses", async (req, res, next) => {
actorHandle: `@${handle}@${hostname}`, actorHandle: `@${handle}@${hostname}`,
inReplyTo: inReplyTo || null, inReplyTo: inReplyTo || null,
content: { text: (statusText || "").trim(), html: (statusText || "").trim() }, content: { text: (statusText || "").trim(), html: (statusText || "").trim() },
published: new Date().toISOString(), published: now,
createdAt: new Date().toISOString(), createdAt: now,
}); });
} }
} catch (storeError) { } 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 // Store in ap_timeline with visibility=direct so serializeStatus can
return res.json({ // produce a full Mastodon status object. Home/public timelines already
id: noteId.href, // exclude direct-visibility items (visibility: { $nin: ["direct"] }).
created_at: new Date().toISOString(), const timelineItem = {
content: (statusText || "").trim(), uid: noteId.href,
visibility: "direct",
url: 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 ─────────────────────────────────────────────────────────── // ── End DM path ───────────────────────────────────────────────────────────