diff --git a/packages/gateway/src/manager.ts b/packages/gateway/src/manager.ts index 3bf1ef2e3..4c2e3dde9 100644 --- a/packages/gateway/src/manager.ts +++ b/packages/gateway/src/manager.ts @@ -1,9 +1,9 @@ -import type { AtLeastOne, BigString, Camelize, DiscordGetGatewayBot } from '@discordeno/types' -import { GatewayOpcodes } from '@discordeno/types' +import type { AtLeastOne, BigString, Camelize, DiscordGetGatewayBot, DiscordMember, RequestGuildMembers } from '@discordeno/types' +import { GatewayIntents, GatewayOpcodes } from '@discordeno/types' import type { LeakyBucket } from '@discordeno/utils' -import { createLeakyBucket, delay } from '@discordeno/utils' +import { Collection, createLeakyBucket, delay } from '@discordeno/utils' import Shard from './Shard.js' -import type { ShardEvents, UpdateVoiceState } from './types.js' +import type { ShardEvents, StatusUpdate, UpdateVoiceState } from './types.js' export function createGatewayManager(options: CreateGatewayManagerOptions): GatewayManager { if (!options.connection) { @@ -40,6 +40,12 @@ export function createGatewayManager(options: CreateGatewayManagerOptions): Gate spawnShardDelay: options.spawnShardDelay ?? 5300, shards: new Map(), buckets: new Map(), + cache: { + requestMembers: { + enabled: options.cache?.requestMembers?.enabled ?? false, + pending: new Collection(), + } + }, calculateTotalShards() { // Bots under 100k servers do not have access to total shards. @@ -196,6 +202,85 @@ export function createGatewayManager(options: CreateGatewayManagerOptions): Gate }, }) }, + + async editBotStatus(data) { + await Promise.all( + [...gateway.shards.values()].map(async (shard) => { + gateway.editShardStatus(shard.id, data) + }), + ) + }, + + async editShardStatus(shardId, data) { + const shard = gateway.shards.get(shardId) + if (!shard) { + throw new Error(`Shard (id: ${shardId}) not found.`) + } + + return await shard.send({ + op: GatewayOpcodes.PresenceUpdate, + d: { + since: null, + afk: false, + activities: data.activities, + status: data.status, + }, + }) + }, + + async requestMembers(guildId, options) { + // You can request 1 member without the intent + // Check if intents is not 0 as proxy ws won't set intents in other instances + if (gateway.intents && (!options?.limit || options.limit > 1) && !(gateway.intents & GatewayIntents.GuildMembers)) { + throw new Error('MISSING_INTENT_GUILD_MEMBERS') + } + + if (options?.userIds?.length) { + options.limit = options.userIds.length + } + + const shardId = gateway.calculateShardId(guildId) + const shard = gateway.shards.get(shardId) + if (!shard) { + throw new Error(`Shard (id: ${shardId}) not found.`) + } + + const nonce = `${guildId}-${Date.now()}` + + // Gateway does not require caching these requests so directly send and return + if (!gateway.cache.requestMembers?.enabled) { + await shard.send({ + op: GatewayOpcodes.RequestGuildMembers, + d: { + guild_id: guildId.toString(), + // If a query is provided use it, OR if a limit is NOT provided use "" + query: options?.query ?? (options?.limit ? undefined : ''), + limit: options?.limit ?? 0, + presences: options?.presences ?? false, + user_ids: options?.userIds?.map((id) => id.toString()), + nonce, + }, + }) + return []; + } + + return await new Promise((resolve) => { + gateway.cache.requestMembers?.pending.set(nonce, { nonce, resolve, members: [] }) + + shard.send({ + op: GatewayOpcodes.RequestGuildMembers, + d: { + guild_id: guildId.toString(), + // If a query is provided use it, OR if a limit is NOT provided use "" + query: options?.query ?? (options?.limit ? undefined : ''), + limit: options?.limit ?? 0, + presences: options?.presences ?? false, + user_ids: options?.userIds?.map((id) => id.toString()), + nonce, + }, + }) + }) + }, } return gateway @@ -276,6 +361,18 @@ export interface CreateGatewayManagerOptions { version?: number /** The events handlers */ events: ShardEvents + /** This managers cache related settings. */ + cache?: { + requestMembers?: { + /** + * Whether or not request member requests should be cached. + * @default false + */ + enabled?: boolean + /** The pending requests. */ + pending: Collection + } + } } export interface GatewayManager extends Required { @@ -329,4 +426,52 @@ export interface GatewayManager extends Required { channelId: BigString, options?: AtLeastOne>, ) => Promise + /** + * Edits the bot status in all shards that this gateway manages. + * + * @param data The status data to set the bots status to. + * @returns Promise + */ + editBotStatus: (data: StatusUpdate) => Promise + /** + * Edits the bot's status on one shard. + * + * @param shardId The shard id to edit the status for. + * @param data The status data to set the bots status to. + * @returns Promise + */ + editShardStatus: (shardId: number, data: StatusUpdate) => Promise + /** + * Fetches the list of members for a guild over the gateway. + * + * @param guildId - The ID of the guild to get the list of members for. + * @param options - The parameters for the fetching of the members. + * + * @remarks + * If requesting the entire member list: + * - Requires the `GUILD_MEMBERS` intent. + * + * If requesting presences ({@link RequestGuildMembers.presences | presences} set to `true`): + * - Requires the `GUILD_PRESENCES` intent. + * + * If requesting a prefix ({@link RequestGuildMembers.query | query} non-`undefined`): + * - Returns a maximum of 100 members. + * + * If requesting a users by ID ({@link RequestGuildMembers.userIds | userIds} non-`undefined`): + * - Returns a maximum of 100 members. + * + * Fires a _Guild Members Chunk_ gateway event for every 1000 members fetched. + * + * @see {@link https://discord.com/developers/docs/topics/gateway#request-guild-members} + */ + requestMembers: (guildId: BigString, options?: Omit) => Promise> +} + +export interface RequestMemberRequest { + /** The unique nonce for this request. */ + nonce: string + /** The resolver handler to run when all members arrive. */ + resolve: (value: Camelize | PromiseLike>) => void + /** The members that have already arrived for this request. */ + members: Camelize } diff --git a/packages/gateway/src/types.ts b/packages/gateway/src/types.ts index 082e6a1ae..10b8eb564 100644 --- a/packages/gateway/src/types.ts +++ b/packages/gateway/src/types.ts @@ -1,4 +1,4 @@ -import type { ActivityTypes, Camelize, DiscordGatewayPayload, GatewayOpcodes, PresenceStatus } from '@discordeno/types' +import type { ActivityTypes, Camelize, DiscordActivity, DiscordGatewayPayload, GatewayOpcodes, PresenceStatus } from '@discordeno/types' import type Shard from './Shard.js' export enum ShardState { @@ -167,3 +167,15 @@ export interface UpdateVoiceState { /** Is the client deafened */ selfDeaf: boolean } + +/** https://discord.com/developers/docs/topics/gateway-events#update-presence */ +export interface StatusUpdate { + // /** Unix time (in milliseconds) of when the client went idle, or null if the client is not idle */ + // since: number | null; + /** The user's activities */ + activities: Camelize + /** The user's new status */ + status: keyof typeof PresenceStatus + // /** Whether or not the client is afk */ + // afk: boolean; +} diff --git a/packages/rest/src/manager.ts b/packages/rest/src/manager.ts index dae8ff73f..d51e6e21f 100644 --- a/packages/rest/src/manager.ts +++ b/packages/rest/src/manager.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable no-const-assign */ -import { InteractionResponseTypes } from '@discordeno/types' +import { DiscordGuildWidget, InteractionResponseTypes } from '@discordeno/types' import { calculateBits, camelize, @@ -53,6 +53,8 @@ import type { DiscordFollowedChannel, DiscordGetGatewayBot, DiscordGuild, + DiscordGuildPreview, + DiscordGuildWidgetSettings, DiscordIntegration, DiscordInvite, DiscordInviteMetadata, @@ -61,6 +63,8 @@ import type { DiscordMember, DiscordMemberWithUser, DiscordMessage, + DiscordModifyGuildWelcomeScreen, + DiscordPrunedCount, DiscordRole, DiscordScheduledEvent, DiscordStageInstance, @@ -72,6 +76,7 @@ import type { DiscordVanityUrl, DiscordVoiceRegion, DiscordWebhook, + DiscordWelcomeScreen, EditAutoModerationRuleOptions, EditChannelPermissionOverridesOptions, EditGuildRole, @@ -175,6 +180,9 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage dm: () => { return '/users/@me/channels' }, + pins: (channelId) => { + return `/channels/${channelId}/pins` + }, reactions: { bot: (channelId, messageId, emoji) => { return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/@me` @@ -195,6 +203,18 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage if (options.limit) url += `&limit=${options.limit}` } + return url + }, + message: (channelId, messageId, emoji, options) => { + let url = `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}?` + + if (options) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + if (options.after) url += `after=${options.after}` + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + if (options.limit) url += `&limit=${options.limit}` + } + return url }, }, @@ -505,6 +525,26 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage }, }, mfa: (guildId) => `/guilds/${guildId}/mfa`, + preview: (guildId) => { + return `/guilds/${guildId}/preview` + }, + prune: (guildId, options) => { + let url = `/guilds/${guildId}/prune?` + + if (options) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + if (options.days) url += `days=${options.days}` + if (Array.isArray(options.includeRoles)) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url += `&include_roles=${options.includeRoles.join(',')}` + } else if (options.includeRoles) { + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + url += `&include_roles=${options.includeRoles}` + } + } + + return url + }, roles: { one: (guildId, roleId) => { return `/guilds/${guildId}/roles/${roleId}` @@ -545,12 +585,25 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage webhooks: (guildId) => { return `/guilds/${guildId}/webhooks` }, + welcome: (guildId) => { + return `/guilds/${guildId}/welcome-screen` + }, + widget: (guildId) => { + return `/guilds/${guildId}/widget` + }, + widgetJson: (guildId) => { + return `/guilds/${guildId}/widget.json` + }, }, sticker: (stickerId: BigString) => { return `/stickers/${stickerId}` }, + regions: () => { + return '/voice/regions' + }, + // Interaction Endpoints interactions: { commands: { @@ -1235,7 +1288,6 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage ) }, - /** Modify a guild's MFA level. Requires guild ownership. */ async editGuildMfaLevel(guildId: BigString, mfaLevel: MfaLevels, reason?: string): Promise { return await rest.post(rest.routes.guilds.mfa(guildId), { level: mfaLevel, reason }) }, @@ -1309,6 +1361,14 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage return await rest.patch(rest.routes.webhooks.webhook(webhookId, token), options) }, + async editWelcomeScreen(guildId, options) { + return await rest.patch(rest.routes.guilds.welcome(guildId), options) + }, + + async editWidgetSettings(guildId, options) { + return await rest.patch(rest.routes.guilds.widget(guildId), options) + }, + async executeWebhook(webhookId, token, options) { return await rest.post(rest.routes.webhooks.webhook(webhookId, token, options), options) }, @@ -1349,6 +1409,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage return await rest.get(rest.routes.guilds.automod.rules(guildId)) }, + async getAvailableVoiceRegions() { + return await rest.get(rest.routes.regions()) + }, + async getBan(guildId, userId) { return await rest.get(rest.routes.guilds.members.ban(guildId, userId)) }, @@ -1415,6 +1479,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage return await rest.get(rest.routes.interactions.commands.guilds.all(rest.applicationId, guildId)) }, + async getGuildPreview(guildId) { + return await rest.get(rest.routes.guilds.preview(guildId)) + }, + async getGuildTemplate(templateCode) { return await rest.get(rest.routes.guilds.templates.code(templateCode)) }, @@ -1439,6 +1507,14 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage return await rest.get(rest.routes.guilds.invites(guildId)) }, + async getMessage(channelId, messageId) { + return await rest.get(rest.routes.channels.message(channelId, messageId)) + }, + + async getMessages(channelId, options) { + return await rest.get(rest.routes.channels.messages(channelId, options)) + }, + async getNitroStickerPacks() { return await rest.get(rest.routes.nitroStickerPacks()) }, @@ -1447,6 +1523,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage return await rest.get(rest.routes.interactions.responses.original(rest.applicationId, token)) }, + async getPinnedMessages(channelId) { + return await rest.get(rest.routes.channels.pins(channelId)) + }, + async getPrivateArchivedThreads(channelId, options) { return await rest.get(rest.routes.channels.threads.private(channelId, options)) }, @@ -1455,6 +1535,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage return await rest.get(rest.routes.channels.threads.joined(channelId, options)) }, + async getPruneCount(guildId, options) { + return await rest.get(rest.routes.guilds.prune(guildId, options)) + }, + async getPublicArchivedThreads(channelId, options) { return await rest.get(rest.routes.channels.threads.public(channelId, options)) }, @@ -1503,6 +1587,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage return await rest.get(rest.routes.channels.threads.members(channelId)) }, + async getReactions(channelId, messageId, reaction, options) { + return await rest.get(rest.routes.channels.reactions.message(channelId, messageId, reaction, options)) + }, + async getUser(id) { return await rest.get(rest.routes.user(id)) }, @@ -1527,6 +1615,18 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage return await rest.get(rest.routes.webhooks.webhook(webhookId, token)) }, + async getWelcomeScreen(guildId) { + return await rest.get(rest.routes.guilds.welcome(guildId)) + }, + + async getWidget(guildId) { + return await rest.get(rest.routes.guilds.widgetJson(guildId)) + }, + + async getWidgetSettings(guildId) { + return await rest.get(rest.routes.guilds.widget(guildId)) + }, + async joinThread(channelId) { return await rest.put(rest.routes.channels.threads.me(channelId)) }, @@ -1730,6 +1830,8 @@ export interface RestManager { bulk: (channelId: BigString) => string /** Route for non-specific dm channel. */ dm: () => string + /** Route for handling a channels pins. */ + pins: (channelId: BigString) => string /** Route for non-specific webhook in a channel. */ webhooks: (channelId: BigString) => string /** Route for a specific channel. */ @@ -1787,6 +1889,8 @@ export interface RestManager { all: (channelId: BigString, messageId: BigString) => string /** Route for handling all reactions for a single emoji on a message. */ emoji: (channelId: BigString, messageId: BigString, emoji: string, options?: GetReactions) => string + /** Route for handling a specific reaction on a message. */ + message: (channelId: BigString, messageId: BigString, emoji: string, options?: GetReactions) => string } } /** Routes for guild related endpoints. */ @@ -1827,8 +1931,18 @@ export interface RestManager { invite: (inviteCode: string, options?: GetInvite) => string /** Route for handling non-specific invites in a guild. */ invites: (guildId: BigString) => string + /** Route for handling a guild's preview. */ + preview: (guildId: BigString) => string + /** Route for handling pruning of a guild. */ + prune: (guildId: BigString, options?: GetGuildPruneCountQuery) => string /** Route for handling non-specific webhooks in a guild */ webhooks: (guildId: BigString) => string + /** Route for handling a guild's welcome screen. */ + welcome: (guildId: BigString) => string + /** Route for handling a guild's widget. */ + widget: (guildId: BigString) => string + /** Route for handling a guild's widget in the form of json. */ + widgetJson: (guildId: BigString) => string /** Route for handling a guilds mfa level. */ mfa: (guildId: BigString) => string /** Routes for handling a guild's members. */ @@ -1909,6 +2023,8 @@ export interface RestManager { } /** Route for handling a sticker. */ sticker: (stickerId: BigString) => string + /** Route for handling all voice regions. */ + regions: () => string } /** Check the rate limits for a url or a bucket. */ checkRateLimits: (url: string) => number | false @@ -2873,7 +2989,6 @@ export interface RestManager { /** * Edits the voice state of the bot user. * - * @param rest - The rest manager to use to make the request. * @param guildId - The ID of the guild in which to edit the voice state of the bot user. * @param options - The parameters for the edit of the voice state. * @@ -3016,6 +3131,35 @@ export interface RestManager { * @see {@link https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token} */ editWebhookWithToken: (webhookId: BigString, token: string, options: Omit) => Promise> + /** + * Edits a guild's welcome screen. + * + * @param guildId - The ID of the guild to edit the welcome screen of. + * @param options - The parameters for the edit of the welcome screen. + * @returns An instance of the edited {@link WelcomeScreen}. + * + * @remarks + * Requires the `MANAGE_GUILD` permission. + * + * Fires a _Guild Update_ gateway event. + * + * @see {@link https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen} + */ + editWelcomeScreen: (guildId: BigString, options: Camelize) => Promise> + /** + * Edits the settings of a guild's widget. + * + * @param guildId - The ID of the guild to edit the settings of the widget of. + * @returns An instance of the edited {@link GuildWidgetSettings}. + * + * @remarks + * Requires the `MANAGE_GUILD` permission. + * + * Fires a _Guild Update_ gateway event. + * + * @see {@link https://discord.com/developers/docs/resources/guild#modify-guild-widget} + */ + editWidgetSettings: (guildId: BigString, options: Camelize) => Promise> /** * Executes a webhook, causing a message to be posted in the channel configured for the webhook. * @@ -3118,6 +3262,12 @@ export interface RestManager { * @see {@link https://discord.com/developers/docs/resources/auto-moderation#list-auto-moderation-rules-for-guild} */ getAutomodRules: (guildId: BigString) => Promise> + /** + * Gets the list of available voice regions. + * + * @returns A collection of {@link VoiceRegions | VoiceRegion} objects assorted by voice region ID. + */ + getAvailableVoiceRegions: () => Promise> /** * Gets a ban by user ID. * @@ -3289,6 +3439,18 @@ export interface RestManager { * @see {@link https://discord.com/developers/docs/interactions/application-commands#get-global-application-commandss} */ getGuildApplicationCommands: (guildId: BigString) => Promise> + /** + * Gets the preview of a guild by a guild's ID. + * + * @param guildId - The ID of the guild to get the preview of. + * @returns An instance of {@link GuildPreview}. + * + * @remarks + * If the bot user is not in the guild, the guild must be lurkable. + * + * @see {@link https://discord.com/developers/docs/resources/guild#get-guild-preview} + */ + getGuildPreview: (guildId: BigString) => Promise> /** * Returns a sticker object for the given guild and sticker IDs. * @@ -3382,6 +3544,38 @@ export interface RestManager { * @see {@link https://discord.com/developers/docs/resources/invite#get-invites} */ getInvites: (guildId: BigString) => Promise> + /** + * Gets a message from a channel by the ID of the message. + * + * @param channelId - The ID of the channel from which to get the message. + * @param messageId - The ID of the message to get. + * @returns An instance of {@link Message}. + * + * @remarks + * Requires that the bot user be able to see the contents of the channel in which the message was posted. + * + * If getting a message from a guild channel: + * - Requires the `READ_MESSAGE_HISTORY` permission. + * + * @see {@link https://discord.com/developers/docs/resources/channel#get-channel-message} + */ + getMessage: (channelId: BigString, messageId: BigString) => Promise> + /** + * Gets multiple messages from a channel. + * + * @param channelId - The ID of the channel from which to get the messages. + * @param options - The parameters for the fetching of the messages. + * @returns A collection of {@link Message} objects assorted by message ID. + * + * @remarks + * Requires that the bot user be able to see the contents of the channel in which the messages were posted. + * + * If getting a messages from a guild channel: + * - Requires the `READ_MESSAGE_HISTORY` permission. + * + * @see {@link https://discord.com/developers/docs/resources/channel#get-channel-messages} + */ + getMessages: (channelId: BigString, options?: GetMessagesOptions) => Promise> /** * Returns the list of sticker packs available to Nitro subscribers. * @@ -3407,6 +3601,21 @@ export interface RestManager { * @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#get-original-interaction-response} */ getOriginalInteractionResponse: (token: string) => Promise> + /** + * Gets the pinned messages for a channel. + * + * @param channelId - The ID of the channel to get the pinned messages for. + * @returns A collection of {@link Message} objects assorted by message ID. + * + * @remarks + * Requires that the bot user be able to see the contents of the channel in which the messages were posted. + * + * If getting a message from a guild channel: + * - Requires the `READ_MESSAGE_HISTORY` permission. + * + * @see {@link https://discord.com/developers/docs/resources/channel#get-pinned-messages} + */ + getPinnedMessages: (channelId: BigString) => Promise> /** * Gets the list of private archived threads for a channel. * @@ -3442,6 +3651,19 @@ export interface RestManager { * @see {@link https://discord.com/developers/docs/resources/channel#list-joined-private-archived-threads} */ getPrivateJoinedArchivedThreads: (channelId: BigString, options?: ListArchivedThreads) => Promise> + /** + * Gets the number of members that would be kicked from a guild during pruning. + * + * @param guildId - The ID of the guild to get the prune count of. + * @param options - The parameters for the fetching of the prune count. + * @returns A number indicating the number of members that would be kicked. + * + * @remarks + * Requires the `KICK_MEMBERS` permission. + * + * @see {@link https://discord.com/developers/docs/resources/guild#get-guild-prune-count} + */ + getPruneCount: (guildId: BigString, options?: GetGuildPruneCountQuery) => Promise> /** * Gets the list of public archived threads for a channel. * @@ -3555,6 +3777,18 @@ export interface RestManager { * @see {@link https://discord.com/developers/docs/resources/channel#list-thread-members} */ getThreadMembers: (channelId: BigString) => Promise> + /** + * Gets the list of users that reacted with an emoji to a message. + * + * @param channelId - The ID of the channel the message to get the users for is in. + * @param messageId - The ID of the message to get the users for. + * @param reaction - The reaction for which to get the users. + * @param options - The parameters for the fetching of the users. + * @returns A collection of {@link User} objects assorted by user ID. + * + * @see {@link https://discord.com/developers/docs/resources/channel#get-reactions} + */ + getReactions: (channelId: BigString, messageId: BigString, reaction: string, options?: GetReactions) => Promise> /** * Get a user's data from the api * @@ -3624,6 +3858,40 @@ export interface RestManager { * @see {@link https://discord.com/developers/docs/resources/webhook#get-webhook-with-token} */ getWebhookWithToken: (webhookId: BigString, token: string) => Promise> + /** + * Gets the welcome screen for a guild. + * + * @param guildId - The ID of the guild to get the welcome screen for. + * @returns An instance of {@link WelcomeScreen}. + * + * @remarks + * If the welcome screen is not enabled: + * - Requires the `MANAGE_GUILD` permission. + * + * @see {@link https://discord.com/developers/docs/resources/guild#get-guild-welcome-screen} + */ + getWelcomeScreen: (guildId: BigString) => Promise> + /** + * Gets the guild widget by guild ID. + * + * @param guildId - The ID of the guild to get the widget of. + * @returns An instance of {@link GuildWidget}. + * + * @see {@link https://discord.com/developers/docs/resources/guild#get-guild-widget} + */ + getWidget: (guildId: BigString) => Promise> + /** + * Gets the settings of a guild's widget. + * + * @param guildId - The ID of the guild to get the widget of. + * @returns An instance of {@link GuildWidgetSettings}. + * + * @remarks + * Requires the `MANAGE_GUILD` permission. + * + * @see {@link https://discord.com/developers/docs/resources/guild#get-guild-widget-settings} + */ + getWidgetSettings: (guildId: BigString) => Promise> /** * Adds the bot user to a thread. * diff --git a/packages/types/src/discord.ts b/packages/types/src/discord.ts index 3886bd149..62aad3514 100644 --- a/packages/types/src/discord.ts +++ b/packages/types/src/discord.ts @@ -1964,26 +1964,26 @@ export interface DiscordApplicationCommandPermissions { permission: boolean } -// /** https://discord.com/developers/docs/resources/guild#get-guild-widget-example-get-guild-widget */ -// export interface DiscordGuildWidget { -// id: string -// name: string -// instant_invite: string -// channels: Array<{ -// id: string -// name: string -// position: number -// }> -// members: Array<{ -// id: string -// username: string -// discriminator: string -// avatar?: string | null -// status: string -// avatar_url: string -// }> -// presence_count: number -// } +/** https://discord.com/developers/docs/resources/guild#get-guild-widget-example-get-guild-widget */ +export interface DiscordGuildWidget { + id: string + name: string + instant_invite: string + channels: Array<{ + id: string + name: string + position: number + }> + members: Array<{ + id: string + username: string + discriminator: string + avatar?: string | null + status: string + avatar_url: string + }> + presence_count: number +} /** https://discord.com/developers/docs/resources/guild#guild-preview-object */ export interface DiscordGuildPreview { @@ -2467,12 +2467,12 @@ export interface DiscordVoiceRegion { custom: boolean } -// export interface DiscordGuildWidgetSettings { -// /** whether the widget is enabled */ -// enabled: boolean -// /** the widget channel id */ -// channel_id: string | null -// } +export interface DiscordGuildWidgetSettings { + /** whether the widget is enabled */ + enabled: boolean + /** the widget channel id */ + channel_id: string | null +} export interface DiscordInstallParams { /** the scopes to add the application to the server with */ @@ -2886,15 +2886,15 @@ export interface DiscordCreateMessage { // level: MfaLevels // } -// /** https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen */ -// export interface DiscordModifyGuildWelcomeScreen { -// /** Whether the welcome screen is enabled */ -// enabled?: boolean | null -// /** Channels linked in the welcome screen and their display options */ -// welcome_screen?: DiscordWelcomeScreenChannel[] | null -// /** The server description to show in the welcome screen */ -// description?: string | null -// } +/** https://discord.com/developers/docs/resources/guild#modify-guild-welcome-screen */ +export interface DiscordModifyGuildWelcomeScreen { + /** Whether the welcome screen is enabled */ + enabled?: boolean | null + /** Channels linked in the welcome screen and their display options */ + welcome_screen?: DiscordWelcomeScreenChannel[] | null + /** The server description to show in the welcome screen */ + description?: string | null +} // export interface DiscordStartThreadWithMessage { // /** 1-100 character thread name */ @@ -3147,4 +3147,8 @@ export interface DiscordActiveThreads { export interface DiscordVanityUrl { code: string | null uses: number +} + +export interface DiscordPrunedCount { + pruned: number } \ No newline at end of file diff --git a/packages/types/src/discordeno.ts b/packages/types/src/discordeno.ts index 92adb2432..bdc444fa7 100644 --- a/packages/types/src/discordeno.ts +++ b/packages/types/src/discordeno.ts @@ -1087,3 +1087,17 @@ export interface EditUserVoiceState { /** The user id to target */ userId: BigString } + +/** https://discord.com/developers/docs/resources/guild#get-guild-widget-image-query-string-params */ +export interface GetGuildWidgetImageQuery { + /** + * Style of the widget returned, default: shield + * + * Shield: Widget with Discord icon and guild members online count. + * Banner1: Large image with guild icon, name and online count. "POWERED BY DISCORD" as the footer of the widget + * Banner2: Smaller widget style with guild icon, name and online count. Split on the right with Discord logo + * Banner3: Large image with guild icon, name and online count. In the footer, Discord logo on the left and "Chat Now" on the right + * Banner4: 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 + */ + style?: 'shield' | 'banner1' | 'banner2' | 'banner3' | 'banner4' +} diff --git a/packages/utils/src/images.ts b/packages/utils/src/images.ts index afde2afb1..830fb083c 100644 --- a/packages/utils/src/images.ts +++ b/packages/utils/src/images.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/restrict-template-expressions */ -import type { BigString, ImageFormat, ImageSize } from '@discordeno/types' +import type { BigString, GetGuildWidgetImageQuery, ImageFormat, ImageSize } from '@discordeno/types' import { iconBigintToHash } from './hash.js' /** Help format an image url. */ @@ -120,3 +120,23 @@ export function guildSplashUrl( ) : undefined } + +/** + * Builds a URL to the guild widget image stored in the Discord CDN. + * + * @param guildId - The ID of the guild to get the link to the widget image for. + * @param options - The parameters for the building of the URL. + * @returns The link to the resource. + */ +export function getWidgetImageUrl ( + guildId: BigString, + options?: GetGuildWidgetImageQuery +): string { + let url = `https://cdn.discordapp.com/guilds/${guildId}/widget.png` + + if (options?.style) { + url += `?style=${options.style}` + } + + return url +}