From 42d5ecbab044187c885cb1623648087519b50069 Mon Sep 17 00:00:00 2001 From: Nulo Date: Thu, 7 Mar 2024 17:24:16 -0300 Subject: [PATCH] fix: setup announcements actor as a normal "mention" actor store the announcements actor as a "normal" actor in actorsDb, using `@announcements@publicHostname`. also clean up code. --- src/server/announcements.ts | 41 ++++++++++++++++++++++++--------- src/server/api/announcements.ts | 24 ++++++++++--------- src/server/api/wellKnown.ts | 8 +++---- src/server/store/index.ts | 7 ------ 4 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/server/announcements.ts b/src/server/announcements.ts index 4b56dca..95f6f0f 100644 --- a/src/server/announcements.ts +++ b/src/server/announcements.ts @@ -3,6 +3,7 @@ import { ActorInfo } from '../schemas' import ActivityPubSystem, { DEFAULT_PUBLIC_KEY_FIELD } from './apsystem' import { generateKeypair } from 'http-signed-fetch' import { APOrderedCollection } from 'activitypub-types' +import { ActorStore } from './store/ActorStore' export class Announcements { apsystem: ActivityPubSystem @@ -13,20 +14,38 @@ export class Announcements { this.publicURL = publicURL } + get actorUrl (): string { + return `${this.publicURL}/v1/${this.mention}/` + } + + get outboxUrl (): string { + return `${this.actorUrl}outbox` + } + + get mention (): string { + const url = new URL(this.publicURL) + return url.hostname + } + + async getActor (): Promise { + return this.apsystem.store.forActor(this.mention) + } + async init (): Promise { - const actorUrl = `${this.publicURL}/v1/announcements/` + const actorUrl = this.actorUrl + const actor = await this.getActor() try { - const prev = await this.apsystem.store.announcements.getInfo() + const prev = await actor.getInfo() if (prev.actorUrl !== actorUrl) { - await this.apsystem.store.announcements.setInfo({ + await actor.setInfo({ ...prev, actorUrl }) } } catch { const { privateKeyPem, publicKeyPem } = generateKeypair() - await this.apsystem.store.announcements.setInfo({ + await actor.setInfo({ actorUrl, publicKeyId: `${actorUrl}#${DEFAULT_PUBLIC_KEY_FIELD}`, keypair: { @@ -45,23 +64,23 @@ export class Announcements { const activity = { '@context': 'https://www.w3.org/ns/activitystreams', type: 'Note', - id: `${info.actorUrl}outbox/${nanoid()}`, + id: `${this.outboxUrl}/${nanoid()}`, actor: info.actorUrl, attributedTo: info.actorUrl, published: new Date().toUTCString(), to: ['https://www.w3.org/ns/activitystreams#Public'], - cc: ['https://social.distributed.press/v1/announcements/followers'], + cc: [`${this.actorUrl}followers`], // TODO: add a template in config content: `a wild site appears! ${actor}` } - await this.apsystem.store.announcements.outbox.add(activity) - await this.apsystem.notifyFollowers('announcements', activity) + await (await this.getActor()).outbox.add(activity) + await this.apsystem.notifyFollowers(this.mention, activity) } } async getOutbox (): Promise { - const actor = await this.apsystem.store.announcements.getInfo() - const activities = await this.apsystem.store.announcements.outbox.list() + const actor = await this.getActor() + const activities = await actor.outbox.list() const orderedItems = activities // XXX: maybe `new Date()` doesn't correctly parse possible dates? .map(a => ({ ...a, published: typeof a.published === 'string' ? new Date(a.published) : a.published })) @@ -71,7 +90,7 @@ export class Announcements { return { '@context': 'https://www.w3.org/ns/activitystreams', - id: `${actor.actorUrl}outbox`, + id: `${this.actorUrl}outbox`, type: 'OrderedCollection', totalItems: orderedItems.length, orderedItems diff --git a/src/server/api/announcements.ts b/src/server/api/announcements.ts index 7e7644e..63370f9 100644 --- a/src/server/api/announcements.ts +++ b/src/server/api/announcements.ts @@ -16,7 +16,7 @@ type APActorNonStandard = APActor & { export const announcementsRoutes = (cfg: APIConfig, store: Store, apsystem: ActivityPubSystem) => async (server: FastifyTypebox): Promise => { server.get<{ Reply: APActorNonStandard - }>('/announcements/', { + }>(`/${apsystem.announcements.mention}/`, { schema: { params: {}, // XXX: even with Type.Any(), the endpoint returns `{}` :/ @@ -28,7 +28,8 @@ export const announcementsRoutes = (cfg: APIConfig, store: Store, apsystem: Acti tags: ['ActivityPub'] } }, async (request, reply) => { - const actor = await store.announcements.getInfo() + const actor = await apsystem.announcements.getActor() + const actorInfo = await actor.getInfo() return await reply.send({ '@context': [ @@ -39,13 +40,13 @@ export const announcementsRoutes = (cfg: APIConfig, store: Store, apsystem: Acti // https://www.w3.org/TR/activitystreams-vocabulary/#actor-types type: 'Service', name: 'Announcements', - inbox: `${actor.actorUrl}inbox`, - outbox: `${actor.actorUrl}outbox`, + inbox: `${actorInfo.actorUrl}inbox`, + outbox: `${actorInfo.actorUrl}outbox`, publicKey: { - id: `${actor.actorUrl}#main-key`, + id: `${actorInfo.actorUrl}#main-key`, - owner: actor.actorUrl, - publicKeyPem: actor.keypair.publicKeyPem + owner: actorInfo.actorUrl, + publicKeyPem: actorInfo.keypair.publicKeyPem } }) }) @@ -53,7 +54,7 @@ export const announcementsRoutes = (cfg: APIConfig, store: Store, apsystem: Acti server.get<{ // TODO: typebox APOrderedCollection Reply: any - }>('/announcements/outbox', { + }>(`/${apsystem.announcements.mention}/outbox`, { schema: { params: {}, // XXX: even with Type.Any(), the endpoint returns `{}` :/ @@ -74,7 +75,7 @@ export const announcementsRoutes = (cfg: APIConfig, store: Store, apsystem: Acti } // TODO: typebox APOrderedCollection Reply: any - }>('/announcements/outbox/:id', { + }>(`/${apsystem.announcements.mention}/outbox/:id`, { schema: { params: Type.Object({ id: Type.String() @@ -88,8 +89,9 @@ export const announcementsRoutes = (cfg: APIConfig, store: Store, apsystem: Acti tags: ['ActivityPub'] } }, async (request, reply) => { - const actor = await store.announcements.getInfo() - const activity = await store.announcements.outbox.get(`${actor.actorUrl}outbox/${request.params.id}`) + const actor = await apsystem.announcements.getActor() + const actorInfo = await actor.getInfo() + const activity = await actor.outbox.get(`${actorInfo.actorUrl}outbox/${request.params.id}`) return await reply.send(activity) }) } diff --git a/src/server/api/wellKnown.ts b/src/server/api/wellKnown.ts index 6709663..1e39653 100644 --- a/src/server/api/wellKnown.ts +++ b/src/server/api/wellKnown.ts @@ -11,19 +11,19 @@ export const wellKnownRoutes = (cfg: APIConfig, store: Store, apsystem: Activity tags: ['ActivityPub'] } }, async (request, reply) => { - const actor = await store.announcements.getInfo() + // https://docs.joinmastodon.org/spec/webfinger/ return await reply .headers({ 'Content-Type': 'application/jrd+json' }) .send({ - subject: `acct:announcements@${cfg.publicURL}`, - aliases: [actor.actorUrl], + subject: `acct:${apsystem.announcements.mention.slice(1)}`, + aliases: [apsystem.announcements.actorUrl], links: [ { rel: 'self', type: 'application/activity+json', - href: actor.actorUrl + href: apsystem.announcements.actorUrl } ] }) diff --git a/src/server/store/index.ts b/src/server/store/index.ts index 7bf7e0e..ea1a13b 100644 --- a/src/server/store/index.ts +++ b/src/server/store/index.ts @@ -10,7 +10,6 @@ export default class Store { db: AbstractLevel actorCache: Map actorsDb: AbstractLevel - announcements: ActorStore blocklist: AccountListStore allowlist: AccountListStore admins: AccountListStore @@ -20,8 +19,6 @@ export default class Store { this.db = db this.actorCache = new Map() this.actorsDb = this.db.sublevel('actorCache', { valueEncoding: 'json' }) - const announcementsDb = this.db.sublevel('announcements', { valueEncoding: 'json' }) - this.announcements = new ActorStore(announcementsDb) const blocklistDb = this.db.sublevel('blocklist', { valueEncoding: 'json' }) @@ -37,10 +34,6 @@ export default class Store { } forActor (domain: string): ActorStore { - if (domain === 'announcements') { - return this.announcements - } - if (!this.actorCache.has(domain)) { const sub = this.db.sublevel(domain, { valueEncoding: 'json' }) const store = new ActorStore(sub)