diff --git a/lib/controllers/dashboard.js b/lib/controllers/dashboard.js index efe05e8..8c1b4d7 100644 --- a/lib/controllers/dashboard.js +++ b/lib/controllers/dashboard.js @@ -12,6 +12,9 @@ export function dashboardController(mountPath) { const followingCollection = application?.collections?.get("ap_following"); const activitiesCollection = application?.collections?.get("ap_activities"); + const featuredCollection = application?.collections?.get("ap_featured"); + const featuredTagsCollection = + application?.collections?.get("ap_featured_tags"); const followerCount = followersCollection ? await followersCollection.countDocuments() @@ -19,6 +22,12 @@ export function dashboardController(mountPath) { const followingCount = followingCollection ? await followingCollection.countDocuments() : 0; + const pinnedCount = featuredCollection + ? await featuredCollection.countDocuments() + : 0; + const tagCount = featuredTagsCollection + ? await featuredTagsCollection.countDocuments() + : 0; const recentActivities = activitiesCollection ? await activitiesCollection @@ -38,6 +47,8 @@ export function dashboardController(mountPath) { title: response.locals.__("activitypub.title"), followerCount, followingCount, + pinnedCount, + tagCount, recentActivities, refollowStatus, mountPath, diff --git a/lib/inbox-listeners.js b/lib/inbox-listeners.js index c2b6431..6ddade9 100644 --- a/lib/inbox-listeners.js +++ b/lib/inbox-listeners.js @@ -85,9 +85,14 @@ export function registerInboxListeners(inboxChain, options) { }); }) .on(Undo, async (ctx, undo) => { - const actorObj = await undo.getActor(); - const actorUrl = actorObj?.id?.href || ""; - const inner = await undo.getObject(); + const actorUrl = undo.actorId?.href || ""; + let inner; + try { + inner = await undo.getObject(); + } catch { + // Inner activity not dereferenceable — can't determine what was undone + return; + } if (inner instanceof Follow) { await collections.ap_followers.deleteOne({ actorUrl }); @@ -98,14 +103,14 @@ export function registerInboxListeners(inboxChain, options) { summary: `${actorUrl} unfollowed you`, }); } else if (inner instanceof Like) { - const objectId = (await inner.getObject())?.id?.href || ""; + const objectId = inner.objectId?.href || ""; await collections.ap_activities.deleteOne({ type: "Like", actorUrl, objectUrl: objectId, }); } else if (inner instanceof Announce) { - const objectId = (await inner.getObject())?.id?.href || ""; + const objectId = inner.objectId?.href || ""; await collections.ap_activities.deleteOne({ type: "Announce", actorUrl, @@ -194,18 +199,27 @@ export function registerInboxListeners(inboxChain, options) { } }) .on(Like, async (ctx, like) => { - const objectId = (await like.getObject())?.id?.href || ""; + // Use .objectId to get the URL without dereferencing the remote object. + // Calling .getObject() would trigger an HTTP fetch to the remote server, + // which fails with 404 when the server has Authorized Fetch (Secure Mode) + // enabled — causing pointless retries and log spam. + const objectId = like.objectId?.href || ""; // Only log likes of our own content const pubUrl = collections._publicationUrl; if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return; - const actorObj = await like.getActor(); - const actorUrl = actorObj?.id?.href || ""; - const actorName = - actorObj?.name?.toString() || - actorObj?.preferredUsername?.toString() || - actorUrl; + const actorUrl = like.actorId?.href || ""; + let actorName = actorUrl; + try { + const actorObj = await like.getActor(); + actorName = + actorObj?.name?.toString() || + actorObj?.preferredUsername?.toString() || + actorUrl; + } catch { + /* actor not dereferenceable — use URL */ + } await logActivity(collections, storeRawActivities, { direction: "inbound", @@ -217,18 +231,24 @@ export function registerInboxListeners(inboxChain, options) { }); }) .on(Announce, async (ctx, announce) => { - const objectId = (await announce.getObject())?.id?.href || ""; + // Use .objectId — no remote fetch needed (see Like handler comment) + const objectId = announce.objectId?.href || ""; // Only log boosts of our own content const pubUrl = collections._publicationUrl; if (!objectId || (pubUrl && !objectId.startsWith(pubUrl))) return; - const actorObj = await announce.getActor(); - const actorUrl = actorObj?.id?.href || ""; - const actorName = - actorObj?.name?.toString() || - actorObj?.preferredUsername?.toString() || - actorUrl; + const actorUrl = announce.actorId?.href || ""; + let actorName = actorUrl; + try { + const actorObj = await announce.getActor(); + actorName = + actorObj?.name?.toString() || + actorObj?.preferredUsername?.toString() || + actorUrl; + } catch { + /* actor not dereferenceable — use URL */ + } await logActivity(collections, storeRawActivities, { direction: "inbound", @@ -240,11 +260,23 @@ export function registerInboxListeners(inboxChain, options) { }); }) .on(Create, async (ctx, create) => { - const object = await create.getObject(); + let object; + try { + object = await create.getObject(); + } catch { + // Remote object not dereferenceable (Authorized Fetch, deleted, etc.) + return; + } if (!object) return; - const actorObj = await create.getActor(); - const actorUrl = actorObj?.id?.href || ""; + const actorUrl = create.actorId?.href || ""; + let actorObj; + try { + actorObj = await create.getActor(); + } catch { + // Actor not dereferenceable — use URL as fallback + actorObj = null; + } const actorName = actorObj?.name?.toString() || actorObj?.preferredUsername?.toString() || @@ -284,7 +316,7 @@ export function registerInboxListeners(inboxChain, options) { }); }) .on(Delete, async (ctx, del) => { - const objectId = (await del.getObject())?.id?.href || ""; + const objectId = del.objectId?.href || ""; if (objectId) { await collections.ap_activities.deleteMany({ objectUrl: objectId }); } diff --git a/package.json b/package.json index 1579ad9..b8b30b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rmdes/indiekit-endpoint-activitypub", - "version": "1.0.26", + "version": "1.0.27", "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.", "keywords": [ "indiekit", diff --git a/views/activitypub-dashboard.njk b/views/activitypub-dashboard.njk index 6c12d4a..60f5d21 100644 --- a/views/activitypub-dashboard.njk +++ b/views/activitypub-dashboard.njk @@ -22,6 +22,14 @@ title: __("activitypub.activities"), url: mountPath + "/admin/activities" }, + { + title: pinnedCount + " " + __("activitypub.featured"), + url: mountPath + "/admin/featured" + }, + { + title: tagCount + " " + __("activitypub.featuredTags"), + url: mountPath + "/admin/tags" + }, { title: __("activitypub.profile.title"), url: mountPath + "/admin/profile"