mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-15 02:53:31 +01:00
feat: polls overhaul (#11058)
* feat: polls overhaul (#10328) * feat(Managers): add PollAnswerVoterManager * feat(Partials): make Polls partial-safe * types: add typings * chore: add tests * fix: use fetch method in manager instead * chore: add tests for manager * feat: add partial support to poll actions * style: formatting * fix: change all .users references to .voters * refactor: add additional logic for partials * fix: actually add the partials * fix: fixed issue where event does not emit on first event * fix: align property type with DAPI documentation * fix: resolve additional bugs with partials * typings: update typings to reflect property type change * fix: tests * fix: adjust tests * refactor: combine partials logic into one statement * docs: mark getter as readonly * refactor: apply suggestions Co-authored-by: Almeida <github@almeidx.dev> * refactor(Actions): apply suggestions * refactor(PollAnswerVoterManager): apply suggestions * refactor(Message): check for existing poll before creating a poll * refactor(Polls): apply suggestions * revert(types): remove unused method from Poll class * refactor(Actions): consolidate poll creation logic into action class * refactor(PollAnswerVoterManager): set default for fetch parameter * refactor(Message): apply suggestion * fix: remove partial setter * refactor(Polls): apply suggestions * types: apply suggestions * refactor: remove clones * docs: spacing * refactor: move setters from constructor to _patch * types: adjust partials for poll classes * test: add more tests for polls * refactor: move updates around, more correct partial types * fix: handle more cases * refactor: requested changes * fix: missing imports * fix: update imports * fix: require file extensions --------- Co-authored-by: Almeida <github@almeidx.dev> Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> * fix(Poll): ensure `this.answers` is set before we reference it (#10809) * Ensure his.answers is set sooner if it's null during a patch * Move data.answers block up as well to ensure the patched answers are set * Ensure collection is set in constructor instead --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> * fix(PollAnswer): only define _emoji property once (#10811) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --------- Co-authored-by: Kevin <uhkevinmc@eedo.app> Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> Co-authored-by: Jacob Morrison <jake.morrison24@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const Partials = require('../../util/Partials');
|
const { Poll } = require('../../structures/Poll.js');
|
||||||
|
const { PollAnswer } = require('../../structures/PollAnswer.js');
|
||||||
|
const Partials = require('../../util/Partials.js');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@@ -63,6 +65,23 @@ class GenericAction {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPoll(data, message, channel) {
|
||||||
|
const includePollPartial = this.client.options.partials.includes(Partials.Poll);
|
||||||
|
const includePollAnswerPartial = this.client.options.partials.includes(Partials.PollAnswer);
|
||||||
|
if (message.partial && (!includePollPartial || !includePollAnswerPartial)) return null;
|
||||||
|
|
||||||
|
if (!message.poll && includePollPartial) {
|
||||||
|
message.poll = new Poll(this.client, data, message, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.poll && !message.poll.answers.has(data.answer_id) && includePollAnswerPartial) {
|
||||||
|
const pollAnswer = new PollAnswer(this.client, data, message.poll);
|
||||||
|
message.poll.answers.set(data.answer_id, pollAnswer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return message.poll;
|
||||||
|
}
|
||||||
|
|
||||||
getReaction(data, message, user) {
|
getReaction(data, message, user) {
|
||||||
const id = data.emoji.id ?? decodeURIComponent(data.emoji.name);
|
const id = data.emoji.id ?? decodeURIComponent(data.emoji.name);
|
||||||
return this.getPayload(
|
return this.getPayload(
|
||||||
|
|||||||
@@ -11,11 +11,18 @@ class MessagePollVoteAddAction extends Action {
|
|||||||
const message = this.getMessage(data, channel);
|
const message = this.getMessage(data, channel);
|
||||||
if (!message) return false;
|
if (!message) return false;
|
||||||
|
|
||||||
const { poll } = message;
|
const poll = this.getPoll(data, message, channel);
|
||||||
|
if (!poll) return false;
|
||||||
|
|
||||||
const answer = poll?.answers.get(data.answer_id);
|
const answer = poll.answers.get(data.answer_id);
|
||||||
if (!answer) return false;
|
if (!answer) return false;
|
||||||
|
|
||||||
|
const user = this.getUser(data);
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
answer.voters._add(user);
|
||||||
|
}
|
||||||
|
|
||||||
answer.voteCount++;
|
answer.voteCount++;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,12 +11,17 @@ class MessagePollVoteRemoveAction extends Action {
|
|||||||
const message = this.getMessage(data, channel);
|
const message = this.getMessage(data, channel);
|
||||||
if (!message) return false;
|
if (!message) return false;
|
||||||
|
|
||||||
const { poll } = message;
|
const poll = this.getPoll(data, message, channel);
|
||||||
|
if (!poll) return false;
|
||||||
|
|
||||||
const answer = poll?.answers.get(data.answer_id);
|
const answer = poll.answers.get(data.answer_id);
|
||||||
if (!answer) return false;
|
if (!answer) return false;
|
||||||
|
|
||||||
answer.voteCount--;
|
answer.voters.cache.delete(data.user_id);
|
||||||
|
|
||||||
|
if (answer.voteCount > 0) {
|
||||||
|
answer.voteCount--;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted whenever a user removes their vote in a poll.
|
* Emitted whenever a user removes their vote in a poll.
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ exports.GuildStickerManager = require('./managers/GuildStickerManager');
|
|||||||
exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager');
|
exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager');
|
||||||
exports.MessageManager = require('./managers/MessageManager');
|
exports.MessageManager = require('./managers/MessageManager');
|
||||||
exports.PermissionOverwriteManager = require('./managers/PermissionOverwriteManager');
|
exports.PermissionOverwriteManager = require('./managers/PermissionOverwriteManager');
|
||||||
|
exports.PollAnswerVoterManager = require('./managers/PollAnswerVoterManager.js').PollAnswerVoterManager;
|
||||||
exports.PresenceManager = require('./managers/PresenceManager');
|
exports.PresenceManager = require('./managers/PresenceManager');
|
||||||
exports.ReactionManager = require('./managers/ReactionManager');
|
exports.ReactionManager = require('./managers/ReactionManager');
|
||||||
exports.ReactionUserManager = require('./managers/ReactionUserManager');
|
exports.ReactionUserManager = require('./managers/ReactionUserManager');
|
||||||
|
|||||||
50
packages/discord.js/src/managers/PollAnswerVoterManager.js
Normal file
50
packages/discord.js/src/managers/PollAnswerVoterManager.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Collection } = require('@discordjs/collection');
|
||||||
|
const { makeURLSearchParams } = require('@discordjs/rest');
|
||||||
|
const { Routes } = require('discord-api-types/v10');
|
||||||
|
const CachedManager = require('./CachedManager.js');
|
||||||
|
const User = require('../structures/User.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages API methods for users who voted on a poll and stores their cache.
|
||||||
|
* @extends {CachedManager}
|
||||||
|
*/
|
||||||
|
class PollAnswerVoterManager extends CachedManager {
|
||||||
|
constructor(answer) {
|
||||||
|
super(answer.client, User);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The poll answer that this manager belongs to
|
||||||
|
* @type {PollAnswer}
|
||||||
|
*/
|
||||||
|
this.answer = answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cache of this manager
|
||||||
|
* @type {Collection<Snowflake, User>}
|
||||||
|
* @name PollAnswerVoterManager#cache
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the users that voted on this poll answer. Resolves with a collection of users, mapped by their ids.
|
||||||
|
* @param {BaseFetchPollAnswerVotersOptions} [options={}] Options for fetching the users
|
||||||
|
* @returns {Promise<Collection<Snowflake, User>>}
|
||||||
|
*/
|
||||||
|
async fetch({ after, limit } = {}) {
|
||||||
|
const poll = this.answer.poll;
|
||||||
|
const query = makeURLSearchParams({ limit, after });
|
||||||
|
const data = await this.client.rest.get(Routes.pollAnswerVoters(poll.channelId, poll.messageId, this.answer.id), {
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.users.reduce((coll, rawUser) => {
|
||||||
|
const user = this.client.users._add(rawUser);
|
||||||
|
this.cache.set(user.id, user);
|
||||||
|
return coll.set(user.id, user);
|
||||||
|
}, new Collection());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.PollAnswerVoterManager = PollAnswerVoterManager;
|
||||||
@@ -442,11 +442,15 @@ class Message extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.poll) {
|
if (data.poll) {
|
||||||
/**
|
if (this.poll) {
|
||||||
* The poll that was sent with the message
|
this.poll._patch(data.poll);
|
||||||
* @type {?Poll}
|
} else {
|
||||||
*/
|
/**
|
||||||
this.poll = new Poll(this.client, data.poll, this);
|
* The poll that was sent with the message
|
||||||
|
* @type {?Poll}
|
||||||
|
*/
|
||||||
|
this.poll = new Poll(this.client, data.poll, this, this.channel);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.poll ??= null;
|
this.poll ??= null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,30 @@ const { ErrorCodes } = require('../errors/index');
|
|||||||
* @extends {Base}
|
* @extends {Base}
|
||||||
*/
|
*/
|
||||||
class Poll extends Base {
|
class Poll extends Base {
|
||||||
constructor(client, data, message) {
|
constructor(client, data, message, channel) {
|
||||||
super(client);
|
super(client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the channel that this poll is in
|
||||||
|
* @type {Snowflake}
|
||||||
|
*/
|
||||||
|
this.channelId = data.channel_id ?? channel.id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The channel that this poll is in
|
||||||
|
* @name Poll#channel
|
||||||
|
* @type {TextBasedChannel}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
|
||||||
|
Object.defineProperty(this, 'channel', { value: channel });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the message that started this poll
|
||||||
|
* @type {Snowflake}
|
||||||
|
*/
|
||||||
|
this.messageId = data.message_id ?? message.id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The message that started this poll
|
* The message that started this poll
|
||||||
* @name Poll#message
|
* @name Poll#message
|
||||||
@@ -23,51 +44,27 @@ class Poll extends Base {
|
|||||||
|
|
||||||
Object.defineProperty(this, 'message', { value: message });
|
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
|
* The answers of this poll
|
||||||
* @type {Collection<number, PollAnswer>}
|
* @type {Collection<number, PollAnswer|PartialPollAnswer>}
|
||||||
*/
|
*/
|
||||||
this.answers = data.answers.reduce(
|
this.answers = new Collection();
|
||||||
(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);
|
this._patch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
_patch(data) {
|
_patch(data) {
|
||||||
|
if (data.answers) {
|
||||||
|
for (const answer of data.answers) {
|
||||||
|
const existing = this.answers.get(answer.answer_id);
|
||||||
|
if (existing) {
|
||||||
|
existing._patch(answer);
|
||||||
|
} else {
|
||||||
|
this.answers.set(answer.answer_id, new PollAnswer(this.client, answer, this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data.results) {
|
if (data.results) {
|
||||||
/**
|
/**
|
||||||
* Whether this poll's results have been precisely counted
|
* Whether this poll's results have been precisely counted
|
||||||
@@ -82,15 +79,84 @@ class Poll extends Base {
|
|||||||
} else {
|
} else {
|
||||||
this.resultsFinalized ??= false;
|
this.resultsFinalized ??= false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('allow_multiselect' in data) {
|
||||||
|
/**
|
||||||
|
* Whether this poll allows multiple answers
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.allowMultiselect = data.allow_multiselect;
|
||||||
|
} else {
|
||||||
|
this.allowMultiselect ??= null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('layout_type' in data) {
|
||||||
|
/**
|
||||||
|
* The layout type of this poll
|
||||||
|
* @type {PollLayoutType}
|
||||||
|
*/
|
||||||
|
this.layoutType = data.layout_type;
|
||||||
|
} else {
|
||||||
|
this.layoutType ??= null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('expiry' in data) {
|
||||||
|
/**
|
||||||
|
* The timestamp when this poll expires
|
||||||
|
* @type {?number}
|
||||||
|
*/
|
||||||
|
this.expiresTimestamp = data.expiry && Date.parse(data.expiry);
|
||||||
|
} else {
|
||||||
|
this.expiresTimestamp ??= null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.question) {
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.question ??= {
|
||||||
|
text: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The date when this poll expires
|
* The date when this poll expires
|
||||||
* @type {Date}
|
* @type {?Date}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
get expiresAt() {
|
get expiresAt() {
|
||||||
return new Date(this.expiresTimestamp);
|
return this.expiresTimestamp && new Date(this.expiresTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this poll is a partial
|
||||||
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get partial() {
|
||||||
|
return this.allowMultiselect === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the message that started this poll, then updates the poll from the fetched message.
|
||||||
|
* @returns {Promise<Poll>}
|
||||||
|
*/
|
||||||
|
async fetch() {
|
||||||
|
await this.channel.messages.fetch(this.messageId);
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,7 +168,7 @@ class Poll extends Base {
|
|||||||
throw new DiscordjsError(ErrorCodes.PollAlreadyExpired);
|
throw new DiscordjsError(ErrorCodes.PollAlreadyExpired);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.message.channel.messages.endPoll(this.message.id);
|
return this.channel.messages.endPoll(this.messageId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const Base = require('./Base');
|
const process = require('node:process');
|
||||||
const { Emoji } = require('./Emoji');
|
const Base = require('./Base.js');
|
||||||
|
const { Emoji } = require('./Emoji.js');
|
||||||
|
const { PollAnswerVoterManager } = require('../managers/PollAnswerVoterManager.js');
|
||||||
|
|
||||||
|
let deprecationEmittedForFetchVoters = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an answer to a {@link Poll}
|
* Represents an answer to a {@link Poll}
|
||||||
@@ -14,7 +18,7 @@ class PollAnswer extends Base {
|
|||||||
/**
|
/**
|
||||||
* The {@link Poll} this answer is part of
|
* The {@link Poll} this answer is part of
|
||||||
* @name PollAnswer#poll
|
* @name PollAnswer#poll
|
||||||
* @type {Poll}
|
* @type {Poll|PartialPoll}
|
||||||
* @readonly
|
* @readonly
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(this, 'poll', { value: poll });
|
Object.defineProperty(this, 'poll', { value: poll });
|
||||||
@@ -26,10 +30,10 @@ class PollAnswer extends Base {
|
|||||||
this.id = data.answer_id;
|
this.id = data.answer_id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The text of this answer
|
* The manager of the voters for this answer
|
||||||
* @type {?string}
|
* @type {PollAnswerVoterManager}
|
||||||
*/
|
*/
|
||||||
this.text = data.poll_media.text ?? null;
|
this.voters = new PollAnswerVoterManager(this);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The raw emoji of this answer
|
* The raw emoji of this answer
|
||||||
@@ -37,7 +41,7 @@ class PollAnswer extends Base {
|
|||||||
* @type {?APIPartialEmoji}
|
* @type {?APIPartialEmoji}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
Object.defineProperty(this, '_emoji', { value: data.poll_media.emoji ?? null });
|
Object.defineProperty(this, '_emoji', { value: null, writable: true });
|
||||||
|
|
||||||
this._patch(data);
|
this._patch(data);
|
||||||
}
|
}
|
||||||
@@ -51,7 +55,17 @@ class PollAnswer extends Base {
|
|||||||
*/
|
*/
|
||||||
this.voteCount = data.count;
|
this.voteCount = data.count;
|
||||||
} else {
|
} else {
|
||||||
this.voteCount ??= 0;
|
this.voteCount ??= this.voters.cache.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text of this answer
|
||||||
|
* @type {?string}
|
||||||
|
*/
|
||||||
|
this.text ??= data.poll_media?.text ?? null;
|
||||||
|
|
||||||
|
if (data.poll_media?.emoji) {
|
||||||
|
this._emoji = data.poll_media.emoji;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +78,15 @@ class PollAnswer extends Base {
|
|||||||
return this.client.emojis.cache.get(this._emoji.id) ?? new Emoji(this.client, this._emoji);
|
return this.client.emojis.cache.get(this._emoji.id) ?? new Emoji(this.client, this._emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this poll answer is a partial.
|
||||||
|
* @type {boolean}
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
get partial() {
|
||||||
|
return this.poll.partial || (this.text === null && this.emoji === null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options used for fetching voters of a poll answer.
|
* Options used for fetching voters of a poll answer.
|
||||||
* @typedef {Object} BaseFetchPollAnswerVotersOptions
|
* @typedef {Object} BaseFetchPollAnswerVotersOptions
|
||||||
@@ -75,14 +98,16 @@ class PollAnswer extends Base {
|
|||||||
* Fetches the users that voted for this answer.
|
* Fetches the users that voted for this answer.
|
||||||
* @param {BaseFetchPollAnswerVotersOptions} [options={}] The options for fetching voters
|
* @param {BaseFetchPollAnswerVotersOptions} [options={}] The options for fetching voters
|
||||||
* @returns {Promise<Collection<Snowflake, User>>}
|
* @returns {Promise<Collection<Snowflake, User>>}
|
||||||
|
* @deprecated Use {@link PollAnswerVoterManager#fetch} instead
|
||||||
*/
|
*/
|
||||||
fetchVoters({ after, limit } = {}) {
|
fetchVoters({ after, limit } = {}) {
|
||||||
return this.poll.message.channel.messages.fetchPollAnswerVoters({
|
if (!deprecationEmittedForFetchVoters) {
|
||||||
messageId: this.poll.message.id,
|
process.emitWarning('PollAnswer#fetchVoters is deprecated. Use PollAnswer#voters#fetch instead.');
|
||||||
answerId: this.id,
|
|
||||||
after,
|
deprecationEmittedForFetchVoters = true;
|
||||||
limit,
|
}
|
||||||
});
|
|
||||||
|
return this.voters.fetch({ after, limit });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const { createEnum } = require('./Enums');
|
|||||||
* @property {number} GuildScheduledEvent The partial to receive uncached guild scheduled events.
|
* @property {number} GuildScheduledEvent The partial to receive uncached guild scheduled events.
|
||||||
* @property {number} ThreadMember The partial to receive uncached thread members.
|
* @property {number} ThreadMember The partial to receive uncached thread members.
|
||||||
* @property {number} SoundboardSound The partial to receive uncached soundboard sounds.
|
* @property {number} SoundboardSound The partial to receive uncached soundboard sounds.
|
||||||
|
* @property {number} Poll The partial to receive uncached polls.
|
||||||
|
* @property {number} PollAnswer The partial to receive uncached poll answers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// JSDoc for IntelliSense purposes
|
// JSDoc for IntelliSense purposes
|
||||||
@@ -43,4 +45,6 @@ module.exports = createEnum([
|
|||||||
'GuildScheduledEvent',
|
'GuildScheduledEvent',
|
||||||
'ThreadMember',
|
'ThreadMember',
|
||||||
'SoundboardSound',
|
'SoundboardSound',
|
||||||
|
'Poll',
|
||||||
|
'PollAnswer',
|
||||||
]);
|
]);
|
||||||
|
|||||||
53
packages/discord.js/typings/index.d.ts
vendored
53
packages/discord.js/typings/index.d.ts
vendored
@@ -3005,19 +3005,30 @@ export class Presence extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PollQuestionMedia {
|
export interface PollQuestionMedia {
|
||||||
text: string;
|
text: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PollAnswerVoterManager extends CachedManager<Snowflake, User, UserResolvable> {
|
||||||
|
private constructor(answer: PollAnswer);
|
||||||
|
public answer: PollAnswer;
|
||||||
|
public fetch(options?: BaseFetchPollAnswerVotersOptions): Promise<Collection<Snowflake, User>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Poll extends Base {
|
export class Poll extends Base {
|
||||||
private constructor(client: Client<true>, data: APIPoll, message: Message);
|
private constructor(client: Client<true>, data: APIPoll, message: Message, channel: TextBasedChannel);
|
||||||
|
public readonly channel: TextBasedChannel;
|
||||||
|
public channelId: Snowflake;
|
||||||
public readonly message: Message;
|
public readonly message: Message;
|
||||||
|
public messageId: Snowflake;
|
||||||
public question: PollQuestionMedia;
|
public question: PollQuestionMedia;
|
||||||
public answers: Collection<number, PollAnswer>;
|
public answers: Collection<number, PollAnswer | PartialPollAnswer>;
|
||||||
public expiresTimestamp: number;
|
public expiresTimestamp: number | null;
|
||||||
public get expiresAt(): Date;
|
public get expiresAt(): Date | null;
|
||||||
public allowMultiselect: boolean;
|
public allowMultiselect: boolean;
|
||||||
public layoutType: PollLayoutType;
|
public layoutType: PollLayoutType;
|
||||||
public resultsFinalized: boolean;
|
public resultsFinalized: boolean;
|
||||||
|
public get partial(): false;
|
||||||
|
public fetch(): Promise<this>;
|
||||||
public end(): Promise<Message>;
|
public end(): Promise<Message>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3029,11 +3040,14 @@ export interface BaseFetchPollAnswerVotersOptions {
|
|||||||
export class PollAnswer extends Base {
|
export class PollAnswer extends Base {
|
||||||
private constructor(client: Client<true>, data: APIPollAnswer & { count?: number }, poll: Poll);
|
private constructor(client: Client<true>, data: APIPollAnswer & { count?: number }, poll: Poll);
|
||||||
private _emoji: APIPartialEmoji | null;
|
private _emoji: APIPartialEmoji | null;
|
||||||
public readonly poll: Poll;
|
public readonly poll: Poll | PartialPoll;
|
||||||
public id: number;
|
public id: number;
|
||||||
public text: string | null;
|
public text: string | null;
|
||||||
public voteCount: number;
|
public voteCount: number;
|
||||||
|
public voters: PollAnswerVoterManager;
|
||||||
public get emoji(): GuildEmoji | Emoji | null;
|
public get emoji(): GuildEmoji | Emoji | null;
|
||||||
|
public get partial(): false;
|
||||||
|
/** @deprecated Use {@link PollAnswerVoterManager.fetch} instead */
|
||||||
public fetchVoters(options?: BaseFetchPollAnswerVotersOptions): Promise<Collection<Snowflake, User>>;
|
public fetchVoters(options?: BaseFetchPollAnswerVotersOptions): Promise<Collection<Snowflake, User>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5310,7 +5324,9 @@ export type AllowedPartial =
|
|||||||
| MessageReaction
|
| MessageReaction
|
||||||
| GuildScheduledEvent
|
| GuildScheduledEvent
|
||||||
| ThreadMember
|
| ThreadMember
|
||||||
| SoundboardSound;
|
| SoundboardSound
|
||||||
|
| Poll
|
||||||
|
| PollAnswer;
|
||||||
|
|
||||||
export type AllowedThreadTypeForNewsChannel = ChannelType.AnnouncementThread;
|
export type AllowedThreadTypeForNewsChannel = ChannelType.AnnouncementThread;
|
||||||
|
|
||||||
@@ -5882,8 +5898,8 @@ export interface ClientEvents {
|
|||||||
inviteDelete: [invite: Invite];
|
inviteDelete: [invite: Invite];
|
||||||
messageCreate: [message: OmitPartialGroupDMChannel<Message>];
|
messageCreate: [message: OmitPartialGroupDMChannel<Message>];
|
||||||
messageDelete: [message: OmitPartialGroupDMChannel<Message | PartialMessage>];
|
messageDelete: [message: OmitPartialGroupDMChannel<Message | PartialMessage>];
|
||||||
messagePollVoteAdd: [pollAnswer: PollAnswer, userId: Snowflake];
|
messagePollVoteAdd: [pollAnswer: PollAnswer | PartialPollAnswer, userId: Snowflake];
|
||||||
messagePollVoteRemove: [pollAnswer: PollAnswer, userId: Snowflake];
|
messagePollVoteRemove: [pollAnswer: PollAnswer | PartialPollAnswer, userId: Snowflake];
|
||||||
messageReactionRemoveAll: [
|
messageReactionRemoveAll: [
|
||||||
message: OmitPartialGroupDMChannel<Message | PartialMessage>,
|
message: OmitPartialGroupDMChannel<Message | PartialMessage>,
|
||||||
reactions: ReadonlyCollection<string | Snowflake, MessageReaction>,
|
reactions: ReadonlyCollection<string | Snowflake, MessageReaction>,
|
||||||
@@ -7377,6 +7393,23 @@ export interface PartialMessage
|
|||||||
|
|
||||||
export interface PartialMessageReaction extends Partialize<MessageReaction, 'count'> {}
|
export interface PartialMessageReaction extends Partialize<MessageReaction, 'count'> {}
|
||||||
|
|
||||||
|
export interface PartialPoll
|
||||||
|
extends Partialize<
|
||||||
|
Poll,
|
||||||
|
'allowMultiselect' | 'layoutType' | 'expiresTimestamp',
|
||||||
|
null,
|
||||||
|
'question' | 'message' | 'answers'
|
||||||
|
> {
|
||||||
|
question: { text: null };
|
||||||
|
message: PartialMessage;
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
answers: Collection<number, PartialPollAnswer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PartialPollAnswer extends Partialize<PollAnswer, 'emoji' | 'text', null, 'poll'> {
|
||||||
|
readonly poll: PartialPoll;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PartialGuildScheduledEvent
|
export interface PartialGuildScheduledEvent
|
||||||
extends Partialize<GuildScheduledEvent, 'userCount', 'status' | 'privacyLevel' | 'name' | 'entityType'> {}
|
extends Partialize<GuildScheduledEvent, 'userCount', 'status' | 'privacyLevel' | 'name' | 'entityType'> {}
|
||||||
|
|
||||||
@@ -7406,6 +7439,8 @@ export enum Partials {
|
|||||||
GuildScheduledEvent,
|
GuildScheduledEvent,
|
||||||
ThreadMember,
|
ThreadMember,
|
||||||
SoundboardSound,
|
SoundboardSound,
|
||||||
|
Poll,
|
||||||
|
PollAnswer,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PartialUser extends Partialize<User, 'username' | 'tag' | 'discriminator'> {}
|
export interface PartialUser extends Partialize<User, 'username' | 'tag' | 'discriminator'> {}
|
||||||
|
|||||||
@@ -232,7 +232,11 @@ import {
|
|||||||
ContainerComponentData,
|
ContainerComponentData,
|
||||||
InteractionResponse,
|
InteractionResponse,
|
||||||
FetchPinnedMessagesResponse,
|
FetchPinnedMessagesResponse,
|
||||||
} from '.';
|
PartialPoll,
|
||||||
|
PartialPollAnswer,
|
||||||
|
PollAnswer,
|
||||||
|
PollAnswerVoterManager,
|
||||||
|
} from './index.js';
|
||||||
import {
|
import {
|
||||||
expectAssignable,
|
expectAssignable,
|
||||||
expectDeprecated,
|
expectDeprecated,
|
||||||
@@ -715,6 +719,48 @@ client.on('messageDeleteBulk', (messages, { client }) => {
|
|||||||
expectType<Client<true>>(client);
|
expectType<Client<true>>(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
client.on('messagePollVoteAdd', async (answer, userId) => {
|
||||||
|
expectType<Client<true>>(answer.client);
|
||||||
|
expectType<Snowflake>(userId);
|
||||||
|
|
||||||
|
if (answer.partial) {
|
||||||
|
expectType<null>(answer.emoji);
|
||||||
|
expectType<null>(answer.text);
|
||||||
|
expectNotType<null>(answer.id);
|
||||||
|
expectNotType<null>(answer.poll);
|
||||||
|
|
||||||
|
await answer.poll.fetch();
|
||||||
|
answer = answer.poll.answers?.get(answer.id) ?? answer;
|
||||||
|
|
||||||
|
expectType<User>(answer.voters.cache.get(userId)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
expectType<string | null>(answer.text);
|
||||||
|
expectType<GuildEmoji | Emoji | null>(answer.emoji);
|
||||||
|
expectType<number>(answer.id);
|
||||||
|
expectType<number>(answer.voteCount!);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('messagePollVoteRemove', async (answer, userId) => {
|
||||||
|
expectType<Client<true>>(answer.client);
|
||||||
|
expectType<Snowflake>(userId);
|
||||||
|
|
||||||
|
if (answer.partial) {
|
||||||
|
expectType<null>(answer.emoji);
|
||||||
|
expectType<null>(answer.text);
|
||||||
|
expectNotType<null>(answer.id);
|
||||||
|
expectNotType<null>(answer.poll);
|
||||||
|
|
||||||
|
await answer.poll.fetch();
|
||||||
|
answer = answer.poll.answers?.get(answer.id) ?? answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
expectType<string | null>(answer.text);
|
||||||
|
expectType<GuildEmoji | Emoji | null>(answer.emoji);
|
||||||
|
expectType<number>(answer.id);
|
||||||
|
expectType<number>(answer.voteCount!);
|
||||||
|
});
|
||||||
|
|
||||||
client.on('messageReactionAdd', async (reaction, { client }) => {
|
client.on('messageReactionAdd', async (reaction, { client }) => {
|
||||||
expectType<Client<true>>(reaction.client);
|
expectType<Client<true>>(reaction.client);
|
||||||
expectType<Client<true>>(client);
|
expectType<Client<true>>(client);
|
||||||
@@ -1780,6 +1826,12 @@ declare const messageManager: MessageManager;
|
|||||||
messageManager.fetch({ message: '1234567890', after: '1234567890', cache: true, force: false });
|
messageManager.fetch({ message: '1234567890', after: '1234567890', cache: true, force: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare const pollAnswerVoterManager: PollAnswerVoterManager;
|
||||||
|
{
|
||||||
|
expectType<Promise<Collection<Snowflake, User>>>(pollAnswerVoterManager.fetch());
|
||||||
|
expectType<PollAnswer>(pollAnswerVoterManager.answer);
|
||||||
|
}
|
||||||
|
|
||||||
declare const roleManager: RoleManager;
|
declare const roleManager: RoleManager;
|
||||||
expectType<Promise<Collection<Snowflake, Role>>>(roleManager.fetch());
|
expectType<Promise<Collection<Snowflake, Role>>>(roleManager.fetch());
|
||||||
expectType<Promise<Collection<Snowflake, Role>>>(roleManager.fetch(undefined, {}));
|
expectType<Promise<Collection<Snowflake, Role>>>(roleManager.fetch(undefined, {}));
|
||||||
@@ -2802,16 +2854,42 @@ await textChannel.send({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
declare const partialPoll: PartialPoll;
|
||||||
|
{
|
||||||
|
if (partialPoll.partial) {
|
||||||
|
expectType<null>(partialPoll.question.text);
|
||||||
|
expectType<PartialMessage>(partialPoll.message);
|
||||||
|
expectType<null>(partialPoll.allowMultiselect);
|
||||||
|
expectType<null>(partialPoll.layoutType);
|
||||||
|
expectType<null>(partialPoll.expiresTimestamp);
|
||||||
|
expectType<Collection<number, PartialPollAnswer>>(partialPoll.answers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const partialPollAnswer: PartialPollAnswer;
|
||||||
|
{
|
||||||
|
if (partialPollAnswer.partial) {
|
||||||
|
expectType<PartialPoll>(partialPollAnswer.poll);
|
||||||
|
expectType<null>(partialPollAnswer.emoji);
|
||||||
|
expectType<null>(partialPollAnswer.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
declare const poll: Poll;
|
declare const poll: Poll;
|
||||||
declare const message: Message;
|
declare const message: Message;
|
||||||
declare const pollData: PollData;
|
declare const pollData: PollData;
|
||||||
{
|
{
|
||||||
expectType<Message>(await poll.end());
|
expectType<Message>(await poll.end());
|
||||||
|
expectType<false>(poll.partial);
|
||||||
|
expectNotType<Collection<number, PartialPollAnswer>>(poll.answers);
|
||||||
|
|
||||||
const answer = poll.answers.first()!;
|
const answer = poll.answers.first()!;
|
||||||
expectType<number>(answer.voteCount);
|
|
||||||
|
|
||||||
expectType<Collection<Snowflake, User>>(await answer.fetchVoters({ after: snowflake, limit: 10 }));
|
if (!answer.partial) {
|
||||||
|
expectType<number>(answer.voteCount);
|
||||||
|
expectType<number>(answer.id);
|
||||||
|
expectType<PollAnswerVoterManager>(answer.voters);
|
||||||
|
expectType<Collection<Snowflake, User>>(await answer.voters.fetch({ after: snowflake, limit: 10 }));
|
||||||
|
}
|
||||||
|
|
||||||
await messageManager.endPoll(snowflake);
|
await messageManager.endPoll(snowflake);
|
||||||
await messageManager.fetchPollAnswerVoters({
|
await messageManager.fetchPollAnswerVoters({
|
||||||
|
|||||||
Reference in New Issue
Block a user