diff options
author | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2023-09-26 13:47:44 +0000 |
---|---|---|
committer | Madeline <46743919+MaddyUnderStars@users.noreply.github.com> | 2023-09-26 13:47:44 +0000 |
commit | 0bc905a222eed993f65e41700ddedeec7f0eda63 (patch) | |
tree | e2e6a811c47d63ba19b0dea1bbb7791b06f87f01 /src | |
parent | various things (diff) | |
download | server-0bc905a222eed993f65e41700ddedeec7f0eda63.tar.xz |
switch to own activitypub types library
Diffstat (limited to 'src')
-rw-r--r-- | src/activitypub/federation/OrderedCollection.ts | 10 | ||||
-rw-r--r-- | src/activitypub/federation/index.ts | 4 | ||||
-rw-r--r-- | src/activitypub/federation/queue.ts | 6 | ||||
-rw-r--r-- | src/activitypub/federation/transforms.ts | 65 | ||||
-rw-r--r-- | src/activitypub/federation/utils.ts | 32 | ||||
-rw-r--r-- | src/activitypub/routes/channels/#channel_id/inbox.ts | 27 | ||||
-rw-r--r-- | src/activitypub/routes/channels/#channel_id/outbox.ts | 6 | ||||
-rw-r--r-- | src/activitypub/routes/users/#user_id/inbox.ts | 30 |
8 files changed, 61 insertions, 119 deletions
diff --git a/src/activitypub/federation/OrderedCollection.ts b/src/activitypub/federation/OrderedCollection.ts index 70a40754..d530deb8 100644 --- a/src/activitypub/federation/OrderedCollection.ts +++ b/src/activitypub/federation/OrderedCollection.ts @@ -1,14 +1,14 @@ -import { AP } from "activitypub-core-types"; +import { APOrderedCollection, AnyAPObject } from "activitypub-types"; import { ACTIVITYSTREAMS_CONTEXT } from "./utils"; -export const makeOrderedCollection = async <T extends AP.CoreObject>(opts: { +export const makeOrderedCollection = async <T extends AnyAPObject>(opts: { page: boolean; min_id?: string; max_id?: string; - id: URL; + id: string; getTotalElements: () => Promise<number>; getElements: (before?: string, after?: string) => Promise<T[]>; -}): Promise<AP.OrderedCollection> => { +}): Promise<APOrderedCollection> => { const { page, min_id, max_id, id, getTotalElements, getElements } = opts; if (!page) @@ -28,7 +28,7 @@ export const makeOrderedCollection = async <T extends AP.CoreObject>(opts: { return { "@context": ACTIVITYSTREAMS_CONTEXT, - id: new URL(`${id}?page=true`), + id: `${id}?page=true`, type: "OrderedCollection", first: new URL(`${id}?page=true`), last: new URL(`${id}?page=true&min_id=0`), diff --git a/src/activitypub/federation/index.ts b/src/activitypub/federation/index.ts index 62c93b18..072bc878 100644 --- a/src/activitypub/federation/index.ts +++ b/src/activitypub/federation/index.ts @@ -3,7 +3,7 @@ * Responsible for dispatching activitypub events to external instances */ -import { AP } from "activitypub-core-types"; +import { APActivity } from "activitypub-types"; import { federationQueue } from "./queue"; export * from "./OrderedCollection"; @@ -11,7 +11,7 @@ export * from "./transforms"; export * from "./utils"; export class Federation { - static async distribute(activity: AP.Activity) { + static async distribute(activity: APActivity) { await federationQueue.distribute(activity); } } diff --git a/src/activitypub/federation/queue.ts b/src/activitypub/federation/queue.ts index dff08e37..3bee2365 100644 --- a/src/activitypub/federation/queue.ts +++ b/src/activitypub/federation/queue.ts @@ -1,16 +1,16 @@ import { Config, FederationKey } from "@spacebar/util"; -import { AP } from "activitypub-core-types"; import fetch from "node-fetch"; import { APError, signActivity, splitQualifiedMention } from "./utils"; +import { APActivity } from "activitypub-types"; // type Instance = string; class FederationQueue { // TODO: queue messages and send them to shared inbox - private queue: Map<Instance, Array<AP.Activity>> = new Map(); + private queue: Map<Instance, Array<APActivity>> = new Map(); - public async distribute(activity: AP.Activity) { + public async distribute(activity: APActivity) { let { to, actor } = activity; if (!to) diff --git a/src/activitypub/federation/transforms.ts b/src/activitypub/federation/transforms.ts index d2c05c24..3718d7df 100644 --- a/src/activitypub/federation/transforms.ts +++ b/src/activitypub/federation/transforms.ts @@ -10,20 +10,19 @@ import { User, UserSettings, } from "@spacebar/util"; -import { AP } from "activitypub-core-types"; import TurndownService from "turndown"; import { In } from "typeorm"; import { ACTIVITYSTREAMS_CONTEXT, APError, APObjectIsPerson, - APObjectIsSpacebarActor, resolveAPObject, } from "./utils"; +import { APAnnounce, APGroup, APNote, APPerson } from "activitypub-types"; export const transformMessageToAnnounceNoce = async ( message: Message, -): Promise<AP.Announce> => { +): Promise<APAnnounce> => { const { host } = Config.get().federation; const channel = await Channel.findOneOrFail({ @@ -34,10 +33,9 @@ export const transformMessageToAnnounceNoce = async ( }); let to = [ - new URL( - `https://${host}/federation/channels/${message.channel_id}/followers`, - ), + `https://${host}/federation/channels/${message.channel_id}/followers`, ]; + if (channel.isDm()) { const otherUsers = channel.recipients?.filter( (x) => x.user_id != message.author_id, @@ -48,27 +46,25 @@ export const transformMessageToAnnounceNoce = async ( }); to = remoteUsersKeys.map((x) => - x.inbox ? new URL(x.inbox!) : new URL(`${x.federatedId}/inbox`), + x.inbox ? x.inbox! : `${x.federatedId}/inbox`, ); } return { "@context": ACTIVITYSTREAMS_CONTEXT, type: "Announce", - id: new URL( - `https://${host}/federation/channels/${message.channel_id}/messages/${message.id}`, - ), + id: `https://${host}/federation/channels/${message.channel_id}/messages/${message.id}`, // this is wrong for remote users - actor: new URL(`https://${host}/federation/users/${message.author_id}`), + actor: `https://${host}/federation/users/${message.author_id}`, published: message.timestamp, to, object: await transformMessageToNote(message), - }; + } as APAnnounce; }; export const transformMessageToNote = async ( message: Message, -): Promise<AP.Note> => { +): Promise<APNote> => { const { host } = Config.get().federation; const referencedMessage = message.message_reference @@ -78,23 +74,18 @@ export const transformMessageToNote = async ( : null; return { - id: new URL(`https://${host}/federation/messages/${message.id}`), + id: `https://${host}/federation/messages/${message.id}`, type: "Note", content: message.content, // TODO: convert markdown to html inReplyTo: referencedMessage ? await transformMessageToNote(referencedMessage) : undefined, published: message.timestamp, - attributedTo: new URL( - `https://${host}/federation/users/${message.author_id}`, - ), - to: [ - new URL( - `https://${host}/federation/channels/${message.channel_id}`, - ), - ], + attributedTo: `https://${host}/federation/users/${message.author_id}`, + + to: [`https://${host}/federation/channels/${message.channel_id}`], tag: message.mentions?.map( - (x) => new URL(`https://${host}/federation/users/${x.id}`), + (x) => `https://${host}/federation/users/${x.id}`, ), attachment: [], // replies: [], @@ -105,7 +96,7 @@ export const transformMessageToNote = async ( }; // TODO: this was copied from the previous implemention. refactor it. -export const transformNoteToMessage = async (note: AP.Note) => { +export const transformNoteToMessage = async (note: APNote) => { if (!note.id) throw new APError("Note must have ID"); if (note.type != "Note") throw new APError("Message must be Note"); @@ -177,7 +168,7 @@ export const transformNoteToMessage = async (note: AP.Note) => { export const transformChannelToGroup = async ( channel: Channel, -): Promise<AP.Group> => { +): Promise<APGroup> => { const { host, accountDomain } = Config.get().federation; const keys = await FederationKey.findOneOrFail({ @@ -187,7 +178,7 @@ export const transformChannelToGroup = async ( return { "@context": "https://www.w3.org/ns/activitystreams", type: "Group", - id: new URL(`https://${host}/fed/channels/${channel.id}`), + id: `https://${host}/fed/channels/${channel.id}`, name: channel.name, preferredUsername: channel.id, summary: channel.topic, @@ -200,15 +191,13 @@ export const transformChannelToGroup = async ( publicKeyPem: keys.publicKey, }, - inbox: new URL(`https://${host}/fed/channels/${channel.id}/inbox`), - outbox: new URL(`https://${host}/fed/channels/${channel.id}/outbox`), - followers: new URL( - `https://${host}/fed/channels/${channel.id}/followers`, - ), + inbox: `https://${host}/fed/channels/${channel.id}/inbox`, + outbox: `https://${host}/fed/channels/${channel.id}/outbox`, + followers: `https://${host}/fed/channels/${channel.id}/followers`, }; }; -export const transformUserToPerson = async (user: User): Promise<AP.Person> => { +export const transformUserToPerson = async (user: User): Promise<APPerson> => { const { host, accountDomain } = Config.get().federation; const keys = await FederationKey.findOneOrFail({ @@ -218,7 +207,7 @@ export const transformUserToPerson = async (user: User): Promise<AP.Person> => { return { "@context": ACTIVITYSTREAMS_CONTEXT, type: "Person", - id: new URL(`https://${host}/federation/users/${user.id}`), + id: `https://${host}/federation/users/${user.id}`, name: user.username, preferredUsername: user.id, @@ -233,11 +222,9 @@ export const transformUserToPerson = async (user: User): Promise<AP.Person> => { ] : undefined, - inbox: new URL(`https://${host}/federation/users/${user.id}/inbox`), - outbox: new URL(`https://${host}/federation/users/${user.id}/outbox`), - followers: new URL( - `https://${host}/federation/users/${user.id}/followers`, - ), + inbox: `https://${host}/federation/users/${user.id}/inbox`, + outbox: `https://${host}/federation/users/${user.id}/outbox`, + followers: `https://${host}/federation/users/${user.id}/followers`, publicKey: { id: `https://${host}/federation/users/${user.id}#main-key`, owner: `https://${host}/federation/users/${user.id}`, @@ -247,7 +234,7 @@ export const transformUserToPerson = async (user: User): Promise<AP.Person> => { }; // TODO: this was copied from previous implementation. refactor. -export const transformPersonToUser = async (person: AP.Person) => { +export const transformPersonToUser = async (person: APPerson) => { if (!person.id) throw new APError("User must have ID"); const url = new URL(person.id.toString()); diff --git a/src/activitypub/federation/utils.ts b/src/activitypub/federation/utils.ts index 10a8346e..0ff596a2 100644 --- a/src/activitypub/federation/utils.ts +++ b/src/activitypub/federation/utils.ts @@ -5,7 +5,13 @@ import { OrmUtils, WebfingerResponse, } from "@spacebar/util"; -import { AP } from "activitypub-core-types"; +import { + APActivity, + APActor, + APObject, + APPerson, + AnyAPObject, +} from "activitypub-types"; import crypto from "crypto"; import { HTTPError } from "lambert-server"; import fetch from "node-fetch"; @@ -30,7 +36,7 @@ export const hasAPContext = (data: object) => { return context == activitystreams; }; -export const resolveAPObject = async <T extends object>( +export const resolveAPObject = async <T extends AnyAPObject>( data: string | T, ): Promise<T> => { // we were already given an AP object @@ -79,7 +85,7 @@ export const splitQualifiedMention = (lookup: string) => { export const resolveWebfinger = async ( lookup: string, -): Promise<AP.CoreObject> => { +): Promise<AnyAPObject> => { const { domain } = splitQualifiedMention(lookup); const agent = new ProxyAgent(); @@ -94,7 +100,7 @@ export const resolveWebfinger = async ( const link = wellknown.links.find((x) => x.rel == "self"); if (!link) throw new APError(".well-known did not contain rel=self link"); - return await resolveAPObject<AP.CoreObject>(link.href); + return await resolveAPObject<AnyAPObject>(link.href); }; /** @@ -107,7 +113,7 @@ export const resolveWebfinger = async ( export const signActivity = async ( inbox: string, sender: FederationKey, - message: AP.Activity, + message: APActivity, ) => { if (!sender.privateKey) throw new APError("cannot sign without private key"); @@ -152,27 +158,23 @@ export const signActivity = async ( }; // fetch from remote server? -export const APObjectIsPerson = ( - object: AP.EntityReference, -): object is AP.Person => { +export const APObjectIsPerson = (object: AnyAPObject): object is APPerson => { return "type" in object && object.type == "Person"; }; -export const APObjectIsGroup = ( - object: AP.EntityReference, -): object is AP.Person => { +export const APObjectIsGroup = (object: AnyAPObject): object is APPerson => { return "type" in object && object.type == "Group"; }; export const APObjectIsOrganisation = ( - object: AP.EntityReference, -): object is AP.Person => { + object: AnyAPObject, +): object is APPerson => { return "type" in object && object.type == "Organization"; }; export const APObjectIsSpacebarActor = ( - object: AP.EntityReference, -): object is AP.Person => { + object: AnyAPObject, +): object is APPerson => { return ( APObjectIsGroup(object) || APObjectIsOrganisation(object) || diff --git a/src/activitypub/routes/channels/#channel_id/inbox.ts b/src/activitypub/routes/channels/#channel_id/inbox.ts index 5414ab48..896522b7 100644 --- a/src/activitypub/routes/channels/#channel_id/inbox.ts +++ b/src/activitypub/routes/channels/#channel_id/inbox.ts @@ -1,35 +1,14 @@ import { transformNoteToMessage } from "@spacebar/ap"; import { route } from "@spacebar/api"; import { Message, emitEvent } from "@spacebar/util"; -import { AP } from "activitypub-core-types"; +import { APCreate, APNote } from "activitypub-types"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; const router = Router(); -// TODO: check if the activity exists on the remote server router.post("/", route({}), async (req: Request, res: Response) => { - const body = req.body as AP.Create; - - if (body.type != "Create") throw new HTTPError("not implemented"); - - const object = Array.isArray(body.object) ? body.object[0] : body.object; - if (!object) return res.status(400); - if (!("type" in object) || object.type != "Note") - throw new HTTPError("must be Note"); - const message = await transformNoteToMessage(object as AP.Note); - - if ((await Message.count({ where: { nonce: object.id!.toString() } })) != 0) - return res.status(200); - - await message.save(); - - await emitEvent({ - event: "MESSAGE_CREATE", - channel_id: message.channel_id, - data: message.toJSON(), - }); - - return res.status(200); + // TODO: check if the activity exists on the remote server + // TODO: refactor }); export default router; diff --git a/src/activitypub/routes/channels/#channel_id/outbox.ts b/src/activitypub/routes/channels/#channel_id/outbox.ts index 4eedb210..c6388fda 100644 --- a/src/activitypub/routes/channels/#channel_id/outbox.ts +++ b/src/activitypub/routes/channels/#channel_id/outbox.ts @@ -4,7 +4,7 @@ import { } from "@spacebar/ap"; import { route } from "@spacebar/api"; import { Config, Message, Snowflake } from "@spacebar/util"; -import { AP } from "activitypub-core-types"; +import { APAnnounce } from "activitypub-types"; import { Request, Response, Router } from "express"; import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm"; const router = Router(); @@ -19,9 +19,9 @@ router.get("/", route({}), async (req: Request, res: Response) => { page: page != undefined, min_id: min_id?.toString(), max_id: max_id?.toString(), - id: new URL(`https://${host}/federation/channels/${channel_id}/outbox`), + id: `https://${host}/federation/channels/${channel_id}/outbox`, getTotalElements: () => Message.count({ where: { channel_id } }), - getElements: async (before, after): Promise<AP.Announce[]> => { + getElements: async (before, after): Promise<APAnnounce[]> => { const query: FindManyOptions<Message> & { where: { id?: FindOperator<string> | FindOperator<string>[] }; } = { diff --git a/src/activitypub/routes/users/#user_id/inbox.ts b/src/activitypub/routes/users/#user_id/inbox.ts index ad1ed8c1..6734f37c 100644 --- a/src/activitypub/routes/users/#user_id/inbox.ts +++ b/src/activitypub/routes/users/#user_id/inbox.ts @@ -1,36 +1,10 @@ -import { transformNoteToMessage } from "@spacebar/ap"; import { route } from "@spacebar/api"; -import { Message, emitEvent } from "@spacebar/util"; -import { AP } from "activitypub-core-types"; import { Request, Response, Router } from "express"; -import { HTTPError } from "lambert-server"; const router = Router(); -// TODO: check if the activity exists on the remote server -// TODO: support lemmy ChatMessage type? router.post("/", route({}), async (req: Request, res: Response) => { - const body = req.body as AP.Create; - - if (body.type != "Create") throw new HTTPError("not implemented"); - - const object = Array.isArray(body.object) ? body.object[0] : body.object; - if (!object) return res.status(400); - if (!("type" in object) || object.type != "Note") - throw new HTTPError("must be Note"); - const message = await transformNoteToMessage(object as AP.Note); - - if ((await Message.count({ where: { nonce: object.id!.toString() } })) != 0) - return res.status(200); - - await message.save(); - - await emitEvent({ - event: "MESSAGE_CREATE", - channel_id: message.channel_id, - data: message.toJSON(), - }); - - return res.status(200); + // TODO: support lemmy ChatMessage type? + // TODO: check if the activity exists on the remote server }); export default router; |