mirror of
https://github.com/svemagie/indiekit-endpoint-activitypub.git
synced 2026-04-02 15:44:58 +02:00
- FEP-8fcf: add syncCollection to Undo(Announce) sendActivity - FEP-fe34: centralized lookupWithSecurity() helper with crossOrigin: "ignore" on all 23 lookupObject call sites - Custom emoji: replaceCustomEmoji() renders :shortcode: as inline <img> in content and actor display names - Manual follow approval: profile toggle, ap_pending_follows collection, approve/reject controllers with federation, pending tab on followers page, follow_request notification type - Coverage audit updated to v2.12.x (overall ~70% → ~82%) Confab-Link: http://localhost:8080/sessions/1f1e729b-0087-499e-a991-f36f46211fe4
111 lines
3.0 KiB
JavaScript
111 lines
3.0 KiB
JavaScript
/**
|
|
* Resolve controller — accepts any fediverse URL or handle, resolves it
|
|
* via lookupObject(), and redirects to the appropriate internal view.
|
|
*/
|
|
import { lookupWithSecurity } from "../lookup-helpers.js";
|
|
import {
|
|
Article,
|
|
Note,
|
|
Person,
|
|
Service,
|
|
Application,
|
|
Organization,
|
|
Group,
|
|
} from "@fedify/fedify/vocab";
|
|
|
|
/**
|
|
* GET /admin/reader/resolve?q=<url-or-handle>
|
|
* Resolves a fediverse URL or @user@domain handle and redirects to
|
|
* the post detail or remote profile view.
|
|
*/
|
|
export function resolveController(mountPath, plugin) {
|
|
return async (request, response, next) => {
|
|
try {
|
|
const query = (request.query.q || "").trim();
|
|
|
|
if (!query) {
|
|
return response.redirect(`${mountPath}/admin/reader`);
|
|
}
|
|
|
|
if (!plugin._federation) {
|
|
return response.status(503).render("error", {
|
|
title: "Error",
|
|
content: "Federation not initialized",
|
|
});
|
|
}
|
|
|
|
const handle = plugin.options.actor.handle;
|
|
const ctx = plugin._federation.createContext(
|
|
new URL(plugin._publicationUrl),
|
|
{ handle, publicationUrl: plugin._publicationUrl },
|
|
);
|
|
|
|
const documentLoader = await ctx.getDocumentLoader({
|
|
identifier: handle,
|
|
});
|
|
|
|
// Determine if input is a URL or a handle
|
|
// lookupObject accepts: URLs, @user@domain, user@domain, acct:user@domain
|
|
let lookupInput;
|
|
|
|
try {
|
|
// If it parses as a URL, pass as URL object
|
|
const parsed = new URL(query);
|
|
lookupInput = parsed;
|
|
} catch {
|
|
// Not a URL — treat as handle (strip leading @ if present)
|
|
lookupInput = query;
|
|
}
|
|
|
|
let object;
|
|
|
|
try {
|
|
object = await lookupWithSecurity(ctx,lookupInput, { documentLoader });
|
|
} catch (error) {
|
|
console.warn(
|
|
`[resolve] lookupObject failed for "${query}":`,
|
|
error.message,
|
|
);
|
|
}
|
|
|
|
if (!object) {
|
|
return response.status(404).render("error", {
|
|
title: response.locals.__("activitypub.reader.resolve.notFoundTitle"),
|
|
content: response.locals.__(
|
|
"activitypub.reader.resolve.notFound",
|
|
),
|
|
});
|
|
}
|
|
|
|
// Determine object type and redirect accordingly
|
|
const objectUrl =
|
|
object.id?.href || object.url?.href || query;
|
|
|
|
if (
|
|
object instanceof Person ||
|
|
object instanceof Service ||
|
|
object instanceof Application ||
|
|
object instanceof Organization ||
|
|
object instanceof Group
|
|
) {
|
|
return response.redirect(
|
|
`${mountPath}/admin/reader/profile?url=${encodeURIComponent(objectUrl)}`,
|
|
);
|
|
}
|
|
|
|
if (object instanceof Note || object instanceof Article) {
|
|
return response.redirect(
|
|
`${mountPath}/admin/reader/post?url=${encodeURIComponent(objectUrl)}`,
|
|
);
|
|
}
|
|
|
|
// Unknown type — try post detail as fallback
|
|
return response.redirect(
|
|
`${mountPath}/admin/reader/post?url=${encodeURIComponent(objectUrl)}`,
|
|
);
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
};
|
|
}
|