diff --git a/src/cache.ts b/src/cache.ts index 2bab9d370..5d82f4668 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,29 +1,32 @@ // deno-lint-ignore-file require-await no-explicit-any prefer-const - -import { Channel, Guild, Member, Message } from "./structures/mod.ts"; +import { ChannelStruct } from "./structures/channel.ts"; +import { GuildStruct } from "./structures/guild.ts"; +import { MemberStruct } from "./structures/member.ts"; +import { MessageStruct } from "./structures/message.ts"; import { Emoji } from "./types/emojis/emoji.ts"; +import { PresenceUpdate } from "./types/misc/presence_update.ts"; import { Collection } from "./util/collection.ts"; export const cache = { isReady: false, /** All of the guild objects the bot has access to, mapped by their Ids */ - guilds: new Collection(), + guilds: new Collection(), /** All of the channel objects the bot has access to, mapped by their Ids */ - channels: new Collection(), + channels: new Collection(), /** All of the message objects the bot has cached since the bot acquired `READY` state, mapped by their Ids */ - messages: new Collection(), + messages: new Collection(), /** All of the member objects that have been cached since the bot acquired `READY` state, mapped by their Ids */ - members: new Collection(), + members: new Collection(), /** All of the unavailable guilds, mapped by their Ids (id, timestamp) */ unavailableGuilds: new Collection(), /** All of the presence update objects received in PRESENCE_UPDATE gateway event, mapped by their user Id */ - presences: new Collection(), + presences: new Collection(), fetchAllMembersProcessingRequests: new Collection< string, ( value: - | Collection - | PromiseLike>, + | Collection + | PromiseLike>, ) => void >(), executedSlashCommands: new Collection(), @@ -78,28 +81,28 @@ export type TableName = function set( table: "guilds", key: string, - value: Guild, -): Promise>; + value: GuildStruct, +): Promise>; function set( table: "channels", key: string, - value: Channel, -): Promise>; + value: ChannelStruct, +): Promise>; function set( table: "messages", key: string, - value: Message, -): Promise>; + value: MessageStruct, +): Promise>; function set( table: "members", key: string, - value: Member, -): Promise>; + value: MemberStruct, +): Promise>; function set( table: "presences", key: string, - value: PresenceUpdatePayload, -): Promise>; + value: PresenceUpdate, +): Promise>; function set( table: "unavailableGuilds", key: string, @@ -109,41 +112,60 @@ async function set(table: TableName, key: string, value: any) { return cache[table].set(key, value); } -function get(table: "guilds", key: string): Promise; -function get(table: "channels", key: string): Promise; -function get(table: "messages", key: string): Promise; -function get(table: "members", key: string): Promise; +function get(table: "guilds", key: string): Promise; +function get( + table: "channels", + key: string, +): Promise; +function get(table: "messages", key: string): Promise; +function get(table: "members", key: string): Promise; function get( table: "presences", key: string, -): Promise; +): Promise; function get( table: "unavailableGuilds", key: string, -): Promise; +): Promise; async function get(table: TableName, key: string) { return cache[table].get(key); } function forEach( table: "guilds", - callback: (value: Guild, key: string, map: Map) => unknown, + callback: ( + value: GuildStruct, + key: string, + map: Map, + ) => unknown, ): void; function forEach( table: "unavailableGuilds", - callback: (value: Guild, key: string, map: Map) => unknown, + callback: (value: number, key: string, map: Map) => unknown, ): void; function forEach( table: "channels", - callback: (value: Channel, key: string, map: Map) => unknown, + callback: ( + value: ChannelStruct, + key: string, + map: Map, + ) => unknown, ): void; function forEach( table: "messages", - callback: (value: Message, key: string, map: Map) => unknown, + callback: ( + value: MemberStruct, + key: string, + map: Map, + ) => unknown, ): void; function forEach( table: "members", - callback: (value: Member, key: string, map: Map) => unknown, + callback: ( + value: MemberStruct, + key: string, + map: Map, + ) => unknown, ): void; function forEach( table: TableName, @@ -154,24 +176,24 @@ function forEach( function filter( table: "guilds", - callback: (value: Guild, key: string) => boolean, -): Promise>; + callback: (value: GuildStruct, key: string) => boolean, +): Promise>; function filter( table: "unavailableGuilds", - callback: (value: Guild, key: string) => boolean, -): Promise>; + callback: (value: number, key: string) => boolean, +): Promise>; function filter( table: "channels", - callback: (value: Channel, key: string) => boolean, -): Promise>; + callback: (value: ChannelStruct, key: string) => boolean, +): Promise>; function filter( table: "messages", - callback: (value: Message, key: string) => boolean, -): Promise>; + callback: (value: MessageStruct, key: string) => boolean, +): Promise>; function filter( table: "members", - callback: (value: Member, key: string) => boolean, -): Promise>; + callback: (value: MemberStruct, key: string) => boolean, +): Promise>; async function filter( table: TableName, callback: (value: any, key: string) => boolean, diff --git a/src/handlers/commands/APPLICATION_COMMAND_CREATE.ts b/src/handlers/commands/APPLICATION_COMMAND_CREATE.ts index 7b936b250..592fef829 100644 --- a/src/handlers/commands/APPLICATION_COMMAND_CREATE.ts +++ b/src/handlers/commands/APPLICATION_COMMAND_CREATE.ts @@ -1,5 +1,6 @@ import { eventHandlers } from "../../bot.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { DiscordApplicationCommandCreateUpdateDelete } from "../../types/interactions/application_command_create_update_delete.ts"; export function handleApplicationCommandCreate( data: DiscordGatewayPayload, diff --git a/src/handlers/commands/APPLICATION_COMMAND_DELETE.ts b/src/handlers/commands/APPLICATION_COMMAND_DELETE.ts index 69b3f144d..8f73f8ef5 100644 --- a/src/handlers/commands/APPLICATION_COMMAND_DELETE.ts +++ b/src/handlers/commands/APPLICATION_COMMAND_DELETE.ts @@ -1,5 +1,6 @@ import { eventHandlers } from "../../bot.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { DiscordApplicationCommandCreateUpdateDelete } from "../../types/interactions/application_command_create_update_delete.ts"; export function handleApplicationCommandDelete(data: DiscordGatewayPayload) { const { diff --git a/src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts b/src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts index 8e5328d33..2c450a496 100644 --- a/src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts +++ b/src/handlers/commands/APPLICATION_COMMAND_UPDATE.ts @@ -1,5 +1,6 @@ import { eventHandlers } from "../../bot.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { DiscordApplicationCommandCreateUpdateDelete } from "../../types/interactions/application_command_create_update_delete.ts"; export function handleApplicationCommandUpdate(data: DiscordGatewayPayload) { const { diff --git a/src/handlers/guilds/GUILD_BAN_ADD.ts b/src/handlers/guilds/GUILD_BAN_ADD.ts index 7fa687f12..26df8e004 100644 --- a/src/handlers/guilds/GUILD_BAN_ADD.ts +++ b/src/handlers/guilds/GUILD_BAN_ADD.ts @@ -1,6 +1,7 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { DiscordGuildBanAddRemove } from "../../types/guilds/guild_ban_add_remove.ts"; export async function handleGuildBanAdd(data: DiscordGatewayPayload) { const payload = data.d as DiscordGuildBanAddRemove; diff --git a/src/handlers/guilds/GUILD_BAN_REMOVE.ts b/src/handlers/guilds/GUILD_BAN_REMOVE.ts index 14022ede5..61301ce4c 100644 --- a/src/handlers/guilds/GUILD_BAN_REMOVE.ts +++ b/src/handlers/guilds/GUILD_BAN_REMOVE.ts @@ -1,6 +1,7 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { DiscordGuildBanAddRemove } from "../../types/guilds/guild_ban_add_remove.ts"; export async function handleGuildBanRemove(data: DiscordGatewayPayload) { const payload = data.d as DiscordGuildBanAddRemove; diff --git a/src/handlers/guilds/GUILD_UPDATE.ts b/src/handlers/guilds/GUILD_UPDATE.ts index cb92b88af..11898a945 100644 --- a/src/handlers/guilds/GUILD_UPDATE.ts +++ b/src/handlers/guilds/GUILD_UPDATE.ts @@ -1,18 +1,19 @@ import { eventHandlers } from "../../bot.ts"; import { cacheHandlers } from "../../cache.ts"; +import { GuildUpdateChange } from "../../types/discordeno/guild_update_change.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import { DiscordGuild } from "../../types/guilds/guild.ts"; export async function handleGuildUpdate(data: DiscordGatewayPayload) { const payload = data.d as DiscordGuild; - const cachedGuild = await cacheHandlers.get("guilds", payload.id); - if (!cachedGuild) return; + const newGuild = await cacheHandlers.get("guilds", payload.id); + if (!newGuild) return; const keysToSkip = [ "roles", - "guild_hashes", - "guild_id", - "max_members", + "guildHashes", + "guildId", + "maxMembers", "emojis", ]; @@ -21,7 +22,7 @@ export async function handleGuildUpdate(data: DiscordGatewayPayload) { if (keysToSkip.includes(key)) return; // @ts-ignore index signature - const cachedValue = cachedGuild[key]; + const cachedValue = newGuild[key]; if (cachedValue !== value) { // Guild create sends undefined and update sends false. if (!cachedValue && !value) return; @@ -34,12 +35,12 @@ export async function handleGuildUpdate(data: DiscordGatewayPayload) { } // @ts-ignore index signature - cachedGuild[key] = value; + newGuild[key] = value; return { key, oldValue: cachedValue, value }; } }).filter((change) => change) as GuildUpdateChange[]; - await cacheHandlers.set("guilds", payload.id, cachedGuild); + await cacheHandlers.set("guilds", payload.id, newGuild); - eventHandlers.guildUpdate?.(cachedGuild, changes); + eventHandlers.guildUpdate?.(newGuild, changes); } diff --git a/src/handlers/integrations/INTEGRATION_CREATE.ts b/src/handlers/integrations/INTEGRATION_CREATE.ts index 4148db518..255df04c1 100644 --- a/src/handlers/integrations/INTEGRATION_CREATE.ts +++ b/src/handlers/integrations/INTEGRATION_CREATE.ts @@ -1,28 +1,17 @@ import { eventHandlers } from "../../bot.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { + DiscordIntegrationCreateUpdate, + IntegrationCreateUpdate, +} from "../../types/integration/integration_create_update.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; export function handleIntegrationCreate( data: DiscordGatewayPayload, ) { - const { - guild_id: guildId, - enable_emoticons: enableEmoticons, - expire_behavior: expireBehavior, - expire_grace_period: expireGracePeriod, - subscriber_count: subscriberCount, - role_id: roleId, - synced_at: syncedAt, - ...rest - } = data.d as IntegrationCreateUpdateEvent; + const payload = data.d as DiscordIntegrationCreateUpdate; - eventHandlers.integrationCreate?.({ - ...rest, - guildId, - enableEmoticons, - expireBehavior, - expireGracePeriod, - syncedAt, - subscriberCount, - roleId, - }); + eventHandlers.integrationCreate?.( + snakeKeysToCamelCase(payload) as IntegrationCreateUpdate, + ); } diff --git a/src/handlers/integrations/INTEGRATION_DELETE.ts b/src/handlers/integrations/INTEGRATION_DELETE.ts index c89ae3d3b..af1ed9d15 100644 --- a/src/handlers/integrations/INTEGRATION_DELETE.ts +++ b/src/handlers/integrations/INTEGRATION_DELETE.ts @@ -1,16 +1,15 @@ import { eventHandlers } from "../../bot.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { + DiscordIntegrationDelete, + IntegrationDelete, +} from "../../types/integration/integration_delete.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; export function handleIntegrationDelete(data: DiscordGatewayPayload) { - const { - guild_id: guildId, - application_id: applicationId, - ...rest - } = data.d as IntegrationDeleteEvent; + const payload = data.d as DiscordIntegrationDelete; - eventHandlers.integrationDelete?.({ - ...rest, - applicationId, - guildId, - }); + eventHandlers.integrationDelete?.( + snakeKeysToCamelCase(payload) as IntegrationDelete, + ); } diff --git a/src/handlers/integrations/INTEGRATION_UPDATE.ts b/src/handlers/integrations/INTEGRATION_UPDATE.ts index 13fb98bed..db56de2cd 100644 --- a/src/handlers/integrations/INTEGRATION_UPDATE.ts +++ b/src/handlers/integrations/INTEGRATION_UPDATE.ts @@ -1,26 +1,15 @@ import { eventHandlers } from "../../bot.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { + DiscordIntegrationCreateUpdate, + IntegrationCreateUpdate, +} from "../../types/integration/integration_create_update.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; export function handleIntegrationUpdate(data: DiscordGatewayPayload) { - const { - enable_emoticons: enableEmoticons, - expire_behavior: expireBehavior, - expire_grace_period: expireGracePeriod, - role_id: roleId, - subscriber_count: subscriberCount, - synced_at: syncedAt, - guild_id: guildId, - ...rest - } = data.d as IntegrationCreateUpdateEvent; + const payload = data.d as DiscordIntegrationCreateUpdate; - eventHandlers.integrationUpdate?.({ - ...rest, - guildId, - subscriberCount, - enableEmoticons, - expireGracePeriod, - roleId, - expireBehavior, - syncedAt, - }); + eventHandlers.integrationUpdate?.( + snakeKeysToCamelCase(payload) as IntegrationCreateUpdate, + ); } diff --git a/src/handlers/invites/INVITE_CREATE.ts b/src/handlers/invites/INVITE_CREATE.ts index f53f0b102..c21e87e3b 100644 --- a/src/handlers/invites/INVITE_CREATE.ts +++ b/src/handlers/invites/INVITE_CREATE.ts @@ -1,28 +1,13 @@ import { eventHandlers } from "../../bot.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { DiscordInviteCreate } from "../../types/invites/invite_create.ts"; +import { + DiscordInviteCreate, + InviteCreate, +} from "../../types/invites/invite_create.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; -export function handleInviteCreate(payload: DiscordGatewayPayload) { - // TODO: replace with tocamelcase - const { - channel_id: channelId, - created_at: createdAt, - max_age: maxAge, - guild_id: guildId, - target_user: targetUser, - target_user_type: targetUserType, - max_uses: maxUses, - ...rest - } = payload.d as DiscordInviteCreate; +export function handleInviteCreate(data: DiscordGatewayPayload) { + const payload = data.d as DiscordInviteCreate; - eventHandlers.inviteCreate?.({ - ...rest, - channelId, - guildId, - maxAge, - targetUser, - targetUserType, - maxUses, - createdAt, - }); + eventHandlers.inviteCreate?.(snakeKeysToCamelCase(payload) as InviteCreate); } diff --git a/src/handlers/invites/INVITE_DELETE.ts b/src/handlers/invites/INVITE_DELETE.ts index cddae5a27..3704ed7f0 100644 --- a/src/handlers/invites/INVITE_DELETE.ts +++ b/src/handlers/invites/INVITE_DELETE.ts @@ -1,17 +1,13 @@ import { eventHandlers } from "../../bot.ts"; -import { DiscordInviteDelete } from "../../types/invites/invite_delete.ts"; import { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; +import { + DiscordInviteDelete, + InviteDelete, +} from "../../types/invites/invite_delete.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; -export function handleInviteDelete(payload: DiscordGatewayPayload) { - const { - channel_id: channelId, - guild_id: guildId, - ...rest - } = payload.d as DiscordInviteDelete; +export function handleInviteDelete(data: DiscordGatewayPayload) { + const payload = data.d as DiscordInviteDelete; - eventHandlers.inviteDelete?.({ - ...rest, - channelId, - guildId, - }); + eventHandlers.inviteDelete?.(snakeKeysToCamelCase(payload) as InviteDelete); } diff --git a/src/helpers/channels/delete_channel.ts b/src/helpers/channels/delete_channel.ts index 764c73844..cd384bbfa 100644 --- a/src/helpers/channels/delete_channel.ts +++ b/src/helpers/channels/delete_channel.ts @@ -9,7 +9,7 @@ export async function deleteChannel( guildId: string, channelId: string, reason?: string, -) { +): Promise { await requireBotGuildPermissions(guildId, ["MANAGE_CHANNELS"]); const guild = await cacheHandlers.get("guilds", guildId); diff --git a/src/helpers/channels/delete_channel_overwrite.ts b/src/helpers/channels/delete_channel_overwrite.ts index 3b39301e7..da9f04a4f 100644 --- a/src/helpers/channels/delete_channel_overwrite.ts +++ b/src/helpers/channels/delete_channel_overwrite.ts @@ -7,7 +7,7 @@ export async function deleteChannelOverwrite( guildId: string, channelId: string, overwriteId: string, -) { +): Promise { await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); const result = await rest.runMethod( diff --git a/src/helpers/channels/edit_channel.ts b/src/helpers/channels/edit_channel.ts index b6a7eb6a6..cd0ce83b1 100644 --- a/src/helpers/channels/edit_channel.ts +++ b/src/helpers/channels/edit_channel.ts @@ -1,4 +1,5 @@ import { rest } from "../../rest/rest.ts"; +import { ModifyChannel } from "../../types/channels/modify_channel.ts"; import { endpoints } from "../../util/constants.ts"; import { calculateBits, @@ -8,7 +9,7 @@ import { /** Update a channel's settings. Requires the `MANAGE_CHANNELS` permission for the guild. */ export async function editChannel( channelId: string, - options: ChannelEditOptions, + options: ModifyChannel, reason?: string, ) { await requireBotChannelPermissions(channelId, ["MANAGE_CHANNELS"]); @@ -47,7 +48,7 @@ export async function editChannel( // deno-lint-ignore camelcase user_limit: options.userLimit, // deno-lint-ignore camelcase - permission_overwrites: options.overwrites?.map((overwrite) => { + permission_overwrites: options.permissionOverwrites?.map((overwrite) => { return { ...overwrite, allow: calculateBits(overwrite.allow), diff --git a/src/helpers/channels/edit_channel_overwrite.ts b/src/helpers/channels/edit_channel_overwrite.ts index 451dd6f08..e6caad9b0 100644 --- a/src/helpers/channels/edit_channel_overwrite.ts +++ b/src/helpers/channels/edit_channel_overwrite.ts @@ -12,7 +12,7 @@ export async function editChannelOverwrite( channelId: string, overwriteId: string, options: Omit, -) { +): Promise { await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]); const result = await rest.runMethod( diff --git a/src/helpers/channels/start_typing.ts b/src/helpers/channels/start_typing.ts index ca181dcbe..c5cf0144b 100644 --- a/src/helpers/channels/start_typing.ts +++ b/src/helpers/channels/start_typing.ts @@ -10,7 +10,7 @@ import { botHasChannelPermissions } from "../../util/permissions.ts"; * However, if a bot is responding to a command and expects the computation to take a few seconds, * this endpoint may be called to let the user know that the bot is processing their message. */ -export async function startTyping(channelId: string) { +export async function startTyping(channelId: string): Promise { const channel = await cacheHandlers.get("channels", channelId); // If the channel is cached, we can do extra checks/safety if (channel) { diff --git a/src/helpers/channels/swap_channels.ts b/src/helpers/channels/swap_channels.ts index 8b3662278..9c6801c36 100644 --- a/src/helpers/channels/swap_channels.ts +++ b/src/helpers/channels/swap_channels.ts @@ -6,7 +6,7 @@ import { endpoints } from "../../util/constants.ts"; export async function swapChannels( guildId: string, channelPositions: ModifyGuildChannelPositions[], -) { +): Promise { if (channelPositions.length < 2) { throw "You must provide at least two channels to be swapped."; } diff --git a/src/helpers/commands/create_slash_command.ts b/src/helpers/commands/create_slash_command.ts index fafa506fb..8baa84490 100644 --- a/src/helpers/commands/create_slash_command.ts +++ b/src/helpers/commands/create_slash_command.ts @@ -1,7 +1,11 @@ import { applicationId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; +import { CreateGlobalApplicationCommand } from "../../types/interactions/create_global_application_command.ts"; import { endpoints } from "../../util/constants.ts"; -import { validateSlashCommands } from "../../util/utils.ts"; +import { + camelKeysToSnakeCase, + validateSlashCommands, +} from "../../util/utils.ts"; /** * There are two kinds of Slash Commands: global commands and guild commands. Global commands are available for every guild that adds your app; guild commands are specific to the guild you specify when making them. Command names are unique per application within each scope (global and guild). That means: @@ -14,17 +18,18 @@ import { validateSlashCommands } from "../../util/utils.ts"; * Global commands are cached for **1 hour**. That means that new global commands will fan out slowly across all guilds, and will be guaranteed to be updated in an hour. * Guild commands update **instantly**. We recommend you use guild commands for quick testing, and global commands when they're ready for public use. */ -export async function createSlashCommand(options: CreateSlashCommandOptions) { +export async function createSlashCommand( + options: CreateGlobalApplicationCommand, + guildId: string, +) { validateSlashCommands([options], true); const result = await rest.runMethod( "post", - options.guildId - ? endpoints.COMMANDS_GUILD(applicationId, options.guildId) + guildId + ? endpoints.COMMANDS_GUILD(applicationId, guildId) : endpoints.COMMANDS(applicationId), - { - ...options, - }, + camelKeysToSnakeCase(options), ); return result; diff --git a/src/helpers/commands/delete_slash_command.ts b/src/helpers/commands/delete_slash_command.ts index 1eda08083..0b9580e15 100644 --- a/src/helpers/commands/delete_slash_command.ts +++ b/src/helpers/commands/delete_slash_command.ts @@ -3,12 +3,16 @@ import { rest } from "../../rest/rest.ts"; import { endpoints } from "../../util/constants.ts"; /** Deletes a slash command. */ -export function deleteSlashCommand(id: string, guildId?: string) { - if (!guildId) { - return rest.runMethod("delete", endpoints.COMMANDS_ID(applicationId, id)); - } - return rest.runMethod( +export async function deleteSlashCommand( + id: string, + guildId?: string, +): Promise { + const result = await rest.runMethod( "delete", - endpoints.COMMANDS_GUILD_ID(applicationId, guildId, id), + guildId + ? endpoints.COMMANDS_GUILD_ID(applicationId, guildId, id) + : endpoints.COMMANDS_ID(applicationId, id), ); + + return result; } diff --git a/src/helpers/commands/delete_slash_response.ts b/src/helpers/commands/delete_slash_response.ts index 1a9d480e0..14523c3a5 100644 --- a/src/helpers/commands/delete_slash_response.ts +++ b/src/helpers/commands/delete_slash_response.ts @@ -3,7 +3,10 @@ import { rest } from "../../rest/rest.ts"; import { endpoints } from "../../util/constants.ts"; /** To delete your response to a slash command. If a message id is not provided, it will default to deleting the original response. */ -export async function deleteSlashResponse(token: string, messageId?: string) { +export async function deleteSlashResponse( + token: string, + messageId?: string, +): Promise { const result = await rest.runMethod( "delete", messageId diff --git a/src/helpers/commands/edit_slash_response.ts b/src/helpers/commands/edit_slash_response.ts index fa81ac460..f3c49f34d 100644 --- a/src/helpers/commands/edit_slash_response.ts +++ b/src/helpers/commands/edit_slash_response.ts @@ -1,13 +1,16 @@ import { applicationId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; +import { DiscordenoEditWebhookMessage } from "../../types/discordeno/edit_webhook_message.ts"; +import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts"; +import { DiscordMessage } from "../../types/messages/message.ts"; import { Errors } from "../../types/misc/errors.ts"; import { endpoints } from "../../util/constants.ts"; /** To edit your response to a slash command. If a messageId is not provided it will default to editing the original response. */ export async function editSlashResponse( token: string, - options: EditSlashResponseOptions, + options: DiscordenoEditWebhookMessage, ) { if (options.content && options.content.length > 2000) { throw Error(Errors.MESSAGE_MAX_LENGTH); @@ -17,31 +20,39 @@ export async function editSlashResponse( options.embeds.splice(10); } - if (options.allowed_mentions) { - if (options.allowed_mentions.users?.length) { - if (options.allowed_mentions.parse.includes("users")) { - options.allowed_mentions.parse = options.allowed_mentions.parse.filter( + if (options.allowedMentions) { + if (options.allowedMentions.users?.length) { + if ( + options.allowedMentions.parse.includes( + DiscordAllowedMentionsTypes.UserMentions, + ) + ) { + options.allowedMentions.parse = options.allowedMentions.parse.filter( (p) => p !== "users", ); } - if (options.allowed_mentions.users.length > 100) { - options.allowed_mentions.users = options.allowed_mentions.users.slice( + if (options.allowedMentions.users.length > 100) { + options.allowedMentions.users = options.allowedMentions.users.slice( 0, 100, ); } } - if (options.allowed_mentions.roles?.length) { - if (options.allowed_mentions.parse.includes("roles")) { - options.allowed_mentions.parse = options.allowed_mentions.parse.filter( + if (options.allowedMentions.roles?.length) { + if ( + options.allowedMentions.parse.includes( + DiscordAllowedMentionsTypes.RoleMentions, + ) + ) { + options.allowedMentions.parse = options.allowedMentions.parse.filter( (p) => p !== "roles", ); } - if (options.allowed_mentions.roles.length > 100) { - options.allowed_mentions.roles = options.allowed_mentions.roles.slice( + if (options.allowedMentions.roles.length > 100) { + options.allowedMentions.roles = options.allowedMentions.roles.slice( 0, 100, ); @@ -61,7 +72,7 @@ export async function editSlashResponse( if (!options.messageId) return result; const message = await structures.createMessageStruct( - result as MessageCreateOptions, + result as DiscordMessage, ); return message; } diff --git a/src/helpers/commands/get_slash_command.ts b/src/helpers/commands/get_slash_command.ts index 3cc50bc28..72c61b3f6 100644 --- a/src/helpers/commands/get_slash_command.ts +++ b/src/helpers/commands/get_slash_command.ts @@ -1,5 +1,6 @@ import { applicationId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; +import { ApplicationCommand } from "../../types/interactions/application_command.ts"; import { endpoints } from "../../util/constants.ts"; /** Fetchs the global command for the given Id. If a guildId is provided, the guild command will be fetched. */ @@ -11,5 +12,5 @@ export async function getSlashCommand(commandId: string, guildId?: string) { : endpoints.COMMANDS_ID(applicationId, commandId), ); - return result as SlashCommand; + return result as ApplicationCommand; } diff --git a/src/helpers/commands/get_slash_commands.ts b/src/helpers/commands/get_slash_commands.ts index ce2cc50bf..3337baf49 100644 --- a/src/helpers/commands/get_slash_commands.ts +++ b/src/helpers/commands/get_slash_commands.ts @@ -1,5 +1,6 @@ import { applicationId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; +import { ApplicationCommand } from "../../types/interactions/application_command.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; @@ -10,7 +11,7 @@ export async function getSlashCommands(guildId?: string) { guildId ? endpoints.COMMANDS_GUILD(applicationId, guildId) : endpoints.COMMANDS(applicationId), - )) as SlashCommand[]; + )) as ApplicationCommand[]; return new Collection(result.map((command) => [command.name, command])); } diff --git a/src/helpers/commands/send_interaction_response.ts b/src/helpers/commands/send_interaction_response.ts index c60ea83ef..6d9fb0022 100644 --- a/src/helpers/commands/send_interaction_response.ts +++ b/src/helpers/commands/send_interaction_response.ts @@ -1,6 +1,7 @@ import { applicationId } from "../../bot.ts"; import { cache } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; +import { DiscordenoInteractionResponse } from "../../types/discordeno/interaction_response.ts"; import { endpoints } from "../../util/constants.ts"; /** @@ -12,7 +13,7 @@ import { endpoints } from "../../util/constants.ts"; export async function sendInteractionResponse( id: string, token: string, - options: SlashCommandResponseOptions, + options: DiscordenoInteractionResponse, ) { // If its already been executed, we need to send a followup response if (cache.executedSlashCommands.has(token)) { @@ -30,12 +31,12 @@ export async function sendInteractionResponse( // If the user wants this as a private message mark it ephemeral if (options.private) { - options.data.flags = 64; + options.data = { ...options.data, flags: 64 }; } // If no mentions are provided, force disable mentions - if (!options.data.allowed_mentions) { - options.data.allowed_mentions = { parse: [] }; + if (!options.data?.allowedMentions) { + options.data = { ...options.data, allowedMentions: { parse: [] } }; } const result = await rest.runMethod( diff --git a/src/helpers/commands/upsert_slash_command.ts b/src/helpers/commands/upsert_slash_command.ts index 21091f18e..66c064780 100644 --- a/src/helpers/commands/upsert_slash_command.ts +++ b/src/helpers/commands/upsert_slash_command.ts @@ -1,5 +1,6 @@ import { applicationId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; +import { EditGlobalApplicationCommand } from "../../types/interactions/edit_global_application_command.ts"; import { endpoints } from "../../util/constants.ts"; import { validateSlashCommands } from "../../util/utils.ts"; @@ -8,7 +9,7 @@ import { validateSlashCommands } from "../../util/utils.ts"; */ export async function upsertSlashCommand( commandId: string, - options: UpsertSlashCommandOptions, + options: EditGlobalApplicationCommand, guildId?: string, ) { validateSlashCommands([options]); diff --git a/src/helpers/commands/upsert_slash_commands.ts b/src/helpers/commands/upsert_slash_commands.ts index a5befc957..ce0bdeee2 100644 --- a/src/helpers/commands/upsert_slash_commands.ts +++ b/src/helpers/commands/upsert_slash_commands.ts @@ -1,5 +1,6 @@ import { applicationId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; +import { EditGlobalApplicationCommand } from "../../types/interactions/edit_global_application_command.ts"; import { endpoints } from "../../util/constants.ts"; import { validateSlashCommands } from "../../util/utils.ts"; @@ -9,7 +10,7 @@ import { validateSlashCommands } from "../../util/utils.ts"; * **NOTE:** Any slash commands that are not specified in this function will be **deleted**. If you don't provide the commandId and rename your command, the command gets a new Id. */ export async function upsertSlashCommands( - options: UpsertSlashCommandsOptions[], + options: EditGlobalApplicationCommand[], guildId?: string, ) { validateSlashCommands(options); diff --git a/src/helpers/emojis/create_emoji.ts b/src/helpers/emojis/create_emoji.ts index 1070bf7a9..4b2cdff67 100644 --- a/src/helpers/emojis/create_emoji.ts +++ b/src/helpers/emojis/create_emoji.ts @@ -1,14 +1,16 @@ import { rest } from "../../rest/rest.ts"; +import { CreateGuildEmoji } from "../../types/emojis/create_guild_emoji.ts"; +import { Emoji } from "../../types/emojis/emoji.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; -import { urlToBase64 } from "../../util/utils.ts"; +import { snakeKeysToCamelCase, urlToBase64 } from "../../util/utils.ts"; /** Create an emoji in the server. Emojis and animated emojis have a maximum file size of 256kb. Attempting to upload an emoji larger than this limit will fail and return 400 Bad Request and an error message, but not a JSON status code. If a URL is provided to the image parameter, Discordeno will automatically convert it to a base64 string internally. */ export async function createEmoji( guildId: string, name: string, image: string, - options: CreateEmojisOptions, + options: CreateGuildEmoji, ) { await requireBotGuildPermissions(guildId, ["MANAGE_EMOJIS"]); @@ -22,5 +24,5 @@ export async function createEmoji( image, }); - return result; + return snakeKeysToCamelCase(result) as Emoji; } diff --git a/src/helpers/emojis/delete_emoji.ts b/src/helpers/emojis/delete_emoji.ts index 64154ccfe..97783b41c 100644 --- a/src/helpers/emojis/delete_emoji.ts +++ b/src/helpers/emojis/delete_emoji.ts @@ -7,7 +7,7 @@ export async function deleteEmoji( guildId: string, id: string, reason?: string, -) { +): Promise { await requireBotGuildPermissions(guildId, ["MANAGE_EMOJIS"]); const result = await rest.runMethod( diff --git a/src/helpers/emojis/edit_emoji.ts b/src/helpers/emojis/edit_emoji.ts index 0c2e125a6..803dc8ea5 100644 --- a/src/helpers/emojis/edit_emoji.ts +++ b/src/helpers/emojis/edit_emoji.ts @@ -1,4 +1,5 @@ import { rest } from "../../rest/rest.ts"; +import { ModifyGuildEmoji } from "../../types/emojis/modify_guild_emoji.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; @@ -6,7 +7,7 @@ import { requireBotGuildPermissions } from "../../util/permissions.ts"; export async function editEmoji( guildId: string, id: string, - options: EditEmojisOptions, + options: ModifyGuildEmoji, ) { await requireBotGuildPermissions(guildId, ["MANAGE_EMOJIS"]); diff --git a/src/helpers/emojis/get_emoji.ts b/src/helpers/emojis/get_emoji.ts index edf3f4c25..c99baa1d0 100644 --- a/src/helpers/emojis/get_emoji.ts +++ b/src/helpers/emojis/get_emoji.ts @@ -1,5 +1,6 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; +import { Emoji } from "../../types/emojis/emoji.ts"; import { Errors } from "../../types/misc/errors.ts"; import { endpoints } from "../../util/constants.ts"; @@ -21,7 +22,7 @@ export async function getEmoji( if (addToCache) { const guild = await cacheHandlers.get("guilds", guildId); if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); - guild.emojis.set(result.id ?? result.name, result); + guild.emojis.set(emojiId, result); cacheHandlers.set( "guilds", guildId, diff --git a/src/helpers/emojis/get_emojis.ts b/src/helpers/emojis/get_emojis.ts index 133723114..6678c2b81 100644 --- a/src/helpers/emojis/get_emojis.ts +++ b/src/helpers/emojis/get_emojis.ts @@ -1,5 +1,6 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; +import { Emoji } from "../../types/emojis/emoji.ts"; import { Errors } from "../../types/misc/errors.ts"; import { endpoints } from "../../util/constants.ts"; @@ -16,7 +17,7 @@ export async function getEmojis(guildId: string, addToCache = true) { const guild = await cacheHandlers.get("guilds", guildId); if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); - result.forEach((emoji) => guild.emojis.set(emoji.id ?? emoji.name, emoji)); + result.forEach((emoji) => guild.emojis.set(emoji.id!, emoji)); cacheHandlers.set("guilds", guildId, guild); } diff --git a/src/helpers/guilds/create_guild.ts b/src/helpers/guilds/create_guild.ts index 144d260a4..cb58b92ac 100644 --- a/src/helpers/guilds/create_guild.ts +++ b/src/helpers/guilds/create_guild.ts @@ -1,14 +1,16 @@ import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; +import { CreateGuild } from "../../types/guilds/create_guild.ts"; +import { DiscordGuild } from "../../types/guilds/guild.ts"; import { endpoints } from "../../util/constants.ts"; /** Create a new guild. Returns a guild object on success. Fires a Guild Create Gateway event. This endpoint can be used only by bots in less than 10 guilds. */ -export async function createGuild(options: CreateServerOptions) { +export async function createGuild(options: CreateGuild) { const guild = (await rest.runMethod( "post", endpoints.GUILDS, options, - )) as CreateGuildPayload; + )) as DiscordGuild; return structures.createGuildStruct(guild, 0); } diff --git a/src/helpers/guilds/delete_server.ts b/src/helpers/guilds/delete_server.ts index 03da1f717..43a1085eb 100644 --- a/src/helpers/guilds/delete_server.ts +++ b/src/helpers/guilds/delete_server.ts @@ -3,7 +3,7 @@ import { endpoints } from "../../util/constants.ts"; /** Delete a guild permanently. User must be owner. Returns 204 No Content on success. Fires a Guild Delete Gateway event. */ -export async function deleteServer(guildId: string) { +export async function deleteServer(guildId: string): Promise { const result = await rest.runMethod("delete", endpoints.GUILDS_BASE(guildId)); return result; diff --git a/src/helpers/guilds/edit_guild.ts b/src/helpers/guilds/edit_guild.ts index 081b7a64b..a45217594 100644 --- a/src/helpers/guilds/edit_guild.ts +++ b/src/helpers/guilds/edit_guild.ts @@ -1,10 +1,13 @@ import { rest } from "../../rest/rest.ts"; +import { structures } from "../../structures/mod.ts"; +import { DiscordGuild } from "../../types/guilds/guild.ts"; +import { ModifyGuild } from "../../types/guilds/modify_guild.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; import { urlToBase64 } from "../../util/utils.ts"; /** Modify a guilds settings. Requires the MANAGE_GUILD permission. */ -export async function editGuild(guildId: string, options: GuildEditOptions) { +export async function editGuild(guildId: string, options: ModifyGuild) { await requireBotGuildPermissions(guildId, ["MANAGE_GUILD"]); if (options.icon && !options.icon.startsWith("data:image/")) { @@ -23,7 +26,7 @@ export async function editGuild(guildId: string, options: GuildEditOptions) { "patch", endpoints.GUILDS_BASE(guildId), options, - ); + ) as DiscordGuild; - return result; + return structures.createGuildStruct(result, -1); } diff --git a/src/helpers/guilds/edit_widget.ts b/src/helpers/guilds/edit_widget.ts index f36511cc7..42eaefd1b 100644 --- a/src/helpers/guilds/edit_widget.ts +++ b/src/helpers/guilds/edit_widget.ts @@ -1,6 +1,8 @@ import { rest } from "../../rest/rest.ts"; +import { GuildWidget } from "../../types/guilds/guild_widget.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; /** Modify a guild widget object for the guild. Requires the MANAGE_GUILD permission. */ export async function editWidget( @@ -19,5 +21,5 @@ export async function editWidget( }, ); - return result; + return snakeKeysToCamelCase(result) as GuildWidget; } diff --git a/src/helpers/guilds/get_audit_logs.ts b/src/helpers/guilds/get_audit_logs.ts index 63ee31d2f..241c21ff4 100644 --- a/src/helpers/guilds/get_audit_logs.ts +++ b/src/helpers/guilds/get_audit_logs.ts @@ -1,27 +1,30 @@ import { rest } from "../../rest/rest.ts"; +import { AuditLog } from "../../types/audit_log/audit_log.ts"; +import { GetGuildAuditLog } from "../../types/audit_log/get_guild_audit_log.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; +import { + camelKeysToSnakeCase, + snakeKeysToCamelCase, +} from "../../util/utils.ts"; /** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */ export async function getAuditLogs( guildId: string, - options: GetAuditLogsOptions, + options: GetGuildAuditLog, ) { await requireBotGuildPermissions(guildId, ["VIEW_AUDIT_LOG"]); const result = await rest.runMethod( "get", endpoints.GUILD_AUDIT_LOGS(guildId), - { + camelKeysToSnakeCase({ ...options, - action_type: options.action_type - ? AuditLogs[options.action_type] - : undefined, limit: options.limit && options.limit >= 1 && options.limit <= 100 ? options.limit : 50, - }, + }), ); - return result; + return snakeKeysToCamelCase(result) as AuditLog; } diff --git a/src/helpers/guilds/get_available_voice_regions.ts b/src/helpers/guilds/get_available_voice_regions.ts index b1f2b0c67..944973477 100644 --- a/src/helpers/guilds/get_available_voice_regions.ts +++ b/src/helpers/guilds/get_available_voice_regions.ts @@ -1,9 +1,10 @@ import { rest } from "../../rest/rest.ts"; +import { VoiceRegion } from "../../types/voice/voice_region.ts"; import { endpoints } from "../../util/constants.ts"; /** Returns an array of voice regions that can be used when creating servers. */ export async function getAvailableVoiceRegions() { const result = await rest.runMethod("get", endpoints.VOICE_REGIONS); - return result; + return result as VoiceRegion; } diff --git a/src/helpers/guilds/get_ban.ts b/src/helpers/guilds/get_ban.ts index 8d3751965..1cac4ac1d 100644 --- a/src/helpers/guilds/get_ban.ts +++ b/src/helpers/guilds/get_ban.ts @@ -1,6 +1,8 @@ import { rest } from "../../rest/rest.ts"; +import { Ban } from "../../types/guilds/ban.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; /** Returns a ban object for the given user or a 404 not found if the ban cannot be found. Requires the BAN_MEMBERS permission. */ export async function getBan(guildId: string, memberId: string) { @@ -11,5 +13,5 @@ export async function getBan(guildId: string, memberId: string) { endpoints.GUILD_BAN(guildId, memberId), ); - return result as BannedUser; + return snakeKeysToCamelCase(result) as Ban; } diff --git a/src/helpers/guilds/get_bans.ts b/src/helpers/guilds/get_bans.ts index c32820525..c55e27e5d 100644 --- a/src/helpers/guilds/get_bans.ts +++ b/src/helpers/guilds/get_bans.ts @@ -1,7 +1,9 @@ import { rest } from "../../rest/rest.ts"; +import { Ban, DiscordBan } from "../../types/guilds/ban.ts"; import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; /** Returns a list of ban objects for the users banned from this guild. Requires the BAN_MEMBERS permission. */ export async function getBans(guildId: string) { @@ -10,9 +12,9 @@ export async function getBans(guildId: string) { const results = (await rest.runMethod( "get", endpoints.GUILD_BANS(guildId), - )) as BannedUser[]; + )) as DiscordBan[]; - return new Collection( - results.map((res) => [res.user.id, res]), + return new Collection( + results.map((res) => [res.user.id, snakeKeysToCamelCase(res) as Ban]), ); } diff --git a/src/helpers/guilds/get_guild.ts b/src/helpers/guilds/get_guild.ts index 0a46b7d5b..b0b39700b 100644 --- a/src/helpers/guilds/get_guild.ts +++ b/src/helpers/guilds/get_guild.ts @@ -1,5 +1,7 @@ import { rest } from "../../rest/rest.ts"; +import { Guild } from "../../types/guilds/guild.ts"; import { endpoints } from "../../util/constants.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; /** * ⚠️ **If you need this, you are probably doing something wrong. Always use cache.guilds.get() @@ -13,5 +15,5 @@ export async function getGuild(guildId: string, counts = true) { with_counts: counts, }); - return result as UpdateGuildPayload; + return snakeKeysToCamelCase(result) as Guild; } diff --git a/src/helpers/guilds/get_guild_preview.ts b/src/helpers/guilds/get_guild_preview.ts index 6d7cc9386..dfd398273 100644 --- a/src/helpers/guilds/get_guild_preview.ts +++ b/src/helpers/guilds/get_guild_preview.ts @@ -1,9 +1,11 @@ import { rest } from "../../rest/rest.ts"; +import { GuildPreview } from "../../types/guilds/guild_preview.ts"; import { endpoints } from "../../util/constants.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; /** Returns the guild preview object for the given id. If the bot is not in the guild, then the guild must be Discoverable. */ export async function getGuildPreview(guildId: string) { const result = await rest.runMethod("get", endpoints.GUILD_PREVIEW(guildId)); - return result; + return snakeKeysToCamelCase(result) as GuildPreview; } diff --git a/src/helpers/guilds/get_prune_count.ts b/src/helpers/guilds/get_prune_count.ts index 1d81987ba..12b35b418 100644 --- a/src/helpers/guilds/get_prune_count.ts +++ b/src/helpers/guilds/get_prune_count.ts @@ -1,11 +1,15 @@ import { rest } from "../../rest/rest.ts"; +import { GetGuildPruneCountQuery } from "../../types/guilds/get_guild_prune_count.ts"; import { Errors } from "../../types/misc/errors.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; import { camelKeysToSnakeCase } from "../../util/utils.ts"; /** Check how many members would be removed from the server in a prune operation. Requires the KICK_MEMBERS permission */ -export async function getPruneCount(guildId: string, options?: PruneOptions) { +export async function getPruneCount( + guildId: string, + options?: GetGuildPruneCountQuery, +) { if (options?.days && options.days < 1) throw new Error(Errors.PRUNE_MIN_DAYS); if (options?.days && options.days > 30) { throw new Error(Errors.PRUNE_MAX_DAYS); @@ -17,7 +21,7 @@ export async function getPruneCount(guildId: string, options?: PruneOptions) { "get", endpoints.GUILD_PRUNE(guildId), camelKeysToSnakeCase(options ?? {}), - ) as PrunePayload; + ); - return result.pruned; + return result.pruned as number; } diff --git a/src/helpers/guilds/get_vainty_url.ts b/src/helpers/guilds/get_vainty_url.ts index 1ccac2a89..c80c65a81 100644 --- a/src/helpers/guilds/get_vainty_url.ts +++ b/src/helpers/guilds/get_vainty_url.ts @@ -1,12 +1,16 @@ import { rest } from "../../rest/rest.ts"; +import { InviteMetadata } from "../../types/invites/invite_metadata.ts"; import { endpoints } from "../../util/constants.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; -/** Returns the code and uses of the vanity url for this server if it is enabled. Requires the MANAGE_GUILD permission. */ +/** Returns the code and uses of the vanity url for this server if it is enabled else `code` will be null. Requires the `MANAGE_GUILD` permission. */ export async function getVanityURL(guildId: string) { const result = await rest.runMethod( "get", endpoints.GUILD_VANITY_URL(guildId), ); - return result; + return snakeKeysToCamelCase(result) as + | (Partial & Pick) + | { code: null }; } diff --git a/src/helpers/guilds/get_voice_regions.ts b/src/helpers/guilds/get_voice_regions.ts index 8ac73d10a..166c7fa2a 100644 --- a/src/helpers/guilds/get_voice_regions.ts +++ b/src/helpers/guilds/get_voice_regions.ts @@ -1,9 +1,22 @@ import { rest } from "../../rest/rest.ts"; +import { + DiscordVoiceRegion, + VoiceRegion, +} from "../../types/voice/voice_region.ts"; +import { Collection } from "../../util/collection.ts"; import { endpoints } from "../../util/constants.ts"; +import { snakeKeysToCamelCase } from "../../util/utils.ts"; /** Returns a list of voice region objects for the guild. Unlike the similar /voice route, this returns VIP servers when the guild is VIP-enabled. */ export async function getVoiceRegions(guildId: string) { - const result = await rest.runMethod("get", endpoints.GUILD_REGIONS(guildId)); + const result = await rest.runMethod( + "get", + endpoints.GUILD_REGIONS(guildId), + ) as DiscordVoiceRegion[]; - return result; + const convertedRegions = snakeKeysToCamelCase(result); + + return new Collection( + convertedRegions.map((region) => [region.id, region]), + ); } diff --git a/src/helpers/guilds/get_widget_image_url.ts b/src/helpers/guilds/get_widget_image_url.ts index 6e9eb01b8..f552a2326 100644 --- a/src/helpers/guilds/get_widget_image_url.ts +++ b/src/helpers/guilds/get_widget_image_url.ts @@ -1,14 +1,12 @@ import { cacheHandlers } from "../../cache.ts"; +import { GetGuildWidgetImageQuery } from "../../types/guilds/get_guild_widget_image.ts"; import { Errors } from "../../types/misc/errors.ts"; import { endpoints } from "../../util/constants.ts"; /** Returns the widget image URL for the guild. */ export async function getWidgetImageURL( guildId: string, - options?: { - style?: "shield" | "banner1" | "banner2" | "banner3" | "banner4"; - force?: boolean; - }, + options?: GetGuildWidgetImageQuery & { force?: boolean }, ) { if (!options?.force) { const guild = await cacheHandlers.get("guilds", guildId); diff --git a/src/helpers/guilds/get_widget_settings.ts b/src/helpers/guilds/get_widget_settings.ts index 3b719e7c6..8d28e8c9c 100644 --- a/src/helpers/guilds/get_widget_settings.ts +++ b/src/helpers/guilds/get_widget_settings.ts @@ -1,4 +1,5 @@ import { rest } from "../../rest/rest.ts"; +import { GuildWidget } from "../../types/guilds/guild_widget.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotGuildPermissions } from "../../util/permissions.ts"; @@ -8,5 +9,5 @@ export async function getWidgetSettings(guildId: string) { const result = await rest.runMethod("get", endpoints.GUILD_WIDGET(guildId)); - return result; + return result as GuildWidget; } diff --git a/src/helpers/guilds/guild_banner_url.ts b/src/helpers/guilds/guild_banner_url.ts index 89e07efe4..db027d15b 100644 --- a/src/helpers/guilds/guild_banner_url.ts +++ b/src/helpers/guilds/guild_banner_url.ts @@ -6,7 +6,7 @@ import { formatImageURL } from "../../util/utils.ts"; /** The full URL of the banner from Discords CDN. Undefined if no banner is set. */ export function guildBannerURL( id: string, - banner: string, + banner?: string, size: DiscordImageSize = 128, format?: DiscordImageFormat, ) { diff --git a/src/helpers/guilds/guild_icon_url.ts b/src/helpers/guilds/guild_icon_url.ts index 5acb8408f..b1e02e6f2 100644 --- a/src/helpers/guilds/guild_icon_url.ts +++ b/src/helpers/guilds/guild_icon_url.ts @@ -6,7 +6,7 @@ import { formatImageURL } from "../../util/utils.ts"; /** The full URL of the icon from Discords CDN. Undefined when no icon is set. */ export function guildIconURL( id: string, - icon: string, + icon?: string, size: DiscordImageSize = 128, format?: DiscordImageFormat, ) { diff --git a/src/helpers/guilds/guild_splash_url.ts b/src/helpers/guilds/guild_splash_url.ts index 0c51fb682..9409a0e99 100644 --- a/src/helpers/guilds/guild_splash_url.ts +++ b/src/helpers/guilds/guild_splash_url.ts @@ -6,7 +6,7 @@ import { formatImageURL } from "../../util/utils.ts"; /** The full URL of the splash from Discords CDN. Undefined if no splash is set. */ export function guildSplashURL( id: string, - splash: string, + splash?: string, size: DiscordImageSize = 128, format?: DiscordImageFormat, ) { diff --git a/src/helpers/guilds/leave_guild.ts b/src/helpers/guilds/leave_guild.ts index d103a0df3..805b56885 100644 --- a/src/helpers/guilds/leave_guild.ts +++ b/src/helpers/guilds/leave_guild.ts @@ -2,7 +2,7 @@ import { rest } from "../../rest/rest.ts"; import { endpoints } from "../../util/constants.ts"; /** Leave a guild */ -export async function leaveGuild(guildId: string) { +export async function leaveGuild(guildId: string): Promise { const result = await rest.runMethod("delete", endpoints.GUILD_LEAVE(guildId)); return result; diff --git a/src/helpers/messages/send_message.ts b/src/helpers/messages/send_message.ts index b7c628917..e80f5ff72 100644 --- a/src/helpers/messages/send_message.ts +++ b/src/helpers/messages/send_message.ts @@ -2,15 +2,19 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; +import { DiscordenoCreateMessage } from "../../types/discordeno/create_message.ts"; +import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts"; +import { DiscordMessage } from "../../types/messages/message.ts"; import { Errors } from "../../types/misc/errors.ts"; import { PermissionStrings } from "../../types/permissions/permission_strings.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; +import { camelKeysToSnakeCase } from "../../util/utils.ts"; /** Send a message to the channel. Requires SEND_MESSAGES permission. */ export async function sendMessage( channelId: string, - content: string | MessageContent, + content: string | DiscordenoCreateMessage, ) { if (typeof content === "string") content = { content }; @@ -33,7 +37,10 @@ export async function sendMessage( if (content.tts) requiredPerms.add("SEND_TTS_MESSAGES"); if (content.embed) requiredPerms.add("EMBED_LINKS"); - if (content.replyMessageId || content.mentions?.repliedUser) { + if ( + content.messageReference?.messageId || + content.allowedMentions?.repliedUser + ) { requiredPerms.add("READ_MESSAGE_HISTORY"); } @@ -45,50 +52,63 @@ export async function sendMessage( throw new Error(Errors.MESSAGE_MAX_LENGTH); } - if (content.mentions) { - if (content.mentions.users?.length) { - if (content.mentions.parse?.includes("users")) { - content.mentions.parse = content.mentions.parse.filter( + if (content.allowedMentions) { + if (content.allowedMentions.users?.length) { + if ( + content.allowedMentions.parse?.includes( + DiscordAllowedMentionsTypes.UserMentions, + ) + ) { + content.allowedMentions.parse = content.allowedMentions.parse.filter( (p) => p !== "users", ); } - if (content.mentions.users.length > 100) { - content.mentions.users = content.mentions.users.slice(0, 100); + if (content.allowedMentions.users.length > 100) { + content.allowedMentions.users = content.allowedMentions.users.slice( + 0, + 100, + ); } } - if (content.mentions.roles?.length) { - if (content.mentions.parse?.includes("roles")) { - content.mentions.parse = content.mentions.parse.filter( + if (content.allowedMentions.roles?.length) { + if ( + content.allowedMentions.parse?.includes( + DiscordAllowedMentionsTypes.RoleMentions, + ) + ) { + content.allowedMentions.parse = content.allowedMentions.parse.filter( (p) => p !== "roles", ); } - if (content.mentions.roles.length > 100) { - content.mentions.roles = content.mentions.roles.slice(0, 100); + if (content.allowedMentions.roles.length > 100) { + content.allowedMentions.roles = content.allowedMentions.roles.slice( + 0, + 100, + ); } } } const result = - (await rest.runMethod("post", endpoints.CHANNEL_MESSAGES(channelId), { - ...content, - allowed_mentions: content.mentions - ? { - ...content.mentions, - replied_user: content.mentions.repliedUser, - } - : undefined, - ...(content.replyMessageId - ? { - message_reference: { - message_id: content.replyMessageId, - fail_if_not_exists: content.failReplyIfNotExists === true, - }, - } - : {}), - })) as MessageCreateOptions; + (await rest.runMethod( + "post", + endpoints.CHANNEL_MESSAGES(channelId), + camelKeysToSnakeCase({ + ...content, + ...(content.messageReference?.messageId + ? { + messageReference: { + ...content.messageReference, + failIfNotExists: + content.messageReference.failIfNotExists === true, + }, + } + : {}), + }), + )) as DiscordMessage; return structures.createMessageStruct(result); } diff --git a/src/helpers/messages/unpin_message.ts b/src/helpers/messages/unpin_message.ts index db19544ef..c2a5630bf 100644 --- a/src/helpers/messages/unpin_message.ts +++ b/src/helpers/messages/unpin_message.ts @@ -3,7 +3,10 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; /** Unpin a message in a channel. Requires MANAGE_MESSAGES. */ -export async function unpin(channelId: string, messageId: string) { +export async function unpin( + channelId: string, + messageId: string, +): Promise { await requireBotChannelPermissions(channelId, ["MANAGE_MESSAGES"]); const result = await rest.runMethod( diff --git a/src/rest/run_method.ts b/src/rest/run_method.ts index 6c3780b4c..19afefb8c 100644 --- a/src/rest/run_method.ts +++ b/src/rest/run_method.ts @@ -1,16 +1,14 @@ import { Errors } from "../types/misc/errors.ts"; -import { IMAGE_BASE_URL } from "../util/constants.ts"; -import { API_VERSION } from "../util/constants.ts"; -import { BASE_URL } from "../util/constants.ts"; +import { API_VERSION, BASE_URL, IMAGE_BASE_URL } from "../util/constants.ts"; import { rest } from "./rest.ts"; -export function runMethod( +export function runMethod( method: "get" | "post" | "put" | "delete" | "patch", url: string, body?: unknown, retryCount = 0, bucketId?: string | null, -) { +): Promise { rest.eventHandlers.debug?.("requestCreate", { method, url, @@ -37,7 +35,7 @@ export function runMethod( .then((res) => { if (res.status === 204) return undefined; - return res.json(); + return res.json() as unknown as T; }) .catch((error) => { console.error(error); diff --git a/src/structures/guild.ts b/src/structures/guild.ts index 6d7c1287e..488450f11 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -13,17 +13,30 @@ import { getInvites } from "../helpers/invites/get_invites.ts"; import { banMember } from "../helpers/members/ban_member.ts"; import { unbanMember } from "../helpers/members/unban_member.ts"; import { GetGuildAuditLog } from "../types/audit_log/get_guild_audit_log.ts"; +import { Emoji } from "../types/emojis/emoji.ts"; import { CreateGuildBan } from "../types/guilds/create_guild_ban.ts"; import { DiscordGuild, Guild } from "../types/guilds/guild.ts"; import { DiscordGuildFeatures } from "../types/guilds/guild_features.ts"; -import { GuildMember } from "../types/guilds/guild_member.ts"; +import { + DiscordGuildMember, + GuildMember, +} from "../types/guilds/guild_member.ts"; import { ModifyGuild } from "../types/guilds/modify_guild.ts"; import { DiscordImageFormat } from "../types/misc/image_format.ts"; import { DiscordImageSize } from "../types/misc/image_size.ts"; import { PresenceUpdate } from "../types/misc/presence_update.ts"; +import { DiscordUser } from "../types/users/user.ts"; +import { VoiceState } from "../types/voice/voice_state.ts"; import { Collection } from "../util/collection.ts"; -import { createNewProp, snakeKeysToCamelCase } from "../util/utils.ts"; -import { RoleStruct, structures } from "./mod.ts"; +import { + camelKeysToSnakeCase, + createNewProp, + snakeKeysToCamelCase, +} from "../util/utils.ts"; +import { ChannelStruct } from "./channel.ts"; +import { MemberStruct } from "./member.ts"; +import { structures } from "./mod.ts"; +import { RoleStruct } from "./role.ts"; export const initialMemberLoadQueue = new Map(); @@ -118,7 +131,9 @@ export async function createGuildStruct( } = snakeKeysToCamelCase(data) as Guild; const roles = await Promise.all( - data.roles.map((role) => structures.createRoleStruct(role)), + data.roles.map((role) => + structures.createRoleStruct({ role, guild_id: rest.id }) + ), ); await Promise.all(channels.map(async (channel) => { @@ -164,7 +179,9 @@ export async function createGuildStruct( await Promise.allSettled( members.map(async (member) => { const memberStruct = await structures.createMemberStruct( - member, + camelKeysToSnakeCase(member) as Omit & { + user: DiscordUser; + }, guild.id, ); @@ -179,14 +196,25 @@ export async function createGuildStruct( export interface GuildStruct extends Omit< Guild, - "roles" | "presences" | "voiceStates" | "members" | "channels" + | "roles" + | "presences" + | "voiceStates" + | "members" + | "channels" + | "memberCount" + | "owner" + | "emojis" > { + /** Total number of members in this guild */ + memberCount?: number; /** The roles in the guild */ roles: Collection; /** The presences of all the users in the guild. */ presences: Collection; /** The Voice State data for each user in a voice channel in this server. */ - voiceStates: Collection; + voiceStates: Collection; + /** Custom guild emojis */ + emojis: Collection; // GETTERS /** Members in this guild. */ @@ -204,9 +232,12 @@ export interface GuildStruct extends /** The bot member in this guild if cached */ bot?: MemberStruct; /** The bot guild member in this guild if cached */ - botMember?: GuildMember; + botMember?: Omit & { + joinedAt: number; + premiumSince?: number; + }; /** The bots voice state if there is one in this guild */ - botVoice?: CleanVoiceState; + botVoice?: VoiceState; /** The owner member of this guild */ owner?: MemberStruct; /** Whether or not this guild is partnered */ diff --git a/src/structures/member.ts b/src/structures/member.ts index 9bf687d06..376250820 100644 --- a/src/structures/member.ts +++ b/src/structures/member.ts @@ -6,13 +6,13 @@ import { kickMember } from "../helpers/members/kick_member.ts"; import { sendDirectMessage } from "../helpers/members/send_direct_message.ts"; import { addRole } from "../helpers/roles/add_role.ts"; import { removeRole } from "../helpers/roles/remove_role.ts"; -import { CreateMessage } from "../types/channels/create_message.ts"; import { CreateGuildBan } from "../types/guilds/create_guild_ban.ts"; import { DiscordGuildMember, GuildMember, } from "../types/guilds/guild_member.ts"; import { ModifyGuildMember } from "../types/guilds/modify_guild_member.ts"; +import { CreateMessage } from "../types/messages/create_message.ts"; import { DiscordImageFormat } from "../types/misc/image_format.ts"; import { DiscordImageSize } from "../types/misc/image_size.ts"; import { DiscordUser, User } from "../types/users/user.ts"; @@ -151,7 +151,14 @@ export interface MemberStruct extends GuildMember, User { /** Get the nickname or the username if no nickname */ name(guildID: string): string; /** Get the guild member object for the specified guild */ - guildMember(guildID: string): GuildMember | undefined; + guildMember( + guildID: string, + ): + | Omit & { + joinedAt: number; + premiumSince?: number; + } + | undefined; /** Send a direct message to the user is possible */ sendDM( content: string | CreateMessage, diff --git a/src/structures/message.ts b/src/structures/message.ts index 577b965c1..99d8eb926 100644 --- a/src/structures/message.ts +++ b/src/structures/message.ts @@ -9,10 +9,18 @@ import { removeAllReactions } from "../helpers/messages/remove_all_reactions.ts" import { removeReaction } from "../helpers/messages/remove_reaction.ts"; import { removeReactionEmoji } from "../helpers/messages/remove_reaction_emoji.ts"; import { sendMessage } from "../helpers/messages/send_message.ts"; +import { DiscordenoCreateMessage } from "../types/discordeno/create_message.ts"; +import { GuildMember } from "../types/guilds/guild_member.ts"; +import { EditMessage } from "../types/messages/edit_message.ts"; +import { DiscordMessage, Message } from "../types/messages/message.ts"; import { CHANNEL_MENTION_REGEX } from "../util/constants.ts"; -import { createNewProp } from "../util/utils.ts"; +import { createNewProp, snakeKeysToCamelCase } from "../util/utils.ts"; +import { ChannelStruct } from "./channel.ts"; +import { GuildStruct } from "./guild.ts"; +import { MemberStruct } from "./member.ts"; +import { RoleStruct } from "./role.ts"; -const baseMessage: Partial = { +const baseMessage: Partial = { get channel() { if (this.guildId) return cache.channels.get(this.channelId!); return cache.channels.get(this.author?.id!); @@ -34,13 +42,13 @@ const baseMessage: Partial = { "@me"}/${this.channelId}/${this.id}`; }, get mentionedRoles() { - return this.mentionRoleIds?.map((id) => this.guild?.roles.get(id)) || []; + return this.mentionedRoleIds?.map((id) => this.guild?.roles.get(id)) || []; }, get mentionedChannels() { - return this.mentionChannelIds?.map((id) => cache.channels.get(id)) || []; + return this.mentionedChannelIds?.map((id) => cache.channels.get(id)) || []; }, get mentionedMembers() { - return this.mentions?.map((id) => cache.members.get(id)) || []; + return this.mentionedUserIds?.map((id) => cache.members.get(id)) || []; }, // METHODS @@ -69,9 +77,10 @@ const baseMessage: Partial = { } : { ...content, - mentions: { ...(content.mentions || {}), repliedUser: true }, + mentions: { ...(content.allowedMentions || {}), repliedUser: true }, replyMessageId: this.id, - failReplyIfNotExists: content.failReplyIfNotExists === true, + failReplyIfNotExists: + content.messageReference?.failIfNotExists === true, }; if (this.guildId) return sendMessage(this.channelId!, contentWithMention); @@ -108,58 +117,126 @@ const baseMessage: Partial = { }, }; -export async function createMessageStruct(data: MessageCreateOptions) { +export async function createMessageStruct(data: DiscordMessage) { const { - guild_id: guildId = "", - channel_id: channelId, - mentions_everyone: mentionsEveryone, - mention_channels: mentionChannelIds = [], - mention_roles: mentionRoleIds, - webhook_id: webhookId, - message_reference: messageReference, + guildId = "", + channelId, + mentionChannels = [], + mentions, + mentionRoles, edited_timestamp: editedTimestamp, - referenced_message: referencedMessageId, - member, ...rest - } = data; + } = snakeKeysToCamelCase(data) as Message; - const restProps: Record> = {}; + const props: Record> = {}; for (const key of Object.keys(rest)) { // @ts-ignore index signature - restProps[key] = createNewProp(rest[key]); + props[key] = createNewProp(rest[key]); } // Discord doesnt give guild id for getMessage() so this will fill it in const guildIdFinal = guildId || (await cacheHandlers.get("channels", channelId))?.guildId || ""; - const message = Object.create(baseMessage, { - ...restProps, + const message: MessageStruct = Object.create(baseMessage, { + ...props, /** The message id of the original message if this message was sent as a reply. If null, the original message was deleted. */ - referencedMessageId: createNewProp(referencedMessageId), channelId: createNewProp(channelId), - guildId: createNewProp(guildId || guildIdFinal), - mentions: createNewProp(data.mentions.map((m) => m.id)), - mentionsEveryone: createNewProp(mentionsEveryone), - mentionRoleIds: createNewProp(mentionRoleIds), - mentionChannelIds: createNewProp( + guildId: createNewProp(guildIdFinal), + mentionedUserIds: createNewProp(mentions.map((m) => m.id)), + mentionedRoleIds: createNewProp(mentionRoles), + mentionedChannelIds: createNewProp( [ // Keep any ids that discord sends - ...mentionChannelIds, + ...mentionChannels.map((m) => m.id), // Add any other ids that can be validated in a channel mention format ...(rest.content.match(CHANNEL_MENTION_REGEX) || []).map((text) => // converts the <#123> into 123 text.substring(2, text.length - 1) ), - ].map((m) => m.id), + ], ), - webhookId: createNewProp(webhookId), - messageReference: createNewProp(messageReference), timestamp: createNewProp(Date.parse(data.timestamp)), editedTimestamp: createNewProp( editedTimestamp ? Date.parse(editedTimestamp) : undefined, ), }); - return message as Message; + return message; +} + +export interface MessageStruct extends Message { + // For better user experience + /** Ids of users specifically mentioned in the message */ + mentionedUserIds: string[]; + /** Ids of roles specifically mentioned in this message */ + mentionedRoleIds: string[]; + /** Channels specifically mentioned in this message */ + mentionedChannelIds?: string[]; + // GETTERS + + /** The channel where this message was sent. Can be undefined if uncached. */ + channel?: ChannelStruct; + /** The guild of this message. Can be undefined if not in cache or in DM */ + guild?: GuildStruct; + /** The member for the user who sent the message. Can be undefined if not in cache or in dm. */ + member?: MemberStruct; + /** The guild member details for this guild and member. Can be undefined if not in cache or in dm. */ + guildMember?: Omit & { + joinedAt: number; + premiumSince?: number; + }; + /** The url link to this message */ + link: string; + /** The role objects for all the roles that were mentioned in this message */ + mentionedRoles: (RoleStruct | undefined)[]; + /** The channel objects for all the channels that were mentioned in this message. */ + mentionedChannels: (ChannelStruct | undefined)[]; + /** The member objects for all the members that were mentioned in this message. */ + mentionedMembers: (MemberStruct | undefined)[]; + + // METHODS + + /** Delete the message */ + delete( + reason?: string, + delayMilliseconds?: number, + ): ReturnType; + /** Edit the message */ + edit(content: string | EditMessage): ReturnType; + /** Pins the message in the channel */ + pin(): ReturnType; + /** Add a reaction to the message */ + addReaction(reaction: string): ReturnType; + /** Add multiple reactions to the message without or without order. */ + addReactions( + reactions: string[], + ordered?: boolean, + ): ReturnType; + /** Send a inline reply to this message */ + reply( + content: string | DiscordenoCreateMessage, + ): ReturnType; + /** Send a message to this channel where this message is */ + send( + content: string | DiscordenoCreateMessage, + ): ReturnType; + /** Send a message to this channel and then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */ + alert( + content: string | DiscordenoCreateMessage, + timeout?: number, + reason?: string, + ): Promise; + /** Send a inline reply to this message but then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */ + alertReply( + content: string | DiscordenoCreateMessage, + timeout?: number, + reason?: string, + ): Promise; + /** Remove all reactions */ + removeAllReactions(): ReturnType; + /** Remove all reactions */ + removeReactionEmoji(reaction: string): ReturnType; + /** Remove all reactions */ + removeReaction(reaction: string): ReturnType; } diff --git a/src/types/discordeno/create_slash_command.ts b/src/types/discordeno/create_slash_command.ts new file mode 100644 index 000000000..969ac33fc --- /dev/null +++ b/src/types/discordeno/create_slash_command.ts @@ -0,0 +1,7 @@ +import { CreateGlobalApplicationCommand } from "../interactions/create_global_application_command.ts"; + +export interface DiscordenoCreateApplicationCommand + extends CreateGlobalApplicationCommand { + /** Id of the guild to create a guild only application command */ + guildId: string; +} diff --git a/src/types/discordeno/edit_webhook_message.ts b/src/types/discordeno/edit_webhook_message.ts new file mode 100644 index 000000000..97ee0af14 --- /dev/null +++ b/src/types/discordeno/edit_webhook_message.ts @@ -0,0 +1,6 @@ +import { EditWebhookMessage } from "../webhooks/edit_webhook_message.ts"; + +export interface DiscordenoEditWebhookMessage extends EditWebhookMessage { + /** Id of the message you want to edit */ + messageId: string; +} diff --git a/src/types/discordeno/guild_member.ts b/src/types/discordeno/guild_member.ts new file mode 100644 index 000000000..e69de29bb diff --git a/src/types/discordeno/guild_update_change.ts b/src/types/discordeno/guild_update_change.ts new file mode 100644 index 000000000..32e9e966e --- /dev/null +++ b/src/types/discordeno/guild_update_change.ts @@ -0,0 +1,7 @@ +import { Guild } from "../guilds/guild.ts"; + +export interface GuildUpdateChange { + key: keyof Guild; + oldValue?: unknown; + value?: unknown; +} diff --git a/src/types/discordeno/interaction_response.ts b/src/types/discordeno/interaction_response.ts new file mode 100644 index 000000000..4e598eebe --- /dev/null +++ b/src/types/discordeno/interaction_response.ts @@ -0,0 +1,6 @@ +import { InteractionResponse } from "../interactions/interaction_response.ts"; + +export interface DiscordenoInteractionResponse extends InteractionResponse { + /** Set to true if the response should be private */ + private?: boolean; +} diff --git a/src/types/guilds/create_guild_integration.ts b/src/types/integration/create_guild_integration.ts similarity index 100% rename from src/types/guilds/create_guild_integration.ts rename to src/types/integration/create_guild_integration.ts diff --git a/src/types/guilds/guild_integrations_update.ts b/src/types/integration/guild_integrations_update.ts similarity index 100% rename from src/types/guilds/guild_integrations_update.ts rename to src/types/integration/guild_integrations_update.ts diff --git a/src/types/guilds/integration.ts b/src/types/integration/integration.ts similarity index 100% rename from src/types/guilds/integration.ts rename to src/types/integration/integration.ts diff --git a/src/types/guilds/integration_account.ts b/src/types/integration/integration_account.ts similarity index 100% rename from src/types/guilds/integration_account.ts rename to src/types/integration/integration_account.ts diff --git a/src/types/guilds/integration_application.ts b/src/types/integration/integration_application.ts similarity index 100% rename from src/types/guilds/integration_application.ts rename to src/types/integration/integration_application.ts diff --git a/src/types/integration/integration_create_update.ts b/src/types/integration/integration_create_update.ts new file mode 100644 index 000000000..022e879f3 --- /dev/null +++ b/src/types/integration/integration_create_update.ts @@ -0,0 +1,12 @@ +import { SnakeCaseProps } from "../util.ts"; +import { Integration } from "./integration.ts"; + +export interface IntegrationCreateUpdate extends Integration { + /** Id of the guild */ + guildId: string; +} + +/** https://github.com/discord/discord-api-docs/blob/master/docs/topics/Gateway.md#integration-create-event-additional-fields */ +export type DiscordIntegrationCreateUpdate = SnakeCaseProps< + IntegrationCreateUpdate +>; diff --git a/src/types/integration/integration_delete.ts b/src/types/integration/integration_delete.ts new file mode 100644 index 000000000..34fca4f61 --- /dev/null +++ b/src/types/integration/integration_delete.ts @@ -0,0 +1,13 @@ +import { SnakeCaseProps } from "../util.ts"; + +export interface IntegrationDelete { + /** Integration id */ + id: string; + /** Id of the guild */ + guildId: string; + /** Id of the bot/OAuth2 application for this discord integration */ + applicationId?: string; +} + +/** https://github.com/discord/discord-api-docs/blob/master/docs/topics/Gateway.md#integration-delete-event-fields */ +export type DiscordIntegrationDelete = SnakeCaseProps; diff --git a/src/types/guilds/integration_expire_behaviors.ts b/src/types/integration/integration_expire_behaviors.ts similarity index 100% rename from src/types/guilds/integration_expire_behaviors.ts rename to src/types/integration/integration_expire_behaviors.ts diff --git a/src/types/guilds/modify_guild_integration.ts b/src/types/integration/modify_guild_integration.ts similarity index 100% rename from src/types/guilds/modify_guild_integration.ts rename to src/types/integration/modify_guild_integration.ts diff --git a/src/types/interactions/interaction.ts b/src/types/interactions/interaction.ts index 927a9bc88..83980f33d 100644 --- a/src/types/interactions/interaction.ts +++ b/src/types/interactions/interaction.ts @@ -1,3 +1,4 @@ +import { GuildMember } from "../guilds/guild_member.ts"; import { User } from "../users/user.ts"; import { SnakeCaseProps } from "../util.ts"; import { InteractionApplicationCommandCallbackData } from "./application_command_callback_data.ts"; diff --git a/src/types/invites/invite_metadata.ts b/src/types/invites/invite_metadata.ts index 40d654ba5..e897bdc3c 100644 --- a/src/types/invites/invite_metadata.ts +++ b/src/types/invites/invite_metadata.ts @@ -1,6 +1,7 @@ import { SnakeCaseProps } from "../util.ts"; +import { Invite } from "./invite.ts"; -export interface InviteMetadata { +export interface InviteMetadata extends Invite { /** Number of times this invite has been used */ uses: number; /** Max number of times this invite can be used */ diff --git a/src/types/messages/allowed_mentions.ts b/src/types/messages/allowed_mentions.ts index 7fd30d41f..c77d6208f 100644 --- a/src/types/messages/allowed_mentions.ts +++ b/src/types/messages/allowed_mentions.ts @@ -5,11 +5,11 @@ export interface AllowedMentions { /** An array of allowed mention types to parse from the content. */ parse: DiscordAllowedMentionsTypes[]; /** Array of role_ids to mention (Max size of 100) */ - roles: string[]; + roles?: string[]; /** Array of user_ids to mention (Max size of 100) */ - users: string[]; + users?: string[]; /** For replies, whether to mention the author of the message being replied to (default false) */ - repliedUser: boolean; + repliedUser?: boolean; } /** https://discord.com/developers/docs/resources/channel#allowed-mentions-object */ diff --git a/src/types/messages/create_message.ts b/src/types/messages/create_message.ts index 4b3e97b87..b66a94ad8 100644 --- a/src/types/messages/create_message.ts +++ b/src/types/messages/create_message.ts @@ -1,6 +1,7 @@ import { Embed } from "../embeds/embed.ts"; import { AllowedMentions } from "../messages/allowed_mentions.ts"; import { MessageReference } from "../messages/message_reference.ts"; +import { FileContent } from "../misc/file_content.ts"; import { SnakeCaseProps } from "../util.ts"; export interface CreateMessage { @@ -16,7 +17,9 @@ export interface CreateMessage { allowedMentions?: AllowedMentions; /** Include to make your message a reply */ messageReference?: MessageReference; + /** The contents of the file being sent */ + file?: FileContent | FileContent[]; } /** https://discord.com/developers/docs/resources/channel#create-message */ -export type DiscordCreateMessage = SnakeCaseProps; +export type DiscordCreateMessage = SnakeCaseProps>; diff --git a/src/types/messages/edit_message.ts b/src/types/messages/edit_message.ts new file mode 100644 index 000000000..d640625da --- /dev/null +++ b/src/types/messages/edit_message.ts @@ -0,0 +1,17 @@ +import { Embed } from "../embeds/embed.ts"; +import { SnakeCaseProps } from "../util.ts"; +import { AllowedMentions } from "./allowed_mentions.ts"; + +export interface EditMessage { + /** The new message contents (up to 2000 characters) */ + content?: string | null; + /** Embedded `rich` content */ + embed?: Embed | null; + /** Edit the flags of the message (only `SUPRESS_EMBEDS` can currently be set/unset) */ + flags?: 4 | null; + /** Allowed mentions for the message */ + allowedMentions?: AllowedMentions | null; +} + +/** https://discord.com/developers/docs/resources/channel#edit-message-json-params */ +export type DiscordEditMessage = SnakeCaseProps; diff --git a/src/util/permissions.ts b/src/util/permissions.ts index 62ee90feb..eda69d7a8 100644 --- a/src/util/permissions.ts +++ b/src/util/permissions.ts @@ -5,15 +5,18 @@ import { Errors } from "../types/misc/errors.ts"; import { DiscordBitwisePermissionFlags } from "../types/permissions/bitwise_permission_flags.ts"; import { PermissionStrings } from "../types/permissions/permission_strings.ts"; -async function getCached(table: "guilds", key: string | Guild): Promise; +async function getCached( + table: "guilds", + key: string | Guild, +): Promise; async function getCached( table: "channels", key: string | Channel, -): Promise; +): Promise; async function getCached( table: "members", key: string | Member, -): Promise; +): Promise; async function getCached( table: "guilds" | "channels" | "members", key: string | Guild | Channel | Member, @@ -28,7 +31,7 @@ async function getCached( ); } - return cached; + return typeof cached === "string" ? undefined : cached; } /** Calculates the permissions this member has in the given guild */ @@ -39,6 +42,8 @@ export async function calculateBasePermissions( guild = await getCached("guilds", guild); member = await getCached("members", member); + if (!guild || !member) return "8"; + let permissions = 0n; // Calculate the role permissions bits, @everyone role is not in memberRoleIds so we need to pass guildId manualy permissions |= [...(member.guilds.get(guild.id)?.roles || []), guild.id] @@ -64,10 +69,12 @@ export async function calculateChannelOverwrites( channel = await getCached("channels", channel); // This is a DM channel so return ADMINISTRATOR permission - if (!channel.guildId) return "8"; + if (!channel?.guildId) return "8"; member = await getCached("members", member); + if (!member) return "8"; + // Get all the role permissions this member already has let permissions = BigInt( await calculateBasePermissions(channel.guildId, member), @@ -285,8 +292,10 @@ export async function highestRole( ) { guild = await getCached("guilds", guild); + if (!guild) throw new Error(Errors.GUILD_NOT_FOUND); + // Get the roles from the member - const memberRoles = (await getCached("members", member)).guilds.get(guild.id) + const memberRoles = (await getCached("members", member))?.guilds.get(guild.id) ?.roles; // This member has no roles so the highest one is the @everyone role if (!memberRoles) return guild.roles.get(guild.id) as Role; @@ -321,6 +330,8 @@ export async function higherRolePosition( ) { guild = await getCached("guilds", guild); + if (!guild) return true; + const role = guild.roles.get(roleId); const otherRole = guild.roles.get(otherRoleId); if (!role || !otherRole) throw new Error(Errors.ROLE_NOT_FOUND); @@ -341,7 +352,7 @@ export async function isHigherPosition( ) { guild = await getCached("guilds", guild); - if (guild.ownerId === memberId) return true; + if (!guild || guild.ownerId === memberId) return true; const memberHighestRole = await highestRole(guild, memberId); return higherRolePosition(guild.id, memberHighestRole.id, compareRoleId); diff --git a/src/util/utils.ts b/src/util/utils.ts index f06d38b7e..5c012433e 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -72,7 +72,9 @@ function isObject(obj: unknown) { } // deno-lint-ignore no-explicit-any -export function camelKeysToSnakeCase(obj: Record) { +export function camelKeysToSnakeCase( + obj: Record | Record[], +): T { if (isObject(obj)) { // deno-lint-ignore no-explicit-any const convertedObject: Record = {}; @@ -80,20 +82,22 @@ export function camelKeysToSnakeCase(obj: Record) { Object.keys(obj) .forEach((key) => { convertedObject[camelToSnakeCase(key)] = camelKeysToSnakeCase( - obj[key], + (obj as Record)[key], ); }); - return convertedObject; + return convertedObject as T; } else if (Array.isArray(obj)) { obj = obj.map((element) => camelKeysToSnakeCase(element)); } - return obj; + return obj as T; } // deno-lint-ignore no-explicit-any -export function snakeKeysToCamelCase(obj: Record) { +export function snakeKeysToCamelCase( + obj: Record | Record[], +): T { if (isObject(obj)) { // deno-lint-ignore no-explicit-any const convertedObject: Record = {}; @@ -101,16 +105,16 @@ export function snakeKeysToCamelCase(obj: Record) { Object.keys(obj) .forEach((key) => { convertedObject[snakeToCamelCase(key)] = snakeKeysToCamelCase( - obj[key], + (obj as Record)[key], ); }); - return convertedObject; + return convertedObject as T; } else if (Array.isArray(obj)) { obj = obj.map((element) => snakeKeysToCamelCase(element)); } - return obj; + return obj as T; } /** @private */