mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-17 03:53:29 +01:00
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>
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { Poll } = require('../../structures/Poll.js');
|
||||||
|
const { PollAnswer } = require('../../structures/PollAnswer.js');
|
||||||
const { Partials } = require('../../util/Partials.js');
|
const { Partials } = require('../../util/Partials.js');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -63,6 +65,23 @@ class Action {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ exports.GuildStickerManager = require('./managers/GuildStickerManager.js').Guild
|
|||||||
exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager.js').GuildTextThreadManager;
|
exports.GuildTextThreadManager = require('./managers/GuildTextThreadManager.js').GuildTextThreadManager;
|
||||||
exports.MessageManager = require('./managers/MessageManager.js').MessageManager;
|
exports.MessageManager = require('./managers/MessageManager.js').MessageManager;
|
||||||
exports.PermissionOverwriteManager = require('./managers/PermissionOverwriteManager.js').PermissionOverwriteManager;
|
exports.PermissionOverwriteManager = require('./managers/PermissionOverwriteManager.js').PermissionOverwriteManager;
|
||||||
|
exports.PollAnswerVoterManager = require('./managers/PollAnswerVoterManager.js').PollAnswerVoterManager;
|
||||||
exports.PresenceManager = require('./managers/PresenceManager.js').PresenceManager;
|
exports.PresenceManager = require('./managers/PresenceManager.js').PresenceManager;
|
||||||
exports.ReactionManager = require('./managers/ReactionManager.js').ReactionManager;
|
exports.ReactionManager = require('./managers/ReactionManager.js').ReactionManager;
|
||||||
exports.ReactionUserManager = require('./managers/ReactionUserManager.js').ReactionUserManager;
|
exports.ReactionUserManager = require('./managers/ReactionUserManager.js').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;
|
||||||
@@ -414,11 +414,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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,30 @@ const { DiscordjsError, ErrorCodes } = require('../errors/index.js');
|
|||||||
* @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
|
||||||
@@ -22,47 +43,6 @@ 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
|
|
||||||
* @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);
|
this._patch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,15 +61,101 @@ 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 answers of this poll
|
||||||
|
* @type {Collection<number, PollAnswer|PartialPollAnswer>}
|
||||||
|
*/
|
||||||
|
this.answers ??= new Collection();
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,7 +167,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const { Base } = require('./Base.js');
|
const { Base } = require('./Base.js');
|
||||||
const { Emoji } = require('./Emoji.js');
|
const { Emoji } = require('./Emoji.js');
|
||||||
|
const { PollAnswerVoterManager } = require('../managers/PollAnswerVoterManager.js');
|
||||||
const { resolveGuildEmoji } = require('../util/Util.js');
|
const { resolveGuildEmoji } = require('../util/Util.js');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,7 +16,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 });
|
||||||
@@ -27,10 +28,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
|
||||||
@@ -38,7 +39,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 });
|
||||||
|
|
||||||
this._patch(data);
|
this._patch(data);
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,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) {
|
||||||
|
Object.defineProperty(this, '_emoji', { value: data.poll_media.emoji });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +76,15 @@ class PollAnswer extends Base {
|
|||||||
return resolveGuildEmoji(this.client, this._emoji.id) ?? new Emoji(this.client, this._emoji);
|
return resolveGuildEmoji(this.client, 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
|
||||||
@@ -76,14 +96,10 @@ 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({
|
return this.voters.fetch({ after, limit });
|
||||||
messageId: this.poll.message.id,
|
|
||||||
answerId: this.id,
|
|
||||||
after,
|
|
||||||
limit,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ const { createEnum } = require('./Enums.js');
|
|||||||
* @property {number} Reaction The partial to receive uncached reactions.
|
* @property {number} Reaction The partial to receive uncached reactions.
|
||||||
* @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} 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
|
||||||
@@ -41,4 +43,6 @@ exports.Partials = createEnum([
|
|||||||
'Reaction',
|
'Reaction',
|
||||||
'GuildScheduledEvent',
|
'GuildScheduledEvent',
|
||||||
'ThreadMember',
|
'ThreadMember',
|
||||||
|
'Poll',
|
||||||
|
'PollAnswer',
|
||||||
]);
|
]);
|
||||||
|
|||||||
53
packages/discord.js/typings/index.d.ts
vendored
53
packages/discord.js/typings/index.d.ts
vendored
@@ -2675,19 +2675,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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2699,11 +2710,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>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4572,7 +4586,9 @@ export type AllowedPartial =
|
|||||||
| Message
|
| Message
|
||||||
| MessageReaction
|
| MessageReaction
|
||||||
| GuildScheduledEvent
|
| GuildScheduledEvent
|
||||||
| ThreadMember;
|
| ThreadMember
|
||||||
|
| Poll
|
||||||
|
| PollAnswer;
|
||||||
|
|
||||||
export type AllowedThreadTypeForAnnouncementChannel = ChannelType.AnnouncementThread;
|
export type AllowedThreadTypeForAnnouncementChannel = ChannelType.AnnouncementThread;
|
||||||
|
|
||||||
@@ -5123,8 +5139,8 @@ export interface ClientEventTypes {
|
|||||||
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>,
|
||||||
@@ -6536,6 +6552,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'> {}
|
||||||
|
|
||||||
@@ -6560,6 +6593,8 @@ export enum Partials {
|
|||||||
Reaction,
|
Reaction,
|
||||||
GuildScheduledEvent,
|
GuildScheduledEvent,
|
||||||
ThreadMember,
|
ThreadMember,
|
||||||
|
Poll,
|
||||||
|
PollAnswer,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PartialUser extends Partialize<User, 'username' | 'tag' | 'discriminator'> {}
|
export interface PartialUser extends Partialize<User, 'username' | 'tag' | 'discriminator'> {}
|
||||||
|
|||||||
@@ -214,6 +214,10 @@ import {
|
|||||||
InteractionCallbackResponse,
|
InteractionCallbackResponse,
|
||||||
GuildScheduledEventRecurrenceRuleOptions,
|
GuildScheduledEventRecurrenceRuleOptions,
|
||||||
ThreadOnlyChannel,
|
ThreadOnlyChannel,
|
||||||
|
PartialPoll,
|
||||||
|
PartialPollAnswer,
|
||||||
|
PollAnswer,
|
||||||
|
PollAnswerVoterManager,
|
||||||
} from './index.js';
|
} from './index.js';
|
||||||
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';
|
||||||
@@ -656,6 +660,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);
|
||||||
@@ -1724,6 +1770,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, {}));
|
||||||
@@ -2663,16 +2715,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