diff --git a/src/bot.ts b/src/bot.ts index 7d0357d8e..3f937b651 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -293,6 +293,8 @@ import { validDiscoveryTerm, } from "./helpers/mod.ts"; import { DiscordenoEmoji, transformEmoji } from "./transformers/emoji.ts"; +import { transformActivity } from "./transformers/activity.ts"; +import { DiscordenoPresence, transformPresence } from "./transformers/presence.ts"; export async function createBot(options: CreateBotOptions) { return { @@ -309,6 +311,9 @@ export async function createBot(options: CreateBotOptions) { cache: { execute: async function ( type: + | "DELETE_MESSAGES_FROM_CHANNEL" + | "DELETE_ROLE_FROM_MEMBER" + | "BULK_DELETE_MESSAGES" | "GUILD_MEMBER_CHUNK" | "GUILD_MEMBER_COUNT_DECREMENT" | "GUILD_MEMBER_COUNT_INCREMENT" @@ -374,6 +379,20 @@ export async function createBot(options: CreateBotOptions) { return; }, }, + presence: { + get: async function (id: bigint): Promise { + return {} as any as DiscordenoPresence; + }, + has: async function (id: bigint): Promise { + return false; + }, + set: async function (id: bigint, guild: DiscordenoPresence): Promise { + return; + }, + delete: async function (id: bigint): Promise { + return; + }, + }, users: { get: async function (id: bigint): Promise { return {} as any as DiscordenoUser; @@ -440,6 +459,9 @@ export function createEventHandlers(events: Partial): EventHandle reactionRemove: events.reactionRemove ?? ignore, reactionRemoveAll: events.reactionRemoveAll ?? ignore, reactionRemoveEmoji: events.reactionRemoveEmoji ?? ignore, + presenceUpdate: events.presenceUpdate ?? ignore, + voiceServerUpdate: events.voiceServerUpdate ?? ignore, + voiceStateUpdate: events.voiceStateUpdate ?? ignore, channelCreate: events.channelCreate ?? ignore, voiceChannelLeave: events.voiceChannelLeave ?? ignore, channelDelete: events.channelDelete ?? ignore, @@ -1018,10 +1040,13 @@ export interface Transformers { application: typeof transformApplication; team: typeof transformTeam; emoji: typeof transformEmoji; + activity: typeof transformActivity; + presence: typeof transformPresence; } export function createTransformers(options: Partial) { return { + activity: options.activity || transformActivity, application: options.application || transformApplication, channel: options.channel || transformChannel, emoji: options.emoji || transformEmoji, @@ -1031,6 +1056,7 @@ export function createTransformers(options: Partial) { invite: options.invite || transformInvite, member: options.member || transformMember, message: options.message || transformMessage, + presence: options.presence || transformPresence, role: options.role || transformRole, user: options.user || transformUser, team: options.team || transformTeam, @@ -1149,7 +1175,7 @@ export interface EventHandlers { interactionCreate: (bot: Bot, interaction: DiscordenoInteraction) => any; integrationCreate: (bot: Bot, integration: DiscordenoIntegration) => any; integrationDelete: (bot: Bot, payload: { id: bigint; guildId: bigint; applicationId?: bigint }) => any; - integrationUpdate: (bot: Bot, integration: DiscordenoIntegration) => any; + integrationUpdate: (bot: Bot, payload: { guildId: bigint }) => any; inviteCreate: (bot: Bot, invite: DiscordenoInvite) => any; inviteDelete: ( bot: Bot, @@ -1207,9 +1233,21 @@ export interface EventHandlers { guildId?: bigint; } ) => any; + presenceUpdate: (bot: Bot, presence: DiscordenoPresence, oldPresence?: DiscordenoPresence) => any; + voiceServerUpdate: (bot: Bot, payload: { token: string; endpoint?: string; guildId: bigint }) => any; + voiceStateUpdate: ( + bot: Bot, + voiceState: DiscordenoVoiceState, + payload: { guild?: DiscordenoGuild; member?: DiscordenoMember; user?: DiscordenoUser } + ) => any; channelCreate: (bot: Bot, channel: DiscordenoChannel) => any; dispatchRequirements: (bot: Bot, data: GatewayPayload, shardId: number) => any; - voiceChannelLeave: (bot: Bot, voiceState: DiscordenoVoiceState, channel: DiscordenoChannel) => any; + voiceChannelLeave: ( + bot: Bot, + voiceState: DiscordenoVoiceState, + guild: DiscordenoGuild, + channel?: DiscordenoChannel + ) => any; channelDelete: (bot: Bot, channel: DiscordenoChannel) => any; channelPinsUpdate: (bot: Bot, data: { guildId?: bigint; channelId: bigint; lastPinTimestamp?: number }) => any; channelUpdate: (bot: Bot, channel: DiscordenoChannel, oldChannel: DiscordenoChannel) => any; diff --git a/src/handlers/channels/CHANNEL_DELETE.ts b/src/handlers/channels/CHANNEL_DELETE.ts index 5b289d45c..57be9c909 100644 --- a/src/handlers/channels/CHANNEL_DELETE.ts +++ b/src/handlers/channels/CHANNEL_DELETE.ts @@ -6,18 +6,22 @@ import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; export async function handleChannelDelete(bot: Bot, data: SnakeCasedPropertiesDeep) { const payload = data.d as SnakeCasedPropertiesDeep; + if (!payload.guild_id) return; - const channel = await bot.cache.channels.get(bot.transformers.snowflake(payload.id)); + const [channel, guild] = await Promise.all([ + bot.cache.channels.get(bot.transformers.snowflake(payload.id)), + bot.cache.guilds.get(bot.transformers.snowflake(payload.guild_id)), + ]); if (!channel) return; - if ([DiscordChannelTypes.GuildVoice, DiscordChannelTypes.GuildStageVoice].includes(channel.type)) { - channel.voiceStates?.forEach((vs, key) => { + if (guild && [DiscordChannelTypes.GuildVoice, DiscordChannelTypes.GuildStageVoice].includes(channel.type)) { + guild.voiceStates?.forEach((vs, key) => { if (vs.channelId !== channel.id) return; // Since this channel was deleted all voice states for this channel should be deleted - channel.voiceStates?.delete(key); + guild.voiceStates?.delete(key); - bot.events.voiceChannelLeave(bot, vs, channel); + bot.events.voiceChannelLeave(bot, vs, guild, channel); }); } else if ( [ @@ -27,7 +31,7 @@ export async function handleChannelDelete(bot: Bot, data: SnakeCasedPropertiesDe DiscordChannelTypes.GuildNews, ].includes(payload.type) ) { - await bot.cache.channels.forEach("DELETE_MESSAGES_FROM_CHANNEL", { + await bot.cache.execute("DELETE_MESSAGES_FROM_CHANNEL", { channelId: bot.transformers.snowflake(payload.id), }); } diff --git a/src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts b/src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts index e6a2610d7..3bc9be951 100644 --- a/src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts +++ b/src/handlers/guilds/GUILD_INTEGRATIONS_UPDATE.ts @@ -6,5 +6,5 @@ import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; export async function handleGuildIntegrationsUpdate(bot: Bot, data: DiscordGatewayPayload) { const payload = data.d as SnakeCasedPropertiesDeep; - bot.events.integrationsUpdate(bot, { guildId: bot.transformers.snowflake(payload.guild_id) }); + bot.events.integrationUpdate(bot, { guildId: bot.transformers.snowflake(payload.guild_id) }); } diff --git a/src/handlers/misc/PRESENCE_UPDATE.ts b/src/handlers/misc/PRESENCE_UPDATE.ts index ce801616a..3e3424f48 100644 --- a/src/handlers/misc/PRESENCE_UPDATE.ts +++ b/src/handlers/misc/PRESENCE_UPDATE.ts @@ -1,14 +1,17 @@ -import { eventHandlers } from "../../bot.ts"; -import { cacheHandlers } from "../../cache.ts"; +import { Bot } from "../../bot.ts"; import type { PresenceUpdate } from "../../types/activity/presence_update.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import { snowflakeToBigint } from "../../util/bigint.ts"; +import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; -export async function handlePresenceUpdate(data: DiscordGatewayPayload) { - const payload = data.d as PresenceUpdate; +export async function handlePresenceUpdate(bot: Bot, data: DiscordGatewayPayload) { + const payload = data.d as SnakeCasedPropertiesDeep; - const oldPresence = await cacheHandlers.get("presences", snowflakeToBigint(payload.user.id)); - await cacheHandlers.set("presences", snowflakeToBigint(payload.user.id), payload); + const id = bot.transformers.snowflake(payload.user.id); - eventHandlers.presenceUpdate?.(payload, oldPresence); + const oldPresence = await bot.cache.presence.get(id); + const presence = bot.transformers.presence(bot, payload); + await bot.cache.presence.set(id, presence) + + + bot.events.presenceUpdate(bot, presence, oldPresence); } diff --git a/src/handlers/misc/READY.ts b/src/handlers/misc/READY.ts index 7c136f9d6..c91e817dd 100644 --- a/src/handlers/misc/READY.ts +++ b/src/handlers/misc/READY.ts @@ -1,67 +1,20 @@ -import { eventHandlers, setApplicationId, setBotId } from "../../bot.ts"; -import { cache } from "../../cache.ts"; +import { Bot } from "../../bot.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; -import type { Ready } from "../../types/gateway/ready.ts"; -import { snowflakeToBigint } from "../../util/bigint.ts"; -import { DiscordenoShard, ws } from "../../ws/ws.ts"; +import type { DiscordReady } from "../../types/gateway/ready.ts"; -export function handleReady(data: DiscordGatewayPayload, shardId: number) { +export function handleReady(bot: Bot, data: DiscordGatewayPayload, shardId: number) { + const payload = data.d as DiscordReady; // Triggered on each shard - eventHandlers.shardReady?.(shardId); + bot.events.ready(bot, { + shardId, + v: payload.v, + user: bot.transformers.user(bot, payload.user), + guilds: payload.guilds.map((p) => bot.transformers.snowflake(p.id)), + sessionId: payload.session_id, + shard: payload.shard, + applicationId: bot.transformers.snowflake(payload.application.id), + }, payload); - // The bot has already started, the last shard is resumed, however. - if (cache.isReady) return; - - const shard = ws.shards.get(shardId); - if (!shard) return; - - const payload = data.d as Ready; - setBotId(payload.user.id); - setApplicationId(payload.application.id); - - // Set ready to false just to go sure - shard.ready = false; - // All guilds are unavailable at first - shard.unavailableGuildIds = new Set(payload.guilds.map((g) => snowflakeToBigint(g.id))); - - // Falied to load check - shard.failedToLoadTimeoutId = setTimeout(() => { - eventHandlers.shardFailedToLoad?.(shard.id, shard.unavailableGuildIds); - // Force execute the loaded function to prevent infinite loop - return loaded(shard); - }, 5000); -} - -export function guildAvailable(shard: DiscordenoShard, guildId: bigint) { - if (!shard.failedToLoadTimeoutId) return; - - clearTimeout(shard.failedToLoadTimeoutId); - shard.unavailableGuildIds.delete(guildId); - if (!shard.unavailableGuildIds.size) return loaded(shard); - - shard.failedToLoadTimeoutId = setTimeout(() => { - eventHandlers.shardFailedToLoad?.(shard.id, shard.unavailableGuildIds); - // Force execute the loaded function to prevent infinite loop - return loaded(shard); - }, 5000); -} - -function loaded(shard: DiscordenoShard) { - shard.ready = true; - - // If it is not the last shard we can't go full ready - if (shard.id !== ws.lastShardId) return; - - // Still some shards are loading so wait another 2 seconds for them - if (ws.shards.some((shard) => !shard.ready)) { - setTimeout(() => { - eventHandlers.debug?.("loop", `3. Running setTimeout in READY file.`); - loaded(shard); - }, 2000); - - return; - } - - cache.isReady = true; - eventHandlers.ready?.(); + if (!bot.id) bot.id = bot.transformers.snowflake(payload.user.id); + if (!bot.applicationId) bot.applicationId = bot.transformers.snowflake(payload.application.id); } diff --git a/src/handlers/misc/TYPING_START.ts b/src/handlers/misc/TYPING_START.ts index ccea2d5af..a371568fe 100644 --- a/src/handlers/misc/TYPING_START.ts +++ b/src/handlers/misc/TYPING_START.ts @@ -1,7 +1,18 @@ -import { eventHandlers } from "../../bot.ts"; +import { Bot } from "../../bot.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { TypingStart } from "../../types/misc/typing_start.ts"; +import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; -export function handleTypingStart(data: DiscordGatewayPayload) { - eventHandlers.typingStart?.(data.d as TypingStart); +export function handleTypingStart(bot: Bot, data: DiscordGatewayPayload) { + const payload = data.d as SnakeCasedPropertiesDeep; + + const guildId = payload.guild_id ? bot.transformers.snowflake(payload.guild_id) : undefined; + + bot.events.typingStart(bot, { + guildId, + channelId: bot.transformers.snowflake(payload.channel_id), + userId: bot.transformers.snowflake(payload.user_id), + timestamp: payload.timestamp, + member: payload.member && guildId ? bot.transformers.member(bot, payload.member, guildId) : undefined, + }); } diff --git a/src/handlers/misc/USER_UPDATE.ts b/src/handlers/misc/USER_UPDATE.ts index 19c29bab7..ef252ac44 100644 --- a/src/handlers/misc/USER_UPDATE.ts +++ b/src/handlers/misc/USER_UPDATE.ts @@ -1,35 +1,12 @@ -import { eventHandlers } from "../../bot.ts"; -import { cacheHandlers } from "../../cache.ts"; -import { memberToggles } from "../../structures/member.ts"; +import { Bot } from "../../bot.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { User } from "../../types/users/user.ts"; -import { snowflakeToBigint } from "../../util/bigint.ts"; -import { iconHashToBigInt } from "../../util/hash.ts"; +import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; -export async function handleUserUpdate(data: DiscordGatewayPayload) { - const userData = data.d as User; +export async function handleUserUpdate(bot: Bot, data: DiscordGatewayPayload) { + const payload = data.d as SnakeCasedPropertiesDeep; + const user = bot.transformers.user(bot, payload); + await bot.cache.users.set(user.id, user); - const member = await cacheHandlers.get("members", snowflakeToBigint(userData.id)); - if (!member) return; - - // Update username - member.username = userData.username; - // Update discriminator - member.discriminator = Number(userData.discriminator); - - // Check if a avatar is available - const hash = userData.avatar ? iconHashToBigInt(userData.avatar) : undefined; - // Update the avatar - member.avatar = hash?.bigint || 0n; - // Update the animated status if its animated - if (hash?.animated) member.bitfield |= memberToggles.animatedAvatar; - else member.bitfield &= ~memberToggles.animatedAvatar; - - member.flags = userData.flags; - member.premiumType = userData.premiumType; - member.publicFlags = userData.publicFlags; - - await cacheHandlers.set("members", snowflakeToBigint(userData.id), member); - - eventHandlers.botUpdate?.(userData); + bot.events.botUpdate(bot, user); } diff --git a/src/handlers/roles/GUILD_ROLE_CREATE.ts b/src/handlers/roles/GUILD_ROLE_CREATE.ts index 82cf77a59..bfba60e6c 100644 --- a/src/handlers/roles/GUILD_ROLE_CREATE.ts +++ b/src/handlers/roles/GUILD_ROLE_CREATE.ts @@ -1,21 +1,19 @@ -import { eventHandlers } from "../../bot.ts"; -import { cacheHandlers } from "../../cache.ts"; -import { structures } from "../../structures/mod.ts"; +import { Bot } from "../../bot.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { GuildRoleCreate } from "../../types/guilds/guild_role_create.ts"; -import { snowflakeToBigint } from "../../util/bigint.ts"; +import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; -export async function handleGuildRoleCreate(data: DiscordGatewayPayload) { - const payload = data.d as GuildRoleCreate; - const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); +export async function handleGuildRoleCreate(bot: Bot, data: DiscordGatewayPayload) { + const payload = data.d as SnakeCasedPropertiesDeep; + + const guildId = bot.transformers.snowflake(payload.guild_id); + const guild = await bot.cache.guilds.get(guildId); if (!guild) return; - const role = await structures.createDiscordenoRole({ - ...payload, - guildId: guild.id, - }); - guild.roles = guild.roles.set(snowflakeToBigint(payload.role.id), role); - await cacheHandlers.set("guilds", guild.id, guild); + const role = bot.transformers.role(bot, { role: payload.role, guildId }); - eventHandlers.roleCreate?.(guild, role); + guild.roles = guild.roles.set(role.id, role); + await bot.cache.guilds.set(guild.id, guild); + + bot.events.roleCreate(bot, guild, role); } diff --git a/src/handlers/roles/GUILD_ROLE_DELETE.ts b/src/handlers/roles/GUILD_ROLE_DELETE.ts index 24f914d50..d0347c820 100644 --- a/src/handlers/roles/GUILD_ROLE_DELETE.ts +++ b/src/handlers/roles/GUILD_ROLE_DELETE.ts @@ -1,21 +1,23 @@ -import { eventHandlers } from "../../bot.ts"; -import { cacheHandlers } from "../../cache.ts"; +import { Bot } from "../../bot.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { GuildRoleDelete } from "../../types/guilds/guild_role_delete.ts"; -import { snowflakeToBigint } from "../../util/bigint.ts"; +import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; -export async function handleGuildRoleDelete(data: DiscordGatewayPayload) { - const payload = data.d as GuildRoleDelete; - const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); +export async function handleGuildRoleDelete(bot: Bot, data: DiscordGatewayPayload) { + const payload = data.d as SnakeCasedPropertiesDeep; + const guildId = bot.transformers.snowflake(payload.guild_id); + const guild = await bot.cache.guilds.get(guildId); if (!guild) return; - const roleId = snowflakeToBigint(payload.roleId); + const roleId = bot.transformers.snowflake(payload.role_id); - const cachedRole = guild.roles.get(roleId)!; + const cachedRole = guild.roles.get(roleId); guild.roles.delete(roleId); - if (cachedRole) eventHandlers.roleDelete?.(guild, cachedRole); + await bot.cache.guilds.set(guild.id, guild); + + if (cachedRole) bot.events.roleDelete(bot, guild, cachedRole); // For bots without GUILD_MEMBERS member.roles is never updated breaking permissions checking. - await cacheHandlers.forEach("DELETE_ROLE_FROM_MEMBER", { guildId: guild.id, roleId }); + await bot.cache.execute("DELETE_ROLE_FROM_MEMBER", { guildId: guild.id, roleId }); } diff --git a/src/handlers/roles/GUILD_ROLE_UPDATE.ts b/src/handlers/roles/GUILD_ROLE_UPDATE.ts index 8c1a004a8..5d29d6843 100644 --- a/src/handlers/roles/GUILD_ROLE_UPDATE.ts +++ b/src/handlers/roles/GUILD_ROLE_UPDATE.ts @@ -1,24 +1,17 @@ -import { eventHandlers } from "../../bot.ts"; -import { cacheHandlers } from "../../cache.ts"; -import { structures } from "../../structures/mod.ts"; +import { Bot } from "../../bot.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { GuildRoleUpdate } from "../../types/guilds/guild_role_update.ts"; -import { snowflakeToBigint } from "../../util/bigint.ts"; +import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; -export async function handleGuildRoleUpdate(data: DiscordGatewayPayload) { - const payload = data.d as GuildRoleUpdate; - const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); +export async function handleGuildRoleUpdate(bot: Bot, data: DiscordGatewayPayload) { + const payload = data.d as SnakeCasedPropertiesDeep; + const guildId = bot.transformers.snowflake(payload.guild_id); + const guild = await bot.cache.guilds.get(guildId); if (!guild) return; - const cachedRole = guild.roles.get(snowflakeToBigint(payload.role.id)); - if (!cachedRole) return; + const role = bot.transformers.role(bot, { role: payload.role, guildId }); + guild.roles = guild.roles.set(role.id, role); + await bot.cache.guilds.set(guild.id, guild); - const role = await structures.createDiscordenoRole({ - ...payload, - guildId: guild.id, - }); - guild.roles.set(snowflakeToBigint(payload.role.id), role); - await cacheHandlers.set("guilds", guild.id, guild); - - eventHandlers.roleUpdate?.(guild, role, cachedRole); + bot.events.roleUpdate(bot, guild, role, guild.roles.get(role.id)); } diff --git a/src/handlers/voice/VOICE_SERVER_UPDATE.ts b/src/handlers/voice/VOICE_SERVER_UPDATE.ts index d93a41ded..edaedf6ba 100644 --- a/src/handlers/voice/VOICE_SERVER_UPDATE.ts +++ b/src/handlers/voice/VOICE_SERVER_UPDATE.ts @@ -1,14 +1,14 @@ -import { eventHandlers } from "../../bot.ts"; -import { cacheHandlers } from "../../cache.ts"; +import { Bot } from "../../bot.ts"; +import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { VoiceServerUpdate } from "../../types/voice/voice_server_update.ts"; -import { snowflakeToBigint } from "../../util/bigint.ts"; -export async function handleVoiceServerUpdate(data: DiscordGatewayPayload) { - const payload = data.d as VoiceServerUpdate; +export async function handleVoiceServerUpdate(bot: Bot, data: DiscordGatewayPayload) { + const payload = data.d as SnakeCasedPropertiesDeep; - const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); - if (!guild) return; - - eventHandlers.voiceServerUpdate?.(payload, guild); + bot.events.voiceServerUpdate(bot, { + token: payload.token, + guildId: bot.transformers.snowflake(payload.guild_id), + endpoint: payload.endpoint ?? undefined, + }); } diff --git a/src/handlers/voice/VOICE_STATE_UPDATE.ts b/src/handlers/voice/VOICE_STATE_UPDATE.ts index 31c91ccaa..f281a513f 100644 --- a/src/handlers/voice/VOICE_STATE_UPDATE.ts +++ b/src/handlers/voice/VOICE_STATE_UPDATE.ts @@ -1,50 +1,23 @@ -import { eventHandlers } from "../../bot.ts"; -import { cacheHandlers } from "../../cache.ts"; -import { structures } from "../../structures/mod.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { VoiceState } from "../../types/voice/voice_state.ts"; -import { snowflakeToBigint } from "../../util/bigint.ts"; +import { Bot } from "../../bot.ts"; +import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; -export async function handleVoiceStateUpdate(data: DiscordGatewayPayload) { - const payload = data.d as VoiceState; - if (!payload.guildId) return; +export async function handleVoiceStateUpdate(bot: Bot, data: DiscordGatewayPayload) { + const payload = data.d as SnakeCasedPropertiesDeep; + if (!payload.guild_id) return; - const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); - if (!guild) return; + const guildId = bot.transformers.snowflake(payload.guild_id); + const voiceState = bot.transformers.voiceState(bot, { voiceState: payload, guildId }); - const member = payload.member - ? await structures.createDiscordenoMember(payload.member, guild.id) - : await cacheHandlers.get("members", snowflakeToBigint(payload.userId)); - if (!member) return; - - if (!payload.member?.joinedAt) return eventHandlers.lurkerVoiceStateUpdate?.(member, payload); - - // No cached state before so lets make one for em - const cachedState = guild.voiceStates.get(snowflakeToBigint(payload.userId)); - - guild.voiceStates.set( - snowflakeToBigint(payload.userId), - await structures.createDiscordenoVoiceState(guild.id, payload) - ); - - await cacheHandlers.set("guilds", guild.id, guild); - - if (cachedState?.channelId !== (payload.channelId ? snowflakeToBigint(payload.channelId) : null)) { - // Either joined or moved channels - if (payload.channelId) { - if (cachedState?.channelId) { - // Was in a channel before - eventHandlers.voiceChannelSwitch?.(member, snowflakeToBigint(payload.channelId), cachedState.channelId); - } else { - // Was not in a channel before so user just joined - eventHandlers.voiceChannelJoin?.(member, snowflakeToBigint(payload.channelId)); - } - } // Left the channel - else if (cachedState?.channelId) { - guild.voiceStates.delete(snowflakeToBigint(payload.userId)); - eventHandlers.voiceChannelLeave?.(member, cachedState.channelId); - } + const guild = await bot.cache.guilds.get(guildId); + if (guild) { + guild.voiceStates.set(voiceState.userId, voiceState); + await bot.cache.guilds.set(guild.id, guild); } - eventHandlers.voiceStateUpdate?.(member, payload); + const member = payload.member ? bot.transformers.member(bot, payload.member, guildId) : undefined; + const user = payload.member ? bot.transformers.user(bot, payload.member.user) : undefined; + + bot.events.voiceStateUpdate(bot, voiceState, { guild, member, user }); } diff --git a/src/handlers/webhooks/WEBHOOKS_UPDATE.ts b/src/handlers/webhooks/WEBHOOKS_UPDATE.ts index 8b2e621d7..3994fd226 100644 --- a/src/handlers/webhooks/WEBHOOKS_UPDATE.ts +++ b/src/handlers/webhooks/WEBHOOKS_UPDATE.ts @@ -1,9 +1,12 @@ -import { eventHandlers } from "../../bot.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { WebhookUpdate } from "../../types/webhooks/webhooks_update.ts"; -import { snowflakeToBigint } from "../../util/bigint.ts"; +import { Bot } from "../../bot.ts"; +import { SnakeCasedPropertiesDeep } from "../../types/util.ts"; -export function handleWebhooksUpdate(data: DiscordGatewayPayload) { - const options = data.d as WebhookUpdate; - eventHandlers.webhooksUpdate?.(snowflakeToBigint(options.channelId), snowflakeToBigint(options.guildId)); +export function handleWebhooksUpdate(bot: Bot, data: DiscordGatewayPayload) { + const payload = data.d as SnakeCasedPropertiesDeep; + bot.events.webhooksUpdate(bot, { + channelId: bot.transformers.snowflake(payload.channel_id), + guildId: bot.transformers.snowflake(payload.guild_id), + }); } diff --git a/src/transformers/activity.ts b/src/transformers/activity.ts new file mode 100644 index 000000000..c11544380 --- /dev/null +++ b/src/transformers/activity.ts @@ -0,0 +1,82 @@ +import { Bot } from "../bot.ts"; +import { Activity } from "../types/activity/activity.ts"; +import { DiscordActivityTypes } from "../types/activity/activity_types.ts"; +import { SnakeCasedPropertiesDeep } from "../types/util.ts"; +import { DiscordenoEmoji } from "./emoji.ts"; + +export function transformActivity(bot: Bot, payload: SnakeCasedPropertiesDeep): DiscordenoActivity { + return { + name: payload.name, + type: payload.type, + url: payload.url ?? undefined, + createdAt: payload.created_at, + startedAt: payload.timestamps?.start, + endedAt: payload.timestamps?.end, + applicationId: payload.application_id ? bot.transformers.snowflake(payload.application_id) : undefined, + details: payload.details ?? undefined, + state: payload.state ?? undefined, + emoji: payload.emoji ? bot.transformers.emoji(bot, payload.emoji) : undefined, + partyId: payload.party?.id, + partyCurrentSize: payload.party?.size?.[0], + partyMaxSize: payload.party?.size?.[1], + largeImage: payload.assets?.large_image, + largeText: payload.assets?.large_text, + smallImage: payload.assets?.small_image, + smallText: payload.assets?.small_text, + join: payload.secrets?.join, + spectate: payload.secrets?.spectate, + match: payload.secrets?.match, + instance: payload.instance, + flags: payload.flags, + buttonLabels: payload.buttons?.map((button) => button.label), + }; +} + +export interface DiscordenoActivity { + /** The activity's name */ + name: string; + /** Activity type */ + type: DiscordActivityTypes; + /** Stream url, is validated when type is 1 */ + url?: string; + /** Unix timestamp of when the activity was added to the user's session */ + createdAt: number; + /** Unix time (in milliseconds) of when the activity started */ + startedAt?: number; + /** Unix time (in milliseconds) of when the activity ends */ + endedAt?: number; + /** Application id for the game */ + applicationId?: bigint; + /** What the player is currently doing */ + details?: string; + /** The user's current party status */ + state?: string; + /** The emoji used for a custom status */ + emoji?: DiscordenoEmoji; + /** the id of the party */ + partyId?: string; + /** The current size of the party if one exists */ + partyCurrentSize?: number; + /** The max size of the party if one exists */ + partyMaxSize?: number; + /** The id for a large asset of the activity, usually a snowflake */ + largeImage?: string; + /** Text displayed when hovering over the large image of the activity */ + largeText?: string; + /** The id for a small asset of the activity, usually a snowflake */ + smallImage?: string; + /** Text displayed when hovering over the small image of the activity */ + smallText?: string; + /** The secret for joining a party */ + join?: string; + /** The secret for spectating a game */ + spectate?: string; + /** The secret for a specific instanced match */ + match?: string; + /** Whether or not the activity is an instanced game session */ + instance?: boolean; + /** Activity flags `OR`d together, describes what the payload includes */ + flags?: number; + /** The custom button's labels shown in the Rich Presence */ + buttonLabels?: string[]; +} diff --git a/src/transformers/presence.ts b/src/transformers/presence.ts new file mode 100644 index 000000000..5e75e149f --- /dev/null +++ b/src/transformers/presence.ts @@ -0,0 +1,42 @@ +import { Bot } from "../bot.ts"; +import { PresenceUpdate } from "../types/activity/presence_update.ts"; +import { SnakeCasedPropertiesDeep } from "../types/util.ts"; +import { DiscordenoActivity } from "./activity.ts"; +import { DiscordenoUser } from "./member.ts"; + +export const statusTypes = { + online: 0, + dnd: 1, + idle: 2, + invisible: 3, + offline: 4, +} as const; + +export function transformPresence(bot: Bot, payload: SnakeCasedPropertiesDeep): DiscordenoPresence { + return { + user: bot.transformers.user(bot, payload.user), + guildId: bot.transformers.snowflake(payload.guild_id), + status: statusTypes[payload.status], + activities: payload.activities.map((activity) => bot.transformers.activity(bot, activity)), + desktop: payload.client_status.desktop, + mobile: payload.client_status.mobile, + web: payload.client_status.web, + }; +} + +export interface DiscordenoPresence { + /** The user presence is being updated for */ + user: DiscordenoUser; + /** id of the guild */ + guildId: bigint; + /** Either online: 0, dnd: 1, idle: 2, invisible: 3, offline: 4 */ + status: 0 | 1 | 2 | 3 | 4; + /** User's current activities */ + activities: DiscordenoActivity[]; + /** The user's status set for an active desktop (Windows, Linux, Mac) application session */ + desktop?: string; + /** The user's status set for an active mobile (iOS, Android) application session */ + mobile?: string; + /** The user's status set for an active web (browser, bot account) application session */ + web?: string; +} diff --git a/src/transformers/voice_state.ts b/src/transformers/voice_state.ts index e19a67304..34531304e 100644 --- a/src/transformers/voice_state.ts +++ b/src/transformers/voice_state.ts @@ -16,7 +16,7 @@ export function transformVoiceState( (payload.voiceState.self_video ? 32n : 0n) | (payload.voiceState.suppress ? 64n : 0n), - requestToSpeakTimestamp: payload.voiceState.request_to_speak_timestamp, + requestToSpeakTimestamp: payload.voiceState.request_to_speak_timestamp ? Date.parse(payload.voiceState.request_to_speak_timestamp) : undefined, sessionId: payload.voiceState.session_id, channelId: payload.voiceState.channel_id ? bot.transformers.snowflake(payload.voiceState.channel_id) : undefined, @@ -27,7 +27,7 @@ export function transformVoiceState( }; } -export interface DiscordenoVoiceState extends Omit { +export interface DiscordenoVoiceState { /** The guild id */ guildId: bigint; /** The channel id this user is connected to */ @@ -36,4 +36,6 @@ export interface DiscordenoVoiceState extends Omit