diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 2ddfa93f7..359cf1a86 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -8,7 +8,6 @@ RUN mkdir -p /deno \ ENV PATH=${DENO_INSTALL}/bin:${PATH} \ DENO_DIR=${DENO_INSTALL}/.cache/deno -# NOT WORKING ATM # RUN deno cache deps.ts # [Optional] Uncomment this section to install additional OS packages. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9161e12e8..81b10b6db 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,6 +7,6 @@ jobs: - uses: actions/checkout@v2 - uses: denolib/setup-deno@v2 - name: Run fmt check script - run: deno fmt --check --ignore=./src/types/util.ts + run: deno fmt --check - name: Run lint script - run: deno lint src/** test/** --unstable + run: deno lint src/** test/** --unstable --ignore=./src/types/ diff --git a/README.md b/README.md index 4bd590024..0bc857063 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Discord API library for [Deno](https://deno.land) +Discordeno follows [Semantic Versioning](https://semver.org/) + [![Discord](https://img.shields.io/discord/785384884197392384?color=7289da&logo=discord&logoColor=dark)](https://discord.com/invite/5vBgXk3UcZ) ![Lint](https://github.com/discordeno/discordeno/workflows/Lint/badge.svg) ![Test](https://github.com/discordeno/discordeno/workflows/Test/badge.svg) @@ -57,9 +59,9 @@ developers had when I first started out coding Discord bots with existing libraries. If you are a beginner, you can check out these awesome official and unofficial boilerplates: -- [Discordeno Bot Template (official)](https://github.com/discordeno/discordeno-bot-template) +- [Discordeno Boilerplate (official)](https://github.com/discordeno/boilerplate) - [Serverless Slash Commands Template - (official)](https://github.com/discordeno/slash-commands-bot) + (official)](https://github.com/discordeno/slash-commands-boilerplate) - [Add Your Own!](https://github.com/discordeno/discordeno/pulls) ## Useful Links diff --git a/deps.ts b/deps.ts index 6d66e7974..bdb3e0b04 100644 --- a/deps.ts +++ b/deps.ts @@ -1 +1 @@ -export { encode } from "https://deno.land/std@0.81.0/encoding/base64.ts"; +export { encode } from "https://deno.land/std@0.87.0/encoding/base64.ts"; diff --git a/docs/src/migrating.md b/docs/src/migrating.md index 8ccbece2c..b88c6cce4 100644 --- a/docs/src/migrating.md +++ b/docs/src/migrating.md @@ -22,8 +22,8 @@ For the purposes of this guide, I will be using the current ## Preparations - First, create a Discordeno Bot using the - [Generator Boilerplate](https://github.com/discordeno/discordeno-bot-template) - I will name it Zodiac. + [Generator Boilerplate](https://github.com/discordeno/boilerplate) I will name + it Zodiac. - Then `git clone https://github.com/Skillz4Killz/Zodiac.git` diff --git a/docs/src/stepbystep/createbot.md b/docs/src/stepbystep/createbot.md index fd4d3bbf1..ce0f4864e 100644 --- a/docs/src/stepbystep/createbot.md +++ b/docs/src/stepbystep/createbot.md @@ -10,7 +10,7 @@ you go through this guide it will make a lot more sense. > If you don't have these yet please prepare them first before going forward. - First, create a Discordeno Bot using the - [Generator Boilerplate](https://github.com/discordeno/discordeno-bot-template/generate). + [Generator Boilerplate](https://github.com/discordeno/boilerplate/generate). Give it any name you like. For the purpose of this guide we will call it, Stargate. diff --git a/mod.ts b/mod.ts index 21f07a0c4..d7d9a2bd5 100644 --- a/mod.ts +++ b/mod.ts @@ -12,6 +12,7 @@ export * from "./src/api/handlers/channel.ts"; export * from "./src/api/handlers/guild.ts"; export * from "./src/api/handlers/member.ts"; export * from "./src/api/handlers/message.ts"; +export * from "./src/api/handlers/oauth.ts"; export * from "./src/api/handlers/webhook.ts"; export * from "./src/api/structures/channel.ts"; export * from "./src/api/structures/guild.ts"; diff --git a/src/api/controllers/channels.ts b/src/api/controllers/channels.ts index 86c00fe87..455d13bc7 100644 --- a/src/api/controllers/channels.ts +++ b/src/api/controllers/channels.ts @@ -11,10 +11,11 @@ export async function handleInternalChannelCreate(data: DiscordPayload) { if (data.t !== "CHANNEL_CREATE") return; const payload = data.d as ChannelCreatePayload; - const channel = await structures.createChannel(payload); - await cacheHandlers.set("channels", channel.id, channel); + const channelStruct = await structures.createChannel(payload); - eventHandlers.channelCreate?.(channel); + await cacheHandlers.set("channels", channelStruct.id, channelStruct); + + eventHandlers.channelCreate?.(channelStruct); } export async function handleInternalChannelDelete(data: DiscordPayload) { @@ -57,10 +58,11 @@ export async function handleInternalChannelUpdate(data: DiscordPayload) { const payload = data.d as ChannelCreatePayload; const cachedChannel = await cacheHandlers.get("channels", payload.id); - const channel = await structures.createChannel(payload); - await cacheHandlers.set("channels", channel.id, channel); + const channelStruct = await structures.createChannel(payload); + + await cacheHandlers.set("channels", channelStruct.id, channelStruct); if (!cachedChannel) return; - eventHandlers.channelUpdate?.(channel, cachedChannel); + eventHandlers.channelUpdate?.(channelStruct, cachedChannel); } diff --git a/src/api/controllers/guilds.ts b/src/api/controllers/guilds.ts index 151666bc1..0f428f597 100644 --- a/src/api/controllers/guilds.ts +++ b/src/api/controllers/guilds.ts @@ -21,19 +21,19 @@ export async function handleInternalGuildCreate( // When shards resume they emit GUILD_CREATE again. if (await cacheHandlers.has("guilds", payload.id)) return; - const guild = await structures.createGuild( + const guildStruct = await structures.createGuild( data.d as CreateGuildPayload, shardID, ); - await cacheHandlers.set("guilds", guild.id, guild); + await cacheHandlers.set("guilds", guildStruct.id, guildStruct); if (await cacheHandlers.has("unavailableGuilds", payload.id)) { await cacheHandlers.delete("unavailableGuilds", payload.id); } - if (!cache.isReady) return eventHandlers.guildLoaded?.(guild); - eventHandlers.guildCreate?.(guild); + if (!cache.isReady) return eventHandlers.guildLoaded?.(guildStruct); + eventHandlers.guildCreate?.(guildStruct); } export async function handleInternalGuildDelete(data: DiscordPayload) { @@ -52,8 +52,6 @@ export async function handleInternalGuildDelete(data: DiscordPayload) { } }); - await cacheHandlers.delete("guilds", payload.id); - if (payload.unavailable) { return cacheHandlers.set("unavailableGuilds", payload.id, Date.now()); } @@ -61,6 +59,8 @@ export async function handleInternalGuildDelete(data: DiscordPayload) { const guild = await cacheHandlers.get("guilds", payload.id); if (!guild) return; + await cacheHandlers.delete("guilds", payload.id); + eventHandlers.guildDelete?.(guild); } @@ -102,7 +102,7 @@ export async function handleInternalGuildUpdate(data: DiscordPayload) { } }).filter((change) => change) as GuildUpdateChange[]; - await cacheHandlers.set("guilds", payload.id, { ...cachedGuild, ...changes }); + await cacheHandlers.set("guilds", payload.id, cachedGuild); eventHandlers.guildUpdate?.(cachedGuild, changes); } diff --git a/src/api/controllers/interactions.ts b/src/api/controllers/interactions.ts index d33b2a7a9..b6feca382 100644 --- a/src/api/controllers/interactions.ts +++ b/src/api/controllers/interactions.ts @@ -5,15 +5,22 @@ import { InteractionCommandPayload, } from "../../types/mod.ts"; import { structures } from "../structures/mod.ts"; +import { cacheHandlers } from "./cache.ts"; export async function handleInternalInteractionCreate(data: DiscordPayload) { if (data.t !== "INTERACTION_CREATE") return; const payload = data.d as InteractionCommandPayload; + const memberStruct = await structures.createMember( + payload.member, + payload.guild_id, + ); + await cacheHandlers.set("members", memberStruct.id, memberStruct); + eventHandlers.interactionCreate?.( { ...payload, - member: await structures.createMember(payload.member, payload.guild_id), + member: memberStruct, }, ); } diff --git a/src/api/controllers/members.ts b/src/api/controllers/members.ts index 5d253786d..9c2c83aca 100644 --- a/src/api/controllers/members.ts +++ b/src/api/controllers/members.ts @@ -19,12 +19,13 @@ export async function handleInternalGuildMemberAdd(data: DiscordPayload) { if (!guild) return; guild.memberCount++; - const member = await structures.createMember( + const memberStruct = await structures.createMember( payload, payload.guild_id, ); + await cacheHandlers.set("members", memberStruct.id, memberStruct); - eventHandlers.guildMemberAdd?.(guild, member); + eventHandlers.guildMemberAdd?.(guild, memberStruct); } export async function handleInternalGuildMemberRemove(data: DiscordPayload) { @@ -65,41 +66,40 @@ export async function handleInternalGuildMemberUpdate(data: DiscordPayload) { mute: guildMember?.mute || false, roles: payload.roles, }; - const member = await structures.createMember( + const memberStruct = await structures.createMember( newMemberData, payload.guild_id, ); - - await cacheHandlers.set("members", member.id, member); + await cacheHandlers.set("members", memberStruct.id, memberStruct); if (guildMember?.nick !== payload.nick) { eventHandlers.nicknameUpdate?.( guild, - member, + memberStruct, payload.nick, guildMember?.nick, ); } if (payload.pending === false && guildMember?.pending === true) { - eventHandlers.membershipScreeningPassed?.(guild, member); + eventHandlers.membershipScreeningPassed?.(guild, memberStruct); } const roleIDs = guildMember?.roles || []; roleIDs.forEach((id) => { if (!payload.roles.includes(id)) { - eventHandlers.roleLost?.(guild, member, id); + eventHandlers.roleLost?.(guild, memberStruct, id); } }); payload.roles.forEach((id) => { if (!roleIDs.includes(id)) { - eventHandlers.roleGained?.(guild, member, id); + eventHandlers.roleGained?.(guild, memberStruct, id); } }); - eventHandlers.guildMemberUpdate?.(guild, member, cachedMember); + eventHandlers.guildMemberUpdate?.(guild, memberStruct, cachedMember); } export async function handleInternalGuildMembersChunk(data: DiscordPayload) { @@ -108,9 +108,16 @@ export async function handleInternalGuildMembersChunk(data: DiscordPayload) { const payload = data.d as GuildMemberChunkPayload; const members = await Promise.all( - payload.members.map((member) => - structures.createMember(member, payload.guild_id) - ), + payload.members.map(async (member) => { + const memberStruct = await structures.createMember( + member, + payload.guild_id, + ); + + await cacheHandlers.set("members", memberStruct.id, memberStruct); + + return memberStruct; + }), ); // Check if its necessary to resolve the fetchmembers promise for this chunk or if more chunks will be coming diff --git a/src/api/controllers/messages.ts b/src/api/controllers/messages.ts index 86a8974fa..cc4a319a4 100644 --- a/src/api/controllers/messages.ts +++ b/src/api/controllers/messages.ts @@ -21,21 +21,25 @@ export async function handleInternalMessageCreate(data: DiscordPayload) { if (payload.member && guild) { // If in a guild cache the author as a member - await structures.createMember( + const memberStruct = await structures.createMember( { ...payload.member, user: payload.author }, guild.id, ); + + await cacheHandlers.set("members", memberStruct.id, memberStruct); } - payload.mentions.forEach((mention) => { + await Promise.all(payload.mentions.map(async (mention) => { // Cache the member if its a valid member if (mention.member && guild) { - structures.createMember( + const memberStruct = await structures.createMember( { ...mention.member, user: mention }, guild.id, ); + + return cacheHandlers.set("members", memberStruct.id, memberStruct); } - }); + })); const message = await structures.createMessage(payload); // Cache the message diff --git a/src/api/controllers/misc.ts b/src/api/controllers/misc.ts index 839d4e9e5..3a4a87dd6 100644 --- a/src/api/controllers/misc.ts +++ b/src/api/controllers/misc.ts @@ -3,6 +3,8 @@ import { DiscordPayload, IntegrationCreateUpdateEvent, IntegrationDeleteEvent, + InviteCreateEvent, + InviteDeleteEvent, PresenceUpdatePayload, ReadyPayload, TypingStartPayload, @@ -51,7 +53,18 @@ export async function handleInternalReady( // All the members that came in on guild creates should now be processed 1 by 1 for (const [guildID, members] of initialMemberLoadQueue.entries()) { await Promise.all( - members.map((member) => structures.createMember(member, guildID)), + members.map(async (member) => { + const memberStruct = await structures.createMember( + member, + guildID, + ); + + return cacheHandlers.set( + "members", + memberStruct.id, + memberStruct, + ); + }), ); } } @@ -234,3 +247,45 @@ export function handleInternalIntegrationDelete(data: DiscordPayload) { guildID, }); } + +export function handleInternalInviteCreate(payload: DiscordPayload) { + if (payload.t !== "INVITE_CREATE") return; + + const { + channel_id: channelID, + created_at: createdAt, + max_age: maxAge, + guild_id: guildID, + target_user: targetUser, + target_user_type: targetUserType, + max_uses: maxUses, + ...rest + } = payload.d as InviteCreateEvent; + + eventHandlers.inviteCreate?.({ + ...rest, + channelID, + guildID, + maxAge, + targetUser, + targetUserType, + maxUses, + createdAt, + }); +} + +export function handleInternalInviteDelete(payload: DiscordPayload) { + if (payload.t !== "INVITE_DELETE") return; + + const { + channel_id: channelID, + guild_id: guildID, + ...rest + } = payload.d as InviteDeleteEvent; + + eventHandlers.inviteDelete?.({ + ...rest, + channelID, + guildID, + }); +} diff --git a/src/api/controllers/mod.ts b/src/api/controllers/mod.ts index a1b73e9dc..ce97927b5 100644 --- a/src/api/controllers/mod.ts +++ b/src/api/controllers/mod.ts @@ -35,6 +35,8 @@ import { handleInternalIntegrationCreate, handleInternalIntegrationDelete, handleInternalIntegrationUpdate, + handleInternalInviteCreate, + handleInternalInviteDelete, handleInternalPresenceUpdate, handleInternalReady, handleInternalTypingStart, @@ -92,6 +94,8 @@ export let controllers = { INTEGRATION_CREATE: handleInternalIntegrationCreate, INTEGRATION_UPDATE: handleInternalIntegrationUpdate, INTEGRATION_DELETE: handleInternalIntegrationDelete, + INVITE_CREATE: handleInternalInviteCreate, + INVITE_DELETE: handleInternalInviteDelete, }; export type Controllers = typeof controllers; diff --git a/src/api/controllers/reactions.ts b/src/api/controllers/reactions.ts index dc0151846..50e83d0bb 100644 --- a/src/api/controllers/reactions.ts +++ b/src/api/controllers/reactions.ts @@ -39,7 +39,11 @@ export async function handleInternalMessageReactionAdd(data: DiscordPayload) { if (payload.member && payload.guild_id) { const guild = await cacheHandlers.get("guilds", payload.guild_id); if (guild) { - await structures.createMember(payload.member, guild.id); + const memberStruct = await structures.createMember( + payload.member, + guild.id, + ); + await cacheHandlers.set("members", memberStruct.id, memberStruct); } } @@ -91,10 +95,11 @@ export async function handleInternalMessageReactionRemove( if (payload.member && payload.guild_id) { const guild = await cacheHandlers.get("guilds", payload.guild_id); if (guild) { - await structures.createMember( + const memberStruct = await structures.createMember( payload.member, guild.id, ); + await cacheHandlers.set("members", memberStruct.id, memberStruct); } } diff --git a/src/api/controllers/roles.ts b/src/api/controllers/roles.ts index 376e2ca31..63292058e 100644 --- a/src/api/controllers/roles.ts +++ b/src/api/controllers/roles.ts @@ -29,10 +29,10 @@ export async function handleInternalGuildRoleDelete(data: DiscordPayload) { if (!guild) return; const cachedRole = guild.roles.get(payload.role_id)!; - if (cachedRole) eventHandlers.roleDelete?.(guild, cachedRole); - guild.roles.delete(payload.role_id); + if (cachedRole) eventHandlers.roleDelete?.(guild, cachedRole); + // For bots without GUILD_MEMBERS member.roles is never updated breaking permissions checking. cacheHandlers.forEach("members", (member) => { // Not in the relevant guild so just skip. @@ -46,8 +46,6 @@ export async function handleInternalGuildRoleDelete(data: DiscordPayload) { cacheHandlers.set("members", member.id, member); }); }); - - eventHandlers.roleDelete?.(guild, cachedRole); } export async function handleInternalGuildRoleUpdate(data: DiscordPayload) { diff --git a/src/api/handlers/channel.ts b/src/api/handlers/channel.ts index bb31a200d..3cf81ce6f 100644 --- a/src/api/handlers/channel.ts +++ b/src/api/handlers/channel.ts @@ -144,36 +144,59 @@ export async function sendMessage( content: string | MessageContent, ) { if (typeof content === "string") content = { content }; - const hasSendMessagesPerm = await botHasChannelPermissions( - channelID, - ["SEND_MESSAGES"], - ); - if ( - !hasSendMessagesPerm - ) { - throw new Error(Errors.MISSING_SEND_MESSAGES); - } - const hasSendTtsMessagesPerm = await botHasChannelPermissions( - channelID, - ["SEND_TTS_MESSAGES"], - ); - if ( - content.tts && - !hasSendTtsMessagesPerm - ) { - throw new Error(Errors.MISSING_SEND_TTS_MESSAGE); - } + const channel = await cacheHandlers.get("channels", channelID); + // If the channel is cached, we can do extra checks/safety + if (channel) { + if ( + ![ChannelTypes.DM, ChannelTypes.GUILD_NEWS, ChannelTypes.GUILD_TEXT] + .includes(channel.type) + ) { + throw new Error(Errors.CHANNEL_NOT_TEXT_BASED); + } - const hasEmbedLinksPerm = await botHasChannelPermissions( - channelID, - ["EMBED_LINKS"], - ); - if ( - content.embed && - !hasEmbedLinksPerm - ) { - throw new Error(Errors.MISSING_EMBED_LINKS); + const hasSendMessagesPerm = await botHasChannelPermissions( + channelID, + ["SEND_MESSAGES"], + ); + if ( + !hasSendMessagesPerm + ) { + throw new Error(Errors.MISSING_SEND_MESSAGES); + } + + const hasSendTtsMessagesPerm = await botHasChannelPermissions( + channelID, + ["SEND_TTS_MESSAGES"], + ); + if ( + content.tts && + !hasSendTtsMessagesPerm + ) { + throw new Error(Errors.MISSING_SEND_TTS_MESSAGE); + } + + const hasEmbedLinksPerm = await botHasChannelPermissions( + channelID, + ["EMBED_LINKS"], + ); + if ( + content.embed && + !hasEmbedLinksPerm + ) { + throw new Error(Errors.MISSING_EMBED_LINKS); + } + + if (content.mentions?.repliedUser) { + if ( + !(await botHasChannelPermissions( + channelID, + ["READ_MESSAGE_HISTORY"], + )) + ) { + throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY); + } + } } // Use ... for content length due to unicode characters and js .length handling @@ -205,26 +228,6 @@ export async function sendMessage( content.mentions.roles = content.mentions.roles.slice(0, 100); } } - - if (content.mentions.repliedUser) { - if ( - !(await botHasChannelPermissions( - channelID, - ["READ_MESSAGE_HISTORY"], - )) - ) { - throw new Error(Errors.MISSING_READ_MESSAGE_HISTORY); - } - } - } - - const channel = await cacheHandlers.get("channels", channelID); - if (!channel) throw new Error(Errors.CHANNEL_NOT_FOUND); - if ( - ![ChannelTypes.DM, ChannelTypes.GUILD_NEWS, ChannelTypes.GUILD_TEXT] - .includes(channel.type) - ) { - throw new Error(Errors.CHANNEL_NOT_TEXT_BASED); } const result = await RequestManager.post( diff --git a/src/api/handlers/guild.ts b/src/api/handlers/guild.ts index a5f3fbf90..b59c2258b 100644 --- a/src/api/handlers/guild.ts +++ b/src/api/handlers/guild.ts @@ -27,8 +27,6 @@ import { ImageSize, Intents, MemberCreatePayload, - MembershipScreeningFieldTypes, - MembershipScreeningPayload, Overwrite, PositionSwap, PruneOptions, @@ -139,7 +137,11 @@ export async function createGuildChannel( type: options?.type || ChannelTypes.GUILD_TEXT, })) as ChannelCreatePayload; - return structures.createChannel(result); + const channelStruct = await structures.createChannel(result); + + await cacheHandlers.set("channels", channelStruct.id, channelStruct); + + return channelStruct; } /** Delete a channel in your server. Bot needs MANAGE_CHANNEL permissions in the server. */ @@ -185,11 +187,12 @@ export async function getChannels(guildID: string, addToCache = true) { ) as ChannelCreatePayload[]; return Promise.all(result.map(async (res) => { - const channel = await structures.createChannel(res, guildID); + const channelStruct = await structures.createChannel(res, guildID); if (addToCache) { - await cacheHandlers.set("channels", channel.id, channel); + await cacheHandlers.set("channels", channelStruct.id, channelStruct); } - return channel; + + return channelStruct; })); } @@ -202,10 +205,12 @@ export async function getChannel(channelID: string, addToCache = true) { endpoints.CHANNEL_BASE(channelID), ) as ChannelCreatePayload; - const channel = await structures.createChannel(result, result.guild_id); - if (addToCache) await cacheHandlers.set("channels", channel.id, channel); + const channelStruct = await structures.createChannel(result, result.guild_id); + if (addToCache) { + await cacheHandlers.set("channels", channelStruct.id, channelStruct); + } - return channel; + return channelStruct; } /** Modify the positions of channels on the guild. Requires MANAGE_CHANNELS permisison. */ @@ -289,7 +294,11 @@ export async function getMember( endpoints.GUILD_MEMBER(guildID, id), ) as MemberCreatePayload; - return structures.createMember(data, guildID); + const memberStruct = await structures.createMember(data, guildID); + + await cacheHandlers.set("members", memberStruct.id, memberStruct); + + return memberStruct; } /** Returns guild member objects for the specified user by their nickname/username. @@ -305,7 +314,7 @@ export async function getMembersByQuery( if (!guild) return; return new Promise((resolve) => { - requestAllMembers(guild, resolve, { query: name, limit }); + return requestAllMembers(guild, resolve, { query: name, limit }); }) as Promise>; } @@ -382,7 +391,7 @@ export function emojiURL(id: string, animated = false) { /** * Returns a list of emojis for the given guild. - * + * * ⚠️ **If you need this, you are probably doing something wrong. Always use cache.guilds.get()?.emojis */ export async function getEmojis(guildID: string, addToCache = true) { @@ -402,7 +411,7 @@ export async function getEmojis(guildID: string, addToCache = true) { /** * Returns an emoji for the given guild and emoji ID. - * + * * ⚠️ **If you need this, you are probably doing something wrong. Always use cache.guilds.get()?.emojis */ export async function getEmoji( @@ -577,7 +586,7 @@ export function fetchMembers(guild: Guild, options?: FetchMembersOptions) { } return new Promise((resolve) => { - requestAllMembers(guild, resolve, options); + return requestAllMembers(guild, resolve, options); }) as Promise>; } @@ -622,7 +631,13 @@ export async function getMembers( ) as MemberCreatePayload[]; const memberStructures = await Promise.all( - result.map((member) => structures.createMember(member, guildID)), + result.map(async (member) => { + const memberStruct = await structures.createMember(member, guildID); + + await cacheHandlers.set("members", memberStruct.id, memberStruct); + + return memberStruct; + }), ) as Member[]; if (!memberStructures.length) break; @@ -938,8 +953,8 @@ export async function getTemplate(templateCode: string) { return template; } -/** - * Returns the guild template if it exists +/** + * Returns the guild template if it exists * @deprecated will get removed in v11 use `getTemplate` instead */ export function getGuildTemplate( @@ -1085,69 +1100,3 @@ export async function editGuildTemplate( return structures.createTemplate(template); } - -function createMembershipObj( - { form_fields: formFields, ...props }: MembershipScreeningPayload, -) { - return { - ...props, - formFields: formFields.map(({ field_type, ...rest }) => ({ - ...rest, - fieldType: field_type, - })), - }; -} - -export type MembershipScreening = ReturnType; - -/** Get the membership screening form of a guild. */ -export async function getGuildMembershipScreeningForm(guildID: string) { - const membershipScreeningPayload = await RequestManager.get( - endpoints.GUILD_MEMBER_VERIFICATION(guildID), - ) as MembershipScreeningPayload; - - return createMembershipObj(membershipScreeningPayload); -} - -/** Edit the guild's Membership Screening form. Requires the `MANAGE_GUILD` permission. */ -export async function editGuildMembershipScreeningForm( - guildID: string, - options?: EditGuildMembershipScreeningForm, -) { - const membershipScreeningFormPayload = await RequestManager.patch( - endpoints.GUILD_MEMBER_VERIFICATION(guildID), - { - ...options, - form_fields: JSON.stringify( - options?.formFields?.map(({ fieldType, ...props }) => ({ - ...props, - field_type: fieldType, - })), - ), - }, - ) as MembershipScreeningPayload; - - return createMembershipObj( - membershipScreeningFormPayload, - ); -} - -export interface EditGuildMembershipScreeningForm { - /** whether Membership Screening is enabled */ - enabled?: boolean; - /** array of field objects */ - formFields?: MembershipScreeningField[]; - /** the steps in the screening form */ - description?: string; -} - -export interface MembershipScreeningField { - /** the type of field */ - fieldType: MembershipScreeningFieldTypes; - /** the title of the field */ - label: string; - /** the list of rules */ - values?: string[]; - /** whether the user has to fill out this field */ - required: boolean; -} diff --git a/src/api/handlers/member.ts b/src/api/handlers/member.ts index 2b3d28c99..143e48f9c 100644 --- a/src/api/handlers/member.ts +++ b/src/api/handlers/member.ts @@ -131,14 +131,12 @@ export async function sendDirectMessage( endpoints.USER_DM, { recipient_id: memberID }, ) as DMChannelCreatePayload; - // Channel create event will have added this channel to the cache - await cacheHandlers.delete("channels", dmChannelData.id); - const channel = await structures.createChannel( + const channelStruct = await structures.createChannel( dmChannelData as unknown as ChannelCreatePayload, ); // Recreate the channel and add it undert he users id - await cacheHandlers.set("channels", memberID, channel); - dmChannel = channel; + await cacheHandlers.set("channels", memberID, channelStruct); + dmChannel = channelStruct; } // If it does exist try sending a message to this user diff --git a/src/api/structures/channel.ts b/src/api/structures/channel.ts index d7c5dba32..f0706c3ab 100644 --- a/src/api/structures/channel.ts +++ b/src/api/structures/channel.ts @@ -1,15 +1,28 @@ import { ChannelCreatePayload, + ChannelEditOptions, ChannelType, MessageContent, + Overwrite, + Permission, RawOverwrite, } from "../../types/mod.ts"; import { cache } from "../../util/cache.ts"; import { Collection } from "../../util/collection.ts"; import { createNewProp } from "../../util/utils.ts"; -import { cacheHandlers } from "../controllers/cache.ts"; -import { sendMessage } from "../handlers/channel.ts"; -import { Guild } from "./guild.ts"; +import { + channelOverwriteHasPermission, + editChannel, + sendMessage, +} from "../handlers/channel.ts"; +import { + deleteChannel, + deleteChannelOverwrite, + editChannelOverwrite, +} from "../handlers/guild.ts"; +import { kickFromVoiceChannel } from "../handlers/member.ts"; +import { CleanVoiceState, Guild } from "./guild.ts"; +import { Member } from "./member.ts"; import { Message } from "./message.ts"; const baseChannel: Partial = { @@ -22,11 +35,48 @@ const baseChannel: Partial = { get mention() { return `<#${this.id!}>`; }, + get voiceStates() { + return this.guild?.voiceStates.filter((voiceState) => + voiceState.channelID === this.id + ); + }, + get connectedMembers() { + const voiceStates = this.voiceStates; + if (!voiceStates) return undefined; + + return new Collection( + voiceStates.map((vs, key) => [key, cache.members.get(key)]), + ); + }, send(content) { return sendMessage(this.id!, content); }, + disconnect(memberID) { + return kickFromVoiceChannel(this.guildID!, memberID); + }, + delete() { + return deleteChannel(this.guildID!, this.id!); + }, + editOverwrite(id, options) { + return editChannelOverwrite(this.guildID!, this.id!, id, options); + }, + deleteOverwrite(id) { + return deleteChannelOverwrite(this.guildID!, this.id!, id); + }, + hasPermission(overwrites, permissions) { + return channelOverwriteHasPermission( + this.guildID!, + this.id!, + overwrites, + permissions, + ); + }, + edit(options, reason) { + return editChannel(this.id!, options, reason); + }, }; +// deno-lint-ignore require-await export async function createChannel( data: ChannelCreatePayload, guildID?: string, @@ -63,7 +113,6 @@ export async function createChannel( nsfw: createNewProp(nsfw), }); - await cacheHandlers.set("channels", data.id, channel); return channel as Channel; } @@ -113,9 +162,44 @@ export interface Channel { messages: Collection; /** The mention of the channel */ mention: string; + /** + * Gets the voice states for this channel + * + * ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async. + */ + voiceStates?: Collection; + /** + * Gets the connected members for this channel undefined if member is not cached + * + * ⚠️ ADVANCED: If you use the custom cache, these will not work for you. Getters can not be async and custom cache requires async. + */ + connectedMembers?: Collection; // METHODS /** Send a message to the channel. Requires SEND_MESSAGES permission. */ send(content: string | MessageContent): ReturnType; + /** Disconnect a member from a voice channel. Requires MOVE_MEMBERS permission. */ + disconnect(memberID: string): ReturnType; + /** Delete the channel */ + delete(): ReturnType; + /** Edit a channel Overwrite */ + editOverwrite( + overwriteID: string, + options: Omit, + ): ReturnType; + /** Delete a channel Overwrite */ + deleteOverwrite( + overwriteID: string, + ): ReturnType; + /** Checks if a channel overwrite for a user id or a role id has permission in this channel */ + hasPermission( + overwrites: RawOverwrite[], + permissions: Permission[], + ): ReturnType; + /** Edit the channel */ + edit( + options: ChannelEditOptions, + reason?: string, + ): ReturnType; } diff --git a/src/api/structures/guild.ts b/src/api/structures/guild.ts index 600e6141e..bffd97546 100644 --- a/src/api/structures/guild.ts +++ b/src/api/structures/guild.ts @@ -1,7 +1,6 @@ import { botID } from "../../bot.ts"; import { BanOptions, - ChannelCreatePayload, CreateGuildPayload, Emoji, GetAuditLogsOptions, @@ -12,12 +11,12 @@ import { ImageSize, MemberCreatePayload, Presence, - RoleData, VoiceState, } from "../../types/mod.ts"; import { cache } from "../../util/cache.ts"; import { Collection } from "../../util/collection.ts"; import { createNewProp } from "../../util/utils.ts"; +import { cacheHandlers } from "../controllers/cache.ts"; import { ban, deleteServer, @@ -142,16 +141,15 @@ export async function createGuild(data: CreateGuildPayload, shardID: number) { ...rest } = data; - const roles = (await Promise.all( - data.roles.map((r: RoleData) => structures.createRole(r)), - )) as Role[]; - - await Promise.all( - channels.map((c: ChannelCreatePayload) => - structures.createChannel(c, data.id) - ), + const roles = await Promise.all( + data.roles.map((role) => structures.createRole(role)), ); + await Promise.all(channels.map(async (channel) => { + const channelStruct = await structures.createChannel(channel); + return cacheHandlers.set("channels", channelStruct.id, channelStruct); + })); + const restProps: Record> = {}; for (const key of Object.keys(rest)) { // @ts-ignore index signature @@ -347,7 +345,7 @@ export interface Guild { invites(): ReturnType; } -interface CleanVoiceState extends VoiceState { +export interface CleanVoiceState extends VoiceState { /** The guild id where this voice state is from */ guildID: string; /** The channel id where this voice state is from */ diff --git a/src/api/structures/member.ts b/src/api/structures/member.ts index ce17249fb..b46c41f47 100644 --- a/src/api/structures/member.ts +++ b/src/api/structures/member.ts @@ -124,8 +124,6 @@ export async function createMember(data: MemberCreatePayload, guildID: string) { mute: mute, }); - await cacheHandlers.set("members", member.id, member); - return member as Member; } diff --git a/src/api/structures/message.ts b/src/api/structures/message.ts index 474a071bc..47e828cae 100644 --- a/src/api/structures/message.ts +++ b/src/api/structures/message.ts @@ -15,6 +15,7 @@ import { cache } from "../../util/cache.ts"; import { createNewProp } from "../../util/utils.ts"; import { cacheHandlers } from "../controllers/cache.ts"; import { sendMessage } from "../handlers/channel.ts"; +import { sendDirectMessage } from "../handlers/member.ts"; import { addReaction, addReactions, @@ -32,7 +33,8 @@ import { Role } from "./role.ts"; const baseMessage: Partial = { get channel() { - return cache.channels.get(this.channelID!); + if (this.guildID) return cache.channels.get(this.channelID!); + return cache.channels.get(this.author?.id!); }, get guild() { if (!this.guildID) return undefined; @@ -51,7 +53,6 @@ const baseMessage: Partial = { "@me"}/${this.channelID}/${this.id}`; }, get mentionedRoles() { - // TODO: add getters for Guild structure, that will fix this error return this.mentionRoleIDs?.map((id) => this.guild?.roles.get(id)) || []; }, get mentionedChannels() { @@ -91,13 +92,21 @@ const baseMessage: Partial = { replyMessageID: this.id, }; - return sendMessage(this.channelID!, contentWithMention); + if (this.guildID) return sendMessage(this.channelID!, contentWithMention); + return sendDirectMessage(this.author!.id, contentWithMention); }, send(content) { - return sendMessage(this.channelID!, content); + if (this.guildID) return sendMessage(this.channelID!, content); + return sendDirectMessage(this.author!.id, content); }, alert(content, timeout = 10, reason = "") { - return sendMessage(this.channelID!, content).then((response) => { + if (this.guildID) { + return sendMessage(this.channelID!, content).then((response) => { + response.delete(reason, timeout * 1000).catch(console.error); + }); + } + + return sendDirectMessage(this.author!.id, content).then((response) => { response.delete(reason, timeout * 1000).catch(console.error); }); }, @@ -199,7 +208,7 @@ export interface Message { /** Whether this message is pinned */ pinned: boolean; /** If the message is generated by a webhook, this is the webhooks id */ - webhook_id?: string; + "webhook_id"?: string; /** The type of message */ type: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; /** The activities sent with Rich Presence-related chat embeds. */ diff --git a/src/bot.ts b/src/bot.ts index 6cb39ead3..111f58426 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -2,6 +2,7 @@ import { getGatewayBot } from "./api/handlers/gateway.ts"; import { BotConfig, DiscordBotGatewayData, + DiscordIdentify, EventHandlers, Intents, } from "./types/mod.ts"; @@ -9,6 +10,7 @@ import { baseEndpoints, GATEWAY_VERSION } from "./util/constants.ts"; import { spawnShards } from "./ws/shard_manager.ts"; export let authorization = ""; +export let restAuthorization = ""; export let botID = ""; export let applicationID = ""; @@ -17,7 +19,7 @@ export let eventHandlers: EventHandlers = {}; export let botGatewayData: DiscordBotGatewayData; export let proxyWSURL = `wss://gateway.discord.gg`; -export const identifyPayload: IdentifyPayload = { +export const identifyPayload: DiscordIdentify = { token: "", compress: true, properties: { @@ -29,6 +31,7 @@ export const identifyPayload: IdentifyPayload = { shard: [0, 0], }; +/** @deprecated Use "DiscordIdentify" instead */ export interface IdentifyPayload { token: string; compress: boolean; @@ -59,7 +62,7 @@ export async function startBot(config: BotConfig) { ); identifyPayload.shard = [0, botGatewayData.shards]; - spawnShards(botGatewayData, identifyPayload, 0, botGatewayData.shards); + await spawnShards(botGatewayData, identifyPayload, 0, botGatewayData.shards); } /** Allows you to dynamically update the event handlers by passing in new eventHandlers */ @@ -92,6 +95,7 @@ export async function startBigBrainBot(data: BigBrainBotConfig) { authorization = `Bot ${data.token}`; identifyPayload.token = `Bot ${data.token}`; + if (data.restAuthorization) restAuthorization = data.restAuthorization; if (data.restURL) baseEndpoints.BASE_URL = data.restURL; if (data.cdnURL) baseEndpoints.CDN_URL = data.cdnURL; if (data.wsURL) proxyWSURL = data.wsURL; @@ -113,9 +117,10 @@ export async function startBigBrainBot(data: BigBrainBotConfig) { botGatewayData, identifyPayload, data.firstShardID, - data.lastShardID || botGatewayData.shards >= 25 - ? (data.firstShardID + 25) - : botGatewayData.shards, + data.lastShardID || + (botGatewayData.shards >= 25 + ? (data.firstShardID + 25) + : botGatewayData.shards), ); } @@ -130,4 +135,6 @@ export interface BigBrainBotConfig extends BotConfig { restURL?: string; /** This can be used to forward the CDN handling to a proxy. */ cdnURL?: string; + /** This is the authorization header that your rest proxy will validate */ + restAuthorization?: string; } diff --git a/src/interactions/deps.ts b/src/interactions/deps.ts index 921102596..842c1dde6 100644 --- a/src/interactions/deps.ts +++ b/src/interactions/deps.ts @@ -1,2 +1,2 @@ -export { serve } from "https://deno.land/std@0.81.0/http/server.ts"; -export { verify } from "https://unpkg.com/@evan/wasm@0.0.25/target/ed25519/deno.js"; +export { serve } from "https://deno.land/std@0.87.0/http/server.ts"; +export { verify } from "https://esm.sh/@evan/wasm@0.0.41/target/ed25519/deno.js"; diff --git a/src/interactions/types/embed.ts b/src/interactions/types/embed.ts index 0bc98cd45..fceae3f43 100644 --- a/src/interactions/types/embed.ts +++ b/src/interactions/types/embed.ts @@ -31,16 +31,16 @@ export interface EmbedFooter { /** The text of the footer */ text: string; /** The url of the footer icon. Only supports http(s) and attachments */ - icon_url?: string; + "icon_url"?: string; /** A proxied url of footer icon */ - proxy_icon_url?: string; + "proxy_icon_url"?: string; } export interface EmbedImage { /** The source url of image (only supports http(s) and attachments) */ url?: string; /** A proxied url of the image */ - proxy_url?: string; + "proxy_url"?: string; /** The height of image */ height?: number; /** The width of the image */ @@ -51,7 +51,7 @@ export interface EmbedThumbnail { /** The source url of image (only supports http(s) and attachments) */ url?: string; /** A proxied url of the thumbnail */ - proxy_url?: string; + "proxy_url"?: string; /** The height of the thumbnail */ height?: number; /** The width of the thumbnail */ @@ -80,9 +80,9 @@ export interface EmbedAuthor { /** The url of the author */ url?: string; /** The url of the author icon (supports http(s) and attachments) */ - icon_url?: string; + "icon_url"?: string; /** A proxied url of author icon */ - proxy_icon_url?: string; + "proxy_icon_url"?: string; } export interface EmbedField { diff --git a/src/interactions/types/interactions.ts b/src/interactions/types/interactions.ts index 2cf244a8f..87e36f8a9 100644 --- a/src/interactions/types/interactions.ts +++ b/src/interactions/types/interactions.ts @@ -10,9 +10,9 @@ export interface Interaction { /** The command data payload */ data?: SlashCommandInteractionData; /** The id of the guild it was sent from */ - guild_id: string; + "guild_id": string; /** The id of the channel it was sent from */ - channel_id: string; + "channel_id": string; /** The Payload of the member it was sent from */ member: MemberCreatePayload; /** The token for this interaction */ @@ -53,7 +53,7 @@ export interface SlashCommandCallbackData { /** supports up to 10 embeds */ embeds?: Embed[]; /** allowed mentions for the message */ - allowed_mentions?: AllowedMentions; + "allowed_mentions"?: AllowedMentions; /** acceptable values are message flags */ flags?: number; } diff --git a/src/interactions/types/member.ts b/src/interactions/types/member.ts index f80c24fdc..94634a62e 100644 --- a/src/interactions/types/member.ts +++ b/src/interactions/types/member.ts @@ -12,7 +12,7 @@ export interface UserPayload { /** Whether the user is an official discord system user (part of the urgent message system.) */ system?: boolean; /** Whether the user has two factor enabled on their account */ - mfa_enabled?: boolean; + "mfa_enabled"?: boolean; /** the user's chosen language option */ locale?: string; /** Whether the email on this account has been verified */ @@ -22,7 +22,7 @@ export interface UserPayload { /** The flags on a user's account. */ flags?: number; /** The type of Nitro subscription on a user's account. */ - premium_type?: number; + "premium_type"?: number; } export interface MemberCreatePayload { @@ -33,9 +33,9 @@ export interface MemberCreatePayload { /** Array of role ids that the member has */ roles: string[]; /** When the user joined the guild. */ - joined_at: string; + "joined_at": string; /** When the user used their nitro boost on the server. */ - premium_since?: string; + "premium_since"?: string; /** Whether the user is deafened in voice channels */ deaf: boolean; /** Whether the user is muted in voice channels */ diff --git a/src/interactions/types/slash.ts b/src/interactions/types/slash.ts index ae9e2a1a7..b466b6bbd 100644 --- a/src/interactions/types/slash.ts +++ b/src/interactions/types/slash.ts @@ -18,7 +18,7 @@ export interface SlashCommand { /** unique id of the command */ id: string; /** unique id of the parent application */ - application_id: string; + "application_id": string; /** 3-32 character name */ name: string; /** 1-100 character description */ diff --git a/src/interactions/types/webhook.ts b/src/interactions/types/webhook.ts index 896e0faa9..9743bb8b9 100644 --- a/src/interactions/types/webhook.ts +++ b/src/interactions/types/webhook.ts @@ -8,7 +8,7 @@ export interface ExecuteWebhookOptions { /** override the default username of the webhook */ username?: string; /** override the default avatar of the webhook*/ - avatar_url?: string; + "avatar_url"?: string; /** true if this is a TTS message */ tts?: boolean; /** file contents the contents of the file being sent one of content, file, embeds */ diff --git a/src/rest/deps.ts b/src/rest/deps.ts index 5d1187c48..1bb43a371 100644 --- a/src/rest/deps.ts +++ b/src/rest/deps.ts @@ -1 +1 @@ -export * from "https://deno.land/std@0.83.0/http/server.ts"; +export * from "https://deno.land/std@0.87.0/http/server.ts"; diff --git a/src/rest/request_manager.ts b/src/rest/request_manager.ts index 63c481964..071bf91c9 100644 --- a/src/rest/request_manager.ts +++ b/src/rest/request_manager.ts @@ -1,4 +1,4 @@ -import { authorization, eventHandlers } from "../bot.ts"; +import { authorization, eventHandlers, restAuthorization } from "../bot.ts"; import { Errors, FileContent, @@ -121,7 +121,7 @@ async function processQueue() { } if (Object.keys(pathQueues).length) { - await cleanupQueues(); + cleanupQueues(); } else queueInProcess = false; } } @@ -163,11 +163,10 @@ function createRequestBody(body: any, method: RequestMethods) { if (!Array.isArray(body.file)) body.file = [body.file]; const form = new FormData(); - // form.append("file", body.file.blob, body.file.name); body.file.map((file: FileContent, index: number) => - // The key of the form data item must be unique; otherwise, Discordeno only considers the first item in the form data with the same names. - form.append(`file${index + 1}`, file.blob, file.name) + // The key of the form data item must be unique; otherwise, Discordeno only considers the first item in the form data with the same names + form.append(`file${index + 1}`, file.blob as Blob, file.name) ); form.append("payload_json", JSON.stringify({ ...body, file: undefined })); @@ -222,7 +221,16 @@ function runMethod( !url.startsWith(`${BASE_URL}/v${API_VERSION}`) && !url.startsWith(IMAGE_BASE_URL) ) { - return fetch(url, { method, body: body ? JSON.stringify(body) : undefined }) + return fetch(url, { + body: JSON.stringify({ + url, + method, + ...(body as Record || {}), + }), + headers: { + authorization: restAuthorization, + }, + }) .then((res) => res.json()) .catch((error) => { console.error(error); @@ -231,7 +239,8 @@ function runMethod( } // No proxy so we need to handle all rate limiting and such - return new Promise((resolve, reject) => { + // deno-lint-ignore no-async-promise-executor + return new Promise(async (resolve, reject) => { const callback = async () => { try { const rateLimitResetIn = await checkRatelimits(url); @@ -263,7 +272,7 @@ function runMethod( }, ); const bucketIDFromHeaders = processHeaders(url, response.headers); - handleStatusCode(response, errorStack); + await handleStatusCode(response, errorStack); // Sometimes Discord returns an empty 204 response that can't be made to JSON. if (response.status === 204) return resolve(undefined); @@ -315,7 +324,7 @@ function runMethod( }); if (!queueInProcess) { queueInProcess = true; - processQueue(); + await processQueue(); } }); } @@ -337,7 +346,7 @@ async function logErrors(response: Response, errorStack?: unknown) { } } -function handleStatusCode(response: Response, errorStack?: unknown) { +async function handleStatusCode(response: Response, errorStack?: unknown) { const status = response.status; if ( @@ -347,7 +356,7 @@ function handleStatusCode(response: Response, errorStack?: unknown) { return true; } - logErrors(response, errorStack); + await logErrors(response, errorStack); switch (status) { case HttpResponseCode.BadRequest: diff --git a/src/types/README.md b/src/types/README.md new file mode 100644 index 000000000..37a5133e4 --- /dev/null +++ b/src/types/README.md @@ -0,0 +1,166 @@ +# Discordeno Typings Guidelines / Explanations + +Discordeno has a certain standard guidelines for our typings. Please follow this +as best as possible when contributing to the library. + +1. Discordeno Types + +These types will be specifically used by the end user for functions inside +Discordeno. + +Example: + +```ts +interface EditMemberOptions { + /** Value to set users nickname to. Requires MANAGE_NICKNAMES permission. */ + nick?: string; + /** Array of role ids the member will have after this edit. Useful for adding/removing multiple roles in 1 API call. Requires MANAGE_ROLES permission. */ + roles?: string[]; + /** Whether the user is muted in voice channels. Requires MUTE_MEMBERS permission. */ + mute?: boolean; + /** Whether the user is deafened in voice channels. Requires DEAFEN_MEMBERS permission. */ + deaf?: boolean; + /** The id of the channel to move user to if they are connected to voice. To kick the user from their current channel, set to null. Requires MOVE_MEMBERS permission. When moving members to channels, must have permissions to both CONNECT to the channel and have the MOVE_MEMBER permission. */ + channelID?: string; +} +``` + +Rules: + +- Camel Case +- Do not allow `null` +- Everything should have a comment explaining it +- These typings should be kept in the file with the function. + - Example: EditMemberOptions is at the bottom of the file where editMember() + is declared. + +2. Discord Types Incoming + +These types are meant for the payloads that we receive from Discord, whether +through gateway or REST. + +Example: + +```ts +export interface MemberCreatePayload { + /** The user this guild member represents */ + user: UserPayload; + /** The user's guild nickname if one is set. */ + nick?: string; + /** Array of role ids that the member has */ + roles: string[]; + /** When the user joined the guild. */ + joined_at: string; + /** When the user used their nitro boost on the server. */ + premium_since?: string; + /** Whether the user is deafened in voice channels */ + deaf: boolean; + /** Whether the user is muted in voice channels */ + mute: boolean; + /** Whether the user has passed the guild's Membership Screening requirements */ + pending?: boolean; +} +``` + +Rules: + +- Snake case (Or whatever discord uses. Everything here should be letter to + letter in accordance with discord's docs.) +- Everything should have a comment explaining it +- Kept in the src/types/api/incoming folder + +3. Discord Types Outgoing + +These types are meant **US** as we develop Discordeno. These will help us +prevent bugs when we are sending payloads to Discord whether through gateway or +REST. + +Example: + +```ts +export interface EditMemberPayload { + /** Value to set users nickname to. Requires MANAGE_NICKNAMES permission. */ + nick?: string; + /** Array of role ids the member is assigned. Requires MANAGE_ROLES permission. */ + roles?: string[]; + /** Whether the user is muted in voice channels. Requires MUTE_MEMBERS permission. */ + mute?: boolean; + /** Whether the user is deafened in voice channels. Requires DEAFEN_MEMBERS permission. */ + deaf?: boolean; + /** The id of the channel to move user to if they are connected to voice. To kick the user from their current channel, set to null. Requires MOVE_MEMBERS permission. When moving members to channels, must have permissions to both CONNECT to the channel and have the MOVE_MEMBER permission. */ + channel_id?: string | null; +} +``` + +Rules: + +- Snake case (Or whatever discord uses. Everything here should be letter to + letter in accordance with discord's docs.) +- Everything should have a comment explaining it +- Kept in the src/types/api/outgoing folder + +## Minimalistic + +Since Discord uses `snake_case` but we use `camelCase` we will end up with +redundant typings. In order to solve this, we have the `Camelize` type which +helps create a type using the snake case. + +If we have the base payload for Discord's `User` as follows: + +```ts +export interface DiscordUserPayload { + /** The user's id */ + id: string; + /** the user's username, not unique across the platform */ + username: string; + /** The user's 4 digit discord tag */ + discriminator: string; + /** The user's avatar hash */ + avatar: string | null; + /** Whether the user is a bot */ + bot?: boolean; + /** Whether the user is an official discord system user (part of the urgent message system.) */ + system?: boolean; + /** Whether the user has two factor enabled on their account */ + "mfa_enabled"?: boolean; + /** the user's chosen language option */ + locale?: string; + /** Whether the email on this account has been verified */ + verified?: boolean; + /** The user's email */ + email?: string; + /** The flags on a user's account. */ + flags?: number; + /** The type of Nitro subscription on a user's account. */ + premium_type?: number; +} +``` + +To create the Discordeno version for this it would be done as: + +```ts +export interface UserPayload extends Camelize {} +``` + +Now we have 2 unique interfaces, without having all the extra work or headaches +of maintaing 2 different interfaces. + +Similarily, for any outgoing Discord types, we can also camelize them to have +better user experience for users. + +Example: + +```ts +export interface DiscordBanOptions { + /** number of days to delete messages for (0-7) */ + delete_message_days?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + /** The reason for the ban. */ + reason?: string; +} +``` + +To create the Discordeno version for this it would be done as: + +```ts +export interface BanOptions extends Camelize {} +``` diff --git a/src/types/activity.ts b/src/types/activity.ts index cc8110d87..6aca25172 100644 --- a/src/types/activity.ts +++ b/src/types/activity.ts @@ -2,9 +2,9 @@ export interface ActivityPayload { name: string; type: number; url?: string; - created_at: number; + "created_at": number; timestamps?: ActivityTimestamps; - application_id?: string; + "application_id"?: string; details?: string; state?: string; emoji?: ActivityEmoji; @@ -45,10 +45,10 @@ export interface ActivityParty { } export interface ActivityAssets { - large_image?: string; - large_text?: string; - small_image?: string; - small_text?: string; + "large_image"?: string; + "large_text"?: string; + "small_image"?: string; + "small_text"?: string; } export interface ActivitySecrets { diff --git a/src/types/api/auditlog.ts b/src/types/api/auditlog.ts new file mode 100644 index 000000000..a2047f362 --- /dev/null +++ b/src/types/api/auditlog.ts @@ -0,0 +1,205 @@ +import { + DiscordIntegration, + DiscordOverwrite, + DiscordRole, + DiscordUser, + DiscordWebhook, +} from "./mod.ts"; + +/** https://discord.com/developers/docs/resources/audit-log#audit-log-object */ +export interface DiscordAuditLogPayload { + /** list of webhooks found in the audit log */ + webhooks: DiscordWebhook[]; + /** list of users found in the audit log */ + users: DiscordUser[]; + /** list of audit log entries */ + audit_log_entries: DiscordAuditLogEntry[]; + /** list of partial integration objects */ + integrations: Partial[]; +} + +/** https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-entry-structure */ +export interface DiscordAuditLogEntry { + /** id of the affected entity (webhook, user, role, etc.) */ + target_id: string | null; + /** changes made to the target_id */ + changes?: DiscordAuditLogChange[]; + /** the user who made the changes */ + user_id: string; + /** id of the entry */ + id: string; + /** type of action that occured */ + action_type: DiscordAuditLogEvent; + /** additional info for certain action types */ + options?: DiscordOptionalAuditEntryInfoParam; + /** the reason for the change (0-512 characters) */ + reason?: string; +} + +/** https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-events */ +export enum DiscordAuditLogEvent { + GUILD_UPDATE = 1, + CHANNEL_CREATE = 10, + CHANNEL_UPDATE, + CHANNEL_DELETE, + CHANNEL_OVERWRITE_CREATE, + CHANNEL_OVERWRITE_UPDATE, + CHANNEL_OVERWRITE_DELETE, + MEMBER_KICK = 20, + MEMBER_PRUNE, + MEMBER_BAN_ADD, + MEMBER_BAN_REMOVE, + MEMBER_UPDATE, + MEMBER_ROLE_UPDATE, + MEMBER_MOVE, + MEMBER_DISCONNECT, + BOT_ADD, + ROLE_CREATE = 30, + ROLE_UPDATE, + ROLE_DELETE, + INVITE_CREATE = 40, + INVITE_UPDATE, + INVITE_DELETE, + WEBHOOK_CREATE = 50, + WEBHOOK_UPDATE, + WEBHOOK_DELETE, + EMOJI_CREATE = 60, + EMOJI_UPDATE, + EMOJI_DELETE, + MESSAGE_DELETE = 72, + MESSAGE_BULK_DELETE, + MESSAGE_PIN, + MESSAGE_UNPIN, + INTEGRATION_CREATE = 80, + INTEGRATION_UPDATE, + INTEGRATION_DELETE, +} + +/** https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-optional-audit-entry-info */ +export interface DiscordOptionalAuditEntryInfoParam { + /** number of days after which inactive members were kicked, type: MEMBER_PRUNE */ + delete_member_days: string; + /** number of members removed by the prune, type: MEMBER_PRUNE */ + members_removed: string; + /** channel in which the entities were targeted, types: MEMBER_MOVE & MESSAGE_PIN & MESSAGE_UNPIN & MESSAGE_DELETE */ + channel_id: string; + /** id of the message that was targeted, types: MESSAGE_PIN & MESSAGE_UNPIN */ + message_id: string; + /** number of entities that were targeted, types: MESSAGE_DELETE & MESSAGE_BULK_DELETE & MEMBER_DISCONNECT & MEMBER_MOVE */ + count: string; + /** id of the overwritten entity, types CHANNEL_OVERWRITE_CREATE & CHANNEL_OVERWRITE_UPDATE & CHANNEL_OVERWRITE_DELETE */ + id: string; + /** type of overwritten entity - "0", for "role", or "1" for "member", types: CHANNEL_OVERWRITE_CREATE & CHANNEL_OVERWRITE_UPDATE & CHANNEL_OVERWRITE_DELETE */ + type: string; + /** name of the role if type is "0" (not present if type is "1"), types: CHANNEL_OVERWRITE_CREATE & CHANNEL_OVERWRITE_UPDATE & CHANNEL_OVERWRITE_DELETE */ + role_name: string; +} + +/** https://discord.com/developers/docs/resources/audit-log#audit-log-change-object-audit-log-change-structure */ +export interface DiscordAuditLogChange { + /** new value of the key */ + new_value?: DiscordAuditLogChangeValue; + /** old value of the key */ + old_value?: DiscordAuditLogChangeValue; + /** name of audit log change key */ + key: string; +} + +/** https://discord.com/developers/docs/resources/audit-log#audit-log-change-object-audit-log-change-structure */ +export type DiscordAuditLogChangeValue = + | { + new_value: string; + old_value: string; + key: + | "name" + | "icon_hash" + | "splash_hash" + | "owner_id" + | "region" + | "afk_channel_id" + | "vanity_url_code" + | "widget_channel_id" + | "system_channel_id" + | "topic" + | "application_id" + | "permissions" + | "allow" + | "deny" + | "code" + | "channel_id" + | "inviter_id" + | "nick" + | "avatar_hash" + | "id"; + } + | { + new_value: number; + old_value: number; + key: + | "afk_timeout" + | "mfa_level" + | "verification_level" + | "explicit_content_filter" + | "default_messagae_notifications" + | "prune_delete_days" + | "position" + | "bitrate" + | "rate_limit_per_user" + | "color" + | "max_uses" + | "uses" + | "max_age" + | "expire_behavior" + | "expire_grace_period"; + } + | { + new_value: Partial; + old_value: Partial; + key: "$add" | "$remove"; + } + | { + new_value: boolean; + old_value: boolean; + key: + | "widget_enabled" + | "nsfw" + | "hoist" + | "mentionable" + | "temporary" + | "deaf" + | "mute" + | "enable_emoticons"; + } + | { + new_value: DiscordOverwrite[]; + old_value: DiscordOverwrite[]; + key: "permission_overwrites"; + } + | { + new_value: string | number; + old_value: string | number; + key: "type"; + }; + +/** https://discord.com/developers/docs/resources/audit-log#get-guild-audit-log-query-string-parameters */ +export interface DiscordGetGuildAuditLogParams { + /** filter the log for actions made by a user */ + user_id: string; + /** the type of audit log event */ + action_type: DiscordAuditLogEvent; + /** filter the log before a certain entry id */ + before: string; + /** how many entries are returned (default 50, minimum 1, maximum 100) */ + limit: number; +} + +export interface DiscordGetAuditLogsOptions { + /** Filter the logs for actions made by this user. */ + user_id?: string; + /** The type of audit log. */ + action_type?: DiscordAuditLogEvent; + /** Filter the logs before a certain log entry. */ + before?: string; + /** How many entries are returned. Between 1-100. Default 50. */ + limit?: number; +} diff --git a/src/types/api/channel.ts b/src/types/api/channel.ts new file mode 100644 index 000000000..31b7f79fc --- /dev/null +++ b/src/types/api/channel.ts @@ -0,0 +1,107 @@ +import { DiscordOverwrite, DiscordUser } from "./mod.ts"; + +/** https://discord.com/developers/docs/resources/channel#channel-object-channel-types */ +export enum DiscordChannelTypes { + /** a text channel within a server */ + GUILD_TEXT, + /** a direct message between users */ + DM, + /** a voice channel within a server */ + GUILD_VOICE, + /** a direct message between multiple users */ + GROUP_DM, + /** an organizational category that contains up to 50 channels */ + GUILD_CATEGORY, + /** a channel that users can follow and crosspost into their own server */ + GUILD_NEWS, + /** a channel in which game developers can sell their game on Discord */ + GUILD_STORE, +} + +/** https://discord.com/developers/docs/resources/channel#channel-object-channel-structure */ +export interface DiscordChannel { + /** the id of this channel */ + id: string; + /** the type of channel */ + type: DiscordChannelTypes; + /** the id of the guild */ + guild_id?: string; + /** sorting position of the channel */ + position?: number; + /** explicit permission overwrites for members and roles */ + permission_overwrites?: DiscordOverwrite[]; + /** the name of the channel (2-100 characters) */ + name?: string; + /** the channel topic (0-1024 characters) */ + topic?: string | null; + /** whether the channel is nsfw */ + nsfw?: boolean; + /** the id of the last message sent in this channel (may not point to an existing or valid message) */ + last_message_id?: string | null; + /** the bitrate (in bits) of the voice channel */ + bitrate?: number; + /** the user limit of the voice channel */ + user_limit?: number; + /** amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages or manage_channel, are unaffected */ + rate_limit_per_user?: number; + /** the recipients of the DM */ + recipients?: DiscordUser[]; + /** icon hash */ + icon?: string | null; + /** id of the DM creator */ + owner_id?: string; + /** application id of the group DM creator if it is bot-created */ + application_id?: string; + /** id of the parent category for a channel (each parent category can contain up to 50 channels) */ + parent_id?: string | null; + /** when the last pinned message was pinned. This may be null in events such as GUILD_CREATE when a message is not pinned. */ + last_pin_timestamp?: string | null; +} + +/** https://discord.com/developers/docs/resources/channel#followed-channel-object-followed-channel-structure */ +export interface DiscordFollowedChannel { + /** source channel id */ + channel_id: string; + /** created target webhook id */ + webhook_id: string; +} + +/** https://discord.com/developers/docs/resources/channel#modify-channel-json-params */ +export interface DiscordModifyChannelParams { + /** 2-100 character channel name */ + name?: string; + /** the type of channel; only conversion between text and news is supported and only in guilds with the "NEWS" feature */ + type?: DiscordChannelTypes; + /** the position of the channel in the left-hand listing */ + position?: number | null; + /** 0-1024 character channel topic */ + topic?: string | null; + /** whether the channel is nsfw */ + nsfw?: boolean | null; + /** amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission `MANAGE_MESSAGES` or `MANAGE_CHANNELS`, are unaffected */ + rate_limit_per_user?: number | null; + /** the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers) */ + bitrate?: number | null; + /** the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit */ + user_limit?: number | null; + /** channel or category-specific permissions */ + permission_overwrites?: DiscordOverwrite[] | null; + /** id of the new parent category for a channel */ + parent_id?: string | null; +} + +/** https://discord.com/developers/docs/resources/channel#edit-channel-permissions-json-params */ +export interface DiscordEditChannelPermissions { + /** the bitwise value of all allowed permissions */ + allow: string; + /** the bitwise value of all disallowed permissions */ + deny: string; + /** 0 for a role or 1 for a member */ + type: number; +} + +/** https://discord.com/developers/docs/resources/channel#follow-news-channel-json-params */ +export interface DiscordFollowNewsChannelParams { + /** id of target channel */ + webhook_channel_id: string; +} diff --git a/src/types/api/code.ts b/src/types/api/code.ts new file mode 100644 index 000000000..db12b5de4 --- /dev/null +++ b/src/types/api/code.ts @@ -0,0 +1,188 @@ +/** https://discord.com/developers/docs/topics/opcodes-and-status-codes#opcodes-and-status-codes */ +export enum DiscordGatewayOpcodes { + Dispatch, + Heartbeat, + Identify, + PresenceUpdate, + VoiceStateUpdate, + Resume = 6, + Reconnect, + RequestGuildMembers, + InvalidSession, + Hello, + HeartbeatACK, +} + +/** https://discord.com/developers/docs/topics/opcodes-and-status-codes#opcodes-and-status-codes */ +export enum DiscordGatewayCloseEventCodes { + UnknownError = 4000, + UnknownOpcode, + DecodeError, + NotAuthenticated, + AuthenticationFailed, + AlreadyAuthenticated, + InvalidSeq = 4007, + RateLimited, + SessionTimedOut, + InvalidShard, + ShardingRequired, + InvalidApiVersion, + InvalidIntents, + DisallowedIntents, +} + +/** https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice */ +export enum DiscordVoiceOpcodes { + Identify, + SelectProtocol, + Ready, + Heartbeat, + SessionDescription, + Speaking, + HeartbeatACK, + Resume, + Hello, + Resumed, + ClientDisconnect = 13, +} + +/** https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice */ +export enum DiscordVoiceCloseEventCodes { + UnknownOpcode = 4001, + FailedToDecodePayload, + NotAuthenticated, + AuthenticationFailed, + AlreadyAuthenticated, + SessionNoLongerValid, + SessionTimedOut = 4009, + ServerNotFound = 4011, + UnknownProtocol, + Disconnect = 4014, + VoiceServerCrashed, + UnknownEncryptionMode, +} + +/** https://discord.com/developers/docs/topics/opcodes-and-status-codes#http */ +export enum DiscordHTTPResponseCodes { + Ok = 200, + Created, + NoContent = 204, + NotModified = 304, + BadRequest = 400, + Unauthorized, + Forbidden = 403, + NotFound, + MethodNotAllowed, + TooManyRequests = 429, + GatewayUnavailable = 502, +} + +/** https://discord.com/developers/docs/topics/opcodes-and-status-codes#json */ +export enum DiscordJsonErrorCodes { + GeneralError, + UnknownAccount = 10001, + UnknownApplication, + UnknownChannel, + UnknownGuild, + UnknownIntegration, + UnknownInvite, + UnknownMember, + UnknownMessage, + UnknownPermissionOverwrite, + UnknownProvider, + UnknownRole, + UnknownToken, + UnknownUser, + UnknownEmoji, + UnknownWebhook, + UnknownBan = 10026, + UnknownSKU, + UnknownStoreListing, + UnknownEntitlement, + UnknownBuild, + UnknownLobby, + UnknownBranch, + UnknownRedistributable = 10036, + UnknownGuildTemplate = 10057, + UnknownApplicationCommand = 10063, + BotsCannotUseThisEndpoint = 20001, + OnlyBotsCanUseThisEndpoint, + ThisMessageCannotBeEditedDueToAnnouncementRateLimits = 20022, + TheChannelYouAreWritingHasHitTheWriteRateLimit = 20028, + MaximumNumberOfGuildsReached = 30001, + MaximumNumberOfFriendsReached, + MaximumNumberOfPinsReachedForTheChannel, + MaximumNumberOfGuildRolesReached = 30005, + MaximumNumberOfWebhooksReached = 30007, + MaximumNumberOfReactionsReached = 30010, + MaximumNumberOfGuildChannelsReached = 30013, + MaximumNumberOfAttachmentsInAMessageReached = 30015, + MaximumNumberOfInvitesReached, + GuildAlreadyHasTemplate = 30031, + UnauthorizedProvideAValidTokenAndTryAgain = 40001, + YouNeedToVerifyYourAccountInOrderToPerformThisAction, + RequestEntityTooLargeTrySendingSomethingSmallerInSize = 40005, + ThisFeatureHasBeenTemporarilyDisabledServerSide, + ThisUserBannedFromThisGuild, + ThisMessageHasAlreadyBeenCrossposted = 40033, + MissingAccess = 50001, + InvalidAccountType, + CannotExecuteActionOnADMChannel, + GuildWidgetDisabled, + CannotEditMessageAuthoredByAnotherUser, + CannotSendAnEmptyMessage, + CannotSendMessagesToThisUser, + CannotSendMessagesInAVoiceChannel, + ChannelVerificationLevelIsTooHighForYouToGainAccess, + Oauth2ApplicationDoesNotHaveABot, + Oauth2ApplicationLimitReached, + InvalidOauth2State, + YouLackPermissionsToPerformThatAction, + InvalidAuthenticationTokenProvided, + NoteWasTooLong, + ProvidedTooFewOrTooManyMessagesToDeleteMustProvideAtLeast2AndFewerThan100MessagesToDelete, + AMessageCanOnlyBePinnedInTheChannelItWasSentIn = 50019, + InviteCodeWasEitherInvalidOrTaken, + CannotExecuteActionOnASystemMessage, + CannotExecuteActionOnThisChannelType = 50024, + InvalidOauth2AccessTokenProvided, + InvalidRecipients = 50033, + AMessageProvidedWasTooOldToBulkDelete, + InvalidFormBodyOrContentTypeProvided, + AnInviteWasAcceptedToAGuildTheApplicationsBotIsNotIn, + InvalidApiVersionProvided = 50041, + CannotDeleteAChannelRequiredForCommunityGuilds = 50074, + InvalidStickerSent = 50081, + ReqctionWasBlocked = 90001, + ApiResourceIsCurrentlyOverloadedTryAgainALittleLater = 130000, +} + +/** https://discord.com/developers/docs/topics/opcodes-and-status-codes#rpc */ +export enum DiscordRpcErrorCodes { + UnknownError = 1000, + InvalidPayload = 4000, + InvalidCommand = 4002, + InvalidGuild, + InvalidEvent, + InvalidChannel, + InvalidPermissions, + InvalidClientID, + InvalidOrigin, + InvalidToken, + InvalidUser, + OAuth2Error = 5000, + SelectChannelTimedOut, + GetGuildTimedOut, + SelectVoiceForceRequired, + CaptureShortcutAlreadyListening, +} + +/** https://discord.com/developers/docs/topics/opcodes-and-status-codes#rpc */ +export enum DiscordRpcCloseEventCodes { + InvalidClientID = 4000, + InvalidOrigin, + RateLimited, + TokenRevoked, + InvalidVersion, + InvalidEncoding, +} diff --git a/src/types/api/embed.ts b/src/types/api/embed.ts new file mode 100644 index 000000000..2188c3c11 --- /dev/null +++ b/src/types/api/embed.ts @@ -0,0 +1,115 @@ +/** https://discord.com/developers/docs/resources/channel#embed-object-embed-structure */ +export interface DiscordEmbed { + /** title of embed */ + title?: string; + /** type of embed (always "rich" for webhook embeds) */ + type?: string; + /** description of embed */ + description?: string; + /** url of embed */ + url?: string; + /** timestamp of embed content */ + timestamp?: string; + /** color code of the embed */ + color?: number; + /** footer information */ + footer?: DiscordEmbedFooter; + /** image information */ + image?: DiscordEmbedImage; + /** thumbnail information */ + thumbnail?: DiscordEmbedThumbnail; + /** video information */ + video?: DiscordEmbedVideo; + /** provider information */ + provider?: DiscordEmbedProvider; + /** author information */ + author?: DiscordEmbedAuthor; + /** fields information */ + fields?: DiscordEmbedField[]; +} + +/** + * https://discord.com/developers/docs/resources/channel#embed-object-embed-types + * @deprecated + */ +export type EmbedTypes = + | "rich" + | "image" + | "video" + | "gifv" + | "article" + | "link"; + +/** https://discord.com/developers/docs/resources/channel#embed-object-embed-thumbnail-structure */ +export interface DiscordEmbedThumbnail { + /** source url of thumbnail (only supports http(s) and attachments) */ + url?: string; + /** a proxied url of the thumbnail */ + proxy_url?: string; + /** height of thumbnail */ + height?: number; + /** width of thumbnail */ + width?: number; +} + +/** https://discord.com/developers/docs/resources/channel#embed-object-embed-video-structure */ +export interface DiscordEmbedVideo { + /** source url of video */ + url?: string; + /** height of video */ + height?: number; + /** width of video */ + width?: number; +} + +/** https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure */ +export interface DiscordEmbedImage { + /** source url of image (only supports http(s) and attachments) */ + url?: string; + /** a proxied url of the image */ + proxy_url?: string; + /** height of image */ + height?: number; + /** width of image */ + width?: number; +} + +/** https://discord.com/developers/docs/resources/channel#embed-object-embed-provider-structure */ +export interface DiscordEmbedProvider { + /** name of provider */ + name?: string; + /** url of provider */ + url?: string; +} + +/** https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure */ +export interface DiscordEmbedAuthor { + /** name of author */ + name?: string; + /** url of author */ + url?: string; + /** url of author icon (only supports http(s) and attachments) */ + icon_url?: string; + /** a proxied url of author icon */ + proxy_icon_url?: string; +} + +/** https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure */ +export interface DiscordEmbedFooter { + /** footer text */ + text: string; + /** url of footer icon (only supports http(s) and attachments) */ + icon_url?: string; + /** a proxied url of footer icon */ + proxy_icon_url?: string; +} + +/** https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure */ +export interface DiscordEmbedField { + /** name of the field */ + name: string; + /** value of the field */ + value: string; + /** whether or not this field should display inline */ + inline?: boolean; +} diff --git a/src/types/api/emoji.ts b/src/types/api/emoji.ts new file mode 100644 index 000000000..6a8943da6 --- /dev/null +++ b/src/types/api/emoji.ts @@ -0,0 +1,39 @@ +import { DiscordUser } from "./mod.ts"; + +/** https://discord.com/developers/docs/resources/emoji#emoji-object-emoji-structure */ +export interface DiscordEmoji { + /** emoji id */ + id: string | null; + /** emoji name */ + name: string | null; + /** roles this emoji is whitelisted to */ + roles?: string[]; + /** user that created this emoji */ + user?: DiscordUser; + /** whether this emoji must be wrapped in colons */ + require_colons?: boolean; + /** whether this emoji is managed */ + managed?: boolean; + /** whether this emoji is animated */ + animated?: boolean; + /** whether this emoji can be used, may be false due to loss of Server Boosts */ + available?: boolean; +} + +/** https://discord.com/developers/docs/resources/emoji#create-guild-emoji */ +export interface DiscordCreateGuildEmojiParams { + /** name of the emoji */ + name: string; + /** the 128x128 emoji image (Data URI scheme) */ + image: string; + /** roles for which this emoji will be whitelisted */ + roles: string[]; +} + +/** https://discord.com/developers/docs/resources/emoji#modify-guild-emoji */ +export interface DiscordModifyGuildEmojiParams { + /** name of the emoji */ + name?: string; + /** roles to which this emoji will be whitelisted */ + roles?: string[] | null; +} diff --git a/src/types/api/event.ts b/src/types/api/event.ts new file mode 100644 index 000000000..38c8bc0bf --- /dev/null +++ b/src/types/api/event.ts @@ -0,0 +1,350 @@ +import { + DiscordActivity, + DiscordApplication, + DiscordClientStatus, + DiscordEmoji, + DiscordIntegration, + DiscordMember, + DiscordRole, + DiscordStatusTypes, + DiscordUnavailableGuild, + DiscordUser, +} from "./mod.ts"; + +/** https://discord.com/developers/docs/topics/gateway#hello */ +export interface DiscordHelloEvent { + /** the interval (in milliseconds) the client should heartbeat with */ + heartbeat_interval: number; +} + +/** https://discord.com/developers/docs/topics/gateway#ready */ +export interface DiscordReadyEvent { + /** gateway version */ + v: number; + /** information about the user including email */ + user: DiscordUser; + /** empty array */ + private_channels: []; + /** the guilds the user is in */ + guilds: DiscordUnavailableGuild[]; + /** used for resuming connections */ + session_id: string; + /** the shard information associated with this session, if sent when identifying */ + shard?: [number, number]; + /** contains id and flags */ + application: Pick; +} + +/** https://discord.com/developers/docs/topics/gateway#channel-pins-update-channel-pins-update-event-fields */ +export interface DiscordChannelPinsUpdateEvent { + /** the id of the guild */ + guild_id?: string; + /** the id of the channel */ + channel_id: string; + /** the time at which the most recent pinned message was pinned */ + last_pin_timestamp?: string | null; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-ban-add-guild-ban-add-event-fields */ +export interface DiscordGuildBanAddEvent { + /** id of the guild */ + guild_id: string; + /** the banned user */ + user: DiscordUser; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-ban-remove-guild-ban-remove-event-fields */ +export interface DiscordGuildBanRemoveEvent { + /** id of the guild */ + guild_id: string; + /** the unbanned user */ + user: DiscordUser; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-emojis-update-guild-emojis-update-event-fields */ +export interface DiscordGuildEmojisUpdateEvent { + /** id of the guild */ + guild_id: string; + /** array of emojis */ + emojis: DiscordEmoji[]; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-integrations-update-guild-integrations-update-event-fields */ +export interface DiscordGuildIntegrationsUpdateEvent { + /** id of the guild whose integrations were updated */ + guild_id: string; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-member-add-guild-member-add-extra-fields */ +export interface DiscordGuildMemberAddExtra extends DiscordMember { + /** id of the guild */ + guild_id: string; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-member-remove-guild-member-remove-event-fields */ +export interface DiscordGuildMemberRemoveEvent { + /** the id of the guild */ + guild_id: string; + /** the user who was removed */ + user: DiscordUser; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-member-update-guild-member-update-event-fields */ +export interface DiscordGuildMemberUpdateEvent { + /** the id of the guild */ + guild_id: string; + /** user role ids */ + roles: string[]; + /** the user */ + user: DiscordUser; + /** nickname of the user in the guild */ + nick?: string | null; + /** when the user joined the guild */ + joied_at: string; + /** when the user starting boosting the guild */ + premium_since?: string | null; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-members-chunk-guild-members-chunk-event-fields */ +export interface DiscordGuildMembersChunkEvent { + /** the id of the guild */ + guild_id: string; + /** set of guild members */ + members: DiscordMember[]; + /** the chunk index in the expected chunks for this response (0 <= chunk_index < chunk_count) */ + chunk_index: number; + /** the total number of expected chunks for this response */ + chunk_count: number; + /** if passing an invalid id to REQUEST_GUILD_MEMBERS, it will be returned here */ + not_found?: []; + /** if passing true REQUEST_GUILD_MEMBERS, presences of the returned members will be here */ + presences?: DiscordPresenceUpdateEvent[]; + /** the nonce used in the Guild Members Request */ + nonce?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-role-create-guild-role-create-event-fields */ +export interface DiscordGuildRoleCreateEvent { + /** the id of the guild */ + guild_id: string; + /** the role created */ + role: DiscordRole; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-role-update-guild-role-update-event-fields */ +export interface DiscordGuildRoleUpdateEvent { + /** the id of the guild */ + guild_id: string; + /** the role updated */ + role: DiscordRole; +} + +/** https://discord.com/developers/docs/topics/gateway#guild-role-delete-guild-role-delete-event-fields */ +export interface DiscordGuildRoleDeleteEvent { + /** id of the guild */ + guild_id: string; + /** id of the role */ + role_id: string; +} + +// TODO: Add the documentation Link +export interface DiscordIntegrationCreate extends DiscordIntegration { + /** id of the guild */ + guild_id: string; +} + +export interface DiscordIntegrationUpdate extends DiscordIntegration { + /** id of the guild */ + guild_id: string; +} + +export interface DiscordIntegrationDelete { + /** integration id */ + id: string; + /** id of the guild */ + guild_id: string; + /** id of the bot/OAuth2 application for this discordd integration */ + application_id?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#invite-create-invite-create-event-fields */ +export interface DiscordInviteCreateEvent { + /** the channel the invite is for */ + channel_id: string; + /** the unique invite code */ + code: string; + /** the time at which the invite was created */ + created_at: string; + /** the guild of the invite */ + guild_id?: string; + /** the user that created the invite */ + inviter?: DiscordUser; + /** how long the invite is valid for (in seconds) */ + max_age: number; + /** the maximum number of times the invite can be used */ + max_uses: number; + /** the target user for this invite */ + target_user?: Partial; + /** the type of user target for this invite */ + target_user_type?: number; + /** whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) */ + temporary: boolean; + /** how many times the invite has been used (always will be 0) */ + uses: number; +} + +/** https://discord.com/developers/docs/topics/gateway#invite-delete-invite-delete-event-fields */ +export interface DiscordInviteDeleteEvent { + /** the channel of the invite */ + channel_id: string; + /** the guild of the invite */ + guild_id?: string; + /** the unique invite code */ + code: string; +} + +/** https://discord.com/developers/docs/topics/gateway#message-delete-message-delete-event-fields */ +export interface DiscordMessageDeleteEvent { + /** the id of the message */ + id: string; + /** the id of the channel */ + channel_id: string; + /** the id of the guild */ + guild_id?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#message-delete-bulk-message-delete-bulk-event-fields */ +export interface DiscordMessageDeleteBulkEvent { + /** the ids of the messages */ + ids: string[]; + /** the id of the channel */ + channel_id: string; + /** the id of the guild */ + guild_id?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#message-reaction-add-message-reaction-add-event-fields */ +export interface DiscordMessageReactionAddEvent { + /** the id of the user */ + user_id: string; + /** the id of the channel */ + channel_id: string; + /** the id of the message */ + message_id: string; + /** the id of the guild */ + guild_id?: string; + /** the member who reacted if this happened in a guild */ + member?: DiscordMember; + /** the emoji used to react */ + emoji: Partial; +} + +/** https://discord.com/developers/docs/topics/gateway#message-reaction-remove-message-reaction-remove-event-fields */ +export interface DiscordMessageReactionRemoveEvent { + /** the id of the user */ + user_id: string; + /** the id of the channel */ + channel_id: string; + /** the id of the message */ + message_id: string; + /** the id of the guild */ + guild_id?: string; + /** the emoji used to react */ + emoji: Partial; +} + +/** https://discord.com/developers/docs/topics/gateway#message-reaction-remove-all-message-reaction-remove-all-event-fields */ +export interface DiscordMessageReactionRemoveAllEvent { + /** the id of the channel */ + channel_id: string; + /** the id of the message */ + message_id: string; + /** the id of the guild */ + guild_id?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#message-reaction-remove-emoji-message-reaction-remove-emoji */ +export interface DiscordMessageReactionRemoveEmoji { + /** the id of the channel */ + channel_id: string; + /** the id of the guild */ + guild_id?: string; + /** the id of the message */ + message_id: string; + /** the emoji that was removed */ + emoji: Partial; +} + +/** https://discord.com/developers/docs/topics/gateway#presence-update-presence-update-event-fields */ +export interface DiscordPresenceUpdateEvent { + /** the user presence is being updated for */ + user: DiscordUser; + /** id of the guild */ + guild_id: string; + /** either "idle", "dnd", "online", or "offline" */ + status: DiscordStatusTypes; + /** user's current activities */ + activities: DiscordActivity[]; + /** user's platform-dependent status */ + client_status: DiscordClientStatus; +} + +/** https://discord.com/developers/docs/topics/gateway#typing-start-typing-start-event-fields */ +export interface DiscordTypingStartEvent { + /** id of the channel */ + channel_id: string; + /** id of the guild */ + guild_id?: string; + /** id of the user */ + user_id: string; + /** unix time (in seconds) of when the user started typing */ + timestamp: number; + /** the member who started typing if this happened in a guild */ + member?: DiscordMember; +} + +/** https://discord.com/developers/docs/topics/gateway#voice-server-update-voice-server-update-event-fields */ +export interface DiscordVoiceServerUpdateEvent { + /** voice connection token */ + token: string; + /** the guildd this voice server update is for */ + guild_id: string; + /** the voice server host */ + endpoint: string; +} + +/** https://discord.com/developers/docs/topics/gateway#webhooks-update-webhook-update-event-fields */ +export interface DiscordWebhooksUpdateEvent { + /** id of the guild */ + guild_id: string; + /** id of the channel */ + channel_id: string; +} + +/** https://discord.com/developers/docs/resources/voice#voice-resource */ +export interface DiscordVoiceStateUpdateEvent { + /** the guild id this voice state is for */ + guild_id?: string; + /** the channel id this user is connected to */ + channel_id: string; + /** the user id this voice state is for */ + user_id: string; + /** the guild member this voice state is for */ + member?: DiscordMember; + /** the session id for this voice state */ + session_id: string; + /** whether this user is deafened by the server */ + deaf: boolean; + /** whether this user is muted by the server */ + mute: boolean; + /** whether this user is locally deafened */ + self_deaf: boolean; + /** whether this user is locally muted */ + self_mute: boolean; + /** whether this user is streaming using "Go Live" */ + self_stream?: boolean; + /** whether this user's camera is enabled */ + self_video: boolean; + /** whether this user is muted by the current user */ + suppress: boolean; +} diff --git a/src/types/api/gateway.ts b/src/types/api/gateway.ts new file mode 100644 index 000000000..cfe4864be --- /dev/null +++ b/src/types/api/gateway.ts @@ -0,0 +1,487 @@ +import { DiscordInteractionCommand } from "./interaction.ts"; +import { + DiscordChannel, + DiscordChannelPinsUpdateEvent, + DiscordGatewayOpcodes, + DiscordGuild, + DiscordGuildBanAddEvent, + DiscordGuildBanRemoveEvent, + DiscordGuildEmojisUpdateEvent, + DiscordGuildIntegrationsUpdateEvent, + DiscordGuildMemberAddExtra, + DiscordGuildMemberRemoveEvent, + DiscordGuildMembersChunkEvent, + DiscordGuildMemberUpdateEvent, + DiscordGuildRoleCreateEvent, + DiscordGuildRoleDeleteEvent, + DiscordGuildRoleUpdateEvent, + DiscordHelloEvent, + DiscordIntegrationCreate, + DiscordIntegrationDelete, + DiscordIntegrationUpdate, + DiscordInviteCreateEvent, + DiscordInviteDeleteEvent, + DiscordMember, + DiscordMessage, + DiscordMessageDeleteBulkEvent, + DiscordMessageDeleteEvent, + DiscordMessageReactionAddEvent, + DiscordMessageReactionRemoveAllEvent, + DiscordMessageReactionRemoveEmoji, + DiscordMessageReactionRemoveEvent, + DiscordPresenceUpdateEvent, + DiscordReadyEvent, + DiscordTypingStartEvent, + DiscordUnavailableGuild, + DiscordUser, + DiscordVoiceServerUpdateEvent, + DiscordVoiceStateUpdateEvent, + DiscordWebhooksUpdateEvent, +} from "./mod.ts"; + +/** https://discord.com/developers/docs/topics/gateway#payloads */ +export interface DiscordGateway { + /** opcode for the payload */ + op: DiscordGatewayOpcodes; + /** event data */ + d: DiscordGatewayDTypes; + /** sequence number, used for resuming sessions and heartbeats */ + s: number | null; + /** the event name for this payload */ + t: DiscordGatewayTTypes; +} + +/** GatewayPayload event data type list */ +export type DiscordGatewayDTypes = + | DiscordHelloEvent + | DiscordReadyEvent + | DiscordResume + | DiscordChannel + | DiscordChannelPinsUpdateEvent + | DiscordGuild + | DiscordUnavailableGuild + | DiscordGuildBanAddEvent + | DiscordGuildBanRemoveEvent + | DiscordGuildEmojisUpdateEvent + | DiscordGuildIntegrationsUpdateEvent + | DiscordMember + | DiscordGuildMemberAddExtra + | DiscordGuildMemberRemoveEvent + | DiscordGuildMemberUpdateEvent + | DiscordGuildMembersChunkEvent + | DiscordGuildRoleCreateEvent + | DiscordGuildRoleUpdateEvent + | DiscordGuildRoleDeleteEvent + | DiscordIntegrationCreate + | DiscordIntegrationUpdate + | DiscordIntegrationDelete + | DiscordInviteCreateEvent + | DiscordInviteDeleteEvent + | DiscordMessage + | DiscordMessageDeleteEvent + | DiscordMessageDeleteBulkEvent + | DiscordMessageReactionAddEvent + | DiscordMessageReactionRemoveEvent + | DiscordMessageReactionRemoveAllEvent + | DiscordMessageReactionRemoveEmoji + | DiscordPresenceUpdateEvent + | DiscordTypingStartEvent + | DiscordUser + | DiscordVoiceStateUpdateEvent + | DiscordVoiceServerUpdateEvent + | DiscordWebhooksUpdateEvent + | DiscordInteractionCommand + | false + | null; + +/** GatewayPayload event name list */ +export type DiscordGatewayTTypes = + | "HELLO" + | "READY" + | "RESUMED" + | "RECONNECT" + | "INVALID_SESSION" + | "CHANNEL_CREATE" + | "CHANNEL_UPDATE" + | "CHANNEL_DELETE" + | "CHANNEL_PINS_UPDATE" + | "GUILD_CREATE" + | "GUILD_UPDATE" + | "GUILD_DELETE" + | "GUILD_BAN_ADD" + | "GUILD_BAN_REMOVE" + | "GUILD_EMOJIS_UPDATE" + | "GUILD_INTEGRATIONS_UPDATE" + | "GUILD_MEMBER_ADD" + | "GUILD_MEMBER_REMOVE" + | "GUILD_MEMBER_UPDATE" + | "GUILD_MEMBERS_CHUNK" + | "GUILD_ROLE_CREATE" + | "GUILD_ROLE_UPDATE" + | "GUILD_ROLE_DELETE" + | "INTEGRATION_CREATE" + | "INTEGRATION_UPDATE" + | "INTEGRATION_DELETE" + | "INVITE_CREATE" + | "INVITE_DELETE" + | "MESSAGE_CREATE" + | "MESSAGE_UPDATE" + | "MESSAGE_DELETE" + | "MESSAGE_DELETE_BULK" + | "MESSAGE_REACTION_ADD" + | "MESSAGE_REACTION_REMOVE" + | "MESSAGE_REACTION_REMOVE_ALL" + | "MESSAGE_REACTION_REMOVE_EMOJI" + | "PRESENCE_UPDATE" + | "TYPING_START" + | "USER_UPDATE" + | "VOICE_STATE_UPDATE" + | "VOICE_SERVER_UPDATE" + | "WEBHOOKS_UPDATE" + | "INTERACTION_CREATE" + // Not in DC documentation + | "APPLICATION_COMMAND_CREATE" + | null; + +/** https://discord.com/developers/docs/topics/gateway#connecting-to-the-gateway */ +export interface DiscordGatewayURLParams { + /** gateway Version to use */ + v: number; + /** the encoding of recieved gateway packets */ + encoding: string; + /** the (optional) compression of gateway packets */ + compress?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#gateway-intents */ +export enum DiscordGatewayIntents { + /** Enables the following events: + * - GUILD_CREATE + * - GUILD_DELETE + * - GUILD_ROLE_CREATE + * - GUILD_ROLE_UPDATE + * - GUILD_ROLE_DELETE + * - CHANNEL_CREATE + * - CHANNEL_UPDATE + * - CHANNEL_DELETE + * - CHANNEL_PINS_UPDATE + */ + GUILDS = 1 << 0, + /** Enables the following events: + * - GUILD_MEMBER_ADD + * - GUILD_MEMBER_UPDATE + * - GUILD_MEMBER_REMOVE + */ + GUILD_MEMBERS = 1 << 1, + /** Enables the following events: + * - GUILD_BAN_ADD + * - GUILD_BAN_REMOVE + */ + GUILD_BANS = 1 << 2, + /** Enables the following events: + * - GUILD_EMOJIS_UPDATE + */ + GUILD_EMOJIS = 1 << 3, + /** Enables the following events: + * - GUILD_INTEGRATIONS_UPDATE + * - INTEGRATION_CREATE + * - INTEGRATION_UPDATE + * - INTEGRATION_DELETE + */ + GUILD_INTEGRATIONS = 1 << 4, + /** Enables the following events: + * - WEBHOOKS_UPDATE + */ + GUILD_WEBHOOKS = 1 << 5, + /** Enables the following events: + * - INVITE_CREATE + * - INVITE_DELETE + */ + GUILD_INVITES = 1 << 6, + /** Enables the following events: + * - VOICE_STATE_UPDATE + */ + GUILD_VOICE_STATES = 1 << 7, + /** Enables the following events: + * - PRESENCE_UPDATE + */ + GUILD_PRESENCES = 1 << 8, + /** Enables the following events: + * - MESSAGE_CREATE + * - MESSAGE_UPDATE + * - MESSAGE_DELETE + */ + GUILD_MESSAGES = 1 << 9, + /** Enables the following events: + * - MESSAGE_REACTION_ADD + * - MESSAGE_REACTION_REMOVE + * - MESSAGE_REACTION_REMOVE_ALL + * - MESSAGE_REACTION_REMOVE_EMOJI + */ + GUILD_MESSAGE_REACTIONS = 1 << 10, + /** Enables the following events: + * - TYPING_START + */ + GUILD_MESSAGE_TYPING = 1 << 11, + /** Enables the following events: + * - CHANNEL_CREATE + * - MESSAGE_CREATE + * - MESSAGE_UPDATE + * - MESSAGE_DELETE + * - CHANNEL_PINS_UPDATE + */ + DIRECT_MESSAGES = 1 << 12, + /** Enables the following events: + * - MESSAGE_REACTION_ADD + * - MESSAGE_REACTION_REMOVE + * - MESSAGE_REACTION_REMOVE_ALL + * - MESSAGE_REACTION_REMOVE_EMOJI + */ + DIRECT_MESSAGE_REACTIONS = 1 << 13, + /** Enables the following events: + * - TYPING_START + */ + DIRECT_MESSAGE_TYPING = 1 << 14, +} + +/** https://discord.com/developers/docs/topics/gateway#identify */ +export interface DiscordIdentify { + /** authentication token */ + token: string; + /** connection properties */ + properties: DiscordIdentifyConnectionProps; + /** whether this connection supports compression of packets, default: false */ + compress?: boolean; + /** value between 50 and 250, total number of members where the gateway will stop sending offline members in the guild member list, default: 50 */ + large_threshold?: number; + /** used for Guild Sharding */ + shard: [number, number]; + /** presence structure for initial presence information */ + presence?: DiscordUpdateStatus; + /** enables dispatching of guild subscription events (presence and typing events), default: true */ + guild_subscriptions?: boolean; + /** the Gateway Intents you wish to receive */ + intents: number; +} + +/** https://discord.com/developers/docs/topics/gateway#identify-identify-connection-properties */ +export interface DiscordIdentifyConnectionProps { + /** your operating system */ + $os: string; + /** your library name */ + $browser: string; + /** your library name */ + $device: string; +} + +/** https://discord.com/developers/docs/topics/gateway#resume */ +export interface DiscordResume { + /** session token */ + token: string; + /** session id */ + session_id: string; + /** last sequence number received */ + seq: number; +} + +/** https://discord.com/developers/docs/topics/gateway#request-guild-members */ +export interface DiscordRequestGuildMembers { + /** id of the guild to get members for */ + guild_id: string; + /** string that username starts with, or an empty string to return all members */ + query?: string; + /** maximum number of members to send matching the query; a limit of 0 can be used with an empty string query to return all members */ + limit: number; + /** used to specify if we want the presence of the matched members */ + presences?: boolean; + /** used to specify which users you wish to fetch */ + user_ids?: string | string[]; + /** nonce to identify the Guild Members Chunk response */ + nonce?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#update-voice-state */ +export interface DiscordUpdateVoiceState { + /** id of the guild */ + guild_id: string; + /** id of the voice channel client wants to join (null if disconnecting) */ + channel_id: string | null; + /** is the client muted */ + self_mute: boolean; + /** is the client deafened */ + self_deaf: boolean; +} + +/** https://discord.com/developers/docs/topics/gateway#update-status */ +export interface DiscordUpdateStatus { + /** unix time (in milliseconds) of when the client went idle, or null if the client is not idle */ + since: number | null; + /** null, or the user's activities */ + activities: DiscordActivity[]; + /** the user's new status */ + status: DiscordStatusTypes; + /** whether or not the client is afk */ + afk: boolean; +} + +/** https://discord.com/developers/docs/topics/gateway#update-status-status-types */ +export enum DiscordStatusTypes { + ONLINE = "online", + DND = "dnd", + IDLE = "idle", + INVISIBLE = "invisible", + OFFLINE = "offline", +} + +/** https://discord.com/developers/docs/topics/gateway#client-status-object */ +export interface DiscordClientStatus { + /** the user's status set for an active desktop (Windows, Linux, Mac) application session */ + desktop?: string; + /** the user's status set for an active mobile (iOS, Android) application session */ + mobile?: string; + /** the user's status set for an active web (browser, bot account) application session */ + web?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#activity-object */ +export interface DiscordActivity { + /** the activity's id */ + id?: string; + /** the activity's name */ + name: string; + /** activity type */ + type: DiscordActivityTypes; + /** stream url, is validated when type is 1 */ + url?: string | null; + /** unix timestamp of when the activity was added to the user's session */ + created_at: number; + /** unix timestamps for start and/or end of the game */ + timestamps?: DiscordActivityTimestamps; + /** the id of the song on Spotify */ + sync_id?: string; + /** the platform the game is being played on ("desktop", "samsung", or "xbox") */ + platform?: string; + /** application id for the game */ + application_id?: string; + /** what the player is currently doing */ + details?: string | null; + /** the user's current party status */ + state?: string | null; + /** the emoji used for a custom status */ + emoji?: DiscordActivityEmoji | null; + /** the id of the game or Spotify session */ + session_id?: string; + /** information for the current party of the player */ + party?: DiscordActivityParty; + /** images for the presence and their hover texts */ + assets?: DiscordActivityAssets; + /** secrets for Rich Presence joining and spectating */ + secrets?: DiscordActivitySecrets; + /** whether or not the activity is an instanced game session */ + instance?: boolean; + /** activity flags OR d together, describes what the payload includes */ + flags?: DiscordActivityFlags; + /** the custom buttons shown in the Rich Presence (max 2) */ + buttons?: DiscordActivityButton[]; +} + +export interface DiscordActivityButton { + /** the text shown on the button (1-32 characters) */ + label: string; + /** the url opened when clicking the button (1-512 characters) */ + url: string; +} + +/** https://discord.com/developers/docs/topics/gateway#activity-object-activity-types */ +export enum DiscordActivityTypes { + /** Playing {name} */ + GAME, + /** Streaming {details} */ + STREAMING, + /** Listening to {name} */ + LISTENING, + /** {emoji} {name} */ + CUSTOM = 4, + /** Competing in {name} */ + COMPETING, +} + +/** https://discord.com/developers/docs/topics/gateway#activity-object-activity-timestamps */ +export interface DiscordActivityTimestamps { + /** unix time (in milliseconds) of when the activity started */ + start?: number; + /** unix time (in milliseconds) of when the activity ends */ + end?: number; +} + +/** https://discord.com/developers/docs/topics/gateway#activity-object-activity-emoji */ +export interface DiscordActivityEmoji { + /** the name of the emoji */ + name: string; + /** the id of the emoji */ + id?: string; + /** whether this emoji is animated */ + animated?: boolean; +} + +/** https://discord.com/developers/docs/topics/gateway#activity-object-activity-party */ +export interface DiscordActivityParty { + /** the id of the party */ + id?: string; + /** used to show the party's currrent and maximum size */ + size?: [number, number]; +} + +/** https://discord.com/developers/docs/topics/gateway#activity-object-activity-assets */ +export interface DiscordActivityAssets { + /** the id for a large asset of the activity, usually a snowflake */ + large_image?: string; + /** text displayed when hovering over the large image of the activity */ + large_text?: string; + /** the id for a small asset of the activity, usually a snowflake */ + small_image?: string; + /** text displlayed when hovering over the small image of the activity */ + small_text?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#activity-object-activity-secrets */ +export interface DiscordActivitySecrets { + /** the secret for joining a party */ + join?: string; + /** the secret for spectating a game */ + spectate?: string; + /** the secret for a specific instanced match */ + match?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#activity-object-activity-flags */ +export enum DiscordActivityFlags { + INSTANCE = 1 << 0, + JOIN = 1 << 1, + SPECTATE = 1 << 2, + JOIN_REQUEST = 1 << 3, + SYNC = 1 << 4, + PLAY = 1 << 5, +} + +/** https://discord.com/developers/docs/topics/gateway#get-gateway-bot-json-response */ +export interface DiscordGetGatewayBot { + /** the WSS URL that can be used for connecting to the gateway */ + url: string; + /** the recommended number of shards to use when connecting */ + shards: number; + /** information on the current session start limit */ + session_start_limit: DiscordSessionStartLimit; +} + +/** https://discord.com/developers/docs/topics/gateway#session-start-limit-object-session-start-limit-structure */ +export interface DiscordSessionStartLimit { + /** the total number of session starts the current user is allowed */ + total: number; + /** the remaining number of session starts the current user is allowed */ + remaining: number; + /** the number of milliseconds after which the limit resets */ + reset_after: number; + /** the number of identify requests allowed per 5 seconds */ + max_concurrency: number; +} diff --git a/src/types/api/guild.ts b/src/types/api/guild.ts new file mode 100644 index 000000000..4dcff62bd --- /dev/null +++ b/src/types/api/guild.ts @@ -0,0 +1,465 @@ +import { + DiscordChannel, + DiscordChannelTypes, + DiscordEmoji, + DiscordMember, + DiscordOverwrite, + DiscordPresenceUpdateEvent, + DiscordRole, + DiscordUser, + DiscordVoiceStateUpdateEvent, +} from "./mod.ts"; + +/** https://discord.com/developers/docs/resources/guild#guild-object */ +export interface DiscordGuild { + /** guild id */ + id: string; + /** guild name (2-100 characaters, excluding trailing and leading whitespace) */ + name: string; + /** icon hash */ + icon: string | null; + /** icon hash, returned when in the template object */ + icon_hash?: string | null; + /** splash hash */ + splash: string | null; + /** discovery splash hash; only present for guilds with the "DISCOVERABLE" feature */ + discovery_splash: string | null; + /** true if the user is the owner of the guild */ + owner?: boolean; + /** id of the owner */ + owner_id: string; + /** total permissions for the user in the guild (execludes overrides) */ + permissions?: string; + /** voice region id for the guild */ + region: string; + /** id of afk channel */ + afk_channel_id: string | null; + /** afk timeout in seconds */ + afk_timeout: number; + /** true if the server widget is enabled */ + widget_enabled?: boolean; + /** the channel id that the widget will generate an invite to, or null if set to no invite */ + widget_channel_id?: string | null; + /** verification level required for the guild */ + verification_level: DiscordVerificationLevel; + /** default message notifications level */ + default_message_notifications: DiscordDefaultMessageNotificationLevel; + /** explicit content filter level */ + explicit_content_filter: DiscordExplicitContentFilterLevel; + /** roles in the guild */ + roles: DiscordRole[]; + /** custom guild emojis */ + emojis: DiscordEmoji[]; + /** enabled guild features */ + features: DiscordGuildFeatures[]; + /** required MFA level for the guild */ + mfa_level: DiscordMFALevel; + /** application id of the guild creator if it is bot-created */ + application_id: string | null; + /** the id of the channel where guild notices such as welcome messages and boost events are posted */ + system_channel_id: string | null; + /** system channel flags */ + system_channel_flags: DiscordSystemChannelFlags; + /** the id of the channel where community guilds can display rules and/or guidelines */ + rules_channel_id: string | null; + /** when this guild was joined at */ + joined_at?: string; + /** true if this is considered a large guild */ + large?: boolean; + /** true if this guild is unavailable due to an outage */ + unavailable?: boolean; + /** total number of members in this guild */ + member_count?: number; + /** states of members currently in voice channels; lacks the guild_id key */ + voice_states?: Partial[]; + /** users in the guild */ + members?: DiscordMember[]; + /** channels in the guild */ + channels?: DiscordChannel[]; + /** presences of the members in the guild, will only include non-offline members if the size is greater than large threshold */ + presences?: Partial[]; + /** the maximum number of presences for the guild (the default value, currently 25000, is in effect when null is returned) */ + max_presences?: number | null; + /** the maximum number of members for the guild */ + max_members?: number; + /** the vaniy url code for the guild */ + vanity_url_code: string | null; + /** the description for the guild, if the guild is discoverable */ + description: string | null; + /** banner hash */ + banner: string | null; + /** premium tier (Server Boost level) */ + premium_tier: DiscordPremiumTier; + /** the number of boosts this guild currently has */ + premium_subscription_count?: number; + /** the preferred locale of a Community guild; used in server discovery and notices from Discord; defaults to "en-US" */ + preferred_locale: string; + /** the id of the channel where admins and moderators of Community guilds receive notices from Discord */ + public_updates_channel_id: string | null; + /** the maximum amount of users in a video channel */ + max_video_channel_users?: number; + /** approximate number of members in this guild, returned from the GET /guilds/ endpoint when with_counts is true */ + approximate_member_count?: number; + /** approximate number of non-offline members in this guild, returned from the GET /guilds/ endpoint when with_counts is true */ + approximate_presence_count?: number; +} + +/** https://discord.com/developers/docs/resources/guild#guild-object-default-message-notification-level */ +export enum DiscordDefaultMessageNotificationLevel { + ALL_MESSAGES, + ONLY_MENTIONS, +} + +/** https://discord.com/developers/docs/resources/guild#guild-object-explicit-content-filter-level */ +export enum DiscordExplicitContentFilterLevel { + DISABLED, + MEMBERS_WITHOUT_ROLES, + ALL_MEMBERS, +} + +/** https://discord.com/developers/docs/resources/guild#guild-object-mfa-level */ +export enum DiscordMFALevel { + NONE, + ELEVATED, +} + +/** https://discord.com/developers/docs/resources/guild#guild-object-verification-level */ +export enum DiscordVerificationLevel { + NONE, + LOW, + MEDIUM, + HIGH, + VERY_HIGH, +} + +/** https://discord.com/developers/docs/resources/guild#guild-object-premium-tier */ +export enum DiscordPremiumTier { + NONE, + TIER_1, + TIER_2, + TIER_3, +} + +/** https://discord.com/developers/docs/resources/guild#guild-object-system-channel-flags */ +export enum DiscordSystemChannelFlags { + SUPPRESS_JOIN_NOTIFICATIONS = 1 << 0, + SUPPRESS_PREMIUM_SUBSCRIPTIONS = 1 << 1, +} + +/** https://discord.com/developers/docs/resources/guild#guild-object-guild-features */ +export enum DiscordGuildFeatures { + INVITE_SPLASH, + VIP_REGIONS, + VANITY_URL, + VERIFIED, + PARTNERED, + COMMUNITY, + COMMERCE, + NEWS, + DISCOVERABLE, + FEATURABLE, + ANIMATED_ICON, + BANNER, + WELCOME_SCREEN_ENABLED, + MEMBER_VERIFICATION_GATE_ENABLED, + PREVIEW_ENABLED, +} + +/** https://discord.com/developers/docs/resources/guild#unavailable-guild-object */ +export type DiscordUnavailableGuild = Pick; + +/** https://discord.com/developers/docs/resources/guild#guild-preview-object */ +export interface DiscordGuildPreview { + /** guild id */ + id: string; + /** guild name (2-100 characters) */ + name: string; + /** icon hash */ + icon: string | null; + /** splash hash */ + splash: string | null; + /** discovery splash hash */ + discovery_splash: string | null; + /** custom guild emojis */ + emojis: DiscordEmoji[]; + /** enabled guild features */ + features: DiscordGuildFeatures[]; + /** approximate number of members in this guild */ + approximate_member_count: number; + /** approximate number of online members in this guild */ + approximate_presence_count: number; + /** the description for the guild */ + description: string | null; +} + +/** https://discord.com/developers/docs/resources/guild#guild-widget-object-guild-widget-structure */ +export interface DiscordGuildWidget { + /** whether the widget is enabled */ + enabled: boolean; + /** the widget channel id */ + channel_id: string | null; +} + +/** https://discord.com/developers/docs/resources/guild#ban-object */ +export interface DiscordBan { + /** the reason for the ban */ + reason: string | null; + /** the banned user */ + user: DiscordUser; +} + +export interface DiscordMembershipScreening { + /** when the fields were last updated */ + version: string; + /** the steps in the screening form */ + form_fields: DiscordMembershipScreeningField[]; + /** the server description shown in the screening form */ + description: string | null; +} + +export interface DiscordMembershipScreeningField { + /** the type of field (currently "TERMS" is the only type) */ + field_type: DiscordMembershipScreeningFieldTypes; + /** the title of the field */ + label: string; + /** the list of rules */ + values?: string[]; + /** whether the user has to fill out this field */ + required: boolean; +} + +export enum DiscordMembershipScreeningFieldTypes { + /** Server Rules */ + TERMS = "TERMS", +} + +/** https://discord.com/developers/docs/resources/guild#create-guild */ +export interface DiscordCreateGuildParams { + /** name of the guild (2-100 characters) */ + name: string; + /** voice region id */ + region?: string; + /** base64 128x128 image for the guild icon */ + icon?: string; + /** verification level */ + verification_level?: DiscordVerificationLevel; + /** default message notification level */ + default_message_notifications?: DiscordDefaultMessageNotificationLevel; + /** explicit content filter level */ + explicit_content_filter?: DiscordExplicitContentFilterLevel; + /** new guild roles (first role is the everyone role) */ + roles?: DiscordRole[]; + /** new guild's channels */ + channels?: Partial[]; + /** id for afk channel */ + afk_channel_id?: string; + /** afk timeout in seconds */ + afk_timeout?: number; + /** the id of the channel where guild notices such as welcome messages and boost events are posted */ + system_channel_id?: string; +} + +/** https://discord.com/developers/docs/resources/guild#get-guild */ +export interface DiscordGetGuildParams { + /** when true, will return approximate member and presence counts for the guild */ + with_counts?: boolean; +} + +/** https://discord.com/developers/docs/resources/guild#modify-guild */ +export interface DiscordModifyGuildParams { + /** guild name */ + name?: string; + /** guild voice region id */ + region?: string | null; + /** verification level */ + verification_level?: DiscordVerificationLevel | null; + /** default message notification filter level */ + default_message_notifications?: DiscordDefaultMessageNotificationLevel | null; + /** explicit content filter level */ + explicit_content_filter?: DiscordExplicitContentFilterLevel | null; + /** id for afk channel */ + afk_channel_id?: string | null; + /** afk timeout in seconds */ + afk_timeout?: number; + /** base64 1024x1024 png/jpeg/gif image for the guild icon (can be animated gif when the server has ANIMATED_ICON feature) */ + icon?: string | null; + /** user id to transfer guild ownershop to (must be owner) */ + owner_id?: string; + /** base64 16:9 png/jpeg image for the guild splash (when the server has INVITE_SPLASH feature) */ + splash?: string | null; + /** base64 16:9 png/jpeg image for the guild banner (when the server has BANNER feature) */ + banner?: string | null; + /** the id of the channel where guild notices such as welcome messages and boost events are posted */ + system_channel_id?: string | null; + /** the id of the channel where Community guilds display rules and/or guidelines */ + rules_channel_id?: string | null; + /** the id of the channel where admins and moderators of Community guilds receive notices from Discord */ + public_updates_channel_id?: string | null; + /** the preferred locale of a Community guild used in server discovery and notices from Discord; defaults to "en-US" */ + preferred_locale?: string | null; +} + +/** https://discord.com/developers/docs/resources/guild#create-guild-channel */ +export interface DiscordCreateGuildChannelParams { + /** channel name (2-100 characters) */ + name: string; + /** the type of channel */ + type?: DiscordChannelTypes; + /** channel topic (0-1024 characters) */ + topic?: string; + /** the bitrate (in bits) of the voice channel (voice only) */ + bitrate?: number; + /** the user limit of the voice channel (voice only) */ + user_limit?: number; + /** amount of seconds a user has to wait before sending another message (0-21600); bots, as well as users with the permission manage_messages or manage_channel, are unaffected */ + rate_limit_per_user?: number; + /** sorting position of the channel */ + position?: number; + /** the channel's permission overwrites */ + permission_overwrites?: DiscordOverwrite[]; + /** id of the parent category for a channel */ + parent_id?: string; + /** whether the channel is nsfw */ + nsfw?: boolean; +} + +/** https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions */ +export interface DiscordModifyGuildChannelPositionsParam { + /** channel id */ + id: string; + /** sorting position of the channel */ + position: number | null; +} + +/** https://discord.com/developers/docs/resources/guild#list-guild-members */ +export interface DiscordListGuildMembersParams { + /** max number of members to return (1-1000), default 1 */ + limit: number; + /** the highest user id in the previous page, default 0 */ + after: string; +} + +/** https://discord.com/developers/docs/resources/guild#add-guild-member */ +export interface DiscordAddGuildMemberParams { + /** an oauth2 access token granted with the guilds.join to the bot's application for the user you want to add to the guild */ + access_token: string; + /** value to set users nickname to. Requires the MANAGE_NICKNAMES permission */ + nick?: string; + /** array of role ids the member is assigned. Requires the MANAGE_ROLES permission */ + roles?: string[]; + /** whether the user is muted in voice channels. Requires the MUTE_MEMBERS permission */ + mute?: boolean; + /** whether the user is deafened in voice channels. Requires the DEAFEN_MEMBERS permission */ + deaf?: boolean; +} + +/** https://discord.com/developers/docs/resources/guild#modify-guild-member */ +export interface DiscordModifyGuildMemberParams { + /** value to set users nickname to. Requires the MANAGE_NICKNAMES permission */ + nick?: string | null; + /** array of role ids the member is assigned. Requires the MANAGE_ROLES permission */ + roles?: string[] | null; + /** whether the user is muted in voice channels. Will throw a 400 if the user is not in a voice channel. Requires the MUTE_MEMBERS permission */ + mute?: boolean | null; + /** whether the user is deafened in voice channels. Will throw a 400 if the user is not in a voice channel. Requires the MOVE_MEMBERS permission */ + deaf?: boolean | null; + /** id of channel to move user to (if they are connected to voice). Requires the MOVE_MEMBERS permission */ + channel_id: string | null; +} + +/** https://discord.com/developers/docs/resources/guild#modify-current-user-nick */ +export interface DiscordModifyCurrentUserNickParams { + /** value to set users nickname to. Requires the CHANGE_NICKNAME permission */ + nick?: string | null; +} + +/** https://discord.com/developers/docs/resources/guild#create-guild-ban */ +export interface DiscordCreateGuildBan { + /** number of days to delete messages for (0-7) */ + delete_message_days?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + /** reason for the ban */ + reason?: string; +} + +/** https://discord.com/developers/docs/resources/guild#create-guild-role */ +export interface DiscordCreateGuildRoleParams { + /** name of the role, default: "new role" */ + name?: string; + /** bitwise value of the enabled/disabled permissions, default: everyone permissions in guild */ + permissions?: string; + /** RGB color value, default: 0 */ + color?: number; + /** whether the role should be displayed separately in the sidebar, default: false */ + hoist?: boolean; + /** whether the role should be mentionable, default: false */ + mentionable?: boolean; +} + +/** https://discord.com/developers/docs/resources/guild#modify-guild-role-positions */ +export interface DiscordModifyGuildRolePositionsParams { + /** role id */ + id: string; + /** sorting position of the role */ + position?: number | null; +} + +/** https://discord.com/developers/docs/resources/guild#modify-guild-role */ +export interface DiscordModifyGuildRoleParams { + /** name of the role */ + name?: string | null; + /** bitwise value of the enabled/disabled permissions */ + permissions?: string | null; + /** RGB color value */ + color?: number | null; + /** whether the role should be displayed seperately in the sidebar */ + hoist?: boolean | null; + /** whether the role should be mentionable */ + mentionable?: boolean | null; +} + +/** https://discord.com/developers/docs/resources/guild#get-guild-prune-count */ +export interface DiscordGetGuildPruneCountParams { + /** number of days to count prune for (1 or more), default: 7 */ + days?: number; + /** role(s) to include, default: none */ + include_roles: string | string[]; +} + +/** https://discord.com/developers/docs/resources/guild#begin-guild-prune */ +export interface DiscordBeginGuildPruneParams { + /** number of days to prune (1 or more), default: 7 */ + days?: number; + /** whether 'pruned' is returned, discouraged for large guilds, default: true */ + compute_prune_count?: boolean; + /** role(s) ro include, default: none */ + include_roles?: string[]; +} + +/** https://discord.com/developers/docs/resources/guild#modify-guild-integration */ +export interface DiscordGetGuildWidgetImageParams { + /** style of the widget returned, default: shield */ + style?: DiscordGetGuildWidgetImageStyleOptions; +} + +/** https://discord.com/developers/docs/resources/guild#modify-guild-integration */ +export enum DiscordGetGuildWidgetImageStyleOptions { + /** shield style widget with Discord icon and guild members online count */ + SHIELD = "shield", + /** large image with guild icon, name and online count. "POWERED BY DISCORD" as the footer of the widget */ + BANNER_1 = "banner1", + /** smaller widget style with guild icon, name and online count. Split on the right with Discord logo */ + BANNER_2 = "banner2", + /** large image with guild icon, name and online count. In the footer, Discord logo on the left and "Chat Now" on the right */ + BANNER_3 = "banner3", + /** large Discord logo at the top of the widget. Guild icon, name and online count in the middle portion of the widget and a "JOIN MY SERVER" button at the bottom */ + BANNER_4 = "banner4", +} + +export interface DiscordModifyGuildMembershipScreeningFormParams { + /** whether Membership Screening is enabled */ + enabled: boolean; + /** arrray of field objects serialized in a string */ + form_fields: string; + /** the server description to show in the screening form */ + description: string; +} diff --git a/src/types/api/image.ts b/src/types/api/image.ts new file mode 100644 index 000000000..68d266f23 --- /dev/null +++ b/src/types/api/image.ts @@ -0,0 +1,3 @@ +/** https://discord.com/developers/docs/reference#image-formatting */ +export type DiscordImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048; +export type DiscordImageFormat = "jpg" | "jpeg" | "png" | "webp" | "gif"; diff --git a/src/types/api/integration.ts b/src/types/api/integration.ts new file mode 100644 index 000000000..e7b056d3e --- /dev/null +++ b/src/types/api/integration.ts @@ -0,0 +1,145 @@ +import { DiscordUser } from "./mod.ts"; + +/** https://discord.com/developers/docs/resources/guild#integration-object-integration-structure */ +export interface DiscordIntegration { + /** integration id */ + id: string; + /** integration name */ + name: string; + /** integration type (twitch, youtube, or discord) */ + type: string; + /** is this integration enabled */ + enabled: boolean; + /** is this integration syncing */ + syncing?: boolean; + /** id that this integration uses for "subscribers" */ + role_id?: string; + /** whether emoticons should be synced for this integration (twitch only currently) */ + enable_emoticons?: boolean; + /** the behavior of expiring subscribers */ + expire_behavior?: DiscordIntegrationExpireBehavior; + /** the grace period (in days) before expiring subscribers */ + expire_grace_period?: number; + /** user for this integration */ + user?: DiscordUser; + /** integration account information */ + account: DiscordIntegrationAccount; + /** when this integration was last synced */ + synced_at?: string; + /** how many subscribers this integration has */ + subscriber_count: number; + /** has this integration been revoked */ + revoked?: boolean; + /** the bot/OAuth2 application for discord integrations */ + application?: DiscordIntegrationApplication; +} + +/** https://discord.com/developers/docs/resources/guild#integration-object-integration-expire-behaviors */ +export enum DiscordIntegrationExpireBehavior { + REMOVE_ROLE, + KICK, +} + +/** https://discord.com/developers/docs/resources/guild#integration-account-object */ +export interface DiscordIntegrationAccount { + /** id of the account */ + id: string; + /** name of the account */ + name: string; +} + +/** https://discord.com/developers/docs/resources/guild#integration-application-object */ +export interface DiscordIntegrationApplication { + /** the id of the app */ + id: string; + /** the name of the app */ + name: string; + /** the icon hash of the app */ + icon: string | null; + /** the description of the app */ + description: string; + /** the summary of the app */ + summary: string; + /** If this application is a game sold on Discord, this field will be the hash of the image on store embeds */ + cover_image?: string; + /** the bot associated with this application */ + bot?: DiscordUser; +} + +/** https://discord.com/developers/docs/resources/guild#integration-object-integration-structure */ +export interface DiscordIntegration { + /** integration id */ + id: string; + /** integration name */ + name: string; + /** integration type (twitch, youtube, or discord) */ + type: string; + /** is this integration enabled */ + enabled: boolean; + /** is this integration syncing */ + syncing?: boolean; + /** id that this integration uses for "subscribers" */ + role_id?: string; + /** whether emoticons should be synced for this integration (twitch only currently) */ + enable_emoticons?: boolean; + /** the behavior of expiring subscribers */ + expire_behavior?: DiscordIntegrationExpireBehavior; + /** the grace period (in days) before expiring subscribers */ + expire_grace_period?: number; + /** user for this integration */ + user?: DiscordUser; + /** integration account information */ + account: DiscordIntegrationAccount; + /** when this integration was last synced */ + synced_at?: string; + /** how many subscribers this integration has */ + subscriber_count: number; + /** has this integration been revoked */ + revoked?: boolean; + /** the bot/OAuth2 application for discord integrations */ + application?: DiscordIntegrationApplication; +} + +/** https://discord.com/developers/docs/resources/guild#integration-account-object */ +export interface DiscordIntegrationAccount { + /** id of the account */ + id: string; + /** name of the account */ + name: string; +} + +/** https://discord.com/developers/docs/resources/guild#integration-application-object */ +export interface DiscordIntegrationApplication { + /** the id of the app */ + id: string; + /** the name of the app */ + name: string; + /** the icon hash of the app */ + icon: string | null; + /** the description of the app */ + description: string; + /** the summary of the app */ + summary: string; + /** If this application is a game sold on Discord, this field will be the hash of the image on store embeds */ + cover_image?: string; + /** the bot associated with this application */ + bot?: DiscordUser; +} + +/** https://discord.com/developers/docs/resources/guild#create-guild-integration */ +export interface DiscordCreateGuildIntegrationParams { + /** the integration type */ + type: string; + /** the integration id */ + id: string; +} + +/** https://discord.com/developers/docs/resources/guild#modify-guild-integration */ +export interface DiscordModifyGuildIntegration { + /** the behavior when an integration subscription lapses (see the integration expire behaviors documentation) */ + expire_behavior?: number | null; + /** perios (in days) where the integration will ignore lapsed subscriptions */ + expire_grace_period?: number | null; + /** whether emoticons should be synced for this integration (twitch only currently) */ + enable_emoticons?: boolean | null; +} diff --git a/src/types/api/interaction.ts b/src/types/api/interaction.ts new file mode 100644 index 000000000..d9cb62f7c --- /dev/null +++ b/src/types/api/interaction.ts @@ -0,0 +1,102 @@ +import { DiscordMember } from "./mod.ts"; + +export interface DiscordInteractionCommand { + /** id of the interaction */ + id: string; + /** the type of interaction */ + type: DiscordInteractionType; + /** the command data payload */ + data?: DiscordInteractionData; + /** the guild it was sent from */ + guild_id: string; + /** the channel it was sent from */ + channel_id: string; + /** guild member data for the invoking user */ + member: DiscordMember; + /** a continuation token for responding to the interaction */ + token: string; + /** read-only property, always 1 */ + version: number; +} + +export enum DiscordInteractionType { + /** This type is for ACK on webhook only setup. Discord may send these which require. In a sense its a heartbeat. */ + PING = 1, + /** Slash commands */ + APPLICATION_COMMAND, +} + +export interface DiscordInteractionData { + /** the ID of the invoked command */ + id: string; + /** the name of the invoked command */ + name: string; + /** the params + values from the user */ + options: DiscordInteractionDataOption[]; +} + +export interface DiscordInteractionDataOption { + /** the name of the parameter */ + name: string; + /** the value of the pair. present if there was no more options */ + value?: string | number; + /** present if this option is a group or subcommand */ + options?: DiscordInteractionDataOption[]; +} + +/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommand */ +export interface DiscordApplicationCommand { + /** unique id of the command */ + id: string; + /** unique id of the parent application */ + "application_id": string; + /** 3-32 character name matching `^[\w-]{3,32}$` */ + name: string; + /** 1-100 character description */ + description: string; + /** the parameters for the command */ + options?: DiscordApplicationCommandOption[]; +} + +/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoption */ +export interface DiscordApplicationCommandOption { + /** the type of the option */ + type: DiscordApplicationCommandOptionType; + /** 1-32 character name matching `^[\w-]{1,32}$` */ + name: string; + /** 1-100 character description */ + description: string; + /** the first `required` option for the user to complete--only one option can be `default` */ + default?: boolean; + /** if the parameter is required or optional--default `false` */ + required?: boolean; + /** choices for `string` and `int` types for the user to pick from */ + choices?: DiscordApplicationCommandOptionChoice[]; + /** if the option is a subcommand or subcommand group type, this nested options will be the parameters */ + options?: DiscordApplicationCommandOption[]; +} + +/** https://discord.com/developers/docs/interactions/slash-commands#Discordapplicationcommandoptiontype */ +export enum DiscordApplicationCommandOptionType { + SUB_COMMAND = 1, + SUB_COMMAND_GROUP, + STRING, + INTEGER, + BOOLEAN, + USER, + CHANNEL, + ROLE, +} + +/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice */ +export interface DiscordApplicationCommandOptionChoice { + /** 1-100 character choice name */ + name: string; + /** value of the choice */ + value: string | number; +} + +export type DiscordApplicationCommandEvent = DiscordApplicationCommand & { + /** id of the guild the command is in */ + guild_id?: string; +}; diff --git a/src/types/api/invite.ts b/src/types/api/invite.ts new file mode 100644 index 000000000..f45613a9d --- /dev/null +++ b/src/types/api/invite.ts @@ -0,0 +1,46 @@ +import { DiscordChannel, DiscordGuild, DiscordUser } from "./mod.ts"; + +/** https://discord.com/developers/docs/resources/invite#invite-object */ +export interface DiscordInvite { + /** the invite code (unique ID) */ + code: string; + /** the guild this invite is for */ + guild?: Partial; + /** the channel this invite is for */ + channel: Partial; + /** the user who created the invite */ + inviter?: DiscordUser; + /** the target user for this invite */ + target_user?: Partial; + /** the type of user target for this invite */ + target_user_type?: DiscordInviteTargetUserTypes; + /** approximate count of online members (only present when target_user is set) */ + approximate_presence_count?: number; + /** approximate count of total members */ + approximate_member_count: number; +} + +/** https://discord.com/developers/docs/resources/invite#invite-resource */ +export enum DiscordInviteTargetUserTypes { + STREAM = 1, +} + +/** https://discord.com/developers/docs/resources/invite#invite-resource */ +export interface DiscordInviteMetadata extends DiscordInvite { + /** number of times this invite has been used */ + uses: number; + /** max number of times this invite can be used */ + max_uses: number; + /** duration (in seconds) after which the invite expires */ + max_age: number; + /** whether this invite only grants temporary membership */ + temporary: boolean; + /** when this invite was created */ + created_at: string; +} + +/** https://discord.com/developers/docs/resources/invite#get-invite */ +export interface DiscordGetInviteURLParams { + /** whether the invite should contain approximate member counts */ + with_counts?: boolean; +} diff --git a/src/types/api/member.ts b/src/types/api/member.ts new file mode 100644 index 000000000..e17403cb0 --- /dev/null +++ b/src/types/api/member.ts @@ -0,0 +1,89 @@ +export interface DiscordBaseUser { + /** the user's id */ + id: string; + /** the user's username, not unique across the platform */ + username: string; + /** the user's 4-digit discord-tag */ + discriminator: string; + /** the user's avatar hash */ + avatar: string | null; +} + +/** https://discord.com/developers/docs/resources/user#users-resource */ +export interface DiscordUser extends DiscordBaseUser { + /** the user's id */ + id: string; + /** the user's username, not unique across the platform */ + username: string; + /** the user's 4-digit discord-tag */ + discriminator: string; + /** the user's avatar hash */ + avatar: string | null; + /** whether the user belongs to an OAuth2 application */ + bot?: boolean; + /** whether the user is an Official Discord System user (part of the urgent message system) */ + system?: boolean; + /** whether the user has two factor enabled on their account */ + mfa_enabled?: boolean; + /** the user's chosen language option */ + locale?: string; + /** whether the email on this account has been verified */ + verified?: boolean; + /** the user's email */ + email?: string | null; + /** the flags on a user's account */ + flags?: DiscordUserFlags; + /** the type of Nitro subscription on a user's account */ + premium_type?: DiscordPremiumTypes; + /** the public flags on a user's account */ + public_flags?: DiscordUserFlags; +} + +/** https://discord.com/developers/docs/resources/user#users-resource */ +export enum DiscordUserFlags { + NONE = 0, + DISCORD_EMPLOYEE = 1 << 0, + PARTNERED_SERVER_OWNER = 1 << 1, + HYPE_SQUAD_EVENTS = 1 << 2, + BUG_HUNTER_LEVEL_1 = 1 << 3, + HOUSE_BRAVERY = 1 << 6, + HOUSE_BRILLIANCE = 1 << 7, + HOUSE_BALANCE = 1 << 8, + EARLY_SUPPORTER = 1 << 9, + TEAM_USER = 1 << 10, + SYSTEM = 1 << 12, + BUG_HUNTER_LEVEL_2 = 1 << 14, + VERIFIED_BOT = 1 << 16, + EARLY_VERIFIED_BOT_DEVELOPER = 1 << 17, +} + +/** https://discord.com/developers/docs/resources/user#users-resource */ +export enum DiscordPremiumTypes { + NONE = 0, + NITRO_CLASSIC = 1, + NITRO = 2, +} + +/** https://discord.com/developers/docs/resources/guild#guild-member-object-guild-member-structure */ +export interface DiscordBaseMember { + /** this users guild nickname */ + nick: string | null; + /** array of role payload ids */ + roles: string[]; + /** when the user joined the guild */ + joined_at: string; + /** when the user started boosting the guild */ + premium_since?: string | null; + /** whether the user is deafened in voice channels */ + deaf: boolean; + /** whether the user is muted in voice channels */ + mute: boolean; + /** whether the user has not yet passed the guild's Membership Screening requirements */ + pending?: boolean; +} + +/** https://discord.com/developers/docs/resources/guild#guild-member-object-guild-member-structure */ +export interface DiscordMember { + /** the user this guild member represents */ + user?: DiscordUser; +} diff --git a/src/types/api/message.ts b/src/types/api/message.ts new file mode 100644 index 000000000..d05de3392 --- /dev/null +++ b/src/types/api/message.ts @@ -0,0 +1,290 @@ +import { DiscordMember } from "./member.ts"; +import { + DiscordBaseMember, + DiscordChannelTypes, + DiscordEmbed, + DiscordEmoji, + DiscordUser, +} from "./mod.ts"; + +/** https://discord.com/developers/docs/resources/channel#message-object-message-structure */ +export interface DiscordMessage { + /** id of the message */ + id: string; + /** id of the channel the message was sent in */ + channel_id: string; + /** id of the guild the message was sent in */ + guild_id?: string; + /** the author of this message (not guaranteed to be a valid user) */ + author: DiscordUser; + /** member properties for this message's author */ + member?: DiscordBaseMember; + /** contents of the message */ + content: string; + /** when this message was sent */ + timestamp: string; + /** when this message was edited (or null if never) */ + edited_timestamp: string | null; + /** whether this was a TTS message */ + tts: boolean; + /** whether this message mentions everyone */ + mention_everyone: boolean; + /** users specifically mentioned in the message */ + mentions: (DiscordUser & Partial)[]; + /** roles specifically mentioned in this message */ + mention_roles: string[]; + /** channels specifically mentioned in this message */ + mention_channels?: DiscordChannelMention[]; + /** any attached files */ + attachments: DiscordAttachment[]; + /** any embedded content */ + embeds: DiscordEmbed[]; + /** reactions to the message */ + reactions?: DiscordReaction[]; + /** used for validating a message was sent */ + nonce?: number | string; + /** whether this message is pinned */ + pinned: boolean; + /** if the message is generated by a webhook, this is the webhook's id */ + webhook_id?: string; + /** type of message */ + type: DiscordMessageTypes; + /** sent with Rich Presence-related chat embeds */ + activity?: DiscordMessageActivity; + /** sent with Rich Presence-related chat embeds */ + application?: DiscordMessageApplication; + /** reference data sent with crossposted messages and replies */ + message_reference?: DiscordMessageReference; + /** message flags combined as a bitfield */ + flags?: DiscordMessageFlags; + /** the stickers sent with the message (bots currently can only receive messages with stickers, not send) */ + stickers?: DiscordMessageSticker[]; + /** the message associated with the `message_reference` */ + referenced_message?: DiscordMessage | null; +} + +/** https://discord.com/developers/docs/resources/channel#message-object-message-types */ +export enum DiscordMessageTypes { + DEFAULT, + RECIPIENT_ADD, + RECIPIENT_REMOVE, + CALL, + CHANNEL_NAME_CHANGE, + CHANNEL_ICON_CHANGE, + CHANNEL_PINNED_MESSAGE, + GUILD_MEMBER_JOIN, + USER_PREMIUM_GUILD_SUBSCRIPTION, + USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1, + USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2, + USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3, + CHANNEL_FOLLOW_ADD, + GUILD_DISCOVERY_DISQUALIFIED = 14, + GUILD_DISCOVERY_REQUALIFIED, + GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING, + GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING, + REPLY = 19, + APPLICATION_COMMAND, +} + +/** https://discord.com/developers/docs/resources/channel#message-object-message-activity-structure */ +export interface DiscordMessageActivity { + /** type of message activity */ + type: DiscordMessageActivityTypes; + /** party_id from a Rich Presence event */ + party_id?: string; +} + +/** https://discord.com/developers/docs/resources/channel#message-object-message-application-structure */ +export interface DiscordMessageApplication { + /** id of the application */ + id: string; + /** id of the embed's image asset */ + cover_image?: string; + /** application's description */ + description: string; + /** id of the application's icon */ + icon: string | null; + /** name of the application */ + name: string; +} + +/** https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure */ +export interface DiscordMessageReference { + /** id of the originating message */ + message_id?: string; + /** id of the originating message's channel */ + channel_id?: string; + /** id of the originating message's guild */ + guild_id?: string; +} + +/** https://discord.com/developers/docs/resources/channel#message-object-message-activity-types */ +export enum DiscordMessageActivityTypes { + JOIN = 1, + SPECTATE, + LISTEN, + JOIN_REQUEST = 5, +} + +/** https://discord.com/developers/docs/resources/channel#message-object-message-flags */ +export enum DiscordMessageFlags { + /** this message has been published to subscribed channels (via Channel Following) */ + CROSSPOSTED = 1 << 0, + /** this message originated from a message in another channel (via Channel Following) */ + IS_CROSSPOST = 1 << 1, + /** do not include any embeds when serializing this message */ + SUPPRESS_EMBEDS = 1 << 2, + /** the source message for this crosspost has been deleted (via Channel Following) */ + SOURCE_MESSAGE_DELETED = 1 << 3, + /** this message came from the urgent message system */ + URGENT = 1 << 4, +} + +/** https://discord.com/developers/docs/resources/channel#message-object-message-sticker-structure */ +export interface DiscordMessageSticker { + /** id of the sticker */ + id: string; + /** id of the pack the sticker is from */ + pack_id: string; + /** name of the sticker */ + name: string; + /** description of the sticker */ + description: string; + /** a comma-separated list of tags for the sticker */ + tags?: string; + /** sticker asset hash */ + asset: string; + /** sticker preview asset hash */ + preview_asset: string | null; + /** type of sticker format */ + format_type: DiscordMessageStickerFormatTypes; +} + +/** https://discord.com/developers/docs/resources/channel#message-object-message-sticker-format-types */ +export enum DiscordMessageStickerFormatTypes { + PNG = 1, + APNG, + LOTTIE, +} + +/** https://discord.com/developers/docs/resources/channel#reaction-object-reaction-structure */ +export interface DiscordReaction { + /** times this emoji has been used to react */ + count: number; + /** whether the current user reacted using this emoji */ + me: boolean; + /** emoji information */ + emoji: Partial; +} + +/** https://discord.com/developers/docs/resources/channel#attachment-object-attachment-structure */ +export interface DiscordAttachment { + /** attachment id */ + id: string; + /** name of file attached */ + filename: string; + /** size of file in bytes */ + size: number; + /** source url of file */ + url: string; + /** a proxied url of file */ + proxy_url: string; + /** height of file (if image) */ + height?: number | null; + /** width of file (if image) */ + width?: number | null; +} + +/** https://discord.com/developers/docs/resources/channel#channel-mention-object-channel-mention-structure */ +export interface DiscordChannelMention { + /** id of the channel */ + id: string; + /** id of the guild containing the channel */ + guild_id: string; + /** the type of channel */ + type: DiscordChannelTypes; + /** the name of the channel */ + name: string; +} + +/** https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mention-types */ +export enum AllowedMentionTypes { + /** Controls role mentions */ + ROLES = "roles", + /** Controls user mentions */ + USERS = "users", + /** Controls `@everyone` and `@here` mentions */ + EVERYONE = "everyone", +} + +/** https://discord.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mentions-structure */ +export interface DiscordAllowedMentions { + /** An array of allowed mention types to parse from the content. */ + parse: AllowedMentionTypes[]; + /** Array of role_ids to mention (Max size of 100) */ + roles: string[]; + /** Array of user_ids to mention (Max size of 100) */ + users: string[]; + /** For replies, whether to mention the author of the message being replied to (default false) */ + replied_user: boolean; +} + +/** https://discord.com/developers/docs/resources/channel#get-channel-messages-query-string-params */ +export interface DiscordGetChannelMessagesParams { + /** get messages around this message ID */ + around?: string; + /** get messages before this message ID */ + before?: string; + /** get messages after this message ID */ + after?: string; + /** max number of messages to return (1-100) */ + limit?: number; +} + +/** https://discord.com/developers/docs/resources/channel#create-message-params */ +export interface DiscordCreateMessageParams { + /** the message contents (up to 2000 characters) */ + content?: string; + /** a nonce that can be used for optimistic message sending */ + nonce?: number | string; + /** `true` if this is a TTS message */ + tts: boolean; + /** the contents of the file being sent */ + file?: { blob: unknown; name: string }; + /** embedded rich content */ + embed?: DiscordEmbed; + /** JSON encoded body of any additional request fields. */ + payload_json?: string; + /** allowed mentions for a message */ + allowed_mentions?: DiscordAllowedMentions; + /** include to make your message a reply */ + message_reference?: DiscordMessageReference; +} + +/** https://discord.com/developers/docs/resources/channel#get-reactions-query-string-params */ +export interface DiscordGetReactionsParams { + /** get users before this user ID */ + before?: string; + /** get users after this user ID */ + after?: string; + /** max number of users to return (1-100) */ + limit?: number; +} + +/** https://discord.com/developers/docs/resources/channel#edit-message-json-params */ +export interface DiscordEditMessageParams { + /** the new message contents (up to 2000 characters) */ + content?: string | null; + /** embedded rich content */ + embed?: DiscordEmbed | null; + /** edit the flags of a message (only SUPPRESS_EMBEDS can currently be set/unset) */ + flags?: DiscordMessageFlags | null; + /** allowed mentions for the message */ + allowed_mentions?: DiscordAllowedMentions | null; +} + +/** https://discord.com/developers/docs/resources/channel#bulk-delete-messages-json-params */ +export interface DiscordBulkDeleteMessagesParams { + /** an array of message ids to delete (2-100) */ + messages: string[]; +} diff --git a/src/types/api/mod.ts b/src/types/api/mod.ts new file mode 100644 index 000000000..205d6ab70 --- /dev/null +++ b/src/types/api/mod.ts @@ -0,0 +1,22 @@ +export * from "./auditlog.ts"; +export * from "./channel.ts"; +export * from "./code.ts"; +export * from "./embed.ts"; +export * from "./emoji.ts"; +export * from "./event.ts"; +export * from "./gateway.ts"; +export * from "./guild.ts"; +export * from "./image.ts"; +export * from "./integration.ts"; +export * from "./interaction.ts"; +export * from "./invite.ts"; +export * from "./member.ts"; +export * from "./message.ts"; +export * from "./oauth2.ts"; +export * from "./permission.ts"; +export * from "./ratelimits.ts"; +export * from "./role.ts"; +export * from "./teams.ts"; +export * from "./template.ts"; +export * from "./voice.ts"; +export * from "./webhook.ts"; diff --git a/src/types/api/oauth2.ts b/src/types/api/oauth2.ts new file mode 100644 index 000000000..b60302fe9 --- /dev/null +++ b/src/types/api/oauth2.ts @@ -0,0 +1,37 @@ +import { DiscordTeam, DiscordUser } from "./mod.ts"; + +/** https://discord.com/developers/docs/topics/oauth2#get-current-application-information */ +export interface DiscordApplication { + /** id of the app */ + id: string; + /** the name of the app */ + name: string; + /** the icon hash of the app */ + icon: string | null; + /** the description of the app */ + description: string; + /** an array of rpc origin urls, if rpx is enabled */ + rpc_origins?: string[]; + /** when false only app owner can join the app's bot to guilds */ + bot_public: boolean; + /** when true the app's bot will only join upon completion of the full oauth2 code grant flow */ + bot_require_code_grand: boolean; + /** partial user object containing info on the owner of the application */ + owner: Partial; + /** if this application is a game sold on Disccord, this field will be the summary field for the store page of its primary sku */ + summary: string; + /** the base64 enccoded key for the GameSDK'S GetTicket */ + verify_key: string; + /** if the application belongs to a team, this will be a list of the members of that team */ + team: DiscordTeam | null; + /** if this application is a game sold on Discord, this field will be the guild to which it has been linked */ + guild_id?: string; + /** if this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists */ + primary_sku_id?: string; + /** if this application is a game sold on Discord, this field will be the URL slug that links to the store page */ + slug?: string; + /** if this application is a game sold on Discord, this field wil be the hash of the image on store embeds */ + cover_image?: string; + /** the application's public flags */ + flags: number; +} diff --git a/src/types/api/permission.ts b/src/types/api/permission.ts new file mode 100644 index 000000000..fa6708c4e --- /dev/null +++ b/src/types/api/permission.ts @@ -0,0 +1,10 @@ +export interface DiscordOverwrite { + /** role or user id */ + id: string; + /** either 0 (role) or 1 (member) */ + type: number; + /** permission bit set */ + allow: string; + /** permission bit set */ + deny: string; +} diff --git a/src/types/api/ratelimits.ts b/src/types/api/ratelimits.ts new file mode 100644 index 000000000..b7fa5725d --- /dev/null +++ b/src/types/api/ratelimits.ts @@ -0,0 +1,9 @@ +/** https://discord.com/developers/docs/topics/rate-limits#exceeding-a-rate-limit */ +export interface DiscordRateLimitResponse { + /** a message saying you are being rate limited */ + message: string; + /** the number of seconds to wait before submitting another request. */ + retry_after: number; + /** a value indicating if you are being globally limited or not */ + global: boolean; +} diff --git a/src/types/api/role.ts b/src/types/api/role.ts new file mode 100644 index 000000000..02f70a0ad --- /dev/null +++ b/src/types/api/role.ts @@ -0,0 +1,31 @@ +/** https://discord.com/developers/docs/topics/permissions#role-object-role-structure */ +export interface DiscordRole { + /** role id */ + id: string; + /** role name */ + name: string; + /** number representation of hexadecimal color code */ + color: number; + /** if this role is pinned in the user listing */ + hoist: boolean; + /** position of this role */ + position: number; + /** permission bit set */ + permissions: string; + /** whether this role is managed by an integration */ + managed: boolean; + /** whether this role is mentionable */ + mentionable: boolean; + /** the tags this role has */ + tags?: DiscordRoleTags; +} + +/** https://discord.com/developers/docs/topics/permissions#role-object-role-tags-structure */ +export interface DiscordRoleTags { + /** the id of the bot this role belongs to */ + bot_id?: string; + /** the id of the integration this role belongs to */ + integration_id?: string; + /** whether this is the guild's premium subscriber role */ + premium_subscriber?: null; +} diff --git a/src/types/api/teams.ts b/src/types/api/teams.ts new file mode 100644 index 000000000..3e7624ce2 --- /dev/null +++ b/src/types/api/teams.ts @@ -0,0 +1,31 @@ +import { DiscordBaseUser } from "./mod.ts"; + +/** https://discord.com/developers/docs/topics/teams#data-models-team-object */ +export interface DiscordTeam { + /** a hash of the image of the team's icon */ + icon: string | null; + /** the unique id of the team */ + id: string; + /** the members of the team */ + members: DiscordTeamMembers[]; + /** the user id of the current team owner */ + owner_user_id: string; +} + +/** https://discord.com/developers/docs/topics/teams#data-models-team-members-object */ +export interface DiscordTeamMembers { + /** the user's membership state on the team */ + membership_state: keyof typeof DiscordMembershipState; + /** will always be ["*"] */ + permissions: string[]; + /** the id of the parent team of which they are a member */ + team_id: string; + /** the avatar, discriminator, id, and username of the user */ + user: DiscordBaseUser; +} + +/** https://discord.com/developers/docs/topics/teams#data-models-membership-state-enum */ +export enum DiscordMembershipState { + INVITED = 1, + ACCEPTED, +} diff --git a/src/types/api/template.ts b/src/types/api/template.ts new file mode 100644 index 000000000..20a3ba781 --- /dev/null +++ b/src/types/api/template.ts @@ -0,0 +1,51 @@ +import { DiscordGuild, DiscordUser } from "./mod.ts"; + +/** https://discord.com/developers/docs/resources/template#template-resource */ +export interface DiscordTemplate { + /** the template code (unique ID) */ + code: string; + /** template name */ + name: string; + /** the description for the template */ + description: string | null; + /** number of times this template has been used */ + usage_count: number; + /** the ID of teh user who created the template */ + creator_id: string; + /** the user who created the template */ + creator: DiscordUser; + /** when this template was created*/ + created_at: string; + /** when this template was last synced to the source guild */ + updated_at: string; + /** the ID of the guild this template is based on */ + source_guild_id: string; + /** the guild snapshot this template contains */ + serialized_source_guild: Partial; + /** whether the template has unsynced changes */ + is_dirty: boolean | null; +} + +/** https://discord.com/developers/docs/resources/template#create-guild-from-template */ +export interface DiscordCreateGuildFromTemplateParams { + /** name of the guild (2-100 characters) */ + name: string; + /** base64 128x128 image for the guild icon */ + icon?: string; +} + +/** https://discord.com/developers/docs/resources/template#create-guild-template */ +export interface DiscordCreateGuildTemplateParams { + /** name of the template (1-100 characters) */ + name: string; + /** description for the template (0-120 characters) */ + description?: string | null; +} + +/** https://discord.com/developers/docs/resources/template#modify-guild-template */ +export interface DiscordModifyGuildTemplateParams { + /** name of the template (1-100 characters) */ + name?: string; + /** description for the template (0-120 characters) */ + description?: string | null; +} diff --git a/src/types/api/voice.ts b/src/types/api/voice.ts new file mode 100644 index 000000000..b2d3bd7fd --- /dev/null +++ b/src/types/api/voice.ts @@ -0,0 +1,14 @@ +export interface DiscordVoiceRegion { + /** unique ID for the region */ + id: string; + /** name of the region */ + name: string; + /** true if this is a vip-only server */ + vip: boolean; + /** true for a single server that is closet to the current user's client */ + optimal: boolean; + /** whether this is a deprecated voice region (avoid switching to these) */ + deprecated: boolean; + /** whether this is a custom voice region (used for events/etc) */ + custom: boolean; +} diff --git a/src/types/api/webhook.ts b/src/types/api/webhook.ts new file mode 100644 index 000000000..bf2a9766a --- /dev/null +++ b/src/types/api/webhook.ts @@ -0,0 +1,31 @@ +import { DiscordUser } from "./mod.ts"; + +/** https://discord.com/developers/docs/resources/webhook#webhook-resource */ +export interface DiscordWebhook { + /** the id of the webhook */ + id: string; + /** the type of the webhook */ + type: DiscordWebhookTypes; + /** the guild id this webhook is for */ + guild_id?: string; + /** the channel id this webhook is for */ + channel_id: string; + /** the user this webhook was created by (not returned when getting a webhook with its token) */ + user?: DiscordUser; + /** the default name of the webhook */ + name: string | null; + /** the default avatar of the webhook */ + avatar: string | null; + /** the secure token of the webhook (returned for Incoming Webhooks) */ + token?: string; + /** the bot/OAuth2 application that created this webhook */ + application_id: string | null; +} + +/** https://discord.com/developers/docs/resources/webhook#webhook-resource */ +export enum DiscordWebhookTypes { + /** Incoming Webhook can post messages to channels with a generated token */ + INCOMING = 1, + /** Channel Follower Webhooks are internal webhooks used with Channel Following to post new messages into channels */ + CHANNEL_FOLLOWER, +} diff --git a/src/types/cdn.ts b/src/types/cdn.ts index 605617838..220aaa077 100644 --- a/src/types/cdn.ts +++ b/src/types/cdn.ts @@ -1,2 +1,6 @@ +// TODO: v11 Remove +/** @deprecated Use DiscordImageSize */ export type ImageSize = 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 2048; +// TODO: v11 Remove +/** @deprecated Use DiscordImageFormat */ export type ImageFormats = "jpg" | "jpeg" | "png" | "webp" | "gif"; diff --git a/src/types/channel.ts b/src/types/channel.ts index fbf6c41fa..ad3202a95 100644 --- a/src/types/channel.ts +++ b/src/types/channel.ts @@ -24,7 +24,7 @@ export interface ChannelEditOptions { export interface BaseChannelCreate { /** The id of the guild */ - guild_id?: string; + "guild_id"?: string; /** Sorting position of the channel */ position?: number; /** The name of the channel (2-100 characters) */ @@ -34,24 +34,24 @@ export interface BaseChannelCreate { /** Whether the channel is nsfw */ nsfw?: boolean; /** The id of the last message sent in this channel (may not point to an existing or valid message) */ - last_message_id?: string | null; + "last_message_id"?: string | null; /** The bitrate (in bits) of the voice channel */ bitrate?: number; /** The user limit of the voice channel */ - user_limit?: number; + "user_limit"?: number; /** Amount of seconds a user has to wait before sending another message (0-21600) Bots and users with the permission MANAGE_MESSAGES or MANAGE_CHANNEL are unaffected. */ - rate_limit_per_user?: number; + "rate_limit_per_user"?: number; /** The parent category id */ - parent_id?: string | null; + "parent_id"?: string | null; /** When the last pinned message was pinned */ - last_pin_timestamp?: string; + "last_pin_timestamp"?: string; } export interface DMChannelCreatePayload { /** This is a unique channel id. It is NOT the users id. */ id: string; /** The id of the last message sent in this dm channel */ - last_message_id: string; + "last_message_id": string; /** The type of channel */ type: 1; /** The user */ @@ -71,7 +71,7 @@ export interface ChannelCreatePayload extends BaseChannelCreate { /** The type of the channel */ type: ChannelType; /** Explicit permission overwrites for members and roles */ - permission_overwrites?: RawOverwrite[]; + "permission_overwrites"?: RawOverwrite[]; } export type ChannelType = 0 | 1 | 2 | 4 | 5 | 6; @@ -115,13 +115,13 @@ export interface MessageContent { /** Embed object */ embed?: Embed; /** JSON encoded body of any additional request fields. */ - payload_json?: string; + "payload_json"?: string; /** If you want to send a reply message, provide the original message id here */ replyMessageID?: string; } export interface FileContent { - blob: Blob; + blob: unknown; name: string; } @@ -156,9 +156,9 @@ export interface GetMessagesAround extends GetMessages { export interface CreateInviteOptions { /** Duration of invite in seconds before expiry, or 0 for never. Defaults to 86400 (24 hours) */ - max_age: number; + "max_age": number; /** Max number of uses or 0 for unlimited. Default 0 */ - max_uses: number; + "max_uses": number; /** Whether this invite only grants temporary membership. */ temporary: boolean; /** If true, don't try to reuse a similar invite (useful for creating many unique one time use invites.) */ @@ -167,7 +167,7 @@ export interface CreateInviteOptions { export interface FollowedChannelPayload { /** The source channel id */ - channel_id: string; + "channel_id": string; /** The webhook id */ - webhook_id: string; + "webhook_id": string; } diff --git a/src/types/discord.ts b/src/types/discord.ts index 0cc6f741a..cf5efb255 100644 --- a/src/types/discord.ts +++ b/src/types/discord.ts @@ -53,7 +53,9 @@ export interface DiscordPayload { | "WEBHOOKS_UPDATE" | "INTEGRATION_CREATE" | "INTEGRATION_UPDATE" - | "INTEGRATION_DELETE"; + | "INTEGRATION_DELETE" + | "INVITE_CREATE" + | "INVITE_DELETE"; } export interface DiscordBotGatewayData { @@ -62,23 +64,23 @@ export interface DiscordBotGatewayData { /** The recommended number of shards to use when connecting. */ shards: number; /** Info on the current start limit. */ - session_start_limit: { + "session_start_limit": { /** The total number of session starts the current user is allowed. */ total: number; /** The remaining number of session starts the current user is allowed. */ remaining: number; /** Milliseconds left until limit is reset. */ - reset_after: number; + "reset_after": number; /** The number of identify requests allowed per 5 seconds. * So, if you had a max concurrency of 16, and 16 shards for example, you could start them all up at the same time. * Whereas if you had 32 shards, if you tried to start up shard 0 and 16 at the same time for example, it would not work. You can start shards 0-15 concurrently, then 16-31... * */ - max_concurrency: number; + "max_concurrency": number; }; } export interface DiscordHeartbeatPayload { - heartbeat_interval: number; + "heartbeat_interval": number; } export enum GatewayOpcode { @@ -235,30 +237,30 @@ export interface Status { } export interface WebhookUpdatePayload { - channel_id: string; - guild_id: string; + "channel_id": string; + "guild_id": string; } export interface PresenceUpdatePayload { /** The user presence is being updated for. */ user: PartialUser; /** The id of the guild */ - guild_id: string; + "guild_id": string; /** Either idle, dnd, online, or offline */ status: StatusType; /** All user's current activity */ activities: Activity[]; /** The user's platform dependent status */ - client_status: ClientStatusPayload; + "client_status": ClientStatusPayload; } export interface TypingStartPayload { /** The id of the channel */ - channel_id: string; + "channel_id": string; /** The id of the guild */ - guild_id?: string; + "guild_id"?: string; /** The id of the user */ - user_id: string; + "user_id": string; /** The unix time in seconds of when the user started typing */ timestamp: number; /** The member who started typing if this happened in a guild */ @@ -267,25 +269,25 @@ export interface TypingStartPayload { export interface VoiceStateUpdatePayload { /** The guild id this voice state is for */ - guild_id?: string; + "guild_id"?: string; /** The channel id this user is connected to */ - channel_id: string | null; + "channel_id": string | null; /** The user id this voice state is for */ - user_id: string; + "user_id": string; /** The guild member this voice state is for */ member?: MemberCreatePayload; /** The session id for this voice state */ - session_id: string; + "session_id": string; /** Whether this user is deafened by the server */ deaf: boolean; /** Whether this user is muted by the server */ mute: boolean; /** Whether this user is locally deafened */ - self_deaf: boolean; + "self_deaf": boolean; /** Whether this user is locally muted */ - self_mute: boolean; + "self_mute": boolean; /** Whether this user is streaming using Go Live */ - self_stream?: boolean; + "self_stream"?: boolean; /** Whether this user is muted by the bot */ suppress: boolean; } @@ -296,11 +298,11 @@ export interface ReadyPayload { /** information about the user including email */ user: UserPayload; /** empty array */ - private_channels: []; + "private_channels": []; /** the guilds the user is in */ guilds: UnavailableGuildPayload[]; /** used for resuming connections */ - session_id: string; + "session_id": string; /** (shard_id, num_shards) the shard information associated with this session, if sent when identifying */ shard?: [number, number]; /** contains id and flags */ @@ -321,7 +323,43 @@ export interface IntegrationDeleteEvent { /** integration id */ id: string; /** id of the guild */ - guild_id: string; + "guild_id": string; /** id of the bot/OAuth2 application for this discord integration */ - application_id?: string; + "application_id"?: string; +} + +/** https://discord.com/developers/docs/topics/gateway#invite-create */ +export interface InviteCreateEvent { + /** the channel the invite is for */ + "channel_id": string; + /** the unique invite code */ + code: string; + /** the time at which the invite was created */ + "created_at": string; + /** the guild of the invite */ + "guild_id"?: string; + /** the user that created the invite */ + inviter?: UserPayload; + /** how long the invite is valid for (in seconds) */ + "max_age": number; + /** the maximum number of times the invite can be used */ + "max_uses": number; + /** the target user for this invite */ + "target_user"?: Partial; + /** the type of user target for this invite */ + "target_user_type"?: number; + /** whether or not the invite is temporary (invited users will be kicked on disconnect unless they're assigned a role) */ + temporary: boolean; + /** how many times the invite has been used (always will be 0) */ + uses: number; +} + +/** https://discord.com/developers/docs/topics/gateway#invite-delete */ +export interface InviteDeleteEvent { + /** the channel of the invite */ + "channel_id": string; + /** the guild of the invite */ + "guild_id"?: string; + /** the unique invite code */ + code: string; } diff --git a/src/types/errors.ts b/src/types/errors.ts index fe9582e7e..cc64e5b3c 100644 --- a/src/types/errors.ts +++ b/src/types/errors.ts @@ -1,48 +1,73 @@ export enum Errors { - MISSING_SEND_MESSAGES = "MISSING_SEND_MESSAGES", - MISSING_MANAGE_ROLES = "MISSING_MANAGE_ROLES", - MISSING_KICK_MEMBERS = "MISSING_KICK_MEMBERS", - MISSING_VIEW_CHANNEL = "MISSING_VIEW_CHANNEL", - MISSING_READ_MESSAGE_HISTORY = "MISSING_READ_MESSAGE_HISTORY", - MISSING_MANAGE_NICKNAMES = "MISSING_MANAGE_NICKNAMES", - MISSING_MUTE_MEMBERS = "MISSING_MUTE_MEMBERS", - MISSING_DEAFEN_MEMBERS = "MISSING_DEAFEN_MEMBERS", - MISSING_SEND_TTS_MESSAGE = "MISSING_SEND_TTS_MESSAGE", - MISSING_MANAGE_MESSAGES = "MISSING_MANAGE_MESSAGES", - MISSING_MANAGE_CHANNELS = "MISSING_MANAGE_CHANNELS", - MISSING_CREATE_INSTANT_INVITE = "MISSING_CREATE_INSTANT_INVITE", - MISSING_MANAGE_WEBHOOKS = "MISSING_MANAGE_WEBHOOKS", - MISSING_MANAGE_EMOJIS = "MISSING_MANAGE_EMOJIS", - MISSING_BAN_MEMBERS = "MISSING_BAN_MEMBERS", - MISSING_MANAGE_GUILD = "MISSING_MANAGE_GUILD", - MISSING_VIEW_AUDIT_LOG = "MISSING_VIEW_AUDIT_LOG", - MISSING_EMBED_LINKS = "MISSING_EMBED_LINKS", - MISSING_ADD_REACTIONS = "MISSING_ADD_REACTIONS", - DELETE_MESSAGES_MIN = "DELETE_MESSAGES_MIN", + // Bot Role errors + BOTS_HIGHEST_ROLE_TOO_LOW = "BOTS_HIGHEST_ROLE_TOO_LOW", + // Channel Errors + CHANNEL_NOT_FOUND = "CHANNEL_NOT_FOUND", + CHANNEL_NOT_IN_GUILD = "CHANNEL_NOT_IN_GUILD", + CHANNEL_NOT_TEXT_BASED = "CHANNEL_NOT_TEXT_BASED", MESSAGE_MAX_LENGTH = "MESSAGE_MAX_LENGTH", - NICKNAMES_MAX_LENGTH = "NICKNAMES_MAX_LENGTH", + RULES_CHANNEL_CANNOT_BE_DELETED = "RULES_CHANNEL_CANNOT_BE_DELETED", + UPDATES_CHANNEL_CANNOT_BE_DELETED = "UPDATES_CHANNEL_CANNOT_BE_DELETED", + // Guild Errors + GUILD_NOT_DISCOVERABLE = "GUILD_NOT_DISCOVERABLE", + GUILD_NOT_FOUND = "GUILD_NOT_FOUND", + MEMBER_NOT_FOUND = "MEMBER_NOT_FOUND", + PRUNE_MAX_DAYS = "PRUNE_MAX_DAYS", + ROLE_NOT_FOUND = "ROLE_NOT_FOUND", + // Message Delete Errors + DELETE_MESSAGES_MIN = "DELETE_MESSAGES_MIN", PRUNE_MIN_DAYS = "PRUNE_MIN_DAYS", - RATE_LIMIT_RETRY_MAXED = "RATE_LIMIT_RETRY_MAXED", + // Interaction Errors + INVALID_SLASH_DESCRIPTION = "INVALID_SLASH_DESCRIPTION", + INVALID_SLASH_NAME = "INVALID_SLASH_NAME", + // Webhook Errors + INVALID_WEBHOOK_NAME = "INVALID_WEBHOOK_NAME", + INVALID_WEBHOOK_OPTIONS = "INVALID_WEBHOOK_OPTIONS", + // Permission Errors + MISSING_ADD_REACTIONS = "MISSING_ADD_REACTIONS", + MISSING_ADMINISTRATOR = "MISSING_ADMINISTRATOR", + MISSING_ATTACH_FILES = "MISSING_ATTACH_FILES", + MISSING_BAN_MEMBERS = "MISSING_BAN_MEMBERS", + MISSING_CHANGE_NICKNAME = "MISSING_CHANGE_NICKNAME", + MISSING_CONNECT = "MISSING_CONNECT", + MISSING_CREATE_INSTANT_INVITE = "MISSING_CREATE_INSTANT_INVITE", + MISSING_DEAFEN_MEMBERS = "MISSING_DEAFEN_MEMBERS", + MISSING_EMBED_LINKS = "MISSING_EMBED_LINKS", MISSING_INTENT_GUILD_MEMBERS = "MISSING_INTENT_GUILD_MEMBERS", + MISSING_KICK_MEMBERS = "MISSING_KICK_MEMBERS", + MISSING_MANAGE_CHANNELS = "MISSING_MANAGE_CHANNELS", + MISSING_MANAGE_EMOJIS = "MISSING_MANAGE_EMOJIS", + MISSING_MANAGE_GUILD = "MISSING_MANAGE_GUILD", + MISSING_MANAGE_MESSAGES = "MISSING_MANAGE_MESSAGES", + MISSING_MANAGE_NICKNAMES = "MISSING_MANAGE_NICKNAMES", + MISSING_MANAGE_ROLES = "MISSING_MANAGE_ROLES", + MISSING_MANAGE_WEBHOOKS = "MISSING_MANAGE_WEBHOOKS", + MISSING_MENTION_EVERYONE = "MISSING_MENTION_EVERYONE", + MISSING_MOVE_MEMBERS = "MISSING_MOVE_MEMBERS", + MISSING_MUTE_MEMBERS = "MISSING_MUTE_MEMBERS", + MISSING_PRIORITY_SPEAKER = "MISSING_PRIORITY_SPEAKER", + MISSING_READ_MESSAGE_HISTORY = "MISSING_READ_MESSAGE_HISTORY", + MISSING_SEND_MESSAGES = "MISSING_SEND_MESSAGES", + // TODO: Remove v11 + /** @deprecated Use MISSING_SEND_TTS_MESSAGES */ + MISSING_SEND_TTS_MESSAGE = "MISSING_SEND_TTS_MESSAGE", + MISSING_SEND_TTS_MESSAGES = "MISSING_SEND_TTS_MESSAGES", + MISSING_SPEAK = "MISSING_SPEAK", + MISSING_STREAM = "MISSING_STREAM", + MISSING_USE_VAD = "MISSING_USE_VAD", + MISSING_USE_EXTERNAL_EMOJIS = "MISSING_USE_EXTERNAL_EMOJIS", + MISSING_VIEW_AUDIT_LOG = "MISSING_VIEW_AUDIT_LOG", + MISSING_VIEW_CHANNEL = "MISSING_VIEW_CHANNEL", + MISSING_VIEW_GUILD_INSIGHTS = "MISSING_VIEW_GUILD_INSIGHTS", + // User Errors + NICKNAMES_MAX_LENGTH = "NICKNAMES_MAX_LENGTH", + USERNAME_INVALID_CHARACTER = "USERNAME_INVALID_CHARACTER", + USERNAME_INVALID_USERNAME = "USERNAME_INVALID_USERNAME", + USERNAME_MAX_LENGTH = "USERNAME_MAX_LENGTH", + USERNAME_MIN_LENGTH = "USERNAME_MIN_LENGTH", + // API Errors + RATE_LIMIT_RETRY_MAXED = "RATE_LIMIT_RETRY_MAXED", REQUEST_CLIENT_ERROR = "REQUEST_CLIENT_ERROR", REQUEST_SERVER_ERROR = "REQUEST_SERVER_ERROR", REQUEST_UNKNOWN_ERROR = "REQUEST_UNKNOWN_ERROR", - BOTS_HIGHEST_ROLE_TOO_LOW = "BOTS_HIGHEST_ROLE_TOO_LOW", - CHANNEL_NOT_IN_GUILD = "CHANNEL_NOT_IN_GUILD", - INVALID_WEBHOOK_NAME = "INVALID_WEBHOOK_NAME", - INVALID_SLASH_NAME = "INVALID_SLASH_NAME", - INVALID_SLASH_DESCRIPTION = "INVALID_SLASH_DESCRIPTION", - INVALID_WEBHOOK_OPTIONS = "INVALID_WEBHOOK_OPTIONS", - CHANNEL_NOT_FOUND = "CHANNEL_NOT_FOUND", - CHANNEL_NOT_TEXT_BASED = "CHANNEL_NOT_TEXT_BASED", - USERNAME_MAX_LENGTH = "USERNAME_MAX_LENGTH", - USERNAME_MIN_LENGTH = "USERNAME_MIN_LENGTH", - USERNAME_INVALID_CHARACTER = "USERNAME_INVALID_CHARACTER", - USERNAME_INVALID_USERNAME = "USERNAME_INVALID_USERNAME", - RULES_CHANNEL_CANNOT_BE_DELETED = "RULES_CHANNEL_CANNOT_BE_DELETED", - UPDATES_CHANNEL_CANNOT_BE_DELETED = "UPDATES_CHANNEL_CANNOT_BE_DELETED", - GUILD_NOT_FOUND = "GUILD_NOT_FOUND", - PRUNE_MAX_DAYS = "PRUNE_MAX_DAYS", - GUILD_NOT_DISCOVERABLE = "GUILD_NOT_DISCOVERABLE", - MISSING_CHANGE_NICKNAME = "MISSING_CHANGE_NICKNAME", } diff --git a/src/types/guild.ts b/src/types/guild.ts index a9b2faaad..74f45390d 100644 --- a/src/types/guild.ts +++ b/src/types/guild.ts @@ -9,29 +9,29 @@ import { RoleData } from "./role.ts"; export interface GuildRolePayload { /** The id of the guild */ - guild_id: string; + "guild_id": string; /** The role object of the role created, deleted, or updated */ role: RoleData; } export interface GuildRoleDeletePayload { /** The id of the guild */ - guild_id: string; + "guild_id": string; /** The id of the role */ - role_id: string; + "role_id": string; } export interface GuildMemberChunkPayload { /** The id of the guild */ - guild_id: string; + "guild_id": string; /** The set of guild members */ members: MemberCreatePayload[]; /** The chunk index in the expected chunks for this response */ - chunk_index: number; + "chunk_index": number; /** The total number of expected chunks for this response */ - chunk_count: number; + "chunk_count": number; /** if passing an invalid id, it will be found here */ - not_found?: string[]; + "not_found"?: string[]; /** if passing true, presences of the members will be here */ presences?: Presence[]; /** The nonce to help identify */ @@ -40,7 +40,7 @@ export interface GuildMemberChunkPayload { export interface GuildMemberUpdatePayload { /** The id of the guild */ - guild_id: string; + "guild_id": string; /** The user's role ids */ roles: string[]; /** The user */ @@ -48,23 +48,23 @@ export interface GuildMemberUpdatePayload { /** The nickname of the user in the guild */ nick: string; /** When the user used their nitro boost on the guild. */ - premium_since: string | null; + "premium_since": string | null; /** whether the user has not yet passed the guild's Membership Screening requirements */ pending?: boolean; } export interface GuildMemberAddPayload extends MemberCreatePayload { - guild_id: string; + "guild_id": string; } export interface GuildEmojisUpdatePayload { - guild_id: string; + "guild_id": string; emojis: Emoji[]; } export interface GuildBanPayload { /** The id of the guild */ - guild_id: string; + "guild_id": string; /** The banned user. Not a member as you can ban users outside of your guild. */ user: UserPayload; } @@ -86,21 +86,21 @@ export interface UpdateGuildPayload { /** The guild splash image hash */ splash: string | null; /** Discovery splash has; only present for guilds with the "DISCOVERABLE" feature */ - disovery_splash: string | null; + "disovery_splash": string | null; /** The id of the owner */ - owner_id: string; + "owner_id": string; /** The voice region id for the guild */ region: string; /** The afk channel id */ - afk_channel_id: string | null; + "afk_channel_id": string | null; /** AFK Timeout in seconds. */ - afk_timeout: number; + "afk_timeout": number; /** The verification level required for the guild */ - verification_level: number; + "verification_level": number; /** Default message notifications level */ - default_message_notifications: number; + "default_message_notifications": number; /** Explicit content filter level */ - explicit_content_filter: number; + "explicit_content_filter": number; /** The roles in the guild */ roles: RoleData[]; /** The custom guild emojis */ @@ -108,53 +108,53 @@ export interface UpdateGuildPayload { /** Enabled guild features */ features: GuildFeatures[]; /** Required MFA level for the guild */ - mfa_level: number; + "mfa_level": number; /** True if the server widget is enabled */ - widget_enabled?: boolean; + "widget_enabled"?: boolean; /** The channel id that the widget will generate an invite to, or null if set to no invite. */ - widget_channel_id?: string | null; + "widget_channel_id"?: string | null; /** The id of the channel to which system mesages are sent */ - system_channel_id: string | null; + "system_channel_id": string | null; /** System channel flags */ - system_channel_flags: number; + "system_channel_flags": number; /** The id of the channel where guilds with the PUBLIC feature can display rules and or guidelines. */ - rules_channel_id: string | null; + "rules_channel_id": string | null; /** The maximum amount of presences for the guild(the default value, currently 5000 is in effect when null is returned.) */ - max_presences?: number | null; + "max_presences"?: number | null; /** The maximum amount of members for the guild */ - max_members?: number; + "max_members"?: number; /** The vanity url code for the guild */ - vanity_url_code: string | null; + "vanity_url_code": string | null; /** The description for the guild */ description: string | null; /** The banner hash */ banner: string | null; /** The premium tier */ - premium_tier: number; + "premium_tier": number; /** The total number of users currently boosting this server. */ - premium_subscription_count: number; + "premium_subscription_count": number; /** The preferred local of this guild only set if guild has the DISCOVERABLE feature, defaults to en-US */ - preferred_locale: string; + "preferred_locale": string; /** The id of the channel where admins and moderators of guilds with the PUBLIC feature receive notices from Discord */ - public_updates_channel_id: string | null; + "public_updates_channel_id": string | null; /** The maximum amount of users in a video channel. */ - max_video_channel_users?: number; + "max_video_channel_users"?: number; /** The approximate number of members in this guild, returned from the GET /guild/id endpoint when with_counts is true */ - approximate_member_count?: number; + "approximate_member_count"?: number; /** The approximate number of non-offline members in this guild, returned from the GET /guild/id endpoint when with_counts is true */ - approximate_presence_count?: number; + "approximate_presence_count"?: number; } export interface CreateGuildPayload extends UpdateGuildPayload { /** When this guild was joined at */ - joined_at: string; + "joined_at": string; /** Whether this is considered a large guild */ large: boolean; /** Whether this guild is unavailable */ unavailable: boolean; /** Total number of members in this guild */ - member_count?: number; - voice_states: VoiceState[]; + "member_count"?: number; + "voice_states": VoiceState[]; /** Users in the guild */ members: MemberCreatePayload[]; /** Channels in the guild */ @@ -174,12 +174,10 @@ export type GuildFeatures = | "DISCOVERABLE" | "FEATURABLE" | "ANIMATED_ICON" - | "BANNER" - /** guild has enabled Membership Screening */ - | "MEMBER_VERIFICATION_GATE_ENABLED" - /** guild can be previewed before joining via Membership Screening or the directory */ - | "PREVIEW_ENABLED"; + | "BANNER"; +// TODO: v11 Remove +/** @deprecated Use DiscordVoiceRegion */ export interface VoiceRegion { /** unique ID for the region */ id: string; @@ -222,34 +220,34 @@ export interface GuildEditOptions { /** The guild voice region id */ region?: string; /** The verification level. 0 is UNRESTRICTED. 1 is Verified email. 2 is 5 minutes user. 3 is 10 minutes member in guild. 4 is verified phone number */ - verification_level?: 0 | 1 | 2 | 3; + "verification_level"?: 0 | 1 | 2 | 3; /** The default message notification level. 0 is ALL_MESSAGES and 1 is ONLY_MENTINS */ - default_message_notifications?: 0 | 1; + "default_message_notifications"?: 0 | 1; /** Explicit content filter level. 0 is DISABLED 1 is members without roles. 2 is all members */ - explicit_content_filter?: 0 | 1 | 2; + "explicit_content_filter"?: 0 | 1 | 2; /** The id for the afk channel. */ - afk_channel_id?: string; + "afk_channel_id"?: string; /** The afk timeout in seconds. */ - afk_timeout?: number; + "afk_timeout"?: number; /** If a URL is provided to the image parameter, Discordeno will automatically convert it to a base64 string internally. base64 1024x1024 png/jpeg/gif image for the guild icon (can be animated gif when the server has ANIMATED_ICON feature) */ icon?: string; /** user id to transfer guild ownership to (must be owner) */ - owner_id?: string; + "owner_id"?: string; /** If a URL is provided to the image parameter, Discordeno will automatically convert it to a base64 string internally. base64 16:9 png/jpeg image for the guild splash (when the server has INVITE_SPLASH feature) */ splash?: string; /** If a URL is provided to the image parameter, Discordeno will automatically convert it to a base64 string internally. base64 16:9 png/jpeg image for the guild banner (when the server has BANNER feature) */ banner?: string; /** the id of the channel to which system messages are sent */ - system_channel_id?: string; + "system_channel_id"?: string; } export interface EditIntegrationOptions { /** The behavior when an integration subscription lapses. */ - expire_behavior: number; + "expire_behavior": number; /** The period in seconds where the integration will ignore lapsed subscriptions */ - expire_grace_period: number; + "expire_grace_period": number; /** Whether emoticons should be synced for this integrations (twitch only currently) */ - enable_emoticons: boolean; + "enable_emoticons": boolean; } export interface Integration { @@ -264,21 +262,21 @@ export interface Integration { /** is this integration syncing */ syncing?: boolean; /** id that this integration uses for "subscribers" */ - role_id?: string; + "role_id"?: string; /** whether emoticons should be synced for this integration (twitch only currently) */ - enable_emoticons?: boolean; + "enable_emoticons"?: boolean; /** The behavior of expiring subscribers */ - expire_behavior?: IntegrationExpireBehaviors; + "expire_behavior"?: IntegrationExpireBehaviors; /** The grace period before expiring subscribers */ - expire_grace_period?: number; + "expire_grace_period"?: number; /** The user for this integration */ user?: UserPayload; /** The integration account information */ account: Account; /** When this integration was last synced */ - synced_at?: string; + "synced_at"?: string; /** how many subscribers this integration has */ - subscriber_count?: number; + "subscriber_count"?: number; /** has this integration been revoked */ revoked?: boolean; /** The bot/OAuth2 application for discord integrations */ @@ -311,7 +309,7 @@ export interface UserPayload { /** Whether the user is an official discord system user (part of the urgent message system.) */ system?: boolean; /** Whether the user has two factor enabled on their account */ - mfa_enabled?: boolean; + "mfa_enabled"?: boolean; /** the user's chosen language option */ locale?: string; /** Whether the email on this account has been verified */ @@ -321,7 +319,7 @@ export interface UserPayload { /** The flags on a user's account. */ flags?: number; /** The type of Nitro subscription on a user's account. */ - premium_type?: number; + "premium_type"?: number; } export interface PartialUser { @@ -338,7 +336,7 @@ export interface PartialUser { /** Whether the user is an official discord system user (part of the urgent message system.) */ system?: boolean; /** Whether the user has two factor enabled on their account */ - mfa_enabled?: boolean; + "mfa_enabled"?: boolean; /** the user's chosen language option */ locale?: string; /** Whether the email on this account has been verified */ @@ -348,7 +346,7 @@ export interface PartialUser { /** The flags on a user's account. */ flags?: number; /** The type of Nitro subscription on a user's account. */ - premium_type?: number; + "premium_type"?: number; } export enum UserFlags { @@ -382,9 +380,9 @@ export interface GuildEmbed { export interface GetAuditLogsOptions { /** Filter the logs for actions made by this user. */ - user_id?: string; + "user_id"?: string; /** The type of audit log. */ - action_type?: AuditLogType; + "action_type"?: AuditLogType; /** Filter the logs before a certain log entry. */ before?: string; /** How many entries are returned. Between 1-100. Default 50. */ @@ -507,15 +505,15 @@ export interface ChannelCreateOptions { /** The bitrate(in bits) of the voice channel. */ bitrate?: number; /** The user limit of the voice channel. */ - user_limit?: number; + "user_limit"?: number; /** The amount of seconds a user has to wait before sending another message. (0-21600 seconds). Bots, as well as users with the permission `manage_messages or manage_channel` are unaffected. */ - rate_limit_per_user?: number; + "rate_limit_per_user"?: number; /** The sorting position of the channel */ position?: number; /** The channel's permission overwrites */ permissionOverwrites?: Overwrite[]; /** The id of the parent category for the channel */ - parent_id?: string; + "parent_id"?: string; /** Whether the channel is nsfw */ nsfw?: boolean; /** The reason to add in the Audit Logs. */ @@ -557,27 +555,27 @@ export interface PruneOptions { export interface VoiceState { /** the guild id this voice state is for */ - guild_id?: string; + "guild_id"?: string; /** the channel id this user is connected to */ - channel_id: string | null; + "channel_id": string | null; /** the user id this voice state is for */ - user_id: string; + "user_id": string; /** the guild member this voice state is for */ member?: MemberCreatePayload; /** the session id for this voice state */ - session_id: string; + "session_id": string; /** whether this user is deafened by the server */ deaf: boolean; /** whether this user is muted by the server */ mute: boolean; /** whether this user is locally deafened */ - self_deaf: boolean; + "self_deaf": boolean; /** whether this user is locally muted */ - self_mute: boolean; + "self_mute": boolean; /** whether this user is streaming using "Go Live" */ - self_stream?: boolean; + "self_stream"?: boolean; /** whether this user's camera is enabled */ - self_video?: boolean; + "self_video"?: boolean; /** whether this user is muted by the current user */ suppress: boolean; } @@ -588,12 +586,12 @@ export interface Presence { /** The roles this user is in */ roles: string[]; /** The id of the guild */ - guild_id: string; + "guild_id": string; /** Either idle */ status: StatusType; activities: Activity[]; - client_status: ClientStatusPayload; - premium_since?: string | null; + "client_status": ClientStatusPayload; + "premium_since"?: string | null; nick?: string | null; } @@ -623,21 +621,21 @@ export interface CreateServerOptions { /** guild icon image url or base64 128x128 image for the guild icon */ icon?: string; /** verification level */ - verification_level?: number; + "verification_level"?: number; /** default message notification level */ - default_message_notifications?: number; + "default_message_notifications"?: number; /** explicit content filter level */ - explicit_content_filter?: number; + "explicit_content_filter"?: number; /** array of role objects new guild roles */ roles?: RoleData[]; /** array of partial channel objects new guild's channels */ channels?: ChannelCreatePayload[]; /** id for afk channel */ - afk_channel_id?: string; + "afk_channel_id"?: string; /** afk timeout in seconds */ - afk_timeout?: number; + "afk_timeout"?: number; /** the id of the channel where guild notices such as welcome messages and boost events are posted */ - system_channel_id?: string; + "system_channel_id"?: string; } // https://discord.com/developers/docs/resources/template#template-object @@ -649,21 +647,21 @@ export interface GuildTemplate { /** the description for the template */ description: string | null; /** number of times this template has been used */ - usage_count: number; + "usage_count": number; /** the ID of the user who created the template */ - creator_id: string; + "creator_id": string; /** the user who created the template */ user: UserPayload; /** when this template was created */ - created_at: string; + "created_at": string; /** when this template was last synced to the source guild */ - updated_at: string; + "updated_at": string; /** the ID of the guild this template is based on */ - source_guild_id: string; + "source_guild_id": string; /** the guild snapshot this template contains */ - serialized_source_guild: Guild; + "serialized_source_guild": Guild; /** whether the template has unsynced changes */ - is_dirty: boolean | null; + "is_dirty": boolean | null; } export interface CreateGuildFromTemplate { @@ -686,27 +684,3 @@ export interface EditGuildTemplate { /** description for the template (0-120 characters) */ description?: string | null; } - -export interface MembershipScreeningPayload { - /** when the fields were last updated */ - version: string; - /** the steps in the screening form */ - form_fields: MembershipScreeningFieldPayload[]; - /** the server description shown in the screening form */ - description: string | null; -} - -export interface MembershipScreeningFieldPayload { - /** the type of field */ - field_type: MembershipScreeningFieldTypes; - /** the title of the field */ - label: string; - /** the list of rules */ - values?: string[]; - /** whether the user has to fill out this field */ - required: boolean; -} - -export type MembershipScreeningFieldTypes = - /** Server Rules */ - "TERMS"; diff --git a/src/types/interactions.ts b/src/types/interactions.ts index c78be8ed3..dc405be01 100644 --- a/src/types/interactions.ts +++ b/src/types/interactions.ts @@ -8,9 +8,9 @@ export interface InteractionCommandPayload { /** the command data payload */ data?: InteractionData; /** the guild it was sent from */ - guild_id: string; + "guild_id": string; /** the channel it was sent from */ - channel_id: string; + "channel_id": string; /** guild member data for the invoking user */ member: MemberCreatePayload; /** a continuation token for responding to the interaction */ @@ -47,7 +47,7 @@ export interface ApplicationCommand { /** unique id of the command */ id: string; /** unique id of the parent application */ - application_id: string; + "application_id": string; /** 3-32 character name matching `^[\w-]{3,32}$` */ name: string; /** 1-100 character description */ @@ -94,6 +94,8 @@ export interface ApplicationCommandOptionChoice { value: string | number; } +// TODO: v11 Remove +/** @deprecated Use DiscordApplicationCommandEvent */ export type ApplicationCommandEvent = ApplicationCommand & { /** id of the guild the command is in */ guild_id?: string; diff --git a/src/types/invite.ts b/src/types/invite.ts index dd3789f55..0aa54ecda 100644 --- a/src/types/invite.ts +++ b/src/types/invite.ts @@ -13,13 +13,13 @@ export interface InvitePayload { /** the user who created the invite */ inviter?: UserPayload; /** the target user for this invite */ - target_user?: Partial; + "target_user"?: Partial; /** the type of user target for this invite */ - target_user_type?: InviteTargetUserTypes; + "target_user_type"?: InviteTargetUserTypes; /** approximate count of online members (only present when target_user is set) */ - approximate_presence_count?: number; + "approximate_presence_count"?: number; /** approximate count of total members */ - approximate_member_count: number; + "approximate_member_count": number; } /** https://discord.com/developers/docs/resources/invite#invite-resource */ diff --git a/src/types/member.ts b/src/types/member.ts index 0688769c0..5b9dc4854 100644 --- a/src/types/member.ts +++ b/src/types/member.ts @@ -10,7 +10,7 @@ export interface EditMemberOptions { /** Whether the user is deafened in voice channels. Requires DEAFEN_MEMBERS permission. */ deaf?: boolean; /** The id of the channel to move user to if they are connected to voice. To kick the user from their current channel, set to null. Requires MOVE_MEMBERS permission. When moving members to channels, must have permissions to both CONNECT to the channel and have the MOVE_MEMBER permission. */ - channel_id?: string | null; + "channel_id"?: string | null; } export interface MemberCreatePayload { @@ -21,9 +21,9 @@ export interface MemberCreatePayload { /** Array of role ids that the member has */ roles: string[]; /** When the user joined the guild. */ - joined_at: string; + "joined_at": string; /** When the user used their nitro boost on the server. */ - premium_since?: string; + "premium_since"?: string; /** Whether the user is deafened in voice channels */ deaf: boolean; /** Whether the user is muted in voice channels */ diff --git a/src/types/message.ts b/src/types/message.ts index e957c51ef..24f036a95 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -12,7 +12,7 @@ export interface MentionedChannel { /** The id of the channel */ id: string; /** The id of the guild containing the channel */ - guild_id: string; + "guild_id": string; /** The type of the channel. */ type: ChannelType; /** The name of the channel. */ @@ -29,7 +29,7 @@ export interface Attachment { /** Source url of file */ url: string; /** A proxied url of file */ - proxy_url: string; + "proxy_url": string; /** The height of file if an image */ height?: number | null; /** The width of the file if an image */ @@ -69,16 +69,16 @@ export interface EmbedFooter { /** The text of the footer */ text: string; /** The url of the footer icon. Only supports http(s) and attachments */ - icon_url?: string; + "icon_url"?: string; /** A proxied url of footer icon */ - proxy_icon_url?: string; + "proxy_icon_url"?: string; } export interface EmbedImage { /** The source url of image (only supports http(s) and attachments) */ url?: string; /** A proxied url of the image */ - proxy_url?: string; + "proxy_url"?: string; /** The height of image */ height?: number; /** The width of the image */ @@ -89,7 +89,7 @@ export interface EmbedThumbnail { /** The source url of image (only supports http(s) and attachments) */ url?: string; /** A proxied url of the thumbnail */ - proxy_url?: string; + "proxy_url"?: string; /** The height of the thumbnail */ height?: number; /** The width of the thumbnail */ @@ -118,9 +118,9 @@ export interface EmbedAuthor { /** The url of the author */ url?: string; /** The url of the author icon (supports http(s) and attachments) */ - icon_url?: string; + "icon_url"?: string; /** A proxied url of author icon */ - proxy_icon_url?: string; + "proxy_icon_url"?: string; } export interface EmbedField { @@ -141,6 +141,8 @@ export interface Reaction { emoji: EmojiReaction; } +// TODO: v11 Remove +/** @deprecated Use DiscordMessageTypes */ export enum MessageTypes { DEFAULT, RECIPIENT_ADD, @@ -174,14 +176,14 @@ export interface Activity { /** The type of message activity */ type: 1 | 2 | 3 | 5; /** The party id from a rich presence event */ - party_id?: string; + "party_id"?: string; } export interface Application { /** The id of the application */ id: string; /** The id of the embed's image asset */ - cover_image?: string; + "cover_image"?: string; /** The application's description */ description: string; /** The id of the application's icon */ @@ -196,11 +198,11 @@ export interface Application { export interface Reference { /** The id of the originating message */ - message_id?: string; + "message_id"?: string; /** The id of the originating message's channel */ - channel_id: string; + "channel_id": string; /** The id of the originating message's guild */ - guild_id?: string; + "guild_id"?: string; } export enum MessageFlags { @@ -221,7 +223,7 @@ export interface EmojiReaction { /** The user that created this emoji */ user?: UserPayload; /** Whether this emoji must be wrapped in colons */ - require_colons?: boolean; + "require_colons"?: boolean; /** Whether this emoji is managed */ managed?: boolean; /** Whether this emoji is animated */ @@ -241,9 +243,9 @@ export interface MessageCreateOptions { /** The id of the message */ id: string; /** The id of the channel the message was sent in */ - channel_id: string; + "channel_id": string; /** The id of the guild the message was sent in */ - guild_id?: string; + "guild_id"?: string; /** The author of this message (not guaranteed to be a valid user such as a webhook.) */ author: UserPayload; /** The member properties for this message's author. Can be partial. */ @@ -253,17 +255,17 @@ export interface MessageCreateOptions { /** When this message was sent */ timestamp: string; /** When this message was edited (if it was not edited, null) */ - edited_timestamp: string | null; + "edited_timestamp": string | null; /** Whether this was a TextToSpeech message. */ tts: boolean; /** Whether this message mentions everyone */ - mentions_everyone: boolean; + "mentions_everyone": boolean; /** Users specifically mentioned in the message. */ mentions: MentionedUser[]; /** Roles specifically mentioned in this message */ - mention_roles: string[]; + "mention_roles": string[]; /** Channels specifically mentioned in this message */ - mention_channels?: MentionedChannel[]; + "mention_channels"?: MentionedChannel[]; /** Any attached files */ attachments: Attachment[]; /** Any embedded content */ @@ -275,7 +277,7 @@ export interface MessageCreateOptions { /** Whether this message is pinned */ pinned: boolean; /** If the message is generated by a webhook, this is the webhooks id */ - webhook_id?: string; + "webhook_id"?: string; /** The type of message */ type: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; /** The activities sent with Rich Presence-related chat embeds. */ @@ -283,20 +285,20 @@ export interface MessageCreateOptions { /** Applications that sent with Rich Presence related chat embeds. */ applications?: Application; /** The reference data sent with crossposted messages */ - message_reference?: Reference; + "message_reference"?: Reference; /** The message flags combined like permission bits describe extra features of the message */ flags?: 1 | 2 | 4 | 8 | 16; /** the stickers sent with the message (bots currently can only receive messages with stickers, not send) */ stickers?: MessageSticker[]; /** The message id of the original message if this message was sent as a reply. If null, the original message was deleted. */ - referenced_message?: MessageCreateOptions | null; + "referenced_message"?: MessageCreateOptions | null; } export interface BaseMessageDeletePayload { /** The id of the channel */ - channel_id: string; + "channel_id": string; /** The id of the guild */ - guild_id?: string; + "guild_id"?: string; } export interface MessageDeletePayload extends BaseMessageDeletePayload { @@ -313,21 +315,21 @@ export interface MessageUpdatePayload { /** The message id */ id: string; /** The channel id */ - channel_id: string; + "channel_id": string; } export interface BaseMessageReactionPayload { /** The id of the channel */ - channel_id: string; + "channel_id": string; /** The id of the message */ - message_id: string; + "message_id": string; /** The id of the guild */ - guild_id?: string; + "guild_id"?: string; } export interface MessageReactionPayload extends BaseMessageReactionPayload { /** The id of the user */ - user_id: string; + "user_id": string; /** The member who reacted if this happened in a guild. Not available for MESSAGE_REACTION_REMOVE */ member?: MemberCreatePayload; /** The emoji used to react */ @@ -355,7 +357,7 @@ export interface MessageSticker { /** id of the sticker */ id: string; /** id of the pack the sticker is from */ - pack_id: string; + "pack_id": string; /** name of the sticker */ name: string; /** description of the sticker */ @@ -365,9 +367,9 @@ export interface MessageSticker { /** sticker asset hash. The URL for fetching sticker assets is currently private. */ asset: string; /** sticker preview asset hash. The URL for fetching sticker assets is currently private. */ - preview_asset: string | null; + "preview_asset": string | null; /** type of sticker format */ - format_type: MessageStickerFormat; + "format_type": MessageStickerFormat; } export enum MessageStickerFormat { diff --git a/src/types/mod.ts b/src/types/mod.ts index c4128d9d8..13ca88bb5 100644 --- a/src/types/mod.ts +++ b/src/types/mod.ts @@ -14,3 +14,4 @@ export * from "./permission.ts"; export * from "./presence.ts"; export * from "./role.ts"; export * from "./webhook.ts"; +export * from "./api/mod.ts"; diff --git a/src/types/oauth.ts b/src/types/oauth.ts index 859bc87c7..69d3cd952 100644 --- a/src/types/oauth.ts +++ b/src/types/oauth.ts @@ -11,27 +11,27 @@ export interface OAuthApplication { /** the description of the app */ description: string; /** an array of rpc origin urls, if rpx is enabled */ - rpc_origins?: string[]; + "rpc_origins"?: string[]; /** when false only app owner can join the app's bot to guilds */ - bot_public: boolean; + "bot_public": boolean; /** when true the app's bot will only join upon completion of the full oauth2 code grant flow */ - bot_require_code_grand: boolean; + "bot_require_code_grand": boolean; /** partial user object containing info on the owner of the application */ owner: Partial; /** if this application is a game sold on Disccord, this field will be the summary field for the store page of its primary sku */ summary: string; /** the base64 enccoded key for the GameSDK'S GetTicket */ - verify_key: string; + "verify_key": string; /** if the application belongs to a team, this will be a list of the members of that team */ team: TeamPayload | null; /** if this application is a game sold on Discord, this field will be the guild to which it has been linked */ - guild_id?: string; + "guild_id"?: string; /** if this application is a game sold on Discord, this field will be the id of the "Game SKU" that is created, if exists */ - primary_sku_id?: string; + "primary_sku_id"?: string; /** if this application is a game sold on Discord, this field will be the URL slug that links to the store page */ slug?: string; /** if this application is a game sold on Discord, this field wil be the hash of the image on store embeds */ - cover_image?: string; + "cover_image"?: string; /** the application's public flags */ flags: number; } diff --git a/src/types/options.ts b/src/types/options.ts index 0ec4008d2..770d031e2 100644 --- a/src/types/options.ts +++ b/src/types/options.ts @@ -10,6 +10,8 @@ import { Emoji, IntegrationCreateUpdateEvent, IntegrationDeleteEvent, + InviteCreateEvent, + InviteDeleteEvent, PresenceUpdatePayload, TypingStartPayload, VoiceStateUpdatePayload, @@ -232,6 +234,10 @@ export interface EventHandlers { integrationUpdate?: (data: Camelize) => unknown; /** Sent when an integration is deleted. */ integrationDelete?: (data: Camelize) => undefined; + /** Sent when a new invite to a channel is created. */ + inviteCreate?: (data: Camelize) => unknown; + /** Sent when an invite is deleted. */ + inviteDelete?: (data: Camelize) => unknown; } /** https://discord.com/developers/docs/topics/gateway#list-of-intents */ diff --git a/src/types/role.ts b/src/types/role.ts index 46c9c7feb..58e160f0a 100644 --- a/src/types/role.ts +++ b/src/types/role.ts @@ -21,9 +21,9 @@ export interface RoleData { export interface RoleTags { /** the id of the bot who has this role */ - bot_id?: string; + "bot_id"?: string; /** whether this is the premium subscriber role for this guild */ - premium_subscriber?: null; + "premium_subscriber"?: null; /** the id of the integration this role belongs to */ - integration_id?: string; + "integration_id"?: string; } diff --git a/src/types/teams.ts b/src/types/teams.ts index 425c2b66d..3cdea56f4 100644 --- a/src/types/teams.ts +++ b/src/types/teams.ts @@ -9,17 +9,17 @@ export interface TeamPayload { /** the members of the team */ members: TeamMembersPayload[]; /** the user id of the current team owner */ - owner_user_id: string; + "owner_user_id": string; } /** https://discord.com/developers/docs/topics/teams#data-models-team-members-object */ export interface TeamMembersPayload { /** the user's membership state on the team */ - membership_state: MembershipState; + "membership_state": MembershipState; /** will always be ["*"] */ permissions: string[]; /** the id of the parent team of which they are a member */ - team_id: string; + "team_id": string; /** the avatar, discriminator, id, and username of the user */ user: Partial; } diff --git a/src/types/util.ts b/src/types/util.ts index dd17960f5..2a02f1f50 100644 --- a/src/types/util.ts +++ b/src/types/util.ts @@ -6,4 +6,5 @@ export type CamelizeString = T extends string : T : T; -export type Camelize = { [K in keyof T]: T[K] }; +// deno-fmt-ignore +export type Camelize = { [K in keyof T as CamelizeString]: T[K] }; diff --git a/src/types/webhook.ts b/src/types/webhook.ts index 8d7ae47c4..d5a680785 100644 --- a/src/types/webhook.ts +++ b/src/types/webhook.ts @@ -9,9 +9,9 @@ export interface WebhookPayload { /** The type of the webhook */ type: WebhookType; /** The guild id this webhook is for */ - guild_id?: string; + "guild_id"?: string; /** The channel id this webhook is for */ - channel_id: string; + "channel_id": string; /** The user this webhook was created by(not returned when getting a webhook with its token) */ user?: UserPayload; /** The default name of the webhook */ @@ -53,7 +53,7 @@ export interface ExecuteWebhookOptions { /** override the default username of the webhook */ username?: string; /** override the default avatar of the webhook*/ - avatar_url?: string; + "avatar_url"?: string; /** true if this is a TTS message */ tts?: boolean; /** file contents the contents of the file being sent one of content, file, embeds */ @@ -74,7 +74,7 @@ export interface ExecuteWebhookOptions { export interface EditWebhookMessageOptions { content?: string; embeds?: Embed[]; - allowed_mentions?: AllowedMentions; + "allowed_mentions"?: AllowedMentions; } export interface CreateSlashCommandOptions { @@ -92,7 +92,7 @@ export interface SlashCommand { /** unique id of the command */ id: string; /** unique id of the parent application */ - application_id: string; + "application_id": string; /** 3-32 character name */ name: string; /** 1-100 character description */ @@ -147,9 +147,9 @@ export interface Interaction { /** The command data payload */ data?: SlashCommandInteractionData; /** The id of the guild it was sent from */ - guild_id: string; + "guild_id": string; /** The id of the channel it was sent from */ - channel_id: string; + "channel_id": string; /** The Payload of the member it was sent from */ member: UserPayload; /** The token for this interaction */ @@ -190,7 +190,7 @@ export interface SlashCommandCallbackData { /** supports up to 10 embeds */ embeds?: Embed[]; /** allowed mentions for the message */ - allowed_mentions?: AllowedMentions; + "allowed_mentions"?: AllowedMentions; /** acceptable values are message flags */ flags?: number; } diff --git a/src/util/constants.ts b/src/util/constants.ts index c48fc2e03..afad4433c 100644 --- a/src/util/constants.ts +++ b/src/util/constants.ts @@ -116,8 +116,6 @@ export const endpoints = { `${baseEndpoints.BASE_URL}/guilds/templates/${code}`, GUILD_TEMPLATES: (guildID: string) => `${GUILDS_BASE(guildID)}/templates`, GUILD_PREVIEW: (guildID: string) => `${GUILDS_BASE(guildID)}/preview`, - GUILD_MEMBER_VERIFICATION: (guildID: string) => - `${GUILDS_BASE(guildID)}/member-verification`, // Voice VOICE_REGIONS: `${baseEndpoints.BASE_URL}/voice/regions`, diff --git a/src/ws/deps.ts b/src/ws/deps.ts index d008f1cb6..0ccaaafef 100644 --- a/src/ws/deps.ts +++ b/src/ws/deps.ts @@ -1 +1 @@ -export { decompress_with as decompressWith } from "https://unpkg.com/@evan/wasm@0.0.25/target/zlib/deno.js"; +export { decompress_with as decompressWith } from "https://esm.sh/@evan/wasm@0.0.41/target/zlib/deno.js"; diff --git a/src/ws/shard.ts b/src/ws/shard.ts index 1acd29a3f..9f41b45bc 100644 --- a/src/ws/shard.ts +++ b/src/ws/shard.ts @@ -1,12 +1,9 @@ -import { - botGatewayData, - eventHandlers, - IdentifyPayload, - proxyWSURL, -} from "../bot.ts"; +import { botGatewayData, eventHandlers, proxyWSURL } from "../bot.ts"; import { DiscordBotGatewayData, DiscordHeartbeatPayload, + DiscordIdentify, + DiscordPayload, FetchMembersOptions, GatewayOpcode, ReadyPayload, @@ -14,8 +11,9 @@ import { import { BotStatusRequest, delay } from "../util/utils.ts"; import { decompressWith } from "./deps.ts"; import { handleDiscordPayload } from "./shard_manager.ts"; +import { Collection } from "../util/collection.ts"; -const basicShards = new Map(); +const basicShards = new Collection(); const heartbeating = new Map(); const utf8decoder = new TextDecoder(); const RequestMembersQueue: RequestMemberQueuedRequest[] = []; @@ -39,7 +37,7 @@ interface RequestMemberQueuedRequest { export function createShard( data: DiscordBotGatewayData, - identifyPayload: IdentifyPayload, + identifyPayload: DiscordIdentify, resuming = false, shardID = 0, ) { @@ -74,7 +72,7 @@ export function createShard( }); }; - ws.onmessage = ({ data: message }) => { + ws.onmessage = async ({ data: message }) => { if (message instanceof ArrayBuffer) { message = new Uint8Array(message); } @@ -93,7 +91,7 @@ export function createShard( switch (messageData.op) { case GatewayOpcode.Hello: if (!heartbeating.has(basicShard.id)) { - heartbeat( + await heartbeat( basicShard, (messageData.d as DiscordHeartbeatPayload).heartbeat_interval, identifyPayload, @@ -109,7 +107,7 @@ export function createShard( { type: "gatewayReconnect", data: { shardID: basicShard.id } }, ); basicShard.needToResume = true; - resumeConnection(data, identifyPayload, basicShard.id); + await resumeConnection(data, identifyPayload, basicShard.id); break; case GatewayOpcode.InvalidSession: eventHandlers.debug?.( @@ -124,7 +122,7 @@ export function createShard( break; } basicShard.needToResume = true; - resumeConnection(data, identifyPayload, basicShard.id); + await resumeConnection(data, identifyPayload, basicShard.id); break; default: if (messageData.t === "RESUMED") { @@ -143,13 +141,13 @@ export function createShard( // Update the sequence number if it is present if (messageData.s) basicShard.previousSequenceNumber = messageData.s; - handleDiscordPayload(messageData, basicShard.id); + await handleDiscordPayload(messageData, basicShard.id); break; } } }; - ws.onclose = ({ reason, code, wasClean }) => { + ws.onclose = async ({ reason, code, wasClean }) => { eventHandlers.debug?.( { type: "wsClose", @@ -157,60 +155,22 @@ export function createShard( }, ); - switch (code) { - case 4001: - throw new Error( - "[Unknown opcode] Sent an invalid Gateway opcode or an invalid payload for an opcode.", - ); - case 4002: - throw new Error("[Decode error] Sent an invalid payload to API."); - case 4004: - throw new Error( - "[Authentication failed] The account token sent with your identify payload is incorrect.", - ); - case 4005: - throw new Error( - "[Already authenticated] Sent more than one identify payload.", - ); - case 4010: - throw new Error( - "[Invalid shard] Sent an invalid shard when identifying.", - ); - case 4011: - throw new Error( - "[Sharding required] The session would have handled too many guilds - you are required to shard your connection in order to connect.", - ); - case 4012: - throw new Error( - "[Invalid API version] Sent an invalid version for the gateway.", - ); - case 4013: - throw new Error( - "[Invalid intent(s)] Sent an invalid intent for a Gateway Intent.", - ); - case 4014: - throw new Error( - "[Disallowed intent(s)] Sent a disallowed intent for a Gateway Intent. You may have tried to specify an intent that you have not enabled or are not whitelisted for.", - ); - case 4003: - case 4007: - case 4008: - case 4009: - eventHandlers.debug?.({ - type: "wsReconnect", - data: { shardID: basicShard.id, code, reason, wasClean }, - }); - createShard(data, identifyPayload, false, shardID); - break; - default: - basicShard.needToResume = true; - resumeConnection(botGatewayData, identifyPayload, shardID); - break; + if ([4001, 4002, 4004, 4005, 4010, 4011, 4012, 4013, 4014].includes(code)) { + throw new Error(reason); + } else if ([4000, 4003, 4007, 4008, 4009].includes(code)) { + eventHandlers.debug?.({ + type: "wsReconnect", + data: { shardID: basicShard.id, code, reason, wasClean }, + }); + createShard(data, identifyPayload, false, shardID); + } else { + basicShard.needToResume = true; + await resumeConnection(botGatewayData, identifyPayload, shardID); } }; } -function identify(shard: BasicShard, payload: IdentifyPayload) { +function identify(shard: BasicShard, payload: DiscordIdentify) { eventHandlers.debug?.( { type: "gatewayIdentify", @@ -220,31 +180,27 @@ function identify(shard: BasicShard, payload: IdentifyPayload) { }, ); - return shard.ws.send( - JSON.stringify( - { - op: GatewayOpcode.Identify, - d: { ...payload, shard: [shard.id, payload.shard[1]] }, - }, - ), - ); + sendWS({ + op: GatewayOpcode.Identify, + d: { ...payload, shard: [shard.id, payload.shard[1]] }, + }, shard.id); } -function resume(shard: BasicShard, payload: IdentifyPayload) { - return shard.ws.send(JSON.stringify({ +function resume(shard: BasicShard, payload: DiscordIdentify) { + sendWS({ op: GatewayOpcode.Resume, d: { token: payload.token, session_id: shard.sessionID, seq: shard.previousSequenceNumber, }, - })); + }, shard.id); } async function heartbeat( shard: BasicShard, interval: number, - payload: IdentifyPayload, + payload: DiscordIdentify, data: DiscordBotGatewayData, ) { // We lost socket connection between heartbeats, resume connection @@ -269,17 +225,17 @@ async function heartbeat( }, }, ); - return shard.ws.send(JSON.stringify({ op: 4009 })); + + return shard.ws.close(4009, "Session timed out"); } } // Set it to false as we are issuing a new heartbeat heartbeating.set(shard.id, false); - shard.ws.send( - JSON.stringify( - { op: GatewayOpcode.Heartbeat, d: shard.previousSequenceNumber }, - ), + sendWS( + { op: GatewayOpcode.Heartbeat, d: shard.previousSequenceNumber }, + shard.id, ); eventHandlers.debug?.( { @@ -297,7 +253,7 @@ async function heartbeat( async function resumeConnection( data: DiscordBotGatewayData, - payload: IdentifyPayload, + payload: DiscordIdentify, shardID: number, ) { const shard = basicShards.get(shardID); @@ -312,13 +268,13 @@ async function resumeConnection( eventHandlers.debug?.({ type: "gatewayResume", data: { shardID: shard.id } }); // Run it once - await createShard(data, payload, true, shard.id); + createShard(data, payload, true, shard.id); // Then retry every 15 seconds await delay(1000 * 15); if (shard.needToResume) await resumeConnection(data, payload, shardID); } -export function requestGuildMembers( +export async function requestGuildMembers( guildID: string, shardID: number, nonce: string, @@ -345,11 +301,11 @@ export function requestGuildMembers( // If its closed add back to queue to redo on resume if (shard?.ws.readyState === WebSocket.CLOSED) { - requestGuildMembers(guildID, shardID, nonce, options); + await requestGuildMembers(guildID, shardID, nonce, options); return; } - shard?.ws.send(JSON.stringify({ + sendWS({ op: GatewayOpcode.RequestGuildMembers, d: { guild_id: guildID, @@ -360,7 +316,7 @@ export function requestGuildMembers( user_ids: options?.userIDs, nonce, }, - })); + }, shard?.id); } async function processGatewayQueue() { @@ -369,7 +325,7 @@ async function processGatewayQueue() { return; } - basicShards.forEach((shard) => { + await Promise.all(basicShards.map(async (shard) => { const index = RequestMembersQueue.findIndex((q) => q.shardID === shard.id); // 2 events per second is the rate limit. const request = RequestMembersQueue[index]; @@ -383,7 +339,7 @@ async function processGatewayQueue() { }, }, ); - requestGuildMembers( + await requestGuildMembers( request.guildID, request.shardID, request.nonce, @@ -407,7 +363,7 @@ async function processGatewayQueue() { }, }, ); - requestGuildMembers( + await requestGuildMembers( secondRequest.guildID, secondRequest.shardID, secondRequest.nonce, @@ -418,7 +374,7 @@ async function processGatewayQueue() { RequestMembersQueue.splice(secondIndex, 1); } } - }); + })); await delay(1500); @@ -427,7 +383,7 @@ async function processGatewayQueue() { export function botGatewayStatusRequest(payload: BotStatusRequest) { basicShards.forEach((shard) => { - shard.ws.send(JSON.stringify({ + sendWS({ op: GatewayOpcode.StatusUpdate, d: { since: null, @@ -440,6 +396,16 @@ export function botGatewayStatusRequest(payload: BotStatusRequest) { status: payload.status, afk: false, }, - })); + }, shard.id); }); } + +/** Enqueues the specified data to be transmitted to the server over the WebSocket connection, */ +export function sendWS(payload: DiscordPayload, shardID = 0) { + const shard = basicShards.get(shardID); + if (!shard) return false; + + const serialized = JSON.stringify(payload); + shard.ws.send(serialized); + return true; +} diff --git a/src/ws/shard_manager.ts b/src/ws/shard_manager.ts index 38fafb515..b43a8eaa5 100644 --- a/src/ws/shard_manager.ts +++ b/src/ws/shard_manager.ts @@ -1,9 +1,10 @@ import { controllers } from "../api/controllers/mod.ts"; import { Guild } from "../api/structures/guild.ts"; import { Member } from "../api/structures/mod.ts"; -import { eventHandlers, IdentifyPayload } from "../bot.ts"; +import { eventHandlers } from "../bot.ts"; import { DiscordBotGatewayData, + DiscordIdentify, DiscordPayload, FetchMembersOptions, GatewayOpcode, @@ -26,7 +27,7 @@ export function allowNextShard(enabled = true) { export async function spawnShards( data: DiscordBotGatewayData, - payload: IdentifyPayload, + payload: DiscordIdentify, shardID: number, lastShardID: number, skipChecks?: number, @@ -39,8 +40,8 @@ export async function spawnShards( shardID, data.shards > lastShardID ? data.shards : lastShardID, ]; - // Startx The shard - await createShard(data, payload, false, shardID); + // Start The shard + createShard(data, payload, false, shardID); // Spawn next shard await spawnShards( data, @@ -90,7 +91,7 @@ export async function handleDiscordPayload( } } -export function requestAllMembers( +export async function requestAllMembers( guild: Guild, resolve: ( value: Collection | PromiseLike>, @@ -99,7 +100,13 @@ export function requestAllMembers( ) { const nonce = `${guild.id}-${Date.now()}`; cache.fetchAllMembersProcessingRequests.set(nonce, resolve); - return requestGuildMembers(guild.id, guild.shardID, nonce, options); + + await requestGuildMembers( + guild.id, + guild.shardID, + nonce, + options, + ); } export function sendGatewayCommand( diff --git a/test/deps.ts b/test/deps.ts index 34bf09d90..e028981d2 100644 --- a/test/deps.ts +++ b/test/deps.ts @@ -2,5 +2,5 @@ export { assertArrayIncludes, assertEquals, assertExists, -} from "https://deno.land/std@0.81.0/testing/asserts.ts"; +} from "https://deno.land/std@0.86.0/testing/asserts.ts"; export * from "../mod.ts";