Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ A Discord moderation and utility bot built with [Bun](https://bun.sh), [discord.
- **Moderation Activity** — View staff moderation stats (infractions dealt, requests reviewed/made), filterable by month and year.
- **Auto-Publish** — Automatically crosspost messages in announcement channels.
- **Auto-Reactions** — Add configured reactions to messages in specified channels.
- **Auto-Threads** — Automatically start threads on messages in specified channels.
- **Media Channels** — Enforce attachment requirements in specified channels.
- **Scheduled Messages** — Cron-based scheduled messages with Sentry monitor slugs.
- **Role Requests** — Configurable role request channels with optional TTL.
Expand Down
14 changes: 14 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ auto_reactions:
exclude_patterns: ["regex"] # Optional
```

### Auto-Threads

```yaml
auto_threads:
- channel_id: "<channel_id>"
# $USERNAME = user's username
# $SURFACE_NAME = user's username and surface name, if any
# $USER_ID = user's ID
name: "$SURFACE_NAME's thread"
role_scoping:
include_roles: ["<role_id>"]
exclude_roles: ["<role_id>"]
```

### Media Channels

```yaml
Expand Down
20 changes: 20 additions & 0 deletions src/events/MessageCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export default class MessageCreate extends EventListener {

MessageCache.cache(message);
MessageCreate._handleAutoReactions(message, config);
MessageCreate._handleAutoThreads(message, config);
await MessageCreate._handleMediaChannel(message, config);

// Handle media conversion
Expand Down Expand Up @@ -235,6 +236,25 @@ export default class MessageCreate extends EventListener {
}
}

private static _handleAutoThreads(message: Message<true>, config: GuildConfig): void {
const autoThreadChannel = config.data.auto_threads
.find(autoThreadChannel => autoThreadChannel.channel_id === message.channel.id);

if (!autoThreadChannel || !message.member) return;

const inScope = config.roleInScope(message.member, autoThreadChannel.role_scoping);

if (!inScope) return;

message.startThread({
name: autoThreadChannel.name
.replace("$USERNAME", `@${message.author.username}`)
.replace("$SURFACE_NAME", getSurfaceName(message.member))
.replace("$USER_ID", message.author.id),
reason: `Auto-thread created from @${message.author.username} (${message.author.id})'s message with ID ${message.id} in #${message.channel.name}`
}).catch(() => null);
}

private static async _handleMediaConversion(message: Message<true>, config: GuildConfig): Promise<void> {
if (!message.attachments.size || message.content) return;

Expand Down
17 changes: 17 additions & 0 deletions src/managers/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,22 @@ const autoReactionSchema = z.object({
exclude_patterns: z.array(stringSchema).default([])
});

const autoThreadSchema = z.object({
// The channel to listen for messages in
channel_id: snowflakeSchema,
/**
* The name of the thread to create
*
* ## Args
*
* - `$USERNAME`: The username of the message author
* - `$SURFACE_NAME`: The username and, if available, visible name of the message author on the current surface (e.g., their nickname in that guild)
* - `$USER_ID`: The ID of the message author
*/
name: placeholderString(["USERNAME", "SURFACE_NAME", "USER_ID"], 1, 100).default("$SURFACE_NAME's thread"),
role_scoping: roleScopingSchema.default({})
});

const reportSchema = z.object({
// Channel to send reports to
channel_id: snowflakeSchema,
Expand Down Expand Up @@ -546,6 +562,7 @@ export const rawGuildConfigSchema = z.object({
// Automatically publish announcement messages in these channels
auto_publish_announcements: z.array(snowflakeSchema).default([]),
auto_reactions: z.array(autoReactionSchema).default([]),
auto_threads: z.array(autoThreadSchema).default([]),
// Toggle the `SendMessages` permission in a channel depending on whether a stage event is active
stage_event_overrides: z.array(stageEventOverrideSchema).default([]),
notification_channel_id: snowflakeSchema.optional(),
Expand Down
Loading