feat: polls
Added support for polls similar to discord.js v14 (including class, event). Breaking change: Do not use and remove MessagePoll.
This commit is contained in:
@@ -1,38 +1,37 @@
|
|||||||
const { Client, MessagePoll } = require('../src/index');
|
const { Client } = require('../src/index');
|
||||||
const client = new Client();
|
const client = new Client();
|
||||||
|
|
||||||
client.on('ready', async () => {
|
client.on('ready', async () => {
|
||||||
console.log(`${client.user.username} is ready!`);
|
console.log(`${client.user.username} is ready!`);
|
||||||
const channel = client.channels.cache.get('channel id');
|
const channel = client.channels.cache.get('channel id');
|
||||||
const poll = new MessagePoll();
|
const message = await channel.send({
|
||||||
poll
|
poll: {
|
||||||
.setQuestion('Test question')
|
question: {
|
||||||
.setAnswers([
|
text: 'What is your favorite color?',
|
||||||
{
|
|
||||||
text: 'answer 1',
|
|
||||||
emoji: {
|
|
||||||
name: '🎈',
|
|
||||||
},
|
},
|
||||||
|
answers: [{ text: 'Red', emoji: '🍎' }, { text: 'Green', emoji: '🥗' }, { text: 'Blue', emoji: '💙' }, { text: 'Yellow', emoji: '🟡' }],
|
||||||
|
duration: 8,
|
||||||
|
allowMultiselect: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: 'answer 2',
|
|
||||||
emoji: {
|
|
||||||
name: '🎃',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'answer 3',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
.setAllowMultiSelect(true)
|
|
||||||
.setDuration(4); // 4h
|
|
||||||
const msg = await channel.send({
|
|
||||||
poll,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(message.poll);
|
||||||
// Multi select
|
// Multi select
|
||||||
await msg.vote(1,3);
|
await message.vote(1, 3);
|
||||||
// End poll
|
});
|
||||||
await msg.endPoll();
|
|
||||||
|
client.on('messagePollVoteAdd', (answer, userId) => {
|
||||||
|
console.log(`User ${userId} voted for answer ${answer.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('messagePollVoteRemove', (answer, userId) => {
|
||||||
|
console.log(`User ${userId} removed their vote for answer ${answer.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('messageUpdate', async (_oldMessage, newMessage) => {
|
||||||
|
if (!newMessage.poll) return;
|
||||||
|
|
||||||
|
console.log('Poll was updated', newMessage.poll);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.login('token');
|
client.login('token');
|
||||||
|
|||||||
@@ -55,13 +55,13 @@
|
|||||||
"@discordjs/collection": "^1.5.3",
|
"@discordjs/collection": "^1.5.3",
|
||||||
"@sapphire/async-queue": "^1.5.3",
|
"@sapphire/async-queue": "^1.5.3",
|
||||||
"@sapphire/shapeshift": "^3.9.5",
|
"@sapphire/shapeshift": "^3.9.5",
|
||||||
"discord-api-types": "^0.37.61",
|
"discord-api-types": "^0.37.103",
|
||||||
"fetch-cookie": "^2.1.0",
|
"fetch-cookie": "^2.1.0",
|
||||||
"find-process": "^1.4.7",
|
"find-process": "^1.4.7",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.1",
|
||||||
"node-fetch": "^2.6.9",
|
"node-fetch": "^2.6.9",
|
||||||
"prism-media": "^1.3.5",
|
"prism-media": "^1.3.5",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.4",
|
||||||
"tough-cookie": "^4.1.4",
|
"tough-cookie": "^4.1.4",
|
||||||
"tree-kill": "^1.2.2",
|
"tree-kill": "^1.2.2",
|
||||||
"ws": "^8.16.0"
|
"ws": "^8.16.0"
|
||||||
|
|||||||
8487
pnpm-lock.yaml
generated
Normal file
8487
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -50,6 +50,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
src/client/actions/MessagePollVoteAdd.js
Normal file
33
src/client/actions/MessagePollVoteAdd.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Action = require('./Action');
|
||||||
|
const { Events } = require('../../util/Constants');
|
||||||
|
|
||||||
|
class MessagePollVoteAddAction extends Action {
|
||||||
|
handle(data) {
|
||||||
|
const channel = this.getChannel(data);
|
||||||
|
if (!channel?.isText()) 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.MESSAGE_POLL_VOTE_ADD, answer, data.user_id);
|
||||||
|
|
||||||
|
return { poll };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MessagePollVoteAddAction;
|
||||||
33
src/client/actions/MessagePollVoteRemove.js
Normal file
33
src/client/actions/MessagePollVoteRemove.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Action = require('./Action');
|
||||||
|
const { Events } = require('../../util/Constants');
|
||||||
|
|
||||||
|
class MessagePollVoteRemoveAction extends Action {
|
||||||
|
handle(data) {
|
||||||
|
const channel = this.getChannel(data);
|
||||||
|
if (!channel?.isText()) 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.MESSAGE_POLL_VOTE_REMOVE, answer, data.user_id);
|
||||||
|
|
||||||
|
return { poll };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MessagePollVoteRemoveAction;
|
||||||
@@ -1,22 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { Events } = require('../../../util/Constants');
|
module.exports = (client, packet) => {
|
||||||
|
client.actions.MessagePollVoteAdd.handle(packet.d);
|
||||||
module.exports = (client, { d: data }) => {
|
|
||||||
/**
|
|
||||||
* Poll Vote Structure
|
|
||||||
* @see {@link https://docs.discord.sex/resources/message#poll-results-structure}
|
|
||||||
* @typedef {Object} MessagePollUserVote
|
|
||||||
* @property {Snowflake} user_id ID of the user
|
|
||||||
* @property {Snowflake} channel_id ID of the channel
|
|
||||||
* @property {Snowflake} message_id ID of the message
|
|
||||||
* @property {?Snowflake} guild_id ID of the guild
|
|
||||||
* @property {number} answer_id ID of the answer
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Emitted when a user votes on a poll. If the poll allows multiple selection, one event will be sent per answer.
|
|
||||||
* @event Client#messagePollVoteAdd
|
|
||||||
* @param {MessagePollUserVote} data Raw data
|
|
||||||
*/
|
|
||||||
client.emit(Events.MESSAGE_POLL_VOTE_ADD, data);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { Events } = require('../../../util/Constants');
|
module.exports = (client, packet) => {
|
||||||
|
client.actions.MessagePollVoteRemove.handle(packet.d);
|
||||||
module.exports = (client, { d: data }) => {
|
|
||||||
/**
|
|
||||||
* Emitted when a user removes their vote on a poll. If the poll allows for multiple selections, one event will be sent per answer.
|
|
||||||
* @event Client#messagePollVoteRemove
|
|
||||||
* @param {MessagePollUserVote} data Raw data
|
|
||||||
*/
|
|
||||||
client.emit(Events.MESSAGE_POLL_VOTE_REMOVE, data);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ const Messages = {
|
|||||||
STREAM_CONNECTION_READONLY: 'Cannot send data to a read-only stream',
|
STREAM_CONNECTION_READONLY: 'Cannot send data to a read-only stream',
|
||||||
STREAM_CANNOT_JOIN: 'Cannot join a stream to itself',
|
STREAM_CANNOT_JOIN: 'Cannot join a stream to itself',
|
||||||
VOICE_USER_NOT_STREAMING: 'User is not streaming',
|
VOICE_USER_NOT_STREAMING: 'User is not streaming',
|
||||||
|
POLL_ALREADY_EXPIRED: 'This poll has already expired.',
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [name, message] of Object.entries(Messages)) register(name, message);
|
for (const [name, message] of Object.entries(Messages)) register(name, message);
|
||||||
|
|||||||
@@ -163,4 +163,5 @@ exports.SpotifyRPC = require('./structures/Presence').SpotifyRPC;
|
|||||||
exports.WebEmbed = require('./structures/WebEmbed');
|
exports.WebEmbed = require('./structures/WebEmbed');
|
||||||
exports.DiscordAuthWebsocket = require('./util/RemoteAuth');
|
exports.DiscordAuthWebsocket = require('./util/RemoteAuth');
|
||||||
exports.PurchasedFlags = require('./util/PurchasedFlags');
|
exports.PurchasedFlags = require('./util/PurchasedFlags');
|
||||||
exports.MessagePoll = require('./structures/MessagePoll');
|
exports.Poll = require('./structures/Poll').Poll;
|
||||||
|
exports.PollAnswer = require('./structures/PollAnswer').PollAnswer;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const MessageAttachment = require('./MessageAttachment');
|
|||||||
const Embed = require('./MessageEmbed');
|
const Embed = require('./MessageEmbed');
|
||||||
const Mentions = require('./MessageMentions');
|
const Mentions = require('./MessageMentions');
|
||||||
const MessagePayload = require('./MessagePayload');
|
const MessagePayload = require('./MessagePayload');
|
||||||
const MessagePoll = require('./MessagePoll');
|
const { Poll } = require('./Poll');
|
||||||
const ReactionCollector = require('./ReactionCollector');
|
const ReactionCollector = require('./ReactionCollector');
|
||||||
const { Sticker } = require('./Sticker');
|
const { Sticker } = require('./Sticker');
|
||||||
const Application = require('./interfaces/Application');
|
const Application = require('./interfaces/Application');
|
||||||
@@ -257,14 +257,14 @@ class Message extends Base {
|
|||||||
this.webhookId ??= null;
|
this.webhookId ??= null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.poll) {
|
||||||
/**
|
/**
|
||||||
* The poll that was sent with the message
|
* The poll that was sent with the message
|
||||||
* @type {?MessagePoll}
|
* @type {?Poll}
|
||||||
*/
|
*/
|
||||||
if ('poll' in data) {
|
this.poll = new Poll(this.client, data.poll, this);
|
||||||
this.poll = new MessagePoll(data.poll, this.client);
|
|
||||||
} else {
|
} else {
|
||||||
this.poll = null;
|
this.poll ??= null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('application' in data) {
|
if ('application' in data) {
|
||||||
@@ -941,32 +941,6 @@ class Message extends Base {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Immediately ends the poll. You cannot end polls from other users.
|
|
||||||
* @returns {Promise<this>}
|
|
||||||
* @deprecated Using MessageManager#endPoll(messageId) instead
|
|
||||||
*/
|
|
||||||
endPoll() {
|
|
||||||
return this.channel.messages.endPoll(this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of users that voted for this specific answer.
|
|
||||||
* @param {number} answerId Answer Id
|
|
||||||
* @param {Snowflake} [afterUserId] Get users after this user ID
|
|
||||||
* @param {number} [limit=25] Max number of users to return (1-100, default 25)
|
|
||||||
* @returns {Promise<Collection<Snowflake, User>>}
|
|
||||||
* @deprecated Using MessageManager#fetchPollAnswerVoters({ messageId, answerId, after, limit }) instead
|
|
||||||
*/
|
|
||||||
getAnswerVoter(answerId, afterUserId, limit = 25) {
|
|
||||||
return this.channel.messages.fetchPollAnswerVoters({
|
|
||||||
messageId: this.id,
|
|
||||||
answerId,
|
|
||||||
after: afterUserId,
|
|
||||||
limit,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch this message.
|
* Fetch this message.
|
||||||
* @param {boolean} [force=true] Whether to skip the cache check and request the API
|
* @param {boolean} [force=true] Whether to skip the cache check and request the API
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
const { Buffer } = require('node:buffer');
|
const { Buffer } = require('node:buffer');
|
||||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||||
const MessageEmbed = require('./MessageEmbed');
|
const MessageEmbed = require('./MessageEmbed');
|
||||||
const MessagePoll = require('./MessagePoll');
|
|
||||||
const { RangeError } = require('../errors');
|
const { RangeError } = require('../errors');
|
||||||
const ActivityFlags = require('../util/ActivityFlags');
|
const ActivityFlags = require('../util/ActivityFlags');
|
||||||
|
const { PollLayoutTypes } = require('../util/Constants');
|
||||||
const DataResolver = require('../util/DataResolver');
|
const DataResolver = require('../util/DataResolver');
|
||||||
const MessageFlags = require('../util/MessageFlags');
|
const MessageFlags = require('../util/MessageFlags');
|
||||||
const Util = require('../util/Util');
|
const Util = require('../util/Util');
|
||||||
@@ -220,6 +220,24 @@ class MessagePayload {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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: Util.resolvePartialEmoji(answer.emoji) },
|
||||||
|
})),
|
||||||
|
duration: this.options.poll.duration,
|
||||||
|
allow_multiselect: this.options.poll.allowMultiselect,
|
||||||
|
layout_type:
|
||||||
|
typeof this.options.poll.layoutType == 'number'
|
||||||
|
? this.options.poll.layoutType
|
||||||
|
: PollLayoutTypes[this.options.poll.layoutType],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
this.data = {
|
this.data = {
|
||||||
activity,
|
activity,
|
||||||
content,
|
content,
|
||||||
@@ -237,7 +255,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: this.options.poll instanceof MessagePoll ? this.options.poll.toJSON() : this.options.poll,
|
poll,
|
||||||
};
|
};
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,238 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const { Collection } = require('@discordjs/collection');
|
|
||||||
const { MessagePollLayoutTypes } = require('../util/Constants');
|
|
||||||
const Util = require('../util/Util');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the poll object has a lot of levels and nested structures. It was also designed to support future extensibility, so some fields may appear to be more complex than necessary.
|
|
||||||
*/
|
|
||||||
class MessagePoll {
|
|
||||||
/**
|
|
||||||
* @param {Object} data Message poll to clone or raw data
|
|
||||||
*/
|
|
||||||
constructor(data = {}) {
|
|
||||||
this._patch(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
_patch(data = {}) {
|
|
||||||
if (data?.constructor?.name == 'MessagePoll') data = data.toJSON();
|
|
||||||
/**
|
|
||||||
* The poll media object is a common object that backs both the question and answers. For now, `question` only supports `text`, while `answers` can have an optional `emoji`.
|
|
||||||
* @see {@link https://docs.discord.sex/resources/message#poll-media-structure}
|
|
||||||
* @typedef {Object} MessagePollMedia
|
|
||||||
* @property {?string} text The text of the field (max 300 characters for question, 55 characters for answer)
|
|
||||||
* @property {?RawEmoji} emoji The emoji of the field
|
|
||||||
*/
|
|
||||||
|
|
||||||
if ('question' in data) {
|
|
||||||
/**
|
|
||||||
* The question of the poll
|
|
||||||
* @type {?MessagePollMedia}
|
|
||||||
*/
|
|
||||||
this.question = this._resolvePollMedia(data.question);
|
|
||||||
} else {
|
|
||||||
this.question ??= null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.answers?.length) {
|
|
||||||
/**
|
|
||||||
* The answers available in the poll
|
|
||||||
* @type {Collection<number, MessagePollMedia>}
|
|
||||||
*/
|
|
||||||
this.answers = new Collection();
|
|
||||||
|
|
||||||
data.answers.forEach((obj, index) => {
|
|
||||||
this.answers.set(obj?.answer_id || index + 1, this._resolvePollMedia(obj.poll_media));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.answers ??= new Collection();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('layout_type' in data) {
|
|
||||||
/**
|
|
||||||
* The layout type of the poll
|
|
||||||
* @type {?MessagePollLayoutTypes}
|
|
||||||
*/
|
|
||||||
this.layoutType = MessagePollLayoutTypes[data.layout_type];
|
|
||||||
} else {
|
|
||||||
this.layoutType ??= MessagePollLayoutTypes[1]; // Default type
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('allow_multiselect' in data) {
|
|
||||||
/**
|
|
||||||
* Whether a user can select multiple answers
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
this.allowMultiSelect = !!data.allow_multiselect;
|
|
||||||
} else {
|
|
||||||
this.allowMultiSelect ??= false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('expiry' in data) {
|
|
||||||
/**
|
|
||||||
* When the poll ends
|
|
||||||
* @type {?Date}
|
|
||||||
*/
|
|
||||||
this.expiry = new Date(data.expiry);
|
|
||||||
} else {
|
|
||||||
this.expiry ??= null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('duration' in data) {
|
|
||||||
/**
|
|
||||||
* Number of hours the poll should be open for (max 32 days, default 1)
|
|
||||||
* @type {?Number}
|
|
||||||
*/
|
|
||||||
this.duration = data.duration;
|
|
||||||
} else {
|
|
||||||
this.duration ??= null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('results' in data) {
|
|
||||||
/**
|
|
||||||
* Poll Results Structure
|
|
||||||
* @see {@link https://docs.discord.sex/resources/message#poll-results-structure}
|
|
||||||
* @typedef {Object} MessagePollResult
|
|
||||||
* @property {boolean} isFinalized Whether the votes have been precisely counted
|
|
||||||
* @property {Collection<number, MessagePollResultAnswerCount>} answerCounts The counts for each answer
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Poll Answer Count Structure
|
|
||||||
* @see {@link https://docs.discord.sex/resources/message#poll-answer-count-structure}
|
|
||||||
* @typedef {Object} MessagePollResultAnswerCount
|
|
||||||
* @property {MessagePollMedia} answer answer
|
|
||||||
* @property {number} count The number of votes for this answer
|
|
||||||
* @property {boolean} selfVoted Whether the current user voted for this answer
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* In a nutshell, this contains the number of votes for each answer.
|
|
||||||
* The `results` field may be not present in certain responses where, as an implementation detail,
|
|
||||||
* Discord does not fetch the poll results in the backend.
|
|
||||||
* This should be treated as "unknown results", as opposed to "no results".
|
|
||||||
* You can keep using the results if you have previously received them through other means.
|
|
||||||
* Due to the intricacies of counting at scale, while a poll is in progress the results may not
|
|
||||||
* be perfectly accurate. They usually are accurate, and shouldn't deviate significantly — it's
|
|
||||||
* just difficult to make guarantees. To compensate for this, after a poll is finished there is
|
|
||||||
* a background job which performs a final, accurate tally of votes. This tally concludes once
|
|
||||||
* `is_finalized` is `true`. Polls that have ended will also always contain results.
|
|
||||||
* If `answer_counts` does not contain an entry for a particular answer, then there are no votes
|
|
||||||
* for that answer.
|
|
||||||
* @type {?MessagePollResult}
|
|
||||||
*/
|
|
||||||
this.results = {
|
|
||||||
isFinalized: data.results.is_finalized,
|
|
||||||
answerCounts: new Collection(),
|
|
||||||
};
|
|
||||||
data.results.answer_counts.forEach(obj => {
|
|
||||||
this.results.answerCounts.set(obj.id, {
|
|
||||||
count: obj.count,
|
|
||||||
selfVoted: obj.me_voted,
|
|
||||||
answer: this.answers.get(obj.id),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.results ??= null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_resolvePollMedia(obj) {
|
|
||||||
return {
|
|
||||||
text: obj.text,
|
|
||||||
emoji: Util.resolvePartialEmoji(obj.emoji),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert to JSON
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
toJSON() {
|
|
||||||
const data = {
|
|
||||||
question: {
|
|
||||||
text: this.question.text,
|
|
||||||
emoji: this.question.emoji,
|
|
||||||
},
|
|
||||||
expiry: this.expiry?.toISOString(),
|
|
||||||
allow_multiselect: this.allowMultiSelect,
|
|
||||||
layout_type: typeof this.layoutType == 'number' ? this.layoutType : MessagePollLayoutTypes[this.layoutType],
|
|
||||||
answers: Array.from(this.answers.entries()).map(([id, data]) => ({
|
|
||||||
answer_id: id,
|
|
||||||
poll_media: {
|
|
||||||
text: data.text,
|
|
||||||
emoji: data.emoji,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
duration: this.duration ?? 1,
|
|
||||||
};
|
|
||||||
if (this.results) {
|
|
||||||
data.results = {
|
|
||||||
is_finalized: this.results.isFinalized,
|
|
||||||
answer_counts: Array.from(this.results.answerCounts.entries()).map(([id, data]) => ({
|
|
||||||
id: id,
|
|
||||||
count: data.count,
|
|
||||||
me_voted: data.selfVoted,
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set question
|
|
||||||
* @param {string} text question
|
|
||||||
* @returns {MessagePoll}
|
|
||||||
*/
|
|
||||||
setQuestion(text) {
|
|
||||||
this.question = {
|
|
||||||
text,
|
|
||||||
};
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set answers
|
|
||||||
* @param {MessagePollMedia[]} answers answers
|
|
||||||
* @returns {MessagePoll}
|
|
||||||
*/
|
|
||||||
setAnswers(answers) {
|
|
||||||
this.answers.clear();
|
|
||||||
answers.forEach((obj, index) => {
|
|
||||||
this.answers.set(index + 1, this._resolvePollMedia(obj));
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add answer
|
|
||||||
* @param {MessagePollMedia} answer answer
|
|
||||||
* @returns {MessagePoll}
|
|
||||||
*/
|
|
||||||
addAnswer(answer) {
|
|
||||||
this.answers.set(this.answers.size + 1, answer);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set allow multi select
|
|
||||||
* @param {boolean} state state
|
|
||||||
* @returns {MessagePoll}
|
|
||||||
*/
|
|
||||||
setAllowMultiSelect(state) {
|
|
||||||
this.allowMultiSelect = state;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set duration
|
|
||||||
* @param {number} duration duration (hours)
|
|
||||||
* @returns {MessagePoll}
|
|
||||||
*/
|
|
||||||
setDuration(duration) {
|
|
||||||
// [1, 4, 8, 24, 72, 168, 336];
|
|
||||||
this.duration = duration;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MessagePoll;
|
|
||||||
108
src/structures/Poll.js
Normal file
108
src/structures/Poll.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Collection } = require('@discordjs/collection');
|
||||||
|
const Base = require('./Base');
|
||||||
|
const { PollAnswer } = require('./PollAnswer');
|
||||||
|
const { Error } = require('../errors');
|
||||||
|
const { PollLayoutTypes } = require('../util/Constants');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = PollLayoutTypes[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends this poll.
|
||||||
|
* @returns {Promise<Message>}
|
||||||
|
*/
|
||||||
|
end() {
|
||||||
|
if (Date.now() > this.expiresTimestamp) {
|
||||||
|
return Promise.reject(new Error('POLL_ALREADY_EXPIRED'));
|
||||||
|
}
|
||||||
|
return this.message.channel.messages.endPoll(this.message.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { Poll };
|
||||||
88
src/structures/PollAnswer.js
Normal file
88
src/structures/PollAnswer.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
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>>}
|
||||||
|
*/
|
||||||
|
fetchVoters({ after, limit } = {}) {
|
||||||
|
return this.poll.message.channel.messages.fetchPollAnswerVoters({
|
||||||
|
messageId: this.poll.message.id,
|
||||||
|
answerId: this.id,
|
||||||
|
after,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { PollAnswer };
|
||||||
@@ -111,7 +111,7 @@ class Webhook {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Options that can be passed into send.
|
* Options that can be passed into send.
|
||||||
* @typedef {BaseMessageOptions} WebhookMessageOptions
|
* @typedef {BaseMessageOptionsWithPoll} WebhookMessageOptions
|
||||||
* @property {string} [username=this.name] Username override for the message
|
* @property {string} [username=this.name] Username override for the message
|
||||||
* @property {string} [avatarURL] Avatar URL override for the message
|
* @property {string} [avatarURL] Avatar URL override for the message
|
||||||
* @property {Snowflake} [threadId] The id of the thread in the channel to send to.
|
* @property {Snowflake} [threadId] The id of the thread in the channel to send to.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class InteractionResponses {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for a reply to an {@link Interaction}.
|
* Options for a reply to an {@link Interaction}.
|
||||||
* @typedef {BaseMessageOptions} InteractionReplyOptions
|
* @typedef {BaseMessageOptionsWithPoll} InteractionReplyOptions
|
||||||
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
|
* @property {boolean} [ephemeral] Whether the reply should be ephemeral
|
||||||
* @property {boolean} [fetchReply] Whether to fetch the reply
|
* @property {boolean} [fetchReply] Whether to fetch the reply
|
||||||
* @property {MessageFlags} [flags] Which flags to set for the message.
|
* @property {MessageFlags} [flags] Which flags to set for the message.
|
||||||
|
|||||||
@@ -60,6 +60,28 @@ class TextBasedChannel {
|
|||||||
return this.lastPinTimestamp ? new Date(this.lastPinTimestamp) : null;
|
return this.lastPinTimestamp ? new Date(this.lastPinTimestamp) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @external PollLayoutType
|
||||||
|
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/PollLayoutType}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base options provided when sending.
|
* Base options provided when sending.
|
||||||
* @typedef {Object} BaseMessageOptions
|
* @typedef {Object} BaseMessageOptions
|
||||||
@@ -75,7 +97,12 @@ class TextBasedChannel {
|
|||||||
* @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
|
* @property {Array<(MessageActionRow|MessageActionRowOptions)>} [components]
|
||||||
* Action rows containing interactive components for the message (buttons, select menus)
|
* Action rows containing interactive components for the message (buttons, select menus)
|
||||||
* @property {MessageAttachment[]} [attachments] Attachments to send in the message
|
* @property {MessageAttachment[]} [attachments] Attachments to send in the message
|
||||||
* @property {MessagePoll} [poll] A poll!
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base message options for messages including a poll.
|
||||||
|
* @typedef {BaseMessageOptions} BaseMessageOptionsWithPoll
|
||||||
|
* @property {PollData} [poll] The poll to send with the message
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1768,10 +1768,10 @@ exports.ForumLayoutTypes = createEnum(['NOT_SET', 'LIST_VIEW', 'GALLERY_VIEW']);
|
|||||||
* Different layouts for {@link MessagePoll} will come in the future. For now though, this value will always be `DEFAULT`.
|
* Different layouts for {@link MessagePoll} will come in the future. For now though, this value will always be `DEFAULT`.
|
||||||
* * DEFAULT
|
* * DEFAULT
|
||||||
* * IMAGE_ONLY_ANSWERS
|
* * IMAGE_ONLY_ANSWERS
|
||||||
* @typedef {string} MessagePollLayoutType
|
* @typedef {string} PollLayoutType
|
||||||
* @see {@link https://docs.discord.sex/resources/message#poll-layout-type}
|
* @see {@link https://docs.discord.sex/resources/message#poll-layout-type}
|
||||||
*/
|
*/
|
||||||
exports.MessagePollLayoutTypes = createEnum([null, 'DEFAULT', 'IMAGE_ONLY_ANSWERS']);
|
exports.PollLayoutTypes = createEnum([null, 'DEFAULT', 'IMAGE_ONLY_ANSWERS']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Relationship Enums:
|
* Relationship Enums:
|
||||||
|
|||||||
2
typings/enums.d.ts
vendored
2
typings/enums.d.ts
vendored
@@ -88,7 +88,7 @@ export const enum ForumLayoutTypes {
|
|||||||
GALLERY_VIEW = 2,
|
GALLERY_VIEW = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum MessagePollLayoutTypes {
|
export const enum PollLayoutTypes {
|
||||||
DEFAULT = 1,
|
DEFAULT = 1,
|
||||||
IMAGE_ONLY_ANSWERS,
|
IMAGE_ONLY_ANSWERS,
|
||||||
}
|
}
|
||||||
|
|||||||
106
typings/index.d.ts
vendored
106
typings/index.d.ts
vendored
@@ -55,6 +55,8 @@ import {
|
|||||||
APIGuildMember,
|
APIGuildMember,
|
||||||
APIChannel,
|
APIChannel,
|
||||||
TeamMemberRole,
|
TeamMemberRole,
|
||||||
|
APIPoll,
|
||||||
|
APIPollAnswer,
|
||||||
} from 'discord-api-types/v9';
|
} from 'discord-api-types/v9';
|
||||||
import { ChildProcess, ChildProcessWithoutNullStreams } from 'node:child_process';
|
import { ChildProcess, ChildProcessWithoutNullStreams } from 'node:child_process';
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
@@ -102,7 +104,7 @@ import {
|
|||||||
RelationshipTypes,
|
RelationshipTypes,
|
||||||
SelectMenuComponentTypes,
|
SelectMenuComponentTypes,
|
||||||
InviteTypes,
|
InviteTypes,
|
||||||
MessagePollLayoutTypes,
|
PollLayoutTypes,
|
||||||
ReactionTypes,
|
ReactionTypes,
|
||||||
MessageReferenceTypes,
|
MessageReferenceTypes,
|
||||||
} from './enums';
|
} from './enums';
|
||||||
@@ -2115,7 +2117,7 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
|||||||
public type: MessageType;
|
public type: MessageType;
|
||||||
public readonly url: string;
|
public readonly url: string;
|
||||||
public webhookId: Snowflake | null;
|
public webhookId: Snowflake | null;
|
||||||
public poll: MessagePoll | null;
|
public poll: Poll | null;
|
||||||
public call: MessageCall | null;
|
public call: MessageCall | null;
|
||||||
public flags: Readonly<MessageFlags>;
|
public flags: Readonly<MessageFlags>;
|
||||||
public reference: MessageReference | null;
|
public reference: MessageReference | null;
|
||||||
@@ -2153,14 +2155,6 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
|||||||
public markRead(): Promise<void>;
|
public markRead(): Promise<void>;
|
||||||
public report(breadcrumbs: number[], elements?: object): Promise<{ report_id: Snowflake }>;
|
public report(breadcrumbs: number[], elements?: object): Promise<{ report_id: Snowflake }>;
|
||||||
public vote(...ids: number[]): Promise<void>;
|
public vote(...ids: number[]): Promise<void>;
|
||||||
/** @deprecated Using MessageManager#endPoll(messageId) instead */
|
|
||||||
public endPoll(): Promise<this>;
|
|
||||||
/** @deprecated Using MessageManager#fetchPollAnswerVoters({ messageId, answerId, after, limit }) instead */
|
|
||||||
public getAnswerVoter(
|
|
||||||
answerId: number,
|
|
||||||
afterUserId?: Snowflake,
|
|
||||||
limit?: number,
|
|
||||||
): Promise<Collection<Snowflake, User>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CallState extends Base {
|
export class CallState extends Base {
|
||||||
@@ -2171,15 +2165,6 @@ export class CallState extends Base {
|
|||||||
public readonly ringing: Collection<Snowflake, User>;
|
public readonly ringing: Collection<Snowflake, User>;
|
||||||
public setRTCRegion(): Promise<void>;
|
public setRTCRegion(): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessagePollUserVote {
|
|
||||||
user_id: Snowflake;
|
|
||||||
message_id: Snowflake;
|
|
||||||
channel_id: Snowflake;
|
|
||||||
answer_id: number;
|
|
||||||
guild_id?: Snowflake;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MessageActionRow<
|
export class MessageActionRow<
|
||||||
T extends MessageActionRowComponent | ModalActionRowComponent = MessageActionRowComponent,
|
T extends MessageActionRowComponent | ModalActionRowComponent = MessageActionRowComponent,
|
||||||
U = T extends ModalActionRowComponent ? ModalActionRowComponentResolvable : MessageActionRowComponentResolvable,
|
U = T extends ModalActionRowComponent ? ModalActionRowComponentResolvable : MessageActionRowComponentResolvable,
|
||||||
@@ -2557,37 +2542,55 @@ export class Modal {
|
|||||||
public toJSON(): RawModalSubmitInteractionData;
|
public toJSON(): RawModalSubmitInteractionData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessagePollMedia {
|
export interface PollQuestionMedia {
|
||||||
text?: string;
|
text: string;
|
||||||
emoji?: RawEmojiData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessagePollResultAnswerCount {
|
export class Poll extends Base {
|
||||||
answer: MessagePollMedia;
|
private constructor(client: Client<true>, data: APIPoll, message: Message);
|
||||||
count: number;
|
public readonly message: Message;
|
||||||
selfVoted: boolean;
|
public question: PollQuestionMedia;
|
||||||
|
public answers: Collection<number, PollAnswer>;
|
||||||
|
public expiresTimestamp: number;
|
||||||
|
public get expiresAt(): Date;
|
||||||
|
public allowMultiselect: boolean;
|
||||||
|
public layoutType: PollLayoutTypes;
|
||||||
|
public resultsFinalized: boolean;
|
||||||
|
public end(): Promise<Message>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessagePollResult {
|
export class PollAnswer extends Base {
|
||||||
isFinalized: boolean;
|
private constructor(client: Client<true>, data: APIPollAnswer & { count?: number }, poll: Poll);
|
||||||
answerCounts: Collection<number, MessagePollResultAnswerCount>;
|
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?: BaseFetchPollAnswerVotersOptions): Promise<Collection<Snowflake, User>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MessagePoll {
|
export interface PollData {
|
||||||
public constructor(data: MessagePoll | object);
|
question: PollQuestionMedia;
|
||||||
public question: MessagePollMedia | null;
|
answers: readonly PollAnswerData[];
|
||||||
public answers: Collection<number, MessagePollMedia>;
|
duration: number;
|
||||||
public layoutType: MessagePollLayoutTypes | null;
|
allowMultiselect: boolean;
|
||||||
public allowMultiSelect: boolean;
|
layoutType?: PollLayoutTypes;
|
||||||
public expiry: Date | null;
|
}
|
||||||
public results: MessagePollResult | null;
|
|
||||||
public duration: number | null;
|
export interface PollAnswerData {
|
||||||
public toJSON(): object;
|
text: string;
|
||||||
public setQuestion(text: string): this;
|
emoji?: EmojiIdentifierResolvable;
|
||||||
public setAnswers(answers: MessagePollMedia[]): this;
|
}
|
||||||
public addAnswer(answer: MessagePollMedia): this;
|
|
||||||
public setAllowMultiSelect(state: boolean): this;
|
export interface BaseFetchPollAnswerVotersOptions {
|
||||||
public setDuration(duration: number): this;
|
after?: Snowflake;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchPollAnswerVotersOptions extends BaseFetchPollAnswerVotersOptions {
|
||||||
|
messageId: Snowflake;
|
||||||
|
answerId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MessageCall {
|
export interface MessageCall {
|
||||||
@@ -4478,15 +4481,6 @@ export class GuildMemberRoleManager extends DataManager<Snowflake, Role, RoleRes
|
|||||||
): Promise<GuildMember>;
|
): Promise<GuildMember>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseFetchPollAnswerVotersOptions {
|
|
||||||
after?: Snowflake;
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FetchPollAnswerVotersOptions extends BaseFetchPollAnswerVotersOptions {
|
|
||||||
messageId: Snowflake;
|
|
||||||
answerId: number;
|
|
||||||
}
|
|
||||||
export class MessageManager extends CachedManager<Snowflake, Message, MessageResolvable> {
|
export class MessageManager extends CachedManager<Snowflake, Message, MessageResolvable> {
|
||||||
private constructor(channel: TextBasedChannel, iterable?: Iterable<RawMessageData>);
|
private constructor(channel: TextBasedChannel, iterable?: Iterable<RawMessageData>);
|
||||||
public channel: TextBasedChannel;
|
public channel: TextBasedChannel;
|
||||||
@@ -5661,8 +5655,8 @@ export interface ClientEvents extends BaseClientEvents {
|
|||||||
callCreate: [call: CallState];
|
callCreate: [call: CallState];
|
||||||
callUpdate: [call: CallState];
|
callUpdate: [call: CallState];
|
||||||
callDelete: [call: CallState];
|
callDelete: [call: CallState];
|
||||||
messagePollVoteAdd: [data: MessagePollUserVote];
|
messagePollVoteAdd: [pollAnswer: PollAnswer, userId: Snowflake];
|
||||||
messagePollVoteRemove: [data: MessagePollUserVote];
|
messagePollVoteRemove: [pollAnswer: PollAnswer, userId: Snowflake];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientFetchInviteOptions {
|
export interface ClientFetchInviteOptions {
|
||||||
@@ -7044,7 +7038,7 @@ export interface MessageOptions {
|
|||||||
stickers?: StickerResolvable[];
|
stickers?: StickerResolvable[];
|
||||||
attachments?: MessageAttachment[];
|
attachments?: MessageAttachment[];
|
||||||
flags?: BitFieldResolvable<'SUPPRESS_EMBEDS' | 'SUPPRESS_NOTIFICATIONS' | 'IS_VOICE_MESSAGE', number>;
|
flags?: BitFieldResolvable<'SUPPRESS_EMBEDS' | 'SUPPRESS_NOTIFICATIONS' | 'IS_VOICE_MESSAGE', number>;
|
||||||
poll?: MessagePoll;
|
poll?: Poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageReactionResolvable = MessageReaction | Snowflake | string;
|
export type MessageReactionResolvable = MessageReaction | Snowflake | string;
|
||||||
|
|||||||
Reference in New Issue
Block a user