Skip to content

Commit 8f8128b

Browse files
committed
feat(auto-threads): Add auto-thread creation and config schema
Automatically create threads from messages in configured channels. Adds a call to MessageCreate._handleAutoThreads and implements the handler to lookup per-channel auto-thread config, skip users with excluded roles, and start a thread with a templated name (supports $USERNAME, $SURFACE_NAME, $USER_ID). Updates the guild config schema with autoThreadSchema and registers auto_threads in rawGuildConfigSchema (includes channel_id, name with placeholders, and exclude_roles). Thread creation errors are caught to avoid unhandled rejections.
1 parent ec70a5e commit 8f8128b

2 files changed

Lines changed: 39 additions & 0 deletions

File tree

src/events/MessageCreate.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export default class MessageCreate extends EventListener {
6161

6262
Messages.queue(message);
6363
MessageCreate._handleAutoReactions(message, config);
64+
MessageCreate._handleAutoThreads(message, config);
6465
await MessageCreate._handleMediaChannel(message, config);
6566

6667
// Handle media conversion
@@ -235,6 +236,27 @@ export default class MessageCreate extends EventListener {
235236
}
236237
}
237238

239+
private static _handleAutoThreads(message: Message<true>, config: GuildConfig): void {
240+
const autoThreadChannel = config.data.auto_threads
241+
.find(autoThreadChannel => autoThreadChannel.channel_id === message.channel.id);
242+
243+
if (!autoThreadChannel) return;
244+
245+
const isExcluded = message.member?.roles.cache.some(role =>
246+
autoThreadChannel.exclude_roles.includes(role.id)
247+
);
248+
249+
if (isExcluded) return;
250+
251+
message.startThread({
252+
name: autoThreadChannel.name
253+
.replace("$USERNAME", `@${message.author.username}`)
254+
.replace("$SURFACE_NAME", getSurfaceName(message.member ?? message.author))
255+
.replace("$USER_ID", message.author.id),
256+
reason: `Auto-thread created from @${message.author.username} (${message.author.id})'s message with ID ${message.id} in #${message.channel.name}`
257+
}).catch(() => null);
258+
}
259+
238260
private static async _handleMediaConversion(message: Message<true>, config: GuildConfig): Promise<void> {
239261
if (!message.attachments.size || message.content) return;
240262

src/managers/config/schema.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,22 @@ const autoReactionSchema = z.object({
293293
exclude_patterns: z.array(stringSchema).default([])
294294
});
295295

296+
const autoThreadSchema = z.object({
297+
// The channel to listen for messages in
298+
channel_id: snowflakeSchema,
299+
/**
300+
* The name of the thread to create
301+
*
302+
* ## Args
303+
*
304+
* - `$USERNAME`: The username of the message author
305+
* - `$SURFACE_NAME`: The username and, if available, visible name of the message author on the current surface (e.g., their nickname in that guild)
306+
* - `$USER_ID`: The ID of the message author
307+
*/
308+
name: placeholderString(["USERNAME", "SURFACE_NAME", "USER_ID"], 1, 100).default("$SURFACE_NAME's thread"),
309+
exclude_roles: z.array(snowflakeSchema).default([]),
310+
});
311+
296312
const reportSchema = z.object({
297313
// Channel to send reports to
298314
report_channel: snowflakeSchema,
@@ -540,6 +556,7 @@ export const rawGuildConfigSchema = z.object({
540556
// Automatically publish announcement messages in these channels
541557
auto_publish_announcements: z.array(snowflakeSchema).default([]),
542558
auto_reactions: z.array(autoReactionSchema).default([]),
559+
auto_threads: z.array(autoThreadSchema).default([]),
543560
// Toggle the `SendMessages` permission in a channel depending on whether a stage event is active
544561
stage_event_overrides: z.array(stageEventOverrideSchema).default([]),
545562
notification_channel: snowflakeSchema.optional(),

0 commit comments

Comments
 (0)