Skip to content

Commit

Permalink
fix - entrant checking for drops + new help funcs
Browse files Browse the repository at this point in the history
  • Loading branch information
Nico105 committed Apr 16, 2022
1 parent 29761dd commit c936500
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 49 deletions.
20 changes: 10 additions & 10 deletions src/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ exports.GiveawayMessages = {
* @typedef EmbedFooterObject
*
* @property {string} [text] The text of the footer.
* @property {string} [iconURL] The icon URL of the footer of ALL giveaway embeds. "text" is required to be defined, when the icon is to be shown in the main embed.
* @property {string} [iconURL] The icon URL of the footer of ALL giveaway embeds.<br>"text" is required to be defined, when the icon is to be shown in the main embed.
*/

/**
Expand All @@ -47,7 +47,7 @@ exports.GiveawayMessages = {
*
* @property {string} [content] The raw message
* @property {Discord.MessageEmbed|Discord.MessageEmbedOptions} [embed] The embed
* @property {Array<Discord.MessageActionRow|Discord.MessageActionRowOptions>} [components] The components. "content" or "embed" is required to be defined.
* @property {Array<Discord.MessageActionRow|Discord.MessageActionRowOptions>} [components] The components.<br>"content" or "embed" is required to be defined.
* @property {Boolean} [replyToGiveaway] If the sent message should reply to the giveaway embed.
*/

Expand All @@ -61,7 +61,7 @@ exports.GiveawayMessages = {
* @property {Discord.User} [hostedBy] The user who hosts the giveaway.
* @property {Boolean} [botsCanWin] If bots can win the giveaway.
* @property {Discord.PermissionResolvable[]} [exemptPermissions] Members with any of these permissions will not be able to win a giveaway.
* @property {Function} [exemptMembers] Function to filter members. If true is returned, the member will not be able to win the giveaway.
* @property {Function} [exemptMembers] Function to filter members.<br>If true is returned, the member will not be able to win the giveaway.
* @property {BonusEntry[]} [bonusEntries] An array of BonusEntry objects.
* @property {Discord.ColorResolvable} [embedColor] The color of the giveaway embed when it is running.
* @property {Discord.ColorResolvable} [embedColorEnd] The color of the giveaway embed when it has ended.
Expand All @@ -71,7 +71,7 @@ exports.GiveawayMessages = {
* @property {any} [extraData] The extra data for this giveaway.
* @property {LastChanceOptions} [lastChance] The options for the last chance system.
* @property {PauseOptions} [pauseOptions] The options for the pause system.
* @property {Boolean} [isDrop] If the giveaway is a drop, or not. Drop means that if the amount of reactions to the giveaway is the same as "winnerCount" then it immediately ends.
* @property {Boolean} [isDrop] If the giveaway is a drop, or not. <br>Drop means that if the amount of valid entrants to the giveaway is the same as "winnerCount" then it immediately ends.
* @property {Discord.MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the giveaway messages content.
*/
exports.GiveawayStartOptions = {};
Expand Down Expand Up @@ -109,7 +109,7 @@ exports.LastChanceOptions = {
* @property {string} [content='⚠️ **THIS GIVEAWAY IS PAUSED !** ⚠️'] The text of the embed when the giveaway is paused.
* @property {number} [unPauseAfter=null] The number of milliseconds, or a timestamp in milliseconds, after which the giveaway will automatically unpause.
* @property {Discord.EmbedColorResolveAble} [embedColor='#FFFF00'] The color of the embed when the giveaway is paused.
* @private @property {number} [durationAfterPause=null|giveaway.remainingTime] The remaining duration after the giveaway is unpaused. ⚠ This property gets set by the manager so that the pause system works properly. It is not recommended to set it manually!
* @private @property {number} [durationAfterPause=null|giveaway.remainingTime] The remaining duration after the giveaway is unpaused.<br>⚠ This property gets set by the manager so that the pause system works properly. It is not recommended to set it manually!
* @property {string} [infiniteDurationText='`NEVER`'] The text that gets displayed next to "GiveawayMessages#drawing" in the paused embed, when there is no "unPauseAfter".
*/
exports.PauseOptions = {
Expand All @@ -127,11 +127,11 @@ exports.PauseOptions = {
*
* @property {string} [storage='./giveaways.json'] The storage path for the giveaways.
* @property {number} [forceUpdateEvery=null] Force the giveaway messages to be updated at a specific interval.
* @property {number} [endedGiveawaysLifetime=null] The number of milliseconds after which ended giveaways should get deleted from the DB. ⚠ Giveaways deleted from the DB cannot get rerolled anymore!
* @property {number} [endedGiveawaysLifetime=null] The number of milliseconds after which ended giveaways should get deleted from the DB.<br>⚠ Giveaways deleted from the DB cannot get rerolled anymore!
* @property {Object} [default] The default options for new giveaways.
* @property {Boolean} [default.botsCanWin=false] If bots can win giveaways.
* @property {Discord.PermissionResolvable[]} [default.exemptPermissions=[]] Members with any of these permissions won't be able to win a giveaway.
* @property {Function} [default.exemptMembers] Function to filter members. If true is returned, the member won't be able to win a giveaway.
* @property {Function} [default.exemptMembers] Function to filter members.<br>If true is returned, the member won't be able to win a giveaway.
* @property {Discord.ColorResolvable} [default.embedColor='#FF0000'] The color of the giveaway embeds when they are running.
* @property {Discord.ColorResolvable} [default.embedColorEnd='#000000'] The color of the giveaway embeds when they have ended.
* @property {Discord.EmojiIdentifierResolvable} [default.reaction='🎉'] The reaction to participate in giveaways.
Expand Down Expand Up @@ -184,12 +184,12 @@ exports.GiveawayRerollOptions = {
* @property {string} [newPrize] The new giveaway prize.
* @property {number} [addTime] Number of milliseconds to add to the giveaway duration.
* @property {number} [setEndTimestamp] The timestamp of the new end date.
* @property {GiveawayMessages} [newMessages] The new giveaway messages. Will get merged with the existing object, if there.
* @property {GiveawayMessages} [newMessages] The new giveaway messages.<br>Will get merged with the existing object, if there.
* @property {string} [newThumbnail] The new thumbnail url.
* @property {any} [newExtraData] The new extra data for this giveaway.
* @property {BonusEntry[]} [newBonusEntries] The new BonusEntry objects.
* @property {Function} [newExemptMembers] The new filter function to exempt members from winning the giveaway.
* @property {LastChanceOptions} [newLastChance] The new options for the last chance system. Will get merged with the existing object, if there.
* @property {LastChanceOptions} [newLastChance] The new options for the last chance system.<br>Will get merged with the existing object, if there.
*/
exports.GiveawayEditOptions = {};

Expand Down Expand Up @@ -219,7 +219,7 @@ exports.GiveawayEditOptions = {};
* @property {any} [extraData] The extra data for this giveaway.
* @property {LastChanceOptions} [lastChance] The options for the last chance system.
* @property {PauseOptions} [pauseOptions] The options for the pause system.
* @property {Boolean} [isDrop] If the giveaway is a drop, or not. Drop means that if the amount of reactions to the giveaway is the same as "winnerCount" then it immediately ends.
* @property {Boolean} [isDrop] If the giveaway is a drop, or not.<br>Drop means that if the amount of valid entrants to the giveaway is the same as "winnerCount" then it immediately ends.
* @property {Discord.MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the giveaway messages content.
*/
exports.GiveawayData = {};
64 changes: 43 additions & 21 deletions src/Giveaway.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class Giveaway extends EventEmitter {
}

/**
* The reaction on the giveaway message.
* The emoji used for the reaction on the giveaway message.
* @type {Discord.EmojiIdentifierResolvable}
*/
get reaction() {
Expand Down Expand Up @@ -231,7 +231,7 @@ class Giveaway extends EventEmitter {

/**
* If the giveaway is a drop, or not.
* Drop means that if the amount of reactions to the giveaway is the same as "winnerCount" then it immediately ends.
* Drop means that if the amount of valid entrants to the giveaway is the same as "winnerCount" then it immediately ends.
* @type {Boolean}
*/
get isDrop() {
Expand All @@ -251,6 +251,19 @@ class Giveaway extends EventEmitter {
: null;
}

/**
* The reaction on the giveaway message.
* @type {?Discord.MessageReaction}
*/
get messageReaction() {
const emoji = Discord.Util.resolvePartialEmoji(this.reaction);
return (
this.message?.reactions.cache.find((r) =>
[r.emoji.name, r.emoji.id].filter(Boolean).includes(emoji?.name ?? emoji?.id)
) ?? null
);
}

/**
* Function to filter members. If true is returned, the member won't be able to win the giveaway.
* @property {Discord.GuildMember} member The member to check
Expand Down Expand Up @@ -423,6 +436,32 @@ class Giveaway extends EventEmitter {
});
}

/**
* Fetches all users of the giveaway reaction, except bots, if not otherwise specified.
* @returns {Promise<Discord.Collection<Discord.Snowflake, Discord.User>>} The collection of reaction users.
*/
async fetchAllEntrants() {
return new Promise(async (resolve, reject) => {
this.message = await this.fetchMessage().catch(() => {});
const reaction = this.messageReaction;
if (!reaction) return reject('Unable to find the giveaway reaction.');

let userCollection = await reaction.users.fetch().catch(() => {});
if (!userCollection) return reject('Unable to fetch the reaction users.');

while (userCollection.size % 100 === 0) {
const newUsers = await reaction.users.fetch({ after: userCollection.lastKey() });
if (newUsers.size === 0) break;
userCollection = userCollection.concat(newUsers);
}

const users = userCollection
.filter((u) => !u.bot || u.bot === this.botsCanWin)
.filter((u) => u.id !== this.client.user.id);
resolve(users);
});
}

/**
* Checks if a user fulfills the requirements to win the giveaway.
* @private
Expand Down Expand Up @@ -481,12 +520,6 @@ class Giveaway extends EventEmitter {
async roll(winnerCount = this.winnerCount) {
if (!this.message) return [];

// Find the reaction
const emoji = Discord.Util.resolvePartialEmoji(this.reaction);
const reaction = this.message.reactions.cache.find((r) =>
[r.emoji.name, r.emoji.id].filter(Boolean).includes(emoji?.name ?? emoji?.id)
);
if (!reaction) return [];
let guild = this.message.guild;

// Fetch all guild members if the intent is available
Expand All @@ -499,19 +532,8 @@ class Giveaway extends EventEmitter {
await guild.members.fetch().catch(() => {});
}

// Fetch all reaction users
let userCollection = await reaction.users.fetch().catch(() => {});
if (!userCollection) return [];
while (userCollection.size % 100 === 0) {
const newUsers = await reaction.users.fetch({ after: userCollection.lastKey() });
if (newUsers.size === 0) break;
userCollection = userCollection.concat(newUsers);
}

const users = userCollection
.filter((u) => !u.bot || u.bot === this.botsCanWin)
.filter((u) => u.id !== this.client.user.id);
if (!users.size) return [];
const users = await this.fetchAllEntrants().catch(() => {});
if (!users?.size) return [];

// Bonus Entries
let userArray;
Expand Down
32 changes: 14 additions & 18 deletions src/Manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,21 +509,17 @@ class GiveawaysManager extends EventEmitter {
// Second case: the giveaway is a drop
if (giveaway.isDrop) {
giveaway.message = await giveaway.fetchMessage().catch(() => {});
const emoji = Discord.Util.resolvePartialEmoji(giveaway.reaction);
const reaction = giveaway.message?.reactions.cache.find((r) =>
[r.emoji.name, r.emoji.id].filter(Boolean).includes(emoji?.id ?? emoji?.name)
);

if (reaction?.count - 1 >= giveaway.winnerCount) {
const users =
(await reaction.users.fetch().catch(() => {}))
?.filter((u) => !u.bot || u.bot === giveaway.botsCanWin)
.filter((u) => u.id !== this.client.user.id) ?? [];
if (giveaway.messageReaction?.count - 1 >= giveaway.winnerCount) {
const users = await giveaway.fetchAllEntrants().catch(() => {});

let validUsers = 0;
for (const user of users) {
if (validUsers === giveaway.winnerCount) return this.end(giveaway.messageId).catch(() => {});
for (const user of [...(users?.values() || [])]) {
if (await giveaway.checkWinnerEntry(user)) validUsers++;
if (validUsers === giveaway.winnerCount) {
await this.end(giveaway.messageId).catch(() => {});
break;
}
}
}

Expand Down Expand Up @@ -643,17 +639,17 @@ class GiveawaysManager extends EventEmitter {
if (giveaway.ended) return this.emit('endedGiveawayReactionAdded', giveaway, member, reaction);
this.emit('giveawayReactionAdded', giveaway, member, reaction);

// Only end drops if the amount of available, valid winners is equal to the winnerCount
if (giveaway.isDrop && reaction.count - 1 >= giveaway.winnerCount) {
// Only end drops if the amount of available, valid winners is equal to the winnerCount
const users =
(await reaction.users.fetch().catch(() => {}))
?.filter((u) => !u.bot || u.bot === giveaway.botsCanWin)
.filter((u) => u.id !== this.client.user.id) ?? [];
const users = await giveaway.fetchAllEntrants().catch(() => {});

let validUsers = 0;
for (const user of users) {
if (validUsers === giveaway.winnerCount) this.end(giveaway.messageId).catch(() => {});
for (const user of [...(users?.values() || [])]) {
if (await giveaway.checkWinnerEntry(user)) validUsers++;
if (validUsers === giveaway.winnerCount) {
await this.end(giveaway.messageId).catch(() => {});
break;
}
}
}
} else this.emit('giveawayReactionRemoved', giveaway, member, reaction);
Expand Down
3 changes: 3 additions & 0 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EventEmitter } from 'node:events';
import {
Client,
Collection,
ColorResolvable,
EmojiIdentifierResolvable,
GuildMember,
Expand Down Expand Up @@ -178,10 +179,12 @@ export class Giveaway<ExtraData = any> extends EventEmitter {
readonly data: GiveawayData<ExtraData>;
readonly pauseOptions: Required<PauseOptions>;
readonly isDrop: boolean;
readonly messageReaction: MessageReaction | null;

private ensureEndTimeout(): void;
private checkWinnerEntry(user: User): Promise<boolean>;
public checkBonusEntries(user: User): Promise<number>;
public fetchAllEntrants(): Promise<Collection<Snowflake, User>>;
public fillInString(string: string): string;
public fillInString(string: unknown): string | null;
public fillInEmbed(embed: MessageEmbed | MessageEmbedOptions): MessageEmbed;
Expand Down

0 comments on commit c936500

Please sign in to comment.