Skip to content

Commit

Permalink
fix(Giveaway): crash when giveaway guild/host/channel cannot be fetched
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowplay1 committed Oct 26, 2024
1 parent f99de7b commit af11fcd
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 80 deletions.
2 changes: 1 addition & 1 deletion examples/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ client.on('messageCreate', async message => {
titleIcon: client.user?.displayAvatarURL({ size: 2048 }),

description: `Prize: **${giveaway.prize}**.\nWinners: **${giveaway.winnersCount}**\n` +
`Entries: **${giveaway.entriesCount}**\nHost: **${host.username}**\nEnds at: <t:${giveaway.endTimestamp}:R>\n\n` +
`Entries: **${giveaway.entriesCount}**\nHost: **${host.username}**\nEnds at: <t:${giveaway.endTimestamp}:R>\n\n` +
`- Required roles: ${participantsFilters.requiredRoles?.join(', ') || 'none'}\n` +
`- Forbidden roles: ${participantsFilters.restrictedRoles?.join(', ') || 'none'}\n`,

Expand Down
101 changes: 50 additions & 51 deletions src/Giveaways.ts

Large diffs are not rendered by default.

115 changes: 104 additions & 11 deletions src/lib/Giveaway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import {
import { convertTimeToMilliseconds } from './util/functions/time.function'

import { MessageUtils } from './util/classes/MessageUtils'
import { GiveawaysError, GiveawaysErrorCodes, errorMessages } from './util/classes/GiveawaysError'
import { TypedObject } from './util/classes/TypedObject'

import {
GiveawaysError, GiveawaysErrorCodes, errorMessages
} from './util/classes/GiveawaysError'

import { AddPrefix, DiscordID, OptionalProps, RequiredProps } from '../types/misc/utils'
import { IDatabaseArrayGiveaway } from '../types/databaseStructure.interface'
Expand Down Expand Up @@ -300,7 +304,7 @@ export class Giveaway<
* Number of users who have joined the giveaway.
* @type {number}
*/
this.entriesCount = giveaway.entries.length
this.entriesCount = giveaway.entries?.length || 0

/**
* An object with conditions for members to join the giveaway.
Expand Down Expand Up @@ -397,6 +401,8 @@ export class Giveaway<
* @returns {Promise<void>}
*/
public async restart(): Promise<void> {
await this._fetchUncached()

const { giveawayIndex } = this._getFromCache(this.guild.id)

this.isEnded = false
Expand All @@ -416,7 +422,7 @@ export class Giveaway<

message.edit({
content: startEmbedStrings?.messageContent,
embeds: Object.keys(startEmbedStrings).length == 1
embeds: TypedObject.keys(startEmbedStrings).length == 1
&& startEmbedStrings?.messageContent ? [] : [embed],
components: [buttonsRow]
})
Expand Down Expand Up @@ -455,6 +461,8 @@ export class Giveaway<
* giveaway.extend('10s') // we know that giveaway is running - the method is safe to run
*/
public async extend(extensionTime: string): Promise<void> {
await this._fetchUncached()

const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id)

if (!extensionTime) {
Expand Down Expand Up @@ -493,7 +501,7 @@ export class Giveaway<

message.edit({
content: startEmbedStrings?.messageContent,
embeds: Object.keys(startEmbedStrings).length == 1
embeds: TypedObject.keys(startEmbedStrings).length == 1
&& startEmbedStrings?.messageContent ? [] : [embed],
components: [buttonsRow]
})
Expand Down Expand Up @@ -535,6 +543,8 @@ export class Giveaway<
* giveaway.reduce('10s') // we know that giveaway is running - the method is safe to run
*/
public async reduce(reductionTime: string): Promise<void> {
await this._fetchUncached()

const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id)

if (!reductionTime) {
Expand Down Expand Up @@ -573,7 +583,7 @@ export class Giveaway<

message.edit({
content: startEmbedStrings?.messageContent,
embeds: Object.keys(startEmbedStrings).length == 1
embeds: TypedObject.keys(startEmbedStrings).length == 1
&& startEmbedStrings?.messageContent ? [] : [embed],
components: [buttonsRow]
})
Expand Down Expand Up @@ -612,6 +622,8 @@ export class Giveaway<
* giveaway.end() // we know that giveaway is running - the method is safe to run
*/
public async end(): Promise<void> {
await this._fetchUncached()

const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id)
const winnersIDs = this._pickWinners(giveaway)

Expand Down Expand Up @@ -649,6 +661,8 @@ export class Giveaway<
* @returns {Promise<string[]>} Rerolled winners users IDs.
*/
public async reroll(): Promise<string[]> {
await this._fetchUncached()

const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id)
const winnersIDs = this._pickWinners(giveaway)

Expand Down Expand Up @@ -677,7 +691,7 @@ export class Giveaway<

giveawayMessage.reply({
content: rerollMessage?.messageContent,
embeds: Object.keys(rerollMessage).length === 1 && rerollMessage?.messageContent ? [] : [rerolledEmbed]
embeds: TypedObject.keys(rerollMessage).length === 1 && rerollMessage?.messageContent ? [] : [rerolledEmbed]
})

this._giveaways.emit('giveawayReroll', {
Expand Down Expand Up @@ -1132,7 +1146,7 @@ export class Giveaway<

message.edit({
content: startEmbedStrings?.messageContent,
embeds: Object.keys(startEmbedStrings).length == 1
embeds: TypedObject.keys(startEmbedStrings).length == 1
&& startEmbedStrings?.messageContent ? [] : [embed],
components: [buttonsRow]
})
Expand All @@ -1152,16 +1166,20 @@ export class Giveaway<
* @returns {Promise<Giveaway<DatabaseType>>} Deleted {@link Giveaway} instance.
*/
public async delete(): Promise<Giveaway<DatabaseType>> {
const { giveawayIndex } = this._getFromCache(this.guild.id)
const giveawayMessage = await this.channel.messages.fetch(this.messageID)
const { giveawayIndex } = this._getFromCache(this.guild?.id || this.raw.guildID)
const giveawayMessage = await this.channel.messages.fetch(this.messageID || this.raw.messageID)

if (giveawayMessage.deletable) {
giveawayMessage.delete()
giveawayMessage?.delete().catch(() => {
return
})
} else {
giveawayMessage.edit({
giveawayMessage?.edit({
content: '',
embeds: [],
components: []
}).catch(() => {
return
})
}

Expand Down Expand Up @@ -1347,6 +1365,81 @@ export class Giveaway<
}
}

/**
* Fetches the objects of guild, host user and giveaway channel
* directly from Discord API if something is not present in the cache.
* @returns {Promise<void>}
* @private
*/
private async _fetchUncached(): Promise<void> {
const { guildID, hostMemberID, channelID } = this.raw

const printErrorAndDeleteGiveaway = async (dataFailedToFetch: 'guild' | 'host member' | 'channel'): Promise<void> => {
this._giveaways.logger.error(
`Unable to fetch the giveaway ${dataFailedToFetch} info. Cannot proceed with operation!!`
)

this._giveaways.logger.info('Unprocessable Giveaway Info:')

this._giveaways.logger.info(`Giveaway ID: ${this.id}`)
this._giveaways.logger.info(`Giveaway prize: "${this.prize}", entries count: ${this.entriesCount}.`)
this._giveaways.logger.info(`Giveaway entries count: ${this.entriesCount}.`)
this._giveaways.logger.info()

this._giveaways.logger.info(`Giveaway Guild ID: ${guildID}`)
this._giveaways.logger.info(`Giveaway Host Memebr ID: ${hostMemberID}`)
this._giveaways.logger.info(`Giveaway Channel ID: ${channelID}`)

this._giveaways.logger.warn('Forcefully deleting the giveaway...')

await this.delete().catch((err: Error) => {
this._giveaways.logger.error(`Failed to delete the unprorcessable giveaway: ${err.name}: ${err.message}`)
})

this._giveaways.logger.warn()
this._giveaways.logger.warn('Unprocessable giveaway was deleted.')
}

if (!this.guild) {
const fetched = await this._giveaways.client.guilds.fetch(guildID).catch(() => {
return null
})

if (!fetched) {
await printErrorAndDeleteGiveaway('guild')
return
}

this.guild = fetched
}

if (!this.host) {
const fetched = await this._giveaways.client.users.fetch(hostMemberID).catch(() => {
return null
})

if (!fetched) {
await printErrorAndDeleteGiveaway('host member')
return
}

this.host = fetched
}

if (!this.channel) {
const fetched = await this._giveaways.client.channels.fetch(channelID).catch(() => {
return null
})

if (!fetched) {
await printErrorAndDeleteGiveaway('channel')
return
}

this.channel = fetched as TextChannel
}
}

/**
* Converts the {@link Giveaway} instance to a plain object representation.
* @returns {IGiveaway} Plain object representation of {@link Giveaway} instance.
Expand Down
2 changes: 1 addition & 1 deletion src/lib/managers/DatabaseManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export class DatabaseManager<TDatabaseType extends DatabaseType, TKey extends st
? this.all<any>()
: this.get<any>(key)

return Object.keys(database)
return Object.keys(database || {})
}

/**
Expand Down
16 changes: 8 additions & 8 deletions src/lib/util/classes/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export class Logger {
* @param {string} [color='red'] Message color to use.
* @returns {void}
*/
public info(message: string, color: keyof ILoggerColors = 'cyan'): void {
console.log(`${this.colors[color]}[Giveaways] ${message}${this.colors.reset}`)
public info(message?: string, color: keyof ILoggerColors = 'cyan'): void {
console.log(`${this.colors[color]}[Giveaways] ${message || ''}${this.colors.reset}`)
}

/**
Expand All @@ -68,8 +68,8 @@ export class Logger {
* @param {string} [color='red'] Message color to use.
* @returns {void}
*/
public error(message: string, color: keyof ILoggerColors = 'red'): void {
console.error(`${this.colors[color]}[Giveaways - Error] ${message}${this.colors.reset}`)
public error(message?: string, color: keyof ILoggerColors = 'red'): void {
console.error(`${this.colors[color]}[Giveaways - Error] ${message || ''}${this.colors.reset}`)
}

/**
Expand All @@ -78,9 +78,9 @@ export class Logger {
* @param {string} [color='yellow'] Message color to use.
* @returns {void}
*/
public debug(message: string, color: keyof ILoggerColors = 'yellow'): void {
public debug(message?: string, color: keyof ILoggerColors = 'yellow'): void {
if (!this.debugMode) return
console.log(`${this.colors[color]}[Giveaways] ${message}${this.colors.reset}`)
console.log(`${this.colors[color]}[Giveaways] ${message || ''}${this.colors.reset}`)
}

/**
Expand All @@ -89,8 +89,8 @@ export class Logger {
* @param {string} [color='lightyellow'] Message color to use.
* @returns {void}
*/
public warn(message: string, color: keyof ILoggerColors = 'lightyellow'): void {
console.log(`${this.colors[color]}[Giveaways - Warning] ${message}${this.colors.reset}`)
public warn(message?: string, color: keyof ILoggerColors = 'lightyellow'): void {
console.log(`${this.colors[color]}[Giveaways - Warning] ${message || ''}${this.colors.reset}`)
}

/**
Expand Down
15 changes: 8 additions & 7 deletions src/lib/util/classes/MessageUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IGiveaway } from '../../giveaway.interface'

import { replaceGiveawayKeys } from '../../../structures/giveawayTemplate'
import { Giveaways } from '../../../Giveaways'
import { TypedObject } from './TypedObject'

/**
* Message utils class.
Expand Down Expand Up @@ -235,7 +236,7 @@ export class MessageUtils {

await message.edit({
content: embedStrings?.messageContent,
embeds: Object.keys(embedStrings).length == 1 &&
embeds: TypedObject.keys(embedStrings).length == 1 &&
embedStrings?.messageContent
? [] : [embed],
components: [buttonsRow]
Expand Down Expand Up @@ -286,7 +287,7 @@ export class MessageUtils {

const giveawayEndEmbed = this.buildGiveawayEmbed(
giveaway,
winnersCondition ? (embedStrings?.endMessage || embedStrings?.newGiveawayMessage || {}) : embedStrings?.noWinnersEndMessage,
winnersCondition ? (embedStrings?.endMessage || embedStrings?.newGiveawayMessage || {}) : embedStrings?.noWinnersEndMessage || {},
winners
)

Expand All @@ -305,19 +306,19 @@ export class MessageUtils {
const finishMessageContent =
replaceGiveawayKeys(
winnersCondition
? endEmbedStrings?.messageContent || embedStrings?.endMessage.messageContent as string
: embedStrings?.noWinnersEndMessage?.messageContent as string,
? endEmbedStrings?.messageContent || embedStrings?.endMessage.messageContent!
: embedStrings?.noWinnersEndMessage?.messageContent || 'There was no winners in this giveaway!',
giveaway,
winners
)

const finishInputObjectKeys = winnersCondition
? Object.keys(embedStrings?.endMessage || {})
: Object.keys(embedStrings?.noWinnersEndMessage || {})
? TypedObject.keys(embedStrings?.endMessage)
: TypedObject.keys(embedStrings?.noWinnersEndMessage)

await message.edit({
content: giveawayMessageContent,
embeds: Object.keys(defaultedEmbedStrings).length == 1 && giveawayMessageContent ? [] : [finishEmbed],
embeds: TypedObject.keys(defaultedEmbedStrings).length == 1 && giveawayMessageContent ? [] : [finishEmbed],
components: winnersCondition ? [rerollButtonRow] : []
})

Expand Down
4 changes: 3 additions & 1 deletion src/lib/util/classes/TypedObject.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { MaybeUndefined } from "../../../types/misc/utils"

/**
* Utility class for working with objects.
*
Expand All @@ -19,7 +21,7 @@ export class TypedObject {
* @returns {Array<ExtractObjectKeys<TObject>>}
* Array of names of the enumerable string properties and methods of the specified object.
*/
public static keys<TObject extends Record<string, any>>(obj: TObject): ExtractObjectKeys<TObject>[] {
public static keys<TObject extends Record<string, any>>(obj: MaybeUndefined<TObject>): ExtractObjectKeys<TObject>[] {
return Object.keys(obj || {})
}

Expand Down
12 changes: 12 additions & 0 deletions src/types/misc/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ export type MapCallback<T, TReturnType> = (item: T) => TReturnType
*/
export type Maybe<T> = Exclude<T | null, undefined>

/**
* A type that represents any value with "undefined" possible to be returned.
*
* Type parameters:
*
* - `T` ({@link any}) - The type to attach.
*
* @template T - The type to attach.
* @typedef {any} MaybeUndefined<T>
*/
export type MaybeUndefined<T> = Exclude<T | undefined, null>

/**
* Adds a prefix at the beginning of a string literal type.
*
Expand Down

0 comments on commit af11fcd

Please sign in to comment.