diff --git a/src/api/routes/channels/#channel_id/greet.ts b/src/api/routes/channels/#channel_id/greet.ts
new file mode 100644
index 00000000..a0b25b99
--- /dev/null
+++ b/src/api/routes/channels/#channel_id/greet.ts
@@ -0,0 +1,103 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2025 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 { Channel, emitEvent, GreetRequestSchema, Message, MessageCreateEvent, MessageType, Permissions, Sticker } from "@spacebar/util";
+import { Request, Response, Router } from "express";
+import { In } from "typeorm";
+
+const router: Router = Router();
+
+router.post(
+ "/",
+ route({
+ requestBody: "GreetRequestSchema",
+ permission: "MANAGE_CHANNELS",
+ responses: {
+ 200: {
+ body: "Message",
+ },
+ 404: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const payload = req.body as GreetRequestSchema;
+ const { channel_id } = req.params;
+
+ const channel = await Channel.findOneOrFail({
+ where: { id: channel_id },
+ });
+
+ const targetMessage = await Message.findOneOrFail({
+ where: {
+ id: payload.message_reference?.message_id,
+ channel_id: payload.message_reference?.channel_id,
+ guild_id: payload.message_reference?.guild_id,
+ },
+ });
+
+ if (!channel.isDm() && targetMessage.type != MessageType.GUILD_MEMBER_JOIN)
+ return res.status(400).json({
+ code: 400, // TODO: what's the actual error code?
+ message: "Cannot send greet message referencing this message.",
+ });
+
+ if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.SEND_MESSAGES)) {
+ return res.status(403).json({
+ code: 403,
+ message: "Missing Permissions: SEND_MESSAGES",
+ });
+ }
+
+ const specCompliant = true; // incase we want to allow clients to add more than one sticker to pick
+ if (specCompliant && payload.sticker_ids.length != 1)
+ return res.status(400).json({
+ code: 400,
+ message: "Must include exactly one sticker.",
+ });
+
+ const stickers = await Sticker.find({ where: { id: In(payload.sticker_ids) } });
+
+ const randomSticker = stickers[Math.floor(Math.random() * stickers.length)];
+
+ const message = Message.create({
+ channel_id: channel_id,
+ author_id: req.user_id,
+ type: MessageType.REPLY,
+ message_reference: { ...payload.message_reference, type: 0 },
+ referenced_message: targetMessage,
+ sticker_items: randomSticker ? [{ id: randomSticker.id, name: randomSticker.name, format_type: randomSticker.format_type }] : [],
+ });
+
+ await Promise.all([
+ message.save(),
+ emitEvent({
+ event: "MESSAGE_CREATE",
+ data: message,
+ channel_id,
+ } as MessageCreateEvent),
+ ]);
+
+ res.send(channel);
+ },
+);
+
+export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 40b27947..d24aff05 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -324,7 +324,7 @@ router.post(
if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) {
const limits = Config.get().limits;
- if (limits.absoluteRate.register.enabled) {
+ if (limits.absoluteRate.sendMessage.enabled) {
const count = await Message.count({
where: {
channel_id,
diff --git a/src/api/routes/users/#id/messages.ts b/src/api/routes/users/#id/messages.ts
index c71b8764..aefa8329 100644
--- a/src/api/routes/users/#id/messages.ts
+++ b/src/api/routes/users/#id/messages.ts
@@ -51,4 +51,6 @@ router.get(
},
);
+// TODO: POST to send a message to the user
+
export default router;
diff --git a/src/util/entities/Message.ts b/src/util/entities/Message.ts
index 74fda7e5..000102bf 100644
--- a/src/util/entities/Message.ts
+++ b/src/util/entities/Message.ts
@@ -210,6 +210,7 @@ export class Message extends BaseClass {
message_id: string;
channel_id?: string;
guild_id?: string;
+ type?: number; // 0 = DEFAULT, 1 = FORWARD
};
@JoinColumn({ name: "message_reference_id" })
diff --git a/src/util/schemas/GreetRequestSchema.ts b/src/util/schemas/GreetRequestSchema.ts
new file mode 100644
index 00000000..62294ece
--- /dev/null
+++ b/src/util/schemas/GreetRequestSchema.ts
@@ -0,0 +1,31 @@
+/*
+ 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 { ConnectedAccountTokenData } from "../interfaces";
+import { AllowedMentions } from "@spacebar/util*";
+
+export interface GreetRequestSchema {
+ sticker_ids: string[];
+ allowed_mentions?: AllowedMentions;
+ message_reference?: {
+ message_id: string;
+ channel_id?: string;
+ guild_id?: string;
+ fail_if_not_exists?: boolean;
+ };
+}
\ No newline at end of file
diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts
index 68c38fb5..9bba9cb4 100644
--- a/src/util/schemas/index.ts
+++ b/src/util/schemas/index.ts
@@ -41,6 +41,7 @@ export * from "./EmojiCreateSchema";
export * from "./EmojiModifySchema";
export * from "./ForgotPasswordSchema";
export * from "./GatewayPayloadSchema";
+export * from "./GreetRequestSchema";
export * from "./GuildCreateSchema";
export * from "./GuildSubscriptionsBulkSchema";
export * from "./GuildTemplateCreateSchema";