From 4581222cf4dbfd45826672f5667e5777ca06bc0d Mon Sep 17 00:00:00 2001 From: Fleny Date: Fri, 31 Jul 2026 11:42:15 +0200 Subject: [PATCH] feat(transformers)!: Partial transformers (#4436) * refactor(bot)!: setup desired properties for all transformers SetupDesiredProps when is given an object that does not corrispond to a transformer object that supports desired properties will behave like TransformProperty on the entire object as when it tries to get the properties for said object it will find `never` as the props and for `IsKeyDesired` a props of `never` means that all props are desired. * feat(transformers)!: Handle partials in transformers * Add expiresAt to invite create event * Use Equals helper, clean up a bit the code * Explicit the IsKeyDesired TProps never behavior * Fix type errors * format * Add all trasformer objects to bot.transformers.$inferredTypes * Use transfromerInformations for bot.transformers.$inferredTypes * code review * code review * fix component transformer * fix * Update packages/bot/src/transformers/types.ts * Fixes from Tri (+ GPT sol) code review --------- Co-authored-by: Link --- packages/bot/src/bot.ts | 6 +- packages/bot/src/events.ts | 29 +- .../bot/src/handlers/invites/INVITE_CREATE.ts | 20 +- .../bot/src/handlers/invites/INVITE_DELETE.ts | 3 - .../handlers/members/GUILD_MEMBER_UPDATE.ts | 6 +- .../handlers/messages/MESSAGE_REACTION_ADD.ts | 3 +- .../messages/MESSAGE_REACTION_REMOVE.ts | 3 +- .../messages/MESSAGE_REACTION_REMOVE_EMOJI.ts | 3 +- packages/bot/src/helpers.ts | 10 +- packages/bot/src/transformers.ts | 307 ++++++++++++------ packages/bot/src/transformers/activity.ts | 15 +- packages/bot/src/transformers/application.ts | 22 +- .../src/transformers/applicationCommand.ts | 2 +- .../transformers/applicationCommandOption.ts | 2 +- .../applicationCommandOptionChoice.ts | 2 +- .../applicationCommandPermission.ts | 5 +- packages/bot/src/transformers/attachment.ts | 9 +- .../bot/src/transformers/auditLogEntry.ts | 2 +- .../transformers/automodActionExecution.ts | 2 +- packages/bot/src/transformers/automodRule.ts | 2 +- .../src/transformers/avatarDecorationData.ts | 7 +- packages/bot/src/transformers/channel.ts | 14 +- packages/bot/src/transformers/component.ts | 187 ++++++----- packages/bot/src/transformers/embed.ts | 2 +- packages/bot/src/transformers/emoji.ts | 13 +- packages/bot/src/transformers/entitlement.ts | 7 +- packages/bot/src/transformers/gatewayBot.ts | 2 +- packages/bot/src/transformers/guild.ts | 52 +-- .../bot/src/transformers/incidentsData.ts | 7 +- packages/bot/src/transformers/integration.ts | 2 +- packages/bot/src/transformers/interaction.ts | 83 ++--- packages/bot/src/transformers/invite.ts | 52 ++- packages/bot/src/transformers/lobby.ts | 27 +- packages/bot/src/transformers/member.ts | 6 +- packages/bot/src/transformers/message.ts | 67 ++-- packages/bot/src/transformers/onboarding.ts | 25 +- packages/bot/src/transformers/poll.ts | 19 +- packages/bot/src/transformers/presence.ts | 13 +- packages/bot/src/transformers/role.ts | 16 +- .../bot/src/transformers/scheduledEvent.ts | 17 +- packages/bot/src/transformers/sku.ts | 7 +- .../bot/src/transformers/soundboardSound.ts | 7 +- .../bot/src/transformers/stageInstance.ts | 7 +- .../src/transformers/stageInviteInstance.ts | 16 +- packages/bot/src/transformers/sticker.ts | 9 +- packages/bot/src/transformers/subscription.ts | 7 +- packages/bot/src/transformers/team.ts | 2 +- packages/bot/src/transformers/template.ts | 2 +- packages/bot/src/transformers/threadMember.ts | 4 +- .../bot/src/transformers/toggles/channel.ts | 2 +- .../bot/src/transformers/toggles/emoji.ts | 2 +- .../bot/src/transformers/toggles/guild.ts | 2 +- packages/bot/src/transformers/toggles/role.ts | 2 +- packages/bot/src/transformers/toggles/user.ts | 2 +- .../bot/src/transformers/toggles/voice.ts | 2 +- packages/bot/src/transformers/types.ts | 13 +- packages/bot/src/transformers/user.ts | 25 +- packages/bot/src/transformers/voiceRegion.ts | 2 +- packages/bot/src/transformers/voiceState.ts | 8 +- packages/bot/src/transformers/webhook.ts | 10 +- .../bot/src/transformers/welcomeScreen.ts | 2 +- packages/bot/src/transformers/widget.ts | 8 +- .../bot/src/transformers/widgetSettings.ts | 4 +- packages/types/src/discord/gateway.ts | 4 +- packages/utils/src/typeguards.ts | 14 +- 65 files changed, 747 insertions(+), 487 deletions(-) diff --git a/packages/bot/src/bot.ts b/packages/bot/src/bot.ts index 756f36724..398b9af78 100644 --- a/packages/bot/src/bot.ts +++ b/packages/bot/src/bot.ts @@ -4,11 +4,11 @@ import type { CreateRestManagerOptions, RestManager } from '@discordeno/rest'; import { createRestManager } from '@discordeno/rest'; import type { BigString, GatewayDispatchEventNames, GatewayIntents, RecursivePartial } from '@discordeno/types'; import { createLogger, getBotIdFromToken, type logger } from '@discordeno/utils'; -import type { CompleteDesiredProperties, DesiredPropertiesBehavior, TransformersDesiredProperties } from './desiredProperties.js'; +import type { CompleteDesiredProperties, DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from './desiredProperties.js'; import type { EventHandlers } from './events.js'; import { type BotGatewayHandler, createBotGatewayHandlers, type GatewayHandlers } from './handlers.js'; import { type BotHelpers, createBotHelpers } from './helpers.js'; -import { createTransformers, type TransformerFunctions, type Transformers } from './transformers.js'; +import { createTransformers, type TransformerInformations, type Transformers } from './transformers.js'; /** * Create a bot object that will maintain the rest and gateway connection. @@ -160,7 +160,7 @@ export interface Bot< /** The functions that should transform discord objects to discordeno shaped objects. */ transformers: Transformers & { $inferredTypes: { - [K in keyof TransformerFunctions]: ReturnType[K]>; + [K in keyof TransformerInformations]: SetupDesiredProps; }; }; /** The handler functions that should handle incoming discord payloads from gateway and call an event. */ diff --git a/packages/bot/src/events.ts b/packages/bot/src/events.ts index f22a42493..8e8c0a3d9 100644 --- a/packages/bot/src/events.ts +++ b/packages/bot/src/events.ts @@ -1,7 +1,8 @@ -import type { DiscordGatewayPayload, DiscordRateLimited, DiscordReady, DiscordVoiceChannelEffectAnimationType } from '@discordeno/types'; +import type { DiscordGatewayPayload, DiscordRateLimited, DiscordReady, DiscordVoiceChannelEffectAnimationType, TargetTypes } from '@discordeno/types'; import type { Collection } from '@discordeno/utils'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from './desiredProperties.js'; import type { + Application, AuditLogEntry, AutoModerationActionExecution, AutoModerationRule, @@ -12,7 +13,6 @@ import type { GuildApplicationCommandPermissions, Integration, Interaction, - Invite, Member, Message, PresenceUpdate, @@ -72,11 +72,26 @@ export type EventHandlers) => unknown; integrationDelete: (payload: { id: bigint; guildId: bigint; applicationId?: bigint }) => unknown; integrationUpdate: (payload: { guildId: bigint }) => unknown; - inviteCreate: (invite: SetupDesiredProps) => unknown; + inviteCreate: (invite: { + channelId: bigint; + code: string; + createdAt: number; + guildId?: bigint; + inviter?: SetupDesiredProps; + maxAge: number; + maxUses: number; + targetType?: TargetTypes; + targetUser?: SetupDesiredProps; + targetApplication?: Partial>; + temporary: boolean; + uses: number; + expiresAt?: number; + roleIds?: bigint[]; + }) => unknown; inviteDelete: (payload: { channelId: bigint; guildId?: bigint; code: string }) => unknown; guildMemberAdd: (member: SetupDesiredProps, user: SetupDesiredProps) => unknown; guildMemberRemove: (user: SetupDesiredProps, guildId: bigint) => unknown; - guildMemberUpdate: (member: SetupDesiredProps, user: SetupDesiredProps) => unknown; + guildMemberUpdate: (member: Partial>, user: SetupDesiredProps) => unknown; guildStickersUpdate: (payload: { guildId: bigint; stickers: SetupDesiredProps[] }) => unknown; messageCreate: (message: SetupDesiredProps) => unknown; messageDelete: (payload: { id: bigint; channelId: bigint; guildId?: bigint }, message?: SetupDesiredProps) => unknown; @@ -89,7 +104,7 @@ export type EventHandlers; user?: SetupDesiredProps; - emoji: SetupDesiredProps; + emoji: Partial>; messageAuthorId?: bigint; burst: boolean; burstColors?: string[]; @@ -99,14 +114,14 @@ export type EventHandlers; + emoji: Partial>; burst: boolean; }) => unknown; reactionRemoveEmoji: (payload: { channelId: bigint; messageId: bigint; guildId?: bigint; - emoji: SetupDesiredProps; + emoji: Partial>; }) => unknown; reactionRemoveAll: (payload: { channelId: bigint; messageId: bigint; guildId?: bigint }) => unknown; presenceUpdate: (presence: SetupDesiredProps) => unknown; diff --git a/packages/bot/src/handlers/invites/INVITE_CREATE.ts b/packages/bot/src/handlers/invites/INVITE_CREATE.ts index 90d0e70df..e26982d11 100644 --- a/packages/bot/src/handlers/invites/INVITE_CREATE.ts +++ b/packages/bot/src/handlers/invites/INVITE_CREATE.ts @@ -6,6 +6,22 @@ export async function handleInviteCreate(bot: Bot, data: DiscordGatewayPayload, const payload = data.d as DiscordInviteCreate; - // TODO: Add role_ids, the transformer should be kept for the Invite type, not for the gateway event - bot.events.inviteCreate(bot.transformers.invite(bot, payload, { shardId })); + bot.events.inviteCreate({ + channelId: bot.transformers.snowflake(payload.channel_id), + code: payload.code, + createdAt: Date.parse(payload.created_at), + guildId: payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined, + inviter: payload.inviter ? bot.transformers.user(bot, payload.inviter) : undefined, + maxAge: payload.max_age, + maxUses: payload.max_uses, + targetType: payload.target_type, + targetUser: payload.target_user ? bot.transformers.user(bot, payload.target_user) : undefined, + targetApplication: payload.target_application + ? bot.transformers.application(bot, payload.target_application, { shardId, partial: true }) + : undefined, + temporary: payload.temporary, + uses: payload.uses, + expiresAt: payload.expires_at ? Date.parse(payload.expires_at) : undefined, + roleIds: payload.role_ids ? payload.role_ids.map((id) => bot.transformers.snowflake(id)) : undefined, + }); } diff --git a/packages/bot/src/handlers/invites/INVITE_DELETE.ts b/packages/bot/src/handlers/invites/INVITE_DELETE.ts index 37918ed1b..5c9c7426f 100644 --- a/packages/bot/src/handlers/invites/INVITE_DELETE.ts +++ b/packages/bot/src/handlers/invites/INVITE_DELETE.ts @@ -7,11 +7,8 @@ export async function handleInviteDelete(bot: Bot, data: DiscordGatewayPayload): const payload = data.d as DiscordInviteDelete; bot.events.inviteDelete({ - /** The channel of the invite */ channelId: bot.transformers.snowflake(payload.channel_id), - /** The guild of the invite */ guildId: payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined, - /** The unique invite code */ code: payload.code, }); } diff --git a/packages/bot/src/handlers/members/GUILD_MEMBER_UPDATE.ts b/packages/bot/src/handlers/members/GUILD_MEMBER_UPDATE.ts index 52b37264a..49b6880b9 100644 --- a/packages/bot/src/handlers/members/GUILD_MEMBER_UPDATE.ts +++ b/packages/bot/src/handlers/members/GUILD_MEMBER_UPDATE.ts @@ -7,9 +7,5 @@ export async function handleGuildMemberUpdate(bot: Bot, data: DiscordGatewayPayl const payload = data.d as DiscordGuildMemberUpdate; const user = bot.transformers.user(bot, payload.user); - bot.events.guildMemberUpdate( - // @ts-expect-error Flags in the update are nullable, while on the member they are be always present - bot.transformers.member(bot, payload, { guildId: payload.guild_id, userId: payload.user.id }), - user, - ); + bot.events.guildMemberUpdate(bot.transformers.member(bot, payload, { guildId: payload.guild_id, userId: payload.user.id, partial: true }), user); } diff --git a/packages/bot/src/handlers/messages/MESSAGE_REACTION_ADD.ts b/packages/bot/src/handlers/messages/MESSAGE_REACTION_ADD.ts index 4f06f09f5..a107273bd 100644 --- a/packages/bot/src/handlers/messages/MESSAGE_REACTION_ADD.ts +++ b/packages/bot/src/handlers/messages/MESSAGE_REACTION_ADD.ts @@ -15,8 +15,7 @@ export async function handleMessageReactionAdd(bot: Bot, data: DiscordGatewayPay guildId, member: payload.member && guildId ? bot.transformers.member(bot, payload.member, { guildId, userId }) : undefined, user: payload.member ? bot.transformers.user(bot, payload.member.user) : undefined, - // @ts-expect-error TODO: Deal with partials - emoji: bot.transformers.emoji(bot, payload.emoji), + emoji: bot.transformers.emoji(bot, payload.emoji, { partial: true }), messageAuthorId: payload.message_author_id ? bot.transformers.snowflake(payload.message_author_id) : undefined, burst: payload.burst, burstColors: payload.burst_colors, diff --git a/packages/bot/src/handlers/messages/MESSAGE_REACTION_REMOVE.ts b/packages/bot/src/handlers/messages/MESSAGE_REACTION_REMOVE.ts index 727a2805a..29d8ef135 100644 --- a/packages/bot/src/handlers/messages/MESSAGE_REACTION_REMOVE.ts +++ b/packages/bot/src/handlers/messages/MESSAGE_REACTION_REMOVE.ts @@ -11,8 +11,7 @@ export async function handleMessageReactionRemove(bot: Bot, data: DiscordGateway channelId: bot.transformers.snowflake(payload.channel_id), messageId: bot.transformers.snowflake(payload.message_id), guildId: payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined, - // @ts-expect-error TODO: Deal with partials - emoji: bot.transformers.emoji(bot, payload.emoji), + emoji: bot.transformers.emoji(bot, payload.emoji, { partial: true }), burst: payload.burst, }); } diff --git a/packages/bot/src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts b/packages/bot/src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts index 07e87fbe6..0f6b582d1 100644 --- a/packages/bot/src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts +++ b/packages/bot/src/handlers/messages/MESSAGE_REACTION_REMOVE_EMOJI.ts @@ -10,7 +10,6 @@ export async function handleMessageReactionRemoveEmoji(bot: Bot, data: DiscordGa channelId: bot.transformers.snowflake(payload.channel_id), messageId: bot.transformers.snowflake(payload.message_id), guildId: payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined, - // @ts-expect-error TODO: Deal with partials - emoji: bot.transformers.emoji(bot, payload.emoji), + emoji: bot.transformers.emoji(bot, payload.emoji, { partial: true }), }); } diff --git a/packages/bot/src/helpers.ts b/packages/bot/src/helpers.ts index 7df2468c4..bfe80b06a 100644 --- a/packages/bot/src/helpers.ts +++ b/packages/bot/src/helpers.ts @@ -383,10 +383,7 @@ export function createBotHelpers { - return (await bot.rest.getGuilds(bearerToken, options)).map>((res) => - // @ts-expect-error getGuilds returns partial guilds - bot.transformers.guild(bot, snakelize(res)), - ); + return (await bot.rest.getGuilds(bearerToken, options)).map((res) => bot.transformers.guild(bot, snakelize(res), { partial: true })); }, getGuildApplicationCommand: async (commandId, guildId) => { return bot.transformers.applicationCommand(bot, snakelize(await bot.rest.getGuildApplicationCommand(commandId, guildId))); @@ -832,10 +829,7 @@ export function createBotHelpers { - // @ts-expect-error createTestEntitlement gives a partial, and this method returns a partial - return bot.transformers.entitlement(bot, snakelize(await bot.rest.createTestEntitlement(applicationId, body))) as Partial< - typeof bot.transformers.$inferredTypes.entitlement - >; + return bot.transformers.entitlement(bot, snakelize(await bot.rest.createTestEntitlement(applicationId, body)), { partial: true }); }, deleteTestEntitlement: async (applicationId, entitlementId) => { await bot.rest.deleteTestEntitlement(applicationId, entitlementId); diff --git a/packages/bot/src/transformers.ts b/packages/bot/src/transformers.ts index 5d48ffdbe..37f5ec1ee 100644 --- a/packages/bot/src/transformers.ts +++ b/packages/bot/src/transformers.ts @@ -38,7 +38,6 @@ import type { DiscordInteractionDataOption, DiscordInteractionDataResolved, DiscordInteractionResource, - DiscordInviteCreate, DiscordInviteMetadata, DiscordInviteStageInstance, DiscordLobby, @@ -245,95 +244,88 @@ import { transformWelcomeScreen } from './transformers/welcomeScreen.js'; import { transformWidget } from './transformers/widget.js'; import { transformWidgetSettings } from './transformers/widgetSettings.js'; -export type TransformerFunctions = { - activity: TransformerFunction; - activityAssets: TransformerFunction; - activityInstance: TransformerFunction; - activityLocation: TransformerFunction; - application: TransformerFunction; - applicationCommand: TransformerFunction; - applicationCommandOption: TransformerFunction; - applicationCommandOptionChoice: TransformerFunction; - applicationCommandPermission: TransformerFunction; - attachment: TransformerFunction; - auditLogEntry: TransformerFunction; - automodActionExecution: TransformerFunction; - automodRule: TransformerFunction; - avatarDecorationData: TransformerFunction; - channel: TransformerFunction; - collectibles: TransformerFunction; - component: TransformerFunction; - defaultReactionEmoji: TransformerFunction; - embed: TransformerFunction; - emoji: TransformerFunction; - entitlement: TransformerFunction; - forumTag: TransformerFunction; - gatewayBot: TransformerFunction; - guild: TransformerFunction; - guildOnboarding: TransformerFunction; - guildOnboardingPrompt: TransformerFunction; - guildOnboardingPromptOption: TransformerFunction; - incidentsData: TransformerFunction; - integration: TransformerFunction; - interaction: TransformerFunction; - interactionCallback: TransformerFunction; - interactionCallbackResponse: TransformerFunction< - TProps, - TBehavior, - DiscordInteractionCallbackResponse, - InteractionCallbackResponse, - { shardId?: number } - >; - interactionDataOptions: TransformerFunction; - interactionDataResolved: TransformerFunction< - TProps, - TBehavior, +export type TransformerInformations = { + activity: TransformerInformation; + activityAssets: TransformerInformation; + activityInstance: TransformerInformation; + activityLocation: TransformerInformation; + application: TransformerInformation; + applicationCommand: TransformerInformation; + applicationCommandOption: TransformerInformation; + applicationCommandOptionChoice: TransformerInformation; + applicationCommandPermission: TransformerInformation; + attachment: TransformerInformation; + auditLogEntry: TransformerInformation; + automodActionExecution: TransformerInformation; + automodRule: TransformerInformation; + avatarDecorationData: TransformerInformation; + channel: TransformerInformation; + collectibles: TransformerInformation; + component: TransformerInformation; + defaultReactionEmoji: TransformerInformation; + embed: TransformerInformation; + emoji: TransformerInformation; + entitlement: TransformerInformation; + forumTag: TransformerInformation; + gatewayBot: TransformerInformation; + guild: TransformerInformation; + guildOnboarding: TransformerInformation; + guildOnboardingPrompt: TransformerInformation; + guildOnboardingPromptOption: TransformerInformation; + incidentsData: TransformerInformation; + integration: TransformerInformation; + interaction: TransformerInformation; + interactionCallback: TransformerInformation; + interactionCallbackResponse: TransformerInformation; + interactionDataOptions: TransformerInformation; + interactionDataResolved: TransformerInformation< DiscordInteractionDataResolved, InteractionDataResolved, + true, { shardId?: number; guildId?: BigString } >; - interactionResource: TransformerFunction; - invite: TransformerFunction; - inviteStageInstance: TransformerFunction; - lobby: TransformerFunction; - lobbyMember: TransformerFunction; - lobbyMessage: TransformerFunction; - lobbyInvite: TransformerFunction; - mediaGalleryItem: TransformerFunction; - member: TransformerFunction; - message: TransformerFunction; - messageCall: TransformerFunction; - messageInteractionMetadata: TransformerFunction; - messagePin: TransformerFunction; - messageSnapshot: TransformerFunction; - nameplate: TransformerFunction; - poll: TransformerFunction; - pollMedia: TransformerFunction; - presence: TransformerFunction; - role: TransformerFunction; - roleColors: TransformerFunction; - scheduledEvent: TransformerFunction; - scheduledEventRecurrenceRule: TransformerFunction; - sharedClientTheme: TransformerFunction; - sku: TransformerFunction; - soundboardSound: TransformerFunction; - stageInstance: TransformerFunction; - sticker: TransformerFunction; - stickerPack: TransformerFunction; - subscription: TransformerFunction; - team: TransformerFunction; - template: TransformerFunction; - threadMember: TransformerFunction; - threadMemberGuildCreate: TransformerFunction; - unfurledMediaItem: TransformerFunction; - user: TransformerFunction; - userPrimaryGuild: TransformerFunction; - voiceRegion: TransformerFunction; - voiceState: TransformerFunction; - webhook: TransformerFunction; - welcomeScreen: TransformerFunction; - widget: TransformerFunction; - widgetSettings: TransformerFunction; + interactionResource: TransformerInformation; + invite: TransformerInformation; + inviteStageInstance: TransformerInformation; + lobby: TransformerInformation; + lobbyMember: TransformerInformation; + lobbyMessage: TransformerInformation; + lobbyInvite: TransformerInformation; + mediaGalleryItem: TransformerInformation; + member: TransformerInformation; + message: TransformerInformation; + messageCall: TransformerInformation; + messageInteractionMetadata: TransformerInformation; + messagePin: TransformerInformation; + messageSnapshot: TransformerInformation; + nameplate: TransformerInformation; + poll: TransformerInformation; + pollMedia: TransformerInformation; + presence: TransformerInformation; + role: TransformerInformation; + roleColors: TransformerInformation; + scheduledEvent: TransformerInformation; + scheduledEventRecurrenceRule: TransformerInformation; + sharedClientTheme: TransformerInformation; + sku: TransformerInformation; + soundboardSound: TransformerInformation; + stageInstance: TransformerInformation; + sticker: TransformerInformation; + stickerPack: TransformerInformation; + subscription: TransformerInformation; + team: TransformerInformation; + template: TransformerInformation; + threadMember: TransformerInformation; + threadMemberGuildCreate: TransformerInformation; + unfurledMediaItem: TransformerInformation; + user: TransformerInformation; + userPrimaryGuild: TransformerInformation; + voiceRegion: TransformerInformation; + voiceState: TransformerInformation; + webhook: TransformerInformation; + welcomeScreen: TransformerInformation; + widget: TransformerInformation; + widgetSettings: TransformerInformation; }; export type Transformers = TransformerFunctions< @@ -553,34 +545,159 @@ export function createTransformers as unknown as Transformers; } +/** + * Information about a transformer including its payload and transformed types. + * + * @template TPayload - The type of the payload received from Discord. + * @template TTransformed - The type of the transformed object returned by the transformer. + * @template TPartial - Indicates if the transformer supports partial payloads. + * @template TExtra - Additional extra information that might be needed for transformation. + */ +export type TransformerInformation = { + payload: TPayload; + transformed: TTransformed; + extra: TExtra; + partial: TPartial; +}; + +/** + * A function that transforms a payload from Discord into a desired format. + * + * @template TProps - The desired properties for the transformer. + * @template TBehavior - The behavior for handling desired properties. + * @template TPayload - The type of the payload received from Discord. + * @template TTransformed - The type of the transformed object returned by the transformer. + * @template TExtra - Additional extra information that might be needed for transformation. + * @template TPartial - Indicates if the transformer supports partial payloads. + */ export type TransformerFunction< TProps extends TransformersDesiredProperties, TBehavior extends DesiredPropertiesBehavior, TPayload, TTransformed, TExtra = {}, -> = (bot: Bot, payload: TPayload, extra?: TExtra) => SetupDesiredProps; + TPartial extends boolean = false, +> = TPartial extends true + ? // We use the method syntax for functions (...Params): ReturnType instead of the arrow syntax because it allows us to have overloads in the type + { + (bot: Bot, payload: TPayload, extra?: TExtra & { partial?: false }): SetupDesiredProps; + ( + bot: Bot, + payload: Partial, + extra: TExtra & { partial: true }, + ): Partial>; + } + : // Even if we don't need to overload, since the 2 syntaxes have slightly different semantics, we use the method syntax in here as well + { (bot: Bot, payload: TPayload, extra?: TExtra): SetupDesiredProps }; +/** + * A collection of transformer functions for various Discord entities. + * + * @template TProps - The desired properties for the transformers. + * @template TBehavior - The behavior for handling desired properties. + */ +export type TransformerFunctions = { + [K in keyof TransformerInformations]: TransformerFunction< + TProps, + TBehavior, + TransformerInformations[K]['payload'], + TransformerInformations[K]['transformed'], + TransformerInformations[K]['extra'], + TransformerInformations[K]['partial'] + >; +}; + +/** + * A function that customizes the transformed object after the initial transformation. + * + * @template TProps - The desired properties for the transformer. + * @template TBehavior - The behavior for handling desired properties. + * @template TPayload - The type of the payload received from Discord. + * @template TTransformed - The type of the transformed object returned by the transformer. + * @template TExtra - Additional extra information that might be needed for customization. + * @template TPartial - Indicates if the transformer supports partial payloads. + */ export type TransformerCustomizerFunction< TProps extends TransformersDesiredProperties, TBehavior extends DesiredPropertiesBehavior, TPayload, TTransformed, TExtra = {}, -> = (bot: Bot, payload: TPayload, transformed: TTransformed, extra?: TExtra) => any; + TPartial extends boolean = false, +> = TPartial extends true + ? // We use the method syntax for functions (...Params): ReturnType instead of the arrow syntax because it allows us to have overloads in the type + { + ( + bot: Bot, + payload: TPayload, + transformed: SetupDesiredProps, + extra: TExtra & { partial: false }, + ): any; + ( + bot: Bot, + payload: Partial, + transformed: Partial>, + extra: TExtra & { partial: true }, + ): any; + } + : // Even if we don't need to overload, since the 2 syntaxes have slightly different semantics, we use the method syntax in here as well + { + (bot: Bot, payload: TPayload, transformed: SetupDesiredProps, extra?: TExtra): any; + }; +/** + * A collection of transformer customizer functions for various Discord entities. + * + * @template TProps - The desired properties for the transformers. + * @template TBehavior - The behavior for handling desired properties. + */ export type TransformerCustomizers = { - [K in keyof TransformerFunctions]: TransformerFunctions[K] extends TransformerFunction< + [K in keyof TransformerInformations]: TransformerCustomizerFunction< TProps, TBehavior, - infer TPayload, - infer TTransformed, - infer TExtra - > - ? TransformerCustomizerFunction, BigStringsToBigints> - : 'ERROR: Invalid transformer found'; + TransformerInformations[K]['payload'], + TransformerInformations[K]['transformed'], + BigStringsToBigints, + TransformerInformations[K]['partial'] + >; }; +/** + * Converting BigString properties in T to bigint, leaving other properties unchanged. + * + * @template T - The object type to transform. + */ export type BigStringsToBigints = { [K in keyof T]: BigString extends T[K] ? bigint : T[K]; }; + +/** + * Calls a transformer customizer function with the provided parameters. + * + * @template TInfo - The key of the transformer information to use. + * @param customizer - The key of the transformer information to use. + * @param bot - The bot instance. + * @param payload - The original payload from Discord. + * @param transformed - The transformed object. + * @param extra - Additional extra information for the customizer. + * @returns The result of the customizer function. + * + * @remarks + * This function is used because it is hard to deal with the customizer overloads directly in the transformers. + * + * Since the overloads are present only with partial transformers, this function requires the partial extra, for non-partial transformers the normal function call can be used + */ +export function callCustomizer( + customizer: TInfo, + bot: Bot, + payload: TransformerInformations[TInfo]['payload'] | Partial, + transformed: + | SetupDesiredProps + | Partial>, + extra: TransformerInformations[TInfo]['extra'] & { partial: boolean }, +): any { + // The type of the customizer is not generalizable, so we use unknown, callCustomizer has the correct type and we cast it here + const customizerFn = bot.transformers.customizers[customizer] as (bot: Bot, payload: unknown, transformed: unknown, extra: unknown) => any; + + return customizerFn(bot, payload, transformed, extra); +} diff --git a/packages/bot/src/transformers/activity.ts b/packages/bot/src/transformers/activity.ts index 0cfe1548c..31b19c5a5 100644 --- a/packages/bot/src/transformers/activity.ts +++ b/packages/bot/src/transformers/activity.ts @@ -1,9 +1,10 @@ import type { DiscordActivity, DiscordActivityAssets, DiscordActivityInstance, DiscordActivityLocation } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { Activity, ActivityAssets, ActivityInstance, ActivityLocation } from './types.js'; -export function transformActivity(bot: Bot, payload: DiscordActivity): Activity { +export function transformActivity(bot: Bot, payload: DiscordActivity) { const activity = { name: payload.name, type: payload.type, @@ -40,7 +41,7 @@ export function transformActivity(bot: Bot, payload: DiscordActivity): Activity return bot.transformers.customizers.activity(bot, payload, activity); } -export function transformActivityInstance(bot: Bot, payload: DiscordActivityInstance): ActivityInstance { +export function transformActivityInstance(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.activityInstance; const activityInstance = {} as SetupDesiredProps; @@ -50,10 +51,12 @@ export function transformActivityInstance(bot: Bot, payload: DiscordActivityInst if (props.location && payload.location) activityInstance.location = bot.transformers.activityLocation(bot, payload.location); if (props.users && payload.users) activityInstance.users = payload.users.map((x) => bot.transformers.snowflake(x)); - return bot.transformers.customizers.activityInstance(bot, payload, activityInstance); + return callCustomizer('activityInstance', bot, payload, activityInstance, { + partial: extra?.partial ?? false, + }); } -export function transformActivityLocation(bot: Bot, payload: DiscordActivityLocation): ActivityLocation { +export function transformActivityLocation(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.activityLocation; const activityLocation = {} as SetupDesiredProps; @@ -62,7 +65,9 @@ export function transformActivityLocation(bot: Bot, payload: DiscordActivityLoca if (props.channelId && payload.channel_id) activityLocation.channelId = bot.transformers.snowflake(payload.channel_id); if (props.guildId && payload.guild_id) activityLocation.guildId = bot.transformers.snowflake(payload.guild_id); - return bot.transformers.customizers.activityLocation(bot, payload, activityLocation); + return callCustomizer('activityLocation', bot, payload, activityLocation, { + partial: extra?.partial ?? false, + }); } export function transformActivityAssets(bot: Bot, payload: DiscordActivityAssets): ActivityAssets { diff --git a/packages/bot/src/transformers/application.ts b/packages/bot/src/transformers/application.ts index bf79f4c11..114fc0a45 100644 --- a/packages/bot/src/transformers/application.ts +++ b/packages/bot/src/transformers/application.ts @@ -1,10 +1,11 @@ -import { type DiscordApplication, DiscordApplicationIntegrationType, type DiscordUser } from '@discordeno/types'; +import { type DiscordApplication, DiscordApplicationIntegrationType } from '@discordeno/types'; import { iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; +import { callCustomizer } from '../transformers.js'; import { ToggleBitfield } from './toggles/ToggleBitfield.js'; import type { Application } from './types.js'; -export function transformApplication(bot: Bot, payload: DiscordApplication, extra?: { shardId?: number }): Application { +export function transformApplication(bot: Bot, payload: Partial, extra?: { shardId?: number; partial?: boolean }) { const application = { name: payload.name, description: payload.description, @@ -21,21 +22,17 @@ export function transformApplication(bot: Bot, payload: DiscordApplication, extr // flags_new is a string with the bitfield inside flagsNew: payload.flags_new ? new ToggleBitfield(Number(payload.flags_new)) : undefined, - id: bot.transformers.snowflake(payload.id), + id: payload.id ? bot.transformers.snowflake(payload.id) : undefined, icon: payload.icon ? iconHashToBigInt(payload.icon) : undefined, - owner: payload.owner - ? // @ts-expect-error the partial here wont break anything - bot.transformers.user(bot, payload.owner) - : undefined, + owner: payload.owner ? bot.transformers.user(bot, payload.owner, { partial: true }) : undefined, team: payload.team ? bot.transformers.team(bot, payload.team) : undefined, guildId: payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined, customInstallUrl: payload.custom_install_url, - // @ts-expect-error the partial here wont break anything - guild: payload.guild ? bot.transformers.guild(bot, payload.guild, { shardId: extra?.shardId }) : undefined, + guild: payload.guild ? bot.transformers.guild(bot, payload.guild, { shardId: extra?.shardId, partial: true }) : undefined, approximateGuildCount: payload.approximate_guild_count, approximateUserInstallCount: payload.approximate_user_install_count, approximateUserAuthorizationCount: payload.approximate_user_authorization_count, - bot: payload.bot ? bot.transformers.user(bot, payload.bot as DiscordUser) : undefined, + bot: payload.bot ? bot.transformers.user(bot, payload.bot, { partial: true }) : undefined, interactionsEndpointUrl: payload.interactions_endpoint_url ? payload.interactions_endpoint_url : undefined, redirectUris: payload.redirect_uris, roleConnectionsVerificationUrl: payload.role_connections_verification_url, @@ -71,5 +68,8 @@ export function transformApplication(bot: Bot, payload: DiscordApplication, extr eventWebhooksTypes: payload.event_webhooks_types, } as Application; - return bot.transformers.customizers.application(bot, payload, application, extra); + return callCustomizer('application', bot, payload, application, { + shardId: extra?.shardId, + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/applicationCommand.ts b/packages/bot/src/transformers/applicationCommand.ts index 1061747a5..3b2feb028 100644 --- a/packages/bot/src/transformers/applicationCommand.ts +++ b/packages/bot/src/transformers/applicationCommand.ts @@ -2,7 +2,7 @@ import type { DiscordApplicationCommand } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { ApplicationCommand } from './types.js'; -export function transformApplicationCommand(bot: Bot, payload: DiscordApplicationCommand): ApplicationCommand { +export function transformApplicationCommand(bot: Bot, payload: DiscordApplicationCommand) { const applicationCommand = { id: bot.transformers.snowflake(payload.id), applicationId: bot.transformers.snowflake(payload.application_id), diff --git a/packages/bot/src/transformers/applicationCommandOption.ts b/packages/bot/src/transformers/applicationCommandOption.ts index 452070ed0..76c7d9b58 100644 --- a/packages/bot/src/transformers/applicationCommandOption.ts +++ b/packages/bot/src/transformers/applicationCommandOption.ts @@ -2,7 +2,7 @@ import type { DiscordApplicationCommandOption } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { ApplicationCommandOption } from './types.js'; -export function transformApplicationCommandOption(bot: Bot, payload: DiscordApplicationCommandOption): ApplicationCommandOption { +export function transformApplicationCommandOption(bot: Bot, payload: DiscordApplicationCommandOption) { const applicationCommandOption = { type: payload.type, name: payload.name, diff --git a/packages/bot/src/transformers/applicationCommandOptionChoice.ts b/packages/bot/src/transformers/applicationCommandOptionChoice.ts index 391ec5bab..bb7b48521 100644 --- a/packages/bot/src/transformers/applicationCommandOptionChoice.ts +++ b/packages/bot/src/transformers/applicationCommandOptionChoice.ts @@ -2,7 +2,7 @@ import type { DiscordApplicationCommandOptionChoice } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { ApplicationCommandOptionChoice } from './types.js'; -export function transformApplicationCommandOptionChoice(bot: Bot, payload: DiscordApplicationCommandOptionChoice): ApplicationCommandOptionChoice { +export function transformApplicationCommandOptionChoice(bot: Bot, payload: DiscordApplicationCommandOptionChoice) { const applicationCommandOptionChoice = { name: payload.name, nameLocalizations: payload.name_localizations ?? undefined, diff --git a/packages/bot/src/transformers/applicationCommandPermission.ts b/packages/bot/src/transformers/applicationCommandPermission.ts index 6c6521d49..a5e56c33d 100644 --- a/packages/bot/src/transformers/applicationCommandPermission.ts +++ b/packages/bot/src/transformers/applicationCommandPermission.ts @@ -2,10 +2,7 @@ import type { DiscordGuildApplicationCommandPermissions } from '@discordeno/type import type { Bot } from '../bot.js'; import type { GuildApplicationCommandPermissions } from './types.js'; -export function transformApplicationCommandPermission( - bot: Bot, - payload: DiscordGuildApplicationCommandPermissions, -): GuildApplicationCommandPermissions { +export function transformApplicationCommandPermission(bot: Bot, payload: DiscordGuildApplicationCommandPermissions) { const applicationCommandPermission = { id: bot.transformers.snowflake(payload.id), applicationId: bot.transformers.snowflake(payload.application_id), diff --git a/packages/bot/src/transformers/attachment.ts b/packages/bot/src/transformers/attachment.ts index 7d215401a..503e23ac8 100644 --- a/packages/bot/src/transformers/attachment.ts +++ b/packages/bot/src/transformers/attachment.ts @@ -1,9 +1,10 @@ import type { DiscordAttachment } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { Attachment } from './types.js'; -export function transformAttachment(bot: Bot, payload: DiscordAttachment): typeof bot.transformers.$inferredTypes.attachment { +export function transformAttachment(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.attachment; const attachment = {} as SetupDesiredProps; @@ -11,7 +12,7 @@ export function transformAttachment(bot: Bot, payload: DiscordAttachment): typeo if (props.filename && payload.filename) attachment.filename = payload.filename; if (props.title && payload.title) attachment.title = payload.title; if (props.contentType && payload.content_type) attachment.contentType = payload.content_type; - if (props.size) attachment.size = payload.size; + if (props.size && payload.size !== undefined) attachment.size = payload.size; if (props.url && payload.url) attachment.url = payload.url; if (props.proxyUrl && payload.proxy_url) attachment.proxyUrl = payload.proxy_url; if (props.height && payload.height) attachment.height = payload.height; @@ -28,5 +29,7 @@ export function transformAttachment(bot: Bot, payload: DiscordAttachment): typeo if (props.clipCreatedAt && payload.clip_created_at) attachment.clipCreatedAt = payload.clip_created_at; if (props.application && payload.application) attachment.application = bot.transformers.application(bot, payload.application); - return bot.transformers.customizers.attachment(bot, payload, attachment); + return callCustomizer('attachment', bot, payload, attachment, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/auditLogEntry.ts b/packages/bot/src/transformers/auditLogEntry.ts index 4acd82677..6340a7a6f 100644 --- a/packages/bot/src/transformers/auditLogEntry.ts +++ b/packages/bot/src/transformers/auditLogEntry.ts @@ -3,7 +3,7 @@ import { camelize } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { AuditLogEntry } from './types.js'; -export function transformAuditLogEntry(bot: Bot, payload: DiscordAuditLogEntry): AuditLogEntry { +export function transformAuditLogEntry(bot: Bot, payload: DiscordAuditLogEntry) { const auditLogEntry = { id: bot.transformers.snowflake(payload.id), changes: camelize(payload.changes), diff --git a/packages/bot/src/transformers/automodActionExecution.ts b/packages/bot/src/transformers/automodActionExecution.ts index c673de22b..6692c1589 100644 --- a/packages/bot/src/transformers/automodActionExecution.ts +++ b/packages/bot/src/transformers/automodActionExecution.ts @@ -2,7 +2,7 @@ import type { DiscordAutoModerationActionExecution } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { AutoModerationActionExecution } from './types.js'; -export function transformAutoModerationActionExecution(bot: Bot, payload: DiscordAutoModerationActionExecution): AutoModerationActionExecution { +export function transformAutoModerationActionExecution(bot: Bot, payload: DiscordAutoModerationActionExecution) { const rule = { content: payload.content, ruleTriggerType: payload.rule_trigger_type, diff --git a/packages/bot/src/transformers/automodRule.ts b/packages/bot/src/transformers/automodRule.ts index b4d518a85..62e2aed0a 100644 --- a/packages/bot/src/transformers/automodRule.ts +++ b/packages/bot/src/transformers/automodRule.ts @@ -2,7 +2,7 @@ import type { DiscordAutoModerationRule } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { AutoModerationRule } from './types.js'; -export function transformAutoModerationRule(bot: Bot, payload: DiscordAutoModerationRule): AutoModerationRule { +export function transformAutoModerationRule(bot: Bot, payload: DiscordAutoModerationRule) { const rule = { name: payload.name, eventType: payload.event_type, diff --git a/packages/bot/src/transformers/avatarDecorationData.ts b/packages/bot/src/transformers/avatarDecorationData.ts index 358fd25ea..6b88c19e2 100644 --- a/packages/bot/src/transformers/avatarDecorationData.ts +++ b/packages/bot/src/transformers/avatarDecorationData.ts @@ -1,14 +1,17 @@ import type { DiscordAvatarDecorationData } from '@discordeno/types'; import { iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; +import { callCustomizer } from '../transformers.js'; import type { AvatarDecorationData } from './types.js'; -export function transformAvatarDecorationData(bot: Bot, payload: DiscordAvatarDecorationData): AvatarDecorationData { +export function transformAvatarDecorationData(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const data = {} as AvatarDecorationData; const props = bot.transformers.desiredProperties.avatarDecorationData; if (props.asset && payload.asset) data.asset = iconHashToBigInt(payload.asset); if (props.skuId && payload.sku_id) data.skuId = bot.transformers.snowflake(payload.sku_id); - return data; + return callCustomizer('avatarDecorationData', bot, payload, data, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/channel.ts b/packages/bot/src/transformers/channel.ts index 5ce806ec0..d61050b26 100644 --- a/packages/bot/src/transformers/channel.ts +++ b/packages/bot/src/transformers/channel.ts @@ -2,6 +2,7 @@ import type { BigString, DiscordChannel, DiscordForumTag } from '@discordeno/typ import { iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { ChannelToggles } from './toggles/channel.js'; import { Permissions } from './toggles/Permissions.js'; import type { Channel, ForumTag } from './types.js'; @@ -70,7 +71,7 @@ export const baseChannel: Channel = { }, }; -export function transformChannel(bot: Bot, payload: DiscordChannel, extra?: { guildId?: BigString }) { +export function transformChannel(bot: Bot, payload: Partial, extra?: { guildId?: BigString; partial?: boolean }) { const channel = Object.create(baseChannel) as SetupDesiredProps; const props = bot.transformers.desiredProperties.channel; channel.toggles = new ChannelToggles(payload); @@ -78,7 +79,7 @@ export function transformChannel(bot: Bot, payload: DiscordChannel, extra?: { gu if (props.id && payload.id) channel.id = bot.transformers.snowflake(payload.id); if (props.guildId && (extra?.guildId ?? payload.guild_id)) channel.guildId = extra?.guildId ? bot.transformers.snowflake(extra.guildId) : bot.transformers.snowflake(payload.guild_id!); - if (props.type) channel.type = payload.type; + if (props.type && payload.type !== undefined) channel.type = payload.type; if (props.position) channel.position = payload.position; if (props.name && payload.name) channel.name = payload.name; if (props.topic && payload.topic) channel.topic = payload.topic; @@ -123,12 +124,13 @@ export function transformChannel(bot: Bot, payload: DiscordChannel, extra?: { gu if (props.defaultSortOrder && payload.default_sort_order !== undefined) channel.defaultSortOrder = payload.default_sort_order; if (props.defaultForumLayout && payload.default_forum_layout !== undefined) channel.defaultForumLayout = payload.default_forum_layout; - return bot.transformers.customizers.channel(bot, payload, channel, { + return callCustomizer('channel', bot, payload, channel, { guildId: extra?.guildId ? bot.transformers.snowflake(extra.guildId) : undefined, + partial: extra?.partial ?? false, }); } -export function transformForumTag(bot: Bot, payload: DiscordForumTag): ForumTag { +export function transformForumTag(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.forumTag; const forumTag = {} as SetupDesiredProps; @@ -138,5 +140,7 @@ export function transformForumTag(bot: Bot, payload: DiscordForumTag): ForumTag if (props.emojiId && payload.emoji_id) forumTag.emojiId = bot.transformers.snowflake(payload.emoji_id); if (props.emojiName && payload.emoji_name) forumTag.emojiName = payload.emoji_name; - return bot.transformers.customizers.forumTag(bot, payload, forumTag); + return callCustomizer('forumTag', bot, payload, forumTag, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/component.ts b/packages/bot/src/transformers/component.ts index 8420b65a2..e8c509919 100644 --- a/packages/bot/src/transformers/component.ts +++ b/packages/bot/src/transformers/component.ts @@ -39,12 +39,22 @@ import { } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { ToggleBitfield } from './toggles/ToggleBitfield.js'; import type { Component, MediaGalleryItem, UnfurledMediaItem } from './types.js'; -export function transformComponent(bot: Bot, payload: DiscordMessageComponent | DiscordMessageComponentFromModalInteractionResponse): Component { +export function transformComponent( + bot: Bot, + payload: Partial, + extra?: { partial?: boolean }, +) { let component: SetupDesiredProps; + // There's nothing to do with a component without a type, as it means an issue on Discord, so we just throw an error + if (!payload.type) { + throw new Error(`[Discordeno] Received a component payload without a type in the component transformer.`); + } + // This switch is exhaustive, so we dont need the default case and TS does not error out for the un-initialized component variable switch (payload.type) { case MessageComponentTypes.ActionRow: @@ -109,10 +119,12 @@ export function transformComponent(bot: Bot, payload: DiscordMessageComponent | break; } - return bot.transformers.customizers.component(bot, payload, component); + return callCustomizer('component', bot, payload, component, { + partial: extra?.partial ?? false, + }); } -export function transformUnfurledMediaItem(bot: Bot, payload: DiscordUnfurledMediaItem): UnfurledMediaItem { +export function transformUnfurledMediaItem(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.unfurledMediaItem; const mediaItem = {} as SetupDesiredProps; @@ -126,10 +138,12 @@ export function transformUnfurledMediaItem(bot: Bot, payload: DiscordUnfurledMed if (props.flags && payload.flags) mediaItem.flags = new ToggleBitfield(payload.flags); if (props.attachmentId && payload.attachment_id) mediaItem.attachmentId = bot.transformers.snowflake(payload.attachment_id); - return bot.transformers.customizers.unfurledMediaItem(bot, payload, mediaItem); + return callCustomizer('unfurledMediaItem', bot, payload, mediaItem, { + partial: extra?.partial ?? false, + }); } -export function transformMediaGalleryItem(bot: Bot, payload: DiscordMediaGalleryItem): MediaGalleryItem { +export function transformMediaGalleryItem(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.mediaGalleryItem; const galleryItem = {} as SetupDesiredProps; @@ -137,10 +151,12 @@ export function transformMediaGalleryItem(bot: Bot, payload: DiscordMediaGallery if (props.description && payload.description) galleryItem.description = payload.description; if (props.spoiler && payload.spoiler) galleryItem.spoiler = payload.spoiler; - return bot.transformers.customizers.mediaGalleryItem(bot, payload, galleryItem); + return callCustomizer('mediaGalleryItem', bot, payload, galleryItem, { + partial: extra?.partial ?? false, + }); } -function transformActionRow(bot: Bot, payload: DiscordActionRow) { +function transformActionRow(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const actionRow = {} as SetupDesiredProps; @@ -152,7 +168,7 @@ function transformActionRow(bot: Bot, payload: DiscordActionRow) { return actionRow; } -function transformContainerComponent(bot: Bot, payload: DiscordContainerComponent) { +function transformContainerComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const container = {} as SetupDesiredProps; @@ -166,7 +182,7 @@ function transformContainerComponent(bot: Bot, payload: DiscordContainerComponen return container; } -function transformButtonComponent(bot: Bot, payload: DiscordButtonComponent) { +function transformButtonComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const button = {} as SetupDesiredProps; @@ -175,8 +191,7 @@ function transformButtonComponent(bot: Bot, payload: DiscordButtonComponent) { if (props.label && payload.label) button.label = payload.label; if (props.customId && payload.custom_id) button.customId = payload.custom_id; if (props.style && payload.style) button.style = payload.style; - // @ts-expect-error TODO: Deal with partials - if (props.emoji && payload.emoji) button.emoji = bot.transformers.emoji(bot, payload.emoji); + if (props.emoji && payload.emoji) button.emoji = bot.transformers.emoji(bot, payload.emoji, { partial: true }); if (props.url && payload.url) button.url = payload.url; if (props.disabled && payload.disabled) button.disabled = payload.disabled; if (props.skuId && payload.sku_id) button.skuId = bot.transformers.snowflake(payload.sku_id); @@ -184,7 +199,7 @@ function transformButtonComponent(bot: Bot, payload: DiscordButtonComponent) { return button; } -function transformInputTextComponent(bot: Bot, payload: DiscordTextInputComponent | DiscordTextInputInteractionResponse) { +function transformInputTextComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const input = {} as SetupDesiredProps; @@ -193,7 +208,7 @@ function transformInputTextComponent(bot: Bot, payload: DiscordTextInputComponen if (props.value && payload.value) input.value = payload.value; if (props.customId && payload.custom_id) input.customId = payload.custom_id; - // Check if it is the component or the response + // We assume that if we find 'values' it is the interaction response if ('style' in payload) { if (props.style && payload.style) input.style = payload.style; if (props.required && payload.required) input.required = payload.required; @@ -206,7 +221,10 @@ function transformInputTextComponent(bot: Bot, payload: DiscordTextInputComponen return input; } -function transformStringSelectMenuComponent(bot: Bot, payload: DiscordStringSelectComponent | DiscordStringSelectInteractionResponseFromModal) { +function transformStringSelectMenuComponent( + bot: Bot, + payload: Partial, +) { const props = bot.transformers.desiredProperties.component; const select = {} as SetupDesiredProps; @@ -214,15 +232,17 @@ function transformStringSelectMenuComponent(bot: Bot, payload: DiscordStringSele if (props.id && payload.id) select.id = payload.id; if (props.customId && payload.custom_id) select.customId = payload.custom_id; - // Check if this is the string select response + // We assume that if we find 'values' it is the interaction response if ('values' in payload) { if (props.values && payload.values) select.values = payload.values; } else { - if (props.placeholder && payload.placeholder) select.placeholder = payload.placeholder; - if (props.minValues && payload.min_values) select.minValues = payload.min_values; - if (props.maxValues && payload.max_values) select.maxValues = payload.max_values; - if (props.options && payload.options) - select.options = payload.options.map((option) => ({ + const _payload = payload as Partial; + + if (props.placeholder && _payload.placeholder) select.placeholder = _payload.placeholder; + if (props.minValues && _payload.min_values) select.minValues = _payload.min_values; + if (props.maxValues && _payload.max_values) select.maxValues = _payload.max_values; + if (props.options && _payload.options) + select.options = _payload.options.map((option) => ({ label: option.label, value: option.value, description: option.description, @@ -235,13 +255,13 @@ function transformStringSelectMenuComponent(bot: Bot, payload: DiscordStringSele : undefined, default: option.default, })); - if (props.disabled && payload.disabled) select.disabled = payload.disabled; + if (props.disabled && _payload.disabled) select.disabled = _payload.disabled; } return select; } -function transformUserSelectMenuComponent(bot: Bot, payload: DiscordUserSelectComponent | DiscordUserSelectInteractionResponseFromModal) { +function transformUserSelectMenuComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const select = {} as SetupDesiredProps; @@ -249,26 +269,28 @@ function transformUserSelectMenuComponent(bot: Bot, payload: DiscordUserSelectCo if (props.id && payload.id) select.id = payload.id; if (props.customId && payload.custom_id) select.customId = payload.custom_id; - // Check if this is the user select response + // We assume that if we find 'values' it is the interaction response if ('values' in payload) { if (props.values && payload.values) select.values = payload.values; if (props.resolved && payload.resolved) select.resolved = bot.transformers.interactionDataResolved(bot, payload.resolved); } else { - if (props.placeholder && payload.placeholder) select.placeholder = payload.placeholder; - if (props.minValues && payload.min_values) select.minValues = payload.min_values; - if (props.maxValues && payload.max_values) select.maxValues = payload.max_values; - if (props.defaultValues && payload.default_values) - select.defaultValues = payload.default_values.map((defaultValue) => ({ + const _payload = payload as Partial; + + if (props.placeholder && _payload.placeholder) select.placeholder = _payload.placeholder; + if (props.minValues && _payload.min_values) select.minValues = _payload.min_values; + if (props.maxValues && _payload.max_values) select.maxValues = _payload.max_values; + if (props.defaultValues && _payload.default_values) + select.defaultValues = _payload.default_values.map((defaultValue) => ({ id: bot.transformers.snowflake(defaultValue.id), type: defaultValue.type, })); - if (props.disabled && payload.disabled) select.disabled = payload.disabled; + if (props.disabled && _payload.disabled) select.disabled = _payload.disabled; } return select; } -function transformRoleSelectMenuComponent(bot: Bot, payload: DiscordRoleSelectComponent | DiscordRoleSelectInteractionResponseFromModal) { +function transformRoleSelectMenuComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const select = {} as SetupDesiredProps; @@ -276,20 +298,22 @@ function transformRoleSelectMenuComponent(bot: Bot, payload: DiscordRoleSelectCo if (props.id && payload.id) select.id = payload.id; if (props.customId && payload.custom_id) select.customId = payload.custom_id; - // Check if this is the role select response + // We assume that if we find 'values' it is the interaction response if ('values' in payload) { if (props.values && payload.values) select.values = payload.values; if (props.resolved && payload.resolved) select.resolved = bot.transformers.interactionDataResolved(bot, payload.resolved); } else { - if (props.placeholder && payload.placeholder) select.placeholder = payload.placeholder; - if (props.minValues && payload.min_values) select.minValues = payload.min_values; - if (props.maxValues && payload.max_values) select.maxValues = payload.max_values; - if (props.defaultValues && payload.default_values) - select.defaultValues = payload.default_values.map((defaultValue) => ({ + const _payload = payload as Partial; + + if (props.placeholder && _payload.placeholder) select.placeholder = _payload.placeholder; + if (props.minValues && _payload.min_values) select.minValues = _payload.min_values; + if (props.maxValues && _payload.max_values) select.maxValues = _payload.max_values; + if (props.defaultValues && _payload.default_values) + select.defaultValues = _payload.default_values.map((defaultValue) => ({ id: bot.transformers.snowflake(defaultValue.id), type: defaultValue.type, })); - if (props.disabled && payload.disabled) select.disabled = payload.disabled; + if (props.disabled && _payload.disabled) select.disabled = _payload.disabled; } return select; @@ -297,7 +321,7 @@ function transformRoleSelectMenuComponent(bot: Bot, payload: DiscordRoleSelectCo function transformMentionableSelectMenuComponent( bot: Bot, - payload: DiscordMentionableSelectComponent | DiscordMentionableSelectInteractionResponseFromModal, + payload: Partial, ) { const props = bot.transformers.desiredProperties.component; const select = {} as SetupDesiredProps; @@ -306,26 +330,31 @@ function transformMentionableSelectMenuComponent( if (props.id && payload.id) select.id = payload.id; if (props.customId && payload.custom_id) select.customId = payload.custom_id; - // Check if this is the mentionable select response + // We assume that if we find 'values' it is the interaction response if ('values' in payload) { if (props.values && payload.values) select.values = payload.values; if (props.resolved && payload.resolved) select.resolved = bot.transformers.interactionDataResolved(bot, payload.resolved); } else { - if (props.placeholder && payload.placeholder) select.placeholder = payload.placeholder; - if (props.minValues && payload.min_values) select.minValues = payload.min_values; - if (props.maxValues && payload.max_values) select.maxValues = payload.max_values; - if (props.defaultValues && payload.default_values) - select.defaultValues = payload.default_values.map((defaultValue) => ({ + const _payload = payload as Partial; + + if (props.placeholder && _payload.placeholder) select.placeholder = _payload.placeholder; + if (props.minValues && _payload.min_values) select.minValues = _payload.min_values; + if (props.maxValues && _payload.max_values) select.maxValues = _payload.max_values; + if (props.defaultValues && _payload.default_values) + select.defaultValues = _payload.default_values.map((defaultValue) => ({ id: bot.transformers.snowflake(defaultValue.id), type: defaultValue.type, })); - if (props.disabled && payload.disabled) select.disabled = payload.disabled; + if (props.disabled && _payload.disabled) select.disabled = _payload.disabled; } return select; } -function transformChannelSelectMenuComponent(bot: Bot, payload: DiscordChannelSelectComponent | DiscordChannelSelectInteractionResponseFromModal) { +function transformChannelSelectMenuComponent( + bot: Bot, + payload: Partial, +) { const props = bot.transformers.desiredProperties.component; const select = {} as SetupDesiredProps; @@ -333,27 +362,29 @@ function transformChannelSelectMenuComponent(bot: Bot, payload: DiscordChannelSe if (props.id && payload.id) select.id = payload.id; if (props.customId && payload.custom_id) select.customId = payload.custom_id; - // Check if this is the channel select response + // We assume that if we find 'values' it is the interaction response if ('values' in payload) { if (props.values && payload.values) select.values = payload.values; if (props.resolved && payload.resolved) select.resolved = bot.transformers.interactionDataResolved(bot, payload.resolved); } else { - if (props.placeholder && payload.placeholder) select.placeholder = payload.placeholder; - if (props.minValues && payload.min_values) select.minValues = payload.min_values; - if (props.maxValues && payload.max_values) select.maxValues = payload.max_values; - if (props.defaultValues && payload.default_values) - select.defaultValues = payload.default_values.map((defaultValue) => ({ + const _payload = payload as Partial; + + if (props.placeholder && _payload.placeholder) select.placeholder = _payload.placeholder; + if (props.minValues && _payload.min_values) select.minValues = _payload.min_values; + if (props.maxValues && _payload.max_values) select.maxValues = _payload.max_values; + if (props.defaultValues && _payload.default_values) + select.defaultValues = _payload.default_values.map((defaultValue) => ({ id: bot.transformers.snowflake(defaultValue.id), type: defaultValue.type, })); - if (props.channelTypes && payload.channel_types) select.channelTypes = payload.channel_types; - if (props.disabled && payload.disabled) select.disabled = payload.disabled; + if (props.channelTypes && _payload.channel_types) select.channelTypes = _payload.channel_types; + if (props.disabled && _payload.disabled) select.disabled = _payload.disabled; } return select; } -function transformSectionComponent(bot: Bot, payload: DiscordSectionComponent) { +function transformSectionComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const section = {} as SetupDesiredProps; @@ -365,7 +396,7 @@ function transformSectionComponent(bot: Bot, payload: DiscordSectionComponent) { return section; } -function transformThumbnailComponent(bot: Bot, payload: DiscordThumbnailComponent) { +function transformThumbnailComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const thumbnail = {} as SetupDesiredProps; @@ -378,7 +409,7 @@ function transformThumbnailComponent(bot: Bot, payload: DiscordThumbnailComponen return thumbnail; } -function transformMediaGalleryComponent(bot: Bot, payload: DiscordMediaGalleryComponent) { +function transformMediaGalleryComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const mediaGallery = {} as SetupDesiredProps; @@ -389,7 +420,7 @@ function transformMediaGalleryComponent(bot: Bot, payload: DiscordMediaGalleryCo return mediaGallery; } -function transformFileComponent(bot: Bot, payload: DiscordFileComponent) { +function transformFileComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const file = {} as SetupDesiredProps; @@ -403,13 +434,13 @@ function transformFileComponent(bot: Bot, payload: DiscordFileComponent) { return file; } -function transformTextDisplayComponent(bot: Bot, payload: DiscordTextDisplayComponent | DiscordTextDisplayInteractionResponse) { +function transformTextDisplayComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const textDisplay = {} as SetupDesiredProps; if (props.type && payload.type) textDisplay.type = payload.type; if (props.id && payload.id) textDisplay.id = payload.id; - // That that this isn't a response + // We assume that if we find 'content' it is the component if ('content' in payload) { if (props.content && payload.content) textDisplay.content = payload.content; } @@ -417,7 +448,7 @@ function transformTextDisplayComponent(bot: Bot, payload: DiscordTextDisplayComp return textDisplay; } -function transformSeparatorComponent(bot: Bot, payload: DiscordSeparatorComponent) { +function transformSeparatorComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const separator = {} as SetupDesiredProps; @@ -429,13 +460,13 @@ function transformSeparatorComponent(bot: Bot, payload: DiscordSeparatorComponen return separator; } -function transformLabelComponent(bot: Bot, payload: DiscordLabelComponent | DiscordLabelInteractionResponse) { +function transformLabelComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const label = {} as SetupDesiredProps; if (props.type && payload.type) label.type = payload.type; if (props.id && payload.id) label.id = payload.id; - // Check that this isn't a response + // We assume that if we find 'label' it is the component if ('label' in payload) { if (props.label && payload.label) label.label = payload.label; if (props.description && payload.description) label.description = payload.description; @@ -445,7 +476,7 @@ function transformLabelComponent(bot: Bot, payload: DiscordLabelComponent | Disc return label; } -function transformFileUploadComponent(bot: Bot, payload: DiscordFileUploadComponent | DiscordFileUploadInteractionResponse) { +function transformFileUploadComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const fileUpload = {} as SetupDesiredProps; @@ -453,19 +484,21 @@ function transformFileUploadComponent(bot: Bot, payload: DiscordFileUploadCompon if (props.id && payload.id) fileUpload.id = payload.id; if (props.customId && payload.custom_id) fileUpload.customId = payload.custom_id; - // Check that this is a response + // We assume that if we find 'values' it is the interaction response if ('values' in payload) { if (props.values && payload.values) fileUpload.values = payload.values; } else { - if (props.minValues && payload.min_values) fileUpload.minValues = payload.min_values; - if (props.maxValues && payload.max_values) fileUpload.maxValues = payload.max_values; - if (props.required && payload.required) fileUpload.required = payload.required; + const _payload = payload as Partial; + + if (props.minValues && _payload.min_values) fileUpload.minValues = _payload.min_values; + if (props.maxValues && _payload.max_values) fileUpload.maxValues = _payload.max_values; + if (props.required && _payload.required) fileUpload.required = _payload.required; } return fileUpload; } -function transformRadioGroupComponent(bot: Bot, payload: DiscordRadioGroupComponent | DiscordRadioGroupInteractionResponse) { +function transformRadioGroupComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const radioGroup = {} as SetupDesiredProps; @@ -473,18 +506,20 @@ function transformRadioGroupComponent(bot: Bot, payload: DiscordRadioGroupCompon if (props.id && payload.id) radioGroup.id = payload.id; if (props.customId && payload.custom_id) radioGroup.customId = payload.custom_id; - // Check if this is the component (has options) or the interaction response (modal submit, has value) + // We assume that if we find 'options' it is the component if ('options' in payload) { if (props.options && payload.options) radioGroup.options = payload.options; if (props.required && payload.required !== undefined) radioGroup.required = payload.required; } else { - if (props.value) radioGroup.value = payload.value ?? undefined; + const _payload = payload as Partial; + + if (props.value) radioGroup.value = _payload.value ?? undefined; } return radioGroup; } -function transformCheckboxGroupComponent(bot: Bot, payload: DiscordCheckboxGroupComponent | DiscordCheckboxGroupInteractionResponse) { +function transformCheckboxGroupComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const checkboxGroup = {} as SetupDesiredProps; @@ -492,20 +527,22 @@ function transformCheckboxGroupComponent(bot: Bot, payload: DiscordCheckboxGroup if (props.id && payload.id) checkboxGroup.id = payload.id; if (props.customId && payload.custom_id) checkboxGroup.customId = payload.custom_id; - // Check if this is the component (has options) or the interaction response (modal submit, has values) + // We assume that if we find 'options' it is the component if ('options' in payload) { if (props.options && payload.options) checkboxGroup.options = payload.options; if (props.minValues && payload.min_values !== undefined) checkboxGroup.minValues = payload.min_values; if (props.maxValues && payload.max_values !== undefined) checkboxGroup.maxValues = payload.max_values; if (props.required && payload.required !== undefined) checkboxGroup.required = payload.required; } else { - if (props.values && payload.values) checkboxGroup.values = payload.values; + const _payload = payload as Partial; + + if (props.values && _payload.values) checkboxGroup.values = _payload.values; } return checkboxGroup; } -function transformCheckboxComponent(bot: Bot, payload: DiscordCheckboxComponent | DiscordCheckboxInteractionResponse) { +function transformCheckboxComponent(bot: Bot, payload: Partial) { const props = bot.transformers.desiredProperties.component; const checkbox = {} as SetupDesiredProps; diff --git a/packages/bot/src/transformers/embed.ts b/packages/bot/src/transformers/embed.ts index 8bf94410d..44ec7b79f 100644 --- a/packages/bot/src/transformers/embed.ts +++ b/packages/bot/src/transformers/embed.ts @@ -3,7 +3,7 @@ import type { Bot } from '../bot.js'; import { ToggleBitfield } from './toggles/ToggleBitfield.js'; import type { Embed } from './types.js'; -export function transformEmbed(bot: Bot, payload: DiscordEmbed): Embed { +export function transformEmbed(bot: Bot, payload: DiscordEmbed) { const embed = { title: payload.title, type: payload.type, diff --git a/packages/bot/src/transformers/emoji.ts b/packages/bot/src/transformers/emoji.ts index 2740206f8..1e0582318 100644 --- a/packages/bot/src/transformers/emoji.ts +++ b/packages/bot/src/transformers/emoji.ts @@ -1,6 +1,7 @@ import type { DiscordDefaultReactionEmoji, DiscordEmoji } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { EmojiToggles } from './toggles/emoji.js'; import type { DefaultReactionEmoji, Emoji } from './types.js'; @@ -22,7 +23,7 @@ export const baseEmoji: Emoji = { }, }; -export function transformEmoji(bot: Bot, payload: DiscordEmoji): Emoji { +export function transformEmoji(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.emoji; const emoji = Object.create(baseEmoji) as SetupDesiredProps; @@ -33,15 +34,19 @@ export function transformEmoji(bot: Bot, payload: DiscordEmoji): Emoji { emoji.toggles = new EmojiToggles(payload); - return bot.transformers.customizers.emoji(bot, payload, emoji); + return callCustomizer('emoji', bot, payload, emoji, { + partial: extra?.partial ?? false, + }); } -export function transformDefaultReactionEmoji(bot: Bot, payload: DiscordDefaultReactionEmoji): DefaultReactionEmoji { +export function transformDefaultReactionEmoji(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.defaultReactionEmoji; const defaultReactionEmoji = {} as SetupDesiredProps; if (props.emojiId && payload.emoji_id) defaultReactionEmoji.emojiId = bot.transformers.snowflake(payload.emoji_id); if (props.emojiName && payload.emoji_name) defaultReactionEmoji.emojiName = payload.emoji_name; - return bot.transformers.customizers.defaultReactionEmoji(bot, payload, defaultReactionEmoji); + return callCustomizer('defaultReactionEmoji', bot, payload, defaultReactionEmoji, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/entitlement.ts b/packages/bot/src/transformers/entitlement.ts index c0956c0bf..fad930b55 100644 --- a/packages/bot/src/transformers/entitlement.ts +++ b/packages/bot/src/transformers/entitlement.ts @@ -1,9 +1,10 @@ import type { DiscordEntitlement } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { Entitlement } from './types.js'; -export function transformEntitlement(bot: Bot, payload: DiscordEntitlement): Entitlement { +export function transformEntitlement(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.entitlement; const entitlement = {} as SetupDesiredProps; @@ -18,5 +19,7 @@ export function transformEntitlement(bot: Bot, payload: DiscordEntitlement): Ent if (props.endsAt && payload.ends_at) entitlement.endsAt = Date.parse(payload.ends_at); if (props.consumed && payload.consumed !== undefined) entitlement.consumed = payload.consumed; - return bot.transformers.customizers.entitlement(bot, payload, entitlement); + return callCustomizer('entitlement', bot, payload, entitlement, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/gatewayBot.ts b/packages/bot/src/transformers/gatewayBot.ts index 9680c6c76..699cd4527 100644 --- a/packages/bot/src/transformers/gatewayBot.ts +++ b/packages/bot/src/transformers/gatewayBot.ts @@ -2,7 +2,7 @@ import type { DiscordGetGatewayBot } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { GetGatewayBot } from './types.js'; -export function transformGatewayBot(bot: Bot, payload: DiscordGetGatewayBot): GetGatewayBot { +export function transformGatewayBot(bot: Bot, payload: DiscordGetGatewayBot) { const gatewayBot = { url: payload.url, shards: payload.shards, diff --git a/packages/bot/src/transformers/guild.ts b/packages/bot/src/transformers/guild.ts index 70fd18fb8..2d3b79786 100644 --- a/packages/bot/src/transformers/guild.ts +++ b/packages/bot/src/transformers/guild.ts @@ -1,7 +1,8 @@ -import { ChannelTypes, type DiscordGuild, type DiscordPresenceUpdate } from '@discordeno/types'; +import { ChannelTypes, type DiscordGuild } from '@discordeno/types'; import { Collection, iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { GuildToggles } from './toggles/guild.js'; import type { Channel, Guild } from './types.js'; @@ -23,30 +24,32 @@ export const baseGuild: Guild = { }, }; -export function transformGuild(bot: Bot, payload: DiscordGuild, extra?: { shardId?: number }): Guild { - const guildId = bot.transformers.snowflake(payload.id); +export function transformGuild(bot: Bot, payload: Partial, extra?: { shardId?: number; partial?: boolean }) { const props = bot.transformers.desiredProperties.guild; const guild: SetupDesiredProps = Object.create(baseGuild); + const guildId = payload.id ? bot.transformers.snowflake(payload.id) : undefined; + if (props.afkTimeout && payload.afk_timeout) guild.afkTimeout = payload.afk_timeout; if (props.approximateMemberCount && payload.approximate_member_count) guild.approximateMemberCount = payload.approximate_member_count; if (props.approximatePresenceCount && payload.approximate_presence_count) guild.approximatePresenceCount = payload.approximate_presence_count; - if (props.defaultMessageNotifications) guild.defaultMessageNotifications = payload.default_message_notifications; + if (props.defaultMessageNotifications && payload.default_message_notifications !== undefined) + guild.defaultMessageNotifications = payload.default_message_notifications; if (props.description && payload.description) guild.description = payload.description; if (props.toggles) guild.toggles = new GuildToggles(payload); - if (props.explicitContentFilter) guild.explicitContentFilter = payload.explicit_content_filter; + if (props.explicitContentFilter && payload.explicit_content_filter !== undefined) guild.explicitContentFilter = payload.explicit_content_filter; if (props.maxMembers && payload.max_members) guild.maxMembers = payload.max_members; if (props.maxPresences && payload.max_presences) guild.maxPresences = payload.max_presences ?? undefined; if (props.maxVideoChannelUsers && payload.max_video_channel_users) guild.maxVideoChannelUsers = payload.max_video_channel_users; if (props.maxStageVideoChannelUsers && payload.max_stage_video_channel_users) guild.maxStageVideoChannelUsers = payload.max_stage_video_channel_users; - if (props.mfaLevel) guild.mfaLevel = payload.mfa_level; + if (props.mfaLevel && payload.mfa_level !== undefined) guild.mfaLevel = payload.mfa_level; if (props.name && payload.name) guild.name = payload.name; - if (props.nsfwLevel) guild.nsfwLevel = payload.nsfw_level; + if (props.nsfwLevel && payload.nsfw_level !== undefined) guild.nsfwLevel = payload.nsfw_level; if (props.preferredLocale && payload.preferred_locale) guild.preferredLocale = payload.preferred_locale; if (props.premiumSubscriptionCount && payload.premium_subscription_count !== undefined) guild.premiumSubscriptionCount = payload.premium_subscription_count; - if (props.premiumTier) guild.premiumTier = payload.premium_tier; + if (props.premiumTier && payload.premium_tier !== undefined) guild.premiumTier = payload.premium_tier; if (props.stageInstances && payload.stage_instances) guild.stageInstances = payload.stage_instances.map((si) => ({ /** The id of this Stage instance */ @@ -62,54 +65,48 @@ export function transformGuild(bot: Bot, payload: DiscordGuild, extra?: { shardI guild.channels = new Collection( [...(payload.channels ?? []), ...(payload.threads ?? [])].map((channel) => { const result = bot.transformers.channel(bot, channel, { guildId }); - // TODO: We should check that id exists, or else the collection will have undefined as it's key (This is valid for all the collections below as well) - // @ts-expect-error: See TODO above - return [result.id, result]; + return [bot.transformers.snowflake(channel.id), result]; }), ); if (props.members && payload.members) guild.members = new Collection( payload.members.map((member) => { - const result = bot.transformers.member(bot, member, { guildId, userId: bot.transformers.snowflake(member.user!.id) }); - // @ts-expect-error: See TODO above - return [result.id, result]; + const userId = bot.transformers.snowflake(member.user.id); + const result = bot.transformers.member(bot, member, { guildId, userId }); + return [userId, result]; }), ); if (props.roles && payload.roles) guild.roles = new Collection( payload.roles.map((role) => { const result = bot.transformers.role(bot, role, { guildId }); - // @ts-expect-error: See TODO above - return [result.id, result]; + return [bot.transformers.snowflake(role.id), result]; }), ); if (props.emojis && payload.emojis) guild.emojis = new Collection( payload.emojis.map((emoji) => { const result = bot.transformers.emoji(bot, emoji); - // @ts-expect-error: See TODO above - return [result.id!, result]; + return [bot.transformers.snowflake(emoji.id!), result]; }), ); if (props.voiceStates && payload.voice_states) guild.voiceStates = new Collection( payload.voice_states.map((voiceState) => { const result = bot.transformers.voiceState(bot, voiceState, { guildId }); - // @ts-expect-error: See TODO above - return [result.userId, result]; + return [bot.transformers.snowflake(voiceState.user_id), result]; }), ); if (props.stickers && payload.stickers) guild.stickers = new Collection( payload.stickers?.map((sticker) => { const result = bot.transformers.sticker(bot, sticker); - // @ts-expect-error: See TODO above - return [result.id, result]; + return [bot.transformers.snowflake(sticker.id), result]; }), ); if (props.systemChannelFlags && payload.system_channel_flags) guild.systemChannelFlags = payload.system_channel_flags; if (props.vanityUrlCode && payload.vanity_url_code) guild.vanityUrlCode = payload.vanity_url_code; - if (props.verificationLevel) guild.verificationLevel = payload.verification_level; + if (props.verificationLevel && payload.verification_level !== undefined) guild.verificationLevel = payload.verification_level; if (props.welcomeScreen && payload.welcome_screen) guild.welcomeScreen = { description: payload.welcome_screen.description ?? undefined, @@ -127,7 +124,7 @@ export function transformGuild(bot: Bot, payload: DiscordGuild, extra?: { shardI if (props.icon && payload.icon) guild.icon = iconHashToBigInt(payload.icon); if (props.banner && payload.banner) guild.banner = iconHashToBigInt(payload.banner); if (props.splash && payload.splash) guild.splash = iconHashToBigInt(payload.splash); - if (props.id && payload.id) guild.id = guildId; + if (props.id && guildId) guild.id = guildId; if (props.ownerId && payload.owner_id) guild.ownerId = bot.transformers.snowflake(payload.owner_id); if (props.permissions && payload.permissions) guild.permissions = bot.transformers.snowflake(payload.permissions); if (props.afkChannelId && payload.afk_channel_id) guild.afkChannelId = bot.transformers.snowflake(payload.afk_channel_id); @@ -144,10 +141,13 @@ export function transformGuild(bot: Bot, payload: DiscordGuild, extra?: { shardI if (props.unavailable && payload.unavailable) guild.unavailable = payload.unavailable; if (props.iconHash && payload.icon_hash) guild.iconHash = iconHashToBigInt(payload.icon_hash); if (props.presences && payload.presences) - guild.presences = payload.presences?.map((presence) => bot.transformers.presence(bot, presence as DiscordPresenceUpdate)); + guild.presences = payload.presences?.map((presence) => bot.transformers.presence(bot, presence, { partial: true })); if (props.safetyAlertsChannelId && payload.safety_alerts_channel_id) guild.safetyAlertsChannelId = bot.transformers.snowflake(payload.safety_alerts_channel_id); if (props.incidentsData && payload.incidents_data) guild.incidentsData = bot.transformers.incidentsData(bot, payload.incidents_data); - return bot.transformers.customizers.guild(bot, payload, guild, extra); + return callCustomizer('guild', bot, payload, guild, { + shardId: extra?.shardId, + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/incidentsData.ts b/packages/bot/src/transformers/incidentsData.ts index 4c1a1952d..2d07ff34c 100644 --- a/packages/bot/src/transformers/incidentsData.ts +++ b/packages/bot/src/transformers/incidentsData.ts @@ -1,9 +1,10 @@ import type { DiscordIncidentsData } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { IncidentsData } from './types.js'; -export function transformIncidentsData(bot: Bot, payload: DiscordIncidentsData): IncidentsData { +export function transformIncidentsData(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.incidentsData; const incidentsData = {} as SetupDesiredProps; @@ -12,5 +13,7 @@ export function transformIncidentsData(bot: Bot, payload: DiscordIncidentsData): if (props.dmSpamDetectedAt && payload.dm_spam_detected_at) incidentsData.dmSpamDetectedAt = Date.parse(payload.dm_spam_detected_at); if (props.raidDetectedAt && payload.raid_detected_at) incidentsData.raidDetectedAt = Date.parse(payload.raid_detected_at); - return bot.transformers.customizers.incidentsData(bot, payload, incidentsData); + return callCustomizer('incidentsData', bot, payload, incidentsData, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/integration.ts b/packages/bot/src/transformers/integration.ts index 2c9468201..6c3046ef7 100644 --- a/packages/bot/src/transformers/integration.ts +++ b/packages/bot/src/transformers/integration.ts @@ -3,7 +3,7 @@ import { iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { Integration } from './types.js'; -export function transformIntegration(bot: Bot, payload: DiscordIntegrationCreateUpdate): Integration { +export function transformIntegration(bot: Bot, payload: DiscordIntegrationCreateUpdate) { const integration = { guildId: bot.transformers.snowflake(payload.guild_id), id: bot.transformers.snowflake(payload.id), diff --git a/packages/bot/src/transformers/interaction.ts b/packages/bot/src/transformers/interaction.ts index 0aed2e0ef..60471ee64 100644 --- a/packages/bot/src/transformers/interaction.ts +++ b/packages/bot/src/transformers/interaction.ts @@ -14,13 +14,8 @@ import { import { Collection } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { InteractionResolvedDataChannel } from '../commandOptionsParser.js'; -import type { - CompleteDesiredProperties, - DesiredPropertiesBehavior, - SetupDesiredProps, - TransformersDesiredProperties, - TransformProperty, -} from '../desiredProperties.js'; +import type { CompleteDesiredProperties, DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { Interaction, InteractionCallback, @@ -144,12 +139,11 @@ export const baseInteraction: SetupDesiredProps = Object.create(baseInteraction); +export function transformInteraction(bot: Bot, payload: Partial, extra?: { shardId?: number; partial?: boolean }) { const props = bot.transformers.desiredProperties.interaction; + const interaction: SetupDesiredProps = Object.create(baseInteraction); + const guildId = payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined; interaction.bot = bot; interaction.acknowledged = false; @@ -160,9 +154,7 @@ export function transformInteraction(bot: Bot, payload: DiscordInteraction, extr if (props.version && payload.version) interaction.version = payload.version; if (props.locale && payload.locale) interaction.locale = payload.locale; if (props.guildLocale && payload.guild_locale) interaction.guildLocale = payload.guild_locale; - if (props.guild && payload.guild) - // @ts-expect-error payload.guild is a Partial - interaction.guild = bot.transformers.guild(bot, payload.guild, { shardId: extra?.shardId }); + if (props.guild && payload.guild) interaction.guild = bot.transformers.guild(bot, payload.guild, { shardId: extra?.shardId, partial: true }); if (props.guildId && guildId) interaction.guildId = guildId; if (props.user) { if (payload.member?.user) interaction.user = bot.transformers.user(bot, payload.member?.user); @@ -170,9 +162,7 @@ export function transformInteraction(bot: Bot, payload: DiscordInteraction, extr } if (props.appPermissions && payload.app_permissions) interaction.appPermissions = bot.transformers.snowflake(payload.app_permissions); if (props.message && payload.message) interaction.message = bot.transformers.message(bot, payload.message, { shardId: extra?.shardId }); - if (props.channel && payload.channel) - // @ts-expect-error payload.channel is a Partial<> - interaction.channel = bot.transformers.channel(bot, payload.channel, { guildId }); + if (props.channel && payload.channel) interaction.channel = bot.transformers.channel(bot, payload.channel, { guildId, partial: true }); if (props.channelId && payload.channel_id) interaction.channelId = bot.transformers.snowflake(payload.channel_id); if (props.member && guildId && payload.member) interaction.member = bot.transformers.member(bot, payload.member, { @@ -211,11 +201,13 @@ export function transformInteraction(bot: Bot, payload: DiscordInteraction, extr }; } - // Typescript has an hard time with interaction.bot, so we need to tell him for sure this interaction is the of the correct type - return bot.transformers.customizers.interaction(bot, payload, interaction as unknown as typeof bot.transformers.$inferredTypes.interaction, extra); + return callCustomizer('interaction', bot, payload, interaction, { + shardId: extra?.shardId, + partial: extra?.partial ?? false, + }); } -export function transformInteractionDataOption(bot: Bot, option: DiscordInteractionDataOption): InteractionDataOption { +export function transformInteractionDataOption(bot: Bot, option: DiscordInteractionDataOption) { const opt = { name: option.name, type: option.type, @@ -229,16 +221,15 @@ export function transformInteractionDataOption(bot: Bot, option: DiscordInteract export function transformInteractionDataResolved( bot: Bot, - payload: DiscordInteractionDataResolved, - extra?: { shardId?: number; guildId?: BigString }, -): TransformProperty { - const transformed: TransformProperty = {}; + payload: Partial, + extra?: { shardId?: number; guildId?: BigString; partial?: boolean }, +) { + const transformed: SetupDesiredProps = {}; if (payload.messages) { transformed.messages = new Collection( Object.entries(payload.messages).map(([key, value]) => { - // @ts-expect-error TODO: Deal with partials - const message = bot.transformers.message(bot, value, { shardId: extra?.shardId }); + const message = bot.transformers.message(bot, value, { shardId: extra?.shardId, partial: true }); const id = bot.transformers.snowflake(key); return [id, message]; @@ -260,10 +251,10 @@ export function transformInteractionDataResolved( if (extra?.guildId && payload.members) { transformed.members = new Collection( Object.entries(payload.members).map(([key, value]) => { - // @ts-expect-error TODO: Deal with partials, value is missing 2 values but the transformer can handle it, despite what the types says const member = bot.transformers.member(bot, value, { guildId: extra.guildId, userId: bot.transformers.snowflake(key), + partial: true, }); const id = bot.transformers.snowflake(key); @@ -286,13 +277,10 @@ export function transformInteractionDataResolved( if (payload.channels) { transformed.channels = new Collection( Object.entries(payload.channels).map(([key, value]) => { - const channel = bot.transformers.channel(bot, value) as InteractionResolvedDataChannel< - TransformersDesiredProperties, - DesiredPropertiesBehavior.RemoveKey - >; + const channel = bot.transformers.channel(bot, value, { partial: true }); const id = bot.transformers.snowflake(key); - return [id, channel]; + return [id, channel as InteractionResolvedDataChannel]; }), ); } @@ -308,17 +296,18 @@ export function transformInteractionDataResolved( ); } - return bot.transformers.customizers.interactionDataResolved(bot, payload, transformed, { + return callCustomizer('interactionDataResolved', bot, payload, transformed, { shardId: extra?.shardId, guildId: extra?.guildId ? bot.transformers.snowflake(extra.guildId) : undefined, + partial: extra?.partial ?? false, }); } export function transformInteractionCallbackResponse( bot: Bot, - payload: DiscordInteractionCallbackResponse, - extra?: { shardId?: number }, -): InteractionCallbackResponse { + payload: Partial, + extra?: { shardId?: number; partial?: boolean }, +) { const props = bot.transformers.desiredProperties.interactionCallbackResponse; const response = {} as SetupDesiredProps; @@ -326,10 +315,13 @@ export function transformInteractionCallbackResponse( if (props.resource && payload.resource) response.resource = bot.transformers.interactionResource(bot, payload.resource, { shardId: extra?.shardId }); - return bot.transformers.customizers.interactionCallbackResponse(bot, payload, response, extra); + return callCustomizer('interactionCallbackResponse', bot, payload, response, { + shardId: extra?.shardId, + partial: extra?.partial ?? false, + }); } -export function transformInteractionCallback(bot: Bot, payload: DiscordInteractionCallback): InteractionCallback { +export function transformInteractionCallback(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.interactionCallback; const callback = {} as SetupDesiredProps; @@ -340,10 +332,16 @@ export function transformInteractionCallback(bot: Bot, payload: DiscordInteracti if (props.responseMessageEphemeral && payload.response_message_ephemeral) callback.responseMessageEphemeral = payload.response_message_ephemeral; if (props.responseMessageLoading && payload.response_message_loading) callback.responseMessageLoading = payload.response_message_loading; - return bot.transformers.customizers.interactionCallback(bot, payload, callback); + return callCustomizer('interactionCallback', bot, payload, callback, { + partial: extra?.partial ?? false, + }); } -export function transformInteractionResource(bot: Bot, payload: DiscordInteractionResource, extra?: { shardId?: number }): InteractionResource { +export function transformInteractionResource( + bot: Bot, + payload: Partial, + extra?: { shardId?: number; partial?: boolean }, +) { const props = bot.transformers.desiredProperties.interactionResource; const resource = {} as SetupDesiredProps; @@ -351,5 +349,8 @@ export function transformInteractionResource(bot: Bot, payload: DiscordInteracti if (props.activityInstance && payload.activity_instance) resource.activityInstance = payload.activity_instance; if (props.message && payload.message) resource.message = bot.transformers.message(bot, payload.message, { shardId: extra?.shardId }); - return bot.transformers.customizers.interactionResource(bot, payload, resource, extra); + return callCustomizer('interactionResource', bot, payload, resource, { + shardId: extra?.shardId, + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/invite.ts b/packages/bot/src/transformers/invite.ts index 63bbf1486..2e3a3eb3e 100644 --- a/packages/bot/src/transformers/invite.ts +++ b/packages/bot/src/transformers/invite.ts @@ -1,46 +1,42 @@ -import type { DiscordInviteCreate, DiscordInviteMetadata } from '@discordeno/types'; -import { isInviteWithMetadata } from '@discordeno/utils'; +import type { DiscordInviteMetadata } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { ToggleBitfield } from './toggles/ToggleBitfield.js'; import type { Invite } from './types.js'; -export function transformInvite(bot: Bot, payload: DiscordInviteCreate | DiscordInviteMetadata, extra?: { shardId?: number }): Invite { +export function transformInvite(bot: Bot, payload: Partial, extra?: { shardId?: number; partial?: boolean }) { const props = bot.transformers.desiredProperties.invite; const invite = {} as SetupDesiredProps; - if (props.type && 'type' in payload) invite.type = payload.type; + if (props.type && payload.type !== undefined) invite.type = payload.type; if (props.code && payload.code) invite.code = payload.code; if (props.createdAt && payload.created_at) invite.createdAt = Date.parse(payload.created_at); if (props.inviter && payload.inviter) invite.inviter = bot.transformers.user(bot, payload.inviter); - if (props.maxAge) invite.maxAge = payload.max_age; - if (props.maxUses) invite.maxUses = payload.max_uses; + if (props.maxAge && payload.max_age !== undefined) invite.maxAge = payload.max_age; + if (props.maxUses && payload.max_uses !== undefined) invite.maxUses = payload.max_uses; if (props.targetType && payload.target_type) invite.targetType = payload.target_type; if (props.targetUser && payload.target_user) invite.targetUser = bot.transformers.user(bot, payload.target_user); if (props.targetApplication && payload.target_application) - // @ts-expect-error TODO: Partials - invite.targetApplication = bot.transformers.application(bot, payload.target_application, { shardId: extra?.shardId }); + invite.targetApplication = bot.transformers.application(bot, payload.target_application, { shardId: extra?.shardId, partial: true }); if (props.temporary && payload.temporary) invite.temporary = payload.temporary; - if (props.uses && payload.uses) invite.uses = payload.uses; - - if (isInviteWithMetadata(payload)) { - if (props.channelId && payload.channel?.id) invite.channelId = bot.transformers.snowflake(payload.channel.id); - if (props.guildId && payload.guild?.id) invite.guildId = bot.transformers.snowflake(payload.guild.id); - if (props.approximateMemberCount && payload.approximate_member_count) invite.approximateMemberCount = payload.approximate_member_count; - if (props.approximatePresenceCount && payload.approximate_presence_count !== undefined) - invite.approximatePresenceCount = payload.approximate_presence_count; - if (props.guildScheduledEvent && payload.guild_scheduled_event) - invite.guildScheduledEvent = bot.transformers.scheduledEvent(bot, payload.guild_scheduled_event); - if (props.expiresAt && payload.expires_at) { - invite.expiresAt = Date.parse(payload.expires_at); - } - if (props.flags && payload.flags) invite.flags = new ToggleBitfield(payload.flags); - // @ts-expect-error TODO: Partials - if (props.roles && payload.roles) invite.roles = payload.roles.map((role) => bot.transformers.role(bot, role)); - } else { - if (props.channelId && payload.channel_id) invite.channelId = bot.transformers.snowflake(payload.channel_id); - if (props.guildId && payload.guild_id) invite.guildId = bot.transformers.snowflake(payload.guild_id); + if (props.uses && payload.uses !== undefined) invite.uses = payload.uses; + if (props.channelId && payload.channel?.id) invite.channelId = bot.transformers.snowflake(payload.channel.id); + if (props.guildId && payload.guild?.id) invite.guildId = bot.transformers.snowflake(payload.guild.id); + if (props.approximateMemberCount && payload.approximate_member_count) invite.approximateMemberCount = payload.approximate_member_count; + if (props.approximatePresenceCount && payload.approximate_presence_count !== undefined) + invite.approximatePresenceCount = payload.approximate_presence_count; + if (props.guildScheduledEvent && payload.guild_scheduled_event) + invite.guildScheduledEvent = bot.transformers.scheduledEvent(bot, payload.guild_scheduled_event); + if (props.expiresAt && payload.expires_at) invite.expiresAt = Date.parse(payload.expires_at); + if (props.flags && payload.flags) invite.flags = new ToggleBitfield(payload.flags); + if (props.roles && payload.roles) { + // Going from a Partial To Pick requires an as, as typescript can't guarantee that the properties exist on the partial + invite.roles = payload.roles.map((role) => bot.transformers.role(bot, role, { partial: true, guildId: payload.guild?.id })) as Invite['roles']; } - return bot.transformers.customizers.invite(bot, payload, invite, extra); + return callCustomizer('invite', bot, payload, invite, { + shardId: extra?.shardId, + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/lobby.ts b/packages/bot/src/transformers/lobby.ts index fdf7d03ba..dafbbc9c4 100644 --- a/packages/bot/src/transformers/lobby.ts +++ b/packages/bot/src/transformers/lobby.ts @@ -1,10 +1,11 @@ import type { DiscordLobby, DiscordLobbyInvite, DiscordLobbyMember, DiscordLobbyMessage } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { ToggleBitfield } from './toggles/ToggleBitfield.js'; import type { Lobby, LobbyInvite, LobbyMember, LobbyMessage } from './types.js'; -export function transformLobby(bot: Bot, payload: DiscordLobby): Lobby { +export function transformLobby(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.lobby; const lobby = {} as SetupDesiredProps; @@ -14,10 +15,12 @@ export function transformLobby(bot: Bot, payload: DiscordLobby): Lobby { if (props.members && payload.members) lobby.members = payload.members.map((member) => bot.transformers.lobbyMember(bot, member)); if (props.linkedChannel && payload.linked_channel) lobby.linkedChannel = bot.transformers.channel(bot, payload.linked_channel); - return bot.transformers.customizers.lobby(bot, payload, lobby); + return callCustomizer('lobby', bot, payload, lobby, { + partial: extra?.partial ?? false, + }); } -export function transformLobbyMember(bot: Bot, payload: DiscordLobbyMember): LobbyMember { +export function transformLobbyMember(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.lobbyMember; const lobbyMember = {} as SetupDesiredProps; @@ -25,15 +28,17 @@ export function transformLobbyMember(bot: Bot, payload: DiscordLobbyMember): Lob if (props.metadata && payload.metadata) lobbyMember.metadata = payload.metadata; if (props.flags && payload.flags) lobbyMember.flags = new ToggleBitfield(payload.flags); - return bot.transformers.customizers.lobbyMember(bot, payload, lobbyMember); + return callCustomizer('lobbyMember', bot, payload, lobbyMember, { + partial: extra?.partial ?? false, + }); } -export function transformLobbyMessage(bot: Bot, payload: DiscordLobbyMessage): LobbyMessage { +export function transformLobbyMessage(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.lobbyMessage; const lobbyMessage = {} as SetupDesiredProps; if (props.id && payload.id) lobbyMessage.id = bot.transformers.snowflake(payload.id); - if (props.type) lobbyMessage.type = payload.type; + if (props.type && payload.type !== undefined) lobbyMessage.type = payload.type; if (props.content && payload.content) lobbyMessage.content = payload.content; if (props.lobbyId && payload.lobby_id) lobbyMessage.lobbyId = bot.transformers.snowflake(payload.lobby_id); if (props.channelId && payload.channel_id) lobbyMessage.channelId = bot.transformers.snowflake(payload.channel_id); @@ -43,14 +48,18 @@ export function transformLobbyMessage(bot: Bot, payload: DiscordLobbyMessage): L if (props.flags && payload.flags) lobbyMessage.flags = new ToggleBitfield(payload.flags); if (props.applicationId && payload.application_id) lobbyMessage.applicationId = bot.transformers.snowflake(payload.application_id); - return bot.transformers.customizers.lobbyMessage(bot, payload, lobbyMessage); + return callCustomizer('lobbyMessage', bot, payload, lobbyMessage, { + partial: extra?.partial ?? false, + }); } -export function transformLobbyInvite(bot: Bot, payload: DiscordLobbyInvite): LobbyInvite { +export function transformLobbyInvite(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.lobbyInvite; const lobbyInvite = {} as SetupDesiredProps; if (props.code && payload.code) lobbyInvite.code = payload.code; - return bot.transformers.customizers.lobbyInvite(bot, payload, lobbyInvite); + return callCustomizer('lobbyInvite', bot, payload, lobbyInvite, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/member.ts b/packages/bot/src/transformers/member.ts index 9bc470d18..d26a24346 100644 --- a/packages/bot/src/transformers/member.ts +++ b/packages/bot/src/transformers/member.ts @@ -2,6 +2,7 @@ import type { BigString, DiscordMember } from '@discordeno/types'; import { iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { MemberToggles } from './toggles/member.js'; import { Permissions } from './toggles/Permissions.js'; import type { Member } from './types.js'; @@ -36,7 +37,7 @@ export const baseMember: Member = { }, }; -export function transformMember(bot: Bot, payload: DiscordMember, extra?: { guildId?: BigString; userId?: BigString }): Member { +export function transformMember(bot: Bot, payload: Partial, extra?: { guildId?: BigString; userId?: BigString; partial?: boolean }) { const member: SetupDesiredProps = Object.create(baseMember); const props = bot.transformers.desiredProperties.member; @@ -57,8 +58,9 @@ export function transformMember(bot: Bot, payload: DiscordMember, extra?: { guil member.avatarDecorationData = bot.transformers.avatarDecorationData(bot, payload.avatar_decoration_data); if (props.collectibles && payload.collectibles) member.collectibles = bot.transformers.collectibles(bot, payload.collectibles); - return bot.transformers.customizers.member(bot, payload, member, { + return callCustomizer('member', bot, payload, member, { guildId: extra?.guildId ? bot.transformers.snowflake(extra.guildId) : undefined, userId: extra?.userId ? bot.transformers.snowflake(extra.userId) : undefined, + partial: extra?.partial ?? false, }); } diff --git a/packages/bot/src/transformers/message.ts b/packages/bot/src/transformers/message.ts index 3f2d3ade2..23fbda2b2 100644 --- a/packages/bot/src/transformers/message.ts +++ b/packages/bot/src/transformers/message.ts @@ -12,6 +12,7 @@ import { snowflakeToTimestamp } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import { CHANNEL_MENTION_REGEX } from '../constants.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { ToggleBitfield } from './toggles/ToggleBitfield.js'; import type { Message, @@ -141,7 +142,7 @@ export const baseMessage: Message = { }, }; -export function transformMessage(bot: Bot, payload: DiscordMessage, extra?: { shardId?: number }): Message { +export function transformMessage(bot: Bot, payload: Partial, extra?: { shardId?: number; partial?: boolean }) { const guildId = payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined; const userId = payload.author?.id ? bot.transformers.snowflake(payload.author.id) : undefined; @@ -153,8 +154,7 @@ export function transformMessage(bot: Bot, payload: DiscordMessage, extra?: { sh if (props.author && payload.author) message.author = bot.transformers.user(bot, payload.author); if (props.application && payload.application) - // @ts-expect-error TODO: Partials - message.application = bot.transformers.application(bot, payload.application, { shardId: extra?.shardId }); + message.application = bot.transformers.application(bot, payload.application, { shardId: extra?.shardId, partial: true }); if (props.applicationId && payload.application_id) message.applicationId = bot.transformers.snowflake(payload.application_id); if (props.attachments && payload.attachments?.length) message.attachments = payload.attachments.map((attachment) => bot.transformers.attachment(bot, attachment)); @@ -175,8 +175,7 @@ export function transformMessage(bot: Bot, payload: DiscordMessage, extra?: { sh interaction.id = bot.transformers.snowflake(payload.interaction.id); } if (messageInteractionProps.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, userId: payload.interaction.user.id }); + interaction.member = bot.transformers.member(bot, payload.interaction.member, { guildId, userId: payload.interaction.user.id, partial: true }); } if (messageInteractionProps.name) { interaction.name = payload.interaction.name; @@ -191,8 +190,7 @@ export function transformMessage(bot: Bot, payload: DiscordMessage, extra?: { sh message.interaction = interaction; } if (props.member && guildId && userId && payload.member) - // @ts-expect-error TODO: partial - message.member = bot.transformers.member(bot, payload.member, { guildId, userId }); + message.member = bot.transformers.member(bot, payload.member, { guildId, userId, partial: true }); if (payload.mention_everyone) message.mentionEveryone = true; if (props.mentionedChannelIds && payload.mention_channels?.length) { message.mentionedChannelIds = [ @@ -240,8 +238,7 @@ export function transformMessage(bot: Bot, payload: DiscordMessage, extra?: { sh burst: reaction.count_details.burst, normal: reaction.count_details.normal, }, - // @ts-expect-error TODO: Deal with partials - emoji: bot.transformers.emoji(bot, reaction.emoji), + emoji: bot.transformers.emoji(bot, reaction.emoji, { partial: true }), burstColors: reaction.burst_colors, })); } @@ -253,41 +250,49 @@ export function transformMessage(bot: Bot, payload: DiscordMessage, extra?: { sh })); if (payload.tts) message.tts = true; if (props.thread && payload.thread) message.thread = bot.transformers.channel(bot, payload.thread, { guildId }); - if (props.type) message.type = payload.type; + if (props.type && payload.type !== undefined) message.type = payload.type; if (props.webhookId && payload.webhook_id) message.webhookId = bot.transformers.snowflake(payload.webhook_id); if (props.poll && payload.poll) message.poll = bot.transformers.poll(bot, payload.poll); if (props.call && payload.call) message.call = bot.transformers.messageCall(bot, payload.call); - return bot.transformers.customizers.message(bot, payload, message, extra); + return callCustomizer('message', bot, payload, message, { + shardId: extra?.shardId, + partial: extra?.partial ?? false, + }); } -export function transformMessagePin(bot: Bot, payload: DiscordMessagePin, extra?: { shardId?: number }): MessagePin { +export function transformMessagePin(bot: Bot, payload: Partial, extra?: { shardId?: number; partial?: boolean }) { const props = bot.transformers.desiredProperties.messagePin; const messagePin = {} as SetupDesiredProps; if (props.pinnedAt && payload.pinned_at) messagePin.pinnedAt = Date.parse(payload.pinned_at); if (props.message && payload.message) messagePin.message = bot.transformers.message(bot, payload.message, { shardId: extra?.shardId }); - return bot.transformers.customizers.messagePin(bot, payload, messagePin, extra); + return callCustomizer('messagePin', bot, payload, messagePin, { + shardId: extra?.shardId, + partial: extra?.partial ?? false, + }); } -export function transformMessageSnapshot(bot: Bot, payload: DiscordMessageSnapshot, extra?: { shardId?: number }): MessageSnapshot { +export function transformMessageSnapshot(bot: Bot, payload: Partial, extra?: { shardId?: number; partial?: boolean }) { const props = bot.transformers.desiredProperties.messageSnapshot; const messageSnapshot = {} as SetupDesiredProps; if (props.message && payload.message) - // @ts-expect-error TODO: Partials - messageSnapshot.message = bot.transformers.message(bot, payload.message, { shardId: extra?.shardId }) as Message; + messageSnapshot.message = bot.transformers.message(bot, payload.message, { shardId: extra?.shardId, partial: true }) as Message; - return bot.transformers.customizers.messageSnapshot(bot, payload, messageSnapshot, extra); + return callCustomizer('messageSnapshot', bot, payload, messageSnapshot, { + shardId: extra?.shardId, + partial: extra?.partial ?? false, + }); } -export function transformMessageInteractionMetadata(bot: Bot, payload: DiscordMessageInteractionMetadata): MessageInteractionMetadata { +export function transformMessageInteractionMetadata(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.messageInteractionMetadata; const metadata = {} as SetupDesiredProps; - if (props.id) metadata.id = bot.transformers.snowflake(payload.id); - if (props.authorizingIntegrationOwners) { + if (props.id && payload.id !== undefined) metadata.id = bot.transformers.snowflake(payload.id); + if (props.authorizingIntegrationOwners && payload.authorizing_integration_owners) { metadata.authorizingIntegrationOwners = {}; if (payload.authorizing_integration_owners['0']) metadata.authorizingIntegrationOwners[DiscordApplicationIntegrationType.GuildInstall] = bot.transformers.snowflake( @@ -300,7 +305,7 @@ export function transformMessageInteractionMetadata(bot: Bot, payload: DiscordMe } if (props.originalResponseMessageId && payload.original_response_message_id) metadata.originalResponseMessageId = bot.transformers.snowflake(payload.original_response_message_id); - if (props.type) metadata.type = payload.type; + if (props.type && payload.type !== undefined) metadata.type = payload.type; if (props.user && payload.user) metadata.user = bot.transformers.user(bot, payload.user); // Application command metadata if ('target_user' in payload) { @@ -318,27 +323,33 @@ export function transformMessageInteractionMetadata(bot: Bot, payload: DiscordMe metadata.triggeringInteractionMetadata = bot.transformers.messageInteractionMetadata(bot, payload.triggering_interaction_metadata); } - return bot.transformers.customizers.messageInteractionMetadata(bot, payload, metadata); + return callCustomizer('messageInteractionMetadata', bot, payload, metadata, { + partial: extra?.partial ?? false, + }); } -export function transformMessageCall(bot: Bot, payload: DiscordMessageCall): MessageCall { +export function transformMessageCall(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const call = {} as SetupDesiredProps; const props = bot.transformers.desiredProperties.messageCall; if (props.participants && payload.participants) call.participants = payload.participants.map((x) => bot.transformers.snowflake(x)); if (props.endedTimestamp && payload.ended_timestamp) call.endedTimestamp = Date.parse(payload.ended_timestamp); - return bot.transformers.customizers.messageCall(bot, payload, call); + return callCustomizer('messageCall', bot, payload, call, { + partial: extra?.partial ?? false, + }); } -export function transformSharedClientTheme(bot: Bot, payload: DiscordSharedClientTheme): SharedClientTheme { +export function transformSharedClientTheme(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.sharedClientTheme; const theme = {} as SetupDesiredProps; if (props.colors && payload.colors) theme.colors = payload.colors; - if (props.baseMix) theme.baseMix = payload.base_mix; - if (props.gradientAngle) theme.gradientAngle = payload.gradient_angle; + if (props.baseMix && payload.base_mix !== undefined) theme.baseMix = payload.base_mix; + if (props.gradientAngle && payload.gradient_angle !== undefined) theme.gradientAngle = payload.gradient_angle; if (props.baseTheme && payload.base_theme !== undefined && payload.base_theme !== null) theme.baseTheme = payload.base_theme; - return bot.transformers.customizers.sharedClientTheme(bot, payload, theme); + return callCustomizer('sharedClientTheme', bot, payload, theme, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/onboarding.ts b/packages/bot/src/transformers/onboarding.ts index 49fc3c98a..7a42c9432 100644 --- a/packages/bot/src/transformers/onboarding.ts +++ b/packages/bot/src/transformers/onboarding.ts @@ -1,24 +1,27 @@ import type { DiscordGuildOnboarding, DiscordGuildOnboardingPrompt, DiscordGuildOnboardingPromptOption } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { GuildOnboarding, GuildOnboardingPrompt, GuildOnboardingPromptOption } from './types.js'; -export function transformGuildOnboarding(bot: Bot, payload: DiscordGuildOnboarding): GuildOnboarding { +export function transformGuildOnboarding(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.guildOnboarding; const guildOnboarding = {} as SetupDesiredProps; if (props.guildId && payload.guild_id) guildOnboarding.guildId = bot.transformers.snowflake(payload.guild_id); if (props.defaultChannelIds && payload.default_channel_ids) guildOnboarding.defaultChannelIds = payload.default_channel_ids.map(bot.transformers.snowflake); - if (props.enabled) guildOnboarding.enabled = payload.enabled; - if (props.mode) guildOnboarding.mode = payload.mode; + if (props.enabled && payload.enabled !== undefined) guildOnboarding.enabled = payload.enabled; + if (props.mode && payload.mode !== undefined) guildOnboarding.mode = payload.mode; if (props.prompts && payload.prompts) guildOnboarding.prompts = payload.prompts.map((prompt) => bot.transformers.guildOnboardingPrompt(bot, prompt)); - return bot.transformers.customizers.guildOnboarding(bot, payload, guildOnboarding); + return callCustomizer('guildOnboarding', bot, payload, guildOnboarding, { + partial: extra?.partial ?? false, + }); } -export function transformGuildOnboardingPrompt(bot: Bot, payload: DiscordGuildOnboardingPrompt): GuildOnboardingPrompt { +export function transformGuildOnboardingPrompt(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.guildOnboardingPrompt; const prompt = {} as SetupDesiredProps; @@ -27,13 +30,15 @@ export function transformGuildOnboardingPrompt(bot: Bot, payload: DiscordGuildOn if (props.required && payload.required) prompt.required = payload.required; if (props.singleSelect && payload.single_select) prompt.singleSelect = payload.single_select; if (props.title && payload.title) prompt.title = payload.title; - if (props.type) prompt.type = payload.type; + if (props.type && payload.type !== undefined) prompt.type = payload.type; if (props.options && payload.options) prompt.options = payload.options.map((option) => bot.transformers.guildOnboardingPromptOption(bot, option)); - return bot.transformers.customizers.guildOnboardingPrompt(bot, payload, prompt); + return callCustomizer('guildOnboardingPrompt', bot, payload, prompt, { + partial: extra?.partial ?? false, + }); } -export function transformGuildOnboardingPromptOption(bot: Bot, payload: DiscordGuildOnboardingPromptOption): GuildOnboardingPromptOption { +export function transformGuildOnboardingPromptOption(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.guildOnboardingPromptOption; const option = {} as SetupDesiredProps; @@ -44,5 +49,7 @@ export function transformGuildOnboardingPromptOption(bot: Bot, payload: DiscordG if (props.title && payload.title) option.title = payload.title; if (props.description && payload.description) option.description = payload.description; - return bot.transformers.customizers.guildOnboardingPromptOption(bot, payload, option); + return callCustomizer('guildOnboardingPromptOption', bot, payload, option, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/poll.ts b/packages/bot/src/transformers/poll.ts index e48fd1062..0e605622d 100644 --- a/packages/bot/src/transformers/poll.ts +++ b/packages/bot/src/transformers/poll.ts @@ -1,9 +1,10 @@ -import type { DiscordEmoji, DiscordPoll, DiscordPollMedia } from '@discordeno/types'; +import type { DiscordPoll, DiscordPollMedia } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { Poll, PollMedia, PollResult } from './types.js'; -export function transformPoll(bot: Bot, payload: DiscordPoll): Poll { +export function transformPoll(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.poll; const poll = {} as SetupDesiredProps; @@ -12,7 +13,7 @@ export function transformPoll(bot: Bot, payload: DiscordPoll): Poll { poll.answers = payload.answers.map((x) => ({ answerId: x.answer_id, pollMedia: bot.transformers.pollMedia(bot, x.poll_media) })); if (props.expiry && payload.expiry) poll.expiry = Date.parse(payload.expiry); if (props.allowMultiselect && payload.allow_multiselect) poll.allowMultiselect = payload.allow_multiselect; - if (props.layoutType) poll.layoutType = payload.layout_type; + if (props.layoutType && payload.layout_type !== undefined) poll.layoutType = payload.layout_type; if (props.results && payload.results) { const results = {} as SetupDesiredProps; const pollResultProps = bot.transformers.desiredProperties.pollResult; @@ -24,15 +25,19 @@ export function transformPoll(bot: Bot, payload: DiscordPoll): Poll { poll.results = results; } - return bot.transformers.customizers.poll(bot, payload, poll); + return callCustomizer('poll', bot, payload, poll, { + partial: extra?.partial ?? false, + }); } -export function transformPollMedia(bot: Bot, payload: DiscordPollMedia): PollMedia { +export function transformPollMedia(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.pollMedia; const pollMedia = {} as SetupDesiredProps; if (props.text && payload.text) pollMedia.text = payload.text; - if (props.emoji && payload.emoji) pollMedia.emoji = bot.transformers.emoji(bot, payload.emoji as DiscordEmoji); + if (props.emoji && payload.emoji) pollMedia.emoji = bot.transformers.emoji(bot, payload.emoji, { partial: true }); - return bot.transformers.customizers.pollMedia(bot, payload, pollMedia); + return callCustomizer('pollMedia', bot, payload, pollMedia, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/presence.ts b/packages/bot/src/transformers/presence.ts index e24cf284f..eafe3916f 100644 --- a/packages/bot/src/transformers/presence.ts +++ b/packages/bot/src/transformers/presence.ts @@ -1,18 +1,21 @@ import { type DiscordPresenceUpdate, PresenceStatus } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { PresenceUpdate, User } from './types.js'; -export function transformPresence(bot: Bot, payload: DiscordPresenceUpdate): PresenceUpdate { +export function transformPresence(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const presence = {} as SetupDesiredProps; if (payload.user) presence.user = bot.transformers.user(bot, payload.user) as User; if (payload.guild_id) presence.guildId = bot.transformers.snowflake(payload.guild_id); if (payload.status) presence.status = PresenceStatus[payload.status]; if (payload.activities) presence.activities = payload.activities.map((activity) => bot.transformers.activity(bot, activity)); - if (payload.client_status.desktop) presence.desktop = payload.client_status.desktop; - if (payload.client_status.mobile) presence.mobile = payload.client_status.mobile; - if (payload.client_status.web) presence.web = payload.client_status.web; + if (payload.client_status?.desktop) presence.desktop = payload.client_status.desktop; + if (payload.client_status?.mobile) presence.mobile = payload.client_status.mobile; + if (payload.client_status?.web) presence.web = payload.client_status.web; - return bot.transformers.customizers.presence(bot, payload, presence); + return callCustomizer('presence', bot, payload, presence, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/role.ts b/packages/bot/src/transformers/role.ts index 15af61ff8..8f7e84f83 100644 --- a/packages/bot/src/transformers/role.ts +++ b/packages/bot/src/transformers/role.ts @@ -2,6 +2,7 @@ import type { BigString, DiscordRole, DiscordRoleColors } from '@discordeno/type import { iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { Permissions } from './toggles/Permissions.js'; import { RoleToggles } from './toggles/role.js'; import type { Role, RoleColors } from './types.js'; @@ -46,20 +47,20 @@ export const baseRole: Role = { }, }; -export function transformRole(bot: Bot, payload: DiscordRole, extra?: { guildId?: BigString }): Role { +export function transformRole(bot: Bot, payload: Partial, extra?: { guildId?: BigString; partial?: boolean }) { const role: SetupDesiredProps = Object.create(baseRole); const props = bot.transformers.desiredProperties.role; if (props.id && payload.id) role.id = bot.transformers.snowflake(payload.id); // Role name can be an empty string if (props.name && payload.name !== undefined) role.name = payload.name; - if (props.position) role.position = payload.position; + if (props.position && payload.position !== undefined) role.position = payload.position; if (props.guildId && extra?.guildId) role.guildId = bot.transformers.snowflake(extra?.guildId); if (props.color && payload.color !== undefined) role.color = payload.color; if (props.colors && payload.colors) role.colors = bot.transformers.roleColors(bot, payload.colors); if (props.permissions && payload.permissions) role.permissions = new Permissions(payload.permissions); if (props.icon && payload.icon) role.icon = iconHashToBigInt(payload.icon); if (props.unicodeEmoji && payload.unicode_emoji) role.unicodeEmoji = payload.unicode_emoji; - if (props.flags) role.flags = payload.flags; + if (props.flags && payload.flags !== undefined) role.flags = payload.flags; if (props.tags && payload.tags) { role.internalTags = {}; if (payload.tags.bot_id) role.internalTags.botId = bot.transformers.snowflake(payload.tags.bot_id); @@ -69,12 +70,13 @@ export function transformRole(bot: Bot, payload: DiscordRole, extra?: { guildId? } if (props.toggles) role.toggles = new RoleToggles(payload); - return bot.transformers.customizers.role(bot, payload, role, { + return callCustomizer('role', bot, payload, role, { guildId: extra?.guildId ? bot.transformers.snowflake(extra.guildId) : undefined, + partial: extra?.partial ?? false, }); } -export function transformRoleColors(bot: Bot, payload: DiscordRoleColors): RoleColors { +export function transformRoleColors(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const roleColors = {} as SetupDesiredProps; const props = bot.transformers.desiredProperties.roleColors; @@ -84,5 +86,7 @@ export function transformRoleColors(bot: Bot, payload: DiscordRoleColors): RoleC if (props.tertiaryColor && payload.tertiary_color !== undefined && payload.tertiary_color !== null) roleColors.tertiaryColor = payload.tertiary_color; - return bot.transformers.customizers.roleColors(bot, payload, roleColors); + return callCustomizer('roleColors', bot, payload, roleColors, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/scheduledEvent.ts b/packages/bot/src/transformers/scheduledEvent.ts index 9f9ecc808..71bfb390b 100644 --- a/packages/bot/src/transformers/scheduledEvent.ts +++ b/packages/bot/src/transformers/scheduledEvent.ts @@ -2,9 +2,10 @@ import type { DiscordScheduledEvent, DiscordScheduledEventRecurrenceRule } from import { iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { ScheduledEvent, ScheduledEventRecurrenceRule } from './types.js'; -export function transformScheduledEvent(bot: Bot, payload: DiscordScheduledEvent): ScheduledEvent { +export function transformScheduledEvent(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.scheduledEvent; const scheduledEvent = {} as SetupDesiredProps; @@ -27,10 +28,16 @@ export function transformScheduledEvent(bot: Bot, payload: DiscordScheduledEvent if (props.recurrenceRule && payload.recurrence_rule) scheduledEvent.recurrenceRule = bot.transformers.scheduledEventRecurrenceRule(bot, payload.recurrence_rule); - return bot.transformers.customizers.scheduledEvent(bot, payload, scheduledEvent); + return callCustomizer('scheduledEvent', bot, payload, scheduledEvent, { + partial: extra?.partial ?? false, + }); } -export function transformScheduledEventRecurrenceRule(bot: Bot, payload: DiscordScheduledEventRecurrenceRule): ScheduledEventRecurrenceRule { +export function transformScheduledEventRecurrenceRule( + bot: Bot, + payload: Partial, + extra?: { partial?: boolean }, +) { const props = bot.transformers.desiredProperties.scheduledEventRecurrenceRule; const recurrenceRule = {} as SetupDesiredProps; @@ -45,5 +52,7 @@ export function transformScheduledEventRecurrenceRule(bot: Bot, payload: Discord if (props.byYearDay && payload.by_year_day) recurrenceRule.byYearDay = payload.by_year_day; if (props.count && payload.count) recurrenceRule.count = payload.count; - return bot.transformers.customizers.scheduledEventRecurrenceRule(bot, payload, recurrenceRule); + return callCustomizer('scheduledEventRecurrenceRule', bot, payload, recurrenceRule, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/sku.ts b/packages/bot/src/transformers/sku.ts index 03e3a8a49..1571975e7 100644 --- a/packages/bot/src/transformers/sku.ts +++ b/packages/bot/src/transformers/sku.ts @@ -1,9 +1,10 @@ import type { DiscordSku } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { Sku } from './types.js'; -export function transformSku(bot: Bot, payload: DiscordSku): Sku { +export function transformSku(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.sku; const sku = {} as SetupDesiredProps; @@ -14,5 +15,7 @@ export function transformSku(bot: Bot, payload: DiscordSku): Sku { if (props.slug && payload.slug) sku.slug = payload.slug; if (props.flags && payload.flags) sku.flags = payload.flags; - return bot.transformers.customizers.sku(bot, payload, sku); + return callCustomizer('sku', bot, payload, sku, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/soundboardSound.ts b/packages/bot/src/transformers/soundboardSound.ts index 74d4537c3..9184f9bae 100644 --- a/packages/bot/src/transformers/soundboardSound.ts +++ b/packages/bot/src/transformers/soundboardSound.ts @@ -1,9 +1,10 @@ import type { DiscordSoundboardSound } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { SoundboardSound } from './types.js'; -export function transformSoundboardSound(bot: Bot, payload: DiscordSoundboardSound): SoundboardSound { +export function transformSoundboardSound(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.soundboardSound; const soundboardSound = {} as SetupDesiredProps; @@ -16,5 +17,7 @@ export function transformSoundboardSound(bot: Bot, payload: DiscordSoundboardSou if (props.available && payload.available) soundboardSound.available = payload.available; if (props.user && payload.user) soundboardSound.user = bot.transformers.user(bot, payload.user); - return bot.transformers.customizers.soundboardSound(bot, payload, soundboardSound); + return callCustomizer('soundboardSound', bot, payload, soundboardSound, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/stageInstance.ts b/packages/bot/src/transformers/stageInstance.ts index 296294d99..9e829f128 100644 --- a/packages/bot/src/transformers/stageInstance.ts +++ b/packages/bot/src/transformers/stageInstance.ts @@ -1,9 +1,10 @@ import type { DiscordStageInstance } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { StageInstance } from './types.js'; -export function transformStageInstance(bot: Bot, payload: DiscordStageInstance): StageInstance { +export function transformStageInstance(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.stageInstance; const stageInstance = {} as SetupDesiredProps; @@ -14,5 +15,7 @@ export function transformStageInstance(bot: Bot, payload: DiscordStageInstance): if (props.guildScheduledEventId && payload.guild_scheduled_event_id) stageInstance.guildScheduledEventId = bot.transformers.snowflake(payload.guild_scheduled_event_id); - return bot.transformers.customizers.stageInstance(bot, payload, stageInstance); + return callCustomizer('stageInstance', bot, payload, stageInstance, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/stageInviteInstance.ts b/packages/bot/src/transformers/stageInviteInstance.ts index 170944705..e3f999363 100644 --- a/packages/bot/src/transformers/stageInviteInstance.ts +++ b/packages/bot/src/transformers/stageInviteInstance.ts @@ -1,26 +1,32 @@ import type { BigString, DiscordInviteStageInstance } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { InviteStageInstance } from './types.js'; -export function transformInviteStageInstance(bot: Bot, payload: DiscordInviteStageInstance, extra?: { guildId?: BigString }): InviteStageInstance { +export function transformInviteStageInstance( + bot: Bot, + payload: Partial, + extra?: { guildId?: BigString; partial?: boolean }, +) { const props = bot.transformers.desiredProperties.inviteStageInstance; const inviteStageInstance = {} as SetupDesiredProps; if (props.members && payload.members) { inviteStageInstance.members = payload.members.map((member) => - // @ts-expect-error TODO: Partials bot.transformers.member(bot, member, { guildId: extra?.guildId, userId: member.user?.id, + partial: true, }), ); } - if (props.participantCount) inviteStageInstance.participantCount = payload.participant_count; - if (props.speakerCount) inviteStageInstance.participantCount = payload.participant_count; + if (props.participantCount && payload.participant_count !== undefined) inviteStageInstance.participantCount = payload.participant_count; + if (props.speakerCount && payload.speaker_count !== undefined) inviteStageInstance.speakerCount = payload.speaker_count; if (props.topic && payload.topic) inviteStageInstance.topic = payload.topic; - return bot.transformers.customizers.inviteStageInstance(bot, payload, inviteStageInstance, { + return callCustomizer('inviteStageInstance', bot, payload, inviteStageInstance, { guildId: extra?.guildId ? bot.transformers.snowflake(extra.guildId) : undefined, + partial: extra?.partial ?? false, }); } diff --git a/packages/bot/src/transformers/sticker.ts b/packages/bot/src/transformers/sticker.ts index 7e8ecc0e0..8c8cff9e2 100644 --- a/packages/bot/src/transformers/sticker.ts +++ b/packages/bot/src/transformers/sticker.ts @@ -1,9 +1,10 @@ import type { DiscordSticker, DiscordStickerPack } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { Sticker, StickerPack } from './types.js'; -export function transformSticker(bot: Bot, payload: DiscordSticker): Sticker { +export function transformSticker(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.sticker; const sticker = {} as SetupDesiredProps; @@ -19,10 +20,12 @@ export function transformSticker(bot: Bot, payload: DiscordSticker): Sticker { if (props.user && payload.user) sticker.user = bot.transformers.user(bot, payload.user); if (props.sortValue && payload.sort_value !== undefined) sticker.sortValue = payload.sort_value; - return bot.transformers.customizers.sticker(bot, payload, sticker); + return callCustomizer('sticker', bot, payload, sticker, { + partial: extra?.partial ?? false, + }); } -export function transformStickerPack(bot: Bot, payload: DiscordStickerPack): StickerPack { +export function transformStickerPack(bot: Bot, payload: DiscordStickerPack) { const pack = { id: bot.transformers.snowflake(payload.id), stickers: payload.stickers.map((sticker) => bot.transformers.sticker(bot, sticker)), diff --git a/packages/bot/src/transformers/subscription.ts b/packages/bot/src/transformers/subscription.ts index c81d8fa22..c633f7051 100644 --- a/packages/bot/src/transformers/subscription.ts +++ b/packages/bot/src/transformers/subscription.ts @@ -1,9 +1,10 @@ import type { DiscordSubscription } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { Subscription } from './types.js'; -export function transformSubscription(bot: Bot, payload: DiscordSubscription): Subscription { +export function transformSubscription(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.subscription; const subscription = {} as SetupDesiredProps; @@ -20,5 +21,7 @@ export function transformSubscription(bot: Bot, payload: DiscordSubscription): S if (props.canceledAt && payload.canceled_at) subscription.canceledAt = Date.parse(payload.canceled_at); if (props.country && payload.country) subscription.country = payload.country; - return bot.transformers.customizers.subscription(bot, payload, subscription); + return callCustomizer('subscription', bot, payload, subscription, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/team.ts b/packages/bot/src/transformers/team.ts index 9ad7dcb1a..9a7479fc9 100644 --- a/packages/bot/src/transformers/team.ts +++ b/packages/bot/src/transformers/team.ts @@ -3,7 +3,7 @@ import { iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { Team } from './types.js'; -export function transformTeam(bot: Bot, payload: DiscordTeam): Team { +export function transformTeam(bot: Bot, payload: DiscordTeam) { const id = bot.transformers.snowflake(payload.id); const team = { diff --git a/packages/bot/src/transformers/template.ts b/packages/bot/src/transformers/template.ts index 5b9bed818..b5bea70a5 100644 --- a/packages/bot/src/transformers/template.ts +++ b/packages/bot/src/transformers/template.ts @@ -1,7 +1,7 @@ import type { DiscordTemplate } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { Template } from './types.js'; -export function transformTemplate(bot: Bot, payload: DiscordTemplate): Template { +export function transformTemplate(bot: Bot, payload: DiscordTemplate) { const template = { code: payload.code, name: payload.name, diff --git a/packages/bot/src/transformers/threadMember.ts b/packages/bot/src/transformers/threadMember.ts index 60f8de122..3077b4562 100644 --- a/packages/bot/src/transformers/threadMember.ts +++ b/packages/bot/src/transformers/threadMember.ts @@ -2,7 +2,7 @@ import type { BigString, DiscordThreadMember, DiscordThreadMemberGuildCreate } f import type { Bot } from '../bot.js'; import type { ThreadMember, ThreadMemberGuildCreate } from './types.js'; -export function transformThreadMember(bot: Bot, payload: DiscordThreadMember, extra?: ThreadMemberTransformerExtra): ThreadMember { +export function transformThreadMember(bot: Bot, payload: DiscordThreadMember, extra?: ThreadMemberTransformerExtra) { const threadMember = { id: payload.id ? bot.transformers.snowflake(payload.id) : undefined, userId: payload.user_id ? bot.transformers.snowflake(payload.user_id) : undefined, @@ -31,7 +31,7 @@ export interface ThreadMemberTransformerExtra { guildId?: BigString; } -export function transformThreadMemberGuildCreate(bot: Bot, payload: DiscordThreadMemberGuildCreate): ThreadMemberGuildCreate { +export function transformThreadMemberGuildCreate(bot: Bot, payload: DiscordThreadMemberGuildCreate) { const threadMember = { joinTimestamp: Date.parse(payload.join_timestamp), } as ThreadMemberGuildCreate; diff --git a/packages/bot/src/transformers/toggles/channel.ts b/packages/bot/src/transformers/toggles/channel.ts index a3df8f782..b1bc3fbb3 100644 --- a/packages/bot/src/transformers/toggles/channel.ts +++ b/packages/bot/src/transformers/toggles/channel.ts @@ -17,7 +17,7 @@ export const ChannelToggle = { }; export class ChannelToggles extends ToggleBitfield { - constructor(channelOrBitfield: DiscordChannel | number) { + constructor(channelOrBitfield: Partial | number) { super(); if (typeof channelOrBitfield === 'number') this.bitfield = channelOrBitfield; diff --git a/packages/bot/src/transformers/toggles/emoji.ts b/packages/bot/src/transformers/toggles/emoji.ts index d46552b83..c82cff5ff 100644 --- a/packages/bot/src/transformers/toggles/emoji.ts +++ b/packages/bot/src/transformers/toggles/emoji.ts @@ -13,7 +13,7 @@ export const EmojiToggle = { }; export class EmojiToggles extends ToggleBitfield { - constructor(roleOrTogglesInt: DiscordEmoji | number) { + constructor(roleOrTogglesInt: Partial | number) { super(); if (typeof roleOrTogglesInt === 'number') this.bitfield = roleOrTogglesInt; diff --git a/packages/bot/src/transformers/toggles/guild.ts b/packages/bot/src/transformers/toggles/guild.ts index 901e34899..b714ecdf7 100644 --- a/packages/bot/src/transformers/toggles/guild.ts +++ b/packages/bot/src/transformers/toggles/guild.ts @@ -117,7 +117,7 @@ export const GuildToggle = { }; export class GuildToggles extends ToggleBitfieldBigint { - constructor(guildOrTogglesBigint: DiscordGuild | bigint) { + constructor(guildOrTogglesBigint: Partial | bigint) { super(); if (typeof guildOrTogglesBigint === 'bigint') this.bitfield = guildOrTogglesBigint; diff --git a/packages/bot/src/transformers/toggles/role.ts b/packages/bot/src/transformers/toggles/role.ts index 3892e8a05..bfd7575fc 100644 --- a/packages/bot/src/transformers/toggles/role.ts +++ b/packages/bot/src/transformers/toggles/role.ts @@ -17,7 +17,7 @@ export const RoleToggle = { }; export class RoleToggles extends ToggleBitfield { - constructor(roleOrTogglesInt: DiscordRole | number) { + constructor(roleOrTogglesInt: Partial | number) { super(); if (typeof roleOrTogglesInt === 'number') this.bitfield = roleOrTogglesInt; diff --git a/packages/bot/src/transformers/toggles/user.ts b/packages/bot/src/transformers/toggles/user.ts index d8fdbdc7b..24b51cba9 100644 --- a/packages/bot/src/transformers/toggles/user.ts +++ b/packages/bot/src/transformers/toggles/user.ts @@ -13,7 +13,7 @@ export const UserToggle = { }; export class UserToggles extends ToggleBitfield { - constructor(userOrTogglesInt: DiscordUser | number) { + constructor(userOrTogglesInt: Partial | number) { super(); if (typeof userOrTogglesInt === 'number') this.bitfield = userOrTogglesInt; diff --git a/packages/bot/src/transformers/toggles/voice.ts b/packages/bot/src/transformers/toggles/voice.ts index 20e6fb768..8fdd56723 100644 --- a/packages/bot/src/transformers/toggles/voice.ts +++ b/packages/bot/src/transformers/toggles/voice.ts @@ -19,7 +19,7 @@ export const VoiceStateToggle = { }; export class VoiceStateToggles extends ToggleBitfield { - constructor(voiceOrTogglesInt: DiscordVoiceState | number) { + constructor(voiceOrTogglesInt: Partial | number) { super(); if (typeof voiceOrTogglesInt === 'number') this.bitfield = voiceOrTogglesInt; diff --git a/packages/bot/src/transformers/types.ts b/packages/bot/src/transformers/types.ts index a70d8124b..38b308e1d 100644 --- a/packages/bot/src/transformers/types.ts +++ b/packages/bot/src/transformers/types.ts @@ -75,6 +75,7 @@ import type { StickerFormatTypes, StickerTypes, SystemChannelFlags, + TargetTypes, TeamMembershipStates, TextStyles, VerificationLevels, @@ -966,7 +967,7 @@ export interface Guild { /** All active threads in the guild that the current user has permission to view */ threads: Collection; /** Presences of the members in the guild, will only include non-offline members if the size is greater than large threshold */ - presences?: PresenceUpdate[]; + presences?: Partial[]; /** Banner hash */ banner?: bigint; /** The preferred locale of a Community guild; used in server discovery and notices from Discord; defaults to "en-US" */ @@ -1173,7 +1174,7 @@ export interface InteractionData { } export interface InteractionDataResolved { - messages?: Collection; + messages?: Collection>; users?: Collection; members?: Collection>; roles?: Collection; @@ -1207,14 +1208,14 @@ export interface Invite { /** The maximum number of times the invite can be used */ maxUses: number; /** The type of target for this voice channel invite */ - targetType: number; + targetType?: TargetTypes; /** The target user for this invite */ - targetUser: User; + targetUser?: User; /** The embedded application to open for this voice channel embedded application invite */ - targetApplication?: Application; + targetApplication?: Partial; /** Whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) */ temporary: boolean; - /** How many times the invite has been used (always will be 0) */ + /** How many times the invite has been used */ uses: number; /** Approximate count of online members (only present when target_user is set) */ approximateMemberCount: number; diff --git a/packages/bot/src/transformers/user.ts b/packages/bot/src/transformers/user.ts index a32ea1add..a8072e5d5 100644 --- a/packages/bot/src/transformers/user.ts +++ b/packages/bot/src/transformers/user.ts @@ -2,6 +2,7 @@ import type { DiscordCollectibles, DiscordNameplate, DiscordUser, DiscordUserPri import { avatarUrl, defaultAvatarUrl, displayAvatarUrl, iconHashToBigInt, snowflakeToTimestamp } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { ToggleBitfield } from './toggles/ToggleBitfield.js'; import { UserToggles } from './toggles/user.js'; import type { Collectibles, Nameplate, User, UserPrimaryGuild } from './types.js'; @@ -44,7 +45,7 @@ export const baseUser: User = { }, }; -export function transformUser(bot: Bot, payload: DiscordUser): User { +export function transformUser(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const user: SetupDesiredProps = Object.create(baseUser); const props = bot.transformers.desiredProperties.user; @@ -66,19 +67,23 @@ export function transformUser(bot: Bot, payload: DiscordUser): User { if (props.collectibles && payload.collectibles) user.collectibles = bot.transformers.collectibles(bot, payload.collectibles); if (props.primaryGuild && payload.primary_guild) user.primaryGuild = bot.transformers.userPrimaryGuild(bot, payload.primary_guild); - return bot.transformers.customizers.user(bot, payload, user); + return callCustomizer('user', bot, payload, user, { + partial: extra?.partial ?? false, + }); } -export function transformCollectibles(bot: Bot, payload: DiscordCollectibles): Collectibles { +export function transformCollectibles(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const collectibles = {} as SetupDesiredProps; const props = bot.transformers.desiredProperties.collectibles; if (props.nameplate && payload.nameplate) collectibles.nameplate = bot.transformers.nameplate(bot, payload.nameplate); - return bot.transformers.customizers.collectibles(bot, payload, collectibles); + return callCustomizer('collectibles', bot, payload, collectibles, { + partial: extra?.partial ?? false, + }); } -export function transformNameplate(bot: Bot, payload: DiscordNameplate): Nameplate { +export function transformNameplate(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const nameplate = {} as SetupDesiredProps; const props = bot.transformers.desiredProperties.nameplate; @@ -87,10 +92,12 @@ export function transformNameplate(bot: Bot, payload: DiscordNameplate): Namepla if (props.label && payload.label) nameplate.label = payload.label; if (props.palette && payload.palette) nameplate.palette = payload.palette; - return bot.transformers.customizers.nameplate(bot, payload, nameplate); + return callCustomizer('nameplate', bot, payload, nameplate, { + partial: extra?.partial ?? false, + }); } -export function transformUserPrimaryGuild(bot: Bot, payload: DiscordUserPrimaryGuild): UserPrimaryGuild { +export function transformUserPrimaryGuild(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const userPrimaryGuild = {} as SetupDesiredProps; const props = bot.transformers.desiredProperties.userPrimaryGuild; @@ -99,5 +106,7 @@ export function transformUserPrimaryGuild(bot: Bot, payload: DiscordUserPrimaryG if (props.tag && payload.tag) userPrimaryGuild.tag = payload.tag; if (props.badge && payload.badge) userPrimaryGuild.badge = iconHashToBigInt(payload.badge); - return bot.transformers.customizers.userPrimaryGuild(bot, payload, userPrimaryGuild); + return callCustomizer('userPrimaryGuild', bot, payload, userPrimaryGuild, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/voiceRegion.ts b/packages/bot/src/transformers/voiceRegion.ts index 0f6e8c1e2..bc6bb4286 100644 --- a/packages/bot/src/transformers/voiceRegion.ts +++ b/packages/bot/src/transformers/voiceRegion.ts @@ -2,7 +2,7 @@ import type { DiscordVoiceRegion } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { VoiceRegion } from './types.js'; -export function transformVoiceRegion(bot: Bot, payload: DiscordVoiceRegion): VoiceRegion { +export function transformVoiceRegion(bot: Bot, payload: DiscordVoiceRegion) { const voiceRegion = { id: payload.id, name: payload.name, diff --git a/packages/bot/src/transformers/voiceState.ts b/packages/bot/src/transformers/voiceState.ts index 2f7eeb6a4..eb63fa6dc 100644 --- a/packages/bot/src/transformers/voiceState.ts +++ b/packages/bot/src/transformers/voiceState.ts @@ -1,10 +1,11 @@ import type { BigString, DiscordVoiceState } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import { VoiceStateToggles } from './toggles/voice.js'; import type { VoiceState } from './types.js'; -export function transformVoiceState(bot: Bot, payload: DiscordVoiceState, extra?: { guildId?: BigString }): VoiceState { +export function transformVoiceState(bot: Bot, payload: Partial, extra?: { guildId?: BigString; partial?: boolean }) { const props = bot.transformers.desiredProperties.voiceState; const voiceState = {} as SetupDesiredProps; @@ -13,10 +14,11 @@ export function transformVoiceState(bot: Bot, payload: DiscordVoiceState, extra? if (props.channelId && payload.channel_id) voiceState.channelId = bot.transformers.snowflake(payload.channel_id); if (props.guildId && extra?.guildId) voiceState.guildId = bot.transformers.snowflake(extra.guildId); if (props.toggles) voiceState.toggles = new VoiceStateToggles(payload); - if (props.sessionId) voiceState.sessionId = payload.session_id; + if (props.sessionId && payload.session_id !== undefined) voiceState.sessionId = payload.session_id; if (props.userId && payload.user_id) voiceState.userId = bot.transformers.snowflake(payload.user_id); - return bot.transformers.customizers.voiceState(bot, payload, voiceState, { + return callCustomizer('voiceState', bot, payload, voiceState, { guildId: extra?.guildId ? bot.transformers.snowflake(extra.guildId) : undefined, + partial: extra?.partial ?? false, }); } diff --git a/packages/bot/src/transformers/webhook.ts b/packages/bot/src/transformers/webhook.ts index c0567eabb..027c23fa6 100644 --- a/packages/bot/src/transformers/webhook.ts +++ b/packages/bot/src/transformers/webhook.ts @@ -2,9 +2,10 @@ import type { DiscordWebhook } from '@discordeno/types'; import { iconHashToBigInt } from '@discordeno/utils'; import type { Bot } from '../bot.js'; import type { DesiredPropertiesBehavior, SetupDesiredProps, TransformersDesiredProperties } from '../desiredProperties.js'; +import { callCustomizer } from '../transformers.js'; import type { Webhook } from './types.js'; -export function transformWebhook(bot: Bot, payload: DiscordWebhook): typeof bot.transformers.$inferredTypes.webhook { +export function transformWebhook(bot: Bot, payload: Partial, extra?: { partial?: boolean }) { const props = bot.transformers.desiredProperties.webhook; const webhook = {} as SetupDesiredProps; @@ -24,9 +25,10 @@ export function transformWebhook(bot: Bot, payload: DiscordWebhook): typeof bot. icon: payload.source_guild.icon ? iconHashToBigInt(payload.source_guild.icon) : undefined, }; if (props.sourceChannel && payload.source_channel) - // @ts-expect-error TODO: Partials - webhook.sourceChannel = bot.transformers.channel(bot, payload.source_channel, { guildId: payload.guild_id }); + webhook.sourceChannel = bot.transformers.channel(bot, payload.source_channel, { guildId: payload.guild_id ?? undefined, partial: true }); if (props.url && payload.url) webhook.url = payload.url; - return bot.transformers.customizers.webhook(bot, payload, webhook); + return callCustomizer('webhook', bot, payload, webhook, { + partial: extra?.partial ?? false, + }); } diff --git a/packages/bot/src/transformers/welcomeScreen.ts b/packages/bot/src/transformers/welcomeScreen.ts index 0aba2d2d3..eeb083fd1 100644 --- a/packages/bot/src/transformers/welcomeScreen.ts +++ b/packages/bot/src/transformers/welcomeScreen.ts @@ -2,7 +2,7 @@ import type { DiscordWelcomeScreen } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { WelcomeScreen } from './types.js'; -export function transformWelcomeScreen(bot: Bot, payload: DiscordWelcomeScreen): WelcomeScreen { +export function transformWelcomeScreen(bot: Bot, payload: DiscordWelcomeScreen) { const welcomeScreen = { description: payload.description ?? undefined, welcomeChannels: payload.welcome_channels.map((channel) => ({ diff --git a/packages/bot/src/transformers/widget.ts b/packages/bot/src/transformers/widget.ts index c5224aade..788fa63ff 100644 --- a/packages/bot/src/transformers/widget.ts +++ b/packages/bot/src/transformers/widget.ts @@ -2,15 +2,13 @@ import type { DiscordGuildWidget } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { GuildWidget } from './types.js'; -export function transformWidget(bot: Bot, payload: DiscordGuildWidget): GuildWidget { +export function transformWidget(bot: Bot, payload: DiscordGuildWidget) { const widget = { id: bot.transformers.snowflake(payload.id), name: payload.name, instantInvite: payload.instant_invite ?? undefined, - // @ts-expect-error TODO: Deal with partials - channels: payload.channels.map((channel) => bot.transformers.channel(bot, channel)), - // @ts-expect-error TODO: Deal with partials - members: payload.members.map((user) => bot.transformers.user(bot, user)), + channels: payload.channels.map((channel) => bot.transformers.channel(bot, channel, { partial: true })), + members: payload.members.map((user) => bot.transformers.user(bot, user, { partial: true })), presenceCount: payload.presence_count, } as GuildWidget; diff --git a/packages/bot/src/transformers/widgetSettings.ts b/packages/bot/src/transformers/widgetSettings.ts index 9945f6d50..5c3df8d0b 100644 --- a/packages/bot/src/transformers/widgetSettings.ts +++ b/packages/bot/src/transformers/widgetSettings.ts @@ -2,11 +2,11 @@ import type { DiscordGuildWidgetSettings } from '@discordeno/types'; import type { Bot } from '../bot.js'; import type { GuildWidgetSettings } from './types.js'; -export function transformWidgetSettings(bot: Bot, payload: DiscordGuildWidgetSettings): GuildWidgetSettings { +export function transformWidgetSettings(bot: Bot, payload: DiscordGuildWidgetSettings) { const widget = { enabled: payload.enabled, channelId: payload.channel_id ?? undefined, - }; + } satisfies GuildWidgetSettings; return bot.transformers.customizers.widgetSettings(bot, payload, widget); } diff --git a/packages/types/src/discord/gateway.ts b/packages/types/src/discord/gateway.ts index 55b7ad380..a14d73eba 100644 --- a/packages/types/src/discord/gateway.ts +++ b/packages/types/src/discord/gateway.ts @@ -700,7 +700,7 @@ export interface DiscordInviteCreate { /** The maximum number of times the invite can be used */ max_uses: number; /** The type of target for this voice channel invite */ - target_type: TargetTypes; + target_type?: TargetTypes; /** The target user for this invite */ target_user?: DiscordUser; /** The embedded application to open for this voice channel embedded application invite */ @@ -710,7 +710,7 @@ export interface DiscordInviteCreate { /** How many times the invite has been used (always will be 0) */ uses: number; /** The expiration date of this invite. */ - expires_at: string; + expires_at: string | null; /** the role ID(s) for roles in the guild given to the users that accept this invite */ role_ids?: string[]; } diff --git a/packages/utils/src/typeguards.ts b/packages/utils/src/typeguards.ts index 553c0f994..6c74094ef 100644 --- a/packages/utils/src/typeguards.ts +++ b/packages/utils/src/typeguards.ts @@ -1,12 +1,4 @@ -import type { - DiscordInviteCreate, - DiscordInviteMetadata, - GetMessagesAfter, - GetMessagesAround, - GetMessagesBefore, - GetMessagesLimit, - GetMessagesOptions, -} from '@discordeno/types'; +import type { GetMessagesAfter, GetMessagesAround, GetMessagesBefore, GetMessagesLimit, GetMessagesOptions } from '@discordeno/types'; import { hasProperty } from './utils.js'; export function isGetMessagesAfter(options: GetMessagesOptions): options is GetMessagesAfter { @@ -24,7 +16,3 @@ export function isGetMessagesAround(options: GetMessagesOptions): options is Get export function isGetMessagesLimit(options: GetMessagesOptions): options is GetMessagesLimit { return hasProperty(options, 'limit'); } - -export function isInviteWithMetadata(options: DiscordInviteCreate | DiscordInviteMetadata): options is DiscordInviteMetadata { - return !hasProperty(options, 'channel_id'); -}