diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f1ab551df..8f082aac8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @Skillz4Killz @itohatweb +* @Skillz4Killz @itohatweb @ayntee diff --git a/README.md b/README.md index 3b99632e4..8bf32fd83 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ unofficial templates: - [Discordeno Template (official)](https://github.com/discordeno/template) - [Serverless Slash Commands Template - (official)](https://github.com/discordeno/slash-commands-template) + (official)](https://github.com/discordeno/serverless-deno-deploy-template) - [Add Your Own!](https://github.com/discordeno/discordeno/pulls) ## Links diff --git a/src/cache.ts b/src/cache.ts index df776e0a0..92c53cc66 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) { @@ -149,58 +170,80 @@ async function get(table: TableName, key: bigint) { return cache[table].get(key); } -function forEach( - table: "threads", - callback: (value: DiscordenoThread, key: bigint, map: Map) => unknown -): void; -function forEach( - table: "guilds", - callback: (value: DiscordenoGuild, key: bigint, map: Map) => unknown -): void; -function forEach( - table: "unavailableGuilds", - callback: (value: number, key: bigint, map: Map) => unknown -): void; -function forEach( - table: "channels", - callback: (value: DiscordenoChannel, key: bigint, map: Map) => unknown -): void; -function forEach( - table: "messages", - callback: (value: DiscordenoMessage, key: bigint, map: Map) => unknown -): void; -function forEach( - table: "members", - callback: (value: DiscordenoMember, key: bigint, map: Map) => unknown -): void; -function forEach(table: TableName, callback: (value: any, key: bigint, map: Map) => unknown) { - return cache[table].forEach(callback); +// callback: (value: DiscordenoThread, key: bigint, map: Map) => void +async function forEach(type: "DELETE_MESSAGES_FROM_CHANNEL", options: { channelId: bigint }): Promise; +async function forEach(type: "DELETE_MESSAGES_FROM_GUILD", options: { guildId: bigint }): Promise; +async function forEach(type: "DELETE_CHANNELS_FROM_GUILD", options: { guildId: bigint }): Promise; +async function forEach(type: "DELETE_GUILD_FROM_MEMBER", options: { guildId: bigint }): Promise; +async function forEach(type: "DELETE_ROLE_FROM_MEMBER", options: { guildId: bigint; roleId: bigint }): Promise; +async function forEach( + type: + | "DELETE_MESSAGES_FROM_CHANNEL" + | "DELETE_MESSAGES_FROM_GUILD" + | "DELETE_CHANNELS_FROM_GUILD" + | "DELETE_GUILD_FROM_MEMBER" + | "DELETE_ROLE_FROM_MEMBER", + options?: Record +) { + if (type === "DELETE_MESSAGES_FROM_CHANNEL") { + cache.messages.forEach((message) => { + if (message.channelId === options?.channelId) cache.messages.delete(message.id); + }); + return; + } + + if (type === "DELETE_MESSAGES_FROM_GUILD") { + cache.messages.forEach((message) => { + if (message.guildId === options?.guildId) cache.messages.delete(message.id); + }); + return; + } + + if (type === "DELETE_CHANNELS_FROM_GUILD") { + cache.channels.forEach((channel) => { + if (channel.guildId === options?.guildId) cache.channels.delete(channel.id); + }); + return; + } + + if (type === "DELETE_GUILD_FROM_MEMBER") { + cache.members.forEach((member) => { + if (!member.guilds.has(options?.guildId as bigint)) return; + + member.guilds.delete(options?.guildId as bigint); + + if (!member.guilds.size) { + return cache.members.delete(member.id); + } + + cache.members.set(member.id, member); + }); + return; + } + + if (type === "DELETE_ROLE_FROM_MEMBER") { + cache.members.forEach((member) => { + // Not in the relevant guild so just skip + if (!member.guilds.has(options?.guildId as bigint)) return; + + const guildMember = member.guilds.get(options?.guildId as bigint)!; + + guildMember.roles = guildMember.roles.filter((id) => id !== (options?.roleId as bigint)); + cache.members.set(member.id, member); + }); + return; + } } async function filter( - table: "threads", - callback: (value: DiscordenoThread, key: bigint) => boolean -): Promise>; -async function filter( - table: "guilds", - callback: (value: DiscordenoGuild, key: bigint) => boolean -): Promise>; -async function filter( - table: "unavailableGuilds", - callback: (value: number, key: bigint) => boolean -): Promise>; -async function filter( - table: "channels", - callback: (value: DiscordenoChannel, key: bigint) => boolean -): Promise>; -async function filter( - table: "messages", - callback: (value: DiscordenoMessage, key: bigint) => boolean -): Promise>; -async function filter( - table: "members", - callback: (value: DiscordenoMember, key: bigint) => boolean + type: "GET_MEMBERS_IN_GUILD", + options: { guildId: bigint } ): Promise>; -async function filter(table: TableName, callback: (value: any, key: bigint) => boolean) { - return cache[table].filter(callback); +async function filter( + type: "GET_MEMBERS_IN_GUILD", + options?: Record +): Promise | undefined> { + if (type === "GET_MEMBERS_IN_GUILD") { + return cache.members.filter((member) => member.guilds.has(options?.guildId as bigint)); + } } diff --git a/src/handlers/channels/CHANNEL_DELETE.ts b/src/handlers/channels/CHANNEL_DELETE.ts index 72feac12a..d0626aabf 100644 --- a/src/handlers/channels/CHANNEL_DELETE.ts +++ b/src/handlers/channels/CHANNEL_DELETE.ts @@ -40,12 +40,7 @@ export async function handleChannelDelete(data: DiscordGatewayPayload) { ].includes(payload.type) ) { await cacheHandlers.delete("channels", snowflakeToBigint(payload.id)); - cacheHandlers.forEach("messages", (message) => { - eventHandlers.debug?.("loop", `Running forEach messages loop in CHANNEL_DELTE file.`); - if (message.channelId === snowflakeToBigint(payload.id)) { - cacheHandlers.delete("messages", message.id); - } - }); + await cacheHandlers.forEach("DELETE_MESSAGES_FROM_CHANNEL", { channelId: snowflakeToBigint(payload.id) }); } await cacheHandlers.delete("channels", snowflakeToBigint(payload.id)); diff --git a/src/handlers/channels/THREAD_DELETE.ts b/src/handlers/channels/THREAD_DELETE.ts index 96a2d7ac6..a713e742f 100644 --- a/src/handlers/channels/THREAD_DELETE.ts +++ b/src/handlers/channels/THREAD_DELETE.ts @@ -11,12 +11,7 @@ export async function handleThreadDelete(data: DiscordGatewayPayload) { if (!cachedChannel) return; await cacheHandlers.delete("threads", snowflakeToBigint(payload.id)); - cacheHandlers.forEach("messages", (message) => { - eventHandlers.debug?.("loop", `Running forEach messages loop in THREAD_DELETE file.`); - if (message.channelId === snowflakeToBigint(payload.id)) { - cacheHandlers.delete("messages", message.id); - } - }); + await cacheHandlers.forEach("DELETE_MESSAGES_FROM_CHANNEL", { channelId: snowflakeToBigint(payload.id) }); eventHandlers.threadDelete?.(cachedChannel); } diff --git a/src/handlers/guilds/GUILD_DELETE.ts b/src/handlers/guilds/GUILD_DELETE.ts index 4235e351e..b2d96db48 100644 --- a/src/handlers/guilds/GUILD_DELETE.ts +++ b/src/handlers/guilds/GUILD_DELETE.ts @@ -23,30 +23,9 @@ export async function handleGuildDelete(data: DiscordGatewayPayload, shardId: nu eventHandlers.guildDelete?.(guild); } - cacheHandlers.forEach("messages", (message) => { - eventHandlers.debug?.("loop", `1. Running forEach messages loop in CHANNEL_DELTE file.`); - if (message.guildId === guild.id) { - cacheHandlers.delete("messages", message.id); - } - }); - - cacheHandlers.forEach("channels", (channel) => { - eventHandlers.debug?.("loop", `2. Running forEach channels loop in CHANNEL_DELTE file.`); - if (channel.guildId === guild.id) { - cacheHandlers.delete("channels", channel.id); - } - }); - - cacheHandlers.forEach("members", (member) => { - eventHandlers.debug?.("loop", `3. Running forEach members loop in CHANNEL_DELTE file.`); - if (!member.guilds.has(guild.id)) return; - - member.guilds.delete(guild.id); - - if (!member.guilds.size) { - return cacheHandlers.delete("members", member.id); - } - - cacheHandlers.set("members", member.id, member); - }); + await Promise.all([ + cacheHandlers.forEach("DELETE_MESSAGES_FROM_GUILD", { guildId: guild.id }), + cacheHandlers.forEach("DELETE_CHANNELS_FROM_GUILD", { guildId: guild.id }), + cacheHandlers.forEach("DELETE_GUILD_FROM_MEMBER", { guildId: guild.id }), + ]); } diff --git a/src/handlers/members/GUILD_MEMBERS_CHUNK.ts b/src/handlers/members/GUILD_MEMBERS_CHUNK.ts index 006eda9f8..61a326e8f 100644 --- a/src/handlers/members/GUILD_MEMBERS_CHUNK.ts +++ b/src/handlers/members/GUILD_MEMBERS_CHUNK.ts @@ -31,7 +31,7 @@ export async function handleGuildMembersChunk(data: DiscordGatewayPayload) { return resolve(new Collection(members.map((m) => [m.id, m]))); } - return resolve(await cacheHandlers.filter("members", (m) => m.guilds.has(guildId))); + return resolve(await cacheHandlers.filter("GET_MEMBERS_IN_GUILD", { guildId })); } } } diff --git a/src/handlers/roles/GUILD_ROLE_DELETE.ts b/src/handlers/roles/GUILD_ROLE_DELETE.ts index 171286373..24f914d50 100644 --- a/src/handlers/roles/GUILD_ROLE_DELETE.ts +++ b/src/handlers/roles/GUILD_ROLE_DELETE.ts @@ -17,18 +17,5 @@ export async function handleGuildRoleDelete(data: DiscordGatewayPayload) { if (cachedRole) eventHandlers.roleDelete?.(guild, cachedRole); // For bots without GUILD_MEMBERS member.roles is never updated breaking permissions checking. - cacheHandlers.forEach("members", (member) => { - eventHandlers.debug?.("loop", `1. Running forEach members loop in GUILD_ROLE_DELETE file.`); - // Not in the relevant guild so just skip. - if (!member.guilds.has(guild.id)) return; - - member.guilds.forEach((g) => { - eventHandlers.debug?.("loop", `2. Running forEach loop in CHANNEL_DELTE file.`); - // Member does not have this role - if (!g.roles.includes(roleId)) return; - // Remove this role from the members cache - g.roles = g.roles.filter((id) => id !== roleId); - cacheHandlers.set("members", member.id, member); - }); - }); + await cacheHandlers.forEach("DELETE_ROLE_FROM_MEMBER", { guildId: guild.id, roleId }); } diff --git a/src/helpers/channels/category_children.ts b/src/helpers/channels/category_children.ts index b3c32e520..c0f31ad83 100644 --- a/src/helpers/channels/category_children.ts +++ b/src/helpers/channels/category_children.ts @@ -1,6 +1,8 @@ -import { cacheHandlers } from "../../cache.ts"; +import { cache } from "../../cache.ts"; -/** Gets an array of all the channels ids that are the children of this category. */ -export async function categoryChildren(id: bigint) { - return await cacheHandlers.filter("channels", (channel) => channel.parentId === id); +/** Gets an array of all the channels ids that are the children of this category. + * ⚠️ This does not work for custom cache users! + */ +export function categoryChildren(parentChannelId: bigint) { + return cache.channels.filter((channel) => channel.parentId === parentChannelId); } diff --git a/src/helpers/channels/delete_channel.ts b/src/helpers/channels/delete_channel.ts index 894cb4883..c9df3bcc1 100644 --- a/src/helpers/channels/delete_channel.ts +++ b/src/helpers/channels/delete_channel.ts @@ -20,7 +20,6 @@ export async function deleteChannel(channelId: bigint, reason?: string) { throw new Error(Errors.UPDATES_CHANNEL_CANNOT_BE_DELETED); } - // TODO(threads): check if this requires guild perms or channel is enough await requireBotGuildPermissions(guild, ["MANAGE_CHANNELS"]); } diff --git a/src/helpers/channels/edit_channel.ts b/src/helpers/channels/edit_channel.ts index d4805f026..05e577954 100644 --- a/src/helpers/channels/edit_channel.ts +++ b/src/helpers/channels/edit_channel.ts @@ -9,7 +9,6 @@ import { endpoints } from "../../util/constants.ts"; import { calculateBits, requireOverwritePermissions } from "../../util/permissions.ts"; import { snakelize } from "../../util/utils.ts"; -//TODO: implement DM group channel edit /** Update a channel's settings. Requires the `MANAGE_CHANNELS` permission for the guild. */ export async function editChannel(channelId: bigint, options: ModifyChannel, reason?: string) { const channel = await cacheHandlers.get("channels", channelId); diff --git a/src/helpers/channels/threads/add_to_thread.ts b/src/helpers/channels/threads/add_to_thread.ts index 3872ecbaa..520ad7c1e 100644 --- a/src/helpers/channels/threads/add_to_thread.ts +++ b/src/helpers/channels/threads/add_to_thread.ts @@ -4,8 +4,7 @@ import { Errors } from "../../../types/discordeno/errors.ts"; import { endpoints } from "../../../util/constants.ts"; import { requireBotChannelPermissions } from "../../../util/permissions.ts"; -/** Adds a user to a thread. Requires the ability to send messages in the thread. Requires the thread is not archived. - */ +/** Adds a user to a thread. Requires the ability to send messages in the thread. Requires the thread is not archived. */ export async function addToThread(threadId: bigint, userId: bigint) { const thread = await cacheHandlers.get("threads", threadId); if (thread) { @@ -19,5 +18,5 @@ export async function addToThread(threadId: bigint, userId: bigint) { if (channel) await requireBotChannelPermissions(channel, ["SEND_MESSAGES"]); } - return await rest.runMethod("put", endpoints.THREAD_USER(threadId, userId)); + return await rest.runMethod("put", endpoints.THREAD_USER(threadId, userId)); } diff --git a/src/helpers/channels/threads/archive_thread.ts b/src/helpers/channels/threads/archive_thread.ts index bb0254fb3..cc3884546 100644 --- a/src/helpers/channels/threads/archive_thread.ts +++ b/src/helpers/channels/threads/archive_thread.ts @@ -1,6 +1,6 @@ import { editThread } from "./edit_thread.ts"; /** Sets a thread channel to be archived. */ -export function archiveThread(threadId: bigint) { - return editThread(threadId, { archived: true }); +export async function archiveThread(threadId: bigint) { + return await editThread(threadId, { archived: true }); } diff --git a/src/helpers/channels/threads/delete_thread.ts b/src/helpers/channels/threads/delete_thread.ts index 53d314697..ea5d01ac3 100644 --- a/src/helpers/channels/threads/delete_thread.ts +++ b/src/helpers/channels/threads/delete_thread.ts @@ -11,5 +11,5 @@ export async function deleteThread(threadId: bigint, reason?: string) { if (channel?.guildId) await requireBotGuildPermissions(channel.guildId, ["MANAGE_THREADS"]); } - return await rest.runMethod("delete", endpoints.CHANNEL_BASE(threadId), { reason }); + return await rest.runMethod("delete", endpoints.CHANNEL_BASE(threadId), { reason }); } diff --git a/src/helpers/channels/threads/get_thread_members.ts b/src/helpers/channels/threads/get_thread_members.ts index bc3f965dc..202e9c81a 100644 --- a/src/helpers/channels/threads/get_thread_members.ts +++ b/src/helpers/channels/threads/get_thread_members.ts @@ -1,9 +1,12 @@ import { cacheHandlers } from "../../../cache.ts"; import { rest } from "../../../rest/rest.ts"; +import { ThreadMember } from "../../../types/channels/threads/thread_member.ts"; import { Errors } from "../../../types/discordeno/errors.ts"; import { DiscordGatewayIntents } from "../../../types/gateway/gateway_intents.ts"; +import { Collection } from "../../../util/collection.ts"; import { endpoints } from "../../../util/constants.ts"; import { botHasChannelPermissions } from "../../../util/permissions.ts"; +import { threadMemberModified } from "../../../util/transformers/thread_member_modified.ts"; import { ws } from "../../../ws/ws.ts"; /** Returns thread members objects that are members of the thread. */ @@ -20,5 +23,9 @@ export async function getThreadMembers(threadId: bigint) { throw new Error(Errors.CANNOT_GET_MEMBERS_OF_AN_UNJOINED_PRIVATE_THREAD); } - return await rest.runMethod("get", endpoints.THREAD_MEMBERS(threadId)); + const result = await rest.runMethod("get", endpoints.THREAD_MEMBERS(threadId)); + + const members = result.map((member) => threadMemberModified(member)); + + return new Collection(members.map((member) => [member.id, member])); } diff --git a/src/helpers/channels/threads/join_thread.ts b/src/helpers/channels/threads/join_thread.ts index c3bf65e24..815975de5 100644 --- a/src/helpers/channels/threads/join_thread.ts +++ b/src/helpers/channels/threads/join_thread.ts @@ -10,5 +10,5 @@ export async function joinThread(threadId: bigint) { throw new Error(Errors.CANNOT_ADD_USER_TO_ARCHIVED_THREADS); } - return await rest.runMethod("put", endpoints.THREAD_ME(threadId)); + return await rest.runMethod("put", endpoints.THREAD_ME(threadId)); } diff --git a/src/helpers/channels/threads/leave_thread.ts b/src/helpers/channels/threads/leave_thread.ts index 546309fb6..867727397 100644 --- a/src/helpers/channels/threads/leave_thread.ts +++ b/src/helpers/channels/threads/leave_thread.ts @@ -8,5 +8,5 @@ export async function leaveThread(threadId: bigint) { const thread = await cacheHandlers.get("threads", threadId); if (thread?.archived) throw new Error(Errors.CANNOT_LEAVE_ARCHIVED_THREAD); - return await rest.runMethod("delete", endpoints.THREAD_ME(threadId)); + return await rest.runMethod("delete", endpoints.THREAD_ME(threadId)); } diff --git a/src/helpers/channels/threads/lock_thread.ts b/src/helpers/channels/threads/lock_thread.ts index 3e2cace3c..5a5ca9fae 100644 --- a/src/helpers/channels/threads/lock_thread.ts +++ b/src/helpers/channels/threads/lock_thread.ts @@ -1,6 +1,6 @@ import { editThread } from "./edit_thread.ts"; /** Sets a thread channel to be locked. */ -export function lockThread(threadId: bigint) { - return editThread(threadId, { locked: true }); +export async function lockThread(threadId: bigint) { + return await editThread(threadId, { locked: true }); } diff --git a/src/helpers/channels/threads/remove_thread_member.ts b/src/helpers/channels/threads/remove_thread_member.ts index 8951e0695..8c338c95e 100644 --- a/src/helpers/channels/threads/remove_thread_member.ts +++ b/src/helpers/channels/threads/remove_thread_member.ts @@ -17,5 +17,5 @@ export async function removeThreadMember(threadId: bigint, userId: bigint) { } } - return await rest.runMethod("delete", endpoints.THREAD_USER(threadId, userId)); + return await rest.runMethod("delete", endpoints.THREAD_USER(threadId, userId)); } diff --git a/src/helpers/channels/threads/start_private_thread.ts b/src/helpers/channels/threads/start_private_thread.ts index 48c07007d..d4653df80 100644 --- a/src/helpers/channels/threads/start_private_thread.ts +++ b/src/helpers/channels/threads/start_private_thread.ts @@ -1,5 +1,6 @@ import { cacheHandlers } from "../../../cache.ts"; import { rest } from "../../../rest/rest.ts"; +import { Channel } from "../../../types/channels/channel.ts"; import { StartThread } from "../../../types/channels/threads/start_thread.ts"; import { Errors } from "../../../types/discordeno/errors.ts"; import { endpoints } from "../../../util/constants.ts"; @@ -18,5 +19,7 @@ export async function startPrivateThread(channelId: bigint, options: StartThread await requireBotChannelPermissions(channel, ["SEND_MESSAGES", "USE_PRIVATE_THREADS"]); } - return channelToThread(await rest.runMethod("post", endpoints.THREAD_START_PRIVATE(channelId), snakelize(options))); + return channelToThread( + await rest.runMethod("post", endpoints.THREAD_START_PRIVATE(channelId), snakelize(options)) + ); } diff --git a/src/helpers/channels/threads/start_thread.ts b/src/helpers/channels/threads/start_thread.ts index 35e9b4ce5..557e7d2f0 100644 --- a/src/helpers/channels/threads/start_thread.ts +++ b/src/helpers/channels/threads/start_thread.ts @@ -1,13 +1,15 @@ import { cacheHandlers } from "../../../cache.ts"; import { rest } from "../../../rest/rest.ts"; +import { Channel } from "../../../types/channels/channel.ts"; import { StartThread } from "../../../types/channels/threads/start_thread.ts"; import { Errors } from "../../../types/discordeno/errors.ts"; import { endpoints } from "../../../util/constants.ts"; import { requireBotChannelPermissions } from "../../../util/permissions.ts"; +import { channelToThread } from "../../../util/transformers/channel_to_thread.ts"; import { snakelize } from "../../../util/utils.ts"; /** Creates a new public thread from an existing message. Returns a thread channel. */ -export async function startThread(channelId: bigint, options: StartThread & { messageId: bigint }) { +export async function startThread(channelId: bigint, messageId: bigint, options: StartThread) { const channel = await cacheHandlers.get("channels", channelId); if (channel) { if (!channel.isGuildTextBasedChannel) { @@ -17,9 +19,7 @@ export async function startThread(channelId: bigint, options: StartThread & { me await requireBotChannelPermissions(channel, ["SEND_MESSAGES", "USE_PUBLIC_THREADS"]); } - return await rest.runMethod( - "post", - endpoints.THREAD_START_PUBLIC(channelId, options.messageId), - snakelize(options) + return channelToThread( + await rest.runMethod("post", endpoints.THREAD_START_PUBLIC(channelId, messageId), snakelize(options)) ); } diff --git a/src/helpers/channels/threads/unarchive_thread.ts b/src/helpers/channels/threads/unarchive_thread.ts index 03671aceb..d80a56885 100644 --- a/src/helpers/channels/threads/unarchive_thread.ts +++ b/src/helpers/channels/threads/unarchive_thread.ts @@ -1,6 +1,6 @@ import { editThread } from "./edit_thread.ts"; /** Sets a thread channel to be unarchived. */ -export function unarchiveThread(threadId: bigint) { - return editThread(threadId, { archived: false }); +export async function unarchiveThread(threadId: bigint) { + return await editThread(threadId, { archived: false }); } diff --git a/src/helpers/channels/threads/unlock_thread.ts b/src/helpers/channels/threads/unlock_thread.ts index e410c77bc..277713aec 100644 --- a/src/helpers/channels/threads/unlock_thread.ts +++ b/src/helpers/channels/threads/unlock_thread.ts @@ -1,6 +1,6 @@ import { editThread } from "./edit_thread.ts"; /** Sets a thread channel to be unlocked. */ -export function unlockThread(threadId: bigint) { - return editThread(threadId, { locked: false }); +export async function unlockThread(threadId: bigint) { + return await editThread(threadId, { locked: false }); } diff --git a/src/helpers/members/ban_member.ts b/src/helpers/members/ban_member.ts index 0f9ee8d4e..a94f73d41 100644 --- a/src/helpers/members/ban_member.ts +++ b/src/helpers/members/ban_member.ts @@ -5,10 +5,10 @@ import { requireBotGuildPermissions } from "../../util/permissions.ts"; import { snakelize } from "../../util/utils.ts"; /** Ban a user from the guild and optionally delete previous messages sent by the user. Requires the BAN_MEMBERS permission. */ -export async function ban(guildId: bigint, id: bigint, options: CreateGuildBan) { +export async function ban(guildId: bigint, id: bigint, options?: CreateGuildBan) { await requireBotGuildPermissions(guildId, ["BAN_MEMBERS"]); - return await rest.runMethod("put", endpoints.GUILD_BAN(guildId, id), snakelize(options)); + return await rest.runMethod("put", endpoints.GUILD_BAN(guildId, id), snakelize(options ?? {})); } // aliases diff --git a/src/helpers/messages/edit_message.ts b/src/helpers/messages/edit_message.ts index 192cbe665..bd58ba51f 100644 --- a/src/helpers/messages/edit_message.ts +++ b/src/helpers/messages/edit_message.ts @@ -29,11 +29,6 @@ export async function editMessage(channelId: bigint, messageId: bigint, content: validateComponents(content.components); } - // TODO: v12 remove - if (content.embed) { - content.embeds = [content.embed, ...(content.embeds || [])]; - content.embed = undefined; - } content.embeds?.splice(10); if (content.content && content.content.length > 2000) { diff --git a/src/helpers/messages/send_message.ts b/src/helpers/messages/send_message.ts index 13f3664b1..0b4ae5f48 100644 --- a/src/helpers/messages/send_message.ts +++ b/src/helpers/messages/send_message.ts @@ -34,11 +34,6 @@ export async function sendMessage(channelId: bigint, content: string | CreateMes const requiredPerms: Set = new Set(["SEND_MESSAGES", "VIEW_CHANNEL"]); if (content.tts) requiredPerms.add("SEND_TTS_MESSAGES"); - // TODO: v12 remove - if (content.embed) { - content.embeds = [content.embed, ...(content.embeds || [])]; - content.embed = undefined; - } if (content.embeds?.length) { requiredPerms.add("EMBED_LINKS"); content.embeds?.splice(10); diff --git a/src/helpers/mod.ts b/src/helpers/mod.ts index ed24ba77e..7dc05aa84 100644 --- a/src/helpers/mod.ts +++ b/src/helpers/mod.ts @@ -100,6 +100,7 @@ import { editBotProfile } from "./misc/edit_bot_profile.ts"; import { editBotStatus } from "./misc/edit_bot_status.ts"; import { getGatewayBot } from "./misc/get_gateway_bot.ts"; import { getUser } from "./misc/get_user.ts"; +import { getApplicationInfo } from "./oauth/get_application.ts"; import { addRole } from "./roles/add_role.ts"; import { createRole } from "./roles/create_role.ts"; import { deleteRole } from "./roles/delete_role.ts"; @@ -246,6 +247,7 @@ export { getStageInstance, getTemplate, getUser, + getApplicationInfo, getVanityURL, getVoiceRegions, getWebhook, diff --git a/src/helpers/oauth/get_application.ts b/src/helpers/oauth/get_application.ts new file mode 100644 index 000000000..589573e94 --- /dev/null +++ b/src/helpers/oauth/get_application.ts @@ -0,0 +1,8 @@ +import { rest } from "../../rest/rest.ts"; +import { endpoints } from "../../util/constants.ts"; +import { Application } from "../../types/applications/application.ts"; + +/** Get the applications info */ +export async function getApplicationInfo() { + return await rest.runMethod>("get", endpoints.OAUTH2_APPLICATION); +} \ No newline at end of file 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 5bc78fd52..19bf93940 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -264,7 +264,11 @@ export async function createDiscordenoGuild(data: Guild, shardId: number) { }); } - await Promise.all(promises); + await Promise.all( + promises.map(async (promise) => { + return await promise(); + }) + ); const roles = await Promise.all( (data.roles || []).map((role) => @@ -283,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); @@ -311,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; } @@ -441,7 +470,7 @@ export interface DiscordenoGuild /** Returns a list of ban objects for the users banned from this guild. Requires the BAN_MEMBERS permission. */ bans(): ReturnType; /** Ban a user from the guild and optionally delete previous messages sent by the user. Requires the BAN_MEMBERS permission. */ - ban(memberId: bigint, options: CreateGuildBan): ReturnType; + ban(memberId: bigint, options?: CreateGuildBan): ReturnType; /** Remove the ban for a user. Requires BAN_MEMBERS permission */ unban(memberId: bigint): ReturnType; /** Get all the invites for this guild. Requires MANAGE_GUILD permission */ diff --git a/src/structures/member.ts b/src/structures/member.ts index 7e2325ad1..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) { @@ -251,7 +260,7 @@ export interface DiscordenoMember extends Omit; /** Ban a member in a guild */ - ban(guildId: bigint, options: CreateGuildBan): ReturnType; + ban(guildId: bigint, options?: CreateGuildBan): ReturnType; /** Add a role to the member */ addRole(guildId: bigint, roleId: bigint, reason?: string): ReturnType; /** Remove a role from the member */ 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 { diff --git a/src/types/codes/json_error_codes.ts b/src/types/codes/json_error_codes.ts index 56959f3ca..4682476f9 100644 --- a/src/types/codes/json_error_codes.ts +++ b/src/types/codes/json_error_codes.ts @@ -33,6 +33,7 @@ export enum DiscordJsonErrorCodes { UnknownInteraction = 10062, UnknownApplicationCommand = 10063, UnknownApplicationCommandPermissions = 10066, + UnknownGuildMemberVerificationForm = 10068, BotsCannotUseThisEndpoint = 20001, OnlyBotsCanUseThisEndpoint, ExplicitContentCannotBeSentToTheDesiredRecipient = 20009, @@ -42,6 +43,7 @@ export enum DiscordJsonErrorCodes { ThisMessageCannotBeEditedDueToAnnouncementRateLimits = 20022, TheChannelYouAreWritingHasHitTheWriteRateLimit = 20028, YourStageTopicContainsWordsThatAreNotAllowedForPublicStages = 20031, + GuildPremiumSubscriptionLevelTooLow = 20035, MaximumNumberOfGuildsReached = 30001, MaximumNumberOfFriendsReached, MaximumNumberOfPinsReachedForTheChannel, diff --git a/src/types/interactions/commands/application_command_interaction_data.ts b/src/types/interactions/commands/application_command_interaction_data.ts index 1be81169a..d5653437a 100644 --- a/src/types/interactions/commands/application_command_interaction_data.ts +++ b/src/types/interactions/commands/application_command_interaction_data.ts @@ -4,9 +4,9 @@ import { ApplicationCommandInteractionDataResolved } from "./application_command /** https://discord.com/developers/docs/interactions/slash-commands#interaction-applicationcommandinteractiondata */ export interface ApplicationCommandInteractionData { /** The Id of the invoked command */ - id?: string; + id: string; /** The name of the invoked command */ - name?: string; + name: string; /** Converted users + roles + channels */ resolved?: ApplicationCommandInteractionDataResolved; /** The params + values from the user */ diff --git a/src/types/messages/create_message.ts b/src/types/messages/create_message.ts index 9cddc7a85..73eaded32 100644 --- a/src/types/messages/create_message.ts +++ b/src/types/messages/create_message.ts @@ -10,11 +10,6 @@ export interface CreateMessage { content?: string; /** true if this is a TTS message */ tts?: boolean; - // TODO: v12 remove - /** Embedded `rich` content - * @deprecated will be removed in Discordeno v12 use embeds - */ - embed?: Embed; /** Embedded `rich` content (up to 6000 characters) */ embeds?: Embed[]; /** Allowed mentions for the message */ diff --git a/src/types/messages/edit_message.ts b/src/types/messages/edit_message.ts index 2d47780e6..ac76bf263 100644 --- a/src/types/messages/edit_message.ts +++ b/src/types/messages/edit_message.ts @@ -8,11 +8,6 @@ import { MessageComponents } from "./components/message_components.ts"; export interface EditMessage { /** The new message contents (up to 2000 characters) */ content?: string | null; - // TODO: v12 remove - /** Embedded `rich` content - * @deprecated will be removed in Discordeno v12 use embeds - */ - embed?: Embed | null; /** Embedded `rich` content (up to 6000 characters) */ embeds?: Embed[] | null; /** Edit the flags of the message (only `SUPRESS_EMBEDS` can currently be set/unset) */ diff --git a/src/types/messages/message.ts b/src/types/messages/message.ts index 2edc2be1c..d37fe4ef9 100644 --- a/src/types/messages/message.ts +++ b/src/types/messages/message.ts @@ -13,6 +13,7 @@ import { MessageReference } from "./message_reference.ts"; import { MessageSticker } from "./message_sticker.ts"; import { DiscordMessageTypes } from "./message_types.ts"; import { Reaction } from "./reaction.ts"; +import { MessageStickerItem } from "./message_sticker_item.ts"; /** https://discord.com/developers/docs/resources/channel#message-object */ export interface Message { @@ -93,5 +94,7 @@ export interface Message { /** The thread that was started from this message, includes thread member object */ thread?: Omit & { member: ThreadMember }; /** The components related to this message */ - components: MessageComponents; + components?: MessageComponents; + /** Sent if the message contains stickers */ + stickerItems?: MessageStickerItem[]; } diff --git a/src/types/messages/message_flags.ts b/src/types/messages/message_flags.ts index 76f4f422c..2959d44f0 100644 --- a/src/types/messages/message_flags.ts +++ b/src/types/messages/message_flags.ts @@ -5,7 +5,7 @@ export enum DiscordMessageFlags { /** This message originated from a message in another channel (via Channel Following) */ IsCrosspost = 1 << 1, /** Do not include any embeds when serializing this message */ - SuppressEmbeeds = 1 << 2, + SuppressEmbeds = 1 << 2, /** The source message for this crosspost has been deleted (via Channel Following) */ SourceMessageDeleted = 1 << 3, /** This message came from the urgent message system */ diff --git a/src/types/messages/message_sticker_item.ts b/src/types/messages/message_sticker_item.ts new file mode 100644 index 000000000..84471ba59 --- /dev/null +++ b/src/types/messages/message_sticker_item.ts @@ -0,0 +1,10 @@ +import { DiscordMessageStickerFormatTypes } from "./message_sticker_format_types.ts"; + +export interface MessageStickerItem { + /** Id of the sticker */ + id: string; + /** Name of the sticker */ + name: string; + /** Type of sticker format */ + formatType: DiscordMessageStickerFormatTypes; +} diff --git a/src/util/transformers/channel_to_thread.ts b/src/util/transformers/channel_to_thread.ts index 0999c6ff9..340417a20 100644 --- a/src/util/transformers/channel_to_thread.ts +++ b/src/util/transformers/channel_to_thread.ts @@ -1,3 +1,4 @@ +import { cache } from "../../cache.ts"; import { Channel } from "../../types/channels/channel.ts"; import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; import { ThreadMemberModified } from "../../types/channels/threads/thread_member.ts"; @@ -25,6 +26,9 @@ const baseThread: Partial = { get isPublic() { return !this.isPrivate; }, + get guildId() { + return cache.channels.get(this.parentId!)!.guildId; + }, toJSON() { return { id: this.id?.toString(), @@ -98,6 +102,7 @@ export interface DiscordenoThread { isPrivate: boolean; isPublic: boolean; botIsMember: boolean; + guildId: bigint; members: Collection>; toJSON(): Thread; } diff --git a/src/util/transformers/mod.ts b/src/util/transformers/mod.ts index 1ee8a3238..f5e53fa40 100644 --- a/src/util/transformers/mod.ts +++ b/src/util/transformers/mod.ts @@ -1,3 +1,3 @@ export * from "./channel_to_thread.ts"; export * from "./thread_member_modified.ts"; -export * from "./thread_members_update_modified.ts" \ No newline at end of file +export * from "./thread_members_update_modified.ts";