feat: requiredStructureProperties (#1068)

* example concept

* add to guilds also

* support members

* support message struct

* add role support

* add support for voice states
This commit is contained in:
Skillz4Killz
2021-07-02 13:24:27 -04:00
committed by GitHub
parent bb52ae5e15
commit 4510dc88c0
7 changed files with 191 additions and 85 deletions

View File

@@ -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<bigint>(),
dispatchedChannelIds: new Set<bigint>(),
threads: new Collection<bigint, DiscordenoThread>(),
/** 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<keyof Channel>(),
/** Only these properties will be added to memory for your guilds. */
guilds: new Set<keyof Guild>(),
/** Only these properties will be added to memory for your members. */
members: new Set<keyof GuildMemberWithUser>(),
/** Only these properties will be added to memory for your messages. */
messages: new Set<keyof Message>(),
/** Only these properties will be added to memory for your roles. */
roles: new Set<keyof Role>(),
/** Only these properties will be added to memory for your voice states. */
voiceStates: new Set<keyof VoiceState>(),
},
};
function messageSweeper(message: DiscordenoMessage) {

View File

@@ -109,32 +109,36 @@ const baseChannel: Partial<DiscordenoChannel> = {
export async function createDiscordenoChannel(data: Channel, guildId?: bigint) {
const { lastPinTimestamp, permissionOverwrites = [], ...rest } = data;
const requiredPropsSize = cache.requiredStructureProperties.channels.size;
const props: Record<string, PropertyDescriptor> = {};
(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

View File

@@ -287,27 +287,37 @@ export async function createDiscordenoGuild(data: Guild, shardId: number) {
);
const props: Record<string, ReturnType<typeof createNewProp>> = {};
(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;
}

View File

@@ -137,39 +137,48 @@ export async function createDiscordenoMember(
let bitfield = 0n;
const props: Record<string, ReturnType<typeof createNewProp>> = {};
(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<bigint, GuildMember>()),
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<bigint, GuildMember>());
// @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) {

View File

@@ -116,16 +116,16 @@ const baseMessage: Partial<DiscordenoMessage> = {
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<string, ReturnType<typeof createNewProp>> = {};
(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

View File

@@ -115,30 +115,36 @@ export async function createDiscordenoRole(
let bitfield = 0n;
const props: Record<string, ReturnType<typeof createNewProp>> = {};
(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<Role, "tags" | "id" | "permissions"> {

View File

@@ -81,16 +81,20 @@ export async function createDiscordenoVoiceState(guildId: bigint, data: VoiceSta
let bitfield = 0n;
const props: Record<string, ReturnType<typeof createNewProp>> = {};
(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<VoiceState, "channelId" | "guildId" | "userId" | "member"> {