diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 99e1a8ca6..f1ab551df 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @ayntee @Skillz4Killz @itohatweb +* @Skillz4Killz @itohatweb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96406d251..cfbb40471 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,13 +15,13 @@ jobs: with: deno-version: ${{ matrix.deno }} - name: Cache dependencies - run: deno cache --no-check mod.ts + run: deno cache mod.ts - name: Run test script for maintainers - if: ${{ github.actor == 'ayntee' || github.actor == 'Skillz4Killz' || github.actor == 'itohatweb' }} - run: deno test --unstable --coverage=coverage -A --no-check tests/mod.ts + if: ${{ github.actor == 'Skillz4Killz' || github.actor == 'itohatweb' }} + run: deno test --unstable --coverage=coverage -A tests/mod.ts - name: Run test script if label added if: ${{ github.event_name == 'pull_request' && github.event.action == 'labeled' && github.event.label.name == 'run-tests' }} - run: DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} deno test --unstable --coverage=coverage --allow-net --no-check tests/mod.ts + run: DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} deno test --unstable --coverage=coverage --allow-net tests/mod.ts - name: Create coverage report run: deno --unstable coverage ./coverage --lcov > coverage.lcov - name: Collect and upload the coverage report diff --git a/src/bot.ts b/src/bot.ts index 670bb98c9..c36939832 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -5,7 +5,6 @@ import { DiscordGatewayIntents } from "./types/gateway/gateway_intents.ts"; import { baseEndpoints, GATEWAY_VERSION } from "./util/constants.ts"; import { ws } from "./ws/ws.ts"; -export let authorization = ""; export let secretKey = ""; export let botId = ""; export let applicationId = ""; @@ -16,7 +15,6 @@ export let proxyWSURL = `wss://gateway.discord.gg`; export async function startBot(config: BotConfig) { if (config.eventHandlers) eventHandlers = config.eventHandlers; - authorization = `Bot ${config.token}`; ws.identifyPayload.token = `Bot ${config.token}`; rest.token = `Bot ${config.token}`; ws.identifyPayload.intents = config.intents.reduce( @@ -38,8 +36,6 @@ export async function startBot(config: BotConfig) { proxyWSURL = ws.botGatewayData.url; - // ws.lastShardId = ws.maxShards; - ws.spawnShards(); } @@ -70,7 +66,6 @@ export function setApplicationId(id: string) { * Advanced Devs: This function will allow you to have an insane amount of customization potential as when you get to large bots you need to be able to optimize every tiny detail to make you bot work the way you need. */ export async function startBigBrainBot(options: BigBrainBotConfig) { - authorization = `Bot ${options.token}`; rest.token = `Bot ${options.token}`; if (options.secretKey) secretKey = options.secretKey; diff --git a/src/handlers/guilds/GUILD_UPDATE.ts b/src/handlers/guilds/GUILD_UPDATE.ts index 186b40c25..607c16e76 100644 --- a/src/handlers/guilds/GUILD_UPDATE.ts +++ b/src/handlers/guilds/GUILD_UPDATE.ts @@ -30,8 +30,6 @@ export async function handleGuildUpdate(data: DiscordGatewayPayload) { if (Array.isArray(cachedValue) && Array.isArray(value)) { const different = (cachedValue.length !== value.length) || cachedValue.find((val) => !value.includes(val)) || - // TODO: check if this really works hehe - // @ts-ignore typescript thinks that this is not an array value.find((val) => !cachedValue.includes(val)); if (!different) return; } diff --git a/src/helpers/guilds/edit_guild.ts b/src/helpers/guilds/edit_guild.ts index 20df19c47..e17206c00 100644 --- a/src/helpers/guilds/edit_guild.ts +++ b/src/helpers/guilds/edit_guild.ts @@ -1,3 +1,4 @@ +import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; import { Guild } from "../../types/guilds/guild.ts"; @@ -5,6 +6,7 @@ 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"; +import { ws } from "../../ws/ws.ts"; /** Modify a guilds settings. Requires the MANAGE_GUILD permission. */ export async function editGuild(guildId: string, options: ModifyGuild) { @@ -28,6 +30,13 @@ export async function editGuild(guildId: string, options: ModifyGuild) { options, ); - // TODO: use ws.botGatewayData to calculate the shard ID - return structures.createDiscordenoGuild(result, -1); + const cached = await cacheHandlers.get("guilds", guildId); + return structures.createDiscordenoGuild( + result, + cached?.shardId || + Number( + (BigInt(result.id) >> 22n % BigInt(ws.botGatewayData.shards)) + .toString(), + ), + ); } diff --git a/src/helpers/guilds/get_widget.ts b/src/helpers/guilds/get_widget.ts index bce3f1adc..d98034f7e 100644 --- a/src/helpers/guilds/get_widget.ts +++ b/src/helpers/guilds/get_widget.ts @@ -1,5 +1,6 @@ import { cacheHandlers } from "../../cache.ts"; import { rest } from "../../rest/rest.ts"; +import { GuildWidgetDetails } from "../../types/guilds/guild_widget_details.ts"; import { Errors } from "../../types/misc/errors.ts"; import { endpoints } from "../../util/constants.ts"; @@ -11,8 +12,7 @@ export async function getWidget(guildId: string, options?: { force: boolean }) { if (!guild?.widgetEnabled) throw new Error(Errors.GUILD_WIDGET_NOT_ENABLED); } - // TODO: add return type - return await rest.runMethod( + return await rest.runMethod( "get", `${endpoints.GUILD_WIDGET(guildId)}.json`, ); diff --git a/src/helpers/invites/create_invite.ts b/src/helpers/invites/create_invite.ts index 0c7af679f..0edab07ba 100644 --- a/src/helpers/invites/create_invite.ts +++ b/src/helpers/invites/create_invite.ts @@ -1,6 +1,7 @@ import { rest } from "../../rest/rest.ts"; import { CreateChannelInvite } from "../../types/invites/create_channel_invite.ts"; -import { Invite } from "../../types/mod.ts"; +import { Invite } from "../../types/invites/invite.ts"; +import { Errors } from "../../types/misc/errors.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; @@ -11,7 +12,12 @@ export async function createInvite( ) { await requireBotChannelPermissions(channelId, ["CREATE_INSTANT_INVITE"]); - // TODO: add proper options validation + if (options.maxAge && (options.maxAge < 0 || options.maxAge > 604800)) { + throw new Error(Errors.INVITE_MAX_AGE_INVALID); + } + if (options.maxUses && (options.maxUses < 0 || options.maxUses > 100)) { + throw new Error(Errors.INVITE_MAX_USES_INVALID); + } return await rest.runMethod( "post", diff --git a/src/helpers/messages/send_message.ts b/src/helpers/messages/send_message.ts index 68e261861..87dd3eb20 100644 --- a/src/helpers/messages/send_message.ts +++ b/src/helpers/messages/send_message.ts @@ -3,6 +3,7 @@ import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts"; +import { ButtonStyles } from "../../types/messages/components/button_styles.ts"; import { CreateMessage } from "../../types/messages/create_message.ts"; import { Message } from "../../types/messages/message.ts"; import { Errors } from "../../types/misc/errors.ts"; @@ -11,6 +12,8 @@ import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; import { camelKeysToSnakeCase } from "../../util/utils.ts"; import { validateLength } from "../../util/validate_length.ts"; +import { isActionRow } from "../type_guards/is_action_row.ts"; +import { isButton } from "../type_guards/is_button.ts"; /** Send a message to the channel. Requires SEND_MESSAGES permission. */ export async function sendMessage( @@ -93,6 +96,59 @@ export async function sendMessage( } } + if (content.components?.length) { + let actionRowCounter = 0; + + for (const component of content.components) { + // 5 Link buttons can not have a customId + if (isButton(component)) { + if ( + component.type === ButtonStyles.Link && + component.customId + ) { + throw new Error(Errors.LINK_BUTTON_CANNOT_HAVE_CUSTOM_ID); + } + // Other buttons must have a customId + if ( + !component.customId && component.type !== ButtonStyles.Link + ) { + throw new Error(Errors.BUTTON_REQUIRES_CUSTOM_ID); + } + + if (!validateLength(component.label, { max: 80 })) { + throw new Error(Errors.COMPONENT_LABEL_TOO_BIG); + } + + if ( + component.customId && + !validateLength(component.customId, { max: 100 }) + ) { + throw new Error(Errors.COMPONENT_CUSTOM_ID_TOO_BIG); + } + } + + if (!isActionRow(component)) { + continue; + } + + actionRowCounter++; + // Max of 5 ActionRows per message + if (actionRowCounter > 5) throw new Error(Errors.TOO_MANY_ACTION_ROWS); + + // Max of 5 Buttons (or any component type) within an ActionRow + if (component.components?.length > 5) { + throw new Error(Errors.TOO_MANY_COMPONENTS); + } + } + } + + if ( + content.nonce && + !validateLength(content.nonce.toString(), { max: 25 }) + ) { + throw new Error(Errors.NONCE_TOO_LONG); + } + const result = await rest.runMethod( "post", endpoints.CHANNEL_MESSAGES(channelId), diff --git a/src/helpers/mod.ts b/src/helpers/mod.ts index 483f7378f..44bb6a467 100644 --- a/src/helpers/mod.ts +++ b/src/helpers/mod.ts @@ -122,6 +122,9 @@ import { executeWebhook } from "./webhooks/execute_webhook.ts"; import { getWebhook } from "./webhooks/get_webhook.ts"; import { getWebhooks } from "./webhooks/get_webhooks.ts"; import { getWebhookWithToken } from "./webhooks/get_webhook_with_token.ts"; +// Type Guards +import { isActionRow } from "./type_guards/is_action_row.ts"; +import { isButton } from "./type_guards/is_button.ts"; export { addDiscoverySubcategory, @@ -225,6 +228,8 @@ export { guildBannerURL, guildIconURL, guildSplashURL, + isActionRow, + isButton, isChannelSynced, kick, kickMember, diff --git a/src/helpers/type_guards/is_action_row.ts b/src/helpers/type_guards/is_action_row.ts new file mode 100644 index 000000000..c64fc8a6d --- /dev/null +++ b/src/helpers/type_guards/is_action_row.ts @@ -0,0 +1,10 @@ +import { ActionRow } from "../../types/messages/components/action_row.ts"; +import { MessageComponent } from "../../types/messages/components/message_components.ts"; +import { MessageComponentTypes } from "../../types/messages/components/message_component_types.ts"; + +/** A type guard function to tell if it is a action row component */ +export function isActionRow( + component: MessageComponent, +): component is ActionRow { + return component.type === MessageComponentTypes.ActionRow; +} diff --git a/src/helpers/type_guards/is_button.ts b/src/helpers/type_guards/is_button.ts new file mode 100644 index 000000000..dfe2b92cd --- /dev/null +++ b/src/helpers/type_guards/is_button.ts @@ -0,0 +1,10 @@ +import { ButtonComponent } from "../../types/messages/components/button_component.ts"; +import { MessageComponent } from "../../types/messages/components/message_components.ts"; +import { MessageComponentTypes } from "../../types/messages/components/message_component_types.ts"; + +/** A type guard function to tell if it is a button component */ +export function isButton( + component: MessageComponent, +): component is ButtonComponent { + return component.type === MessageComponentTypes.Button; +} diff --git a/src/types/codes/json_error_codes.ts b/src/types/codes/json_error_codes.ts index 906828b3d..5d1a9bb30 100644 --- a/src/types/codes/json_error_codes.ts +++ b/src/types/codes/json_error_codes.ts @@ -43,6 +43,7 @@ export enum DiscordJsonErrorCodes { MaximumNumberOfInvitesReached, MaximumNumberOfGuildDiscoverySubcategoriesHasBeenReached = 30030, GuildAlreadyHasTemplate = 30031, + MaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035, UnauthorizedProvideAValidTokenAndTryAgain = 40001, YouNeedToVerifyYourAccountInOrderToPerformThisAction, RequestEntityTooLargeTrySendingSomethingSmallerInSize = 40005, diff --git a/src/types/guilds/guild.ts b/src/types/guilds/guild.ts index 7c590919f..edbd1fbb0 100644 --- a/src/types/guilds/guild.ts +++ b/src/types/guilds/guild.ts @@ -110,4 +110,6 @@ export interface Guild { approximatePresenceCount?: number; /** The welcome screen of a Community guild, shown to new members, returned when in the invite object */ welcomeScreen?: WelcomeScreen; + /** `true` if this guild is designated as NSFW */ + nsfw: boolean; } diff --git a/src/types/guilds/guild_widget_details.ts b/src/types/guilds/guild_widget_details.ts new file mode 100644 index 000000000..d10e75211 --- /dev/null +++ b/src/types/guilds/guild_widget_details.ts @@ -0,0 +1,24 @@ +import { SnakeCasedPropertiesDeep } from "../util.ts"; + +export interface GuildWidgetDetails { + id: string; + name: string; + instantInvite: string; + channels: { + id: string; + name: string; + position: number; + }[]; + members: { + id: string; + username: string; + discriminator: string; + avatar?: string | null; + status: string; + avatar_url: string; + }[]; + presenceCount: number; +} + +/** https://discord.com/developers/docs/resources/guild#get-guild-widget-example-get-guild-widget */ +export type DiscordGuildWidget = SnakeCasedPropertiesDeep; diff --git a/src/types/interactions/application_command_interaction_data.ts b/src/types/interactions/application_command_interaction_data.ts index d5653437a..e209cd208 100644 --- a/src/types/interactions/application_command_interaction_data.ts +++ b/src/types/interactions/application_command_interaction_data.ts @@ -4,11 +4,15 @@ import { ApplicationCommandInteractionDataResolved } from "./application_command /** https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata */ export interface ApplicationCommandInteractionData { /** The Id of the invoked command */ - id: string; + id?: string; /** The name of the invoked command */ - name: string; + name?: string; /** Converted users + roles + channels */ resolved?: ApplicationCommandInteractionDataResolved; /** The params + values from the user */ options?: ApplicationCommandInteractionDataOption[]; + /** with the value you defined for this component */ + customId?: string; + /** The type of this component */ + componentType?: 2; } diff --git a/src/types/interactions/application_command_option_types.ts b/src/types/interactions/application_command_option_types.ts index a3a45451c..da0f2ed76 100644 --- a/src/types/interactions/application_command_option_types.ts +++ b/src/types/interactions/application_command_option_types.ts @@ -8,6 +8,7 @@ export enum DiscordApplicationCommandOptionTypes { USER, CHANNEL, ROLE, + MENTIONABLE, } export type ApplicationCommandOptionTypes = diff --git a/src/types/interactions/interaction.ts b/src/types/interactions/interaction.ts index 32612abaf..975238ac8 100644 --- a/src/types/interactions/interaction.ts +++ b/src/types/interactions/interaction.ts @@ -1,4 +1,5 @@ import { GuildMemberWithUser } from "../guilds/guild_member.ts"; +import { Message } from "../messages/message.ts"; import { User } from "../users/user.ts"; import { ApplicationCommandInteractionData } from "./application_command_interaction_data.ts"; import { DiscordInteractionTypes } from "./interaction_types.ts"; @@ -25,4 +26,6 @@ export interface Interaction { token: string; /** Read-only property, always `1` */ version: 1; + /** For the message the button was attached to */ + message?: Message; } diff --git a/src/types/interactions/interaction_response_types.ts b/src/types/interactions/interaction_response_types.ts index 23343d0da..2fc9b09c6 100644 --- a/src/types/interactions/interaction_response_types.ts +++ b/src/types/interactions/interaction_response_types.ts @@ -6,6 +6,8 @@ export enum DiscordInteractionResponseTypes { ChannelMessageWithSource = 4, /** ACK an interaction and edit a response later, the user sees a loading state */ DeferredChannelMessageWithSource = 5, + /** It has no data fields. You can send this type **only in response to a button interaction .** It will acknowledge the interaction and update the button to a loading state. */ + DeferredMessageUpdate, } export type InteractionResponseTypes = DiscordInteractionResponseTypes; diff --git a/src/types/interactions/interaction_types.ts b/src/types/interactions/interaction_types.ts index 31b873a0f..5afcac139 100644 --- a/src/types/interactions/interaction_types.ts +++ b/src/types/interactions/interaction_types.ts @@ -2,6 +2,7 @@ export enum DiscordInteractionTypes { Ping = 1, ApplicationCommand, + Button, } export type InteractionTypes = DiscordInteractionTypes; diff --git a/src/types/invites/create_channel_invite.ts b/src/types/invites/create_channel_invite.ts index 50ac50762..4271e6984 100644 --- a/src/types/invites/create_channel_invite.ts +++ b/src/types/invites/create_channel_invite.ts @@ -1,7 +1,7 @@ import { DiscordInviteTargetTypes } from "./invite_target_types.ts"; export interface CreateChannelInvite { - /** Durationi of invite in seconds before expiry, or 0 for never. Between 0 and 604800 (7 days). Default: 86400 (24 hours) */ + /** Duration of invite in seconds before expiry, or 0 for never. Between 0 and 604800 (7 days). Default: 86400 (24 hours) */ maxAge?: number; /** Max number of users or 0 for unlimited. Between 0 and 100. Default: 0 */ maxUses?: number; diff --git a/src/types/invites/get_invite.ts b/src/types/invites/get_invite.ts index 46a3caaad..d071d1569 100644 --- a/src/types/invites/get_invite.ts +++ b/src/types/invites/get_invite.ts @@ -2,4 +2,6 @@ export interface GetInvite { /** Whether the invite should contain approximate member counts */ withCounts?: boolean; + /** Whether the invite should contain the expiration date */ + withExpiration?: boolean; } diff --git a/src/types/invites/invite.ts b/src/types/invites/invite.ts index f06dcb266..107016a8f 100644 --- a/src/types/invites/invite.ts +++ b/src/types/invites/invite.ts @@ -24,4 +24,6 @@ export interface Invite { approximatePresenceCount?: number; /** Approximate count of total members */ approximateMemberCount?: number; + /** The expiration date of this invite, returned from the `GET /invites/` endpoint when `with_expiration` is `true` */ + expiresAt?: string | null; } diff --git a/src/types/messages/components/action_row.ts b/src/types/messages/components/action_row.ts new file mode 100644 index 000000000..27a4a26e2 --- /dev/null +++ b/src/types/messages/components/action_row.ts @@ -0,0 +1,9 @@ +import { ButtonComponent } from "./button_component.ts"; + +// TODO: add docs link +export interface ActionRow { + /** Action rows are a group of buttons. */ + type: 1; + /** The button components */ + components: ButtonComponent[]; +} diff --git a/src/types/messages/components/button_component.ts b/src/types/messages/components/button_component.ts new file mode 100644 index 000000000..d72cdf137 --- /dev/null +++ b/src/types/messages/components/button_component.ts @@ -0,0 +1,29 @@ +import { SnakeCasedPropertiesDeep } from "../../util.ts"; +import { ButtonStyles } from "./button_styles.ts"; + +// TODO: add docs link +export interface ButtonComponent { + /** All button components have type 2 */ + type: 2; + /** for what the button says (max 80 characters) */ + label: string; + /** a dev-defined unique string sent on click (max 100 characters). type 5 Link buttons can not have a custom_id */ + customId?: string; + /** For different styles/colors of the buttons */ + style: ButtonStyles; + /** Emoji object that includes fields of name, id, and animated supporting unicode and custom emojis. */ + emoji?: { + /** Emoji id */ + id: string | null; + /** Emoji name (can only be null in reaction emoji objects) */ + name: string | null; + /** Whether this emoji is animated */ + animated?: boolean; + }; + /** optional url for link-style buttons that can navigate a user to the web. Only type 5 Link buttons can have a url */ + url?: string; + /** Whether or not this button is disabled */ + disabled?: boolean; +} + +export type DiscordButtonComponent = SnakeCasedPropertiesDeep; diff --git a/src/types/messages/components/button_styles.ts b/src/types/messages/components/button_styles.ts new file mode 100644 index 000000000..d0fc2b33c --- /dev/null +++ b/src/types/messages/components/button_styles.ts @@ -0,0 +1,16 @@ +// TODO: add docs link +export enum DiscordButtonStyles { + /** A blurple button */ + Primary = 1, + /** A grey button */ + Secondary, + /** A green button */ + Success, + /** A red button */ + Danger, + /** A button that navigates to a URL */ + Link, +} + +export type ButtonStyles = DiscordButtonStyles; +export const ButtonStyles = DiscordButtonStyles; diff --git a/src/types/messages/components/message_component_types.ts b/src/types/messages/components/message_component_types.ts new file mode 100644 index 000000000..18e37875e --- /dev/null +++ b/src/types/messages/components/message_component_types.ts @@ -0,0 +1,10 @@ +// TODO: add docs link +export enum DiscordMessageComponentTypes { + /** A row of components at the bottom of a message */ + ActionRow = 1, + /** A button! */ + Button, +} + +export type MessageComponentTypes = DiscordMessageComponentTypes; +export const MessageComponentTypes = DiscordMessageComponentTypes; diff --git a/src/types/messages/components/message_components.ts b/src/types/messages/components/message_components.ts new file mode 100644 index 000000000..045b0f75a --- /dev/null +++ b/src/types/messages/components/message_components.ts @@ -0,0 +1,6 @@ +import { ActionRow } from "./action_row.ts"; +import { ButtonComponent } from "./button_component.ts"; + +export type MessageComponent = ActionRow | ButtonComponent; + +export type MessageComponents = MessageComponent[]; diff --git a/src/types/messages/create_message.ts b/src/types/messages/create_message.ts index e4cdcf7e4..836b0040c 100644 --- a/src/types/messages/create_message.ts +++ b/src/types/messages/create_message.ts @@ -3,6 +3,7 @@ import { AllowedMentions } from "../messages/allowed_mentions.ts"; import { MessageReference } from "../messages/message_reference.ts"; import { FileContent } from "../misc/file_content.ts"; import { SnakeCasedPropertiesDeep } from "../util.ts"; +import { MessageComponents } from "./components/message_components.ts"; export interface CreateMessage { /** The message contents (up to 2000 characters) */ @@ -19,6 +20,8 @@ export interface CreateMessage { messageReference?: MessageReference; /** The contents of the file being sent */ file?: FileContent | FileContent[]; + /** The components you would like to have sent in this message */ + components?: MessageComponents; } /** https://discord.com/developers/docs/resources/channel#create-message */ diff --git a/src/types/messages/edit_message.ts b/src/types/messages/edit_message.ts index 9b726a411..41169bc55 100644 --- a/src/types/messages/edit_message.ts +++ b/src/types/messages/edit_message.ts @@ -1,5 +1,6 @@ import { Embed } from "../embeds/embed.ts"; import { AllowedMentions } from "./allowed_mentions.ts"; +import { Attachment } from "./attachment.ts"; /** https://discord.com/developers/docs/resources/channel#edit-message-json-params */ export interface EditMessage { @@ -11,4 +12,6 @@ export interface EditMessage { flags?: 4 | null; /** Allowed mentions for the message */ allowedMentions?: AllowedMentions | null; + /** Attached files to keep */ + attachments?: Attachment | null; } diff --git a/src/types/messages/message.ts b/src/types/messages/message.ts index 9c4d87deb..de3956f0a 100644 --- a/src/types/messages/message.ts +++ b/src/types/messages/message.ts @@ -6,6 +6,7 @@ import { MessageInteraction } from "../interactions/message_interaction.ts"; import { Application } from "../oauth2/application.ts"; import { User } from "../users/user.ts"; import { Attachment } from "./attachment.ts"; +import { MessageComponents } from "./components/message_components.ts"; import { MessageActivity } from "./message_activity.ts"; import { MessageReference } from "./message_reference.ts"; import { MessageSticker } from "./message_sticker.ts"; @@ -86,4 +87,6 @@ export interface Message { interaction?: MessageInteraction; /** The thread that was started from this message, includes thread member object */ thread?: Omit & { member: ThreadMember }; + /** The components related to this message */ + components: MessageComponents; } diff --git a/src/types/messages/message_sticker.ts b/src/types/messages/message_sticker.ts index bcc369508..d1a9bf04e 100644 --- a/src/types/messages/message_sticker.ts +++ b/src/types/messages/message_sticker.ts @@ -17,11 +17,6 @@ export interface MessageSticker { * Note: The URL for fetching sticker assets is currently private. */ asset: string; - /** - * Sticker preview asset hash - * Note: The URL for fetching sticker assets is currently private. - */ - previewAsset?: string | null; /** Type of sticker format */ formatType: DiscordMessageStickerFormatTypes; } diff --git a/src/types/misc/errors.ts b/src/types/misc/errors.ts index f8e441b72..ee55755c0 100644 --- a/src/types/misc/errors.ts +++ b/src/types/misc/errors.ts @@ -68,9 +68,19 @@ export enum Errors { USERNAME_INVALID_USERNAME = "USERNAME_INVALID_USERNAME", USERNAME_MAX_LENGTH = "USERNAME_MAX_LENGTH", USERNAME_MIN_LENGTH = "USERNAME_MIN_LENGTH", + NONCE_TOO_LONG = "NONCE_TOO_LONG", + INVITE_MAX_AGE_INVALID = "INVITE_MAX_AGE_INVALID", + INVITE_MAX_USES_INVALID = "INVITE_MAX_USES_INVALID", // API Errors RATE_LIMIT_RETRY_MAXED = "RATE_LIMIT_RETRY_MAXED", REQUEST_CLIENT_ERROR = "REQUEST_CLIENT_ERROR", REQUEST_SERVER_ERROR = "REQUEST_SERVER_ERROR", REQUEST_UNKNOWN_ERROR = "REQUEST_UNKNOWN_ERROR", + // Component Errors + TOO_MANY_COMPONENTS = "TOO_MANY_COMPONENTS", + TOO_MANY_ACTION_ROWS = "TOO_MANY_ACTION_ROWS", + LINK_BUTTON_CANNOT_HAVE_CUSTOM_ID = "LINK_BUTTON_CANNOT_HAVE_CUSTOM_ID", + COMPONENT_LABEL_TOO_BIG = "COMPONENT_LABEL_TOO_BIG", + COMPONENT_CUSTOM_ID_TOO_BIG = "COMPONENT_CUSTOM_ID_TOO_BIG", + BUTTON_REQUIRES_CUSTOM_ID = "BUTTON_REQUIRES_CUSTOM_ID", } diff --git a/src/types/mod.ts b/src/types/mod.ts index dd18a8ec4..bc4d06750 100644 --- a/src/types/mod.ts +++ b/src/types/mod.ts @@ -131,6 +131,11 @@ export * from "./members/guild_member_add.ts"; export * from "./members/guild_member_remove.ts"; export * from "./members/guild_member_update.ts"; export * from "./members/search_guild_members.ts"; +export * from "./messages/components/action_row.ts"; +export * from "./messages/components/button_component.ts"; +export * from "./messages/components/button_styles.ts"; +export * from "./messages/components/message_component_types.ts"; +export * from "./messages/components/message_components.ts"; export * from "./messages/allowed_mentions.ts"; export * from "./messages/allowed_mentions_types.ts"; export * from "./messages/attachment.ts"; diff --git a/src/types/webhooks/edit_webhook_message.ts b/src/types/webhooks/edit_webhook_message.ts index 5b8275c98..4076c5cb8 100644 --- a/src/types/webhooks/edit_webhook_message.ts +++ b/src/types/webhooks/edit_webhook_message.ts @@ -1,6 +1,7 @@ import { Embed } from "../embeds/embed.ts"; import { AllowedMentions } from "../messages/allowed_mentions.ts"; -import { FileContent } from "../mod.ts"; +import { FileContent } from "../misc/file_content.ts"; +import { Attachment } from "../messages/attachment.ts"; /** https://discord.com/developers/docs/resources/webhook#edit-webhook-message-jsonform-params */ export interface EditWebhookMessage { @@ -12,4 +13,6 @@ export interface EditWebhookMessage { file: FileContent | FileContent[]; /** Allowed mentions for the message */ allowedMentions?: AllowedMentions | null; + /** Attached files to keep */ + attachments?: Attachment | null; } diff --git a/src/ws/create_shard.ts b/src/ws/create_shard.ts index a4b90af7a..59843e66d 100644 --- a/src/ws/create_shard.ts +++ b/src/ws/create_shard.ts @@ -35,6 +35,15 @@ export async function createShard(shardId: number) { } switch (event.code) { + // Discordeno tests finished + case 3061: + return; + case 3063: // Resharded + case 3064: // Resuming + case 3065: // Reidentifying + case 3066: // Missing ACK + // Will restart shard manually + return ws.log("CLOSED_RECONNECT", { shardId, payload: event }); case DiscordGatewayCloseEventCodes.UnknownOpcode: case DiscordGatewayCloseEventCodes.DecodeError: case DiscordGatewayCloseEventCodes.AuthenticationFailed: diff --git a/src/ws/events.ts b/src/ws/events.ts index 02b70000d..a64d7e474 100644 --- a/src/ws/events.ts +++ b/src/ws/events.ts @@ -61,5 +61,6 @@ export function log( | "DEBUG", data: unknown, ) { - console.log(type, data); + // This is just a placeholder for the dev to override + if (!type && !data) console.log(type, data); } diff --git a/src/ws/spawn_shards.ts b/src/ws/spawn_shards.ts index b7b449ba0..7dd24eff2 100644 --- a/src/ws/spawn_shards.ts +++ b/src/ws/spawn_shards.ts @@ -50,7 +50,6 @@ export function spawnShards(firstShardId = 0) { } } - console.log("BUCKETS", ws.buckets); // SPREAD THIS OUT TO DIFFERENT CLUSTERS TO BEGIN STARTING UP ws.buckets.forEach(async (bucket, bucketId) => { ws.log( diff --git a/tests/messages/add_reaction.ts b/tests/messages/add_reaction.ts index e8df075d9..540cb0776 100644 --- a/tests/messages/add_reaction.ts +++ b/tests/messages/add_reaction.ts @@ -1,11 +1,11 @@ import { defaultTestOptions, tempData } from "../ws/start_bot.ts"; import { assertEquals, assertExists } from "../deps.ts"; import { cache } from "../../src/cache.ts"; -import { DiscordReaction } from "../../src/types/messages/reaction.ts"; import { sendMessage } from "../../src/helpers/messages/send_message.ts"; import { addReaction } from "../../src/helpers/messages/add_reaction.ts"; import { createEmoji } from "../../src/helpers/emojis/create_emoji.ts"; import { delayUntil } from "../util/delay_until.ts"; +import { Reaction } from "../../src/types/messages/reaction.ts"; async function ifItFailsBlameWolf(type: "getter" | "raw", custom = false) { const message = await sendMessage(tempData.channelId, "Hello World!"); @@ -54,7 +54,7 @@ async function ifItFailsBlameWolf(type: "getter" | "raw", custom = false) { await cache.messages .get(message.id) ?.reactions?.filter( - (reaction: DiscordReaction) => + (reaction: Reaction) => reaction.emoji?.name === (custom ? "blamewolf" : "❤"), ).length, 1,