From d58414c9c5a79992a33692f6b412357a2ba7b60d Mon Sep 17 00:00:00 2001 From: Rory& Date: Tue, 8 Jul 2025 22:45:57 +0200 Subject: [PATCH] Modern pins endpoints --- .../#channel_id/messages/pins/index.ts | 194 ++++++++++++++++++ src/api/routes/channels/#channel_id/pins.ts | 1 + 2 files changed, 195 insertions(+) create mode 100644 src/api/routes/channels/#channel_id/messages/pins/index.ts diff --git a/src/api/routes/channels/#channel_id/messages/pins/index.ts b/src/api/routes/channels/#channel_id/messages/pins/index.ts new file mode 100644 index 00000000..32e3e901 --- /dev/null +++ b/src/api/routes/channels/#channel_id/messages/pins/index.ts @@ -0,0 +1,194 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import { route } from "@spacebar/api"; +import { + ChannelPinsUpdateEvent, + Config, + DiscordApiErrors, + emitEvent, + Message, + MessageCreateEvent, + MessageUpdateEvent, + User, +} from "@spacebar/util"; +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.put( + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; + + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); + + // * in dm channels anyone can pin messages -> only check for guilds + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + + const pinned_count = await Message.count({ + where: { channel: { id: channel_id }, pinned: true }, + }); + const { maxPins } = Config.get().limits.channel; + if (pinned_count >= maxPins) + throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); + + message.pinned = true; + + const author = await User.getPublicUser(req.user_id); + + const systemPinMessage = Message.create({ + timestamp: new Date(), + type: 6, + guild_id: message.guild_id, + channel_id: message.channel_id, + author, + message_reference: { + message_id: message.id, + channel_id: message.channel_id, + guild_id: message.guild_id, + }, + reactions: [], + attachments: [], + embeds: [], + sticker_items: [], + edited_timestamp: undefined, + mentions: [], + mention_channels: [], + mention_roles: [], + mention_everyone: false, + }); + + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + systemPinMessage.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: message.channel_id, + data: systemPinMessage, + } as MessageCreateEvent), + ]); + + res.sendStatus(204); + }, +); + +router.delete( + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; + + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); + + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + + message.pinned = false; + + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + ]); + + res.sendStatus(204); + }, +); + +router.get( + "/", + route({ + permission: ["READ_MESSAGE_HISTORY"], + responses: { + 200: { + body: "APIMessageArray", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + + const pins = await Message.find({ + where: { channel_id: channel_id, pinned: true }, + relations: ["author"], + }); + + res.send({ + items: pins, + has_more: false + }); + }, +); + +export default router; diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts index 96162870..bf746148 100644 --- a/src/api/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts @@ -31,6 +31,7 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); +// This is the old endpoint router.put( "/:message_id", route({