diff --git a/helpers/channels/editChannel.ts b/helpers/channels/editChannel.ts index de1afdae6..57e97a83b 100644 --- a/helpers/channels/editChannel.ts +++ b/helpers/channels/editChannel.ts @@ -63,6 +63,21 @@ export async function editChannel( deny: overwrite.deny ? bot.utils.calculateBits(overwrite.deny) : null, })) : undefined, + available_tags: options.availableTags + ? options.availableTags.map((availableTag) => ({ + id: availableTag.id, + name: availableTag.name, + moderated: availableTag.moderated, + emoji_id: availableTag.emojiId, + emoji_name: availableTag.emojiName, + })) + : undefined, + default_reaction_emoji: options.defaultReactionEmoji + ? { + emoji_id: options.defaultReactionEmoji.emojiId, + emoji_name: options.defaultReactionEmoji.emojiName, + } + : undefined, reason, }, ); @@ -160,4 +175,27 @@ export interface ModifyChannel { locked?: boolean; /** whether non-moderators can add other non-moderators to a thread; only available on private threads */ invitable?: boolean; + + /** The set of tags that can be used in a GUILD_FORUM channel */ + availableTags?: { + /** The id of the tag */ + id: string; + /** The name of the tag (0-20 characters) */ + name: string; + /** Whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission */ + moderated: boolean; + /** The id of a guild's custom emoji At most one of emoji_id and emoji_name may be set. */ + emojiId: string; + /** The unicode character of the emoji */ + emojiName: string; + }[]; + /** the emoji to show in the add reaction button on a thread in a GUILD_FORUM channel */ + defaultReactionEmoji?: { + /** The id of a guild's custom emoji */ + emojiId: string; + /** The unicode character of the emoji */ + emojiName: string | null; + }; + /** the initial rate_limit_per_user to set on newly created threads in a channel. this field is copied to the thread at creation time and does not live update. */ + defaultThreadRateLimitPerUser: number; } diff --git a/plugins/permissions/src/channels/createChannel.ts b/plugins/permissions/src/channels/createChannel.ts new file mode 100644 index 000000000..6e9cf771b --- /dev/null +++ b/plugins/permissions/src/channels/createChannel.ts @@ -0,0 +1,54 @@ +import { BotWithCache, ChannelTypes, PermissionStrings } from "../../deps.ts"; +import { requireBotGuildPermissions } from "../permissions.ts"; + +export default function createChannel(bot: BotWithCache) { + const createChannelOld = bot.helpers.createChannel; + + bot.helpers.createChannel = async function (guildId, options, reason) { + const guild = bot.guilds.get(guildId); + + if (guild) { + if (options?.rateLimitPerUser && !(0 < options.rateLimitPerUser && options.rateLimitPerUser < 21600)) { + throw new Error("Amount of seconds a user has to wait before sending another message must be between 0-21600"); + } + + if (options?.name && !bot.utils.validateLength(options.name, { min: 1, max: 100 })) { + throw new Error("The channel name must be between 1-100 characters."); + } + + const requiredPerms: PermissionStrings[] = []; + + requiredPerms.push("MANAGE_CHANNELS"); + + if (options?.permissionOverwrites) requiredPerms.push("MANAGE_ROLES"); + + if (options?.type === ChannelTypes.GuildNews && !guild.toggles.has("news")) { + throw new Error("The NEWS feature is missing in this guild to be able to modify the channel type."); + } + + if ( + options?.topic && !bot.utils.validateLength(options.topic, { + min: 1, + max: options.type === ChannelTypes.GuildForum ? 4096 : 1024, + }) + ) { + throw new Error("The topic length must be between 1 and 1024 (4096 if forum)"); + } + + if (options?.userLimit && options.userLimit > 99) { + throw new Error("The user limit must be less than 99."); + } + + if (options?.parentId) { + const category = bot.channels.get(options.parentId); + if (category && category.type !== ChannelTypes.GuildCategory) { + throw new Error("The parent id must be for a category channel type."); + } + } + + requireBotGuildPermissions(bot, guild, requiredPerms); + } + + return await createChannelOld(guildId, options, reason); + }; +} diff --git a/plugins/permissions/src/channels/mod.ts b/plugins/permissions/src/channels/mod.ts index 4fd36f48c..46f3a1c90 100644 --- a/plugins/permissions/src/channels/mod.ts +++ b/plugins/permissions/src/channels/mod.ts @@ -1,16 +1,18 @@ import { BotWithCache } from "../../deps.ts"; -import setupThreadPermChecks from "./threads/mod.ts"; -import setupForumPermChecks from "./forums/mod.ts"; -import setupStagePermChecks from "./stage.ts"; +import createChannel from "./createChannel.ts"; import deleteChannel from "./deleteChannel.ts"; import deleteChannelOverwrite from "./deleteChannelOverwrite.ts"; import editChannel from "./editChannel.ts"; import editChannelOverwrite from "./editChannelOverwrite.ts"; import followChannel from "./followChannel.ts"; +import setupForumPermChecks from "./forums/mod.ts"; import getChannelWebhooks from "./getChannelWebhooks.ts"; +import setupStagePermChecks from "./stage.ts"; import swapChannels from "./swapChannels.ts"; +import setupThreadPermChecks from "./threads/mod.ts"; export default function setupChannelPermChecks(bot: BotWithCache) { + createChannel(bot); setupThreadPermChecks(bot); setupForumPermChecks(bot); setupStagePermChecks(bot); diff --git a/types/discord.ts b/types/discord.ts index 540b08833..505d6b43d 100644 --- a/types/discord.ts +++ b/types/discord.ts @@ -17,7 +17,6 @@ import { GuildNsfwLevel, IntegrationExpireBehaviors, InteractionTypes, - Locales, Localization, MessageActivityTypes, MessageComponentTypes, @@ -707,7 +706,7 @@ export interface DiscordChannel { position?: number; /** The name of the channel (1-100 characters) */ name?: string; - /** The channel topic (0-1024 characters) */ + /** The channel topic (0-4096 characters for GUILD_FORUM channels, 0-1024 characters for all others) */ topic?: string | null; /** The bitrate (in bits) of the voice or stage channel */ bitrate?: number; @@ -752,6 +751,14 @@ export interface DiscordChannel { permissions?: string; /** When a thread is created this will be true on that channel payload for the thread. */ newly_created?: boolean; + /** The set of tags that can be used in a GUILD_FORUM channel */ + available_tags: DiscordForumTag[]; + /** The IDs of the set of tags that have been applied to a thread in a GUILD_FORUM channel */ + applied_tags: string[]; + /** the emoji to show in the add reaction button on a thread in a GUILD_FORUM channel */ + default_reaction_emoji?: DiscordDefaultReactionEmoji | null; + /** the initial rate_limit_per_user to set on newly created threads in a channel. this field is copied to the thread at creation time and does not live update. */ + default_thread_rate_limit_per_user: number; } /** https://discord.com/developers/docs/topics/gateway#presence-update */ @@ -2425,3 +2432,23 @@ export interface DiscordInstallParams { /** the permissions to request for the bot role */ permissions: string; } + +export interface DiscordForumTag { + /** The id of the tag */ + id: string; + /** The name of the tag (0-20 characters) */ + name: string; + /** Whether this tag can only be added to or removed from threads by a member with the MANAGE_THREADS permission */ + moderated: boolean; + /** The id of a guild's custom emoji At most one of emoji_id and emoji_name may be set. */ + emoji_id: string; + /** The unicode character of the emoji */ + emoji_name: string | null; +} + +export interface DiscordDefaultReactionEmoji { + /** The id of a guild's custom emoji */ + emoji_id: string; + /** The unicode character of the emoji */ + emoji_name: string | null; +} diff --git a/types/shared.ts b/types/shared.ts index 83379c020..d2cfef2f4 100644 --- a/types/shared.ts +++ b/types/shared.ts @@ -41,7 +41,10 @@ export enum UserFlags { /** https://discord.com/developers/docs/resources/channel#channels-resource */ export enum ChannelFlags { None, + /** this thread is pinned to the top of its parent `GUILD_FORUM` channel */ Pinned = 1 << 1, + /** Whether a tag is required to be specified when creating a thread in a `GUILD_FORUM` channel. Tags are specified in the `applied_tags` field. */ + RequireTag, } /** https://discord.com/developers/docs/resources/guild#integration-object-integration-expire-behaviors */