diff --git a/deno/gateway/v10.ts b/deno/gateway/v10.ts index c4e3eb65..ce627507 100644 --- a/deno/gateway/v10.ts +++ b/deno/gateway/v10.ts @@ -203,6 +203,8 @@ export enum GatewayIntentBits { GuildScheduledEvents = 1 << 16, AutoModerationConfiguration = 1 << 20, AutoModerationExecution = 1 << 21, + GuildMessagePolls = 1 << 24, + DirectMessagePolls = 1 << 25, } /** @@ -260,6 +262,8 @@ export enum GatewayDispatchEvents { VoiceServerUpdate = 'VOICE_SERVER_UPDATE', VoiceStateUpdate = 'VOICE_STATE_UPDATE', WebhooksUpdate = 'WEBHOOKS_UPDATE', + MessagePollVoteAdd = 'MESSAGE_POLL_VOTE_ADD', + MessagePollVoteRemove = 'MESSAGE_POLL_VOTE_REMOVE', GuildScheduledEventCreate = 'GUILD_SCHEDULED_EVENT_CREATE', GuildScheduledEventUpdate = 'GUILD_SCHEDULED_EVENT_UPDATE', GuildScheduledEventDelete = 'GUILD_SCHEDULED_EVENT_DELETE', @@ -328,6 +332,8 @@ export type GatewayDispatchPayload = | GatewayMessageCreateDispatch | GatewayMessageDeleteBulkDispatch | GatewayMessageDeleteDispatch + | GatewayMessagePollVoteAddDispatch + | GatewayMessagePollVoteRemoveDispatch | GatewayMessageReactionAddDispatch | GatewayMessageReactionRemoveAllDispatch | GatewayMessageReactionRemoveDispatch @@ -1813,6 +1819,45 @@ export interface GatewayGuildAuditLogEntryCreateDispatchData extends APIAuditLog guild_id: Snowflake; } +/** + * https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-add + */ +export type GatewayMessagePollVoteAddDispatch = DataPayload< + GatewayDispatchEvents.MessagePollVoteAdd, + GatewayMessagePollVoteDispatchData +>; + +/** + * https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-remove + */ +export type GatewayMessagePollVoteRemoveDispatch = DataPayload< + GatewayDispatchEvents.MessagePollVoteRemove, + GatewayMessagePollVoteDispatchData +>; + +export interface GatewayMessagePollVoteDispatchData { + /** + * ID of the user + */ + user_id: Snowflake; + /** + * ID of the channel + */ + channel_id: Snowflake; + /** + * ID of the message + */ + message_id: Snowflake; + /** + * ID of the guild + */ + guild_id?: Snowflake; + /** + * ID of the answer + */ + answer_id: number; +} + // #endregion Dispatch Payloads // #region Sendable Payloads diff --git a/deno/gateway/v9.ts b/deno/gateway/v9.ts index b13baa24..809b8719 100644 --- a/deno/gateway/v9.ts +++ b/deno/gateway/v9.ts @@ -202,6 +202,8 @@ export enum GatewayIntentBits { GuildScheduledEvents = 1 << 16, AutoModerationConfiguration = 1 << 20, AutoModerationExecution = 1 << 21, + GuildMessagePolls = 1 << 24, + DirectMessagePolls = 1 << 25, } /** @@ -259,6 +261,8 @@ export enum GatewayDispatchEvents { VoiceServerUpdate = 'VOICE_SERVER_UPDATE', VoiceStateUpdate = 'VOICE_STATE_UPDATE', WebhooksUpdate = 'WEBHOOKS_UPDATE', + MessagePollVoteAdd = 'MESSAGE_POLL_VOTE_ADD', + MessagePollVoteRemove = 'MESSAGE_POLL_VOTE_REMOVE', GuildScheduledEventCreate = 'GUILD_SCHEDULED_EVENT_CREATE', GuildScheduledEventUpdate = 'GUILD_SCHEDULED_EVENT_UPDATE', GuildScheduledEventDelete = 'GUILD_SCHEDULED_EVENT_DELETE', @@ -327,6 +331,8 @@ export type GatewayDispatchPayload = | GatewayMessageCreateDispatch | GatewayMessageDeleteBulkDispatch | GatewayMessageDeleteDispatch + | GatewayMessagePollVoteAddDispatch + | GatewayMessagePollVoteRemoveDispatch | GatewayMessageReactionAddDispatch | GatewayMessageReactionRemoveAllDispatch | GatewayMessageReactionRemoveDispatch @@ -1812,6 +1818,45 @@ export interface GatewayGuildAuditLogEntryCreateDispatchData extends APIAuditLog guild_id: Snowflake; } +/** + * https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-add + */ +export type GatewayMessagePollVoteAddDispatch = DataPayload< + GatewayDispatchEvents.MessagePollVoteAdd, + GatewayMessagePollVoteDispatchData +>; + +/** + * https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-remove + */ +export type GatewayMessagePollVoteRemoveDispatch = DataPayload< + GatewayDispatchEvents.MessagePollVoteRemove, + GatewayMessagePollVoteDispatchData +>; + +export interface GatewayMessagePollVoteDispatchData { + /** + * ID of the user + */ + user_id: Snowflake; + /** + * ID of the channel + */ + channel_id: Snowflake; + /** + * ID of the message + */ + message_id: Snowflake; + /** + * ID of the guild + */ + guild_id?: Snowflake; + /** + * ID of the answer + */ + answer_id: number; +} + // #endregion Dispatch Payloads // #region Sendable Payloads diff --git a/deno/payloads/common.ts b/deno/payloads/common.ts index af098d53..b640d13b 100644 --- a/deno/payloads/common.ts +++ b/deno/payloads/common.ts @@ -276,6 +276,12 @@ export const PermissionFlagsBits = { * Applies to channel types: Text, Voice, Stage */ SendVoiceMessages: 1n << 46n, + /** + * Allows sending polls + * + * Applies to channel types: Text, Voice, Stage + */ + SendPolls: 1n << 49n, } as const; /** diff --git a/deno/payloads/v10/channel.ts b/deno/payloads/v10/channel.ts index a30379b0..83d10d2b 100644 --- a/deno/payloads/v10/channel.ts +++ b/deno/payloads/v10/channel.ts @@ -8,6 +8,7 @@ import type { APIPartialEmoji } from './emoji.ts'; import type { APIGuildMember } from './guild.ts'; import type { APIInteractionDataResolved, APIMessageInteraction } from './interactions.ts'; import type { APIRole } from './permissions.ts'; +import type { APIPoll } from './poll.ts'; import type { APISticker, APIStickerItem } from './sticker.ts'; import type { APIUser } from './user.ts'; @@ -715,6 +716,17 @@ export interface APIMessage { * See https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure */ resolved?: APIInteractionDataResolved; + /** + * A poll! + * + * The `MESSAGE_CONTENT` privileged gateway intent is required for verified applications to receive a non-empty value from this field + * + * In the Discord Developers Portal, you need to enable the toggle of this intent of your application in **Bot > Privileged Gateway Intents**. + * You also need to specify the intent bit value (`1 << 15`) if you are connecting to the gateway + * + * See https://support-dev.discord.com/hc/articles/4404772028055 + */ + poll?: APIPoll; } /** diff --git a/deno/payloads/v10/mod.ts b/deno/payloads/v10/mod.ts index 06f590de..fb0b509f 100644 --- a/deno/payloads/v10/mod.ts +++ b/deno/payloads/v10/mod.ts @@ -10,6 +10,7 @@ export * from './guildScheduledEvent.ts'; export * from './interactions.ts'; export * from './invite.ts'; export * from './oauth2.ts'; +export * from './poll.ts'; export * from './permissions.ts'; export * from './stageInstance.ts'; export * from './sticker.ts'; diff --git a/deno/payloads/v10/poll.ts b/deno/payloads/v10/poll.ts new file mode 100644 index 00000000..ee2b4963 --- /dev/null +++ b/deno/payloads/v10/poll.ts @@ -0,0 +1,107 @@ +/** + * Types extracted from https://discord.com/developers/docs/resources/poll + */ + +import type { APIPartialEmoji } from './emoji.ts'; + +/** + * https://discord.com/developers/docs/resources/poll#poll-object-poll-object-structure + */ +export interface APIPoll { + /** + * The question of the poll + */ + question: APIPollMedia; + /** + * Each of the answers available in the poll, up to 10 + */ + answers: APIPollAnswer[]; + /** + * The time when the poll ends (IS08601 timestamp) + */ + expiry: string; + /** + * Whether a user can select multiple answers + */ + allow_multiselect: boolean; + /** + * The layout type of the poll + */ + layout_type: PollLayoutType; + /** + * The results of the poll + */ + results?: APIPollResults; +} + +/** + * https://discord.com/developers/docs/resources/poll#layout-type + */ +export enum PollLayoutType { + /** + * The, uhm, default layout type + */ + Default = 1, +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-media-object-poll-media-object-structure + */ +export interface APIPollMedia { + /** + * The text of the field + * + * The maximum length is `300` for the question, and `55` for any answer + */ + text?: string; + /** + * The emoji of the field + */ + emoji?: APIPartialEmoji; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-answer-object-poll-answer-object-structure + */ +export interface APIPollAnswer { + /** + * The ID of the answer. Starts at `1` for the first answer and goes up sequentially + */ + answer_id: number; + /** + * The data of the answer + */ + media: APIPollMedia; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-results-object-poll-results-object-structure + */ +export interface APIPollResults { + /** + * Whether the votes have been precisely counted + */ + is_finalized: boolean; + /** + * The counts for each answer + */ + answer_counts: APIPollAnswerCount[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-results-object-poll-answer-count-object-structure + */ +export interface APIPollAnswerCount { + /** + * The `answer_id` + */ + id: number; + /** + * The number of votes for this answer + */ + count: number; + /** + * Whether the current user voted for this answer + */ + me_voted: boolean; +} diff --git a/deno/payloads/v9/channel.ts b/deno/payloads/v9/channel.ts index b3ee6c1b..d0efc933 100644 --- a/deno/payloads/v9/channel.ts +++ b/deno/payloads/v9/channel.ts @@ -8,6 +8,7 @@ import type { APIPartialEmoji } from './emoji.ts'; import type { APIGuildMember } from './guild.ts'; import type { APIInteractionDataResolved, APIMessageInteraction } from './interactions.ts'; import type { APIRole } from './permissions.ts'; +import type { APIPoll } from './poll.ts'; import type { APISticker, APIStickerItem } from './sticker.ts'; import type { APIUser } from './user.ts'; @@ -702,6 +703,17 @@ export interface APIMessage { * See https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure */ resolved?: APIInteractionDataResolved; + /** + * A poll! + * + * The `MESSAGE_CONTENT` privileged gateway intent is required for verified applications to receive a non-empty value from this field + * + * In the Discord Developers Portal, you need to enable the toggle of this intent of your application in **Bot > Privileged Gateway Intents**. + * You also need to specify the intent bit value (`1 << 15`) if you are connecting to the gateway + * + * See https://support-dev.discord.com/hc/articles/4404772028055 + */ + poll?: APIPoll; } /** diff --git a/deno/payloads/v9/mod.ts b/deno/payloads/v9/mod.ts index 06f590de..fb0b509f 100644 --- a/deno/payloads/v9/mod.ts +++ b/deno/payloads/v9/mod.ts @@ -10,6 +10,7 @@ export * from './guildScheduledEvent.ts'; export * from './interactions.ts'; export * from './invite.ts'; export * from './oauth2.ts'; +export * from './poll.ts'; export * from './permissions.ts'; export * from './stageInstance.ts'; export * from './sticker.ts'; diff --git a/deno/payloads/v9/poll.ts b/deno/payloads/v9/poll.ts new file mode 100644 index 00000000..ee2b4963 --- /dev/null +++ b/deno/payloads/v9/poll.ts @@ -0,0 +1,107 @@ +/** + * Types extracted from https://discord.com/developers/docs/resources/poll + */ + +import type { APIPartialEmoji } from './emoji.ts'; + +/** + * https://discord.com/developers/docs/resources/poll#poll-object-poll-object-structure + */ +export interface APIPoll { + /** + * The question of the poll + */ + question: APIPollMedia; + /** + * Each of the answers available in the poll, up to 10 + */ + answers: APIPollAnswer[]; + /** + * The time when the poll ends (IS08601 timestamp) + */ + expiry: string; + /** + * Whether a user can select multiple answers + */ + allow_multiselect: boolean; + /** + * The layout type of the poll + */ + layout_type: PollLayoutType; + /** + * The results of the poll + */ + results?: APIPollResults; +} + +/** + * https://discord.com/developers/docs/resources/poll#layout-type + */ +export enum PollLayoutType { + /** + * The, uhm, default layout type + */ + Default = 1, +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-media-object-poll-media-object-structure + */ +export interface APIPollMedia { + /** + * The text of the field + * + * The maximum length is `300` for the question, and `55` for any answer + */ + text?: string; + /** + * The emoji of the field + */ + emoji?: APIPartialEmoji; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-answer-object-poll-answer-object-structure + */ +export interface APIPollAnswer { + /** + * The ID of the answer. Starts at `1` for the first answer and goes up sequentially + */ + answer_id: number; + /** + * The data of the answer + */ + media: APIPollMedia; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-results-object-poll-results-object-structure + */ +export interface APIPollResults { + /** + * Whether the votes have been precisely counted + */ + is_finalized: boolean; + /** + * The counts for each answer + */ + answer_counts: APIPollAnswerCount[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-results-object-poll-answer-count-object-structure + */ +export interface APIPollAnswerCount { + /** + * The `answer_id` + */ + id: number; + /** + * The number of votes for this answer + */ + count: number; + /** + * Whether the current user voted for this answer + */ + me_voted: boolean; +} diff --git a/deno/rest/common.ts b/deno/rest/common.ts index 00f9a861..184d5dcf 100644 --- a/deno/rest/common.ts +++ b/deno/rest/common.ts @@ -295,6 +295,14 @@ export enum RESTJSONErrorCodes { CannotUpdateOnboardingWhileBelowRequirements, FailedToBanUsers = 500_000, + + PollVotingBlocked = 520_000, + PollExpired, + InvalidChannelTypeForPollCreation, + CannotEditAPollMessage, + CannotUseAnEmojiIncludedWithThePoll, + + CannotExpireANonPollMessage = 520_006, } /** diff --git a/deno/rest/v10/channel.ts b/deno/rest/v10/channel.ts index ad4a4099..4e1416f6 100644 --- a/deno/rest/v10/channel.ts +++ b/deno/rest/v10/channel.ts @@ -26,6 +26,7 @@ import type { ChannelFlags, } from '../../payloads/v10/mod.ts'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, StrictPartial } from '../../utils/internals.ts'; +import type { RESTAPIPollCreate } from './poll.ts'; export interface APIChannelPatchOverwrite extends RESTPutAPIChannelPermissionJSONBody { id: Snowflake; @@ -325,6 +326,10 @@ export interface RESTPostAPIChannelMessageJSONBody { * If another message was created by the same author with the same nonce, that message will be returned and no new message will be created. */ enforce_nonce?: boolean | undefined; + /** + * A poll! + */ + poll?: RESTAPIPollCreate | undefined; } /** diff --git a/deno/rest/v10/mod.ts b/deno/rest/v10/mod.ts index c4bf0989..1120ff80 100644 --- a/deno/rest/v10/mod.ts +++ b/deno/rest/v10/mod.ts @@ -12,6 +12,7 @@ export * from './guildScheduledEvent.ts'; export * from './interactions.ts'; export * from './invite.ts'; export * from './oauth2.ts'; +export * from './poll.ts'; export * from './stageInstance.ts'; export * from './sticker.ts'; export * from './template.ts'; @@ -461,6 +462,22 @@ export const Routes = { return `/guilds/${guildId}/templates/${code}` as const; }, + /** + * Route for: + * - GET `/channels/{channel.id}/polls/{message.id}/answers/{answer_id}` + */ + pollAnswerVoters(channelId: Snowflake, messageId: Snowflake, answerId: number) { + return `/channels/${channelId}/polls/${messageId}/answers/${answerId}` as const; + }, + + /** + * Route for: + * - POST `/channels/{channel.id}/polls/{message.id}/expire` + */ + expirePoll(channelId: Snowflake, messageId: Snowflake) { + return `/channels/${channelId}/polls/${messageId}/expire` as const; + }, + /** * Route for: * - POST `/channels/{channel.id}/threads` diff --git a/deno/rest/v10/poll.ts b/deno/rest/v10/poll.ts new file mode 100644 index 00000000..fcc52d43 --- /dev/null +++ b/deno/rest/v10/poll.ts @@ -0,0 +1,47 @@ +import type { Snowflake } from '../../globals.ts'; +import type { APIMessage, APIPoll, APIPollAnswer, APIUser } from '../../v10.ts'; + +/** + * https://discord.com/developers/docs/resources/poll#get-answer-voters + */ +export interface RESTGetAPIPollAnswerVotersQuery { + /** + * Get users after this user ID + */ + after?: Snowflake; + /** + * Max number of users to return (1-100) + * + * @default 25 + */ + limit?: number; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-create-request-object-poll-create-request-object-structure + */ +export interface RESTAPIPollCreate extends Omit { + /** + * Number of hours the poll should be open for, up to 7 days + */ + duration: number; + /** + * Each of the answers available in the poll, up to 10 + */ + answers: Omit[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#get-answer-voters + */ +export interface RESTGetAPIPollAnswerVotersResult { + /** + * Users who voted for this answer + */ + users: APIUser[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#expire-poll + */ +export type RESTPostAPIPollExpireResult = APIMessage; diff --git a/deno/rest/v10/webhook.ts b/deno/rest/v10/webhook.ts index 561e4407..683b5cb4 100644 --- a/deno/rest/v10/webhook.ts +++ b/deno/rest/v10/webhook.ts @@ -10,6 +10,7 @@ import type { } from '../../payloads/v10/mod.ts'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, Nullable } from '../../utils/internals.ts'; import type { RESTAPIAttachment } from './channel.ts'; +import type { RESTAPIPollCreate } from './poll.ts'; /** * https://discord.com/developers/docs/resources/webhook#create-webhook */ @@ -154,6 +155,10 @@ export interface RESTPostAPIWebhookWithTokenJSONBody { * Array of tag ids to apply to the thread */ applied_tags?: Snowflake[] | undefined; + /** + * A poll! + */ + poll?: RESTAPIPollCreate | undefined; } /** diff --git a/deno/rest/v9/channel.ts b/deno/rest/v9/channel.ts index 8b863554..33e8c8c3 100644 --- a/deno/rest/v9/channel.ts +++ b/deno/rest/v9/channel.ts @@ -26,6 +26,7 @@ import type { ChannelFlags, } from '../../payloads/v9/mod.ts'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, StrictPartial } from '../../utils/internals.ts'; +import type { RESTAPIPollCreate } from './poll.ts'; export interface APIChannelPatchOverwrite extends RESTPutAPIChannelPermissionJSONBody { id: Snowflake; @@ -333,6 +334,10 @@ export interface RESTPostAPIChannelMessageJSONBody { * If another message was created by the same author with the same nonce, that message will be returned and no new message will be created. */ enforce_nonce?: boolean | undefined; + /** + * A poll! + */ + poll?: RESTAPIPollCreate | undefined; } /** diff --git a/deno/rest/v9/mod.ts b/deno/rest/v9/mod.ts index 04072a11..e5a50d3b 100644 --- a/deno/rest/v9/mod.ts +++ b/deno/rest/v9/mod.ts @@ -12,6 +12,7 @@ export * from './guildScheduledEvent.ts'; export * from './interactions.ts'; export * from './invite.ts'; export * from './oauth2.ts'; +export * from './poll.ts'; export * from './stageInstance.ts'; export * from './sticker.ts'; export * from './template.ts'; @@ -461,6 +462,22 @@ export const Routes = { return `/guilds/${guildId}/templates/${code}` as const; }, + /** + * Route for: + * - GET `/channels/{channel.id}/polls/{message.id}/answers/{answer_id}` + */ + pollAnswerVoters(channelId: Snowflake, messageId: Snowflake, answerId: number) { + return `/channels/${channelId}/polls/${messageId}/answers/${answerId}` as const; + }, + + /** + * Route for: + * - POST `/channels/{channel.id}/polls/{message.id}/expire` + */ + expirePoll(channelId: Snowflake, messageId: Snowflake) { + return `/channels/${channelId}/polls/${messageId}/expire` as const; + }, + /** * Route for: * - POST `/channels/{channel.id}/threads` diff --git a/deno/rest/v9/poll.ts b/deno/rest/v9/poll.ts new file mode 100644 index 00000000..850e39e2 --- /dev/null +++ b/deno/rest/v9/poll.ts @@ -0,0 +1,47 @@ +import type { Snowflake } from '../../globals.ts'; +import type { APIMessage, APIPoll, APIPollAnswer, APIUser } from '../../v9.ts'; + +/** + * https://discord.com/developers/docs/resources/poll#get-answer-voters + */ +export interface RESTGetAPIPollAnswerVotersQuery { + /** + * Get users after this user ID + */ + after?: Snowflake; + /** + * Max number of users to return (1-100) + * + * @default 25 + */ + limit?: number; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-create-request-object-poll-create-request-object-structure + */ +export interface RESTAPIPollCreate extends Omit { + /** + * Number of hours the poll should be open for, up to 7 days + */ + duration: number; + /** + * Each of the answers available in the poll, up to 10 + */ + answers: Omit[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#get-answer-voters + */ +export interface RESTGetAPIPollAnswerVotersResult { + /** + * Users who voted for this answer + */ + users: APIUser[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#expire-poll + */ +export type RESTPostAPIPollExpireResult = APIMessage; diff --git a/deno/rest/v9/webhook.ts b/deno/rest/v9/webhook.ts index b2081ce6..5d82b61c 100644 --- a/deno/rest/v9/webhook.ts +++ b/deno/rest/v9/webhook.ts @@ -10,6 +10,7 @@ import type { } from '../../payloads/v9/mod.ts'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, Nullable } from '../../utils/internals.ts'; import type { RESTAPIAttachment } from './channel.ts'; +import type { RESTAPIPollCreate } from './poll.ts'; /** * https://discord.com/developers/docs/resources/webhook#create-webhook */ @@ -154,6 +155,10 @@ export interface RESTPostAPIWebhookWithTokenJSONBody { * Array of tag ids to apply to the thread */ applied_tags?: Snowflake[] | undefined; + /** + * A poll! + */ + poll?: RESTAPIPollCreate | undefined; } /** diff --git a/gateway/v10.ts b/gateway/v10.ts index 14500cfe..41cbf1e5 100644 --- a/gateway/v10.ts +++ b/gateway/v10.ts @@ -203,6 +203,8 @@ export enum GatewayIntentBits { GuildScheduledEvents = 1 << 16, AutoModerationConfiguration = 1 << 20, AutoModerationExecution = 1 << 21, + GuildMessagePolls = 1 << 24, + DirectMessagePolls = 1 << 25, } /** @@ -260,6 +262,8 @@ export enum GatewayDispatchEvents { VoiceServerUpdate = 'VOICE_SERVER_UPDATE', VoiceStateUpdate = 'VOICE_STATE_UPDATE', WebhooksUpdate = 'WEBHOOKS_UPDATE', + MessagePollVoteAdd = 'MESSAGE_POLL_VOTE_ADD', + MessagePollVoteRemove = 'MESSAGE_POLL_VOTE_REMOVE', GuildScheduledEventCreate = 'GUILD_SCHEDULED_EVENT_CREATE', GuildScheduledEventUpdate = 'GUILD_SCHEDULED_EVENT_UPDATE', GuildScheduledEventDelete = 'GUILD_SCHEDULED_EVENT_DELETE', @@ -328,6 +332,8 @@ export type GatewayDispatchPayload = | GatewayMessageCreateDispatch | GatewayMessageDeleteBulkDispatch | GatewayMessageDeleteDispatch + | GatewayMessagePollVoteAddDispatch + | GatewayMessagePollVoteRemoveDispatch | GatewayMessageReactionAddDispatch | GatewayMessageReactionRemoveAllDispatch | GatewayMessageReactionRemoveDispatch @@ -1813,6 +1819,45 @@ export interface GatewayGuildAuditLogEntryCreateDispatchData extends APIAuditLog guild_id: Snowflake; } +/** + * https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-add + */ +export type GatewayMessagePollVoteAddDispatch = DataPayload< + GatewayDispatchEvents.MessagePollVoteAdd, + GatewayMessagePollVoteDispatchData +>; + +/** + * https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-remove + */ +export type GatewayMessagePollVoteRemoveDispatch = DataPayload< + GatewayDispatchEvents.MessagePollVoteRemove, + GatewayMessagePollVoteDispatchData +>; + +export interface GatewayMessagePollVoteDispatchData { + /** + * ID of the user + */ + user_id: Snowflake; + /** + * ID of the channel + */ + channel_id: Snowflake; + /** + * ID of the message + */ + message_id: Snowflake; + /** + * ID of the guild + */ + guild_id?: Snowflake; + /** + * ID of the answer + */ + answer_id: number; +} + // #endregion Dispatch Payloads // #region Sendable Payloads diff --git a/gateway/v9.ts b/gateway/v9.ts index e66a6c88..4496b041 100644 --- a/gateway/v9.ts +++ b/gateway/v9.ts @@ -202,6 +202,8 @@ export enum GatewayIntentBits { GuildScheduledEvents = 1 << 16, AutoModerationConfiguration = 1 << 20, AutoModerationExecution = 1 << 21, + GuildMessagePolls = 1 << 24, + DirectMessagePolls = 1 << 25, } /** @@ -259,6 +261,8 @@ export enum GatewayDispatchEvents { VoiceServerUpdate = 'VOICE_SERVER_UPDATE', VoiceStateUpdate = 'VOICE_STATE_UPDATE', WebhooksUpdate = 'WEBHOOKS_UPDATE', + MessagePollVoteAdd = 'MESSAGE_POLL_VOTE_ADD', + MessagePollVoteRemove = 'MESSAGE_POLL_VOTE_REMOVE', GuildScheduledEventCreate = 'GUILD_SCHEDULED_EVENT_CREATE', GuildScheduledEventUpdate = 'GUILD_SCHEDULED_EVENT_UPDATE', GuildScheduledEventDelete = 'GUILD_SCHEDULED_EVENT_DELETE', @@ -327,6 +331,8 @@ export type GatewayDispatchPayload = | GatewayMessageCreateDispatch | GatewayMessageDeleteBulkDispatch | GatewayMessageDeleteDispatch + | GatewayMessagePollVoteAddDispatch + | GatewayMessagePollVoteRemoveDispatch | GatewayMessageReactionAddDispatch | GatewayMessageReactionRemoveAllDispatch | GatewayMessageReactionRemoveDispatch @@ -1812,6 +1818,45 @@ export interface GatewayGuildAuditLogEntryCreateDispatchData extends APIAuditLog guild_id: Snowflake; } +/** + * https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-add + */ +export type GatewayMessagePollVoteAddDispatch = DataPayload< + GatewayDispatchEvents.MessagePollVoteAdd, + GatewayMessagePollVoteDispatchData +>; + +/** + * https://discord.com/developers/docs/topics/gateway-events#message-poll-vote-remove + */ +export type GatewayMessagePollVoteRemoveDispatch = DataPayload< + GatewayDispatchEvents.MessagePollVoteRemove, + GatewayMessagePollVoteDispatchData +>; + +export interface GatewayMessagePollVoteDispatchData { + /** + * ID of the user + */ + user_id: Snowflake; + /** + * ID of the channel + */ + channel_id: Snowflake; + /** + * ID of the message + */ + message_id: Snowflake; + /** + * ID of the guild + */ + guild_id?: Snowflake; + /** + * ID of the answer + */ + answer_id: number; +} + // #endregion Dispatch Payloads // #region Sendable Payloads diff --git a/payloads/common.ts b/payloads/common.ts index 6d5b31a5..d8684ee5 100644 --- a/payloads/common.ts +++ b/payloads/common.ts @@ -276,6 +276,12 @@ export const PermissionFlagsBits = { * Applies to channel types: Text, Voice, Stage */ SendVoiceMessages: 1n << 46n, + /** + * Allows sending polls + * + * Applies to channel types: Text, Voice, Stage + */ + SendPolls: 1n << 49n, } as const; /** diff --git a/payloads/v10/channel.ts b/payloads/v10/channel.ts index 71246755..9dddd7b3 100644 --- a/payloads/v10/channel.ts +++ b/payloads/v10/channel.ts @@ -8,6 +8,7 @@ import type { APIPartialEmoji } from './emoji'; import type { APIGuildMember } from './guild'; import type { APIInteractionDataResolved, APIMessageInteraction } from './interactions'; import type { APIRole } from './permissions'; +import type { APIPoll } from './poll'; import type { APISticker, APIStickerItem } from './sticker'; import type { APIUser } from './user'; @@ -715,6 +716,17 @@ export interface APIMessage { * See https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure */ resolved?: APIInteractionDataResolved; + /** + * A poll! + * + * The `MESSAGE_CONTENT` privileged gateway intent is required for verified applications to receive a non-empty value from this field + * + * In the Discord Developers Portal, you need to enable the toggle of this intent of your application in **Bot > Privileged Gateway Intents**. + * You also need to specify the intent bit value (`1 << 15`) if you are connecting to the gateway + * + * See https://support-dev.discord.com/hc/articles/4404772028055 + */ + poll?: APIPoll; } /** diff --git a/payloads/v10/index.ts b/payloads/v10/index.ts index 0a13eeeb..a30701a6 100644 --- a/payloads/v10/index.ts +++ b/payloads/v10/index.ts @@ -10,6 +10,7 @@ export * from './guildScheduledEvent'; export * from './interactions'; export * from './invite'; export * from './oauth2'; +export * from './poll'; export * from './permissions'; export * from './stageInstance'; export * from './sticker'; diff --git a/payloads/v10/poll.ts b/payloads/v10/poll.ts new file mode 100644 index 00000000..c597214d --- /dev/null +++ b/payloads/v10/poll.ts @@ -0,0 +1,107 @@ +/** + * Types extracted from https://discord.com/developers/docs/resources/poll + */ + +import type { APIPartialEmoji } from './emoji'; + +/** + * https://discord.com/developers/docs/resources/poll#poll-object-poll-object-structure + */ +export interface APIPoll { + /** + * The question of the poll + */ + question: APIPollMedia; + /** + * Each of the answers available in the poll, up to 10 + */ + answers: APIPollAnswer[]; + /** + * The time when the poll ends (IS08601 timestamp) + */ + expiry: string; + /** + * Whether a user can select multiple answers + */ + allow_multiselect: boolean; + /** + * The layout type of the poll + */ + layout_type: PollLayoutType; + /** + * The results of the poll + */ + results?: APIPollResults; +} + +/** + * https://discord.com/developers/docs/resources/poll#layout-type + */ +export enum PollLayoutType { + /** + * The, uhm, default layout type + */ + Default = 1, +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-media-object-poll-media-object-structure + */ +export interface APIPollMedia { + /** + * The text of the field + * + * The maximum length is `300` for the question, and `55` for any answer + */ + text?: string; + /** + * The emoji of the field + */ + emoji?: APIPartialEmoji; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-answer-object-poll-answer-object-structure + */ +export interface APIPollAnswer { + /** + * The ID of the answer. Starts at `1` for the first answer and goes up sequentially + */ + answer_id: number; + /** + * The data of the answer + */ + media: APIPollMedia; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-results-object-poll-results-object-structure + */ +export interface APIPollResults { + /** + * Whether the votes have been precisely counted + */ + is_finalized: boolean; + /** + * The counts for each answer + */ + answer_counts: APIPollAnswerCount[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-results-object-poll-answer-count-object-structure + */ +export interface APIPollAnswerCount { + /** + * The `answer_id` + */ + id: number; + /** + * The number of votes for this answer + */ + count: number; + /** + * Whether the current user voted for this answer + */ + me_voted: boolean; +} diff --git a/payloads/v9/channel.ts b/payloads/v9/channel.ts index db7d2846..edf589b2 100644 --- a/payloads/v9/channel.ts +++ b/payloads/v9/channel.ts @@ -8,6 +8,7 @@ import type { APIPartialEmoji } from './emoji'; import type { APIGuildMember } from './guild'; import type { APIInteractionDataResolved, APIMessageInteraction } from './interactions'; import type { APIRole } from './permissions'; +import type { APIPoll } from './poll'; import type { APISticker, APIStickerItem } from './sticker'; import type { APIUser } from './user'; @@ -702,6 +703,17 @@ export interface APIMessage { * See https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure */ resolved?: APIInteractionDataResolved; + /** + * A poll! + * + * The `MESSAGE_CONTENT` privileged gateway intent is required for verified applications to receive a non-empty value from this field + * + * In the Discord Developers Portal, you need to enable the toggle of this intent of your application in **Bot > Privileged Gateway Intents**. + * You also need to specify the intent bit value (`1 << 15`) if you are connecting to the gateway + * + * See https://support-dev.discord.com/hc/articles/4404772028055 + */ + poll?: APIPoll; } /** diff --git a/payloads/v9/index.ts b/payloads/v9/index.ts index 0a13eeeb..a30701a6 100644 --- a/payloads/v9/index.ts +++ b/payloads/v9/index.ts @@ -10,6 +10,7 @@ export * from './guildScheduledEvent'; export * from './interactions'; export * from './invite'; export * from './oauth2'; +export * from './poll'; export * from './permissions'; export * from './stageInstance'; export * from './sticker'; diff --git a/payloads/v9/poll.ts b/payloads/v9/poll.ts new file mode 100644 index 00000000..c597214d --- /dev/null +++ b/payloads/v9/poll.ts @@ -0,0 +1,107 @@ +/** + * Types extracted from https://discord.com/developers/docs/resources/poll + */ + +import type { APIPartialEmoji } from './emoji'; + +/** + * https://discord.com/developers/docs/resources/poll#poll-object-poll-object-structure + */ +export interface APIPoll { + /** + * The question of the poll + */ + question: APIPollMedia; + /** + * Each of the answers available in the poll, up to 10 + */ + answers: APIPollAnswer[]; + /** + * The time when the poll ends (IS08601 timestamp) + */ + expiry: string; + /** + * Whether a user can select multiple answers + */ + allow_multiselect: boolean; + /** + * The layout type of the poll + */ + layout_type: PollLayoutType; + /** + * The results of the poll + */ + results?: APIPollResults; +} + +/** + * https://discord.com/developers/docs/resources/poll#layout-type + */ +export enum PollLayoutType { + /** + * The, uhm, default layout type + */ + Default = 1, +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-media-object-poll-media-object-structure + */ +export interface APIPollMedia { + /** + * The text of the field + * + * The maximum length is `300` for the question, and `55` for any answer + */ + text?: string; + /** + * The emoji of the field + */ + emoji?: APIPartialEmoji; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-answer-object-poll-answer-object-structure + */ +export interface APIPollAnswer { + /** + * The ID of the answer. Starts at `1` for the first answer and goes up sequentially + */ + answer_id: number; + /** + * The data of the answer + */ + media: APIPollMedia; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-results-object-poll-results-object-structure + */ +export interface APIPollResults { + /** + * Whether the votes have been precisely counted + */ + is_finalized: boolean; + /** + * The counts for each answer + */ + answer_counts: APIPollAnswerCount[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-results-object-poll-answer-count-object-structure + */ +export interface APIPollAnswerCount { + /** + * The `answer_id` + */ + id: number; + /** + * The number of votes for this answer + */ + count: number; + /** + * Whether the current user voted for this answer + */ + me_voted: boolean; +} diff --git a/rest/common.ts b/rest/common.ts index 00f9a861..184d5dcf 100644 --- a/rest/common.ts +++ b/rest/common.ts @@ -295,6 +295,14 @@ export enum RESTJSONErrorCodes { CannotUpdateOnboardingWhileBelowRequirements, FailedToBanUsers = 500_000, + + PollVotingBlocked = 520_000, + PollExpired, + InvalidChannelTypeForPollCreation, + CannotEditAPollMessage, + CannotUseAnEmojiIncludedWithThePoll, + + CannotExpireANonPollMessage = 520_006, } /** diff --git a/rest/v10/channel.ts b/rest/v10/channel.ts index 5091e578..7fbacebf 100644 --- a/rest/v10/channel.ts +++ b/rest/v10/channel.ts @@ -26,6 +26,7 @@ import type { ChannelFlags, } from '../../payloads/v10/index'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, StrictPartial } from '../../utils/internals'; +import type { RESTAPIPollCreate } from './poll'; export interface APIChannelPatchOverwrite extends RESTPutAPIChannelPermissionJSONBody { id: Snowflake; @@ -325,6 +326,10 @@ export interface RESTPostAPIChannelMessageJSONBody { * If another message was created by the same author with the same nonce, that message will be returned and no new message will be created. */ enforce_nonce?: boolean | undefined; + /** + * A poll! + */ + poll?: RESTAPIPollCreate | undefined; } /** diff --git a/rest/v10/index.ts b/rest/v10/index.ts index fe6ea05a..efac5dae 100644 --- a/rest/v10/index.ts +++ b/rest/v10/index.ts @@ -12,6 +12,7 @@ export * from './guildScheduledEvent'; export * from './interactions'; export * from './invite'; export * from './oauth2'; +export * from './poll'; export * from './stageInstance'; export * from './sticker'; export * from './template'; @@ -461,6 +462,22 @@ export const Routes = { return `/guilds/${guildId}/templates/${code}` as const; }, + /** + * Route for: + * - GET `/channels/{channel.id}/polls/{message.id}/answers/{answer_id}` + */ + pollAnswerVoters(channelId: Snowflake, messageId: Snowflake, answerId: number) { + return `/channels/${channelId}/polls/${messageId}/answers/${answerId}` as const; + }, + + /** + * Route for: + * - POST `/channels/{channel.id}/polls/{message.id}/expire` + */ + expirePoll(channelId: Snowflake, messageId: Snowflake) { + return `/channels/${channelId}/polls/${messageId}/expire` as const; + }, + /** * Route for: * - POST `/channels/{channel.id}/threads` diff --git a/rest/v10/poll.ts b/rest/v10/poll.ts new file mode 100644 index 00000000..7beb29fe --- /dev/null +++ b/rest/v10/poll.ts @@ -0,0 +1,47 @@ +import type { Snowflake } from '../../globals'; +import type { APIMessage, APIPoll, APIPollAnswer, APIUser } from '../../v10'; + +/** + * https://discord.com/developers/docs/resources/poll#get-answer-voters + */ +export interface RESTGetAPIPollAnswerVotersQuery { + /** + * Get users after this user ID + */ + after?: Snowflake; + /** + * Max number of users to return (1-100) + * + * @default 25 + */ + limit?: number; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-create-request-object-poll-create-request-object-structure + */ +export interface RESTAPIPollCreate extends Omit { + /** + * Number of hours the poll should be open for, up to 7 days + */ + duration: number; + /** + * Each of the answers available in the poll, up to 10 + */ + answers: Omit[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#get-answer-voters + */ +export interface RESTGetAPIPollAnswerVotersResult { + /** + * Users who voted for this answer + */ + users: APIUser[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#expire-poll + */ +export type RESTPostAPIPollExpireResult = APIMessage; diff --git a/rest/v10/webhook.ts b/rest/v10/webhook.ts index 5dc01159..acb37d49 100644 --- a/rest/v10/webhook.ts +++ b/rest/v10/webhook.ts @@ -10,6 +10,7 @@ import type { } from '../../payloads/v10/index'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, Nullable } from '../../utils/internals'; import type { RESTAPIAttachment } from './channel'; +import type { RESTAPIPollCreate } from './poll'; /** * https://discord.com/developers/docs/resources/webhook#create-webhook */ @@ -154,6 +155,10 @@ export interface RESTPostAPIWebhookWithTokenJSONBody { * Array of tag ids to apply to the thread */ applied_tags?: Snowflake[] | undefined; + /** + * A poll! + */ + poll?: RESTAPIPollCreate | undefined; } /** diff --git a/rest/v9/channel.ts b/rest/v9/channel.ts index 0aa010bb..86a49db1 100644 --- a/rest/v9/channel.ts +++ b/rest/v9/channel.ts @@ -26,6 +26,7 @@ import type { ChannelFlags, } from '../../payloads/v9/index'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, StrictPartial } from '../../utils/internals'; +import type { RESTAPIPollCreate } from './poll'; export interface APIChannelPatchOverwrite extends RESTPutAPIChannelPermissionJSONBody { id: Snowflake; @@ -333,6 +334,10 @@ export interface RESTPostAPIChannelMessageJSONBody { * If another message was created by the same author with the same nonce, that message will be returned and no new message will be created. */ enforce_nonce?: boolean | undefined; + /** + * A poll! + */ + poll?: RESTAPIPollCreate | undefined; } /** diff --git a/rest/v9/index.ts b/rest/v9/index.ts index 60636481..78625e9c 100644 --- a/rest/v9/index.ts +++ b/rest/v9/index.ts @@ -12,6 +12,7 @@ export * from './guildScheduledEvent'; export * from './interactions'; export * from './invite'; export * from './oauth2'; +export * from './poll'; export * from './stageInstance'; export * from './sticker'; export * from './template'; @@ -461,6 +462,22 @@ export const Routes = { return `/guilds/${guildId}/templates/${code}` as const; }, + /** + * Route for: + * - GET `/channels/{channel.id}/polls/{message.id}/answers/{answer_id}` + */ + pollAnswerVoters(channelId: Snowflake, messageId: Snowflake, answerId: number) { + return `/channels/${channelId}/polls/${messageId}/answers/${answerId}` as const; + }, + + /** + * Route for: + * - POST `/channels/{channel.id}/polls/{message.id}/expire` + */ + expirePoll(channelId: Snowflake, messageId: Snowflake) { + return `/channels/${channelId}/polls/${messageId}/expire` as const; + }, + /** * Route for: * - POST `/channels/{channel.id}/threads` diff --git a/rest/v9/poll.ts b/rest/v9/poll.ts new file mode 100644 index 00000000..943ec111 --- /dev/null +++ b/rest/v9/poll.ts @@ -0,0 +1,47 @@ +import type { Snowflake } from '../../globals'; +import type { APIMessage, APIPoll, APIPollAnswer, APIUser } from '../../v9'; + +/** + * https://discord.com/developers/docs/resources/poll#get-answer-voters + */ +export interface RESTGetAPIPollAnswerVotersQuery { + /** + * Get users after this user ID + */ + after?: Snowflake; + /** + * Max number of users to return (1-100) + * + * @default 25 + */ + limit?: number; +} + +/** + * https://discord.com/developers/docs/resources/poll#poll-create-request-object-poll-create-request-object-structure + */ +export interface RESTAPIPollCreate extends Omit { + /** + * Number of hours the poll should be open for, up to 7 days + */ + duration: number; + /** + * Each of the answers available in the poll, up to 10 + */ + answers: Omit[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#get-answer-voters + */ +export interface RESTGetAPIPollAnswerVotersResult { + /** + * Users who voted for this answer + */ + users: APIUser[]; +} + +/** + * https://discord.com/developers/docs/resources/poll#expire-poll + */ +export type RESTPostAPIPollExpireResult = APIMessage; diff --git a/rest/v9/webhook.ts b/rest/v9/webhook.ts index e0a8099c..4925f7b6 100644 --- a/rest/v9/webhook.ts +++ b/rest/v9/webhook.ts @@ -10,6 +10,7 @@ import type { } from '../../payloads/v9/index'; import type { AddUndefinedToPossiblyUndefinedPropertiesOfInterface, Nullable } from '../../utils/internals'; import type { RESTAPIAttachment } from './channel'; +import type { RESTAPIPollCreate } from './poll'; /** * https://discord.com/developers/docs/resources/webhook#create-webhook */ @@ -154,6 +155,10 @@ export interface RESTPostAPIWebhookWithTokenJSONBody { * Array of tag ids to apply to the thread */ applied_tags?: Snowflake[] | undefined; + /** + * A poll! + */ + poll?: RESTAPIPollCreate | undefined; } /**