diff --git a/scripts/ap/get.js b/scripts/ap/get.js
new file mode 100644
index 00000000..ab235427
--- /dev/null
+++ b/scripts/ap/get.js
@@ -0,0 +1,34 @@
+const nodeFetch = require("node-fetch");
+
+const fetch = (url, opts) =>
+ nodeFetch(url, {
+ ...opts,
+ headers: {
+ Accept: "application/activity+json",
+ ...(opts?.headers || {}),
+ },
+ }).then((x) => x.json());
+
+const webfinger = async (domain, user) => {
+ const query = `https://${domain}/.well-known/webfinger?resource=@${user}@${domain}`;
+ const json = await fetch(query);
+ return json.links.find((x) => x.rel == "self").href;
+};
+
+(async () => {
+ const userLocation = await webfinger(
+ "chat.understars.dev",
+ "1140599542186631381",
+ );
+ console.log(userLocation);
+
+ const user = await fetch(userLocation);
+
+ const outbox = await fetch(user.outbox);
+
+ const firstPage = await fetch(outbox.first);
+
+ const mostRecent = firstPage.orderedItems[0];
+
+ console.log(mostRecent);
+})();
diff --git a/src/activitypub/routes/channel/#channel_id/inbox.ts b/src/activitypub/routes/channel/#channel_id/inbox.ts
index 01e267e7..a44944b6 100644
--- a/src/activitypub/routes/channel/#channel_id/inbox.ts
+++ b/src/activitypub/routes/channel/#channel_id/inbox.ts
@@ -1,9 +1,22 @@
import { route } from "@spacebar/api";
+import { Message, emitEvent } from "@spacebar/util";
import { Router } from "express";
+import { HTTPError } from "lambert-server";
const router = Router();
export default router;
router.post("/", route({}), async (req, res) => {
- console.log(req.body);
+ const body = req.body;
+
+ if (body.type != "Create") throw new HTTPError("not implemented");
+
+ const message = await Message.fromAP(body);
+ await message.save();
+
+ await emitEvent({
+ event: "MESSAGE_CREATE",
+ channel_id: message.channel_id,
+ data: message.toJSON(),
+ });
});
diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts
index 07ec8195..d157ba35 100644
--- a/src/util/entities/Message.ts
+++ b/src/util/entities/Message.ts
@@ -16,7 +16,13 @@
along with this program. If not, see .
*/
-import type { APAnnounce, APNote } from "activitypub-types";
+import type {
+ APAnnounce,
+ APNote,
+ APPerson,
+ AnyAPObject,
+} from "activitypub-types";
+import fetch from "node-fetch";
import {
Column,
CreateDateColumn,
@@ -29,7 +35,7 @@ import {
OneToMany,
RelationId,
} from "typeorm";
-import { Config } from "..";
+import { Config, Snowflake } from "..";
import { InteractionType } from "../interfaces/Interaction";
import { Application } from "./Application";
import { Attachment } from "./Attachment";
@@ -269,6 +275,54 @@ export class Message extends BaseClass {
content: this.content,
};
}
+
+ static async fromAP(data: APNote): Promise {
+ if (!data.attributedTo)
+ throw new Error("sb Message must have author (attributedTo)");
+
+ let attrib = Array.isArray(data.attributedTo)
+ ? data.attributedTo[0]
+ : data.attributedTo;
+ if (typeof attrib == "string") {
+ // fetch it
+ attrib = (await fetch(attrib).then((x) => x.json())) as AnyAPObject;
+ }
+
+ if (attrib.type != "Person")
+ throw new Error("only Person can be author of sb Message"); //hm
+
+ let to = data.to;
+
+ if (Array.isArray(to))
+ to = to.filter((x) => {
+ if (typeof x == "string") return x.includes("channel");
+ return false;
+ });
+
+ if (!to) throw new Error("not deliverable");
+
+ const channel_id = (to as string).split("/").reverse()[0];
+
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ relations: { guild: true },
+ });
+
+ return Message.create({
+ id: Snowflake.generate(),
+ author: await User.fromAP(attrib as APPerson),
+ content: data.content, // convert html to markdown
+ timestamp: data.published,
+ channel_id,
+
+ sticker_items: [],
+ guild_id: channel.guild_id,
+ attachments: [],
+ embeds: [],
+ reactions: [],
+ type: 0,
+ });
+ }
}
export interface MessageComponent {
diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts
index 1adeae3a..59713bce 100644
--- a/src/util/entities/User.ts
+++ b/src/util/entities/User.ts
@@ -37,6 +37,7 @@ import { Session } from "./Session";
import { UserSettings } from "./UserSettings";
import crypto from "crypto";
+import fetch from "node-fetch";
import { promisify } from "util";
const generateKeyPair = promisify(crypto.generateKeyPair);
@@ -322,6 +323,20 @@ export class User extends BaseClass {
};
}
+ static async fromAP(data: APPerson | string): Promise {
+ if (typeof data == "string") {
+ data = (await fetch(data, {
+ headers: { Accept: "application/activity+json" },
+ }).then((x) => x.json())) as APPerson;
+ }
+
+ return User.create({
+ id: Snowflake.generate(), // hm
+ username: data.preferredUsername,
+ bio: data.summary, // TODO: convert to markdown
+ });
+ }
+
static async getPublicUser(user_id: string, opts?: FindOneOptions) {
return await User.findOneOrFail({
where: { id: user_id },