From 4510dc88c015bbdb94ced00aaec3af560a6ca001 Mon Sep 17 00:00:00 2001 From: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com> Date: Fri, 2 Jul 2021 13:24:27 -0400 Subject: [PATCH] feat: requiredStructureProperties (#1068) * example concept * add to guilds also * support members * support message struct * add role support * add support for voice states --- src/cache.ts | 21 +++++++++ src/structures/channel.ts | 24 ++++++----- src/structures/guild.ts | 57 +++++++++++++++++------- src/structures/member.ts | 33 ++++++++------ src/structures/message.ts | 81 ++++++++++++++++++++++++----------- src/structures/role.ts | 30 +++++++------ src/structures/voice_state.ts | 30 ++++++++----- 7 files changed, 191 insertions(+), 85 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index df776e0a0..eeb863a99 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -8,6 +8,12 @@ import type { PresenceUpdate } from "./types/activity/presence_update.ts"; import type { Emoji } from "./types/emojis/emoji.ts"; import { DiscordenoThread } from "./util/transformers/channel_to_thread.ts"; import { Collection } from "./util/collection.ts"; +import { Channel } from "./types/channels/channel.ts"; +import { Guild } from "./types/guilds/guild.ts"; +import { GuildMemberWithUser } from "./types/members/guild_member.ts"; +import { Message } from "./types/messages/message.ts"; +import { Role } from "./types/permissions/role.ts"; +import { VoiceState } from "./types/voice/voice_state.ts"; export const cache = { isReady: false, @@ -37,6 +43,21 @@ export const cache = { dispatchedGuildIds: new Set(), dispatchedChannelIds: new Set(), threads: new Collection(), + /** ADVANCED USER ONLY: Please ask for help before modifying these. The properties that you want to use for your bot's structures. If you do not set any properties, all properties will be used by default. */ + requiredStructureProperties: { + /** Only these properties will be added to memory for your channels. */ + channels: new Set(), + /** Only these properties will be added to memory for your guilds. */ + guilds: new Set(), + /** Only these properties will be added to memory for your members. */ + members: new Set(), + /** Only these properties will be added to memory for your messages. */ + messages: new Set(), + /** Only these properties will be added to memory for your roles. */ + roles: new Set(), + /** Only these properties will be added to memory for your voice states. */ + voiceStates: new Set(), + }, }; function messageSweeper(message: DiscordenoMessage) { diff --git a/src/structures/channel.ts b/src/structures/channel.ts index 1f2ec7c28..fb84fc755 100644 --- a/src/structures/channel.ts +++ b/src/structures/channel.ts @@ -109,32 +109,36 @@ const baseChannel: Partial = { export async function createDiscordenoChannel(data: Channel, guildId?: bigint) { const { lastPinTimestamp, permissionOverwrites = [], ...rest } = data; + const requiredPropsSize = cache.requiredStructureProperties.channels.size; + const props: Record = {}; - (Object.keys(rest) as (keyof typeof rest)[]).forEach((key) => { + for (const key of Object.keys(rest) as (keyof typeof rest)[]) { eventHandlers.debug?.("loop", `Running forEach loop in createDiscordenoChannel function.`); + // If empty then support all, otherwise we only allow the ones user added + if (requiredPropsSize && !cache.requiredStructureProperties.channels.has(key)) continue; props[key] = createNewProp( CHANNEL_SNOWFLAKES.includes(key) ? (rest[key] ? snowflakeToBigint(rest[key] as string) : undefined) : rest[key] ); - }); + } // Set the guildId seperately because sometimes guildId is not included - props.guildId = createNewProp(snowflakeToBigint(guildId?.toString() || data.guildId || "")); + if (!requiredPropsSize || cache.requiredStructureProperties.channels.has("guildId")) + props.guildId = createNewProp(snowflakeToBigint(guildId?.toString() || data.guildId || "")); - const channel: DiscordenoChannel = Object.create(baseChannel, { - ...props, - lastPinTimestamp: createNewProp(lastPinTimestamp ? Date.parse(lastPinTimestamp) : undefined), - permissionOverwrites: createNewProp( + if (!requiredPropsSize || cache.requiredStructureProperties.channels.has("lastPinTimestamp")) + props.lastPinTimestamp = createNewProp(lastPinTimestamp ? Date.parse(lastPinTimestamp) : undefined); + if (!requiredPropsSize || cache.requiredStructureProperties.channels.has("permissionOverwrites")) + props.permissionOverwrites = createNewProp( permissionOverwrites.map((o) => ({ ...o, id: snowflakeToBigint(o.id), allow: snowflakeToBigint(o.allow), deny: snowflakeToBigint(o.deny), })) - ), - }); + ); - return channel; + return Object.create(baseChannel, props) as DiscordenoChannel; } export interface DiscordenoChannel diff --git a/src/structures/guild.ts b/src/structures/guild.ts index 080468775..19bf93940 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -287,27 +287,37 @@ export async function createDiscordenoGuild(data: Guild, shardId: number) { ); const props: Record> = {}; - (Object.keys(rest) as (keyof typeof rest)[]).forEach((key) => { + for (const key of Object.keys(rest) as (keyof typeof rest)[]) { eventHandlers.debug?.("loop", `Running for of loop in createDiscordenoGuild function.`); + // If its empty default allows all, otherwise only allow those users required. + if (cache.requiredStructureProperties.guilds.size && !cache.requiredStructureProperties.guilds.has(key)) { + continue; + } + const toggleBits = guildToggles[key as keyof typeof guildToggles]; if (toggleBits) { bitfield |= rest[key] ? toggleBits : 0n; - return; + continue; } props[key] = createNewProp( GUILD_SNOWFLAKES.includes(key) ? (rest[key] ? snowflakeToBigint(rest[key] as string) : undefined) : rest[key] ); - }); + } const hashes = [ { name: "icon", toggle: guildToggles.animatedIcon, value: icon }, { name: "banner", toggle: guildToggles.animatedBanner, value: banner }, { name: "splash", toggle: guildToggles.animatedSplash, value: splash }, - ]; + ] as const; for (const hash of hashes) { + // If its empty default allows all, otherwise only allow those users required. + if (cache.requiredStructureProperties.guilds.size && !cache.requiredStructureProperties.guilds.has(hash.name)) { + continue; + } + const transformed = hash.value ? iconHashToBigInt(hash.value) : undefined; if (transformed) { props[hash.name] = createNewProp(hash.value); @@ -315,20 +325,35 @@ export async function createDiscordenoGuild(data: Guild, shardId: number) { } } - const guild: DiscordenoGuild = Object.create(baseGuild, { - ...props, - shardId: createNewProp(shardId), - roles: createNewProp(new Collection(roles.map((r: DiscordenoRole) => [r.id, r]))), - joinedAt: createNewProp(Date.parse(joinedAt)), - presences: createNewProp(new Collection(presences.map((p) => [snowflakeToBigint(p.user!.id), p]))), - memberCount: createNewProp(memberCount), - emojis: createNewProp(new Collection(emojis.map((emoji) => [snowflakeToBigint(emoji.id!), emoji]))), - voiceStates: createNewProp(new Collection(voiceStateStructs.map((vs) => [vs.userId, vs]))), - bitfield: createNewProp(bitfield), - }); + if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("roles")) { + props.roles = createNewProp(new Collection(roles.map((r: DiscordenoRole) => [r.id, r]))); + } + if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("joinedAt")) { + props.joinedAt = createNewProp(Date.parse(joinedAt)); + } + if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("presences")) { + props.presences = createNewProp(new Collection(presences.map((p) => [snowflakeToBigint(p.user!.id), p]))); + } + if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("memberCount")) { + props.memberCount = createNewProp(memberCount); + } + if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("emojis")) { + props.emojis = createNewProp(new Collection(emojis.map((emoji) => [snowflakeToBigint(emoji.id!), emoji]))); + } + if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("voiceStates")) { + props.voiceStates = createNewProp(new Collection(voiceStateStructs.map((vs) => [vs.userId, vs]))); + } + // @ts-ignore allow using these props + if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("shardId")) { + props.shardId = createNewProp(shardId); + } + // @ts-ignore allow using these props + if (!cache.requiredStructureProperties.guilds.size || cache.requiredStructureProperties.guilds.has("bitfield")) { + props.bitfield = createNewProp(bitfield); + } + const guild: DiscordenoGuild = Object.create(baseGuild, props); await cacheMembers(guild.id, members as GuildMemberWithUser[]); - return guild; } diff --git a/src/structures/member.ts b/src/structures/member.ts index ad2a967a0..b03b59554 100644 --- a/src/structures/member.ts +++ b/src/structures/member.ts @@ -137,39 +137,48 @@ export async function createDiscordenoMember( let bitfield = 0n; const props: Record> = {}; - (Object.keys(user) as (keyof typeof user)[]).forEach((key) => { + + for (const key of Object.keys(user) as (keyof typeof user)[]) { eventHandlers.debug?.("loop", `Running for of for Object.keys(user) loop in DiscordenoMember function.`); + // @ts-ignore allow user prop args + if (cache.requiredStructureProperties.members.size && !cache.requiredStructureProperties.members.has(key)) continue; + const toggleBits = memberToggles[key as keyof typeof memberToggles]; if (toggleBits) { bitfield |= user[key] ? toggleBits : 0n; - return; + continue; } if (key === "avatar") { const transformed = user[key] ? iconHashToBigInt(user[key] as string) : undefined; if (transformed?.animated) bitfield |= memberToggles.animatedAvatar; props.avatar = createNewProp(transformed?.bigint); - return; + continue; } if (key === "discriminator") { props.discriminator = createNewProp(Number(user[key])); - return; + continue; } props[key] = createNewProp( MEMBER_SNOWFLAKES.includes(key) ? (user[key] ? snowflakeToBigint(user[key] as string) : undefined) : user[key] ); - }); + } - const member: DiscordenoMember = Object.create(baseMember, { - ...props, - /** The guild related data mapped by guild id */ - guilds: createNewProp(new Collection()), - bitfield: createNewProp(bitfield), - cachedAt: createNewProp(Date.now()), - }); + /** The guild related data mapped by guild id */ + // @ts-ignore allow this prop to be required + if (!cache.requiredStructureProperties.members.size || cache.requiredStructureProperties.members.has("guilds")) + props.guilds = createNewProp(new Collection()); + // @ts-ignore allow this prop to be required + if (!cache.requiredStructureProperties.members.size || cache.requiredStructureProperties.members.has("bitfield")) + props.bitfield = createNewProp(bitfield); + // @ts-ignore allow this prop to be required + if (!cache.requiredStructureProperties.members.size || cache.requiredStructureProperties.members.has("cachedAt")) + props.cachedAt = createNewProp(Date.now()); + + const member: DiscordenoMember = Object.create(baseMember, props); const cached = await cacheHandlers.get("members", snowflakeToBigint(user.id)); if (cached) { diff --git a/src/structures/message.ts b/src/structures/message.ts index ef1fb78c7..5772e86be 100644 --- a/src/structures/message.ts +++ b/src/structures/message.ts @@ -116,16 +116,16 @@ const baseMessage: Partial = { async alert(content, timeout = 10, reason = "") { if (this.guildId) { return await sendMessage(this.channelId!, content).then((response) => { - response.delete(reason, timeout * 1000).catch(console.error); + response?.delete(reason, timeout * 1000).catch(console.error); }); } return await sendDirectMessage(this.authorId!, content).then((response) => { - response.delete(reason, timeout * 1000).catch(console.error); + response?.delete(reason, timeout * 1000).catch(console.error); }); }, async alertReply(content, timeout = 10, reason = "") { - return await this.reply!(content).then((response) => response.delete(reason, timeout * 1000).catch(console.error)); + return await this.reply!(content).then((response) => response?.delete(reason, timeout * 1000).catch(console.error)); }, removeAllReactions() { return removeAllReactions(this.channelId!, this.id!); @@ -213,29 +213,41 @@ export async function createDiscordenoMessage(data: Message) { let bitfield = 0n; const props: Record> = {}; - (Object.keys(rest) as (keyof typeof rest)[]).forEach((key) => { + + const requiredPropsSize = cache.requiredStructureProperties.messages.size; + + for (const key of Object.keys(rest) as (keyof typeof rest)[]) { eventHandlers.debug?.("loop", `Running for of loop in createDiscordenoMessage function.`); + // If empty all are allowed, otherwise check if this prop is allowed + if (requiredPropsSize && !cache.requiredStructureProperties.messages.has(key)) continue; + const toggleBits = messageToggles[key as keyof typeof messageToggles]; if (toggleBits) { bitfield |= rest[key] ? toggleBits : 0n; - return; + continue; } // Don't add member to props since it would overwrite the message.member getter // thread should not be cached on a message - if (["member", "thread"].includes(key)) return; + if (["member", "thread"].includes(key)) continue; props[key] = createNewProp( MESSAGE_SNOWFLAKES.includes(key) ? (rest[key] ? snowflakeToBigint(rest[key] as string) : undefined) : rest[key] ); - }); + } if (rest.thread) await cacheHandlers.set("threads", snowflakeToBigint(data.id), channelToThread(rest.thread)); - props.authorId = createNewProp(snowflakeToBigint(author.id)); - props.isBot = createNewProp(author.bot || false); - props.tag = createNewProp(`${author.username}#${author.discriminator.toString().padStart(4, "0")}`); + // @ts-ignore allow this prop + if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("authorId")) + props.authorId = createNewProp(snowflakeToBigint(author.id)); + // @ts-ignore allow this prop + if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("isBot")) + props.isBot = createNewProp(author.bot || false); + // @ts-ignore allow this prop + if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("tag")) + props.tag = createNewProp(`${author.username}#${author.discriminator.toString().padStart(4, "0")}`); // Discord doesnt give guild id for getMessage() so this will fill it in const guildIdFinal = @@ -243,13 +255,28 @@ export async function createDiscordenoMessage(data: Message) { (await cacheHandlers.get("channels", snowflakeToBigint(data.channelId)))?.guildId || 0n; - const message: DiscordenoMessage = Object.create(baseMessage, { - ...props, - content: createNewProp(data.content || ""), - guildId: createNewProp(guildIdFinal), - mentionedUserIds: createNewProp(mentions.map((m) => snowflakeToBigint(m.id))), - mentionedRoleIds: createNewProp(mentionRoles.map((id) => snowflakeToBigint(id))), - mentionedChannelIds: createNewProp([ + if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("content")) + props.content = createNewProp(data.content || ""); + if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("guildId")) + props.guildId = createNewProp(guildIdFinal); + if ( + !requiredPropsSize || + // @ts-ignore allow this prop + cache.requiredStructureProperties.messages.has("mentionedUserIds") + ) + props.mentionedUserIds = createNewProp(mentions.map((m) => snowflakeToBigint(m.id))); + if ( + !requiredPropsSize || + // @ts-ignore allow this prop + cache.requiredStructureProperties.messages.has("mentionedRoleIds") + ) + props.mentionedRoleIds = createNewProp(mentionRoles.map((id) => snowflakeToBigint(id))); + if ( + !requiredPropsSize || + // @ts-ignore allow this prop + cache.requiredStructureProperties.messages.has("mentionedChannelIds") + ) + props.mentionedChannelIds = createNewProp([ // Keep any ids that discord sends ...mentionChannels.map((m) => snowflakeToBigint(m.id)), // Add any other ids that can be validated in a channel mention format @@ -257,10 +284,13 @@ export async function createDiscordenoMessage(data: Message) { // converts the <#123> into 123 snowflakeToBigint(text.substring(2, text.length - 1)) ), - ]), - timestamp: createNewProp(Date.parse(data.timestamp)), - editedTimestamp: createNewProp(editedTimestamp ? Date.parse(editedTimestamp) : undefined), - messageReference: createNewProp( + ]); + if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("timestamp")) + props.timestamp = createNewProp(Date.parse(data.timestamp)); + if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("editedTimestamp")) + props.editedTimestamp = createNewProp(editedTimestamp ? Date.parse(editedTimestamp) : undefined); + if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("messageReference")) + props.messageReference = createNewProp( messageReference ? { messageId: messageReference.messageId ? snowflakeToBigint(messageReference.messageId) : undefined, @@ -268,11 +298,12 @@ export async function createDiscordenoMessage(data: Message) { guildId: messageReference.guildId ? snowflakeToBigint(messageReference.guildId) : undefined, } : undefined - ), - bitfield: createNewProp(bitfield), - }); + ); + // @ts-ignore allow this prop + if (!requiredPropsSize || cache.requiredStructureProperties.messages.has("bitfield")) + props.bitfield = createNewProp(bitfield); - return message; + return Object.create(baseMessage, props) as DiscordenoMessage; } export interface DiscordenoMessage diff --git a/src/structures/role.ts b/src/structures/role.ts index 9a1105a39..b5c217463 100644 --- a/src/structures/role.ts +++ b/src/structures/role.ts @@ -115,30 +115,36 @@ export async function createDiscordenoRole( let bitfield = 0n; const props: Record> = {}; - (Object.keys(rest) as (keyof typeof rest)[]).forEach((key) => { + for (const key of Object.keys(rest) as (keyof typeof rest)[]) { eventHandlers.debug?.("loop", `Running for of loop in createDiscordenoRole function.`); const toggleBits = roleToggles[key as keyof typeof roleToggles]; if (toggleBits) { bitfield |= rest[key] ? toggleBits : 0n; - return; + continue; } props[key] = createNewProp( ROLE_SNOWFLAKES.includes(key) ? (rest[key] ? snowflakeToBigint(rest[key] as string) : undefined) : rest[key] ); - }); + } - const role: DiscordenoRole = Object.create(baseRole, { - ...props, - permissions: createNewProp(BigInt(rest.permissions)), - botId: createNewProp(tags.botId ? snowflakeToBigint(tags.botId) : undefined), - isNitroBoostRole: createNewProp("premiumSubscriber" in tags), - integrationId: createNewProp(tags.integrationId ? snowflakeToBigint(tags.integrationId) : undefined), - bitfield: createNewProp(bitfield), - }); + if (!cache.requiredStructureProperties.roles.size || cache.requiredStructureProperties.roles.has("permissions")) + props.permissions = createNewProp(BigInt(rest.permissions)); + // @ts-ignore allow this prop + if (!cache.requiredStructureProperties.roles.size || cache.requiredStructureProperties.roles.has("botId")) + props.botId = createNewProp(tags.botId ? snowflakeToBigint(tags.botId) : undefined); + // @ts-ignore allow this prop + if (!cache.requiredStructureProperties.roles.size || cache.requiredStructureProperties.roles.has("isNitroBoostRole")) + props.isNitroBoostRole = createNewProp("premiumSubscriber" in tags); + // @ts-ignore allow this prop + if (!cache.requiredStructureProperties.roles.size || cache.requiredStructureProperties.roles.has("integrationId")) + props.integrationId = createNewProp(tags.integrationId ? snowflakeToBigint(tags.integrationId) : undefined); + // @ts-ignore allow this prop + if (!cache.requiredStructureProperties.roles.size || cache.requiredStructureProperties.roles.has("bitfield")) + props.bitfield = createNewProp(bitfield); - return role; + return Object.create(baseRole, props) as DiscordenoRole; } export interface DiscordenoRole extends Omit { diff --git a/src/structures/voice_state.ts b/src/structures/voice_state.ts index dd82b0f94..7dd6e7e87 100644 --- a/src/structures/voice_state.ts +++ b/src/structures/voice_state.ts @@ -81,16 +81,20 @@ export async function createDiscordenoVoiceState(guildId: bigint, data: VoiceSta let bitfield = 0n; const props: Record> = {}; - (Object.keys(data) as (keyof typeof data)[]).forEach((key) => { + for (const key of Object.keys(data) as (keyof typeof data)[]) { eventHandlers.debug?.("loop", `Running for of loop in createDiscordenoVoiceState function.`); + // if is empty allow all, otherwise check if prop is required + if (cache.requiredStructureProperties.voiceStates.size && !cache.requiredStructureProperties.voiceStates.has(key)) + continue; + // We don't need to cache member twice. It will be in cache.members - if (key === "member") return; + if (key === "member") continue; const toggleBits = voiceStateToggles[key as keyof typeof voiceStateToggles]; if (toggleBits) { bitfield |= data[key] ? toggleBits : 0n; - return; + continue; } props[key] = createNewProp( @@ -100,15 +104,21 @@ export async function createDiscordenoVoiceState(guildId: bigint, data: VoiceSta : undefined : data[key] ); - }); + } - const voiceState: DiscordenoVoiceState = Object.create(baseRole, { - ...props, - guildId: createNewProp(guildId), - bitfield: createNewProp(bitfield), - }); + if ( + !cache.requiredStructureProperties.voiceStates.size || + cache.requiredStructureProperties.voiceStates.has("guildId") + ) + props.guildId = createNewProp(guildId); + if ( + !cache.requiredStructureProperties.voiceStates.size || + // @ts-ignore allow this prop + cache.requiredStructureProperties.voiceStates.has("bitfield") + ) + props.bitfield = createNewProp(bitfield); - return voiceState; + return Object.create(baseRole, props) as DiscordenoVoiceState; } export interface DiscordenoVoiceState extends Omit {