diff --git a/lib/controllers/profile.js b/lib/controllers/profile.js index 8a5062d..e372690 100644 --- a/lib/controllers/profile.js +++ b/lib/controllers/profile.js @@ -5,6 +5,8 @@ * POST: saves updated profile fields back to ap_profile */ +const ACTOR_TYPES = ["Person", "Service", "Organization"]; + export function profileGetController(mountPath) { return async (request, response, next) => { try { @@ -18,6 +20,7 @@ export function profileGetController(mountPath) { title: response.locals.__("activitypub.profile.title"), mountPath, profile, + actorTypes: ACTOR_TYPES, result: null, }); } catch (error) { @@ -42,10 +45,23 @@ export function profilePostController(mountPath) { url, icon, image, + actorType, manuallyApprovesFollowers, authorizedFetch, } = request.body; + // Parse profile links (attachments) from form arrays + const linkNames = [].concat(request.body["link_name[]"] || []); + const linkValues = [].concat(request.body["link_value[]"] || []); + const attachments = []; + for (let i = 0; i < linkNames.length; i++) { + const n = linkNames[i]?.trim(); + const v = linkValues[i]?.trim(); + if (n && v) { + attachments.push({ name: n, value: v }); + } + } + const update = { $set: { name: name?.trim() || "", @@ -53,8 +69,10 @@ export function profilePostController(mountPath) { url: url?.trim() || "", icon: icon?.trim() || "", image: image?.trim() || "", + actorType: ACTOR_TYPES.includes(actorType) ? actorType : "Person", manuallyApprovesFollowers: manuallyApprovesFollowers === "true", authorizedFetch: authorizedFetch === "true", + attachments, updatedAt: new Date().toISOString(), }, }; @@ -67,6 +85,7 @@ export function profilePostController(mountPath) { title: response.locals.__("activitypub.profile.title"), mountPath, profile, + actorTypes: ACTOR_TYPES, result: { type: "success", text: response.locals.__("activitypub.profile.saved"), diff --git a/lib/federation-setup.js b/lib/federation-setup.js index 9158b49..7f78801 100644 --- a/lib/federation-setup.js +++ b/lib/federation-setup.js @@ -197,7 +197,11 @@ export function setupFederation(options) { personOptions.published = Temporal.Instant.from(profile.createdAt); } - return new ActorClass(personOptions); + // Actor type from profile overrides config default + const profileActorType = profile.actorType || actorType; + const ResolvedActorClass = actorTypeMap[profileActorType] || ActorClass; + + return new ResolvedActorClass(personOptions); }, ) .mapHandle((_ctx, username) => { diff --git a/locales/en.json b/locales/en.json index c6e81e5..55ee510 100644 --- a/locales/en.json +++ b/locales/en.json @@ -38,6 +38,14 @@ "imageHint": "URL to a banner image shown at the top of your profile", "manualApprovalLabel": "Manually approve followers", "manualApprovalHint": "When enabled, follow requests require your approval before they take effect", + "actorTypeLabel": "Actor type", + "actorTypeHint": "How your account appears in the fediverse. Person for individuals, Service for bots or automated accounts, Organization for groups or companies.", + "linksLabel": "Profile links", + "linksHint": "Links shown on your fediverse profile. Add your website, social accounts, or other URLs. Pages that link back with rel=\"me\" will show as verified on Mastodon.", + "linkNameLabel": "Label", + "linkValueLabel": "URL", + "addLink": "Add link", + "removeLink": "Remove", "authorizedFetchLabel": "Require authorized fetch (secure mode)", "authorizedFetchHint": "When enabled, only servers with valid HTTP Signatures can fetch your actor and collections. This improves privacy but may reduce compatibility with some clients.", "save": "Save profile", diff --git a/package.json b/package.json index 329e87f..9109cc7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rmdes/indiekit-endpoint-activitypub", - "version": "1.0.28", + "version": "1.0.29", "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-profile.njk b/views/activitypub-profile.njk index 223ffd9..38d72c0 100644 --- a/views/activitypub-profile.njk +++ b/views/activitypub-profile.njk @@ -4,6 +4,7 @@ {% from "input/macro.njk" import input with context %} {% from "textarea/macro.njk" import textarea with context %} {% from "checkboxes/macro.njk" import checkboxes with context %} +{% from "radios/macro.njk" import radios with context %} {% from "button/macro.njk" import button with context %} {% from "notification-banner/macro.njk" import notificationBanner with context %} {% from "prose/macro.njk" import prose with context %} @@ -57,6 +58,50 @@ type: "url" }) }} + {{ radios({ + name: "actorType", + fieldset: { + legend: __("activitypub.profile.actorTypeLabel") + }, + hint: __("activitypub.profile.actorTypeHint"), + items: [{ + label: "Person", + value: "Person" + }, { + label: "Service", + value: "Service" + }, { + label: "Organization", + value: "Organization" + }], + values: [profile.actorType or "Person"] + }) }} + +
+ {{ checkboxes({ name: "manuallyApprovesFollowers", items: [ @@ -83,4 +128,57 @@ {{ button({ text: __("activitypub.profile.save") }) }} + + {% endblock %}