diff --git a/src/activitypub/routes/channel/#channel_id/followers.ts b/src/activitypub/routes/channel/#channel_id/followers.ts
new file mode 100644
index 00000000..248e2c2c
--- /dev/null
+++ b/src/activitypub/routes/channel/#channel_id/followers.ts
@@ -0,0 +1,28 @@
+import { route } from "@spacebar/api";
+import { Config, Member } from "@spacebar/util";
+import { makeOrderedCollection } from "activitypub/util/OrderedCollection";
+import { Router } from "express";
+
+const router = Router();
+export default router;
+
+router.get("/", route({}), async (req, res) => {
+ // TODO auth
+ const { channel_id } = req.params;
+
+ const { webDomain } = Config.get().federation;
+
+ const ret = makeOrderedCollection(
+ req,
+ `https://${webDomain}/fed/channels/${channel_id}/followers`,
+ () =>
+ Member.count({
+ where: { guild: { channels: { id: channel_id } } },
+ }),
+ async (before, after) => {
+ return [];
+ },
+ );
+
+ return res.json(ret);
+});
diff --git a/src/activitypub/routes/channel/#channel_id/outbox.ts b/src/activitypub/routes/channel/#channel_id/outbox.ts
index bb44e174..f5dbab3a 100644
--- a/src/activitypub/routes/channel/#channel_id/outbox.ts
+++ b/src/activitypub/routes/channel/#channel_id/outbox.ts
@@ -1,6 +1,6 @@
import { route } from "@spacebar/api";
import { Config, Message, Snowflake } from "@spacebar/util";
-import { APOrderedCollection } from "activitypub-types";
+import { makeOrderedCollection } from "activitypub/util/OrderedCollection";
import { Router } from "express";
import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm";
@@ -15,60 +15,36 @@ router.get("/", route({}), async (req, res) => {
const { webDomain } = Config.get().federation;
- if (!page) {
- const ret: APOrderedCollection = {
- "@context": "https://www.w3.org/ns/activitystreams",
- id: `https://${webDomain}/fed/channel/${channel_id}/outbox`,
- type: "OrderedCollection",
- first: `https://${webDomain}/fed/channel/${channel_id}/outbox?page=true`,
- last: `https://${webDomain}/fed/channel/${channel_id}/outbox?page=true&min_id=0`,
- };
- return res.json(ret);
- }
-
- const after = min_id ? `${min_id}` : undefined;
- const before = max_id ? `${max_id}` : undefined;
-
- const query: FindManyOptions<Message> & {
- where: { id?: FindOperator<string> | FindOperator<string>[] };
- } = {
- order: { timestamp: "DESC" },
- take: 20,
- where: { channel_id: channel_id },
- relations: ["author"],
- };
-
- if (after) {
- if (BigInt(after) > BigInt(Snowflake.generate()))
- return res.status(422);
- query.where.id = MoreThan(after);
- } else if (before) {
- if (BigInt(before) > BigInt(Snowflake.generate()))
- return res.status(422);
- query.where.id = LessThan(before);
- }
-
- const messages = await Message.find(query);
-
- // move this to like, Channel.createAPMessages or smth
- const apMessages = messages.map((message) => ({
- id: `https://${webDomain}/fed/channel/${message.channel_id}/messages/${message.id}`,
- type: "Announce",
- actor: `https://${webDomain}/fed/user/${message.author_id}`,
- published: message.timestamp,
- to: `https://${webDomain}/fed/channel/${message.channel_id}`,
- object: message.toAP(),
- }));
-
- const ret: APOrderedCollection = {
- "@context": "https://www.w3.org/ns/activitystreams",
- id: `https://${webDomain}/fed/channel/${channel_id}/outbox?page=true`,
- type: "OrderedCollection",
- first: `https://${webDomain}/fed/channel/${channel_id}/outbox?page=true`,
- last: `https://${webDomain}/fed/channel/${channel_id}/outbox?page=true&min_id=0`,
- totalItems: await Message.count({ where: { channel_id } }),
- items: apMessages,
- };
+ const ret = makeOrderedCollection(
+ req,
+ `https://${webDomain}/fed/channels/${channel_id}/outbox`,
+ () => Message.count({ where: { channel_id } }),
+ async (before, after) => {
+ const query: FindManyOptions<Message> & {
+ where: { id?: FindOperator<string> | FindOperator<string>[] };
+ } = {
+ order: { timestamp: "DESC" },
+ take: 20,
+ where: { channel_id: channel_id },
+ relations: ["author"],
+ };
+
+ if (after) {
+ if (BigInt(after) > BigInt(Snowflake.generate())) return [];
+ query.where.id = MoreThan(after);
+ } else if (before) {
+ if (BigInt(before) > BigInt(Snowflake.generate())) return [];
+ query.where.id = LessThan(before);
+ }
+
+ const messages = await Message.find(query);
+
+ return messages.map((x) => ({
+ ...x,
+ toAP: () => x.toAnnounceAP(),
+ }));
+ },
+ );
return res.json(ret);
});
diff --git a/src/activitypub/util/OrderedCollection.ts b/src/activitypub/util/OrderedCollection.ts
new file mode 100644
index 00000000..4ad78a81
--- /dev/null
+++ b/src/activitypub/util/OrderedCollection.ts
@@ -0,0 +1,41 @@
+import { APObject, APOrderedCollection } from "activitypub-types";
+import { Request } from "express";
+
+interface ActivityPubable {
+ toAP(): APObject;
+}
+
+export const makeOrderedCollection = async <T extends ActivityPubable>(
+ req: Request,
+ id: string,
+ getTotalElements: () => Promise<number>,
+ getElements: (before?: string, after?: string) => Promise<T[]>,
+): Promise<APOrderedCollection> => {
+ const { page, min_id, max_id } = req.query;
+
+ if (!page)
+ return {
+ "@context": "https://www.w3.org/ns/activitystreams",
+ id: id,
+ type: "OrderedCollection",
+ first: `${id}page=true`,
+ last: `${id}?page=true&min_id=0`,
+ };
+
+ const after = min_id ? `${min_id}` : undefined;
+ const before = max_id ? `${max_id}` : undefined;
+
+ const elems = await getElements(before, after);
+
+ const items = elems.map((elem) => elem.toAP());
+
+ return {
+ "@context": "https://www.w3.org/ns/activitystreams",
+ id: `${id}?page=true`,
+ type: "OrderedCollection",
+ first: `${id}?page=true`,
+ last: `${id}?page=true&min_id=0`,
+ totalItems: await getTotalElements(),
+ items: items,
+ };
+};
diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts
index 46a3758d..32bd715e 100644
--- a/src/util/entities/Message.ts
+++ b/src/util/entities/Message.ts
@@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import type { APNote } from "activitypub-types";
+import type { APAnnounce, APNote } from "activitypub-types";
import {
Column,
CreateDateColumn,
@@ -243,6 +243,19 @@ export class Message extends BaseClass {
};
}
+ toAnnounceAP(): APAnnounce {
+ const { webDomain } = Config.get().federation;
+
+ return {
+ id: `https://${webDomain}/fed/channel/${this.channel_id}/messages/${this.id}`,
+ type: "Announce",
+ actor: `https://${webDomain}/fed/user/${this.author_id}`,
+ published: this.timestamp,
+ to: `https://${webDomain}/fed/channel/${this.channel_id}`,
+ object: this.toAP(),
+ };
+ }
+
toAP(): APNote {
const { webDomain } = Config.get().federation;
|