This commit is contained in:
Skillz4Killz
2021-10-24 19:13:23 +00:00
committed by GitHub
parent ebdbfcf721
commit 2427a5d2c8
17 changed files with 293 additions and 211 deletions
+40 -2
View File
@@ -293,6 +293,8 @@ import {
validDiscoveryTerm, validDiscoveryTerm,
} from "./helpers/mod.ts"; } from "./helpers/mod.ts";
import { DiscordenoEmoji, transformEmoji } from "./transformers/emoji.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) { export async function createBot(options: CreateBotOptions) {
return { return {
@@ -309,6 +311,9 @@ export async function createBot(options: CreateBotOptions) {
cache: { cache: {
execute: async function ( execute: async function (
type: type:
| "DELETE_MESSAGES_FROM_CHANNEL"
| "DELETE_ROLE_FROM_MEMBER"
| "BULK_DELETE_MESSAGES"
| "GUILD_MEMBER_CHUNK" | "GUILD_MEMBER_CHUNK"
| "GUILD_MEMBER_COUNT_DECREMENT" | "GUILD_MEMBER_COUNT_DECREMENT"
| "GUILD_MEMBER_COUNT_INCREMENT" | "GUILD_MEMBER_COUNT_INCREMENT"
@@ -374,6 +379,20 @@ export async function createBot(options: CreateBotOptions) {
return; return;
}, },
}, },
presence: {
get: async function (id: bigint): Promise<DiscordenoPresence | undefined> {
return {} as any as DiscordenoPresence;
},
has: async function (id: bigint): Promise<boolean> {
return false;
},
set: async function (id: bigint, guild: DiscordenoPresence): Promise<void> {
return;
},
delete: async function (id: bigint): Promise<void> {
return;
},
},
users: { users: {
get: async function (id: bigint): Promise<DiscordenoUser | undefined> { get: async function (id: bigint): Promise<DiscordenoUser | undefined> {
return {} as any as DiscordenoUser; return {} as any as DiscordenoUser;
@@ -440,6 +459,9 @@ export function createEventHandlers(events: Partial<EventHandlers>): EventHandle
reactionRemove: events.reactionRemove ?? ignore, reactionRemove: events.reactionRemove ?? ignore,
reactionRemoveAll: events.reactionRemoveAll ?? ignore, reactionRemoveAll: events.reactionRemoveAll ?? ignore,
reactionRemoveEmoji: events.reactionRemoveEmoji ?? ignore, reactionRemoveEmoji: events.reactionRemoveEmoji ?? ignore,
presenceUpdate: events.presenceUpdate ?? ignore,
voiceServerUpdate: events.voiceServerUpdate ?? ignore,
voiceStateUpdate: events.voiceStateUpdate ?? ignore,
channelCreate: events.channelCreate ?? ignore, channelCreate: events.channelCreate ?? ignore,
voiceChannelLeave: events.voiceChannelLeave ?? ignore, voiceChannelLeave: events.voiceChannelLeave ?? ignore,
channelDelete: events.channelDelete ?? ignore, channelDelete: events.channelDelete ?? ignore,
@@ -1018,10 +1040,13 @@ export interface Transformers {
application: typeof transformApplication; application: typeof transformApplication;
team: typeof transformTeam; team: typeof transformTeam;
emoji: typeof transformEmoji; emoji: typeof transformEmoji;
activity: typeof transformActivity;
presence: typeof transformPresence;
} }
export function createTransformers(options: Partial<Transformers>) { export function createTransformers(options: Partial<Transformers>) {
return { return {
activity: options.activity || transformActivity,
application: options.application || transformApplication, application: options.application || transformApplication,
channel: options.channel || transformChannel, channel: options.channel || transformChannel,
emoji: options.emoji || transformEmoji, emoji: options.emoji || transformEmoji,
@@ -1031,6 +1056,7 @@ export function createTransformers(options: Partial<Transformers>) {
invite: options.invite || transformInvite, invite: options.invite || transformInvite,
member: options.member || transformMember, member: options.member || transformMember,
message: options.message || transformMessage, message: options.message || transformMessage,
presence: options.presence || transformPresence,
role: options.role || transformRole, role: options.role || transformRole,
user: options.user || transformUser, user: options.user || transformUser,
team: options.team || transformTeam, team: options.team || transformTeam,
@@ -1149,7 +1175,7 @@ export interface EventHandlers {
interactionCreate: (bot: Bot, interaction: DiscordenoInteraction) => any; interactionCreate: (bot: Bot, interaction: DiscordenoInteraction) => any;
integrationCreate: (bot: Bot, integration: DiscordenoIntegration) => any; integrationCreate: (bot: Bot, integration: DiscordenoIntegration) => any;
integrationDelete: (bot: Bot, payload: { id: bigint; guildId: bigint; applicationId?: bigint }) => 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; inviteCreate: (bot: Bot, invite: DiscordenoInvite) => any;
inviteDelete: ( inviteDelete: (
bot: Bot, bot: Bot,
@@ -1207,9 +1233,21 @@ export interface EventHandlers {
guildId?: bigint; guildId?: bigint;
} }
) => any; ) => 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; channelCreate: (bot: Bot, channel: DiscordenoChannel) => any;
dispatchRequirements: (bot: Bot, data: GatewayPayload, shardId: number) => 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; channelDelete: (bot: Bot, channel: DiscordenoChannel) => any;
channelPinsUpdate: (bot: Bot, data: { guildId?: bigint; channelId: bigint; lastPinTimestamp?: number }) => any; channelPinsUpdate: (bot: Bot, data: { guildId?: bigint; channelId: bigint; lastPinTimestamp?: number }) => any;
channelUpdate: (bot: Bot, channel: DiscordenoChannel, oldChannel: DiscordenoChannel) => any; channelUpdate: (bot: Bot, channel: DiscordenoChannel, oldChannel: DiscordenoChannel) => any;
+10 -6
View File
@@ -6,18 +6,22 @@ import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export async function handleChannelDelete(bot: Bot, data: SnakeCasedPropertiesDeep<DiscordGatewayPayload>) { export async function handleChannelDelete(bot: Bot, data: SnakeCasedPropertiesDeep<DiscordGatewayPayload>) {
const payload = data.d as SnakeCasedPropertiesDeep<Channel>; const payload = data.d as SnakeCasedPropertiesDeep<Channel>;
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 (!channel) return;
if ([DiscordChannelTypes.GuildVoice, DiscordChannelTypes.GuildStageVoice].includes(channel.type)) { if (guild && [DiscordChannelTypes.GuildVoice, DiscordChannelTypes.GuildStageVoice].includes(channel.type)) {
channel.voiceStates?.forEach((vs, key) => { guild.voiceStates?.forEach((vs, key) => {
if (vs.channelId !== channel.id) return; if (vs.channelId !== channel.id) return;
// Since this channel was deleted all voice states for this channel should be deleted // 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 ( } else if (
[ [
@@ -27,7 +31,7 @@ export async function handleChannelDelete(bot: Bot, data: SnakeCasedPropertiesDe
DiscordChannelTypes.GuildNews, DiscordChannelTypes.GuildNews,
].includes(payload.type) ].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), channelId: bot.transformers.snowflake(payload.id),
}); });
} }
@@ -6,5 +6,5 @@ import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export async function handleGuildIntegrationsUpdate(bot: Bot, data: DiscordGatewayPayload) { export async function handleGuildIntegrationsUpdate(bot: Bot, data: DiscordGatewayPayload) {
const payload = data.d as SnakeCasedPropertiesDeep<GuildIntegrationsUpdate>; const payload = data.d as SnakeCasedPropertiesDeep<GuildIntegrationsUpdate>;
bot.events.integrationsUpdate(bot, { guildId: bot.transformers.snowflake(payload.guild_id) }); bot.events.integrationUpdate(bot, { guildId: bot.transformers.snowflake(payload.guild_id) });
} }
+11 -8
View File
@@ -1,14 +1,17 @@
import { eventHandlers } from "../../bot.ts"; import { Bot } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import type { PresenceUpdate } from "../../types/activity/presence_update.ts"; import type { PresenceUpdate } from "../../types/activity/presence_update.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.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) { export async function handlePresenceUpdate(bot: Bot, data: DiscordGatewayPayload) {
const payload = data.d as PresenceUpdate; const payload = data.d as SnakeCasedPropertiesDeep<PresenceUpdate>;
const oldPresence = await cacheHandlers.get("presences", snowflakeToBigint(payload.user.id)); const id = bot.transformers.snowflake(payload.user.id);
await cacheHandlers.set("presences", snowflakeToBigint(payload.user.id), payload);
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);
} }
+15 -62
View File
@@ -1,67 +1,20 @@
import { eventHandlers, setApplicationId, setBotId } from "../../bot.ts"; import { Bot } from "../../bot.ts";
import { cache } from "../../cache.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { Ready } from "../../types/gateway/ready.ts"; import type { DiscordReady } from "../../types/gateway/ready.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
import { DiscordenoShard, ws } from "../../ws/ws.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 // 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 (!bot.id) bot.id = bot.transformers.snowflake(payload.user.id);
if (cache.isReady) return; if (!bot.applicationId) bot.applicationId = bot.transformers.snowflake(payload.application.id);
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?.();
} }
+14 -3
View File
@@ -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 { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { TypingStart } from "../../types/misc/typing_start.ts"; import type { TypingStart } from "../../types/misc/typing_start.ts";
import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
export function handleTypingStart(data: DiscordGatewayPayload) { export function handleTypingStart(bot: Bot, data: DiscordGatewayPayload) {
eventHandlers.typingStart?.(data.d as TypingStart); const payload = data.d as SnakeCasedPropertiesDeep<TypingStart>;
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,
});
} }
+7 -30
View File
@@ -1,35 +1,12 @@
import { eventHandlers } from "../../bot.ts"; import { Bot } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import { memberToggles } from "../../structures/member.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { User } from "../../types/users/user.ts"; import type { User } from "../../types/users/user.ts";
import { snowflakeToBigint } from "../../util/bigint.ts"; import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
import { iconHashToBigInt } from "../../util/hash.ts";
export async function handleUserUpdate(data: DiscordGatewayPayload) { export async function handleUserUpdate(bot: Bot, data: DiscordGatewayPayload) {
const userData = data.d as User; const payload = data.d as SnakeCasedPropertiesDeep<User>;
const user = bot.transformers.user(bot, payload);
await bot.cache.users.set(user.id, user);
const member = await cacheHandlers.get("members", snowflakeToBigint(userData.id)); bot.events.botUpdate(bot, user);
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);
} }
+12 -14
View File
@@ -1,21 +1,19 @@
import { eventHandlers } from "../../bot.ts"; import { Bot } 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 { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { GuildRoleCreate } from "../../types/guilds/guild_role_create.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) { export async function handleGuildRoleCreate(bot: Bot, data: DiscordGatewayPayload) {
const payload = data.d as GuildRoleCreate; const payload = data.d as SnakeCasedPropertiesDeep<GuildRoleCreate>;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId));
const guildId = bot.transformers.snowflake(payload.guild_id);
const guild = await bot.cache.guilds.get(guildId);
if (!guild) return; if (!guild) return;
const role = await structures.createDiscordenoRole({ const role = bot.transformers.role(bot, { role: payload.role, guildId });
...payload,
guildId: guild.id,
});
guild.roles = guild.roles.set(snowflakeToBigint(payload.role.id), role);
await cacheHandlers.set("guilds", guild.id, guild);
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);
} }
+12 -10
View File
@@ -1,21 +1,23 @@
import { eventHandlers } from "../../bot.ts"; import { Bot } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { GuildRoleDelete } from "../../types/guilds/guild_role_delete.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) { export async function handleGuildRoleDelete(bot: Bot, data: DiscordGatewayPayload) {
const payload = data.d as GuildRoleDelete; const payload = data.d as SnakeCasedPropertiesDeep<GuildRoleDelete>;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); const guildId = bot.transformers.snowflake(payload.guild_id);
const guild = await bot.cache.guilds.get(guildId);
if (!guild) return; 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); 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. // 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 });
} }
+10 -17
View File
@@ -1,24 +1,17 @@
import { eventHandlers } from "../../bot.ts"; import { Bot } 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 { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { GuildRoleUpdate } from "../../types/guilds/guild_role_update.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) { export async function handleGuildRoleUpdate(bot: Bot, data: DiscordGatewayPayload) {
const payload = data.d as GuildRoleUpdate; const payload = data.d as SnakeCasedPropertiesDeep<GuildRoleUpdate>;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); const guildId = bot.transformers.snowflake(payload.guild_id);
const guild = await bot.cache.guilds.get(guildId);
if (!guild) return; if (!guild) return;
const cachedRole = guild.roles.get(snowflakeToBigint(payload.role.id)); const role = bot.transformers.role(bot, { role: payload.role, guildId });
if (!cachedRole) return; guild.roles = guild.roles.set(role.id, role);
await bot.cache.guilds.set(guild.id, guild);
const role = await structures.createDiscordenoRole({ bot.events.roleUpdate(bot, guild, role, guild.roles.get(role.id));
...payload,
guildId: guild.id,
});
guild.roles.set(snowflakeToBigint(payload.role.id), role);
await cacheHandlers.set("guilds", guild.id, guild);
eventHandlers.roleUpdate?.(guild, role, cachedRole);
} }
+9 -9
View File
@@ -1,14 +1,14 @@
import { eventHandlers } from "../../bot.ts"; import { Bot } from "../../bot.ts";
import { cacheHandlers } from "../../cache.ts"; import { SnakeCasedPropertiesDeep } from "../../types/util.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { VoiceServerUpdate } from "../../types/voice/voice_server_update.ts"; import type { VoiceServerUpdate } from "../../types/voice/voice_server_update.ts";
import { snowflakeToBigint } from "../../util/bigint.ts";
export async function handleVoiceServerUpdate(data: DiscordGatewayPayload) { export async function handleVoiceServerUpdate(bot: Bot, data: DiscordGatewayPayload) {
const payload = data.d as VoiceServerUpdate; const payload = data.d as SnakeCasedPropertiesDeep<VoiceServerUpdate>;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); bot.events.voiceServerUpdate(bot, {
if (!guild) return; token: payload.token,
guildId: bot.transformers.snowflake(payload.guild_id),
eventHandlers.voiceServerUpdate?.(payload, guild); endpoint: payload.endpoint ?? undefined,
});
} }
+15 -42
View File
@@ -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 { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { VoiceState } from "../../types/voice/voice_state.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) { export async function handleVoiceStateUpdate(bot: Bot, data: DiscordGatewayPayload) {
const payload = data.d as VoiceState; const payload = data.d as SnakeCasedPropertiesDeep<VoiceState>;
if (!payload.guildId) return; if (!payload.guild_id) return;
const guild = await cacheHandlers.get("guilds", snowflakeToBigint(payload.guildId)); const guildId = bot.transformers.snowflake(payload.guild_id);
if (!guild) return; const voiceState = bot.transformers.voiceState(bot, { voiceState: payload, guildId });
const member = payload.member const guild = await bot.cache.guilds.get(guildId);
? await structures.createDiscordenoMember(payload.member, guild.id) if (guild) {
: await cacheHandlers.get("members", snowflakeToBigint(payload.userId)); guild.voiceStates.set(voiceState.userId, voiceState);
if (!member) return; await bot.cache.guilds.set(guild.id, guild);
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);
}
} }
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 });
} }
+8 -5
View File
@@ -1,9 +1,12 @@
import { eventHandlers } from "../../bot.ts";
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts"; import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
import type { WebhookUpdate } from "../../types/webhooks/webhooks_update.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) { export function handleWebhooksUpdate(bot: Bot, data: DiscordGatewayPayload) {
const options = data.d as WebhookUpdate; const payload = data.d as SnakeCasedPropertiesDeep<WebhookUpdate>;
eventHandlers.webhooksUpdate?.(snowflakeToBigint(options.channelId), snowflakeToBigint(options.guildId)); bot.events.webhooksUpdate(bot, {
channelId: bot.transformers.snowflake(payload.channel_id),
guildId: bot.transformers.snowflake(payload.guild_id),
});
} }
+82
View File
@@ -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<Activity>): 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[];
}
+42
View File
@@ -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<PresenceUpdate>): 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;
}
+4 -2
View File
@@ -16,7 +16,7 @@ export function transformVoiceState(
(payload.voiceState.self_video ? 32n : 0n) | (payload.voiceState.self_video ? 32n : 0n) |
(payload.voiceState.suppress ? 64n : 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, sessionId: payload.voiceState.session_id,
channelId: payload.voiceState.channel_id ? bot.transformers.snowflake(payload.voiceState.channel_id) : undefined, 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<VoiceState, "channelId" | "guildId" | "userId" | "member"> { export interface DiscordenoVoiceState {
/** The guild id */ /** The guild id */
guildId: bigint; guildId: bigint;
/** The channel id this user is connected to */ /** The channel id this user is connected to */
@@ -36,4 +36,6 @@ export interface DiscordenoVoiceState extends Omit<VoiceState, "channelId" | "gu
userId: bigint; userId: bigint;
/** Holds all the boolean toggles. */ /** Holds all the boolean toggles. */
bitfield: bigint; bitfield: bigint;
/** The time at which the user requested to speak */
requestToSpeakTimestamp?: number;
} }
+1
View File
@@ -1,2 +1,3 @@
/** https://discord.com/developers/docs/topics/gateway#update-status-status-types */ /** https://discord.com/developers/docs/topics/gateway#update-status-status-types */
export type DiscordStatusTypes = "online" | "dnd" | "idle" | "invisible" | "offline"; export type DiscordStatusTypes = "online" | "dnd" | "idle" | "invisible" | "offline";