summary refs log tree commit diff
path: root/src/api/routes/channels/#channel_id/messages/index.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/routes/channels/#channel_id/messages/index.ts')
-rw-r--r--src/api/routes/channels/#channel_id/messages/index.ts277
1 files changed, 148 insertions, 129 deletions
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 7f0c9fb5..a15b462d 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -16,165 +16,172 @@
 	along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
 
-import { Router, Response, Request } from "express";
+import { handleMessage, postHandleMessage, route } from "@spacebar/api";
 import {
 	Attachment,
 	Channel,
-	ChannelType,
 	Config,
 	DmChannelDTO,
-	emitEvent,
 	FieldErrors,
-	getPermission,
+	Member,
 	Message,
 	MessageCreateEvent,
-	Snowflake,
-	uploadFile,
-	Member,
 	MessageCreateSchema,
+	Reaction,
 	ReadState,
 	Rights,
-	Reaction,
+	Snowflake,
 	User,
+	emitEvent,
+	getPermission,
+	isTextChannel,
+	uploadFile,
 } from "@spacebar/util";
+import { Request, Response, Router } from "express";
 import { HTTPError } from "lambert-server";
-import { handleMessage, postHandleMessage, route } from "@spacebar/api";
 import multer from "multer";
 import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm";
 import { URL } from "url";
 
 const router: Router = Router();
 
-export default router;
-
-export function isTextChannel(type: ChannelType): boolean {
-	switch (type) {
-		case ChannelType.GUILD_STORE:
-		case ChannelType.GUILD_VOICE:
-		case ChannelType.GUILD_STAGE_VOICE:
-		case ChannelType.GUILD_CATEGORY:
-		case ChannelType.GUILD_FORUM:
-		case ChannelType.DIRECTORY:
-			throw new HTTPError("not a text channel", 400);
-		case ChannelType.DM:
-		case ChannelType.GROUP_DM:
-		case ChannelType.GUILD_NEWS:
-		case ChannelType.GUILD_NEWS_THREAD:
-		case ChannelType.GUILD_PUBLIC_THREAD:
-		case ChannelType.GUILD_PRIVATE_THREAD:
-		case ChannelType.GUILD_TEXT:
-		case ChannelType.ENCRYPTED:
-		case ChannelType.ENCRYPTED_THREAD:
-			return true;
-		default:
-			throw new HTTPError("unimplemented", 400);
-	}
-}
-
 // https://discord.com/developers/docs/resources/channel#create-message
 // get messages
-router.get("/", route({}), async (req: Request, res: Response) => {
-	const channel_id = req.params.channel_id;
-	const channel = await Channel.findOneOrFail({
-		where: { id: channel_id },
-	});
-	if (!channel) throw new HTTPError("Channel not found", 404);
-
-	isTextChannel(channel.type);
-	const around = req.query.around ? `${req.query.around}` : undefined;
-	const before = req.query.before ? `${req.query.before}` : undefined;
-	const after = req.query.after ? `${req.query.after}` : undefined;
-	const limit = Number(req.query.limit) || 50;
-	if (limit < 1 || limit > 100)
-		throw new HTTPError("limit must be between 1 and 100", 422);
-
-	const halfLimit = Math.floor(limit / 2);
-
-	const permissions = await getPermission(
-		req.user_id,
-		channel.guild_id,
-		channel_id,
-	);
-	permissions.hasThrow("VIEW_CHANNEL");
-	if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
-
-	const query: FindManyOptions<Message> & {
-		where: { id?: FindOperator<string> | FindOperator<string>[] };
-	} = {
-		order: { timestamp: "DESC" },
-		take: limit,
-		where: { channel_id },
-		relations: [
-			"author",
-			"webhook",
-			"application",
-			"mentions",
-			"mention_roles",
-			"mention_channels",
-			"sticker_items",
-			"attachments",
-		],
-	};
-
-	if (after) {
-		if (BigInt(after) > BigInt(Snowflake.generate()))
-			return res.status(422);
-		query.where.id = MoreThan(after);
-	} else if (before) {
-		if (BigInt(before) < BigInt(req.params.channel_id))
-			return res.status(422);
-		query.where.id = LessThan(before);
-	} else if (around) {
-		query.where.id = [
-			MoreThan((BigInt(around) - BigInt(halfLimit)).toString()),
-			LessThan((BigInt(around) + BigInt(halfLimit)).toString()),
-		];
-
-		return res.json([]); // TODO: fix around
-	}
+router.get(
+	"/",
+	route({
+		query: {
+			around: {
+				type: "string",
+			},
+			before: {
+				type: "string",
+			},
+			after: {
+				type: "string",
+			},
+			limit: {
+				type: "number",
+				description:
+					"max number of messages to return (1-100). defaults to 50",
+			},
+		},
+		responses: {
+			200: {
+				body: "APIMessageArray",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {},
+			404: {},
+		},
+	}),
+	async (req: Request, res: Response) => {
+		const channel_id = req.params.channel_id;
+		const channel = await Channel.findOneOrFail({
+			where: { id: channel_id },
+		});
+		if (!channel) throw new HTTPError("Channel not found", 404);
+
+		isTextChannel(channel.type);
+		const around = req.query.around ? `${req.query.around}` : undefined;
+		const before = req.query.before ? `${req.query.before}` : undefined;
+		const after = req.query.after ? `${req.query.after}` : undefined;
+		const limit = Number(req.query.limit) || 50;
+		if (limit < 1 || limit > 100)
+			throw new HTTPError("limit must be between 1 and 100", 422);
+
+		const permissions = await getPermission(
+			req.user_id,
+			channel.guild_id,
+			channel_id,
+		);
+		permissions.hasThrow("VIEW_CHANNEL");
+		if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
+
+		const query: FindManyOptions<Message> & {
+			where: { id?: FindOperator<string> | FindOperator<string>[] };
+		} = {
+			order: { timestamp: "DESC" },
+			take: limit,
+			where: { channel_id },
+			relations: [
+				"author",
+				"webhook",
+				"application",
+				"mentions",
+				"mention_roles",
+				"mention_channels",
+				"sticker_items",
+				"attachments",
+			],
+		};
+
+		let messages: Message[];
+		if (after) {
+			if (BigInt(after) > BigInt(Snowflake.generate()))
+				return res.status(422);
+			query.where.id = MoreThan(after);
+			messages = await Message.find(query);
+		} else if (before) {
+			if (BigInt(before) < BigInt(req.params.channel_id))
+				return res.status(422);
+			query.where.id = LessThan(before);
+			messages = await Message.find(query);
+		} else if (around) {
+			query.take = Math.floor(limit / 2);
+			query.where.id = LessThan(around);
+			const messages_before = await Message.find(query);
+			query.where.id = MoreThan(around);
+			const messages_after = await Message.find(query);
+			messages = messages_before.concat(messages_after);
+		} else {
+			throw new HTTPError("after, around or before must be present", 422);
+		}
 
-	const messages = await Message.find(query);
-	const endpoint = Config.get().cdn.endpointPublic;
+		const endpoint = Config.get().cdn.endpointPublic;
 
-	return res.json(
-		messages.map((x: Partial<Message>) => {
-			(x.reactions || []).forEach((y: Partial<Reaction>) => {
-				// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-				//@ts-ignore
-				if ((y.user_ids || []).includes(req.user_id)) y.me = true;
-				delete y.user_ids;
-			});
-			if (!x.author)
-				x.author = User.create({
-					id: "4",
-					discriminator: "0000",
-					username: "Spacebar Ghost",
-					public_flags: 0,
+		return res.json(
+			messages.map((x: Partial<Message>) => {
+				(x.reactions || []).forEach((y: Partial<Reaction>) => {
+					// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+					//@ts-ignore
+					if ((y.user_ids || []).includes(req.user_id)) y.me = true;
+					delete y.user_ids;
+				});
+				if (!x.author)
+					x.author = User.create({
+						id: "4",
+						discriminator: "0000",
+						username: "Spacebar Ghost",
+						public_flags: 0,
+					});
+				x.attachments?.forEach((y: Attachment) => {
+					// dynamically set attachment proxy_url in case the endpoint changed
+					const uri = y.proxy_url.startsWith("http")
+						? y.proxy_url
+						: `https://example.org${y.proxy_url}`;
+					y.proxy_url = `${endpoint == null ? "" : endpoint}${
+						new URL(uri).pathname
+					}`;
 				});
-			x.attachments?.forEach((y: Attachment) => {
-				// dynamically set attachment proxy_url in case the endpoint changed
-				const uri = y.proxy_url.startsWith("http")
-					? y.proxy_url
-					: `https://example.org${y.proxy_url}`;
-				y.proxy_url = `${endpoint == null ? "" : endpoint}${
-					new URL(uri).pathname
-				}`;
-			});
 
-			/**
+				/**
 			Some clients ( discord.js ) only check if a property exists within the response,
 			which causes errors when, say, the `application` property is `null`.
 			**/
 
-			// for (var curr in x) {
-			// 	if (x[curr] === null)
-			// 		delete x[curr];
-			// }
+				// for (var curr in x) {
+				// 	if (x[curr] === null)
+				// 		delete x[curr];
+				// }
 
-			return x;
-		}),
-	);
-});
+				return x;
+			}),
+		);
+	},
+);
 
 // TODO: config max upload size
 const messageUpload = multer({
@@ -205,9 +212,19 @@ router.post(
 		next();
 	},
 	route({
-		body: "MessageCreateSchema",
+		requestBody: "MessageCreateSchema",
 		permission: "SEND_MESSAGES",
 		right: "SEND_MESSAGES",
+		responses: {
+			200: {
+				body: "Message",
+			},
+			400: {
+				body: "APIErrorResponse",
+			},
+			403: {},
+			404: {},
+		},
 	}),
 	async (req: Request, res: Response) => {
 		const { channel_id } = req.params;
@@ -366,3 +383,5 @@ router.post(
 		return res.json(message);
 	},
 );
+
+export default router;