fix(mastodon-api): pass createdAt for follower/following accounts; URL-type AP lookup
- In accounts.js: all places that build an actor object from ap_followers or ap_following docs now include `createdAt: f.createdAt || undefined`. Previously the field was omitted, causing serializeAccount() to fall back to `new Date().toISOString()`, making every follower/following appear to have joined "just now" in the Mastodon client. Affected: GET /api/v1/accounts/:id/followers, /following, /lookup, and the resolveActorData() fallback used by GET /api/v1/accounts/:id. - In resolve-account.js: HTTP actor URLs are now passed to lookupWithSecurity() as a native URL object instead of a bare string, matching Fedify's preferred type. The acct:user@domain WebFinger path remains a string (new URL() would misparse the @ as a user-info separator under WHATWG rules). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,9 @@ export async function resolveRemoteAccount(acct, pluginOptions, baseUrl) {
|
|||||||
{ handle, publicationUrl },
|
{ handle, publicationUrl },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Determine lookup URI
|
// Determine lookup URI.
|
||||||
|
// acct:user@domain — kept as a string; Fedify resolves it via WebFinger.
|
||||||
|
// HTTP URLs — converted to URL objects for type-correct AP object fetch.
|
||||||
let actorUri;
|
let actorUri;
|
||||||
if (acct.includes("@")) {
|
if (acct.includes("@")) {
|
||||||
const parts = acct.replace(/^@/, "").split("@");
|
const parts = acct.replace(/^@/, "").split("@");
|
||||||
@@ -33,7 +35,7 @@ export async function resolveRemoteAccount(acct, pluginOptions, baseUrl) {
|
|||||||
if (!username || !domain) return null;
|
if (!username || !domain) return null;
|
||||||
actorUri = `acct:${username}@${domain}`;
|
actorUri = `acct:${username}@${domain}`;
|
||||||
} else if (acct.startsWith("http")) {
|
} else if (acct.startsWith("http")) {
|
||||||
actorUri = acct;
|
actorUri = new URL(acct);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ router.get("/api/v1/accounts/lookup", async (req, res, next) => {
|
|||||||
if (follower) {
|
if (follower) {
|
||||||
return res.json(
|
return res.json(
|
||||||
serializeAccount(
|
serializeAccount(
|
||||||
{ name: follower.name, url: follower.actorUrl, photo: follower.avatar, handle: follower.handle, bannerUrl: follower.banner || "" },
|
{ name: follower.name, url: follower.actorUrl, photo: follower.avatar, handle: follower.handle, bannerUrl: follower.banner || "", createdAt: follower.createdAt || undefined },
|
||||||
{ baseUrl },
|
{ baseUrl },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -128,7 +128,7 @@ router.get("/api/v1/accounts/lookup", async (req, res, next) => {
|
|||||||
if (following) {
|
if (following) {
|
||||||
return res.json(
|
return res.json(
|
||||||
serializeAccount(
|
serializeAccount(
|
||||||
{ name: following.name, url: following.actorUrl, photo: following.avatar, handle: following.handle },
|
{ name: following.name, url: following.actorUrl, photo: following.avatar, handle: following.handle, createdAt: following.createdAt || undefined },
|
||||||
{ baseUrl },
|
{ baseUrl },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -396,7 +396,7 @@ router.get("/api/v1/accounts/:id/followers", async (req, res, next) => {
|
|||||||
|
|
||||||
const accounts = followers.map((f) =>
|
const accounts = followers.map((f) =>
|
||||||
serializeAccount(
|
serializeAccount(
|
||||||
{ name: f.name, url: f.actorUrl, photo: f.avatar, handle: f.handle, bannerUrl: f.banner || "" },
|
{ name: f.name, url: f.actorUrl, photo: f.avatar, handle: f.handle, bannerUrl: f.banner || "", createdAt: f.createdAt || undefined },
|
||||||
{ baseUrl },
|
{ baseUrl },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -429,7 +429,7 @@ router.get("/api/v1/accounts/:id/following", async (req, res, next) => {
|
|||||||
|
|
||||||
const accounts = following.map((f) =>
|
const accounts = following.map((f) =>
|
||||||
serializeAccount(
|
serializeAccount(
|
||||||
{ name: f.name, url: f.actorUrl, photo: f.avatar, handle: f.handle, bannerUrl: f.banner || "" },
|
{ name: f.name, url: f.actorUrl, photo: f.avatar, handle: f.handle, bannerUrl: f.banner || "", createdAt: f.createdAt || undefined },
|
||||||
{ baseUrl },
|
{ baseUrl },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -786,6 +786,7 @@ async function resolveActorData(id, collections) {
|
|||||||
photo: f.avatar,
|
photo: f.avatar,
|
||||||
handle: f.handle,
|
handle: f.handle,
|
||||||
bannerUrl: f.banner || "",
|
bannerUrl: f.banner || "",
|
||||||
|
createdAt: f.createdAt || undefined,
|
||||||
},
|
},
|
||||||
actorUrl: f.actorUrl,
|
actorUrl: f.actorUrl,
|
||||||
};
|
};
|
||||||
@@ -803,6 +804,7 @@ async function resolveActorData(id, collections) {
|
|||||||
photo: f.avatar,
|
photo: f.avatar,
|
||||||
handle: f.handle,
|
handle: f.handle,
|
||||||
bannerUrl: f.banner || "",
|
bannerUrl: f.banner || "",
|
||||||
|
createdAt: f.createdAt || undefined,
|
||||||
},
|
},
|
||||||
actorUrl: f.actorUrl,
|
actorUrl: f.actorUrl,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user