diff --git a/packages/benchmark/src/benchmarks/transformers.ts b/packages/benchmark/src/benchmarks/transformers.ts new file mode 100644 index 000000000..69e87c781 --- /dev/null +++ b/packages/benchmark/src/benchmarks/transformers.ts @@ -0,0 +1,753 @@ +import { + ApplicationFlags, + ButtonStyles, + iconHashToBigInt, + InteractionTypes, + MessageActivityTypes, + MessageComponentTypes, + MessageTypes, + PremiumTypes, + StickerFormatTypes, + TeamMembershipStates, + TextStyles, + UserFlags, + type Bot, + type DiscordMessage, +} from '@discordeno/bot' +import { MemberToggles } from '@discordeno/bot/dist/transformers/index.js' +import { createBot } from '../../../bot/dist/index.js' +import { memoryBenchmark } from '../utils/memoryBenchmark.js' + +export const CHANNEL_MENTION_REGEX = /<#[0-9]+>/g + +const MESSAGE_SIZE = 20000 + +const bot = createBot({ + token: process.env.DISCORD_TOKEN ?? " ", + events: {}, +}) + +bot.transformers.desiredProperties.message.activity = true +bot.transformers.desiredProperties.message.application = true +bot.transformers.desiredProperties.message.applicationId = true +bot.transformers.desiredProperties.message.attachments = true +bot.transformers.desiredProperties.message.author = true +bot.transformers.desiredProperties.message.channelId = true +bot.transformers.desiredProperties.message.components = true +bot.transformers.desiredProperties.message.content = true +bot.transformers.desiredProperties.message.editedTimestamp = true +bot.transformers.desiredProperties.message.embeds = true +bot.transformers.desiredProperties.message.guildId = true +bot.transformers.desiredProperties.message.id = true +bot.transformers.desiredProperties.message.interaction.id = true +bot.transformers.desiredProperties.message.interaction.member = true +bot.transformers.desiredProperties.message.interaction.name = true +bot.transformers.desiredProperties.message.interaction.user = true +bot.transformers.desiredProperties.message.interaction.type = true +bot.transformers.desiredProperties.message.member = true +bot.transformers.desiredProperties.message.mentionedChannelIds = true +bot.transformers.desiredProperties.message.mentionedRoleIds = true +bot.transformers.desiredProperties.message.mentions = true +bot.transformers.desiredProperties.message.messageReference.messageId = true +bot.transformers.desiredProperties.message.messageReference.channelId = true +bot.transformers.desiredProperties.message.messageReference.guildId = true +bot.transformers.desiredProperties.message.nonce = true +bot.transformers.desiredProperties.message.reactions = true +bot.transformers.desiredProperties.message.stickerItems = true +bot.transformers.desiredProperties.message.thread = true +bot.transformers.desiredProperties.message.type = true +bot.transformers.desiredProperties.message.webhookId = true + +const URL = 'https://discordeno.js.org/' +const IMAGE_HASH = '5fff867ae5f666fcd0626bd84f5e69c0' +const GUILD_ID = '785384884197392384' +const USER = { + accent_color: 0, + avatar: IMAGE_HASH, + banner: IMAGE_HASH, + bot: true, + discriminator: '1234', + email: 'discordeno@discordeno.com', + flags: UserFlags.BotHttpInteractions, + id: GUILD_ID, + locale: 'en', + mfa_enabled: true, + premium_type: PremiumTypes.Nitro, + public_flags: UserFlags.BotHttpInteractions, + system: true, + username: 'skillz', + verified: true, +} +const MEMBER = { + nick: 'John', + roles: ['111111111111111111', '222222222222222222', '333333333333333333'], + joined_at: '2022-01-01T00:00:00.000Z', + premium_since: '2022-02-01T00:00:00.000Z', + deaf: false, + mute: true, + pending: false, + permissions: '2147483647', +} + +console.log('before the bench') +await memoryBenchmark( + '[transformer] message cache check', + () => ({ + cache: [] as any[], + }), // function reutrn a new instance of object wanted to test with + (object, event: DiscordMessage) => object.cache.push(bot.transformers.message(bot, event)), + // function specify how to add event to the object/ run the object + [...new Array(MESSAGE_SIZE)].map( + (i) => + ({ + activity: { + party_id: 'party_id', + type: MessageActivityTypes.Join, + }, + application: { + bot_public: true, + bot_require_code_grant: true, + cover_image: IMAGE_HASH, + custom_install_url: 'https://google.com', + description: 'discordeno is the best lib ever', + flags: ApplicationFlags.GatewayGuildMembers, + guild_id: GUILD_ID, + icon: IMAGE_HASH, + id: GUILD_ID, + install_params: { + permissions: '8', + scopes: ['identify'], + }, + name: 'skillz', + owner: USER, + primary_sku_id: GUILD_ID, + privacy_policy_url: 'https://discordeno.js.org', + role_connections_verification_url: 'https://discordeno.js.org', + rpc_origins: [], + slug: 'discordeno', + tags: ['discordeno', 'discordeno', 'discordeno', 'discordeno', 'discordeno'], + team: { + icon: IMAGE_HASH, + id: GUILD_ID, + members: [ + { + membership_state: TeamMembershipStates.Accepted, + permissions: ['*'], + team_id: GUILD_ID, + user: USER, + }, + { + membership_state: TeamMembershipStates.Accepted, + permissions: ['*'], + team_id: GUILD_ID, + user: USER, + }, + ], + name: 'discordeno', + owner_user_id: GUILD_ID, + }, + terms_of_service_url: 'https://discordeno.js.org', + verify_key: IMAGE_HASH, + }, + application_id: GUILD_ID, + attachments: [ + { + content_type: 'application/json', + description: 'discordeno discordeno discordeno', + ephemeral: true, + filename: 'discordeno', + height: 100, + id: GUILD_ID, + proxy_url: 'https://discordeno.js.org', + size: 100, + url: 'https://discordeno.js.org', + width: 100, + }, + { + content_type: 'application/json', + description: 'discordeno discordeno discordeno', + ephemeral: true, + filename: 'discordeno', + height: 100, + id: GUILD_ID, + proxy_url: 'https://discordeno.js.org', + size: 100, + url: 'https://discordeno.js.org', + width: 100, + }, + ], + author: USER, + channel_id: GUILD_ID, + components: [ + { + type: 1, + components: [ + { + custom_id: GUILD_ID, + disabled: true, + emoji: { + animated: true, + id: GUILD_ID, + name: 'discordeno', + }, + label: 'discordeno', + style: ButtonStyles.Danger, + type: MessageComponentTypes.Button, + url: 'https://discordeno.js.org', + }, + ], + }, + { + type: 1, + components: [ + { + type: MessageComponentTypes.InputText, + custom_id: 'discordeno', + label: 'discordeno', + max_length: 100, + min_length: 100, + placeholder: 'discordeno', + required: true, + style: TextStyles.Paragraph, + value: 'discordeno', + }, + ], + }, + { + type: 1, + components: [ + { + type: MessageComponentTypes.SelectMenu, + custom_id: 'discordeno', + max_values: 100, + min_values: 100, + options: [ + { + default: true, + description: 'idk idk idk', + emoji: { + animated: true, + id: GUILD_ID, + name: 'discordeno', + }, + label: 'discordeno', + value: 'discordeno', + }, + ], + placeholder: 'discordeno', + }, + ], + }, + ], + content: 'discordeno', + edited_timestamp: new Date().toISOString(), + embeds: [ + { + author: { + icon_url: URL, + name: 'discordeno', + proxy_icon_url: URL, + url: URL, + }, + color: 0, + description: 'discordeno', + fields: [ + { + name: 'discordeno', + value: 'discordeno', + inline: true, + }, + { + name: 'discordeno', + value: 'discordeno', + inline: true, + }, + ], + footer: { + icon_url: URL, + proxy_icon_url: URL, + text: 'discordeno', + }, + image: { + height: 100, + width: 100, + proxy_url: URL, + url: URL, + }, + provider: { + name: 'discordeno', + url: URL, + }, + thumbnail: { + height: 100, + width: 100, + proxy_url: URL, + url: URL, + }, + timestamp: new Date().toISOString(), + title: 'discordeno', + type: 'rich', + url: URL, + video: { + height: 100, + width: 100, + proxy_url: URL, + url: URL, + }, + }, + ], + flags: 64, + guild_id: GUILD_ID, + id: GUILD_ID, + interaction: { + id: GUILD_ID, + name: 'discordeno', + type: InteractionTypes.ApplicationCommand, + user: USER, + member: MEMBER, + }, + member: MEMBER, + mention_channels: [ + { + guild_id: GUILD_ID, + id: GUILD_ID, + name: 'discordeno', + type: 0, + }, + ], + mention_roles: ['111111111111111111', '222222222222222222'], + mention_everyone: false, + mentions: [USER, USER, USER], + message_reference: { + message_id: GUILD_ID, + channel_id: GUILD_ID, + guild_id: GUILD_ID, + fail_if_not_exists: true, + }, + nonce: 'discordeno', + pinned: true, + position: 100, + reactions: [ + { + count: 100, + emoji: { + animated: true, + id: GUILD_ID, + name: 'discordeno', + }, + me: true, + }, + ], + sticker_items: [ + { + format_type: StickerFormatTypes.APng, + id: GUILD_ID, + name: 'discordeno', + }, + ], + thread: { + id: '987654321098765432', + name: 'My Thread', + type: 11, + guild_id: '123456789012345678', + parent_id: '876543210987654321', + owner_id: '111111111111111111', + message_count: 10, + member_count: 5, + created_timestamp: 1651388000, + last_message_id: '876543210987654321', + applied_tags: ['discordeno'], + default_thread_rate_limit_per_user: 100, + member: { + flags: 100, + id: GUILD_ID, + join_timestamp: new Date().toISOString(), + user_id: GUILD_ID, + }, + }, + timestamp: new Date().toISOString(), + tts: true, + type: MessageTypes.Default, + webhook_id: GUILD_ID, + } as DiscordMessage), + ), // array of event to test with + { times: 1, log: false, table: false }, +) +console.log('after the bench') + +function oldtransformMessage(bot: Bot, payload: DiscordMessage): any { + const guildId = payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined + const userId = bot.transformers.snowflake(payload.author.id) + + const message = { + // UNTRANSFORMED STUFF HERE + content: payload.content ?? '', + isFromBot: payload.author.bot ?? false, + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + tag: `${payload.author.username}#${payload.author.discriminator}`, + timestamp: Date.parse(payload.timestamp), + editedTimestamp: payload.edited_timestamp ? Date.parse(payload.edited_timestamp) : undefined, + bitfield: (payload.tts ? 1n : 0n) | (payload.mention_everyone ? 2n : 0n) | (payload.pinned ? 4n : 0n), + attachments: payload.attachments?.map((attachment) => bot.transformers.attachment(bot, attachment)), + embeds: payload.embeds?.map((embed) => bot.transformers.embed(bot, embed)), + reactions: payload.reactions?.map((reaction) => ({ + me: reaction.me, + count: reaction.count, + emoji: bot.transformers.emoji(bot, reaction.emoji), + })), + type: payload.type, + activity: payload.activity + ? { + type: payload.activity.type, + partyId: payload.activity.party_id, + } + : undefined, + application: payload.application, + flags: payload.flags, + interaction: payload.interaction + ? { + id: bot.transformers.snowflake(payload.interaction.id), + type: payload.interaction.type, + name: payload.interaction.name, + user: bot.transformers.user(bot, payload.interaction.user), + member: payload.interaction.member + ? { + id: userId, + guildId, + nick: payload.interaction.member.nick ?? undefined, + roles: payload.interaction.member.roles?.map((id) => bot.transformers.snowflake(id)), + joinedAt: payload.interaction.member.joined_at ? Date.parse(payload.interaction.member.joined_at) : undefined, + premiumSince: payload.interaction.member.premium_since ? Date.parse(payload.interaction.member.premium_since) : undefined, + toggles: new MemberToggles(payload.interaction.member), + avatar: payload.interaction.member.avatar ? iconHashToBigInt(payload.interaction.member.avatar) : undefined, + permissions: payload.interaction.member.permissions ? bot.transformers.snowflake(payload.interaction.member.permissions) : undefined, + communicationDisabledUntil: payload.interaction.member.communication_disabled_until + ? Date.parse(payload.interaction.member.communication_disabled_until) + : undefined, + } + : undefined, + } + : undefined, + thread: payload.thread ? bot.transformers.channel(bot, { channel: payload.thread, guildId }) : undefined, + components: payload.components?.map((component) => bot.transformers.component(bot, component)), + stickerItems: payload.sticker_items?.map((sticker) => ({ + id: bot.transformers.snowflake(sticker.id), + name: sticker.name, + formatType: sticker.format_type, + })), + + // TRANSFORMED STUFF BELOW + id: bot.transformers.snowflake(payload.id), + guildId, + channelId: bot.transformers.snowflake(payload.channel_id), + webhookId: payload.webhook_id ? bot.transformers.snowflake(payload.webhook_id) : undefined, + authorId: userId, + applicationId: payload.application_id ? bot.transformers.snowflake(payload.application_id) : undefined, + messageReference: payload.message_reference + ? { + messageId: payload.message_reference.message_id ? bot.transformers.snowflake(payload.message_reference.message_id) : undefined, + channelId: payload.message_reference.channel_id ? bot.transformers.snowflake(payload.message_reference.channel_id) : undefined, + guildId: payload.message_reference.guild_id ? bot.transformers.snowflake(payload.message_reference.guild_id) : undefined, + } + : undefined, + mentionedUserIds: payload.mentions ? payload.mentions.map((m) => bot.transformers.snowflake(m.id)) : [], + mentionedRoleIds: payload.mention_roles ? payload.mention_roles.map((id) => bot.transformers.snowflake(id)) : [], + mentionedChannelIds: [ + // Keep any ids tht discord sends + ...(payload.mention_channels ?? []).map((m) => bot.transformers.snowflake(m.id)), + // Add any other ids that can be validated in a channel mention format + ...(payload.content?.match(CHANNEL_MENTION_REGEX) ?? []).map((text) => + // converts the <#123> into 123 + bot.transformers.snowflake(text.substring(2, text.length - 1)), + ), + ], + member: payload.member && guildId ? bot.transformers.member(bot, payload.member, guildId, userId) : undefined, + nonce: payload.nonce, + } + + return message +} + +await memoryBenchmark( + '[transformer] old message cache check', + () => ({ + cache: [] as any[], + }), // function reutrn a new instance of object wanted to test with + (object, event: DiscordMessage) => object.cache.push(oldtransformMessage(bot as any, event)), + // function specify how to add event to the object/ run the object + [...new Array(MESSAGE_SIZE)].map( + (i) => + ({ + activity: { + party_id: 'party_id', + type: MessageActivityTypes.Join, + }, + application: { + bot_public: true, + bot_require_code_grant: true, + cover_image: IMAGE_HASH, + custom_install_url: 'https://google.com', + description: 'discordeno is the best lib ever', + flags: ApplicationFlags.GatewayGuildMembers, + guild_id: GUILD_ID, + icon: IMAGE_HASH, + id: GUILD_ID, + install_params: { + permissions: '8', + scopes: ['identify'], + }, + name: 'skillz', + owner: USER, + primary_sku_id: GUILD_ID, + privacy_policy_url: 'https://discordeno.js.org', + role_connections_verification_url: 'https://discordeno.js.org', + rpc_origins: [], + slug: 'discordeno', + tags: ['discordeno', 'discordeno', 'discordeno', 'discordeno', 'discordeno'], + team: { + icon: IMAGE_HASH, + id: GUILD_ID, + members: [ + { + membership_state: TeamMembershipStates.Accepted, + permissions: ['*'], + team_id: GUILD_ID, + user: USER, + }, + { + membership_state: TeamMembershipStates.Accepted, + permissions: ['*'], + team_id: GUILD_ID, + user: USER, + }, + ], + name: 'discordeno', + owner_user_id: GUILD_ID, + }, + terms_of_service_url: 'https://discordeno.js.org', + verify_key: IMAGE_HASH, + }, + application_id: GUILD_ID, + attachments: [ + { + content_type: 'application/json', + description: 'discordeno discordeno discordeno', + ephemeral: true, + filename: 'discordeno', + height: 100, + id: GUILD_ID, + proxy_url: 'https://discordeno.js.org', + size: 100, + url: 'https://discordeno.js.org', + width: 100, + }, + { + content_type: 'application/json', + description: 'discordeno discordeno discordeno', + ephemeral: true, + filename: 'discordeno', + height: 100, + id: GUILD_ID, + proxy_url: 'https://discordeno.js.org', + size: 100, + url: 'https://discordeno.js.org', + width: 100, + }, + ], + author: USER, + channel_id: GUILD_ID, + components: [ + { + type: 1, + components: [ + { + custom_id: GUILD_ID, + disabled: true, + emoji: { + animated: true, + id: GUILD_ID, + name: 'discordeno', + }, + label: 'discordeno', + style: ButtonStyles.Danger, + type: MessageComponentTypes.Button, + url: 'https://discordeno.js.org', + }, + ], + }, + { + type: 1, + components: [ + { + type: MessageComponentTypes.InputText, + custom_id: 'discordeno', + label: 'discordeno', + max_length: 100, + min_length: 100, + placeholder: 'discordeno', + required: true, + style: TextStyles.Paragraph, + value: 'discordeno', + }, + ], + }, + { + type: 1, + components: [ + { + type: MessageComponentTypes.SelectMenu, + custom_id: 'discordeno', + max_values: 100, + min_values: 100, + options: [ + { + default: true, + description: 'idk idk idk', + emoji: { + animated: true, + id: GUILD_ID, + name: 'discordeno', + }, + label: 'discordeno', + value: 'discordeno', + }, + ], + placeholder: 'discordeno', + }, + ], + }, + ], + content: 'discordeno', + edited_timestamp: new Date().toISOString(), + embeds: [ + { + author: { + icon_url: URL, + name: 'discordeno', + proxy_icon_url: URL, + url: URL, + }, + color: 0, + description: 'discordeno', + fields: [ + { + name: 'discordeno', + value: 'discordeno', + inline: true, + }, + { + name: 'discordeno', + value: 'discordeno', + inline: true, + }, + ], + footer: { + icon_url: URL, + proxy_icon_url: URL, + text: 'discordeno', + }, + image: { + height: 100, + width: 100, + proxy_url: URL, + url: URL, + }, + provider: { + name: 'discordeno', + url: URL, + }, + thumbnail: { + height: 100, + width: 100, + proxy_url: URL, + url: URL, + }, + timestamp: new Date().toISOString(), + title: 'discordeno', + type: 'rich', + url: URL, + video: { + height: 100, + width: 100, + proxy_url: URL, + url: URL, + }, + }, + ], + flags: 64, + guild_id: GUILD_ID, + id: GUILD_ID, + interaction: { + id: GUILD_ID, + name: 'discordeno', + type: InteractionTypes.ApplicationCommand, + user: USER, + member: MEMBER, + }, + member: MEMBER, + mention_channels: [ + { + guild_id: GUILD_ID, + id: GUILD_ID, + name: 'discordeno', + type: 0, + }, + ], + mention_roles: ['111111111111111111', '222222222222222222'], + mention_everyone: false, + mentions: [USER, USER, USER], + message_reference: { + message_id: GUILD_ID, + channel_id: GUILD_ID, + guild_id: GUILD_ID, + fail_if_not_exists: true, + }, + nonce: 'discordeno', + pinned: true, + position: 100, + reactions: [ + { + count: 100, + emoji: { + animated: true, + id: GUILD_ID, + name: 'discordeno', + }, + me: true, + }, + ], + sticker_items: [ + { + format_type: StickerFormatTypes.APng, + id: GUILD_ID, + name: 'discordeno', + }, + ], + thread: { + id: '987654321098765432', + name: 'My Thread', + type: 11, + guild_id: '123456789012345678', + parent_id: '876543210987654321', + owner_id: '111111111111111111', + message_count: 10, + member_count: 5, + created_timestamp: 1651388000, + last_message_id: '876543210987654321', + applied_tags: ['discordeno'], + default_thread_rate_limit_per_user: 100, + member: { + flags: 100, + id: GUILD_ID, + join_timestamp: new Date().toISOString(), + user_id: GUILD_ID, + }, + }, + timestamp: new Date().toISOString(), + tts: true, + type: MessageTypes.Default, + webhook_id: GUILD_ID, + } as DiscordMessage), + ), // array of event to test with + { times: 1, log: false, table: false }, +) diff --git a/packages/benchmark/src/utils/memoryBenchmark.ts b/packages/benchmark/src/utils/memoryBenchmark.ts index 353624d73..256ac4f17 100644 --- a/packages/benchmark/src/utils/memoryBenchmark.ts +++ b/packages/benchmark/src/utils/memoryBenchmark.ts @@ -35,9 +35,13 @@ export async function memoryBenchmark( // Set the memory stats for when files are loaded in. results.loaded = process.memoryUsage() - events.forEach((event) => { + // events.forEach((event, i) => { + // console.log('logging event', i) + // objectFeeder(object, event) + // }) + for (const event of events) { objectFeeder(object, event) - }) + } if (options.log) { console.log(`[INFO] Processed ${events.length} events.`) diff --git a/packages/bot/src/transformers.ts b/packages/bot/src/transformers.ts index d7f1eda3f..0273d98e6 100644 --- a/packages/bot/src/transformers.ts +++ b/packages/bot/src/transformers.ts @@ -61,11 +61,6 @@ import { transformEmbed, type Embed } from './transformers/embed.js' import { transformEmoji, type Emoji } from './transformers/emoji.js' import { transformGatewayBot, type GetGatewayBot } from './transformers/gatewayBot.js' import { transformGuild, type Guild } from './transformers/guild.js' -import { transformIntegration, type Integration } from './transformers/integration.js' -import { transformInteraction, transformInteractionDataOption, type Interaction, type InteractionDataOption } from './transformers/interaction.js' -import { transformInvite, type Invite } from './transformers/invite.js' -import { transformMember, transformUser, type Member, type User } from './transformers/member.js' -import { transformMessage, type Message } from './transformers/message.js' import { transformActivityToDiscordActivity, transformApplicationCommandOptionChoiceToDiscordApplicationCommandOptionChoice, @@ -79,6 +74,11 @@ import { transformTeamToDiscordTeam, transformUserToDiscordUser, } from './transformers/index.js' +import { transformIntegration, type Integration } from './transformers/integration.js' +import { transformInteraction, transformInteractionDataOption, type Interaction, type InteractionDataOption } from './transformers/interaction.js' +import { transformInvite, type Invite } from './transformers/invite.js' +import { transformMember, transformUser, type Member, type User } from './transformers/member.js' +import { transformMessage, type Message } from './transformers/message.js' import { transformPresence, type PresenceUpdate } from './transformers/presence.js' import { transformAllowedMentionsToDiscordAllowedMentions } from './transformers/reverse/allowedMentions.js' import { transformCreateApplicationCommandToDiscordCreateApplicationCommand } from './transformers/reverse/createApplicationCommand.js' @@ -99,6 +99,227 @@ import { transformWidgetSettings, type GuildWidgetSettings } from './transformer import type { BotInteractionResponse, DiscordComponent, DiscordInteractionResponse } from './typings.js' export interface Transformers { + customizers: { + message: (bot: Bot, payload: DiscordMessage, message: Message) => any + } + desiredProperties: { + attachment: { + id: boolean + filename: boolean + contentType: boolean + size: boolean + url: boolean + proxyUrl: boolean + height: boolean + width: boolean + ephemeral: boolean + description: boolean + } + channel: { + type: boolean + position: boolean + name: boolean + topic: boolean + nsfw: boolean + bitrate: boolean + userLimit: boolean + rateLimitPerUser: boolean + rtcRegion: boolean + videoQualityMode: boolean + guildId: boolean + lastPinTimestamp: boolean + permissionOverwrites: boolean + id: boolean + permissions: boolean + lastMessageId: boolean + ownerId: boolean + applicationId: boolean + managed: boolean + parentId: boolean + memberCount: boolean + messageCount: boolean + archiveTimestamp: boolean + autoArchiveDuration: boolean + botIsMember: boolean + archived: boolean + locked: boolean + invitable: boolean + createTimestamp: boolean + newlyCreated: boolean + flags: boolean + } + emoji: { + id: boolean + name: boolean + roles: boolean + user: boolean + } + guild: { + afkTimeout: boolean + approximateMemberCount: boolean + approximatePresenceCount: boolean + defaultMessageNotifications: boolean + description: boolean + explicitContentFilter: boolean + maxMembers: boolean + maxPresences: boolean + maxVideoChannelUsers: boolean + mfaLevel: boolean + name: boolean + nsfwLevel: boolean + preferredLocale: boolean + premiumSubscriptionCount: boolean + premiumTier: boolean + stageInstances: boolean + systemChannelFlags: boolean + vanityUrlCode: boolean + verificationLevel: boolean + welcomeScreen: boolean + discoverySplash: boolean + joinedAt: boolean + memberCount: boolean + shardId: boolean + icon: boolean + banner: boolean + splash: boolean + id: boolean + ownerId: boolean + permissions: boolean + afkChannelId: boolean + widgetChannelId: boolean + applicationId: boolean + systemChannelId: boolean + rulesChannelId: boolean + publicUpdatesChannelId: boolean + premiumProgressBarEnabled: boolean + } + invite: { + channelId: boolean + code: boolean + createdAt: boolean + guildId: boolean + inviter: boolean + maxAge: boolean + maxUses: boolean + targetType: boolean + targetUser: boolean + targetApplication: boolean + temporary: boolean + uses: boolean + } + member: { + id: boolean + guildId: boolean + user: boolean + nick: boolean + roles: boolean + joinedAt: boolean + premiumSince: boolean + avatar: boolean + permissions: boolean + communicationDisabledUntil: boolean + } + message: { + activity: boolean + application: boolean + applicationId: boolean + attachments: boolean + author: boolean + channelId: boolean + components: boolean + content: boolean + editedTimestamp: boolean + embeds: boolean + guildId: boolean + id: boolean + interaction: { + id: boolean + member: boolean + name: boolean + user: boolean + type: boolean + } + member: boolean + mentionedChannelIds: boolean + mentionedRoleIds: boolean + mentions: boolean + messageReference: { + messageId: boolean + channelId: boolean + guildId: boolean + } + nonce: boolean + reactions: boolean + stickerItems: boolean + thread: boolean + type: boolean + webhookId: boolean + } + role: { + name: boolean; + guildId: boolean; + position: boolean; + color: boolean; + id: boolean; + botId: boolean; + integrationId: boolean; + permissions: boolean; + icon: boolean; + unicodeEmoji: boolean; + } + scheduledEvent: { + id: boolean; + guildId: boolean; + channelId: boolean + creatorId: boolean + scheduledStartTime: boolean + scheduledEndTime: boolean + entityId: boolean + creator: boolean + name: boolean + description: boolean + privacyLevel: boolean + status: boolean + entityType: boolean + userCount: boolean + location: boolean + image: boolean + } + stageInstance: { + id: boolean + guildId: boolean + channelId: boolean + topic: boolean + guildScheduledEventId: boolean + } + sticker: { + id: boolean + packId: boolean + name: boolean + description: boolean + tags: boolean + type: boolean + formatType: boolean + available: boolean + guildId: boolean + user: boolean + sortValue: boolean + } + webhook: { + id: boolean; + type: boolean + guildId: boolean + channelId: boolean + user: boolean + name: boolean + avatar: boolean + token: boolean + applicationId: boolean + sourceGuild: boolean + sourceChannel: boolean + url: boolean + } + } reverse: { allowedMentions: (bot: Bot, payload: AllowedMentions) => DiscordAllowedMentions embed: (bot: Bot, payload: Embed) => DiscordEmbed @@ -159,6 +380,229 @@ export interface Transformers { export function createTransformers(options: Partial): Transformers { return { + customizers: { + message(bot, payload, message) { + return message + }, + }, + desiredProperties: { + attachment: { + id: false, + filename: false, + contentType: false, + size: false, + url: false, + proxyUrl: false, + height: false, + width: false, + ephemeral: false, + description: false, + }, + channel: { + type: false, + position: false, + name: false, + topic: false, + nsfw: false, + bitrate: false, + userLimit: false, + rateLimitPerUser: false, + rtcRegion: false, + videoQualityMode: false, + guildId: false, + lastPinTimestamp: false, + permissionOverwrites: false, + id: false, + permissions: false, + lastMessageId: false, + ownerId: false, + applicationId: false, + managed: false, + parentId: false, + memberCount: false, + messageCount: false, + archiveTimestamp: false, + autoArchiveDuration: false, + botIsMember: false, + archived: false, + locked: false, + invitable: false, + createTimestamp: false, + newlyCreated: false, + flags: false, + }, + emoji: { + id: false, + name: false, + roles: false, + user: false, + }, + guild: { + afkTimeout: false, + approximateMemberCount: false, + approximatePresenceCount: false, + defaultMessageNotifications: false, + description: false, + explicitContentFilter: false, + maxMembers: false, + maxPresences: false, + maxVideoChannelUsers: false, + mfaLevel: false, + name: false, + nsfwLevel: false, + preferredLocale: false, + premiumSubscriptionCount: false, + premiumTier: false, + stageInstances: false, + systemChannelFlags: false, + vanityUrlCode: false, + verificationLevel: false, + welcomeScreen: false, + discoverySplash: false, + joinedAt: false, + memberCount: false, + shardId: false, + icon: false, + banner: false, + splash: false, + id: false, + ownerId: false, + permissions: false, + afkChannelId: false, + widgetChannelId: false, + applicationId: false, + systemChannelId: false, + rulesChannelId: false, + publicUpdatesChannelId: false, + premiumProgressBarEnabled: false, + }, + invite: { + channelId: false, + code: false, + createdAt: false, + guildId: false, + inviter: false, + maxAge: false, + maxUses: false, + targetType: false, + targetUser: false, + targetApplication: false, + temporary: false, + uses: false, + }, + member: { + id: false, + guildId: false, + user: false, + nick: false, + roles: false, + joinedAt: false, + premiumSince: false, + avatar: false, + permissions: false, + communicationDisabledUntil: false, + }, + message: { + activity: false, + application: false, + applicationId: false, + attachments: false, + author: false, + channelId: false, + components: false, + content: false, + editedTimestamp: false, + embeds: false, + guildId: false, + id: false, + interaction: { + id: false, + member: false, + name: false, + type: false, + user: false, + }, + member: false, + mentionedChannelIds: false, + mentionedRoleIds: false, + mentions: false, + messageReference: { + messageId: false, + channelId: false, + guildId: false, + }, + nonce: false, + reactions: false, + stickerItems: false, + thread: false, + type: false, + webhookId: false, + }, + role: { + name: false, + guildId: false, + position: false, + color: false, + id: false, + botId: false, + integrationId: false, + permissions: false, + icon: false, + unicodeEmoji: false, + }, + scheduledEvent: { + id: false, + guildId: false, + channelId: false, + creatorId: false, + scheduledStartTime: false, + scheduledEndTime: false, + entityId: false, + creator: false, + name: false, + description: false, + privacyLevel: false, + status: false, + entityType: false, + userCount: false, + location: false, + image: false, + }, + stageInstance: { + id: false, + guildId: false, + channelId: false, + topic: false, + guildScheduledEventId: false, + }, + sticker: { + id: false, + packId: false, + name: false, + description: false, + tags: false, + type: false, + formatType: false, + available: false, + guildId: false, + user: false, + sortValue: false, + }, + webhook: { + id: false, + type: false, + guildId: false, + channelId: false, + user: false, + name: false, + avatar: false, + token: false, + applicationId: false, + sourceGuild: false, + sourceChannel: false, + url: false, + } + }, reverse: { allowedMentions: options.reverse?.allowedMentions ?? transformAllowedMentionsToDiscordAllowedMentions, embed: options.reverse?.embed ?? transformEmbedToDiscordEmbed, diff --git a/packages/bot/src/transformers/message.ts b/packages/bot/src/transformers/message.ts index 99aad5573..f73f95314 100644 --- a/packages/bot/src/transformers/message.ts +++ b/packages/bot/src/transformers/message.ts @@ -1,87 +1,307 @@ -import type { DiscordMessage } from '@discordeno/types' +import type { DiscordMessage, InteractionTypes, MessageActivityTypes, MessageTypes, StickerFormatTypes } from '@discordeno/types' import { CHANNEL_MENTION_REGEX } from '../constants.js' -import { iconHashToBigInt, type Bot } from '../index.js' -import type { Optionalize } from '../optionalize.js' -import { MemberToggles } from './toggles/member.js' +import { snowflakeToTimestamp, type Bot } from '../index.js' +import { MessageFlags } from '../typings.js' +import type { Attachment } from './attachment.js' +import type { Channel } from './channel.js' +import type { Component } from './component.js' +import type { Embed } from './embed.js' +import type { Emoji } from './emoji.js' +import type { Member, User } from './member.js' +import { ToggleBitfield } from './toggles/ToggleBitfield.js' -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function transformMessage(bot: Bot, payload: DiscordMessage) { +const baseMessage: Partial & MessageBase = { + get crossposted() { + return this.flags?.contains(MessageFlags.Crossposted) ?? false + }, + set crossposted(value: boolean) { + if (!this.flags) return + if (!this.flags) return + if (value) this.flags.add(MessageFlags.Crossposted) + else this.flags.remove(MessageFlags.Crossposted) + }, + get ephemeral() { + return this.flags?.contains(MessageFlags.Ephemeral) ?? false + }, + set ephemeral(value: boolean) { + if (!this.flags) return + if (value) this.flags.add(MessageFlags.Ephemeral) + else this.flags.remove(MessageFlags.Ephemeral) + }, + get failedToMentionSomeRolesInThread() { + return this.flags?.contains(MessageFlags.FailedToMentionSomeRolesInThread) ?? false + }, + set failedToMentionSomeRolesInThread(value: boolean) { + if (!this.flags) return + if (value) this.flags.add(MessageFlags.FailedToMentionSomeRolesInThread) + else this.flags.remove(MessageFlags.FailedToMentionSomeRolesInThread) + }, + get hasThread() { + return this.flags?.contains(MessageFlags.HasThread) ?? false + }, + set hasThread(value: boolean) { + if (!this.flags) return + if (value) this.flags.add(MessageFlags.HasThread) + else this.flags.remove(MessageFlags.HasThread) + }, + get isCrosspost() { + return this.flags?.contains(MessageFlags.IsCrosspost) ?? false + }, + set isCrosspost(value: boolean) { + if (!this.flags) return + if (value) this.flags.add(MessageFlags.IsCrosspost) + else this.flags.remove(MessageFlags.IsCrosspost) + }, + get loading() { + return this.flags?.contains(MessageFlags.Loading) ?? false + }, + set loading(value: boolean) { + if (!this.flags) return + if (value) this.flags.add(MessageFlags.Loading) + else this.flags.remove(MessageFlags.Loading) + }, + get mentionedUserIds() { + return this.mentions?.map((user) => user.id) ?? [] + }, + get mentionEveryone() { + return this.bitfield?.contains(2) ?? false + }, + set mentionEveryone(value: boolean) { + if (!this.bitfield) return + if (value) this.bitfield.add(2) + else this.bitfield.remove(2) + }, + get pinned() { + return this.bitfield?.contains(3) ?? false + }, + set pinned(value: boolean) { + if (!this.bitfield) return + if (value) this.bitfield.add(3) + else this.bitfield.remove(3) + }, + get sourceMessageDeleted() { + return this.flags?.contains(MessageFlags.SourceMessageDeleted) ?? false + }, + set sourceMessageDeleted(value: boolean) { + if (!this.flags) return + if (value) this.flags.add(MessageFlags.SourceMessageDeleted) + else this.flags.remove(MessageFlags.SourceMessageDeleted) + }, + get suppressEmbeds() { + return this.flags?.contains(MessageFlags.SuppressEmbeds) ?? false + }, + set suppressEmbeds(value: boolean) { + if (!this.flags) return + if (value) this.flags.add(MessageFlags.SuppressEmbeds) + else this.flags.remove(MessageFlags.SuppressEmbeds) + }, + get suppressNotifications() { + return this.flags?.contains(MessageFlags.SuppressNotifications) ?? false + }, + set suppressNotifications(value: boolean) { + if (!this.flags) return + if (value) this.flags.add(MessageFlags.SuppressNotifications) + else this.flags.remove(MessageFlags.SuppressNotifications) + }, + get timestamp() { + return this.id ? snowflakeToTimestamp(this.id) : 0 + }, + get tts() { + return this.bitfield?.contains(1) ?? false + }, + set tts(value: boolean) { + if (!this.bitfield) return + if (value) this.bitfield.add(1) + else this.bitfield.remove(1) + }, + get urgent() { + return this.flags?.contains(MessageFlags.Urgent) ?? false + }, + set urgent(value: boolean) { + if (!this.flags) return + if (value) this.flags.add(MessageFlags.Urgent) + else this.flags.remove(MessageFlags.Urgent) + }, +} + +export interface MessageBase { + /** Holds all the boolean values on this message. */ + bitfield?: ToggleBitfield + /** Whether this message has been published to subscribed channels (via Channel Following) */ + crossposted: boolean + /** Whether this message is only visible to the user who invoked the Interaction */ + ephemeral: boolean + /** Whether this message failed to mention some roles and add their members to the thread */ + failedToMentionSomeRolesInThread: boolean + /** Message flags combined as a bitfield */ + flags?: ToggleBitfield + /** Whether this message has an associated thread, with the same id as the message */ + hasThread: boolean + /** Whether this message originated from a message in another channel (via Channel Following) */ + isCrosspost: boolean + /** Whether this message is an Interaction Response and the bot is "thinking" */ + loading: boolean + /** The ids of the users who were mentioned in this message. */ + mentionedUserIds: bigint[] + /** Whether this message mentions everyone */ + mentionEveryone: boolean + /** Whether this message is pinned */ + pinned: boolean + /** Whether the source message for this crosspost has been deleted (via Channel Following) */ + sourceMessageDeleted: boolean + /** Whether do not include any embeds when serializing this message */ + suppressEmbeds: boolean + /** Whether this message will not trigger push and desktop notifications */ + suppressNotifications: boolean + /** The timestamp in milliseconds when this message was created */ + timestamp: number + /** Whether this was a TTS message. */ + tts: boolean + /** Whether this message came from the urgent message system */ + urgent: boolean +} + +export interface Message extends MessageBase { + /** Sent with Rich Presence-related chat embeds */ + activity?: { + /** Type of message activity */ + type: MessageActivityTypes + /** party_id from a Rich Presence event */ + partyId?: string + } + /** if the message is an Interaction or application-owned webhook, this is the id of the application */ + applicationId?: bigint + /** Any attached files on this message. */ + attachments?: Attachment[] + /** The author of this message (not guaranteed to be a valid user) Note: The author object follows the structure of the user object, but is only a valid user in the case where the message is generated by a user or bot user. If the message is generated by a webhook, the author object corresponds to the webhook's id, username, and avatar. You can tell if a message is generated by a webhook by checking for the webhook_id on the message object. */ + author: User + /** id of the channel the message was sent in */ + channelId: bigint + /** The components related to this message */ + components: Component[] + /** Contents of the message */ + content: string + /** The timestamp in milliseconds when this message was edited last. */ + editedTimestamp?: number + /** Any embedded content */ + embeds?: Embed[] + /** id of the guild the message was sent in Note: For MESSAGE_CREATE and MESSAGE_UPDATE events, the message object may not contain a guild_id or member field since the events are sent directly to the receiving user and the bot who sent the message, rather than being sent through the guild like non-ephemeral messages. */ + guildId?: bigint + /** id of the message */ + id: bigint + /** Sent if the message is a response to an Interaction */ + interaction?: { + /** Id of the interaction */ + id: bigint + /** The member who invoked the interaction in the guild */ + member?: Member + /** The name of the ApplicationCommand including the name of the subcommand/subcommand group */ + name: string + /** The type of interaction */ + type: InteractionTypes + /** The user who invoked the interaction */ + user: User + } + /** Member properties for this message's author Note: The member object exists in MESSAGE_CREATE and MESSAGE_UPDATE events from text-based guild channels. This allows bots to obtain real-time member data without requiring bots to store member state in memory. */ + member?: Member + /** Users specifically mentioned in the message Note: The user objects in the mentions array will only have the partial member field present in MESSAGE_CREATE and MESSAGE_UPDATE events from text-based guild channels. */ + mentions?: User[] + /** Channels specifically mentioned in this message Note: Not all channel mentions in a message will appear in mention_channels. Only textual channels that are visible to everyone in a lurkable guild will ever be included. Only crossposted messages (via Channel Following) currently include mention_channels at all. If no mentions in the message meet these requirements, this field will not be sent. */ + mentionedChannelIds?: bigint[] + /** Roles specifically mentioned in this message */ + mentionedRoleIds?: bigint[] + /** Data showing the source of a crossposted channel follow add, pin or reply message */ + messageReference?: { + /** id of the originating message's channel Note: channel_id is optional when creating a reply, but will always be present when receiving an event/response that includes this data model. */ + channelId?: bigint + /** id of the originating message's guild */ + guildId?: bigint + /** id of the originating message */ + messageId?: bigint + } + /** Used for validating a message was sent */ + nonce?: string | number + /** Reactions on this message. */ + reactions?: Array<{ + /** Whether the current user reacted using this emoji */ + me: boolean + /** Times this emoji has been used to react */ + count: number + /** Emoji information */ + emoji: Emoji + }> + /** Sent if the message contains stickers */ + stickerItems?: Array<{ + /** The id of this sticker. */ + id: bigint + /** The name of this sticker. */ + name: string + /** The type of this stickers format. */ + formatType: StickerFormatTypes + }> + /** Type of message */ + type: MessageTypes + /** The thread that was started from this message, includes thread member object */ + thread?: Channel + /** If the message is generated by a webhook, this is the webhook's id */ + webhookId?: bigint +} + +const EMPTY_STRING = '' + +export function transformMessage(bot: Bot, payload: DiscordMessage): Message { const guildId = payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined const userId = bot.transformers.snowflake(payload.author.id) - const message = { - // UNTRANSFORMED STUFF HERE - content: payload.content ?? '', - isFromBot: payload.author.bot ?? false, - tag: `${payload.author.username}#${payload.author.discriminator}`, - timestamp: Date.parse(payload.timestamp), - editedTimestamp: payload.edited_timestamp ? Date.parse(payload.edited_timestamp) : undefined, - bitfield: (payload.tts ? 1n : 0n) | (payload.mention_everyone ? 2n : 0n) | (payload.pinned ? 4n : 0n), - attachments: payload.attachments?.map((attachment) => bot.transformers.attachment(bot, attachment)), - embeds: payload.embeds?.map((embed) => bot.transformers.embed(bot, embed)), - reactions: payload.reactions?.map((reaction) => ({ - me: reaction.me, - count: reaction.count, - emoji: bot.transformers.emoji(bot, reaction.emoji), - })), - type: payload.type, - activity: payload.activity - ? { - type: payload.activity.type, - partyId: payload.activity.party_id, - } - : undefined, - application: payload.application, - flags: payload.flags, - interaction: payload.interaction - ? { - id: bot.transformers.snowflake(payload.interaction.id), - type: payload.interaction.type, - name: payload.interaction.name, - user: bot.transformers.user(bot, payload.interaction.user), - member: payload.interaction.member - ? { - id: userId, - guildId, - nick: payload.interaction.member.nick ?? undefined, - roles: payload.interaction.member.roles?.map((id) => bot.transformers.snowflake(id)), - joinedAt: payload.interaction.member.joined_at ? Date.parse(payload.interaction.member.joined_at) : undefined, - premiumSince: payload.interaction.member.premium_since ? Date.parse(payload.interaction.member.premium_since) : undefined, - toggles: new MemberToggles(payload.interaction.member), - avatar: payload.interaction.member.avatar ? iconHashToBigInt(payload.interaction.member.avatar) : undefined, - permissions: payload.interaction.member.permissions ? bot.transformers.snowflake(payload.interaction.member.permissions) : undefined, - communicationDisabledUntil: payload.interaction.member.communication_disabled_until - ? Date.parse(payload.interaction.member.communication_disabled_until) - : undefined, - } - : undefined, - } - : undefined, - thread: payload.thread ? bot.transformers.channel(bot, { channel: payload.thread, guildId }) : undefined, - components: payload.components?.map((component) => bot.transformers.component(bot, component)), - stickerItems: payload.sticker_items?.map((sticker) => ({ - id: bot.transformers.snowflake(sticker.id), - name: sticker.name, - formatType: sticker.format_type, - })), + const message: Message = Object.create(baseMessage) + message.bitfield = new ToggleBitfield() + message.flags = new ToggleBitfield(payload.flags) - // TRANSFORMED STUFF BELOW - id: bot.transformers.snowflake(payload.id), - guildId, - channelId: bot.transformers.snowflake(payload.channel_id), - webhookId: payload.webhook_id ? bot.transformers.snowflake(payload.webhook_id) : undefined, - authorId: userId, - applicationId: payload.application_id ? bot.transformers.snowflake(payload.application_id) : undefined, - messageReference: payload.message_reference - ? { - messageId: payload.message_reference.message_id ? bot.transformers.snowflake(payload.message_reference.message_id) : undefined, - channelId: payload.message_reference.channel_id ? bot.transformers.snowflake(payload.message_reference.channel_id) : undefined, - guildId: payload.message_reference.guild_id ? bot.transformers.snowflake(payload.message_reference.guild_id) : undefined, - } - : undefined, - mentionedUserIds: payload.mentions ? payload.mentions.map((m) => bot.transformers.snowflake(m.id)) : [], - mentionedRoleIds: payload.mention_roles ? payload.mention_roles.map((id) => bot.transformers.snowflake(id)) : [], - mentionedChannelIds: [ + const props = bot.transformers.desiredProperties.message + + if (props.author && props.author) message.author = bot.transformers.user(bot, payload.author) + if (payload.application_id && props.applicationId) message.applicationId = bot.transformers.snowflake(payload.application_id) + if (payload.attachments?.length && props.attachments) { + message.attachments = payload.attachments.map((attachment) => bot.transformers.attachment(bot, attachment)) + } + if (props.channelId && props.channelId) message.channelId = bot.transformers.snowflake(payload.channel_id) + if (props.components && payload.components?.length) message.components = payload.components.map((comp) => bot.transformers.component(bot, comp)) + if (props.content && props.content) message.content = payload.content ?? EMPTY_STRING + if (payload.edited_timestamp && props.editedTimestamp) message.editedTimestamp = Date.parse(payload.edited_timestamp) + if (payload.embeds?.length && props.embeds) message.embeds = payload.embeds.map((embed) => bot.transformers.embed(bot, embed)) + if (guildId && props.guildId) message.guildId = guildId + if (props.id && payload.id) message.id = bot.transformers.snowflake(payload.id) + if (payload.interaction) { + const interaction = {} as NonNullable + let edited = false + if (props.interaction.id) { + interaction.id = bot.transformers.snowflake(payload.interaction.id) + edited = true + } + if (props.interaction.member && payload.interaction.member) { + // @ts-expect-error TODO: partial - check why this is partial and handle as needed + interaction.member = bot.transformers.member(bot, payload.interaction.member, guildId, payload.interaction.user.id) + edited = true + } + if (props.interaction.name) { + interaction.name = payload.interaction.name + edited = true + } + if (props.interaction.type) { + interaction.type = payload.interaction.type + edited = true + } + if (props.interaction.user) { + interaction.user = bot.transformers.user(bot, payload.interaction.user) + edited = true + } + + if (edited) message.interaction = interaction + } + if (props.member && payload.member && guildId) message.member = bot.transformers.member(bot, payload.member, guildId, userId) + if (payload.mention_everyone) message.mentionEveryone = true + if (props.mentionedChannelIds && payload.mention_channels?.length) { + message.mentionedChannelIds = [ // Keep any ids tht discord sends ...(payload.mention_channels ?? []).map((m) => bot.transformers.snowflake(m.id)), // Add any other ids that can be validated in a channel mention format @@ -89,12 +309,52 @@ export function transformMessage(bot: Bot, payload: DiscordMessage) { // converts the <#123> into 123 bot.transformers.snowflake(text.substring(2, text.length - 1)), ), - ], - member: payload.member && guildId ? bot.transformers.member(bot, payload.member, guildId, userId) : undefined, - nonce: payload.nonce, + ] } + if (props.mentionedRoleIds && payload.mention_roles?.length) { + message.mentionedRoleIds = payload.mention_roles.map((id) => bot.transformers.snowflake(id)) + } + if (props.mentions && payload.mentions?.length) { + message.mentions = payload.mentions.map((user) => bot.transformers.user(bot, user)) + } + if (payload.message_reference) { + const reference = {} as NonNullable + let edited = false - return message as Optionalize + if (props.messageReference.channelId && payload.message_reference.channel_id) { + reference.channelId = bot.transformers.snowflake(payload.message_reference.channel_id) + edited = true + } + if (props.messageReference.guildId && payload.message_reference.guild_id) { + reference.guildId = bot.transformers.snowflake(payload.message_reference.guild_id) + edited = true + } + if (props.messageReference.messageId && payload.message_reference.message_id) { + reference.messageId = bot.transformers.snowflake(payload.message_reference.message_id) + edited = true + } + + if (edited) message.messageReference = reference + } + if (payload.nonce && props.nonce) message.nonce = payload.nonce + if (payload.pinned) message.pinned = true + if (payload.reactions?.length && props.reactions) { + message.reactions = payload.reactions.map((reaction) => ({ + me: reaction.me, + count: reaction.count, + emoji: bot.transformers.emoji(bot, reaction.emoji), + })) + } + if (props.stickerItems && payload.sticker_items?.length) + message.stickerItems = payload.sticker_items.map((item) => ({ + id: bot.transformers.snowflake(item.id), + name: item.name, + formatType: item.format_type, + })) + if (payload.tts) message.tts = true + if (props.thread && payload.thread) message.thread = bot.transformers.channel(bot, { channel: payload.thread, guildId }) + if (props.type && props.type) message.type = payload.type + if (payload.webhook_id && props.webhookId) message.webhookId = bot.transformers.snowflake(payload.webhook_id) + + return bot.transformers.customizers.message(bot, payload, message) } - -export interface Message extends ReturnType {} diff --git a/packages/bot/src/transformers/toggles/ToggleBitfield.ts b/packages/bot/src/transformers/toggles/ToggleBitfield.ts index 4ccd6d462..417c43510 100644 --- a/packages/bot/src/transformers/toggles/ToggleBitfield.ts +++ b/packages/bot/src/transformers/toggles/ToggleBitfield.ts @@ -21,6 +21,10 @@ export class ToggleBitfield { this.bitfield &= ~bits return this } + + toJSON(): number { + return this.bitfield; + } } export class ToggleBitfieldBigint { diff --git a/packages/bot/src/typings.ts b/packages/bot/src/typings.ts index 6fb04298f..4196c88bf 100644 --- a/packages/bot/src/typings.ts +++ b/packages/bot/src/typings.ts @@ -202,3 +202,26 @@ export interface BotGatewayHandlerOptions { INTEGRATION_UPDATE: typeof handlers.handleIntegrationUpdate INTEGRATION_DELETE: typeof handlers.handleIntegrationDelete } + +export enum MessageFlags { + /** Whether this message has been published to subscribed channels (via Channel Following) */ + Crossposted = 1 << 0, + /** Whether this message originated from a message in another channel (via Channel Following) */ + IsCrosspost = 1 << 1, + /** Whether do not include any embeds when serializing this message */ + SuppressEmbeds = 1 << 2, + /** Whether the source message for this crosspost has been deleted (via Channel Following) */ + SourceMessageDeleted = 1 << 3, + /** Whether this message came from the urgent message system */ + Urgent = 1 << 4, + /** Whether this message has an associated thread, with the same id as the message */ + HasThread = 1 << 5, + /** Whether this message is only visible to the user who invoked the Interaction */ + Ephemeral = 1 << 6, + /** Whether this message is an Interaction Response and the bot is "thinking" */ + Loading = 1 << 7, + /** Whether this message failed to mention some roles and add their members to the thread */ + FailedToMentionSomeRolesInThread = 1 << 8, + /** Whether this message will not trigger push and desktop notifications */ + SuppressNotifications = 1 << 12, +} diff --git a/packages/bot/src/utils.ts b/packages/bot/src/utils.ts index a368c6ecd..c7c2ec052 100644 --- a/packages/bot/src/utils.ts +++ b/packages/bot/src/utils.ts @@ -7,3 +7,7 @@ export function snowflakeToBigint(snowflake: BigString): bigint { export function bigintToSnowflake(snowflake: BigString): string { return snowflake === 0n ? '' : snowflake.toString() } + +export function snowflakeToTimestamp(id: bigint): number { + return Number(id / 4194304n + 1420070400000n) +} \ No newline at end of file