mirror of
https://github.com/discordjs/discord.js.git
synced 2024-08-21 20:44:43 +12:00
feat: polls (#10185)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
c7adce351a
commit
a1aeaeb9d8
23 changed files with 562 additions and 5 deletions
|
@ -123,6 +123,8 @@ body:
|
||||||
- GuildScheduledEvents
|
- GuildScheduledEvents
|
||||||
- AutoModerationConfiguration
|
- AutoModerationConfiguration
|
||||||
- AutoModerationExecution
|
- AutoModerationExecution
|
||||||
|
- GuildMessagePolls
|
||||||
|
- DirectMessagePolls
|
||||||
multiple: true
|
multiple: true
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { InteractionsAPI } from './interactions.js';
|
||||||
import { InvitesAPI } from './invite.js';
|
import { InvitesAPI } from './invite.js';
|
||||||
import { MonetizationAPI } from './monetization.js';
|
import { MonetizationAPI } from './monetization.js';
|
||||||
import { OAuth2API } from './oauth2.js';
|
import { OAuth2API } from './oauth2.js';
|
||||||
|
import { PollAPI } from './poll.js';
|
||||||
import { RoleConnectionsAPI } from './roleConnections.js';
|
import { RoleConnectionsAPI } from './roleConnections.js';
|
||||||
import { StageInstancesAPI } from './stageInstances.js';
|
import { StageInstancesAPI } from './stageInstances.js';
|
||||||
import { StickersAPI } from './sticker.js';
|
import { StickersAPI } from './sticker.js';
|
||||||
|
@ -23,6 +24,7 @@ export * from './interactions.js';
|
||||||
export * from './invite.js';
|
export * from './invite.js';
|
||||||
export * from './monetization.js';
|
export * from './monetization.js';
|
||||||
export * from './oauth2.js';
|
export * from './oauth2.js';
|
||||||
|
export * from './poll.js';
|
||||||
export * from './roleConnections.js';
|
export * from './roleConnections.js';
|
||||||
export * from './stageInstances.js';
|
export * from './stageInstances.js';
|
||||||
export * from './sticker.js';
|
export * from './sticker.js';
|
||||||
|
@ -48,6 +50,8 @@ export class API {
|
||||||
|
|
||||||
public readonly oauth2: OAuth2API;
|
public readonly oauth2: OAuth2API;
|
||||||
|
|
||||||
|
public readonly poll: PollAPI;
|
||||||
|
|
||||||
public readonly roleConnections: RoleConnectionsAPI;
|
public readonly roleConnections: RoleConnectionsAPI;
|
||||||
|
|
||||||
public readonly stageInstances: StageInstancesAPI;
|
public readonly stageInstances: StageInstancesAPI;
|
||||||
|
@ -69,8 +73,9 @@ export class API {
|
||||||
this.guilds = new GuildsAPI(rest);
|
this.guilds = new GuildsAPI(rest);
|
||||||
this.invites = new InvitesAPI(rest);
|
this.invites = new InvitesAPI(rest);
|
||||||
this.monetization = new MonetizationAPI(rest);
|
this.monetization = new MonetizationAPI(rest);
|
||||||
this.roleConnections = new RoleConnectionsAPI(rest);
|
|
||||||
this.oauth2 = new OAuth2API(rest);
|
this.oauth2 = new OAuth2API(rest);
|
||||||
|
this.poll = new PollAPI(rest);
|
||||||
|
this.roleConnections = new RoleConnectionsAPI(rest);
|
||||||
this.stageInstances = new StageInstancesAPI(rest);
|
this.stageInstances = new StageInstancesAPI(rest);
|
||||||
this.stickers = new StickersAPI(rest);
|
this.stickers = new StickersAPI(rest);
|
||||||
this.threads = new ThreadsAPI(rest);
|
this.threads = new ThreadsAPI(rest);
|
||||||
|
|
51
packages/core/src/api/poll.ts
Normal file
51
packages/core/src/api/poll.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/* eslint-disable jsdoc/check-param-names */
|
||||||
|
|
||||||
|
import { makeURLSearchParams, type RequestData, type REST } from '@discordjs/rest';
|
||||||
|
import {
|
||||||
|
Routes,
|
||||||
|
type RESTGetAPIPollAnswerVotersQuery,
|
||||||
|
type RESTGetAPIPollAnswerVotersResult,
|
||||||
|
type RESTPostAPIPollExpireResult,
|
||||||
|
type Snowflake,
|
||||||
|
} from 'discord-api-types/v10';
|
||||||
|
|
||||||
|
export class PollAPI {
|
||||||
|
public constructor(private readonly rest: REST) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of users that voted for a specific answer in a poll
|
||||||
|
*
|
||||||
|
* @see {@link https://discord.com/developers/docs/resources/poll#get-answer-voters}
|
||||||
|
* @param channelId - The id of the channel containing the message
|
||||||
|
* @param messageId - The id of the message containing the poll
|
||||||
|
* @param answerId - The id of the answer to get voters for
|
||||||
|
* @param query - The query for getting the list of voters
|
||||||
|
* @param options - The options for getting the list of voters
|
||||||
|
*/
|
||||||
|
public async getAnswerVoters(
|
||||||
|
channelId: Snowflake,
|
||||||
|
messageId: Snowflake,
|
||||||
|
answerId: number,
|
||||||
|
query: RESTGetAPIPollAnswerVotersQuery,
|
||||||
|
{ signal }: Pick<RequestData, 'signal'> = {},
|
||||||
|
) {
|
||||||
|
return this.rest.get(Routes.pollAnswerVoters(channelId, messageId, answerId), {
|
||||||
|
signal,
|
||||||
|
query: makeURLSearchParams(query),
|
||||||
|
}) as Promise<RESTGetAPIPollAnswerVotersResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immediately expires (i.e. ends) a poll
|
||||||
|
*
|
||||||
|
* @see {@link https://discord.com/developers/docs/resources/poll#expire-poll}
|
||||||
|
* @param channelId - The id of the channel containing the message
|
||||||
|
* @param messageId - The id of the message containing the poll
|
||||||
|
* @param options - The options for expiring the poll
|
||||||
|
*/
|
||||||
|
public async expirePoll(channelId: Snowflake, messageId: Snowflake, { signal }: Pick<RequestData, 'signal'> = {}) {
|
||||||
|
return this.rest.post(Routes.expirePoll(channelId, messageId), {
|
||||||
|
signal,
|
||||||
|
}) as Promise<RESTPostAPIPollExpireResult>;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { setTimeout, clearTimeout } from 'node:timers';
|
import { clearTimeout, setTimeout } from 'node:timers';
|
||||||
import type { REST } from '@discordjs/rest';
|
import type { REST } from '@discordjs/rest';
|
||||||
import { calculateShardId } from '@discordjs/util';
|
import { calculateShardId } from '@discordjs/util';
|
||||||
import { WebSocketShardEvents } from '@discordjs/ws';
|
import { WebSocketShardEvents } from '@discordjs/ws';
|
||||||
|
@ -49,6 +49,7 @@ import {
|
||||||
type GatewayMessageCreateDispatchData,
|
type GatewayMessageCreateDispatchData,
|
||||||
type GatewayMessageDeleteBulkDispatchData,
|
type GatewayMessageDeleteBulkDispatchData,
|
||||||
type GatewayMessageDeleteDispatchData,
|
type GatewayMessageDeleteDispatchData,
|
||||||
|
type GatewayMessagePollVoteDispatchData,
|
||||||
type GatewayMessageReactionAddDispatchData,
|
type GatewayMessageReactionAddDispatchData,
|
||||||
type GatewayMessageReactionRemoveAllDispatchData,
|
type GatewayMessageReactionRemoveAllDispatchData,
|
||||||
type GatewayMessageReactionRemoveDispatchData,
|
type GatewayMessageReactionRemoveDispatchData,
|
||||||
|
@ -143,6 +144,8 @@ export interface MappedEvents {
|
||||||
[GatewayDispatchEvents.MessageCreate]: [WithIntrinsicProps<GatewayMessageCreateDispatchData>];
|
[GatewayDispatchEvents.MessageCreate]: [WithIntrinsicProps<GatewayMessageCreateDispatchData>];
|
||||||
[GatewayDispatchEvents.MessageDelete]: [WithIntrinsicProps<GatewayMessageDeleteDispatchData>];
|
[GatewayDispatchEvents.MessageDelete]: [WithIntrinsicProps<GatewayMessageDeleteDispatchData>];
|
||||||
[GatewayDispatchEvents.MessageDeleteBulk]: [WithIntrinsicProps<GatewayMessageDeleteBulkDispatchData>];
|
[GatewayDispatchEvents.MessageDeleteBulk]: [WithIntrinsicProps<GatewayMessageDeleteBulkDispatchData>];
|
||||||
|
[GatewayDispatchEvents.MessagePollVoteAdd]: [WithIntrinsicProps<GatewayMessagePollVoteDispatchData>];
|
||||||
|
[GatewayDispatchEvents.MessagePollVoteRemove]: [WithIntrinsicProps<GatewayMessagePollVoteDispatchData>];
|
||||||
[GatewayDispatchEvents.MessageReactionAdd]: [WithIntrinsicProps<GatewayMessageReactionAddDispatchData>];
|
[GatewayDispatchEvents.MessageReactionAdd]: [WithIntrinsicProps<GatewayMessageReactionAddDispatchData>];
|
||||||
[GatewayDispatchEvents.MessageReactionRemove]: [WithIntrinsicProps<GatewayMessageReactionRemoveDispatchData>];
|
[GatewayDispatchEvents.MessageReactionRemove]: [WithIntrinsicProps<GatewayMessageReactionRemoveDispatchData>];
|
||||||
[GatewayDispatchEvents.MessageReactionRemoveAll]: [WithIntrinsicProps<GatewayMessageReactionRemoveAllDispatchData>];
|
[GatewayDispatchEvents.MessageReactionRemoveAll]: [WithIntrinsicProps<GatewayMessageReactionRemoveAllDispatchData>];
|
||||||
|
@ -198,9 +201,8 @@ export class Client extends AsyncEventEmitter<MappedEvents> {
|
||||||
|
|
||||||
this.gateway.on(WebSocketShardEvents.Dispatch, ({ data: dispatch, shardId }) => {
|
this.gateway.on(WebSocketShardEvents.Dispatch, ({ data: dispatch, shardId }) => {
|
||||||
this.emit(
|
this.emit(
|
||||||
// TODO: This comment will have to be moved down once the new Poll events are added to the `ManagerShardEventsMap`
|
|
||||||
// @ts-expect-error event props can't be resolved properly, but they are correct
|
|
||||||
dispatch.t,
|
dispatch.t,
|
||||||
|
// @ts-expect-error event props can't be resolved properly, but they are correct
|
||||||
this.wrapIntrinsicProps(dispatch.d, shardId),
|
this.wrapIntrinsicProps(dispatch.d, shardId),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,6 +54,8 @@ class ActionsManager {
|
||||||
this.register(require('./MessageCreate'));
|
this.register(require('./MessageCreate'));
|
||||||
this.register(require('./MessageDelete'));
|
this.register(require('./MessageDelete'));
|
||||||
this.register(require('./MessageDeleteBulk'));
|
this.register(require('./MessageDeleteBulk'));
|
||||||
|
this.register(require('./MessagePollVoteAdd'));
|
||||||
|
this.register(require('./MessagePollVoteRemove'));
|
||||||
this.register(require('./MessageReactionAdd'));
|
this.register(require('./MessageReactionAdd'));
|
||||||
this.register(require('./MessageReactionRemove'));
|
this.register(require('./MessageReactionRemove'));
|
||||||
this.register(require('./MessageReactionRemoveAll'));
|
this.register(require('./MessageReactionRemoveAll'));
|
||||||
|
|
33
packages/discord.js/src/client/actions/MessagePollVoteAdd.js
Normal file
33
packages/discord.js/src/client/actions/MessagePollVoteAdd.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Action = require('./Action');
|
||||||
|
const Events = require('../../util/Events');
|
||||||
|
|
||||||
|
class MessagePollVoteAddAction extends Action {
|
||||||
|
handle(data) {
|
||||||
|
const channel = this.getChannel(data);
|
||||||
|
if (!channel?.isTextBased()) return false;
|
||||||
|
|
||||||
|
const message = this.getMessage(data, channel);
|
||||||
|
if (!message) return false;
|
||||||
|
|
||||||
|
const { poll } = message;
|
||||||
|
|
||||||
|
const answer = poll.answers.get(data.answer_id);
|
||||||
|
if (!answer) return false;
|
||||||
|
|
||||||
|
answer.voteCount++;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted whenever a user votes in a poll.
|
||||||
|
* @event Client#messagePollVoteAdd
|
||||||
|
* @param {PollAnswer} pollAnswer The answer that was voted on
|
||||||
|
* @param {Snowflake} userId The id of the user that voted
|
||||||
|
*/
|
||||||
|
this.client.emit(Events.MessagePollVoteAdd, answer, data.user_id);
|
||||||
|
|
||||||
|
return { poll };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MessagePollVoteAddAction;
|
|
@ -0,0 +1,33 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Action = require('./Action');
|
||||||
|
const Events = require('../../util/Events');
|
||||||
|
|
||||||
|
class MessagePollVoteRemoveAction extends Action {
|
||||||
|
handle(data) {
|
||||||
|
const channel = this.getChannel(data);
|
||||||
|
if (!channel?.isTextBased()) return false;
|
||||||
|
|
||||||
|
const message = this.getMessage(data, channel);
|
||||||
|
if (!message) return false;
|
||||||
|
|
||||||
|
const { poll } = message;
|
||||||
|
|
||||||
|
const answer = poll.answers.get(data.answer_id);
|
||||||
|
if (!answer) return false;
|
||||||
|
|
||||||
|
answer.voteCount--;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted whenever a user removes their vote in a poll.
|
||||||
|
* @event Client#messagePollVoteRemove
|
||||||
|
* @param {PollAnswer} pollAnswer The answer where the vote was removed
|
||||||
|
* @param {Snowflake} userId The id of the user that removed their vote
|
||||||
|
*/
|
||||||
|
this.client.emit(Events.MessagePollVoteRemove, answer, data.user_id);
|
||||||
|
|
||||||
|
return { poll };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MessagePollVoteRemoveAction;
|
|
@ -0,0 +1,5 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = (client, packet) => {
|
||||||
|
client.actions.MessagePollVoteAdd.handle(packet.d);
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = (client, packet) => {
|
||||||
|
client.actions.MessagePollVoteRemove.handle(packet.d);
|
||||||
|
};
|
|
@ -40,6 +40,8 @@ const handlers = Object.fromEntries([
|
||||||
['MESSAGE_CREATE', require('./MESSAGE_CREATE')],
|
['MESSAGE_CREATE', require('./MESSAGE_CREATE')],
|
||||||
['MESSAGE_DELETE', require('./MESSAGE_DELETE')],
|
['MESSAGE_DELETE', require('./MESSAGE_DELETE')],
|
||||||
['MESSAGE_DELETE_BULK', require('./MESSAGE_DELETE_BULK')],
|
['MESSAGE_DELETE_BULK', require('./MESSAGE_DELETE_BULK')],
|
||||||
|
['MESSAGE_POLL_VOTE_ADD', require('./MESSAGE_POLL_VOTE_ADD')],
|
||||||
|
['MESSAGE_POLL_VOTE_REMOVE', require('./MESSAGE_POLL_VOTE_REMOVE')],
|
||||||
['MESSAGE_REACTION_ADD', require('./MESSAGE_REACTION_ADD')],
|
['MESSAGE_REACTION_ADD', require('./MESSAGE_REACTION_ADD')],
|
||||||
['MESSAGE_REACTION_REMOVE', require('./MESSAGE_REACTION_REMOVE')],
|
['MESSAGE_REACTION_REMOVE', require('./MESSAGE_REACTION_REMOVE')],
|
||||||
['MESSAGE_REACTION_REMOVE_ALL', require('./MESSAGE_REACTION_REMOVE_ALL')],
|
['MESSAGE_REACTION_REMOVE_ALL', require('./MESSAGE_REACTION_REMOVE_ALL')],
|
||||||
|
|
|
@ -178,6 +178,8 @@
|
||||||
* @property {'EntitlementCreateInvalidOwner'} EntitlementCreateInvalidOwner
|
* @property {'EntitlementCreateInvalidOwner'} EntitlementCreateInvalidOwner
|
||||||
|
|
||||||
* @property {'BulkBanUsersOptionEmpty'} BulkBanUsersOptionEmpty
|
* @property {'BulkBanUsersOptionEmpty'} BulkBanUsersOptionEmpty
|
||||||
|
|
||||||
|
* @property {'PollAlreadyExpired'} PollAlreadyExpired
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const keys = [
|
const keys = [
|
||||||
|
@ -333,6 +335,8 @@ const keys = [
|
||||||
'EntitlementCreateInvalidOwner',
|
'EntitlementCreateInvalidOwner',
|
||||||
|
|
||||||
'BulkBanUsersOptionEmpty',
|
'BulkBanUsersOptionEmpty',
|
||||||
|
|
||||||
|
'PollAlreadyExpired',
|
||||||
];
|
];
|
||||||
|
|
||||||
// JSDoc for IntelliSense purposes
|
// JSDoc for IntelliSense purposes
|
||||||
|
|
|
@ -171,6 +171,8 @@ const Messages = {
|
||||||
'You must provide either a guild or a user to create an entitlement, but not both',
|
'You must provide either a guild or a user to create an entitlement, but not both',
|
||||||
|
|
||||||
[DjsErrorCodes.BulkBanUsersOptionEmpty]: 'Option "users" array or collection is empty',
|
[DjsErrorCodes.BulkBanUsersOptionEmpty]: 'Option "users" array or collection is empty',
|
||||||
|
|
||||||
|
[DjsErrorCodes.PollAlreadyExpired]: 'This poll has already expired.',
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Messages;
|
module.exports = Messages;
|
||||||
|
|
|
@ -168,6 +168,8 @@ exports.NewsChannel = require('./structures/NewsChannel');
|
||||||
exports.OAuth2Guild = require('./structures/OAuth2Guild');
|
exports.OAuth2Guild = require('./structures/OAuth2Guild');
|
||||||
exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel');
|
exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel');
|
||||||
exports.PermissionOverwrites = require('./structures/PermissionOverwrites');
|
exports.PermissionOverwrites = require('./structures/PermissionOverwrites');
|
||||||
|
exports.Poll = require('./structures/Poll').Poll;
|
||||||
|
exports.PollAnswer = require('./structures/PollAnswer').PollAnswer;
|
||||||
exports.Presence = require('./structures/Presence').Presence;
|
exports.Presence = require('./structures/Presence').Presence;
|
||||||
exports.ReactionCollector = require('./structures/ReactionCollector');
|
exports.ReactionCollector = require('./structures/ReactionCollector');
|
||||||
exports.ReactionEmoji = require('./structures/ReactionEmoji');
|
exports.ReactionEmoji = require('./structures/ReactionEmoji');
|
||||||
|
|
|
@ -17,6 +17,7 @@ const Embed = require('./Embed');
|
||||||
const InteractionCollector = require('./InteractionCollector');
|
const InteractionCollector = require('./InteractionCollector');
|
||||||
const Mentions = require('./MessageMentions');
|
const Mentions = require('./MessageMentions');
|
||||||
const MessagePayload = require('./MessagePayload');
|
const MessagePayload = require('./MessagePayload');
|
||||||
|
const { Poll } = require('./Poll.js');
|
||||||
const ReactionCollector = require('./ReactionCollector');
|
const ReactionCollector = require('./ReactionCollector');
|
||||||
const { Sticker } = require('./Sticker');
|
const { Sticker } = require('./Sticker');
|
||||||
const { DiscordjsError, ErrorCodes } = require('../errors');
|
const { DiscordjsError, ErrorCodes } = require('../errors');
|
||||||
|
@ -406,6 +407,16 @@ class Message extends Base {
|
||||||
} else {
|
} else {
|
||||||
this.interaction ??= null;
|
this.interaction ??= null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.poll) {
|
||||||
|
/**
|
||||||
|
* The poll that was sent with the message
|
||||||
|
* @type {?Poll}
|
||||||
|
*/
|
||||||
|
this.poll = new Poll(this.client, data.poll, this);
|
||||||
|
} else {
|
||||||
|
this.poll ??= null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,7 +7,7 @@ const ActionRowBuilder = require('./ActionRowBuilder');
|
||||||
const { DiscordjsError, DiscordjsRangeError, ErrorCodes } = require('../errors');
|
const { DiscordjsError, DiscordjsRangeError, ErrorCodes } = require('../errors');
|
||||||
const { resolveFile } = require('../util/DataResolver');
|
const { resolveFile } = require('../util/DataResolver');
|
||||||
const MessageFlagsBitField = require('../util/MessageFlagsBitField');
|
const MessageFlagsBitField = require('../util/MessageFlagsBitField');
|
||||||
const { basename, verifyString } = require('../util/Util');
|
const { basename, verifyString, resolvePartialEmoji } = require('../util/Util');
|
||||||
|
|
||||||
const getBaseInteraction = lazy(() => require('./BaseInteraction'));
|
const getBaseInteraction = lazy(() => require('./BaseInteraction'));
|
||||||
|
|
||||||
|
@ -202,6 +202,21 @@ class MessagePayload {
|
||||||
this.options.attachments = attachments;
|
this.options.attachments = attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let poll;
|
||||||
|
if (this.options.poll) {
|
||||||
|
poll = {
|
||||||
|
question: {
|
||||||
|
text: this.options.poll.question.text,
|
||||||
|
},
|
||||||
|
answers: this.options.poll.answers.map(answer => ({
|
||||||
|
poll_media: { text: answer.text, emoji: resolvePartialEmoji(answer.emoji) },
|
||||||
|
})),
|
||||||
|
duration: this.options.poll.duration,
|
||||||
|
allow_multiselect: this.options.poll.allowMultiselect,
|
||||||
|
layout_type: this.options.poll.layoutType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.body = {
|
this.body = {
|
||||||
content,
|
content,
|
||||||
tts,
|
tts,
|
||||||
|
@ -220,6 +235,7 @@ class MessagePayload {
|
||||||
sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker),
|
sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker),
|
||||||
thread_name: threadName,
|
thread_name: threadName,
|
||||||
applied_tags: appliedTags,
|
applied_tags: appliedTags,
|
||||||
|
poll,
|
||||||
};
|
};
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
114
packages/discord.js/src/structures/Poll.js
Normal file
114
packages/discord.js/src/structures/Poll.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Collection } = require('@discordjs/collection');
|
||||||
|
const { Routes } = require('discord-api-types/v10');
|
||||||
|
const Base = require('./Base');
|
||||||
|
const { PollAnswer } = require('./PollAnswer');
|
||||||
|
const { DiscordjsError } = require('../errors/DJSError');
|
||||||
|
const { ErrorCodes } = require('../errors/index');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a Poll
|
||||||
|
* @extends {Base}
|
||||||
|
*/
|
||||||
|
class Poll extends Base {
|
||||||
|
constructor(client, data, message) {
|
||||||
|
super(client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message that started this poll
|
||||||
|
* @name Poll#message
|
||||||
|
* @type {Message}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'message', { value: message });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The media for a poll's question
|
||||||
|
* @typedef {Object} PollQuestionMedia
|
||||||
|
* @property {string} text The text of this question
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The media for this poll's question
|
||||||
|
* @type {PollQuestionMedia}
|
||||||
|
*/
|
||||||
|
this.question = {
|
||||||
|
text: data.question.text,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The answers of this poll
|
||||||
|
* @type {Collection<number, PollAnswer>}
|
||||||
|
*/
|
||||||
|
this.answers = data.answers.reduce(
|
||||||
|
(acc, answer) => acc.set(answer.answer_id, new PollAnswer(this.client, answer, this)),
|
||||||
|
new Collection(),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp when this poll expires
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.expiresTimestamp = Date.parse(data.expiry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this poll allows multiple answers
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.allowMultiselect = data.allow_multiselect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The layout type of this poll
|
||||||
|
* @type {PollLayoutType}
|
||||||
|
*/
|
||||||
|
this.layoutType = data.layout_type;
|
||||||
|
|
||||||
|
this._patch(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_patch(data) {
|
||||||
|
if (data.results) {
|
||||||
|
/**
|
||||||
|
* Whether this poll's results have been precisely counted
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.resultsFinalized = data.results.is_finalized;
|
||||||
|
|
||||||
|
for (const answerResult of data.results.answer_counts) {
|
||||||
|
const answer = this.answers.get(answerResult.id);
|
||||||
|
answer?._patch(answerResult);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.resultsFinalized ??= false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The date when this poll expires
|
||||||
|
* @type {Date}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get expiresAt() {
|
||||||
|
return new Date(this.expiresTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End this poll
|
||||||
|
* @returns {Promise<Message>}
|
||||||
|
*/
|
||||||
|
async end() {
|
||||||
|
if (Date.now() > this.expiresTimestamp) {
|
||||||
|
throw new DiscordjsError(ErrorCodes.PollAlreadyExpired);
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = await this.client.rest.post(Routes.expirePoll(this.message.channel.id, this.message.id));
|
||||||
|
|
||||||
|
const clone = this.message._clone();
|
||||||
|
clone._patch(message);
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.Poll = Poll;
|
92
packages/discord.js/src/structures/PollAnswer.js
Normal file
92
packages/discord.js/src/structures/PollAnswer.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Collection } = require('@discordjs/collection');
|
||||||
|
const { makeURLSearchParams } = require('@discordjs/rest');
|
||||||
|
const { Routes } = require('discord-api-types/v10');
|
||||||
|
const Base = require('./Base');
|
||||||
|
const { Emoji } = require('./Emoji');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an answer to a {@link Poll}
|
||||||
|
* @extends {Base}
|
||||||
|
*/
|
||||||
|
class PollAnswer extends Base {
|
||||||
|
constructor(client, data, poll) {
|
||||||
|
super(client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link Poll} this answer is part of
|
||||||
|
* @name PollAnswer#poll
|
||||||
|
* @type {Poll}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
Object.defineProperty(this, 'poll', { value: poll });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of this answer
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.id = data.answer_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text of this answer
|
||||||
|
* @type {?string}
|
||||||
|
*/
|
||||||
|
this.text = data.poll_media.text ?? null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The raw emoji of this answer
|
||||||
|
* @name PollAnswer#_emoji
|
||||||
|
* @type {?APIPartialEmoji}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
Object.defineProperty(this, '_emoji', { value: data.poll_media.emoji ?? null });
|
||||||
|
|
||||||
|
this._patch(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
_patch(data) {
|
||||||
|
// This `count` field comes from `poll.results.answer_counts`
|
||||||
|
if ('count' in data) {
|
||||||
|
/**
|
||||||
|
* The amount of votes this answer has
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.voteCount = data.count;
|
||||||
|
} else {
|
||||||
|
this.voteCount ??= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The emoji of this answer
|
||||||
|
* @type {?(GuildEmoji|Emoji)}
|
||||||
|
*/
|
||||||
|
get emoji() {
|
||||||
|
if (!this._emoji || (!this._emoji.id && !this._emoji.name)) return null;
|
||||||
|
return this.client.emojis.resolve(this._emoji.id) ?? new Emoji(this.client, this._emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} FetchPollVotersOptions
|
||||||
|
* @property {number} [limit] The maximum number of voters to fetch
|
||||||
|
* @property {Snowflake} [after] The user id to fetch voters after
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the users that voted for this answer
|
||||||
|
* @param {FetchPollVotersOptions} [options={}] The options for fetching voters
|
||||||
|
* @returns {Promise<Collection<Snowflake, User>>}
|
||||||
|
*/
|
||||||
|
async fetchVoters({ after, limit } = {}) {
|
||||||
|
const { message } = this.poll;
|
||||||
|
|
||||||
|
const voters = await this.client.rest.get(Routes.pollAnswerVoters(message.channel.id, message.id, this.id), {
|
||||||
|
query: makeURLSearchParams({ limit, after }),
|
||||||
|
});
|
||||||
|
|
||||||
|
return voters.users.reduce((acc, user) => acc.set(user.id, this.client.users._add(user, false)), new Collection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.PollAnswer = PollAnswer;
|
|
@ -52,6 +52,23 @@ class TextBasedChannel {
|
||||||
return this.lastPinTimestamp && new Date(this.lastPinTimestamp);
|
return this.lastPinTimestamp && new Date(this.lastPinTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the data for a poll answer.
|
||||||
|
* @typedef {Object} PollAnswerData
|
||||||
|
* @property {string} text The text for the poll answer
|
||||||
|
* @property {EmojiIdentifierResolvable} [emoji] The emoji for the poll answer
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the data for a poll.
|
||||||
|
* @typedef {Object} PollData
|
||||||
|
* @property {PollQuestionMedia} question The question for the poll
|
||||||
|
* @property {PollAnswerData[]} answers The answers for the poll
|
||||||
|
* @property {number} duration The duration in hours for the poll
|
||||||
|
* @property {boolean} allowMultiselect Whether the poll allows multiple answers
|
||||||
|
* @property {PollLayoutType} [layoutType] The layout type for the poll
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base message options for messages.
|
* The base message options for messages.
|
||||||
* @typedef {Object} BaseMessageOptions
|
* @typedef {Object} BaseMessageOptions
|
||||||
|
@ -63,6 +80,7 @@ class TextBasedChannel {
|
||||||
* The files to send with the message.
|
* The files to send with the message.
|
||||||
* @property {Array<(ActionRowBuilder|ActionRow|APIActionRowComponent)>} [components]
|
* @property {Array<(ActionRowBuilder|ActionRow|APIActionRowComponent)>} [components]
|
||||||
* Action rows containing interactive components for the message (buttons, select menus)
|
* Action rows containing interactive components for the message (buttons, select menus)
|
||||||
|
* @property {PollData} [poll] The poll to send with the message
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -450,6 +450,11 @@
|
||||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-payloads/common#PermissionFlagsBits}
|
* @see {@link https://discord-api-types.dev/api/discord-api-types-payloads/common#PermissionFlagsBits}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @external PollLayoutType
|
||||||
|
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/PollLayoutType}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @external RoleFlags
|
* @external RoleFlags
|
||||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/RoleFlags}
|
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/RoleFlags}
|
||||||
|
|
|
@ -53,6 +53,8 @@
|
||||||
* @property {string} MessageBulkDelete messageDeleteBulk
|
* @property {string} MessageBulkDelete messageDeleteBulk
|
||||||
* @property {string} MessageCreate messageCreate
|
* @property {string} MessageCreate messageCreate
|
||||||
* @property {string} MessageDelete messageDelete
|
* @property {string} MessageDelete messageDelete
|
||||||
|
* @property {string} MessagePollVoteAdd messagePollVoteAdd
|
||||||
|
* @property {string} MessagePollVoteRemove messagePollVoteRemove
|
||||||
* @property {string} MessageReactionAdd messageReactionAdd
|
* @property {string} MessageReactionAdd messageReactionAdd
|
||||||
* @property {string} MessageReactionRemove messageReactionRemove
|
* @property {string} MessageReactionRemove messageReactionRemove
|
||||||
* @property {string} MessageReactionRemoveAll messageReactionRemoveAll
|
* @property {string} MessageReactionRemoveAll messageReactionRemoveAll
|
||||||
|
@ -138,6 +140,8 @@ module.exports = {
|
||||||
MessageBulkDelete: 'messageDeleteBulk',
|
MessageBulkDelete: 'messageDeleteBulk',
|
||||||
MessageCreate: 'messageCreate',
|
MessageCreate: 'messageCreate',
|
||||||
MessageDelete: 'messageDelete',
|
MessageDelete: 'messageDelete',
|
||||||
|
MessagePollVoteAdd: 'messagePollVoteAdd',
|
||||||
|
MessagePollVoteRemove: 'messagePollVoteRemove',
|
||||||
MessageReactionAdd: 'messageReactionAdd',
|
MessageReactionAdd: 'messageReactionAdd',
|
||||||
MessageReactionRemove: 'messageReactionRemove',
|
MessageReactionRemove: 'messageReactionRemove',
|
||||||
MessageReactionRemoveAll: 'messageReactionRemoveAll',
|
MessageReactionRemoveAll: 'messageReactionRemoveAll',
|
||||||
|
|
71
packages/discord.js/test/polls.js
Normal file
71
packages/discord.js/test/polls.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { token, owner } = require('./auth.js');
|
||||||
|
const { Client, Events, codeBlock, GatewayIntentBits } = require('../src');
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
intents: GatewayIntentBits.Guilds | GatewayIntentBits.GuildMessages | GatewayIntentBits.GuildMessagePolls,
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('raw', console.log);
|
||||||
|
|
||||||
|
client.on(Events.ClientReady, async () => {
|
||||||
|
const channel = client.channels.cache.get('1220510756286631968');
|
||||||
|
|
||||||
|
// const message = await channel.messages.fetch('1220680560414818325');
|
||||||
|
// console.dir(message.poll, { depth: Infinity });
|
||||||
|
|
||||||
|
// const answer = message.poll.answers.first();
|
||||||
|
// const voters = await answer.fetchVoters();
|
||||||
|
// console.dir(voters);
|
||||||
|
|
||||||
|
const message = await channel.send({
|
||||||
|
poll: {
|
||||||
|
question: {
|
||||||
|
text: 'What is your favorite color?',
|
||||||
|
},
|
||||||
|
answers: [{ text: 'Red' }, { text: 'Green' }, { text: 'Blue' }, { text: 'Yellow' }],
|
||||||
|
duration: 8,
|
||||||
|
allowMultiselect: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(message.poll);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on(Events.MessagePollVoteAdd, (answer, userId) => {
|
||||||
|
console.log(`User ${userId} voted for answer ${answer.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on(Events.MessagePollVoteRemove, (answer, userId) => {
|
||||||
|
console.log(`User ${userId} removed their vote for answer ${answer.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on(Events.MessageUpdate, async (_oldMessage, newMessage) => {
|
||||||
|
if (!newMessage.poll) return;
|
||||||
|
|
||||||
|
console.log('Poll was updated', newMessage.poll);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on(Events.MessageCreate, async message => {
|
||||||
|
const prefix = `<@${client.user.id}> `;
|
||||||
|
|
||||||
|
if (message.author.id !== owner || !message.content.startsWith(prefix)) return;
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await eval(message.content.slice(prefix.length));
|
||||||
|
if (typeof res !== 'string') res = require('node:util').inspect(res);
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err.stack);
|
||||||
|
res = err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.length > 2000) {
|
||||||
|
console.log(res);
|
||||||
|
res = 'Output too long, check the console.';
|
||||||
|
}
|
||||||
|
await message.channel.send(codeBlock('js', res));
|
||||||
|
});
|
||||||
|
|
||||||
|
client.login(token);
|
56
packages/discord.js/typings/index.d.ts
vendored
56
packages/discord.js/typings/index.d.ts
vendored
|
@ -175,6 +175,9 @@ import {
|
||||||
SKUType,
|
SKUType,
|
||||||
APIEntitlement,
|
APIEntitlement,
|
||||||
EntitlementType,
|
EntitlementType,
|
||||||
|
APIPoll,
|
||||||
|
PollLayoutType,
|
||||||
|
APIPollAnswer,
|
||||||
} from 'discord-api-types/v10';
|
} from 'discord-api-types/v10';
|
||||||
import { ChildProcess } from 'node:child_process';
|
import { ChildProcess } from 'node:child_process';
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
|
@ -2586,6 +2589,39 @@ export class Presence extends Base {
|
||||||
public equals(presence: Presence): boolean;
|
public equals(presence: Presence): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PollQuestionMedia {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Poll extends Base {
|
||||||
|
private constructor(client: Client<true>, data: APIPoll, message: Message);
|
||||||
|
public readonly message: Message;
|
||||||
|
public question: PollQuestionMedia;
|
||||||
|
public answers: Collection<number, PollAnswer>;
|
||||||
|
public expiresTimestamp: number;
|
||||||
|
public get expiresAt(): Date;
|
||||||
|
public allowMultiselect: boolean;
|
||||||
|
public layoutType: PollLayoutType;
|
||||||
|
public resultsFinalized: boolean;
|
||||||
|
public end(): Promise<Message>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchPollVotersOptions {
|
||||||
|
after?: Snowflake;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PollAnswer extends Base {
|
||||||
|
private constructor(client: Client<true>, data: APIPollAnswer & { count?: number }, poll: Poll);
|
||||||
|
private _emoji: APIPartialEmoji | null;
|
||||||
|
public readonly poll: Poll;
|
||||||
|
public id: number;
|
||||||
|
public text: string | null;
|
||||||
|
public voteCount: number;
|
||||||
|
public get emoji(): GuildEmoji | Emoji | null;
|
||||||
|
public fetchVoters(options?: FetchPollVotersOptions): Promise<Collection<Snowflake, User>>;
|
||||||
|
}
|
||||||
|
|
||||||
export class ReactionCollector extends Collector<Snowflake | string, MessageReaction, [User]> {
|
export class ReactionCollector extends Collector<Snowflake | string, MessageReaction, [User]> {
|
||||||
public constructor(message: Message, options?: ReactionCollectorOptions);
|
public constructor(message: Message, options?: ReactionCollectorOptions);
|
||||||
private _handleChannelDeletion(channel: NonThreadGuildBasedChannel): void;
|
private _handleChannelDeletion(channel: NonThreadGuildBasedChannel): void;
|
||||||
|
@ -3929,6 +3965,8 @@ export enum DiscordjsErrorCodes {
|
||||||
EntitlementCreateInvalidOwner = 'EntitlementCreateInvalidOwner',
|
EntitlementCreateInvalidOwner = 'EntitlementCreateInvalidOwner',
|
||||||
|
|
||||||
BulkBanUsersOptionEmpty = 'BulkBanUsersOptionEmpty',
|
BulkBanUsersOptionEmpty = 'BulkBanUsersOptionEmpty',
|
||||||
|
|
||||||
|
PollAlreadyExpired = 'PollAlreadyExpired',
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DiscordjsError extends Error {
|
export class DiscordjsError extends Error {
|
||||||
|
@ -4977,6 +5015,19 @@ export interface BulkBanResult {
|
||||||
failedUsers: readonly Snowflake[];
|
failedUsers: readonly Snowflake[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PollData {
|
||||||
|
question: PollQuestionMedia;
|
||||||
|
answers: readonly PollAnswerData[];
|
||||||
|
duration: number;
|
||||||
|
allowMultiselect: boolean;
|
||||||
|
layoutType?: PollLayoutType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PollAnswerData {
|
||||||
|
text: string;
|
||||||
|
emoji?: EmojiIdentifierResolvable;
|
||||||
|
}
|
||||||
|
|
||||||
export type Base64Resolvable = Buffer | Base64String;
|
export type Base64Resolvable = Buffer | Base64String;
|
||||||
|
|
||||||
export type Base64String = string;
|
export type Base64String = string;
|
||||||
|
@ -5146,6 +5197,8 @@ export interface ClientEvents {
|
||||||
inviteDelete: [invite: Invite];
|
inviteDelete: [invite: Invite];
|
||||||
messageCreate: [message: Message];
|
messageCreate: [message: Message];
|
||||||
messageDelete: [message: Message | PartialMessage];
|
messageDelete: [message: Message | PartialMessage];
|
||||||
|
messagePollVoteAdd: [pollAnswer: PollAnswer, userId: Snowflake];
|
||||||
|
messagePollVoteRemove: [pollAnswer: PollAnswer, userId: Snowflake];
|
||||||
messageReactionRemoveAll: [
|
messageReactionRemoveAll: [
|
||||||
message: Message | PartialMessage,
|
message: Message | PartialMessage,
|
||||||
reactions: ReadonlyCollection<string | Snowflake, MessageReaction>,
|
reactions: ReadonlyCollection<string | Snowflake, MessageReaction>,
|
||||||
|
@ -5372,6 +5425,8 @@ export enum Events {
|
||||||
MessageDelete = 'messageDelete',
|
MessageDelete = 'messageDelete',
|
||||||
MessageUpdate = 'messageUpdate',
|
MessageUpdate = 'messageUpdate',
|
||||||
MessageBulkDelete = 'messageDeleteBulk',
|
MessageBulkDelete = 'messageDeleteBulk',
|
||||||
|
MessagePollVoteAdd = 'messagePollVoteAdd',
|
||||||
|
MessagePollVoteRemove = 'messagePollVoteRemove',
|
||||||
MessageReactionAdd = 'messageReactionAdd',
|
MessageReactionAdd = 'messageReactionAdd',
|
||||||
MessageReactionRemove = 'messageReactionRemove',
|
MessageReactionRemove = 'messageReactionRemove',
|
||||||
MessageReactionRemoveAll = 'messageReactionRemoveAll',
|
MessageReactionRemoveAll = 'messageReactionRemoveAll',
|
||||||
|
@ -6244,6 +6299,7 @@ export interface BaseMessageOptions {
|
||||||
| ActionRowData<MessageActionRowComponentData | MessageActionRowComponentBuilder>
|
| ActionRowData<MessageActionRowComponentData | MessageActionRowComponentBuilder>
|
||||||
| APIActionRowComponent<APIMessageActionRowComponent>
|
| APIActionRowComponent<APIMessageActionRowComponent>
|
||||||
)[];
|
)[];
|
||||||
|
poll?: PollData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageCreateOptions extends BaseMessageOptions {
|
export interface MessageCreateOptions extends BaseMessageOptions {
|
||||||
|
|
|
@ -204,6 +204,7 @@ import {
|
||||||
RoleSelectMenuComponent,
|
RoleSelectMenuComponent,
|
||||||
ChannelSelectMenuComponent,
|
ChannelSelectMenuComponent,
|
||||||
MentionableSelectMenuComponent,
|
MentionableSelectMenuComponent,
|
||||||
|
Poll,
|
||||||
} from '.';
|
} from '.';
|
||||||
import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd';
|
import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd';
|
||||||
import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders';
|
import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders';
|
||||||
|
@ -2525,3 +2526,24 @@ declare const sku: SKU;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await textChannel.send({
|
||||||
|
poll: {
|
||||||
|
question: {
|
||||||
|
text: 'Question',
|
||||||
|
},
|
||||||
|
duration: 60,
|
||||||
|
answers: [{ text: 'Answer 1' }, { text: 'Answer 2', emoji: '<:1blade:874989932983238726>' }],
|
||||||
|
allowMultiselect: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
declare const poll: Poll;
|
||||||
|
{
|
||||||
|
expectType<Message>(await poll.end());
|
||||||
|
|
||||||
|
const answer = poll.answers.first()!;
|
||||||
|
expectType<number>(answer.voteCount);
|
||||||
|
|
||||||
|
expectType<Collection<Snowflake, User>>(await answer.fetchVoters({ after: snowflake, limit: 10 }));
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue