1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-09 12:12:35 +01:00

switch to own activitypub types library

This commit is contained in:
Madeline 2023-09-26 13:47:44 +00:00
parent b64163f769
commit 0bc905a222
11 changed files with 552776 additions and 539705 deletions

File diff suppressed because it is too large Load Diff

159
package-lock.json generated
View File

@ -13,7 +13,7 @@
"@aws-sdk/client-s3": "^3.385.0",
"@sentry/integrations": "^7.66.0",
"@sentry/node": "^7.66.0",
"activitypub-core-types": "^0.3.2",
"activitypub-types": "github:spacebarchat/activitypub-types#dist",
"ajv": "8.6.2",
"ajv-formats": "2.1.1",
"amqplib": "^0.10.3",
@ -43,6 +43,7 @@
"node-fetch": "^2.6.12",
"node-os-utils": "^1.3.7",
"nodemailer": "^6.9.4",
"pg": "^8.11.3",
"picocolors": "^1.0.0",
"probe-image-size": "^7.2.3",
"proxy-agent": "^6.3.0",
@ -2504,13 +2505,10 @@
"node": ">=0.4.0"
}
},
"node_modules/activitypub-core-types": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/activitypub-core-types/-/activitypub-core-types-0.3.2.tgz",
"integrity": "sha512-hAWCkRIzLJ3eVEjnibPYNHQaM+vD0SCK29gqMEt5pEb/2pbEetkv+4MpVMi0CKtr/GWRCSjIw1C6YAph7yT0pA==",
"dependencies": {
"formidable": "^2.1.1"
}
"node_modules/activitypub-types": {
"version": "1.0.4",
"resolved": "git+ssh://git@github.com/spacebarchat/activitypub-types.git#67d3162d8a982f1d64c18412835b54169682b523",
"license": "MIT"
},
"node_modules/addressparser": {
"version": "1.0.1",
@ -2742,7 +2740,8 @@
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"optional": true
},
"node_modules/asn1js": {
"version": "3.0.5",
@ -2980,6 +2979,14 @@
"resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
"integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg=="
},
"node_modules/buffer-writer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
"engines": {
"node": ">=4"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@ -3605,6 +3612,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
"optional": true,
"dependencies": {
"asap": "^2.0.0",
"wrappy": "1"
@ -4464,6 +4472,7 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz",
"integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==",
"optional": true,
"dependencies": {
"dezalgo": "^1.0.4",
"hexoid": "^1.0.0",
@ -4742,6 +4751,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
"optional": true,
"engines": {
"node": ">=8"
}
@ -6280,6 +6290,11 @@
"node": ">= 14"
}
},
"node_modules/packet-reader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -6375,6 +6390,89 @@
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/pg": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz",
"integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==",
"dependencies": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.6.2",
"pg-pool": "^3.6.1",
"pg-protocol": "^1.6.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
"engines": {
"node": ">= 8.0.0"
},
"optionalDependencies": {
"pg-cloudflare": "^1.1.1"
},
"peerDependencies": {
"pg-native": ">=3.0.1"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-cloudflare": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
"integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
"optional": true
},
"node_modules/pg-connection-string": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz",
"integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA=="
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz",
"integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
"dependencies": {
"split2": "^4.1.0"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -6407,6 +6505,41 @@
"node": ">=12.0.0"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -7109,6 +7242,14 @@
"source-map": "^0.6.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/sqlite3": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz",

View File

@ -69,7 +69,7 @@
"@aws-sdk/client-s3": "^3.385.0",
"@sentry/integrations": "^7.66.0",
"@sentry/node": "^7.66.0",
"activitypub-core-types": "^0.3.2",
"activitypub-types": "github:spacebarchat/activitypub-types#dist",
"ajv": "8.6.2",
"ajv-formats": "2.1.1",
"amqplib": "^0.10.3",
@ -99,6 +99,7 @@
"node-fetch": "^2.6.12",
"node-os-utils": "^1.3.7",
"nodemailer": "^6.9.4",
"pg": "^8.11.3",
"picocolors": "^1.0.0",
"probe-image-size": "^7.2.3",
"proxy-agent": "^6.3.0",

View File

@ -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`),

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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());

View File

@ -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) ||

View File

@ -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;

View File

@ -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>[] };
} = {

View File

@ -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;