Skip to content

Commit

Permalink
fix: setup announcements actor as a normal "mention" actor
Browse files Browse the repository at this point in the history
store the announcements actor as a "normal" actor in actorsDb, using `@announcements@publicHostname`.

also clean up code.
  • Loading branch information
catdevnull committed Mar 7, 2024
1 parent fa7e856 commit 42d5ecb
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 33 deletions.
41 changes: 30 additions & 11 deletions src/server/announcements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<ActorStore> {
return this.apsystem.store.forActor(this.mention)
}

async init (): Promise<void> {
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: {
Expand All @@ -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<APOrderedCollection> {
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 }))
Expand All @@ -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
Expand Down
24 changes: 13 additions & 11 deletions src/server/api/announcements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type APActorNonStandard = APActor & {
export const announcementsRoutes = (cfg: APIConfig, store: Store, apsystem: ActivityPubSystem) => async (server: FastifyTypebox): Promise<void> => {
server.get<{
Reply: APActorNonStandard
}>('/announcements/', {
}>(`/${apsystem.announcements.mention}/`, {
schema: {
params: {},
// XXX: even with Type.Any(), the endpoint returns `{}` :/
Expand All @@ -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': [
Expand All @@ -39,21 +40,21 @@ 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
}
})
})

server.get<{
// TODO: typebox APOrderedCollection
Reply: any
}>('/announcements/outbox', {
}>(`/${apsystem.announcements.mention}/outbox`, {
schema: {
params: {},
// XXX: even with Type.Any(), the endpoint returns `{}` :/
Expand All @@ -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()
Expand All @@ -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)
})
}
8 changes: 4 additions & 4 deletions src/server/api/wellKnown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
]
})
Expand Down
7 changes: 0 additions & 7 deletions src/server/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export default class Store {
db: AbstractLevel<any, string, any>
actorCache: Map<string, ActorStore>
actorsDb: AbstractLevel<any, string, any>
announcements: ActorStore
blocklist: AccountListStore
allowlist: AccountListStore
admins: AccountListStore
Expand All @@ -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'
})
Expand All @@ -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)
Expand Down

0 comments on commit 42d5ecb

Please sign in to comment.