From b5a39e8433b51541c41167b38178f2741fa2c791 Mon Sep 17 00:00:00 2001 From: Asayukiii Date: Sun, 22 Sep 2024 05:15:29 +0000 Subject: [PATCH 1/5] fix: not taking embed string definitions for some cases --- .gitignore | 2 +- dist/package.json | 89 + dist/src/Giveaways.js | 1459 +++++++++++++++++ dist/src/index.js | 38 + dist/src/lib/Giveaway.js | 1034 ++++++++++++ dist/src/lib/giveaway.interface.js | 14 + dist/src/lib/managers/CacheManager.js | 135 ++ dist/src/lib/managers/DatabaseManager.js | 638 +++++++ dist/src/lib/util/classes/Emitter.js | 70 + dist/src/lib/util/classes/GiveawaysError.js | 123 ++ dist/src/lib/util/classes/JSONParser.js | 136 ++ dist/src/lib/util/classes/Logger.js | 121 ++ dist/src/lib/util/classes/MessageUtils.js | 229 +++ dist/src/lib/util/classes/TypedObject.js | 56 + .../functions/checkConfiguration.function.js | 102 ++ .../util/functions/checkUpdates.function.js | 29 + dist/src/lib/util/functions/time.function.js | 44 + .../src/lib/util/functions/typeOf.function.js | 29 + dist/src/structures/defaultConfig.js | 20 + dist/src/structures/giveawayTemplate.js | 33 + dist/src/types/configurations.js | 2 + dist/src/types/databaseStructure.interface.js | 2 + dist/src/types/databaseType.enum.js | 16 + dist/src/types/debug.interface.js | 2 + dist/src/types/giveawaysEvents.interface.js | 2 + dist/src/types/misc/colors.interface.js | 2 + dist/src/types/misc/utils.js | 2 + src/lib/Giveaway.ts | 2 +- src/lib/util/classes/MessageUtils.ts | 3 +- 29 files changed, 4430 insertions(+), 4 deletions(-) create mode 100644 dist/package.json create mode 100644 dist/src/Giveaways.js create mode 100644 dist/src/index.js create mode 100644 dist/src/lib/Giveaway.js create mode 100644 dist/src/lib/giveaway.interface.js create mode 100644 dist/src/lib/managers/CacheManager.js create mode 100644 dist/src/lib/managers/DatabaseManager.js create mode 100644 dist/src/lib/util/classes/Emitter.js create mode 100644 dist/src/lib/util/classes/GiveawaysError.js create mode 100644 dist/src/lib/util/classes/JSONParser.js create mode 100644 dist/src/lib/util/classes/Logger.js create mode 100644 dist/src/lib/util/classes/MessageUtils.js create mode 100644 dist/src/lib/util/classes/TypedObject.js create mode 100644 dist/src/lib/util/functions/checkConfiguration.function.js create mode 100644 dist/src/lib/util/functions/checkUpdates.function.js create mode 100644 dist/src/lib/util/functions/time.function.js create mode 100644 dist/src/lib/util/functions/typeOf.function.js create mode 100644 dist/src/structures/defaultConfig.js create mode 100644 dist/src/structures/giveawayTemplate.js create mode 100644 dist/src/types/configurations.js create mode 100644 dist/src/types/databaseStructure.interface.js create mode 100644 dist/src/types/databaseType.enum.js create mode 100644 dist/src/types/debug.interface.js create mode 100644 dist/src/types/giveawaysEvents.interface.js create mode 100644 dist/src/types/misc/colors.interface.js create mode 100644 dist/src/types/misc/utils.js diff --git a/.gitignore b/.gitignore index 1d32c14..ae323f9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ node_modules/ .vscode/ # build files -dist/ +# dist/ # compiled types *.d.ts diff --git a/dist/package.json b/dist/package.json new file mode 100644 index 0000000..521a906 --- /dev/null +++ b/dist/package.json @@ -0,0 +1,89 @@ +{ + "name": "discord-giveaways-super", + "version": "1.1.0", + "description": "Create and manage giveaways in Discord.", + "types": "./types/src/index.d.ts", + "main": "dist/src/index.js", + "scripts": { + "prep": "npx husky install && npm run commitlint:install", + "lint": "eslint ./src", + "lint:fix": "eslint ./src --fix", + "build": "tsc -p tsconfig.json", + "docs:generate": "bash ./scripts/docgen.sh", + "commitlint:install": "npm i -g @commitlint/cli @commitlint/config-conventional -f" + }, + "keywords": [ + "discord", + "bot", + "bots", + "discord-bot", + "discord bot", + "discord-bots", + "discord bots", + "giveaway", + "giveaways", + "giveaway-bot", + "giveaways-bot", + "giveaways-bots", + "discord-giveaway-bot", + "discord-giveaway-bots", + "easy-giveaway", + "easy-giveaways", + "giveaway-bots", + "discord-giveaways", + "discord-giveaways-super", + "fast", + "easy", + "easy-to-use", + "json", + "mongodb", + "enmap", + "djs", + "discord.js", + "discord.js-v14", + "discord.js-v15", + "djs-v14", + "djs-v15", + "v14", + "v15", + "shadowplay" + ], + "author": "shadowplay1 ", + "license": "MIT", + "dependencies": { + "discord.js": "^14.15.3", + "enmap": "^5.9.8", + "quick-mongo-super": "^1.0.19" + }, + "devDependencies": { + "@babel/core": "^7.22.5", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/preset-env": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@distube/docgen": "github:distubejs/docgen", + "@types/node": "^20.14.3", + "@types/node-fetch": "^2.6.4", + "@typescript-eslint/eslint-plugin": "^6.0.0-alpha.163", + "@typescript-eslint/parser": "^6.0.0-alpha.163", + "eslint": "^8.43.0", + "jsdoc-babel": "^0.5.0" + }, + "quick-mongo-super": { + "postinstall": false + }, + "engines": { + "node": ">=16.9.0" + }, + "directories": { + "lib": "./src" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/shadowplay1/discord-giveaways-super.git" + }, + "bugs": { + "url": "https://github.com/shadowplay1/discord-giveaways-super/issues" + }, + "homepage": "https://dgs-docs.js.org" +} diff --git a/dist/src/Giveaways.js b/dist/src/Giveaways.js new file mode 100644 index 0000000..1609fcc --- /dev/null +++ b/dist/src/Giveaways.js @@ -0,0 +1,1459 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Giveaways = void 0; +const fs_1 = require("fs"); +const promises_1 = require("fs/promises"); +const quick_mongo_super_1 = __importDefault(require("quick-mongo-super")); +const enmap_1 = __importDefault(require("enmap")); +const discord_js_1 = require("discord.js"); +const databaseType_enum_1 = require("./types/databaseType.enum"); +const checkUpdates_function_1 = require("./lib/util/functions/checkUpdates.function"); +const package_json_1 = require("../package.json"); +const GiveawaysError_1 = require("./lib/util/classes/GiveawaysError"); +const Logger_1 = require("./lib/util/classes/Logger"); +const Emitter_1 = require("./lib/util/classes/Emitter"); +const DatabaseManager_1 = require("./lib/managers/DatabaseManager"); +const checkConfiguration_function_1 = require("./lib/util/functions/checkConfiguration.function"); +const Giveaway_1 = require("./lib/Giveaway"); +const giveaway_interface_1 = require("./lib/giveaway.interface"); +const giveawayTemplate_1 = require("./structures/giveawayTemplate"); +const MessageUtils_1 = require("./lib/util/classes/MessageUtils"); +const time_function_1 = require("./lib/util/functions/time.function"); +const TypedObject_1 = require("./lib/util/classes/TypedObject"); +/** + * Main Giveaways class. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. + * + * - `TDatabaseKey` ({@link string}, optional: defaults to `${string}.giveaways`) - + * The type of database key that will be used in database operations. + * + * - `TDatabaseValue` ({@link any}, optional: defaults to {@link IDatabaseStructure}) - + * The type of database content that will be used in database operations. + * + * @extends {Emitter>} + * + * @template TDatabaseType The database type that is used. + * @template TDatabaseKey The type of database key that will be used in database operations. + * @template TDatabaseValue The type of database content that will be used in database operations. + */ +class Giveaways extends Emitter_1.Emitter { + /** + * Discord Client. + * @type {Client} + */ + client; + /** + * {@link Giveaways} ready state. + * @type {boolean} + */ + ready; + /** + * {@link Giveaways} version. + * @type {string} + */ + version; + /** + * Completed, filled and fixed {@link Giveaways} configuration. + * @type {Required>} + */ + options; + /** + * External database instanca (such as Enmap or MongoDB) if used. + * @type {?Database} + */ + db; + /** + * Database Manager. + * @type {DatabaseManager} + */ + database; + /** + * Giveaways logger. + * @type {Logger} + * @private + */ + _logger; + /** + * Message generation utility methods. + * @type {MessageUtils} + * @private + */ + _messageUtils; + /** + * Giveaways ending state checking interval. + * @type {NodeJS.Timeout} + */ + giveawaysCheckingInterval; + /** + * Main {@link Giveaways} constructor. + * @param {Client} client Discord client. + * @param {IGiveawaysConfiguration} options {@link Giveaways} configuration. + */ + constructor(client, options) { + super(); + /** + * Discord Client. + * @type {Client} + */ + this.client = client; + /** + * {@link Giveaways} ready state. + * @type {boolean} + */ + this.ready = false; + /** + * {@link Giveaways} version. + * @type {string} + */ + this.version = package_json_1.version; + /** + * {@link Giveaways} logger. + * @type {Logger} + * @private + */ + this._logger = new Logger_1.Logger(options.debug || false); + this._logger.debug('Giveaways version: ' + this.version, 'lightcyan'); + this._logger.debug(`Database type is ${options.database}.`, 'lightcyan'); + this._logger.debug('Debug mode is enabled.', 'lightcyan'); + this._logger.sendDevVersionWarning(); + this._logger.debug('Checking the configuration...'); + /** + * Completed, filled and fixed {@link Giveaways} configuration. + * @type {Required>} + */ + this.options = (0, checkConfiguration_function_1.checkConfiguration)(options, options.configurationChecker); + /** + * External database instance (such as Enmap or MongoDB) if used. + * @type {?Database} + */ + this.db = null; // specifying 'null' to just initialize the property; for docs purposes + /** + * Database Manager. + * @type {DatabaseManager} + */ + this.database = null; // specifying 'null' to just initialize the property; for docs purposes + /** + * {@link Giveaways} ending state checking interval. + * @type {NodeJS.Timeout} + */ + this.giveawaysCheckingInterval = null; // specifying 'null' to just initialize the property; for docs purposes + /** + * Message utils instance. + * @type {MessageUtils} + * @private + */ + this._messageUtils = new MessageUtils_1.MessageUtils(this); + this._init(); + } + /** + * Initialize the database connection and initialize the {@link Giveaways} module. + * @returns {Promise} + * @private + */ + async _init() { + this._logger.debug('Giveaways starting process launched.', 'lightgreen'); + if (!this.client) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.GiveawaysErrorCodes.NO_DISCORD_CLIENT); + } + if (!this.options.database) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_CONFIG_OPTION_MISSING('database'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_CONFIG_OPTION_MISSING); + } + if (!this.options.connection) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_CONFIG_OPTION_MISSING('connection'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_CONFIG_OPTION_MISSING); + } + const isDatabaseCorrect = Object.keys(databaseType_enum_1.DatabaseType) + .map(databaseType => databaseType.toLowerCase()) + .includes(this.options.database.toLowerCase()); + if (!isDatabaseCorrect) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('"database"', 'value from "DatabaseType" enum: either "JSON", "MONGODB" or "Enmap"', typeof this.options.database), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (typeof this.options.connection !== 'object') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('connection', 'object', typeof this.options.connection), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + const requiredIntents = [ + discord_js_1.GatewayIntentBits.Guilds, + discord_js_1.GatewayIntentBits.GuildMembers, + discord_js_1.GatewayIntentBits.GuildMessages, + discord_js_1.GatewayIntentBits.GuildMessageReactions + ]; + const clientIntents = new discord_js_1.IntentsBitField(this.client.options.intents); + for (const requiredIntent of requiredIntents) { + if (!clientIntents.has(requiredIntent)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INTENT_MISSING(discord_js_1.GatewayIntentBits[requiredIntent]), GiveawaysError_1.GiveawaysErrorCodes.INTENT_MISSING); + } + } + switch (this.options.database) { + case databaseType_enum_1.DatabaseType.JSON: { + this._logger.debug('Checking the database file...'); + const databaseOptions = this.options.connection; + const isFileExists = (0, fs_1.existsSync)(databaseOptions.path); + if (!isFileExists) { + await (0, promises_1.writeFile)(databaseOptions.path, '{}'); + } + if (databaseOptions.checkDatabase) { + try { + setInterval(async () => { + const isFileExists = (0, fs_1.existsSync)(databaseOptions.path); + if (!isFileExists) { + await (0, promises_1.writeFile)(databaseOptions.path, '{}'); + } + const databaseFile = await (0, promises_1.readFile)(databaseOptions.path, 'utf-8'); + JSON.parse(databaseFile); + }, databaseOptions.checkingInterval); + } + catch (err) { + if (err.message.includes('Unexpected token') || err.message.includes('Unexpected end')) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.DATABASE_ERROR(databaseType_enum_1.DatabaseType.JSON, 'malformed'), GiveawaysError_1.GiveawaysErrorCodes.DATABASE_ERROR); + } + if (err.message.includes('no such file')) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.DATABASE_ERROR(databaseType_enum_1.DatabaseType.JSON, 'notFound'), GiveawaysError_1.GiveawaysErrorCodes.DATABASE_ERROR); + } + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.DATABASE_ERROR(databaseType_enum_1.DatabaseType.JSON), GiveawaysError_1.GiveawaysErrorCodes.DATABASE_ERROR); + } + } + this.emit('databaseConnect'); + break; + } + case databaseType_enum_1.DatabaseType.MONGODB: { + this._logger.debug('Connecting to MongoDB...'); + const databaseOptions = this.options.connection; + const mongo = new quick_mongo_super_1.default(databaseOptions); + const connectionStartDate = Date.now(); + await mongo.connect(); + this.db = mongo; + this._logger.debug(`MongoDB connection established in ${Date.now() - connectionStartDate}ms`, 'lightgreen'); + this.emit('databaseConnect'); + break; + } + case databaseType_enum_1.DatabaseType.ENMAP: { + this._logger.debug('Initializing Enmap...'); + const databaseOptions = this.options.connection; + this.db = new enmap_1.default(databaseOptions); + this.emit('databaseConnect'); + break; + } + default: { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_DATABASE); + } + } + this.database = new DatabaseManager_1.DatabaseManager(this); + await this._sendUpdateMessage(); + this._logger.debug('Waiting for client to be ready...'); + const clientReadyInterval = setInterval(() => { + if (this.client.isReady()) { + clearInterval(clientReadyInterval); + const giveawayCheckingInterval = setInterval(() => { + this._checkGiveaways(); + }, this.options.giveawaysCheckingInterval); + this.giveawaysCheckingInterval = giveawayCheckingInterval; + this.ready = true; + this.emit('ready', this); + this._logger.debug('Giveaways module is ready!', 'lightgreen'); + } + }, 100); + this.client.on('interactionCreate', async (interaction) => { + if (interaction.isButton()) { + const interactionMessage = interaction.message; + if (interaction.customId == 'joinGiveawayButton') { + const guildGiveaways = this.getGuildGiveaways(interactionMessage.guild.id); + const giveaway = guildGiveaways.find(giveaway => giveaway.messageID == interactionMessage.id); + if (giveaway) { + const isUserJoined = giveaway.entries.has(interaction.user.id); + const restrictedMessages = giveaway.messageProps?.embeds.restrictionsMessages; + for (const messageObjectName of TypedObject_1.TypedObject.keys(restrictedMessages || {})) { + const messageObject = restrictedMessages[messageObjectName]; + for (const [key, value] of TypedObject_1.TypedObject.entries(messageObject || {})) { + messageObject[key] = value + ?.toString() + ?.replaceAll('{memberMention}', interaction.user.toString()); + } + } + const memberRestrictionMessage = restrictedMessages?.memberRestricted || {}; + const hasNoRequiredRolesMessage = restrictedMessages?.hasNoRequiredRoles || {}; + const hasRestrictedRolesMessage = restrictedMessages?.hasRestrictedRoles || {}; + if (giveaway.participantsFilter?.restrictedMembers?.length) { + if (!(interaction.member instanceof discord_js_1.GuildMember)) + return; + const restrictedMembers = giveaway.participantsFilter?.restrictedMembers + .map(role => role.replaceAll('<@', '').replaceAll('>', '')); + if (restrictedMembers.includes(interaction.user.id)) { + if (!Object.keys(memberRestrictionMessage).length) { + memberRestrictionMessage.messageContent = 'You **cannot** participate in this giveaway.'; + } + const memberRestrictedEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, memberRestrictionMessage); + interaction.reply({ + content: memberRestrictionMessage?.messageContent, + embeds: Object.keys(memberRestrictionMessage).length == 1 && + memberRestrictionMessage?.messageContent + ? [] : [memberRestrictedEmbed], + ephemeral: true + }); + return; + } + } + if (giveaway.participantsFilter?.requiredRoles?.length) { + if (!(interaction.member instanceof discord_js_1.GuildMember)) + return; + const requiredRoles = giveaway.participantsFilter?.requiredRoles + .map(role => role.replaceAll('<@&', '').replaceAll('>', '')); + let memberHasAtLeastOneRequiredRole = false; + for (const roleID of interaction.member.roles.cache.keys()) { + if (requiredRoles.includes(roleID)) { + memberHasAtLeastOneRequiredRole = true; + } + } + if (!memberHasAtLeastOneRequiredRole) { + if (!Object.keys(hasNoRequiredRolesMessage).length) { + hasNoRequiredRolesMessage.messageContent = + 'You **don\'t** have any of the **required** roles' + + `to join this giveaway: ${giveaway.participantsFilter.requiredRoles.join(', ')}.`; + } + const hasNoRequiredRolesEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, hasNoRequiredRolesMessage); + interaction.reply({ + content: hasNoRequiredRolesMessage?.messageContent, + embeds: Object.keys(hasNoRequiredRolesMessage).length == 1 && + hasNoRequiredRolesMessage?.messageContent + ? [] : [hasNoRequiredRolesEmbed], + ephemeral: true + }); + return; + } + } + if (giveaway.participantsFilter?.restrictedRoles?.length) { + if (!(interaction.member instanceof discord_js_1.GuildMember)) + return; + const restrictedRoles = giveaway.participantsFilter?.restrictedRoles + .map(role => role.replaceAll('<@&', '').replaceAll('>', '')); + for (const restrictedRole of restrictedRoles) { + const memberHasRestrictedRole = interaction.member.roles.cache.has(restrictedRole); + if (memberHasRestrictedRole) { + if (!Object.keys(hasRestrictedRolesMessage).length) { + hasRestrictedRolesMessage.messageContent = + 'You **cannot** have any of these roles to join this giveaway: ' + + `${giveaway.participantsFilter.restrictedRoles.join(', ')}.`; + } + const hasRestrictedRolesEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, hasRestrictedRolesMessage); + interaction.reply({ + content: hasRestrictedRolesMessage?.messageContent, + embeds: Object.keys(hasRestrictedRolesMessage).length == 1 && + hasRestrictedRolesMessage?.messageContent + ? [] : [hasRestrictedRolesEmbed], + ephemeral: true + }); + return; + } + } + } + if (!isUserJoined) { + const giveawayJoinMessage = giveaway.messageProps?.embeds?.joinGiveawayMessage || {}; + const giveawayLeaveEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, giveawayJoinMessage); + const newGiveaway = giveaway.addEntry(interaction.guild.id, interaction.user.id); + if (!Object.keys(giveawayJoinMessage).length) { + giveawayJoinMessage.messageContent = 'You have joined the giveaway!'; + } + interaction.reply({ + content: giveawayJoinMessage?.messageContent, + embeds: Object.keys(giveawayJoinMessage).length == 1 && + giveawayJoinMessage?.messageContent + ? [] : [giveawayLeaveEmbed], + ephemeral: true + }).catch((err) => { + // catching the "unknown interaction" error + // while still sending the response on the button click somehow + if (!err.message.toLowerCase().includes('interaction')) { + throw new GiveawaysError_1.GiveawaysError('Cannot join the giveaway: ' + err, GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_ERROR); + } + }); + this._messageUtils.editEntryGiveawayMessage(newGiveaway); + } + else { + const giveawayLeaveMessage = giveaway.messageProps?.embeds?.leaveGiveawayMessage || {}; + const giveawayLeaveEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, giveawayLeaveMessage); + const newGiveaway = giveaway.removeEntry(interaction.guild.id, interaction.user.id); + if (!Object.keys(giveawayLeaveMessage).length) { + giveawayLeaveMessage.messageContent = 'You have left the giveaway!'; + } + interaction.reply({ + content: giveawayLeaveMessage?.messageContent, + embeds: Object.keys(giveawayLeaveMessage).length == 1 && + giveawayLeaveMessage?.messageContent + ? [] : [giveawayLeaveEmbed], + ephemeral: true + }).catch((err) => { + // catching the "unknown interaction" error + // while still sending the responce on the button click somehow + if (!err.message.toLowerCase().includes('interaction')) { + throw new GiveawaysError_1.GiveawaysError('Cannot leave the giveaway: ' + err, GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_ERROR); + } + }); + this._messageUtils.editEntryGiveawayMessage(newGiveaway); + } + } + else { + throw new GiveawaysError_1.GiveawaysError('Cannot join the giveaway: ' + GiveawaysError_1.errorMessages.UNKNOWN_GIVEAWAY(interactionMessage.id), GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_GIVEAWAY); + } + } + if (interaction.customId == 'rerollButton') { + const guildGiveaways = this.getGuildGiveaways(interactionMessage.guild.id); + const giveaway = guildGiveaways.find(giveaway => giveaway.messageID == interactionMessage.id); + const rerollEmbedStrings = giveaway?.messageProps?.embeds?.reroll; + if (giveaway) { + if (interaction.user.id !== giveaway?.host.id) { + const onlyHostCanReroll = rerollEmbedStrings?.onlyHostCanReroll || {}; + const rerollErroredMessageContent = onlyHostCanReroll?.messageContent; + const errorEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, onlyHostCanReroll); + interaction.reply({ + content: rerollErroredMessageContent, + embeds: Object.keys(onlyHostCanReroll).length == 1 && rerollErroredMessageContent ? [] : [errorEmbed], + ephemeral: true + }).catch((err) => { + // catching the "unknown interaction" error + // while still sending the responce on the button click somehow + if (!err.message.toLowerCase().includes('interaction')) { + throw new GiveawaysError_1.GiveawaysError('Cannot reply to the button: ' + err, GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_ERROR); + } + }); + } + else { + const rerollSuccess = rerollEmbedStrings?.successMessage || {}; + const rerollSuccessfulMessageCreate = rerollSuccess?.messageContent; + giveaway.reroll(); + const successEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, rerollSuccess); + interaction.reply({ + content: rerollSuccessfulMessageCreate, + embeds: Object.keys(rerollSuccess).length == 1 && rerollSuccessfulMessageCreate ? [] : [successEmbed], + ephemeral: true + }).catch((err) => { + // catching the "unknown interaction" error + // while still sending the responce on the button click somehow + if (!err.message.toLowerCase().includes('interaction')) { + throw new GiveawaysError_1.GiveawaysError('Cannot reroll the winners: ' + err, GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_ERROR); + } + }); + } + } + else { + throw new GiveawaysError_1.GiveawaysError('Cannot reroll the winners: ' + GiveawaysError_1.errorMessages.UNKNOWN_GIVEAWAY(interactionMessage.id), GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_GIVEAWAY); + } + } + } + }); + } + /** + * Sends the {@link Giveaways} module update state in the console. + * @returns {Promise} + * @private + */ + async _sendUpdateMessage() { + /* eslint-disable max-len */ + if (this.options.updatesChecker?.checkUpdates) { + const result = await (0, checkUpdates_function_1.checkUpdates)(); + if (!result.updated) { + console.log('\n\n'); + console.log(this._logger.colors.green + '╔═════════════════════════════════════════════════════════════════════╗'); + console.log(this._logger.colors.green + '║ @ discord-giveaways-super - [] X ║'); + console.log(this._logger.colors.green + '║═════════════════════════════════════════════════════════════════════║'); + console.log(this._logger.colors.yellow + `║ The module is ${this._logger.colors.red}out of date!${this._logger.colors.yellow} ║`); + console.log(this._logger.colors.magenta + '║ New version is available! ║'); + console.log(this._logger.colors.blue + `║ ${result.installedVersion} --> ${result.availableVersion} ║`); + console.log(this._logger.colors.cyan + '║ Run "npm i discord-giveaways-super@latest" ║'); + console.log(this._logger.colors.cyan + '║ to update! ║'); + console.log(this._logger.colors.white + '║ View the full changelog here: ║'); + console.log(this._logger.colors.red + `║ https://dgs-docs.js.org/#/docs/main/${result.availableVersion}/general/changelog ║`); + console.log(this._logger.colors.green + '╚═════════════════════════════════════════════════════════════════════╝\x1b[37m'); + console.log('\n\n'); + } + else { + if (this.options.updatesChecker?.upToDateMessage) { + console.log('\n\n'); + console.log(this._logger.colors.green + '╔═════════════════════════════════════════════════════════════════╗'); + console.log(this._logger.colors.green + '║ @ discord-giveaways-super - [] X ║'); + console.log(this._logger.colors.green + '║═════════════════════════════════════════════════════════════════║'); + console.log(this._logger.colors.yellow + `║ The module is ${this._logger.colors.cyan}up to date!${this._logger.colors.yellow} ║`); + console.log(this._logger.colors.magenta + '║ No updates are available. ║'); + console.log(this._logger.colors.blue + `║ Current version is ${result.availableVersion}. ║`); + console.log(this._logger.colors.cyan + '║ Enjoy! ║'); + console.log(this._logger.colors.white + '║ View the full changelog here: ║'); + console.log(this._logger.colors.red + `║ https://dgs-docs.js.org/#/docs/main/${result.availableVersion}/general/changelog ║`); + console.log(this._logger.colors.green + '╚═════════════════════════════════════════════════════════════════╝\x1b[37m'); + console.log('\n\n'); + } + } + } + } + /** + * Starts the giveaway. + * @param {IGiveawayStartConfig} giveawayOptions {@link Giveaway} options. + * @returns {Promise>>} Created {@link Giveaway} instance. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid, `INVALID_TIME` - if invalid time string was specified. + */ + async start(giveawayOptions) { + const { channelID, guildID, hostMemberID, prize, time, winnersCount, defineEmbedStrings, buttons, participantsFilter } = giveawayOptions; + if (!channelID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('channelID', 'Giveaways.start'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (!guildID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaways.start'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (!hostMemberID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('hostMemberID', 'Giveaways.start'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (!prize) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('prize', 'Giveaways.start'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof channelID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.channelID', 'string', channelID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (typeof guildID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (typeof hostMemberID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.hostMemberID', 'string', hostMemberID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (typeof prize !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.prize', 'string', prize), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (typeof time !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.time', 'string', time), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (isNaN(winnersCount)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.winnersCount', 'number', winnersCount), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (buttons && typeof buttons !== 'object') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.buttons', 'object', buttons), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (typeof defineEmbedStrings !== 'function') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.defineEmbedStrings', 'function', defineEmbedStrings), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (!(0, time_function_1.isTimeStringValid)(time)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.GiveawaysErrorCodes.INVALID_TIME); + } + const joinGiveawayButton = buttons?.joinGiveawayButton; + const rerollButton = buttons?.rerollButton; + const goToMessageButton = buttons?.goToMessageButton; + const guildGiveaways = this.getGuildGiveaways(guildID); + const newGiveaway = { + id: ((guildGiveaways.at(-1)?.id || 0)) + 1, + hostMemberID, + guildID, + channelID, + messageID: '', + prize, + startTimestamp: Math.floor(Date.now() / 1000), + endTimestamp: Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(time)) / 1000), + endedTimestamp: 0, + time: time || '1d', + state: giveaway_interface_1.GiveawayState.STARTED, + winnersCount: winnersCount || 1, + entriesCount: 0, + entries: [], + winners: [], + participantsFilter: participantsFilter || {}, + isEnded: false + }; + const hostMember = await this.client.users.fetch(hostMemberID).catch(console.error); + if (!hostMember) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.USER_NOT_FOUND(hostMemberID), GiveawaysError_1.GiveawaysErrorCodes.USER_NOT_FOUND); + } + const definedEmbedStrings = defineEmbedStrings ? defineEmbedStrings(giveawayTemplate_1.giveawayTemplate, hostMember, newGiveaway.participantsFilter) : {}; + const startEmbedStrings = definedEmbedStrings?.start || {}; + const finish = definedEmbedStrings?.finish; + const reroll = definedEmbedStrings?.reroll; + const restrictionsMessages = definedEmbedStrings?.restrictionsMessages; + const channel = this.client.channels.cache.get(channelID); + const giveawayEmbed = this._messageUtils.buildGiveawayEmbed(newGiveaway, startEmbedStrings); + const buttonsRow = this._messageUtils.buildButtonsRow(joinGiveawayButton); + const [finishEmbedStrings, rerollEmbedStrings, restrictionsMessagesStrings] = [ + finish ? finish('{winnersString}', winnersCount) : {}, + reroll ? reroll('{winnersString}', winnersCount) : {}, + restrictionsMessages ? restrictionsMessages('{memberMention}') : {} + ]; + const message = await channel.send({ + content: startEmbedStrings?.messageContent, + embeds: Object.keys(startEmbedStrings).length == 1 && startEmbedStrings?.messageContent ? [] : [giveawayEmbed], + components: [buttonsRow] + }); + newGiveaway.messageID = message.id; + newGiveaway.messageURL = message.url; + newGiveaway.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(newGiveaway.time)) / 1000); + newGiveaway.messageProps = { + embeds: { + start: startEmbedStrings, + joinGiveawayMessage: definedEmbedStrings?.joinGiveawayMessage, + leaveGiveawayMessage: definedEmbedStrings?.leaveGiveawayMessage, + finish: finishEmbedStrings, + reroll: rerollEmbedStrings, + restrictionsMessages: restrictionsMessagesStrings + }, + buttons: { + joinGiveawayButton, + rerollButton, + goToMessageButton + } + }; + this.database.push(`${guildID}.giveaways`, newGiveaway); + const startedGiveaway = new Giveaway_1.Giveaway(this, newGiveaway); + this.emit('giveawayStart', startedGiveaway); + return startedGiveaway; + } + /** + * Finds the giveaway in all giveaways database by its ID. + * @param {number} giveawayID Giveaway ID to find the giveaway by. + * @returns {Maybe>>} Giveaway instance. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid. + */ + get(giveawayID) { + if (!giveawayID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('giveawayID', 'Giveaways.get'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (isNaN(giveawayID)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayID', 'number', giveawayID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + const result = this.find(giveaway => giveaway.id == giveawayID) || null; + return result; + } + /** + * Finds the giveaway in all giveaways database by the specified callback function. + * + * @param {FindCallback>} cb + * The callback function to find the giveaway in the giveaways database. + * + * @returns {Maybe>>} Giveaway instance. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid. + */ + find(cb) { + if (!cb) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('cb', 'Giveaways.find'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof cb !== 'function') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('cb', 'function', cb), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + const giveaways = this.getAll(); + const giveaway = giveaways.find(cb) || null; + return giveaway; + } + /** + * Returns the mapped giveaways array based on the specified callback function. + * + * Type parameters: + * + * - `TReturnType` - the type being returned in a callback function. + * + * @param {FindCallback>} cb + * The callback function to call on the giveaway. + * + * @returns {TReturnType[]} Mapped giveaways array. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid. + */ + map(cb) { + if (!cb) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('cb', 'Giveaways.find'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof cb !== 'function') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('cb', 'function', cb), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + const giveaways = this.getAll(); + const giveaway = giveaways.map(cb); + return giveaway; + } + /** + * Gets all the giveaways from the specified guild in database. + * @param {DiscordID} guildID Guild ID to get the giveaways from. + * @returns {Array>>} Giveaways array from the specified guild in database. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid. + */ + getGuildGiveaways(guildID) { + if (!guildID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaways.getGuildGiveaways'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof guildID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + const giveaways = this.database.get(`${guildID}.giveaways`) || []; + return giveaways.map(giveaway => new Giveaway_1.Giveaway(this, giveaway)); + } + /** + * Gets all the giveaways from all the guilds in database. + * @returns {Array>} Giveaways array from all the guilds in database. + */ + getAll() { + const giveaways = []; + const guildIDs = this.database.getKeys(); + for (const guildID of guildIDs.filter(guildID => !isNaN(parseInt(guildID)))) { + const databaseGiveaways = this.database.get(`${guildID}.giveaways`) || []; + for (const databaseGiveaway of databaseGiveaways) { + giveaways.push(databaseGiveaway); + } + } + return giveaways.map(giveaway => new Giveaway_1.Giveaway(this, giveaway)); + } + /** + * Checks for all giveaways to be finished and end them if they are. + * @returns {void} + * @private + */ + _checkGiveaways() { + const giveaways = this.getAll(); + for (const giveaway of giveaways) { + if (giveaway.isFinished && !giveaway.isEnded) { + giveaway.end(); + } + } + } +} +exports.Giveaways = Giveaways; +// For documentation purposes +/** + * An object that contains an information about a giveaway. + * @typedef {object} IGiveaway + * @prop {number} id The ID of the giveaway. + * @prop {string} prize The prize of the giveaway. + * @prop {string} time The time of the giveaway. + * @prop {GiveawayState} state The state of the giveaway. + * @prop {number} winnersCount The number of possible winners in the giveaway. + * @prop {number} startTimestamp The timestamp when the giveaway started. + * @prop {boolean} isEnded Determines if the giveaway was ended in the database. + * @prop {number} endTimestamp The timestamp when the giveaway ended. + * @prop {DiscordID} hostMemberID The ID of the host member. + * @prop {DiscordID} channelID The ID of the channel where the giveaway is held. + * @prop {DiscordID} messageID The ID of the giveaway message. + * @prop {string} messageURL The URL of the giveaway message. + * @prop {DiscordID} guildID The ID of the guild where the giveaway is held. + * @prop {Array>} entries The array of user Set of IDs of users who have joined the giveaway. + * @prop {Array>} winners Array of used ID who have won in the giveaway. + * + * Don't confuse this property with `winnersCount`, the setting that dertermines how many users can win in the giveaway. + * @prop {number} entriesCount The number of users who have joined the giveaway. + * @prop {Partial} participantsFilter An object with conditions for members to join the giveaway. + * @prop {IGiveawayMessageProps} messageProps The message data properties for embeds and buttons. + * + * @template TDatabaseType The database type that is used. + */ +/** + * An object with conditions for members to join the giveaway. + * @typedef {object} IParticipantsFilter + * @prop {Array>} [requiredRoles] + * Array of role IDs that the user *required* to have in order to participate in a giveaway. + * + * @prop {Array>} [restrictedRoles] + * Array of role IDs that the user *cannot have* in order to participate in a giveaway. + * + * @prop {Array>} [restrictedMembers] + * Array of member IDs of the users who *cannot participate* in the giveaway. + */ +/** + * An interface containing embed objects for various giveaway reroll cases. + * @typedef {object} IGiveawayRerollEmbeds + * @prop {IGiveawayEmbedOptions} onlyHostCanReroll The options for the embed when only the host can reroll. + * @prop {IGiveawayEmbedOptions} newGiveawayMessage The options for the embed when sending a new giveaway message. + * @prop {IGiveawayEmbedOptions} successMessage The options for the embed when the giveaway is successful. + */ +/** + * An interface containing embed objects for various giveaway finish cases. + * @typedef {object} IGiveawayFinishEmbeds + * @prop {IGiveawayEmbedOptions} newGiveawayMessage The options for the embed when sending a new giveaway message. + * @prop {IGiveawayEmbedOptions} endMessage The options for the embed when the giveaway has ended. + * @prop {IGiveawayEmbedOptions} noWinnersNewGiveawayMessage The options for the embed when there are no winners for the giveaway. + * + * @prop {IGiveawayEmbedOptions} noWinnersEndMessage + * The options for the embed when there are no winners for the giveaway and it has ended. + */ +/** + * An interface that contains the data properties for embeds and buttons. + * @typedef {object} IGiveawayMessageProps + * @prop {IGiveawayEmbeds} embeds The embed objects for the giveaway message. + * @prop {IGiveawayButtons} buttons The button objects for the giveaway message. + */ +/** + * An interface containing different types of giveaway embeds in the IGiveaways class. + * @typedef {object} IGiveawayEmbeds + * @prop {IGiveawayEmbedOptions} start Message embed data for cases when the giveaway has started. + * @prop {IGiveawayEmbedOptions} joinGiveawayMessage The message to reply to user with when they join the giveaway. + * + * @prop {IGiveawayEmbedOptions} leaveGiveawayMejoinGiveawayMessage + * The message to reply to user with when they leave the giveaway. + * + * @prop {IGiveawayRerollEmbeds} reroll Message embed data for cases when rerolling the giveaway. + * @prop {IGiveawayFinishEmbeds} finish Message embed data for cases when the giveaway has finished. + * + * @prop {IGiveawayJoinRestrictionsMessages} restrictionsMessages + * Message embed data for all the giveaway joining restrictions cases. + */ +/** + * An object that contains messages that are sent in various giveaway cases, such as end with winners or without winners. + * @typedef {object} IGiveawayFinishMessages + * + * @prop {IGiveawayEmbedOptions} newGiveawayMessage + * The separated message to be sent in the giveaway channel when giveaway ends. + * + * @prop {IGiveawayEmbedOptions} endMessage + * The separated message to be sent in the giveaway channel when a giveaway ends with winners. + * @prop {IGiveawayEmbedOptions} noWinnersNewGiveawayMessage + * The message that will be set to the original giveaway message if there are no winners in the giveaway. + * + * @prop {IGiveawayEmbedOptions} noWinnersEndMessage + * The separated message to be sent in the giveaway channel if there are no winners in the giveaway. + */ +/** + * A function that is called when giveaway is finished. + * @callback GiveawayFinishCallback + * @param {string} winnersString A string that contains the users who won the giveaway separated with comma. + * @param {number} winnersCount Number of winners that were picked. + * @returns {IGiveawayFinishMessages} Giveaway message object. + */ +/** + * An object that contains messages that are sent in various giveaway cases, such as end with winners or without winners. + * @typedef {object} IGiveawayRerollMessages + * + * @prop {IGiveawayEmbedOptions} onlyHostCanReroll + * The message to reply to user with when not a giveaway host tries to do a reroll. + * + * @prop {IGiveawayEmbedOptions} newGiveawayMessage + * The message that will be set to the original giveaway message after the reroll. + * + * @prop {IGiveawayEmbedOptions} successMessage + * The separated message to be sent in the giveaway channel when the reroll is successful. + */ +/** + * A function that is called when giveaway winners are rerolled. + * @callback GiveawayRerollCallback + * + * @param {string} winnersMentionsString + * A string that contains the mentions of users who won the giveaway, separated with comma. + * + * @param {number} winnersCount Number of winners that were picked. + * @returns {IGiveawayRerollMessages} Giveaway message object. + */ +/** + * An object that contains the giveaway buttons that may be set up. + * @typedef {object} IGiveawayMessageButtons + * @prop {IGiveawayButtonOptions} joinGiveawayButton The options for the join giveaway button. + * @prop {IGiveawayButtonOptions} rerollButton The options for the reroll button. + * @prop {IGiveawayButtonOptions} goToMessageButton The options for the go to message button. + */ +/** + * An object that contains an information about a giveaway without internal props. + * @typedef {object} GiveawayWithoutInternalProps + * @prop {number} id The ID of the giveaway. + * @prop {string} prize The prize of the giveaway. + * @prop {string} time The time of the giveaway. + * @prop {number} winnersCount The number of possible winners in the giveaway. + * @prop {number} startTimestamp The timestamp when the giveaway started. + * @prop {number} endTimestamp The timestamp when the giveaway ended. + * @prop {DiscordID} hostMemberID The ID of the host member. + * @prop {DiscordID} channelID The ID of the channel where the giveaway is held. + * @prop {DiscordID} messageID The ID of the giveaway message. + * @prop {string} messageURL The URL of the giveaway message. + * @prop {DiscordID} guildID The ID of the guild where the giveaway is held. + * @prop {Array>} entries The array of user Set of IDs of users who have joined the giveaway. + * @prop {IGiveawayMessageProps} messageProps The message data properties for embeds and buttons. + */ +/** + * A type that contains all giveaway properties that may be safely edited. + * @typedef {'prize' | 'winnersCount' | 'hostMemberID'} EditableGiveawayProperties + */ +/** + * The type that returns the property's value type based on the specified {@link Giveaway} property in `TProperty`. + * + * Type parameters: + * + * - `TProperty` ({@link EditableGiveawayProperties}) - {@link Giveaway} property to get its value type. + * + * @typedef {object} GiveawayPropertyValue + * @template TProperty {@link Giveaway} property to get its value type. + */ +/** + * An enum that determines the state of a giveaway. + * @typedef {number} GiveawayState + * @prop {number} STARTED The giveaway has started. + * @prop {number} ENDED The giveaway has ended. + */ +/** + * Full {@link Giveaways} class configuration object. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - Database type that will + * determine which connection configuration should be used. + * + * @typedef {object} IGiveawaysConfiguration + * @prop {DatabaseType} database Database type to use. + * @prop {DatabaseConnectionOptions} connection Database type to use. + * + * @prop {?number} [giveawaysCheckingInterval=1000] + * Determines how often the giveaways ending state will be checked (in ms). Default: 1000. + * + * @prop {?boolean} [debug=false] Determines if debug mode is enabled. Default: false. + * @prop {?number} [minGiveawayEntries=1] Determines the minimum required giveaway entries to draw the winner. Default: 1 + * @prop {Partial} [updatesChecker] Updates checker configuration. + * @prop {Partial} [configurationChecker] Giveaways config checker configuration. + * + * @template TDatabaseType + * The database type that will determine which connection configuration should be used. + */ +/** + * Optional configuration for the {@link Giveaways} class. + * @typedef {object} IGiveawaysOptionalConfiguration + * + * @prop {?number} [giveawaysCheckingInterval=1000] + * Determines how often the giveaways ending state will be checked (in ms). Default: 1000. + * + * @prop {?boolean} [debug=false] Determines if debug mode is enabled. Default: false. + * @prop {?number} [minGiveawayEntries=1] Determines the minimum required giveaway entries to draw the winner. Default: 1 + * @prop {Partial} [updatesChecker] Updates checker configuration. + * @prop {Partial} [configurationChecker] Giveaways config checker configuration. + */ +/** + * Configuration for the updates checker. + * @typedef {object} IUpdateCheckerConfiguration + * @prop {?boolean} [checkUpdates=true] Sends the update state message in console on start. Default: true. + * @prop {?boolean} [upToDateMessage=false] Sends the message in console on start if module is up to date. Default: false. + */ +/** + * Configuration for the configuration checker. + * @typedef {object} IGiveawaysConfigCheckerConfiguration + * @prop {?boolean} ignoreInvalidTypes Allows the method to ignore the options with invalid types. Default: false. + * @prop {?boolean} ignoreUnspecifiedOptions Allows the method to ignore the unspecified options. Default: true. + * @prop {?boolean} ignoreInvalidOptions Allows the method to ignore the unexisting options. Default: false. + * @prop {?boolean} showProblems Allows the method to show all the problems in the console. Default: true. + * @prop {?boolean} sendLog Allows the method to send the result in the console. + * Requires the 'showProblems' or 'sendLog' options to set. Default: true. + * @prop {?boolean} sendSuccessLog Allows the method to send the result if no problems were found. Default: false. + */ +/** + * JSON database configuration. + * @typedef {object} IJSONDatabaseConfiguration + * @prop {?string} [path='./giveaways.json'] Full path to a JSON storage file. Default: './giveaways.json'. + * @prop {?boolean} [checkDatabase=true] Enables the error checking for database file. Default: true + * @prop {?number} [checkingInterval=1000] Determines how often the database file will be checked (in ms). Default: 1000. + */ +/** + * An object that contains an information about a giveaway that is required fo starting. + * @typedef {object} IGiveawayData + * @prop {string} prize The prize of the giveaway. + * @prop {string} time The time of the giveaway. + * @prop {number} winnersCount The number of possible winners in the giveaway. + * @prop {DiscordID} hostMemberID The ID of the host member. + * @prop {DiscordID} channelID The ID of the channel where the giveaway is held. + * @prop {DiscordID} guildID The ID of the guild where the giveaway is held. + */ +/** + * Giveaway start config. + * @typedef {object} IGiveawayStartConfig + * @prop {string} prize The prize of the giveaway. + * @prop {string} time The time of the giveaway. + * @prop {number} winnersCount The number of possible winners in the giveaway. + * @prop {DiscordID} hostMemberID The ID of the host member. + * @prop {DiscordID} channelID The ID of the channel where the giveaway is held. + * @prop {DiscordID} guildID The ID of the guild where the giveaway is held. + * @prop {IGiveawayButtons} [buttons] Giveaway buttons object. + * @prop {DefineEmbedStringsCallback} [defineEmbedStrings] A function that defines the embed strings used in the giveaway. + */ +/** + * Giveaway buttons that may be specified. + * @typedef {object} IGiveawayButtons + * @prop {?IGiveawayButtonOptions} [joinGiveawayButton] Button object for the "join giveaway" button. + * @prop {?IGiveawayButtonOptions} [rerollButton] Button object for the "reroll" button. + * @prop {?ILinkButton} [goToMessageButton] Link button object for the "go to message" button. + */ +/** + * Link button object. + * + * Please note that URL is not required as it's being applied after starting the giveaway. + * @typedef {object} ILinkButton + * @prop {string} [text] Button text string. + * @prop {string} [emoji] Emoji string. + * @prop {ButtonStyle} url URL that the button will take to. + */ +/** + * A function that defines the embed strings used in the giveaway. + * @callback DefineEmbedStringsCallback + * @param {IGiveaway} giveaway - An object containing information about the giveaway. + * @param {User} giveawayHost - The host of the giveaway. + * @returns {Partial>} - An object containing the defined embed strings. + */ +/** + * Giveaway start options. + * @typedef {object} IGiveawayStartOptions + * @prop {IGiveawayButtons} [buttons] Giveaway buttons object. + * @prop {DefineEmbedStringsCallback} [defineEmbedStrings] A function that defines the embed strings used in the giveaway. + */ +/** + * Object containing embed string definitions used in the IGiveaways class. + * + * Type parameters: + * + * - `IsTemplate` ({@link boolean}) - Determine if the specified giveaway object is a template object. + * + * @typedef {object} IEmbedStringsDefinitions + * + * @prop {IGiveawayEmbedOptions} start + * This object is used in the original giveaway message that people will use to join the giveaway. + * + * @prop {GiveawayFinishCallback} finish + * This function is called and all returned message objects are extracted when the giveaway is finished. + * + * @prop {GiveawayRerollCallback} reroll + * This function is called and all returned message objects are extracted when the giveaway winners are rerolled. + * + * @prop {GiveawayJoinRestrictionsCallback} restrictionsMessages + * This function is called and all returned message objects are extracted when any case + * of the user not being able to participate in a giveaway has triggered + * (such as not having the required role or being completely restricted). + * + * @template IsTemplate Determine if the specified giveaway object is a template object. + */ +/** + * A function that is called when the member cannot join the giveaway + * due to participants filter being set up. + * + * @callback GiveawayJoinRestrictionsCallback + * + * @param {string} memberMention The mention of the user who attempted to join the giveaway. + * @returns {Partial} Giveaway join restrictions messages object. + * + * @template IsTemplate Determine if the specified giveaway object is a template object. + */ +/** + * The object where all the giveaway restrictions messages may be specified. + * @typedef {object} IGiveawayJoinRestrictionsMessages + * @prop {IGiveawayEmbedOptions} memberRestricted + * The message to reply with if the member is restricted from participating in the giveaway. + * + * @prop {IGiveawayEmbedOptions} hasNoRequiredRoles + * The message to reply with if the member doesn't have at least one + * of the **required** roles to participate in the giveaway. + * + * @prop {IGiveawayEmbedOptions} hasRestrictedRoles + * The message to reply with if the member has at least one + * of the **restricted** roles that are not allowing to participate in the giveaway. + */ +/** + * Button object. + * @typedef {object} IGiveawayButtonOptions + * @prop {?string} [text] Button text string. + * @prop {?string} [emoji] Emoji string. + * @prop {?ButtonStyle} [style] Button style. + */ +/** + * Message embed options. + * @typedef {object} IGiveawayEmbedOptions + * + * @prop {?string} [messageContent] + * Message content to specify in the message. + * If only message content is specified, it will be sent without the embed. + * + * @prop {?string} [title] The title of the embed. + * @prop {?string} [titleIcon] The icon of the title in the embed. + * @prop {?string} [titleURL] The url of the icon of the title in the embed. + * @prop {?string} [description] The description of the embed. + * @prop {?string} [footer] The footer of the embed. + * @prop {?string} [footerIcon] The icon of the footer in the embed. + * @prop {?string} [thumbnailURL] Embed thumbnail. + * @prop {?string} [imageURL] Embed Image URL. + * @prop {?ColorResolvable} [color] The color of the embed. + * @prop {?number} [timestamp] The embed timestamp to set. + */ +/** + * JSON database configuration. + * @typedef {object} IJSONDatabaseConfiguration + * @prop {?string} [path='./giveaways.json'] Full path to a JSON storage file. Default: './giveaways.json'. + * @prop {?boolean} [checkDatabase=true] Enables the error checking for database file. Default: true + * @prop {?number} [checkingInterval=1000] Determines how often the database file will be checked (in ms). Default: 1000. + */ +/** + * Database connection options based on the used database type. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - Database type that will + * determine which connection configuration should be used. + * + * @typedef {( + * Partial | EnmapOptions | IMongoConnectionOptions + * )} DatabaseConnectionOptions + * + * @see Partial - JSON configuration. + * + * @see EnmapOptions - Enmap configuration. + * + * @see IMongoConnectionOptions - MongoDB connection configuration. + * + * @template TDatabaseType + * The database type that will determine which connection configuration should be used. + */ +/** + * External database object based on the used database type. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - Database type that will determine + * which connection configuration should be used. + * + * - `TKey` ({@link string}) - The type of database key that will be used. + * - `TValue` ({@link any}) - The type of database values that will be used. + * + * @typedef {( + * null | Enmap | Mongo + * )} Database + * + * @see null - JSON database management object - `null` + * is because it's not an external database - JSON is being parsed by the module itself. + * + * @see Enmap - Enmap database. + * + * @see Mongo - MongoDB database. + * + * @template TDatabaseType + * The database type that will determine which external database management object should be used. + * @template TKey The type of database key that will be used. + * @template TValue The type of database values that will be used. + */ +/** + * An interface containing the structure of the database used in the IGiveaways class. + * @typedef {object} IDatabaseStructure + * @prop {DiscordID} guildID Guild ID that stores the giveaways array + * @prop {IGiveaway[]} giveaways Giveaways array property inside the [guildID] object in database. + */ +/** + * The giveaway data that stored in database, + * @typedef {object} IDatabaseArrayGiveaway + * @prop {IGiveaway} giveaway Giveaway object. + * @prop {number} giveawayIndex Giveaway index in the guild giveaways array. + */ +/** + * A type containing all the {@link Giveaways} events and their return types. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. + * + * - `TDatabaseKey` ({@link string}, optional: defaults to `${string}.giveaways`) - + * The type of database key that will be used in database operations. + * + * - `TDatabaseValue` ({@link any}, optional: defaults to {@link IDatabaseStructure}) - + * The type of database content that will be used in database operations. + * + * @typedef {object} IGiveawaysEvents + * @prop {Giveaways} ready Emits when the {@link Giveaways} module is ready. + * @prop {void} databaseConnect Emits when the connection to the database is established. + * @prop {Giveaway} giveawayStart Emits when the giveaway is started. + * @prop {Giveaway} giveawayRestart Emits when the giveaway is restarted. + * @prop {Giveaway} giveawayEnd Emits when the giveaway is ended. + * @prop {IGiveawayRerollEvent} giveawayReroll Emits when the giveaway winners are rerolled. + * @prop {IGiveawayEditEvent} giveawayEdit Emits when the giveaway info was edited. + * + * @template TDatabaseType The database type that is used. + * @template TDatabaseKey The type of database key that will be used in database operations. + * @template TDatabaseValue The type of database content that will be used in database operations. + */ +/** + * Giveaway reroll event object. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. + * + * @typedef {object} IGiveawayRerollEvent + * @prop {Giveaway} giveaway Giveaway instance. + * @prop {string} newWinners Array of the new picked winners after reroll. + * + * @template TDatabaseType The database type that is used. + */ +/** + * Giveaway time change event object. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. + * + * @typedef {object} IGiveawayTimeChangeEvent + * @prop {string} time The time that affected the giveaway's length. + * @prop {Giveaway} giveaway Giveaway instance. + * + * @template TDatabaseType The database type that is used. + */ +/** + * An interface containing different colors that may be used in a logger. + * @typedef {object} ILoggerColors + * @prop {string} red The color red. + * @prop {string} green The color green. + * @prop {string} yellow The color yellow. + * @prop {string} blue The color blue. + * @prop {string} magenta The color magenta. + * @prop {string} cyan The color cyan. + * @prop {string} white The color white. + * @prop {string} reset The reset color. + * @prop {string} black The color black. + * @prop {string} lightgray The color light gray. + * @prop {string} default The default color. + * @prop {string} darkgray The color dark gray. + * @prop {string} lightred The color light red. + * @prop {string} lightgreen The color light green. + * @prop {string} lightyellow The color light yellow. + * @prop {string} lightblue The color light blue. + * @prop {string} lightmagenta The color light magenta. + * @prop {string} lightcyan The color light cyan. + */ +/** + * An object containing the data about available module updates. + * @typedef {object} IUpdateState + * @prop {boolean} updated Whether an update is available or not. + * @prop {string} installedVersion The currently installed version. + * @prop {string} availableVersion The available version, if any. + */ +// Utility types +/** + * Represents the `if` statement on a type level. + * + * Type parameters: + * + * - `T` ({@link boolean}) - The boolean type to compare with. + * - `IfTrue` ({@link any}) - The type that will be returned if `T` is `true`. + * - `IfFalse` ({@link any}) - The type that will be returned if `T` is `false`. + * + * @typedef {IfTrue | IfFalse} If + * + * @template T The boolean type to compare with. + * @template IfTrue The type that will be returned if `T` is `true`. + * @template IfFalse The type that will be returned if `T` is `false`. + */ +/** + * Makes the specified properties in `K` from the object in `T` optional. + * + * Type parameters: + * + * - `T` ({@link object}) - The object to get the properties from. + * - `K` (keyof T) - The properties to make optional. + * + * @typedef {object} OptionalProps + * + * @template T - The object to get the properties from. + * @template K - The properties to make optional. + */ +/** + * Makes the specified properties in `K` from the object in `T` required. + * + * Type parameters: + * + * - `T` ({@link object}) - The object to get the properties from. + * - `K` (keyof T) - The properties to make required. + * + * @template T - The object to get the properties from. + * @template K - The properties to make required. + * + * @typedef {object} RequiredProps + */ +/** + * A callback function that calls when finding an element in array. + * + * Type parameters: + * + * - `T` ({@link any}) - The type of item to be passed to the callback function. + * + * @callback FindCallback + * @template T The type of item to be passed to the callback function. + * + * @param {T} item The item to be passed to the callback function. + * @returns {boolean} The boolean value returned by the callback function. + */ +/** + * A callback function that calls when mapping the array using the {@link Array.prototype.map} method. + * + * Type parameters: + * + * - `T` ({@link any}) - The type of item to be passed to the callback function. + * - `TReturnType` - ({@link any}) The type of value returned by the callback function. + * + * @callback MapCallback + * + * @template T The type of item to be passed to the callback function. + * @template TReturnType The type of value returned by the callback function. + * + * @param {T} item The item to be passed to the callback function. + * @returns {TReturnType} The value returned by the callback function. + */ +/** + * A type that represents any value with "null" possible to be returned. + * + * Type parameters: + * + * - `T` ({@link any}) - The type to attach. + * + * @template T The type to attach. + * @typedef {any} Maybe + */ +/** + * Adds a prefix at the beginning of a string literal type. + * + * Type parameters: + * + * - `TWord` ({@link string}) The string literal type or union type of them to add the prefix to. + * - `TPrefix` ({@link string}) The string literal type of the prefix to use. + * + * @template TWord The string literal type or union type of them to add the prefix to. + * @template TPrefix The string literal type of the prefix to use. + * + * @typedef {string} AddPrefix + */ +/** +* Constructs an object type with prefixed properties and specified value for each of them. +* +* Type parameters: +* +* - `TWords` ({@link string}) The union type of string literals to add the prefix to. +* - `TPrefix` ({@link string}) The string literal type of the prefix to use. +* - `Value` ({@link any}) Any value to assign as value of each property of the constructed object. +* +* @template TWords The union type of string literals to add the prefix to. +* @template TPrefix The string literal type of the prefix to use. +* @template Value Any value to assign as value of each property of the constructed object. +* +* @typedef {string} PrefixedObject +*/ +/** + * Compares the values on type level and returns a boolean value. + * + * Type parameters: + * + * - `ToCompare` ({@link any}) - The type to compare. + * - `CompareWith` ({@link any}) - The type to compare with. + * + * @template ToCompare The type to compare. + * @template CompareWith The type to compare with. + * + * @typedef {boolean} Equals + */ +/** + * Considers the specified giveaway is running and that is safe to edit its data. + * + * Unlocks the following {@link Giveaway} methods - after performing the {@link Giveaway.isRunning()} type-guard check: + * + * - {@link Giveaway.end()} + * - {@link Giveaway.edit()} + * - {@link Giveaway.extend()} + * - {@link Giveaway.reduce()} + * - {@link Giveaway.setPrize()} + * - {@link Giveaway.setWinnersCount()} + * - {@link Giveaway.setTime()} + * - {@link Giveaway.setHostMemberID()} + * + * Type parameters: + * + * - `TGiveaway` ({@link Giveaway} | {@link UnsafeGiveaway>}) - The giveaway to be considered as safe. + * + * @typedef {SafeGiveaway} + * @template TGiveaway The giveaway to be considered as safe. + */ +/** +* Considers the specified giveaway 'that may be ended' and that is *not* safe to edit its data. +* +* Marks the following {@link Giveaway} methods as 'possibly undefined' to prevent them from running +* before performing the {@link Giveaway.isRunning()} type-guard check: +* +* - {@link Giveaway.end()} +* - {@link Giveaway.edit()} +* - {@link Giveaway.extend()} +* - {@link Giveaway.reduce()} +* - {@link Giveaway.setPrize()} +* - {@link Giveaway.setWinnersCount()} +* - {@link Giveaway.setTime()} +* - {@link Giveaway.setHostMemberID()} +* +* Type parameters: +* +* - `TGiveaway` ({@link Giveaway} | {@link SafeGiveaway>}) - The giveaway to be considered as unsafe. +* +* @typedef {UnsafeGiveaway} +* @template TGiveaway The giveaway to be considered as unsafe. +*/ +/** + * Returns a length of a string on type level. + * + * Type parameters: + * + * - `S` ({@link string}) - The string to check the length of. + * + * @template S The string to check the length of. + * @typedef {number} StringLength + */ +/** +* Conditional type that will return the specified string if it matches the specified length. +* +* Type parameters: +* +* - `N` ({@link number}) - The string length to match to. +* - `S` ({@link string}) - The string to check the length of. +* +* @template N The string length to match to. +* @template S The string to check the length of. +* +* @typedef {number} ExactLengthString +*/ +/** +* Conditional type that will return the specified string if it matches any of the possible Discord ID string lengths. +* +* Type parameters: +* +* - `S` ({@link string}) - The string to check the length of. +* +* @template S The string to check the length of. +* @typedef {number} DiscordID +*/ +/** + * Extracts the type that was passed into `Promise<...>` type. + * + * Type parameters: + * + * - `P` ({@link Promise}) - The Promise to extract the type from. + * + * @template P The Promise to extract the type from. + * @typedef {any} ExtractPromisedType

+ */ +// Events, for documentation purposes +/** + * Emits when the {@link Giveaways} module is ready. + * @event Giveaways#ready + * @param {Giveaways} giveaways Initialized {@link Giveaways} instance. + */ +/** + * Emits when the {@link Giveaways} module establishes the database connection. + * @event Giveaways#databaseConnect + * @param {void} databaseConnect Initialized {@link Giveaways} instance. + */ +/** + * Emits when a giveaway is started. + * @event Giveaways#giveawayStart + * @param {Giveaway} giveaway {@link Giveaway} that started. + */ +/** + * Emits when a giveaway is restarted. + * @event Giveaways#giveawayRestart + * @param {Giveaway} giveaway {@link Giveaway} that restarted. + */ +/** + * Emits when a giveaway is ended. + * @event Giveaways#giveawayEnd + * @param {Giveaway} giveaway {@link Giveaway} that ended. + */ +/** + * Emits when a giveaway is rerolled. + * @event Giveaways#giveawayReroll + * @param {IGiveawayRerollEvent} giveaway {@link Giveaway} that was rerolled. + */ diff --git a/dist/src/index.js b/dist/src/index.js new file mode 100644 index 0000000..2004942 --- /dev/null +++ b/dist/src/index.js @@ -0,0 +1,38 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./types/misc/utils"), exports); +__exportStar(require("./types/misc/colors.interface"), exports); +__exportStar(require("./types/configurations"), exports); +__exportStar(require("./types/giveawaysEvents.interface"), exports); +__exportStar(require("./lib/managers/DatabaseManager"), exports); +__exportStar(require("./types/databaseStructure.interface"), exports); +__exportStar(require("./types/databaseType.enum"), exports); +__exportStar(require("./structures/defaultConfig"), exports); +__exportStar(require("./structures/giveawayTemplate"), exports); +__exportStar(require("./lib/util/functions/checkConfiguration.function"), exports); +__exportStar(require("./lib/util/functions/checkUpdates.function"), exports); +__exportStar(require("./lib/util/functions/typeOf.function"), exports); +__exportStar(require("./lib/util/functions/time.function"), exports); +__exportStar(require("./lib/util/classes/Logger"), exports); +__exportStar(require("./lib/util/classes/JSONParser"), exports); +__exportStar(require("./lib/util/classes/MessageUtils"), exports); +__exportStar(require("./lib/util/classes/Emitter"), exports); +__exportStar(require("./lib/util/classes/GiveawaysError"), exports); +__exportStar(require("./lib/util/classes/TypedObject"), exports); +__exportStar(require("./Giveaways"), exports); +__exportStar(require("./lib/Giveaway"), exports); +__exportStar(require("./lib/giveaway.interface"), exports); diff --git a/dist/src/lib/Giveaway.js b/dist/src/lib/Giveaway.js new file mode 100644 index 0000000..99b4229 --- /dev/null +++ b/dist/src/lib/Giveaway.js @@ -0,0 +1,1034 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Giveaway = void 0; +const giveaway_interface_1 = require("./giveaway.interface"); +const time_function_1 = require("./util/functions/time.function"); +const MessageUtils_1 = require("./util/classes/MessageUtils"); +const GiveawaysError_1 = require("./util/classes/GiveawaysError"); +const giveawayTemplate_1 = require("../structures/giveawayTemplate"); +/** + * Class that represents the Giveaway object. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. + * + * @implements {Omit} + * @template TDatabaseType The database type that is used. + */ +class Giveaway { + /** + * {@link Giveaways} instance. + * @type {Giveaways} + * @private + */ + _giveaways; + /** + * Message utils instance. + * @type {MessageUtils} + * @private + */ + _messageUtils; + /** + * Input giveaway object. + * @type {IGiveaway} + * @private + */ + _inputGiveaway; + /** + * Giveaway ID. + * @type {number} + */ + id; + /** + * Giveaway prize. + * @type {string} + */ + prize; + /** + * Giveaway time. + * @type {string} + */ + time; + /** + * Giveaway state. + * @type {GiveawayState} + */ + state; + /** + * Number of possible winnersIDs in the giveaway. + * @type {number} + */ + winnersCount; + /** + * Giveaway start timestamp. + * @type {number} + */ + startTimestamp; + /** + * Giveaway end timestamp. + * @type {number} + */ + endTimestamp; + /** + * Timestamp when the giveaway was ended. + * @type {number} + */ + endedTimestamp; + /** + * Giveaway message ID. + * @type {DiscordID} + */ + messageID; + /** + * Giveaway message URL. + * @type {string} + */ + messageURL; + /** + * Guild where the giveaway was created. + * @type {Guild} + */ + guild; + /** + * User who created the giveaway. + * @type {User} + */ + host; + /** + * Channel where the giveaway was created. + * @type {TextChannel} + */ + channel; + /** + * Number of users who have joined the giveaway. + * @type {number} + */ + entriesCount; + /** + * Set of IDs of users who have joined the giveaway. + * @type {Set>} + */ + entries = new Set(); + /** + * Array of used ID who have won in the giveaway. + * + * Don't confuse this property with `winnersCount`, the setting that dertermines how many users can win in the giveaway. + * @type {Array>} + */ + winners = []; + /** + * Determines if the giveaway was ended in database. + * @type {boolean} + */ + isEnded; + /** + * An object with conditions for members to join the giveaway. + * @type {?IParticipantsFilter} + */ + participantsFilter = {}; + /** + * Message data properties for embeds and buttons. + * @type {?IGiveawayMessageProps} + */ + messageProps; + /** + * Giveaway constructor. + * @param {Giveaways} giveaways {@link Giveaways} instance. + * @param {IGiveaway} giveaway Input {@link Giveaway} object. + */ + constructor(giveaways, giveaway) { + /** + * {@link Giveaways} instance. + * @type {Giveaways} + * @private + */ + this._giveaways = giveaways; + /** + * Message utils instance. + * @type {MessageUtils} + * @private + */ + this._messageUtils = new MessageUtils_1.MessageUtils(giveaways); + /** + * Input giveaway object. + * @type {IGiveaway} + */ + this._inputGiveaway = giveaway; + /** + * Giveaway ID. + * @type {number} + */ + this.id = giveaway.id; + /** + * Giveaway prize. + * @type {string} + */ + this.prize = giveaway.prize; + /** + * Giveaway time. + * @type {string} + */ + this.time = giveaway.time; + /** + * Giveaway state. + * @type {GiveawayState} + */ + this.state = giveaway.state; + /** + * Number of possible winners in the giveaway. + * @type {number} + */ + this.winnersCount = giveaway.winnersCount; + /** + * Giveaway start timestamp. + * @type {number} + */ + this.startTimestamp = giveaway.startTimestamp; + /** + * Giveaway end timestamp. + * @type {number} + */ + this.endTimestamp = giveaway.endTimestamp; + /** + * Giveaway end timestamp. + * @type {number} + */ + this.endedTimestamp = giveaway.endedTimestamp; + /** + * Giveaway message ID. + * @type {DiscordID} + */ + this.messageID = giveaway.messageID; + /** + * Guild where the giveaway was created. + * @type {Guild} + */ + this.guild = this._giveaways.client.guilds.cache.get(giveaway.guildID); + /** + * User who created the giveaway. + * @type {User} + */ + this.host = this._giveaways.client.users.cache.get(giveaway.hostMemberID); + /** + * Channel where the giveaway was created. + * @type {TextChannel} + */ + this.channel = this._giveaways.client.channels.cache.get(giveaway.channelID); + /** + * Giveaway message URL. + * @type {string} + */ + this.messageURL = giveaway.messageURL || ''; + /** + * Determines if the giveaway was ended in database. + * @type {boolean} + */ + this.isEnded = giveaway.isEnded || false; + /** + * Set of IDs of users who have joined the giveaway. + * @type {Set>} + */ + this.entries = new Set(giveaway.entries); + /** + * Array of used ID who have won in the giveaway. + * + * Don't confuse this property with `winnersCount`, + * the setting that dertermines how many users can win in the giveaway. + * @type {Array>} + */ + this.winners = giveaway.winners || []; + /** + * Number of users who have joined the giveaway. + * @type {number} + */ + this.entriesCount = giveaway.entries.length; + /** + * An object with conditions for members to join the giveaway. + * @type {?IParticipantsFilter} + */ + this.participantsFilter = giveaway.participantsFilter; + /** + * Message data properties for embeds and buttons. + * @type {IGiveawayMessageProps} + */ + this.messageProps = giveaway.messageProps || { + embeds: { + start: {}, + joinGiveawayMessage: {}, + leaveGiveawayMessage: {}, + finish: { + endMessage: {}, + newGiveawayMessage: {}, + noWinnersNewGiveawayMessage: {}, + noWinnersEndMessage: {} + }, + reroll: { + newGiveawayMessage: {}, + onlyHostCanReroll: {}, + rerollMessage: {}, + successMessage: {} + }, + restrictionsMessages: { + hasNoRequiredRoles: {}, + hasRestrictedRoles: {}, + memberRestricted: {} + } + }, + buttons: { + joinGiveawayButton: {}, + goToMessageButton: {}, + rerollButton: {} + } + }; + } + /** + * Determines if the giveaway's time is up or if the giveaway was ended forcefully. + * @type {boolean} + */ + get isFinished() { + return (this.state !== giveaway_interface_1.GiveawayState.STARTED || Date.now() > this.endTimestamp * 1000) || this.isEnded; + } + /** + * Raw giveaway object. + * @type {IGiveaway} + */ + get raw() { + const entries = [...this.entries]; + this._inputGiveaway.entries = entries; + return this._inputGiveaway; + } + /** + * [TYPE GUARD FUNCTION] - Determines if the giveaway is running + * and allows to perform actions if it is. + * @returns {boolean} Whether the giveaway is running. + * + * @example + * + * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) + * + * // we don't know if the giveaway is running, + * // so the method is unsafe to run - `extend` will be marked as "possibly undefined" + * // to prevent it from running before the check below + * giveaway.extend('10s') + * + * // checking if the giveaway is running + * if (!giveaway.isRunning()) { + * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) + * } + * + * giveaway.extend('10s') // we know that giveaway is running - the method is safe to run + */ + isRunning() { + return !this.isFinished; + } + /** + * Restarts the giveaway. + * @returns {Promise} + */ + async restart() { + const { giveawayIndex } = this._getFromCache(this.guild.id); + this.isEnded = false; + this.raw.isEnded = false; + this.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(this.time)) / 1000); + this.raw.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(this.time)) / 1000); + const strings = this.messageProps; + const startEmbedStrings = strings?.embeds.start || {}; + const embed = this._messageUtils.buildGiveawayEmbed(this.raw, startEmbedStrings); + const buttonsRow = this._messageUtils.buildButtonsRow(strings?.buttons.joinGiveawayButton || {}); + const message = await this.channel.messages.fetch(this.messageID); + this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); + message.edit({ + content: startEmbedStrings?.messageContent, + embeds: Object.keys(startEmbedStrings).length == 1 + && startEmbedStrings?.messageContent ? [] : [embed], + components: [buttonsRow] + }); + this._giveaways.emit('giveawayRestart', this); + } + /** + * Extends the giveaway length. + * + * [!!!] To be able to run this method, you need to perform a type-guard check + * + * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) + * + * @param {string} extensionTime The time to extend the giveaway length by. + * @returns {Promise} + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid, `INVALID_TIME` - if invalid time string was specified, + * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. + * + * @example + * + * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) + * + * // we don't know if the giveaway is running, + * // so the method is unsafe to run - `extend` will be marked as "possibly undefined" + * // to prevent it from running before the check below + * giveaway.extend('10s') + * + * // checking if the giveaway is running + * if (!giveaway.isRunning()) { + * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) + * } + * + * giveaway.extend('10s') // we know that giveaway is running - the method is safe to run + */ + async extend(extensionTime) { + const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id); + if (!extensionTime) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('extensionTime', 'Giveaways.extend'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof extensionTime !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('extensionTime', 'string', extensionTime), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (this.isEnded) { + throw new GiveawaysError_1.GiveawaysError('Cannot extend the giveaway\'s length: ' + + GiveawaysError_1.errorMessages.GIVEAWAY_ALREADY_ENDED(giveaway.prize, giveaway.id), GiveawaysError_1.GiveawaysErrorCodes.GIVEAWAY_ALREADY_ENDED); + } + this.endTimestamp = this.endTimestamp + this._timeToSeconds(extensionTime); + this.raw.endTimestamp = this.endTimestamp + this._timeToSeconds(extensionTime); + const strings = this.messageProps; + const startEmbedStrings = strings?.embeds.start || {}; + const embed = this._messageUtils.buildGiveawayEmbed(this.raw, startEmbedStrings); + const buttonsRow = this._messageUtils.buildButtonsRow(strings?.buttons.joinGiveawayButton || {}); + const message = await this.channel.messages.fetch(this.messageID); + this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); + message.edit({ + content: startEmbedStrings?.messageContent, + embeds: Object.keys(startEmbedStrings).length == 1 + && startEmbedStrings?.messageContent ? [] : [embed], + components: [buttonsRow] + }); + this._giveaways.emit('giveawayLengthExtend', { + time: extensionTime, + giveaway: this + }); + } + /** + * Reduces the giveaway length. + * + * [!!!] To be able to run this method, you need to perform a type-guard check + * + * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) + * + * @param {string} reductionTime The time to reduce the giveaway length by. + * @returns {Promise} + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid, `INVALID_TIME` - if invalid time string was specified, + * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. + * + * @example + * + * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) + * + * // we don't know if the giveaway is running, + * // so the method is unsafe to run - `reduce` will be marked as "possibly undefined" + * // to prevent it from running before the check below + * giveaway.reduce('10s') + * + * // checking if the giveaway is running + * if (!giveaway.isRunning()) { + * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) + * } + * + * giveaway.reduce('10s') // we know that giveaway is running - the method is safe to run + */ + async reduce(reductionTime) { + const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id); + if (!reductionTime) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('reductionTime', 'Giveaways.extend'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof reductionTime !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('reductionTime', 'string', reductionTime), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (this.isEnded) { + throw new GiveawaysError_1.GiveawaysError('Cannot reduce the giveaway\'s length: ' + + GiveawaysError_1.errorMessages.GIVEAWAY_ALREADY_ENDED(giveaway.prize, giveaway.id), GiveawaysError_1.GiveawaysErrorCodes.GIVEAWAY_ALREADY_ENDED); + } + this.endTimestamp = this.endTimestamp - this._timeToSeconds(reductionTime); + this.raw.endTimestamp = this.endTimestamp - this._timeToSeconds(reductionTime); + const strings = this.messageProps; + const startEmbedStrings = strings?.embeds.start || {}; + const embed = this._messageUtils.buildGiveawayEmbed(this.raw, startEmbedStrings); + const buttonsRow = this._messageUtils.buildButtonsRow(strings?.buttons.joinGiveawayButton || {}); + const message = await this.channel.messages.fetch(this.messageID); + this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); + message.edit({ + content: startEmbedStrings?.messageContent, + embeds: Object.keys(startEmbedStrings).length == 1 + && startEmbedStrings?.messageContent ? [] : [embed], + components: [buttonsRow] + }); + this._giveaways.emit('giveawayLengthReduce', { + time: reductionTime, + giveaway: this + }); + } + /** + * Ends the giveaway. + * + * [!!!] To be able to run this method, you need to perform a type-guard check + * + * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) + * + * @returns {Promise} + * + * @throws {GiveawaysError} `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. + * + * @example + * + * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) + * + * // we don't know if the giveaway is running, + * // so the method is unsafe to run - `end` will be marked as "possibly undefined" + * // to prevent it from running before the check below + * giveaway.end() + * + * // checking if the giveaway is running + * if (!giveaway.isRunning()) { + * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) + * } + * + * giveaway.end() // we know that giveaway is running - the method is safe to run + */ + async end() { + const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id); + const winnersIDs = this._pickWinners(giveaway); + if (this.isEnded) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.GIVEAWAY_ALREADY_ENDED(giveaway.prize, giveaway.id), GiveawaysError_1.GiveawaysErrorCodes.GIVEAWAY_ALREADY_ENDED); + } + const endedTimestamp = Date.now(); + this.isEnded = true; + this.raw.isEnded = true; + this.winners = winnersIDs.map(winnerID => winnerID.slice(2, -1)); + this.raw.winners = winnersIDs.map(winnerID => winnerID.slice(2, -1)); + this.endedTimestamp = endedTimestamp; + this.raw.endedTimestamp = endedTimestamp; + this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); + this._messageUtils.editFinishGiveawayMessage(this.raw, winnersIDs, this.messageProps?.embeds.finish?.newGiveawayMessage); + this._giveaways.emit('giveawayEnd', this); + } + /** + * Redraws the giveaway winners + * @returns {Promise} Rerolled winners users IDs. + */ + async reroll() { + const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id); + const winnersIDs = this._pickWinners(giveaway); + const rerollEmbedStrings = giveaway.messageProps?.embeds?.reroll; + const rerollMessage = rerollEmbedStrings?.rerollMessage || {}; + for (const key in rerollMessage) { + rerollMessage[key] = (0, giveawayTemplate_1.replaceGiveawayKeys)(rerollMessage[key], this, winnersIDs); + } + const rerolledEmbed = this._messageUtils.buildGiveawayEmbed(this.raw, rerollMessage, winnersIDs); + const giveawayMessage = await this.channel.messages.fetch(this.messageID); + this.winners = winnersIDs.map(winnerID => winnerID.slice(2, -1)); + this.raw.winners = winnersIDs.map(winnerID => winnerID.slice(2, -1)); + this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); + this._messageUtils.editFinishGiveawayMessage(this.raw, winnersIDs, rerollEmbedStrings?.newGiveawayMessage, false, rerollEmbedStrings?.successMessage); + giveawayMessage.reply({ + content: rerollMessage?.messageContent, + embeds: Object.keys(rerollMessage).length === 1 && rerollMessage?.messageContent ? [] : [rerolledEmbed] + }); + this._giveaways.emit('giveawayReroll', { + newWinners: winnersIDs, + giveaway: this + }); + return winnersIDs; + } + /** + * Adds the user ID into the giveaway entries. + * @param {DiscordID} guildID The guild ID where the giveaway is hosted. + * @param {DiscordID} userID The user ID to add. + * @returns {IGiveaway} Updated giveaway object. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid. + */ + addEntry(guildID, userID) { + if (!guildID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaway.addEntry'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (!userID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('userID', 'Giveaway.addEntry'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof guildID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (typeof userID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('userID', 'string', userID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + const { giveaway, giveawayIndex } = this._getFromCache(guildID); + this.entries.add(userID); + giveaway.entries.push(userID); + giveaway.entriesCount = this.entries.size; + this._giveaways.database.pull(`${guildID}.giveaways`, giveawayIndex, this.raw); + return giveaway; + } + /** + * Adds the user ID into the giveaway entries. + * @param {DiscordID} guildID The guild ID where the giveaway is hosted. + * @param {DiscordID} userID The user ID to add. + * @returns {IGiveaway} Updated giveaway object. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid. + */ + removeEntry(guildID, userID) { + if (!guildID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaway.removeEntry'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (!userID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('userID', 'Giveaway.removeEntry'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof guildID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (typeof userID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('userID', 'string', userID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + const { giveaway, giveawayIndex } = this._getFromCache(guildID); + this.entries.delete(userID); + giveaway.entries.splice(giveaway.entries.indexOf(userID), 1); + giveaway.entriesCount = this.entries.size; + this.sync(giveaway); + this._giveaways.database.pull(`${guildID}.giveaways`, giveawayIndex, this.raw); + return giveaway; + } + /** + * Changes the giveaway's prize and edits the giveaway message. + * + * [!!!] To be able to run this method, you need to perform a type-guard check + * + * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) + * + * @param {string} prize The new prize to set. + * @returns {Promise>} Updated {@link Giveaway} instance. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid, + * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. + * + * @example + * + * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) + * + * // we don't know if the giveaway is running, + * // so the method is unsafe to run - `setPrize` will be marked as "possibly undefined" + * // to prevent it from running before the check below + * giveaway.setPrize('My New Prize') + * + * // checking if the giveaway is running + * if (!giveaway.isRunning()) { + * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) + * } + * + * giveaway.setPrize('My New Prize') // we know that giveaway is running - the method is safe to run + */ + async setPrize(prize) { + if (!prize) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('prize', 'Giveaways.setPrize'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof prize !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('prize', 'string', prize), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + return this.edit('prize', prize); + } + /** + * Changes the giveaway's winners count and edits the giveaway message. + * + * [!!!] To be able to run this method, you need to perform a type-guard check + * + * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) + * + * @param {string} winnersCount The new winners count to set. + * @returns {Promise>} Updated {@link Giveaway} instance. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid, `INVALID_INPUT` - when the input value is bad or invalid, + * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. + * + * @example + * + * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) + * + * // we don't know if the giveaway is running, + * // so the method is unsafe to run - `setWinnersCount` will be marked as "possibly undefined" + * // to prevent it from running before the check below + * giveaway.setWinnersCount(2) + * + * // checking if the giveaway is running + * if (!giveaway.isRunning()) { + * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) + * } + * + * giveaway.setWinnersCount(2) // we know that giveaway is running - the method is safe to run + */ + async setWinnersCount(winnersCount) { + if (winnersCount == null || winnersCount == undefined) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('winnersCount', 'Giveaways.setWinnersCount'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (isNaN(winnersCount)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('winnersCount', 'number', winnersCount.toString()), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (winnersCount <= 0) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_INPUT('winnersCount', 'Giveaways.setWinnersCount', 'winners count cannot be less or equal 0'), GiveawaysError_1.GiveawaysErrorCodes.INVALID_INPUT); + } + return this.edit('winnersCount', winnersCount); + } + /** + * Changes the giveaway's host member ID and edits the giveaway message. + * + * [!!!] To be able to run this method, you need to perform a type-guard check + * + * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) + * + * @param {DiscordID} hostMemberID The new host member ID to set. + * @returns {Promise>} Updated {@link Giveaway} instance. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid, + * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. + * + * @example + * + * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) + * + * // we don't know if the giveaway is running, + * // so the method is unsafe to run - `setHostMemberID` will be marked as "possibly undefined" + * // to prevent it from running before the check below + * giveaway.setHostMemberID('123456789012345678') + * + * // checking if the giveaway is running + * if (!giveaway.isRunning()) { + * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) + * } + * + * giveaway.setHostMemberID('123456789012345678') // we know that giveaway is running - the method is safe to run + */ + async setHostMemberID(hostMemberID) { + if (!hostMemberID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('hostMemberID', 'Giveaways.setHostMemberID'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof hostMemberID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('hostMemberID', 'string', hostMemberID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + return this.edit('hostMemberID', hostMemberID); + } + /** + * Changes the giveaway's time and edits the giveaway message. + * + * [!!!] To be able to run this method, you need to perform a type-guard check + * + * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) + * + * @param {string} time The new time to set. + * @returns {Promise>} Updated {@link Giveaway} instance. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid, + * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. + * + * @example + * + * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) + * + * // we don't know if the giveaway is running, + * // so the method is unsafe to run - `setTime` will be marked as "possibly undefined" + * // to prevent it from running before the check below + * giveaway.setTime('10s') + * + * // checking if the giveaway is running + * if (!giveaway.isRunning()) { + * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) + * } + * + * giveaway.setTime('10s') // we know that giveaway is running - the method is safe to run + */ + async setTime(time) { + if (!time) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('time', 'Giveaways.setTime'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof time !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('time', 'string', time), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + return this.edit('time', time); + } + /** + * Sets the specified value to the specified giveaway property and edits the giveaway message. + * + * Type parameters: + * + * - `TProperty` ({@link EditableGiveawayProperties}) - Giveaway property to pass in. + * + * [!!!] To be able to run this method, you need to perform a type-guard check + * + * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) + * + * @param {string} key The key of the giveaway object to set. + * @param {string} value The value to set. + * @returns {Promise>} Updated {@link Giveaway} instance. + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid, + * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. + * + * @template TProperty Giveaway property to pass in. + * + * @example + * + * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) + * + * // we don't know if the giveaway is running, + * // so the method is unsafe to run - `edit` will be marked as "possibly undefined" + * // to prevent it from running before the check below + * giveaway.edit('prize', 'My New Prize') + * + * // checking if the giveaway is running + * if (!giveaway.isRunning()) { + * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) + * } + * + * giveaway.edit('prize', 'My New Prize') // we know that giveaway is running - the method is safe to run + */ + async edit(key, value) { + const { giveawayIndex } = this._getFromCache(this.guild.id); + if (!key) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('key', 'Giveaways.edit'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (!value) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('value', 'Giveaways.edit'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof key !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('key', 'string', key), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + if (this.isEnded) { + throw new GiveawaysError_1.GiveawaysError('Cannot edit the giveaway: ' + + GiveawaysError_1.errorMessages.GIVEAWAY_ALREADY_ENDED(this.prize, this.id), GiveawaysError_1.GiveawaysErrorCodes.GIVEAWAY_ALREADY_ENDED); + } + const strings = this.messageProps; + const startEmbedStrings = strings?.embeds.start || {}; + const oldRawGiveaway = { ...this.raw }; + const oldValue = oldRawGiveaway[key]; + if (key == 'hostMemberID') { + const newGiveawayHostUserID = value; + const newGiveawayHost = this._giveaways.client.users.cache.get(newGiveawayHostUserID); + if (!newGiveawayHost) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.USER_NOT_FOUND(newGiveawayHostUserID), GiveawaysError_1.GiveawaysErrorCodes.USER_NOT_FOUND); + } + for (const key in startEmbedStrings) { + if (typeof startEmbedStrings[key] == 'string') { + startEmbedStrings[key] = startEmbedStrings[key] + .replaceAll(this.host.username, newGiveawayHost.username) + .replaceAll(this.host.discriminator, newGiveawayHost.discriminator) + .replaceAll(this.host.tag, newGiveawayHost.tag) + .replaceAll(this.host.avatar, newGiveawayHost.avatar) + .replaceAll(this.host.defaultAvatarURL, newGiveawayHost.defaultAvatarURL) + .replaceAll(this.host.bot, newGiveawayHost.bot) + .replaceAll(this.host.system, newGiveawayHost.system) + .replaceAll(this.host.banner, newGiveawayHost.banner) + .replaceAll(this.host.createdAt, newGiveawayHost.createdAt) + .replaceAll(this.host.createdTimestamp, newGiveawayHost.createdTimestamp) + .replaceAll(this.host.id, newGiveawayHost.id); + } + } + this.host = newGiveawayHost; + this.raw.hostMemberID = newGiveawayHostUserID; + } + else if (key == 'time') { + const time = value; + this.time = time; + this.raw.time = time; + this.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(time)) / 1000); + this.raw.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(time)) / 1000); + } + else { + const that = this; + that[key] = value; + this.raw[key] = value; + } + const embed = this._messageUtils.buildGiveawayEmbed(this.raw, startEmbedStrings); + const buttonsRow = this._messageUtils.buildButtonsRow(strings?.buttons.joinGiveawayButton || {}); + const message = await this.channel.messages.fetch(this.messageID); + this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); + message.edit({ + content: startEmbedStrings?.messageContent, + embeds: Object.keys(startEmbedStrings).length == 1 + && startEmbedStrings?.messageContent ? [] : [embed], + components: [buttonsRow] + }); + this._giveaways.emit('giveawayEdit', { + key, + oldValue, + newValue: value, + giveaway: this + }); + return this; + } + /** + * Deletes the giveaway from database and deletes its message. + * @returns {Promise>} Deleted {@link Giveaway} instance. + */ + async delete() { + const { giveawayIndex } = this._getFromCache(this.guild.id); + const giveawayMessage = await this.channel.messages.fetch(this.messageID); + if (giveawayMessage.deletable) { + giveawayMessage.delete(); + } + else { + giveawayMessage.edit({ + content: '', + embeds: [], + components: [] + }); + } + this._giveaways.database.pop(`${this.guild.id}.giveaways`, giveawayIndex); + return this; + } + /** + * Syncs the constructor properties with specified raw giveaway object. + * @param {IGiveaway} giveaway Giveaway object to sync the constructor properties with. + * @returns {void} + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid. + */ + sync(giveaway) { + const that = this; + const specifiedGiveaway = giveaway; + if (!giveaway) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('giveaway', 'Giveaway.sync'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof giveaway !== 'object') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveaway', 'object', giveaway), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + for (const key in giveaway) { + that[key] = specifiedGiveaway[key]; + if (key == 'entries') { + that[key] = new Set(specifiedGiveaway[key]); + } + } + for (const key in giveaway) { + that.raw[key] = specifiedGiveaway[key]; + if (key == 'entries') { + that[key] = new Set(specifiedGiveaway[key]); + } + } + } + /** + * Shuffles all the giveaway entries, randomly picks the winner user IDs and converts them into mentions. + * @param {IGiveaway} [giveawayToSyncWith] The giveaway object to sync the {@link Giveaway} instance with. + * @returns {string[]} Array of mentions of users who were picked as the winners. + * @private + */ + _pickWinners(giveawayToSyncWith) { + const winnersIDs = []; + if (giveawayToSyncWith) { + this.sync(giveawayToSyncWith); + } + if (!this.entries.size) { + return []; + } + const shuffledEntries = this._shuffleArray([...this.entries]); + for (let i = 0; i < this.winnersCount; i++) { + const recursiveShuffle = () => { + const randomEntryIndex = Math.floor(Math.random() * shuffledEntries.length); + const winnerUserID = shuffledEntries[randomEntryIndex]; + if (winnersIDs.includes(winnerUserID)) { + if (winnersIDs.length !== this.entries.size) { + recursiveShuffle(); + } + } + else { + winnersIDs.push(winnerUserID); + } + }; + recursiveShuffle(); + } + return winnersIDs.map(winnerID => `<@${winnerID}>`); + } + /** + * Shuffles an array and returns it. + * + * Type parameters: + * + * - `T` - The type of array to shuffle. + * + * @param {any[]} arrayToShuffle The array to shuffle. + * @returns {any[]} Shuffled array. + * @private + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid. + * + * @template T The type of array to shuffle. + */ + _shuffleArray(arrayToShuffle) { + if (!arrayToShuffle) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('arrayToShuffle', 'Giveaway._shuffleArray'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (!Array.isArray(arrayToShuffle)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('arrayToShuffle', 'array', arrayToShuffle), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + const shuffledArray = [...arrayToShuffle]; + for (let i = shuffledArray.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]; + } + return shuffledArray; + } + /** + * Gets the giveaway data and its index in guild giveaways array from database. + * @param {DiscordID} guildID Guild ID to get the giveaways array from. + * @returns {IDatabaseArrayGiveaway} Database giveaway object. + * @private + * + * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, + * `INVALID_TYPE` - when argument type is invalid. + */ + _getFromCache(guildID) { + if (!guildID) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaway._getFromCache'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); + } + if (typeof guildID !== 'string') { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); + } + const giveaways = this._giveaways.database.get(`${guildID}.giveaways`) || []; + const giveawayIndex = giveaways.findIndex(giveaway => giveaway.id == this.id); + const giveaway = giveaways[giveawayIndex]; + this.sync(giveaway); + return { + giveaway, + giveawayIndex + }; + } + /** + * Converts the time string into seconds. + * @param {string} time The time string to convert. + * @returns {number} Converted time string into seconds. + * @private + * + * @throws {GiveawaysError} `INVALID_TIME` - if invalid time string was specified. + */ + _timeToSeconds(time) { + try { + const milliseconds = (0, time_function_1.convertTimeToMilliseconds)(time); + return Math.floor(milliseconds / 1000 / 2); + } + catch { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.GiveawaysErrorCodes.INVALID_TIME); + } + } + /** + * Converts the {@link Giveaway} instance to a plain object representation. + * @returns {IGiveaway} Plain object representation of {@link Giveaway} instance. + */ + toJSON() { + return this.raw; + } +} +exports.Giveaway = Giveaway; diff --git a/dist/src/lib/giveaway.interface.js b/dist/src/lib/giveaway.interface.js new file mode 100644 index 0000000..d2d7ad9 --- /dev/null +++ b/dist/src/lib/giveaway.interface.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.GiveawayState = void 0; +/** + * An enum that determines the state of a giveaway. + * @typedef {number} GiveawayState + * @prop {number} STARTED The giveaway has started. + * @prop {number} ENDED The giveaway has ended. + */ +var GiveawayState; +(function (GiveawayState) { + GiveawayState[GiveawayState["STARTED"] = 1] = "STARTED"; + GiveawayState[GiveawayState["ENDED"] = 2] = "ENDED"; +})(GiveawayState || (exports.GiveawayState = GiveawayState = {})); diff --git a/dist/src/lib/managers/CacheManager.js b/dist/src/lib/managers/CacheManager.js new file mode 100644 index 0000000..00ab223 --- /dev/null +++ b/dist/src/lib/managers/CacheManager.js @@ -0,0 +1,135 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.CacheManager = void 0; +const typeOf_function_1 = require("../util/functions/typeOf.function"); +/** + * Cache manager class. + * + * Type parameters: + * + * - `K` ({@link any}) - The cache map key type. + * - `V` ({@link any}) - The cache map value type. + * + * @template K The cache map key type. + * @template V The cache map value type. + */ +class CacheManager { + /** + * Database cache. + * @type {Map} + * @private + */ + _cache; + /** + * Cache manager constructor. + */ + constructor() { + /** + * Database cache. + * @type {Map} + * @private + */ + this._cache = new Map(); + } + /** + * Gets the cache map as an object. + * + * Type parameters: + * + * - `V` ({@link any}) - The type of cache object to return. + * + * @returns {any} Object representation of the cache map. + * @template V The type of cache object to return. + */ + getCacheObject() { + const mapData = {}; + for (const [key, value] of this._cache.entries()) { + mapData[key] = value; + } + return mapData; + } + /** + * Parses the key and fetches the value from cache map. + * + * Type parameters: + * + * - `V` ({@link any}) - The type of data being returned. + * + * @param {K} key The key in cache map. + * @returns {V} The data from cache map. + * + * @template V The type of data being returned. + */ + get(key) { + let data = this.getCacheObject(); + let parsedData = data; + const keys = key.split('.'); + for (let i = 0; i < keys.length; i++) { + if (keys.length - 1 == i) { + data = parsedData?.[keys[i]] || null; + } + parsedData = parsedData?.[keys[i]]; + } + return data; + } + /** + * Parses the key and sets the value in cache map. + * + * Type parameters: + * + * - `TValue` ({@link any}) - The type of data being set. + * - `R` ({@link any}) - The type of data being returned. + * + * @param {K} key The key in cache map. + * @returns {R} The data from cache map. + * + * @template TValue The type of data being set. + * @template R The type of data being returned. + */ + set(key, value) { + const data = this.getCacheObject(); + let updatedData = data; + const keys = key.split('.'); + for (let i = 0; i < keys.length; i++) { + if (keys.length - 1 == i) { + updatedData[keys[i]] = value; + } + else if (!(0, typeOf_function_1.isObject)(data[keys[i]])) { + updatedData[keys[i]] = {}; + } + updatedData = updatedData?.[keys[i]]; + } + this._cache.set(keys[0], data[keys[0]]); + return data; + } + /** + * Parses the key and deletes it from cache map. + * @param {K} key The key in cache map. + * @returns {boolean} `true` if deleted successfully. + */ + delete(key) { + const data = this.getCacheObject(); + let updatedData = data; + const keys = key.split('.'); + for (let i = 0; i < keys.length; i++) { + if (keys.length - 1 == i) { + delete updatedData[keys[i]]; + } + else if (!(0, typeOf_function_1.isObject)(data[keys[i]])) { + updatedData[keys[i]] = {}; + } + updatedData = updatedData?.[keys[i]]; + } + this._cache.set(keys[0], data[keys[0]]); + return true; + } + /** + * Clears the cache. + * @returns {boolean} `true` if cleared successfully. + */ + clear() { + this._cache.clear(); + return true; + } +} +exports.CacheManager = CacheManager; diff --git a/dist/src/lib/managers/DatabaseManager.js b/dist/src/lib/managers/DatabaseManager.js new file mode 100644 index 0000000..77de522 --- /dev/null +++ b/dist/src/lib/managers/DatabaseManager.js @@ -0,0 +1,638 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DatabaseManager = void 0; +const databaseType_enum_1 = require("../../types/databaseType.enum"); +const GiveawaysError_1 = require("../util/classes/GiveawaysError"); +const JSONParser_1 = require("../util/classes/JSONParser"); +const Logger_1 = require("../util/classes/Logger"); +const CacheManager_1 = require("./CacheManager"); +/** + * Database manager class. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - The database type that will determine + * which connection configuration should be used. + * + * - `TKey` ({@link string}) - The type of database key that will be used. + * - `TValue` ({@link any}) - The type of database values that will be used. + * + * @template TDatabaseType + * The database type that will determine which connection configuration should be used. + * @template TKey The type of database key that will be used. + * @template TValue The type of database values that will be used. + */ +class DatabaseManager { + /** + * Database cache manager. + * @type {CacheManager} + * @private + */ + _cache; + /** + * Giveaways logger. + * @type {Logger} + * @private + */ + _logger; + /** + * {@link Giveaways} instance. + * @type {Giveaways} + */ + giveaways; + /** + * Database instance. + * @type {Database} + */ + db; + /** + * Database type. + * @type {DatabaseType} + */ + databaseType; + /** + * JSON parser instance. + * @type {JSONParser} + */ + jsonParser; + /** + * Database manager constructor. + * @param {Giveaways} giveaways {@link Giveaways} instance. + */ + constructor(giveaways) { + /** + * Database cache. + * @type {CacheManager} + * @private + */ + this._cache = new CacheManager_1.CacheManager(); + /** + * Giveaways logger. + * @type {Logger} + * @private + */ + this._logger = new Logger_1.Logger(giveaways.options.debug); + /** + * {@link Giveaways} instance. + * @type {Giveaways} + */ + this.giveaways = giveaways; + /** + * Database instance. + * @type {Database} + */ + this.db = giveaways.db; + /** + * Database type. + * @type {DatabaseType} + */ + this.databaseType = giveaways.options.database; + /** + * JSON parser instance. + * @type {?JSONParser} + */ + this.jsonParser = null; + this._init(); + } + /** + * Initializes the database manager. + * @returns {Promise} + */ + async _init() { + if (this.isJSON()) { + this.jsonParser = new JSONParser_1.JSONParser(this.giveaways.options.connection.path); + } + this._logger.debug('Loading the cache...'); + await this._loadCache(); + } + /** + * Evaluates a database operation ands sends a debug log in the console. + * + * Type parameters: + * + * - `F` ({@link Function}) - The function type to be passed as database operation callback. + * + * @param {string} operation The database operation to put in the debug log. + * @param {string} key The key of the database the operation was performed on. + * @param {Function} toDebug The database operation callback function to call. + * @param {boolean} [sendDebugLog=true] Whether the debug log should be sent in the console if debug mode is enabled. + * + * @returns {Promise>>>} + * Return type of the database callback operation function. + * + * @template F The function type to be passed as database operation callback. + * @private + */ + async _debug(operation, key, toDebug, sendDebugLog = true) { + try { + const callbackResult = await toDebug(); + if (this.giveaways.options.debug && sendDebugLog) { + this._logger.debug(`Performed "${operation}" operation on key "${key}".`); + } + return { + error: null, + result: callbackResult + }; + } + catch (err) { + if (this.giveaways.options.debug && sendDebugLog) { + this._logger.error(`Failed to perform "${operation}" operation on key "${key}": ${err.stack}`); + } + return { + error: err, + result: null + }; + } + } + /** + * [TYPE GUARD FUNCTION] - Determines if the databse type is JSON. + * @returns {boolean} Whether the database type is JSON. + */ + isJSON() { + return this.giveaways.options.database == databaseType_enum_1.DatabaseType.JSON; + } + /** + * [TYPE GUARD FUNCTION] - Determines if the databse type is MongoDB. + * @returns {boolean} Whether the database type is MongoDB. + */ + isMongoDB() { + return this.giveaways.options.database == databaseType_enum_1.DatabaseType.MONGODB; + } + /** + * [TYPE GUARD FUNCTION] - Determines if the databse type is Enmap. + * @returns {boolean} Whether the database type is Enmap. + */ + isEnmap() { + return this.giveaways.options.database == databaseType_enum_1.DatabaseType.ENMAP; + } + /** + * Gets the object keys in database root or in object by specified key. + * @param {TKey} [key] The key in database. Omitting this argument will get the keys from the root of database. + * @returns {string[]} Database object keys array. + */ + getKeys(key) { + const database = key == undefined + ? this.all() + : this.get(key); + return Object.keys(database); + } + /** + * Gets the value from the **cache** by specified key. + * + * Type parameters: + * + * - `V` - The type of data being returned. + * + * @param {TKey} key The key in database. + * @returns {V} Value from database. + * + * @template V The type of data being returned. + */ + get(key) { + const data = this._cache.get(key); + return data; + } + /** + * Gets the value from **database** by specified key. + * @param {TKey} key The key in database. + * @returns {V} Value from database. + * @template V The type that represents the returning value in the method. + */ + async getFromDatabase(key) { + if (this.isJSON()) { + const data = await this.jsonParser.get(key); + return data; + } + if (this.isMongoDB()) { + const data = await this.db.get(key); + return data; + } + if (this.isEnmap()) { + const data = this.db.get(key); + return data; + } + return {}; + } + /** + * Gets the value from the **cache** by specified key. + * + * - This method is an alias to {@link DatabaseManager.get()} method. + * + * Type parameters: + * + * - `V` - The type of data being returned. + * + * @param {TKey} key The key in database. + * @returns {V} Value from database. + * + * @template V The type of data being returned. + */ + fetch(key) { + return this.get(key); + } + /** + * Determines if specified key exists in database. + * @param {TKey} key The key in database. + * @returns {boolean} Boolean value that determines if specified key exists in database. + */ + has(key) { + const data = this.get(key); + return !!data; + } + /** + * Determines if specified key exists in database. + * + * - This method is an alias to {@link DatabaseManager.has()} method. + * + * @param {TKey} key The key in database. + * @returns {boolean} Boolean value that determines if specified key exists in database. + */ + includes(key) { + return this.has(key); + } + /** + * Sets data in database. + * + * Type parameters: + * + * - `V` - The type of data being set. + * - `R` - The type of data being returned. + * + * @param {TKey} key The key in database. + * @param {V} value Any data to set. + * @param {boolean} [sendDebugLog=true] Whether the debug log should be sent in the console if debug mode is enabled. + * @returns {Promise>} The data from the database. + * + * @template V The type of data being set. + * @template R The type of data being returned. + */ + async set(key, value, sendDebugLog = true) { + return this._debug('set', key, async () => { + this._cache.set(key, value); + if (this.isJSON()) { + const data = await this.jsonParser.set(key, value); + return data; + } + if (this.isMongoDB()) { + const data = await this.db.set(key, value); + return data; + } + if (this.isEnmap()) { + this.db.set(key, value); + const data = this.db.get(key); + return data; + } + return {}; + }, sendDebugLog); + } + /** + * Clears the database. + * @param {boolean} [sendDebugLog=true] Whether the debug log should be sent in the console if debug mode is enabled. + * @returns {Promise} `true` if cleared successfully, `false` otherwise. + */ + async clear(sendDebugLog = true) { + this._cache.clear(); + if (this.giveaways.options.debug && sendDebugLog) { + this._logger.debug('Performed "clear" operation on all database.'); + } + if (this.isJSON()) { + await this.jsonParser.clearDatabase(); + return true; + } + if (this.isMongoDB()) { + await this.db.clear(); + return true; + } + if (this.isEnmap()) { + this.db.deleteAll(); + return true; + } + return false; + } + /** + * Clears the database. + * + * - This method is an alias to {@link DatabaseManager.clear()} method. + * @returns {Promise} `true` if set successfully, `false` otherwise. + */ + async deleteAll() { + if (this.giveaways.options.debug) { + this._logger.debug('Performed "deleteAll" operation on all database.'); + } + return this.clear(false); + } + /** + * Adds a number to the data in database. + * @param {TKey} key The key in database. + * @param {number} numberToAdd Any number to add. + * @returns {Promise>} `true` if added successfully, `false` otherwise. + */ + async add(key, numberToAdd) { + return this._debug('add', key, async () => { + const targetNumber = this._cache.get(key); + if (!isNaN(targetNumber)) { + this._cache.set(key, targetNumber + numberToAdd); + } + if (this.isJSON()) { + const targetNumber = await this.jsonParser.get(key); + if (isNaN(targetNumber)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + await this.jsonParser.set(key, targetNumber + numberToAdd); + return true; + } + if (this.isMongoDB()) { + const targetNumber = await this.db.get(key); + if (isNaN(targetNumber)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + await this.db.set(key, targetNumber + numberToAdd); + return true; + } + if (this.isEnmap()) { + const targetNumber = this.db.get(key); + if (isNaN(targetNumber)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + this.db.set(key, targetNumber + numberToAdd); + return true; + } + return false; + }); + } + /** + * Subtracts a number to the data in database. + * @param {TKey} key The key in database. + * @param {number} numberToSubtract Any number to subtract. + * @returns {Promise>} `true` if subtracted successfully, `false` otherwise. + */ + async subtract(key, numberToSubtract) { + return this._debug('subtract', key, async () => { + const targetNumber = this._cache.get(key); + if (!isNaN(targetNumber)) { + this._cache.set(key, targetNumber + numberToSubtract); + } + if (this.isJSON()) { + const targetNumber = await this.jsonParser.get(key); + if (isNaN(targetNumber)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + await this.jsonParser.set(key, targetNumber - numberToSubtract); + return true; + } + if (this.isMongoDB()) { + const targetNumber = await this.db.get(key); + if (isNaN(targetNumber)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + await this.db.set(key, targetNumber - numberToSubtract); + return true; + } + if (this.isEnmap()) { + const targetNumber = this.db.get(key); + if (isNaN(targetNumber)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + this.db.set(key, targetNumber - numberToSubtract); + return true; + } + return false; + }); + } + /** + * Deletes the data from database. + * @param {TKey} key The key in database. + * @returns {Promise>} `true` if deleted successfully, `false` otherwise. + */ + async delete(key) { + return this._debug('delete', key, async () => { + this._cache.delete(key); + if (this.isJSON()) { + await this.jsonParser.delete(key); + return true; + } + if (this.isMongoDB()) { + await this.db.delete(key); + return true; + } + if (this.isEnmap()) { + this.db.delete(key); + return true; + } + return false; + }); + } + /** + * Pushes a value into specified array in database. + * + * Type parameters: + * + * - `V` - The type of data being pushed. + * + * @param {TKey} key The key in database. + * @param {V} value Any value to push into database array. + * @returns {Promise>} `true` if pushed successfully, `false` otherwise. + * + * @template V The type of data being pushed. + */ + async push(key, value) { + return this._debug('push', key, async () => { + const targetArray = this._cache.get(key) || []; + if (Array.isArray(targetArray)) { + targetArray.push(value); + this._cache.set(key, targetArray); + } + if (this.isJSON()) { + const targetArray = await this.jsonParser.get(key) || []; + if (!Array.isArray(targetArray)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + targetArray.push(value); + await this.jsonParser.set(key, targetArray); + return true; + } + if (this.isMongoDB()) { + const targetArray = (await this.db.get(key)) || []; + if (!Array.isArray(targetArray)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + targetArray.push(value); + await this.db.set(key, targetArray); + return true; + } + if (this.isEnmap()) { + const targetArray = (this.db.get(key) || []); + if (!Array.isArray(targetArray)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + targetArray.push(value); + this.db.set(key, targetArray); + return true; + } + return false; + }); + } + /** + * Changes the specified element's value in a specified array in the database. + * + * Type parameters: + * + * - `V` - The type of data being pulled. + * + * @param {TKey} key The key in database. + * @param {number} index The index in the target array. + * @param {V} newValue The new value to set. + * @returns {Promise>} `true` if pulled successfully, `false` otherwise. + * + * @template V The type of data being pulled. + */ + async pull(key, index, newValue) { + return this._debug('pull', key, async () => { + const targetArray = this._cache.get(key) || []; + if (Array.isArray(targetArray)) { + targetArray.splice(index, 1, newValue); + this._cache.set(key, targetArray); + } + if (this.isJSON()) { + const targetArray = await this.jsonParser.get(key) || []; + if (!Array.isArray(targetArray)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + targetArray.splice(index, 1, newValue); + await this.jsonParser.set(key, targetArray); + return true; + } + if (this.isMongoDB()) { + const targetArray = (await this.db.get(key)) || []; + if (!Array.isArray(targetArray)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + targetArray.splice(index, 1, newValue); + await this.db.set(key, targetArray); + return true; + } + if (this.isEnmap()) { + const targetArray = (this.db.get(key) || []); + if (!Array.isArray(targetArray)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + targetArray.splice(index, 1, newValue); + this.db.set(key, targetArray); + return true; + } + return false; + }); + } + /** + * Removes an element from a specified array in the database. + * @param {TKey} key The key in database. + * @param {number} index The index in the target array. + * @returns {Promise>} `true` if popped successfully, `false` otherwise. + */ + async pop(key, index) { + return this._debug('pop', key, async () => { + const targetArray = this._cache.get(key) || []; + if (Array.isArray(targetArray)) { + targetArray.splice(index, 1); + this._cache.set(key, targetArray); + } + if (this.isJSON()) { + const targetArray = await this.jsonParser.get(key) || []; + if (!Array.isArray(targetArray)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + targetArray.splice(index, 1); + await this.jsonParser.set(key, targetArray); + return true; + } + if (this.isMongoDB()) { + const targetArray = (await this.db.get(key)) || []; + if (!Array.isArray(targetArray)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + targetArray.splice(index, 1); + await this.db.set(key, targetArray); + return true; + } + if (this.isEnmap()) { + const targetArray = this.db.get(key) || []; + if (!Array.isArray(targetArray)) { + throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); + } + targetArray.splice(index, 1); + this.db.set(key, targetArray); + return true; + } + return false; + }); + } + /** + * Gets all the data in database. + * + * Type parameters: + * + * - `V` - The type of database object to return. + * + * @returns {V} Database object. + * @template V The type of database object to return + */ + all() { + const data = this._cache.getCacheObject(); + return data; + } + /** + * Gets the whole database object by making a direct database request. + * + * Type parameters: + * + * - `V` - The type of database object to return. + * + * @returns {Promise} Database object. + * @private + * + * @template V The type of database object to return. + */ + async _allFromDatabase() { + if (this.isJSON()) { + const data = await this.jsonParser.fetchDatabaseFile(); + return data; + } + if (this.isMongoDB()) { + const data = await this.db.all(); + return data; + } + if (this.isEnmap()) { + const allData = {}; + for (const databaseKey of this.db.keys()) { + const keys = databaseKey.split('.'); + let currentObject = allData; + for (let i = 0; i < keys.length; i++) { + const currentKey = keys[i]; + if (keys.length - 1 === i) { + currentObject[currentKey] = this.db.get(databaseKey) || null; + } + else { + if (!currentObject[currentKey]) { + currentObject[currentKey] = {}; + } + currentObject = currentObject[currentKey]; + } + } + } + return allData; + } + return {}; + } + /** + * Loads the database into cache. + * @returns {Promise} + */ + async _loadCache() { + const database = await this._allFromDatabase(); + for (const guildID in database) { + const guildDatabase = database[guildID]; + this._cache.set(guildID, guildDatabase); + } + } +} +exports.DatabaseManager = DatabaseManager; diff --git a/dist/src/lib/util/classes/Emitter.js b/dist/src/lib/util/classes/Emitter.js new file mode 100644 index 0000000..403afd1 --- /dev/null +++ b/dist/src/lib/util/classes/Emitter.js @@ -0,0 +1,70 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Emitter = void 0; +const events_1 = require("events"); +/** + * Represents an event emitter for {@link Giveaways} events. + * + * Type parameters: + * + * - `E` ({@link object}) - The object whose **keys** will be used as event names and **values** for events' return types. + * + * @template E The object whose **keys** will be used as event names and **values** for events' return types. + * @private + */ +class Emitter { + _emitter = new events_1.EventEmitter({ + captureRejections: true + }); + /** + * Listens to the event. + * + * Type parameters: + * + * - `T` (keyof E) - Event name to get the callback function type for. + * + * @param {GiveawaysEvents} event Event name. + * @param {Function} listener Callback function. + * @returns {Emitter} Emitter instance. + * + * @template T Event name to get the callback function type for. + */ + on(event, listener) { + this._emitter.on(event, listener); + return this; + } + /** + * Listens to the event only for once. + * + * Type parameters: + * + * - `T` (keyof E) - Event name to get the callback function type for. + * + * @param {IGiveawaysEvents} event Event name. + * @param {Function} listener Callback function. + * @returns {Emitter} Emitter instance. + * + * @template T Event name to get the callback function type for. + */ + once(event, listener) { + this._emitter.once(event, listener); + return this; + } + /** + * Emits the event. + * + * Type parameters: + * + * - `T` (keyof E) - Event name to get the event argument type for. + * + * @param {IGiveawaysEvents} event Event name. + * @param {any} args Arguments to emit the event with. + * @returns {boolean} If event emitted successfully: true, otherwise - false. + * + * @template T Event name to get the event argument type for. + */ + emit(event, ...args) { + return this._emitter.emit(event, args); + } +} +exports.Emitter = Emitter; diff --git a/dist/src/lib/util/classes/GiveawaysError.js b/dist/src/lib/util/classes/GiveawaysError.js new file mode 100644 index 0000000..a67eb6f --- /dev/null +++ b/dist/src/lib/util/classes/GiveawaysError.js @@ -0,0 +1,123 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.errorMessages = exports.GiveawaysErrorCodes = exports.GiveawaysError = void 0; +const databaseType_enum_1 = require("../../../types/databaseType.enum"); +const typeOf_function_1 = require("../functions/typeOf.function"); +/** + * Giveaways error class. + * @extends {Error} + */ +class GiveawaysError extends Error { + /** + * Error code. + * @type {GiveawaysErrorCodes} + */ + code; + /** + * Giveaways error constructor. + * @param {GiveawaysErrorCodes} errorCode Error code to throw. + */ + constructor(error, errorCode) { + const errorMsg = exports.errorMessages; + const isErrorCode = errorMsg[error]; + super(isErrorCode ? errorMsg[error] : error); + /** + * Error name. + * @type {string} + */ + this.name = `GiveawaysError${isErrorCode + ? ` [${error}]` + : errorCode ? ` [${errorCode}]` : ''}`; + /** + * Error code. + * @type {GiveawaysErrorCodes} + */ + this.code = isErrorCode + ? error + : errorCode ? errorCode : GiveawaysErrorCodes.MODULE_ERROR; + } +} +exports.GiveawaysError = GiveawaysError; +/** + * An enum representing the error codes for the Giveaways module. + * @typedef {string} GiveawaysErrorCodes + * @prop {string} UNKNOWN_ERROR An unknown error occurred. + * @prop {string} UNKNOWN_DATABASE The database is unknown or inaccessible. + * @prop {string} NO_DISCORD_CLIENT No Discord client was provided. + * @prop {string} DATABASE_ERROR There was an error with the database. + * @prop {string} MODULE_ERROR There was an error with the Giveaways module. + * @prop {string} INTENT_MISSING The required intent is missing. + * @prop {string} REQUIRED_ARGUMENT_MISSING A required argument is missing. + * @prop {string} REQUIRED_CONFIG_OPTION_MISSING A required configuration option is missing. + * @prop {string} INVALID_TYPE The type is invalid. + * @prop {string} INVALID_TARGET_TYPE The target type is invalid. + * @prop {string} UNKNOWN_GIVEAWAY The giveaway is unknown. + * @prop {string} INVALID_TIME The time is invalid. + * @prop {string} GIVEAWAY_ALREADY_ENDED Giveaway already ended. + * @prop {string} USER_NOT_FOUND User not found. + * @prop {string} INVALID_INPUT Invalid input. + */ +var GiveawaysErrorCodes; +(function (GiveawaysErrorCodes) { + GiveawaysErrorCodes["UNKNOWN_ERROR"] = "UNKNOWN_ERROR"; + GiveawaysErrorCodes["UNKNOWN_DATABASE"] = "UNKNOWN_DATABASE"; + GiveawaysErrorCodes["NO_DISCORD_CLIENT"] = "NO_DISCORD_CLIENT"; + GiveawaysErrorCodes["DATABASE_ERROR"] = "DATABASE_ERROR"; + GiveawaysErrorCodes["MODULE_ERROR"] = "MODULE_ERROR"; + GiveawaysErrorCodes["INTENT_MISSING"] = "INTENT_MISSING"; + GiveawaysErrorCodes["REQUIRED_ARGUMENT_MISSING"] = "REQUIRED_ARGUMENT_MISSING"; + GiveawaysErrorCodes["REQUIRED_CONFIG_OPTION_MISSING"] = "REQUIRED_CONFIG_OPTION_MISSING"; + GiveawaysErrorCodes["INVALID_TYPE"] = "INVALID_TYPE"; + GiveawaysErrorCodes["INVALID_TARGET_TYPE"] = "INVALID_TARGET_TYPE"; + GiveawaysErrorCodes["UNKNOWN_GIVEAWAY"] = "UNKNOWN_GIVEAWAY"; + GiveawaysErrorCodes["INVALID_TIME"] = "INVALID_TIME"; + GiveawaysErrorCodes["GIVEAWAY_ALREADY_ENDED"] = "GIVEAWAY_ALREADY_ENDED"; + GiveawaysErrorCodes["USER_NOT_FOUND"] = "USER_NOT_FOUND"; + GiveawaysErrorCodes["INVALID_INPUT"] = "INVALID_INPUT"; +})(GiveawaysErrorCodes || (exports.GiveawaysErrorCodes = GiveawaysErrorCodes = {})); +exports.errorMessages = { + UNKNOWN_ERROR: 'Unknown error.', + UNKNOWN_DATABASE: 'Unknown database type was specified.', + NO_DISCORD_CLIENT: 'Discord Client should be specified.', + INVALID_TIME: 'Invalid giveaway time was specified.', + DATABASE_ERROR(databaseType, errorType) { + if (databaseType == databaseType_enum_1.DatabaseType.JSON) { + if (errorType == 'malformed') { + return 'Database file is malformed.'; + } + if (errorType == 'notFound') { + return 'Database file path contains unexisting directories.'; + } + } + return `Unknown ${databaseType} error.`; + }, + UNKNOWN_GIVEAWAY(giveawayMessageID) { + return `Unknown giveaway with message ID ${giveawayMessageID}.`; + }, + GIVEAWAY_ALREADY_ENDED(giveawayPrize, giveawayID) { + return `Giveaway "${giveawayPrize}" (ID: ${giveawayID}) has already ended.`; + }, + INTENT_MISSING(missingIntent) { + return `Required intent "${missingIntent}" is missing.`; + }, + INVALID_TYPE(parameter, requiredType, receivedType) { + return `${parameter} must be a ${requiredType}. Received type: ${(0, typeOf_function_1.typeOf)(`${receivedType}`)}.`; + }, + INVALID_TARGET_TYPE(requiredType, receivedType) { + return `Target must be ${requiredType.toLowerCase().startsWith('a') ? 'an' : 'a'} ${requiredType}. ` + + `Received type: ${(0, typeOf_function_1.typeOf)(receivedType)}.`; + }, + // `method` parameter should be specified in format: `{ManagerName}.{methodName}` + REQUIRED_ARGUMENT_MISSING(parameter, method) { + return `${parameter} must be specified in '${method}()' method.`; + }, + INVALID_INPUT(parameter, method, issue) { + return `Invaid value of ${parameter} parameter in ${method} method: ${issue}`; + }, + REQUIRED_CONFIG_OPTION_MISSING(requiredConfigOption) { + return `Required configuration option "${requiredConfigOption}" is missing.`; + }, + USER_NOT_FOUND(userID) { + return `Specified user with ID ${userID} was not found.`; + } +}; diff --git a/dist/src/lib/util/classes/JSONParser.js b/dist/src/lib/util/classes/JSONParser.js new file mode 100644 index 0000000..703c71c --- /dev/null +++ b/dist/src/lib/util/classes/JSONParser.js @@ -0,0 +1,136 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JSONParser = void 0; +const promises_1 = require("fs/promises"); +const typeOf_function_1 = require("../functions/typeOf.function"); +/** + * JSON parser class. + */ +class JSONParser { + /** + * JSON database file path. + * @type {string} + */ + jsonFilePath; + /** + * Minifies the JSON content in database file to save some space. + * @type {boolean} + */ + minifyJSON; + /** + * JSON parser constructor. + * @param {string} jsonFilePath JSON database file path. + * @param {boolean} minifyJSON Minifies the JSON content in database file to save some space. + */ + constructor(jsonFilePath, minifyJSON = false) { + /** + * JSON database file path. + * @type {string} + */ + this.jsonFilePath = jsonFilePath; + /** + * Minifies the JSON content in database file to save some space. + * @type {boolean} + */ + this.minifyJSON = minifyJSON; + } + /** + * Fetches the JSON database object from specified file. + * + * Type parameters: + * + * - `V` - The type of database object to return. + * + * @returns {Promise} JSON database file object. + * + * @template V The type of database object to return. + */ + async fetchDatabaseFile() { + const fileContent = await (0, promises_1.readFile)(this.jsonFilePath, 'utf-8'); + return JSON.parse(fileContent); + } + /** + * Parses the key and fetches the value from JSON database. + * + * Type parameters: + * + * - `V` - The type of data being returned. + * + * @param {string} key The key in JSON database. + * @returns {Promise} The data from JSON database. + * + * @template V The type of data being returned. + */ + async get(key) { + let data = await this.fetchDatabaseFile(); + let parsedData = data; + const keys = key.split('.'); + for (let i = 0; i < keys.length; i++) { + if (keys.length - 1 == i) { + data = parsedData?.[keys[i]] || null; + } + parsedData = parsedData?.[keys[i]]; + } + return data; + } + /** + * Parses the key and sets the value in JSON database. + * + * Type parameters: + * + * - `V` - The type of data being set. + * - `R` - The type of data being returned. + * + * @param {string} key The key in JSON database. + * @returns {Promise} The data from JSON database. + * + * @template V The type of data being set. + * @template R The type of data being returned. + */ + async set(key, value) { + const data = await this.fetchDatabaseFile(); + let updatedData = data; + const keys = key.split('.'); + for (let i = 0; i < keys.length; i++) { + if (keys.length - 1 == i) { + updatedData[keys[i]] = value; + } + else if (!(0, typeOf_function_1.isObject)(data[keys[i]])) { + updatedData[keys[i]] = {}; + } + updatedData = updatedData?.[keys[i]]; + } + await (0, promises_1.writeFile)(this.jsonFilePath, JSON.stringify(data, null, this.minifyJSON ? undefined : '\t')); + return data; + } + /** + * Parses the key and deletes it from JSON database. + * @param {string} key The key in JSON database. + * @returns {Promise} `true` if deleted successfully. + */ + async delete(key) { + const data = await this.fetchDatabaseFile(); + let updatedData = data; + const keys = key.split('.'); + for (let i = 0; i < keys.length; i++) { + if (keys.length - 1 == i) { + delete updatedData[keys[i]]; + } + else if (!(0, typeOf_function_1.isObject)(data[keys[i]])) { + updatedData[keys[i]] = {}; + } + updatedData = updatedData?.[keys[i]]; + } + await (0, promises_1.writeFile)(this.jsonFilePath, JSON.stringify(data, null, this.minifyJSON ? undefined : '\t')); + return true; + } + /** + * Clears the database. + * @returns {Promise} `true` if cleared successfully. + */ + async clearDatabase() { + await (0, promises_1.writeFile)(this.jsonFilePath, '{}'); + return true; + } +} +exports.JSONParser = JSONParser; diff --git a/dist/src/lib/util/classes/Logger.js b/dist/src/lib/util/classes/Logger.js new file mode 100644 index 0000000..87ebffa --- /dev/null +++ b/dist/src/lib/util/classes/Logger.js @@ -0,0 +1,121 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Logger = void 0; +const package_json_1 = require("../../../../package.json"); +/** + * Logger class. + */ +class Logger { + /** + * Logger colors. + * @type {ILoggerColors} + */ + colors; + /** + * Debug mode state. + */ + debugMode; + /** + * Logger constructor. + * @param {boolean} debug Determines if debug mode is enabled. + */ + constructor(debug) { + /** + * Logger colors object. + * @type {ILoggerColors} + */ + this.colors = { + black: '\x1b[30m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + lightgray: '\x1b[37m', + default: '\x1b[39m', + darkgray: '\x1b[90m', + lightred: '\x1b[91m', + lightgreen: '\x1b[92m', + lightyellow: '\x1b[93m', + lightblue: '\x1b[94m', + lightmagenta: '\x1b[95m', + lightcyan: '\x1b[96m', + white: '\x1b[97m', + reset: '\x1b[0m', + }; + this.debugMode = debug; + } + /** + * Sends an info message to the console. + * @param {string} message A message to send. + * @param {string} [color='red'] Message color to use. + * @returns {void} + */ + info(message, color = 'cyan') { + console.log(`${this.colors[color]}[Giveaways] ${message}${this.colors.reset}`); + } + /** + * Sends an error message to the console. + * @param {string} message A message to send. + * @param {string} [color='red'] Message color to use. + * @returns {void} + */ + error(message, color = 'red') { + console.error(`${this.colors[color]}[Giveaways - Error] ${message}${this.colors.reset}`); + } + /** + * Sends a debug message to the console. + * @param {string} message A message to send. + * @param {string} [color='yellow'] Message color to use. + * @returns {void} + */ + debug(message, color = 'yellow') { + if (!this.debugMode) + return; + console.log(`${this.colors[color]}[Giveaways] ${message}${this.colors.reset}`); + } + /** + * Sends a warning message to the console. + * @param {string} message A message to send. + * @param {string} [color='lightyellow'] Message color to use. + * @returns {void} + */ + warn(message, color = 'lightyellow') { + console.log(`${this.colors[color]}[Giveaways - Warning] ${message}${this.colors.reset}`); + } + /** + * Sends a debug log for the optional parameter in method not specified. + * @param {string} method The method (e.g. "ShopItem.use") to set. + * @param {string} parameterName Parameter name to set. + * @param {any} defaultValue Default value to set. + * @returns {void} + */ + optionalParamNotSpecified(method, parameterName, defaultValue) { + this.debug(`${method} - "${parameterName}" optional parameter is not specified - defaulting to "${defaultValue}".`, 'lightcyan'); + } + /** + * Checks if the module version is development version and sends the corresponding warning in the console. + * @returns {void} + */ + sendDevVersionWarning() { + if (package_json_1.version.includes('dev')) { + console.log(); + this.warn('You are using a DEVELOPMENT version of Giveaways, which provides an early access ' + + 'to all the new unfinished features and bug fixes.', 'lightmagenta'); + this.warn('Unlike the stable builds, dev builds DO NOT guarantee that the provided changes are bug-free ' + + 'and won\'t result any degraded performance or crashes. ', 'lightmagenta'); + this.warn('Anything may break at any new commit, so it\'s really important to check ' + + 'the module for new development updates.', 'lightmagenta'); + this.warn('They may include the bug fixes from the ' + + 'old ones and include some new features.', 'lightmagenta'); + this.warn('Use development versions at your own risk.', 'lightmagenta'); + console.log(); + this.warn('Need help? Join the Support Server - https://discord.gg/4pWKq8vUnb.', 'lightmagenta'); + this.warn('Please provide the full version of Giveaways you have installed ' + + '(check in your package.json) when asking for support.', 'lightmagenta'); + console.log(); + } + } +} +exports.Logger = Logger; diff --git a/dist/src/lib/util/classes/MessageUtils.js b/dist/src/lib/util/classes/MessageUtils.js new file mode 100644 index 0000000..003cc8c --- /dev/null +++ b/dist/src/lib/util/classes/MessageUtils.js @@ -0,0 +1,229 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MessageUtils = void 0; +const discord_js_1 = require("discord.js"); +const giveawayTemplate_1 = require("../../../structures/giveawayTemplate"); +/** + * Message utils class. + */ +class MessageUtils { + /** + * Discord Client. + * @type {Client} + */ + _client; + /** + * {@link Giveaways} instance. + * @type {Giveaways} + */ + _giveaways; + /** + * Message utils class constructor. + * @param {Giveaways} giveaways {@link Giveaways} instance. + */ + constructor(giveaways) { + /** + * Discord Client. + * @type {Client} + */ + this._giveaways = giveaways; + /** + * {@link Giveaways} instance. + * @type {Giveaways} + */ + this._client = giveaways.client; + } + /** + * Creates a new message embed based on giveaway and specified embed strings. + * @param {IGiveaway} giveaway Raw giveaway object to get the values from. + * @param {IGiveawayEmbedOptions} newEmbedStrings String values object to use in the embed. + * @param {string[]} winners Array of winners to replace the {winners} statements with. + * @returns {EmbedBuilder} Generated message embed. + */ + buildGiveawayEmbed(giveaway, newEmbedStrings, winners) { + const embedStrings = newEmbedStrings + ? { ...newEmbedStrings } + : { ...giveaway.messageProps?.embeds?.start || {} }; + for (const stringKey in embedStrings) { + const strings = embedStrings; + strings[stringKey] = (0, giveawayTemplate_1.replaceGiveawayKeys)(strings[stringKey], giveaway, winners); + } + if (embedStrings.timestamp) { + embedStrings.timestamp = parseInt(embedStrings.timestamp); + } + const { title, titleIcon, color, titleURL, description, footer, footerIcon, imageURL, thumbnailURL, timestamp, } = embedStrings; + const embed = new discord_js_1.EmbedBuilder() + .setAuthor({ + // discord.js is only accepting "null" for empty value + // even though here it's missing in library's typings :/ + name: title || null, + iconURL: titleIcon, + url: titleURL + }) + .setDescription(description || `**${giveaway.prize}** giveaway has started with **${giveaway.entriesCount}** entries! ` + + 'Press the button below to join!') + .setColor(color || '#d694ff') + .setImage(imageURL || null) + .setThumbnail(thumbnailURL || null) + .setFooter({ + // discord.js is only accepting "null" for empty value + // even though here it's missing in library's typings :/ + text: footer || null, + iconURL: footerIcon + }); + if (timestamp) { + const date = new Date(timestamp); + if (date.getFullYear() < 2000) { + embed.setTimestamp(timestamp * 1000); + } + else { + embed.setTimestamp(timestamp); + } + } + return embed; + } + /** + * Creates a buttons row based on the specified "join giveaway" button object. + * @param {IGiveawayButtonOptions} joinGiveawayButton String values object to use in the button. + * @returns {ActionRowBuilder} Generated buttons row. + */ + buildButtonsRow(joinGiveawayButton) { + const buttonsRow = new discord_js_1.ActionRowBuilder() + .addComponents(new discord_js_1.ButtonBuilder({ + customId: 'joinGiveawayButton', + label: joinGiveawayButton?.text || 'Join the giveaway', + emoji: joinGiveawayButton?.emoji || '🎉', + style: joinGiveawayButton?.style || discord_js_1.ButtonStyle.Primary + })); + return buttonsRow; + } + /** + * Creates a buttons row based on the specified "reroll" and "go to message" button objects. + * @param {IGiveawayButtonOptions} rerollButton String values object to use in the "reroll" button. + * @param {ILinkButton} [goToMessageButton] String values object to use in the "go to message" button. + * @param {string} [giveawayMessageURL] Giveaway message URL to be set in the "go to message" button. + * @returns {ActionRowBuilder} Generated buttons row. + */ + buildGiveawayFinishedButtonsRow(rerollButton, goToMessageButton, giveawayMessageURL) { + const buttonsRow = new discord_js_1.ActionRowBuilder(); + goToMessageButton ? buttonsRow.addComponents(new discord_js_1.ButtonBuilder({ + customId: 'rerollButton', + label: rerollButton?.text || 'Reroll', + emoji: rerollButton?.emoji || '🔁', + style: rerollButton?.style || discord_js_1.ButtonStyle.Primary + }), new discord_js_1.ButtonBuilder({ + label: goToMessageButton?.text || 'Go to Message', + emoji: goToMessageButton?.emoji || '↗️', + style: discord_js_1.ButtonStyle.Link, + url: giveawayMessageURL + })) : buttonsRow.addComponents(new discord_js_1.ButtonBuilder({ + customId: 'rerollButton', + label: rerollButton?.text || 'Reroll', + emoji: rerollButton?.emoji || '🔁', + style: rerollButton?.style || discord_js_1.ButtonStyle.Primary + })); + return buttonsRow; + } + /** + * Creates a buttons row based on the specified "reroll" and "go to message" button objects. + * @param {IGiveawayButtonOptions} rerollButton String values object to use in the "reroll" button. + * @returns {ActionRowBuilder} Generated buttons row. + */ + buildGiveawayRerollButtonRow(rerollButton) { + const buttonsRow = new discord_js_1.ActionRowBuilder() + .addComponents(new discord_js_1.ButtonBuilder({ + customId: 'rerollButton', + label: rerollButton?.text || 'Reroll', + emoji: rerollButton?.emoji || '🔁', + style: rerollButton?.style || discord_js_1.ButtonStyle.Primary + })); + return buttonsRow; + } + /** + * Creates a buttons row based on the specified "go to message" button objects. + * @param {ILinkButton} goToMessageButton String values object to use in the "go to message" link button. + * @param {string} giveawayMessageURL Giveaway message URL to be set in the "go to message" button. + * @returns {ActionRowBuilder} Generated buttons row. + */ + buildGiveawayFinishedButtonsRowWithoutRerollButton(goToMessageButton, giveawayMessageURL) { + const buttonsRow = new discord_js_1.ActionRowBuilder() + .addComponents(new discord_js_1.ButtonBuilder({ + label: goToMessageButton?.text || 'Go to Message', + emoji: goToMessageButton?.emoji || '↗️', + style: discord_js_1.ButtonStyle.Link, + url: giveawayMessageURL + })); + return buttonsRow; + } + /** + * Edits the giveaway message on giveaway entry. + * @param {IGiveaway} giveaway Raw giveaway object. + * @returns {Promise} + */ + async editEntryGiveawayMessage(giveaway) { + const embedStrings = giveaway.messageProps?.embeds?.start || {}; + const channel = this._client.channels.cache.get(giveaway.channelID); + const embed = this.buildGiveawayEmbed(giveaway); + const buttonsRow = this.buildButtonsRow(giveaway.messageProps?.buttons.joinGiveawayButton); + const message = await channel.messages.fetch(giveaway.messageID); + await message.edit({ + content: embedStrings?.messageContent, + embeds: Object.keys(embedStrings).length == 1 && + embedStrings?.messageContent + ? [] : [embed], + components: [buttonsRow] + }); + } + /** + * Edits the giveaway message on giveaway finish. + * @param {IGiveaway} giveaway Raw giveaway object. + * @param {string[]} winners Array of giveaway winners. + * @param {IGiveawayEmbedOptions} customEmbedStrings Embed options to use instead of `finish` embed. + * @param {boolean} sendWinnersMessage Determines if the separated winners message should be sent. + * @returns {Promise} + */ + async editFinishGiveawayMessage(giveaway, winners, customEmbedStrings, sendWinnersMessage = true, endEmbedStrings) { + const embedStrings = giveaway.messageProps?.embeds?.finish; + const finishEmbedStrings = customEmbedStrings || embedStrings?.newGiveawayMessage || {}; + const noWinnersEmbedStrings = giveaway.messageProps?.embeds?.finish + ?.noWinnersNewGiveawayMessage || {}; + const channel = this._client.channels.cache.get(giveaway.channelID); + const finishDefaultedEmbedStrings = { + ...finishEmbedStrings, + color: finishEmbedStrings?.color || '#d694ff', + description: finishEmbedStrings?.description || 'Giveaway is over!' + }; + const noWinnersDefaultedEmbedStrings = { + ...finishEmbedStrings, + color: noWinnersEmbedStrings?.color || '#d694ff', + description: noWinnersEmbedStrings?.description || 'There are no winners in this giveaway!' + }; + const winnersCondition = winners.length >= this._giveaways.options.minGiveawayEntries; + const defaultedEmbedStrings = winnersCondition ? finishDefaultedEmbedStrings : noWinnersDefaultedEmbedStrings; + const finishEmbed = this.buildGiveawayEmbed(giveaway, defaultedEmbedStrings, winners); + const giveawayEndEmbed = this.buildGiveawayEmbed(giveaway, winnersCondition ? (embedStrings?.endMessage || embedStrings?.newGiveawayMessage || {}) : embedStrings?.noWinnersEndMessage, winners); + const goToMessageButtonRow = this.buildGiveawayFinishedButtonsRowWithoutRerollButton(giveaway.messageProps?.buttons.goToMessageButton, giveaway.messageURL); + const rerollButtonRow = this.buildGiveawayRerollButtonRow(giveaway.messageProps?.buttons.rerollButton); + const message = await channel.messages.fetch(giveaway.messageID); + const giveawayMessageContent = defaultedEmbedStrings.messageContent; + const finishMessageContent = (0, giveawayTemplate_1.replaceGiveawayKeys)(winnersCondition + ? endEmbedStrings?.messageContent || embedStrings?.endMessage.messageContent + : embedStrings?.noWinnersEndMessage?.messageContent, giveaway, winners); + const finishInputObjectKeys = winnersCondition + ? Object.keys(embedStrings?.endMessage || {}) + : Object.keys(embedStrings?.noWinnersEndMessage || {}); + await message.edit({ + content: giveawayMessageContent, + embeds: Object.keys(defaultedEmbedStrings).length == 1 && giveawayMessageContent ? [] : [finishEmbed], + components: winnersCondition ? [rerollButtonRow] : [] + }); + if (sendWinnersMessage) { + await message.reply({ + content: finishMessageContent, + embeds: finishInputObjectKeys.length == 1 && finishMessageContent ? [] : [giveawayEndEmbed], + components: [goToMessageButtonRow] + }); + } + } +} +exports.MessageUtils = MessageUtils; diff --git a/dist/src/lib/util/classes/TypedObject.js b/dist/src/lib/util/classes/TypedObject.js new file mode 100644 index 0000000..26e3eb7 --- /dev/null +++ b/dist/src/lib/util/classes/TypedObject.js @@ -0,0 +1,56 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TypedObject = void 0; +/** + * Utility class for working with objects. + * + * Provides **static** methods for retrieving keys and values of an object. + * + * This class enhances type safety by providing better typings for object keys and values. + */ +class TypedObject { + /** + * Returns the names of the enumerable string properties and methods of an object. + * + * Type parameters: + * + * - `TObject` (`Record`) - The object to get the object keys types from. + * + * @param {any} obj Object that contains the properties and methods. + * + * @returns {Array>} + * Array of names of the enumerable string properties and methods of the specified object. + */ + static keys(obj) { + return Object.keys(obj || {}); + } + /** + * Returns an array of values of the enumerable properties of an object. + * + * Type parameters: + * + * - `TObject` (`Record`) - The object to get the object values types from. + * + * @param {any} obj Object that contains the properties and methods. + * + * @returns {Array>} + * Array of values of the enumerable properties of the specified object. + */ + static values(obj) { + return Object.values(obj || {}); + } + /** + * Returns an array of key-value pairs of the enumerable properties of an object. + * + * Type parameters: + * + * - `TObject` (`Record`) - The object to get the object key-value pairs types from. + * + * @param {any} obj Object that contains the properties and methods. + * @returns {Array} Array of key-value pairs of the enumerable properties of the specified object. + */ + static entries(obj) { + return Object.entries(obj || {}); + } +} +exports.TypedObject = TypedObject; diff --git a/dist/src/lib/util/functions/checkConfiguration.function.js b/dist/src/lib/util/functions/checkConfiguration.function.js new file mode 100644 index 0000000..3fb9b96 --- /dev/null +++ b/dist/src/lib/util/functions/checkConfiguration.function.js @@ -0,0 +1,102 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.checkConfiguration = void 0; +const TypedObject_1 = require("../classes/TypedObject"); +const defaultConfig_1 = require("../../../structures/defaultConfig"); +/** + * Completes, fills and fixes the {@link Giveaways} configuration. + * + * Type parameters: + * + * - `TDatabaseType` ({@link DatabaseType}) - The database type that will + * determine which connection configuration should be used. + * + * @callback checkConfiguration + * + * @param {IGiveawaysConfiguration} configurationToCheck The {@link Giveaways} configuration object to check. + * @param {Partial} [checkerConfiguration] Config checker configuration object. + * + * @returns {Required>} Completed, filled and fixed {@link Giveaways} configuration. + * + * @template TDatabaseType + * The database type that will determine which connection configuration should be used. + */ +const checkConfiguration = (configurationToCheck, checkerConfiguration = {}) => { + const problems = []; + const defaultConfiguration = defaultConfig_1.defaultConfig; + const output = { + ...configurationToCheck, + ...defaultConfiguration + }; + if (!checkerConfiguration.ignoreUnspecifiedOptions) { + checkerConfiguration.ignoreUnspecifiedOptions = true; + } + if (!checkerConfiguration.sendLog) { + checkerConfiguration.sendLog = true; + } + if (!checkerConfiguration.showProblems) { + checkerConfiguration.showProblems = true; + } + for (const key of TypedObject_1.TypedObject.keys(configurationToCheck)) { + const config = configurationToCheck; + const defaultValue = defaultConfiguration[key]; + const value = config[key]; + if (key !== 'database' && key !== 'connection') { + if (value == undefined) { + output[key] = defaultValue; + if (!checkerConfiguration.ignoreUnspecifiedOptions) { + problems.push(`options.${key} is not specified.`); + } + } + else if (typeof value !== typeof defaultValue) { + if (!checkerConfiguration.ignoreInvalidTypes) { + problems.push(`options.${key} is not a ${typeof defaultValue}. Received type: ${typeof value}.`); + output[key] = defaultValue; + } + } + else { + output[key] = value; + } + } + } + const checkNestedOptionsObjects = (config, defaultConfig, prefix) => { + for (const key in defaultConfig) { + const defaultValue = defaultConfig[key]; + const value = config[key]; + const fullKey = prefix ? `${prefix}.${key}` : key; + if (value == undefined) { + if (config[key] == undefined) { + config[key] = defaultValue; + } + if (!checkerConfiguration.ignoreUnspecifiedOptions) { + problems.push(`${fullKey} is not specified.`); + } + } + else if (typeof value !== typeof defaultValue) { + if (!checkerConfiguration.ignoreInvalidTypes && (key !== 'database' && key !== 'connection')) { + problems.push(`${fullKey} is not a ${typeof defaultValue}. Received type: ${typeof value}.`); + config[key] = defaultValue; + } + } + else if (typeof value == 'object' && value !== null) { + checkNestedOptionsObjects(value, defaultValue, fullKey); + } + } + }; + checkNestedOptionsObjects(configurationToCheck, defaultConfig_1.defaultConfig, ''); + if (checkerConfiguration.sendLog) { + const problemsCount = problems.length; + if (checkerConfiguration.showProblems) { + if (checkerConfiguration.sendSuccessLog || problemsCount) { + console.log(`Checked the configuration: ${problemsCount} ${problemsCount == 1 ? 'problem' : 'problems'} found.`); + } + if (problemsCount) { + console.log(problems.join('\n')); + } + } + } + output.database = configurationToCheck.database; + output.connection = configurationToCheck.connection; + return output; +}; +exports.checkConfiguration = checkConfiguration; diff --git a/dist/src/lib/util/functions/checkUpdates.function.js b/dist/src/lib/util/functions/checkUpdates.function.js new file mode 100644 index 0000000..fa17d03 --- /dev/null +++ b/dist/src/lib/util/functions/checkUpdates.function.js @@ -0,0 +1,29 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.checkUpdates = void 0; +const node_fetch_1 = __importDefault(require("node-fetch")); +const package_json_1 = require("../../../../package.json"); +/** + * Checks the latest available module version and compares it with installed one. + * @returns {Promise} Update checking results + */ +const checkUpdates = async () => { + const packageData = await (0, node_fetch_1.default)(`https://registry.npmjs.com/${package_json_1.name}`) + .then(text => text.json()); + const latestVersion = packageData['dist-tags']?.latest || '1.0.0'; + if (package_json_1.version == latestVersion) + return { + updated: true, + installedVersion: package_json_1.version, + availableVersion: latestVersion + }; + return { + updated: false, + installedVersion: package_json_1.version, + availableVersion: latestVersion + }; +}; +exports.checkUpdates = checkUpdates; diff --git a/dist/src/lib/util/functions/time.function.js b/dist/src/lib/util/functions/time.function.js new file mode 100644 index 0000000..a715dbd --- /dev/null +++ b/dist/src/lib/util/functions/time.function.js @@ -0,0 +1,44 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.convertTimeToMilliseconds = exports.isTimeStringValid = exports.timeRegex = void 0; +exports.timeRegex = /^(\d+d)?\s?(((\d+h)\s?(\d+m))?|\d+h|\d+m)\s?(\d+s)?$/; +/** + * Checks if specified time string is valid. + * @param {string} timeString The time string to check. + * @returns {boolean} Whether the specified time string is valid. + */ +const isTimeStringValid = (timeString) => { + return exports.timeRegex.test(timeString) && timeString !== ''; +}; +exports.isTimeStringValid = isTimeStringValid; +/** + * Converts a time string to milliseconds. + * @param {string} timeString The time string to convert. + * + * @returns {Maybe} + * The total number of milliseconds in the time string, or `null` if the time string is invalid or empty. + */ +const convertTimeToMilliseconds = (timeString) => { + if (!(0, exports.isTimeStringValid)(timeString)) { + return null; + } + const days = timeString.match(/\d+d/); + const hours = timeString.match(/\d+h/); + const minutes = timeString.match(/\d+m/); + const seconds = timeString.match(/\d+s/); + let totalMilliseconds = 0; + if (days) { + totalMilliseconds += parseInt(days[0].replace('d', '')) * 24 * 60 * 60 * 1000; + } + if (hours) { + totalMilliseconds += parseInt(hours[0].replace('h', '')) * 60 * 60 * 1000; + } + if (minutes) { + totalMilliseconds += parseInt(minutes[0].replace('m', '')) * 60 * 1000; + } + if (seconds) { + totalMilliseconds += parseInt(seconds[0].replace('s', '')) * 1000; + } + return totalMilliseconds; +}; +exports.convertTimeToMilliseconds = convertTimeToMilliseconds; diff --git a/dist/src/lib/util/functions/typeOf.function.js b/dist/src/lib/util/functions/typeOf.function.js new file mode 100644 index 0000000..a4c18bf --- /dev/null +++ b/dist/src/lib/util/functions/typeOf.function.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isObject = exports.typeOf = void 0; +/** + * Returns the exact type of the specified input. Utilility function. + * @param {any} input The input to check. + * @returns {string} Input exact type. + */ +const typeOf = (input) => { + if ((typeof input == 'object' || typeof input == 'function') && input?.prototype) { + return input.name; + } + if (input == null || input == undefined || (typeof input == 'number' && isNaN(input))) { + return `${input}`; + } + return input.constructor.name; +}; +exports.typeOf = typeOf; +/** + * Checks for is the item object and returns it. + * @param {any} item The item to check. + * @returns {boolean} Is the item object or not. +*/ +const isObject = (item) => { + return !Array.isArray(item) + && typeof item == 'object' + && item !== null; +}; +exports.isObject = isObject; diff --git a/dist/src/structures/defaultConfig.js b/dist/src/structures/defaultConfig.js new file mode 100644 index 0000000..d1d50f8 --- /dev/null +++ b/dist/src/structures/defaultConfig.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.defaultConfig = void 0; +exports.defaultConfig = { + giveawaysCheckingInterval: 1000, + minGiveawayEntries: 1, + updatesChecker: { + checkUpdates: true, + upToDateMessage: false + }, + configurationChecker: { + ignoreInvalidTypes: false, + ignoreUnspecifiedOptions: true, + ignoreInvalidOptions: false, + showProblems: true, + sendLog: true, + sendSuccessLog: false + }, + debug: false +}; diff --git a/dist/src/structures/giveawayTemplate.js b/dist/src/structures/giveawayTemplate.js new file mode 100644 index 0000000..c27d979 --- /dev/null +++ b/dist/src/structures/giveawayTemplate.js @@ -0,0 +1,33 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.replaceGiveawayKeys = exports.giveawayTemplate = void 0; +exports.giveawayTemplate = { + id: '{id}', + hostMemberID: '{hostMemberID}', + guildID: '{guildID}', + channelID: '{channelID}', + messageID: '{messageID}', + prize: '{prize}', + startTimestamp: '{startTimestamp}', + endTimestamp: '{endTimestamp}', + endedTimestamp: '{endedTimestamp}', + time: '{time}', + winnersCount: '{winnersCount}', + entriesCount: '{entriesCount}', + messageURL: '{messageURL}', + messageProps: '{messageProps}', + numberOfWinners: '{numberOfWinners}', + winnersString: '{winnersString}', + isEnded: '{isEnded}', + state: '{state}', + entries: '' +}; +function replaceGiveawayKeys(input, giveawayObject, winners = []) { + for (const key in exports.giveawayTemplate) { + input = input?.replaceAll(`{${key}}`, key == 'numberOfWinners' + ? winners?.length + : key == 'winnersString' ? winners?.join(', ') : giveawayObject[key]); + } + return input; +} +exports.replaceGiveawayKeys = replaceGiveawayKeys; diff --git a/dist/src/types/configurations.js b/dist/src/types/configurations.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/src/types/configurations.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/databaseStructure.interface.js b/dist/src/types/databaseStructure.interface.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/src/types/databaseStructure.interface.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/databaseType.enum.js b/dist/src/types/databaseType.enum.js new file mode 100644 index 0000000..5707a82 --- /dev/null +++ b/dist/src/types/databaseType.enum.js @@ -0,0 +1,16 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DatabaseType = void 0; +/** + * An enum containing the possible database types. + * @typedef {string} DatabaseType + * @prop {string} JSON - The JSON database type. + * @prop {string} MONGODB - The MongoDB database type. + * @prop {string} ENMAP - The Enmap database type. + */ +var DatabaseType; +(function (DatabaseType) { + DatabaseType["JSON"] = "JSON"; + DatabaseType["MONGODB"] = "MongoDB"; + DatabaseType["ENMAP"] = "Enmap"; +})(DatabaseType || (exports.DatabaseType = DatabaseType = {})); diff --git a/dist/src/types/debug.interface.js b/dist/src/types/debug.interface.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/src/types/debug.interface.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/giveawaysEvents.interface.js b/dist/src/types/giveawaysEvents.interface.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/src/types/giveawaysEvents.interface.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/misc/colors.interface.js b/dist/src/types/misc/colors.interface.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/src/types/misc/colors.interface.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/misc/utils.js b/dist/src/types/misc/utils.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/dist/src/types/misc/utils.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/src/lib/Giveaway.ts b/src/lib/Giveaway.ts index e9b40b9..96a90e9 100644 --- a/src/lib/Giveaway.ts +++ b/src/lib/Giveaway.ts @@ -677,7 +677,7 @@ export class Giveaway< giveawayMessage.reply({ content: rerollMessage?.messageContent, - embeds: Object.keys(rerollMessage).length && rerollMessage?.messageContent ? [] : [rerolledEmbed] + embeds: Object.keys(rerollMessage).length === 1 && rerollMessage?.messageContent ? [] : [rerolledEmbed] }) this._giveaways.emit('giveawayReroll', { diff --git a/src/lib/util/classes/MessageUtils.ts b/src/lib/util/classes/MessageUtils.ts index 9e3f8f5..71ba731 100644 --- a/src/lib/util/classes/MessageUtils.ts +++ b/src/lib/util/classes/MessageUtils.ts @@ -268,7 +268,6 @@ export class MessageUtils { const finishDefaultedEmbedStrings: Partial = { ...finishEmbedStrings, - color: finishEmbedStrings?.color || '#d694ff', description: finishEmbedStrings?.description || 'Giveaway is over!' } @@ -287,7 +286,7 @@ export class MessageUtils { const giveawayEndEmbed = this.buildGiveawayEmbed( giveaway, - winnersCondition ? finishDefaultedEmbedStrings : embedStrings?.noWinnersEndMessage, + winnersCondition ? (embedStrings?.endMessage || embedStrings?.newGiveawayMessage || {}) : embedStrings?.noWinnersEndMessage, winners ) From ed5fd8ded2708448dc5b1a8484635242324a58a5 Mon Sep 17 00:00:00 2001 From: Asayukiii Date: Sun, 22 Sep 2024 05:19:59 +0000 Subject: [PATCH 2/5] chore: rename package --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index e29131d..a90d01c 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "discord-giveaways-super", - "version": "1.1.0", + "name": "@asayukiii/discord-giveaways-super", + "version": "1.1.1", "description": "Create and manage giveaways in Discord.", "types": "./types/src/index.d.ts", - "main": "dist/src/index.js", + "main": "./dist/src/index.js", "scripts": { "prep": "npx husky install && npm run commitlint:install", "lint": "eslint ./src", From 67b2569a4490e4f536ff30e84745a875c4bb1ec3 Mon Sep 17 00:00:00 2001 From: Asayukiii Date: Mon, 7 Oct 2024 19:22:18 +0000 Subject: [PATCH 3/5] Revert "chore: rename package" This reverts commit ed5fd8ded2708448dc5b1a8484635242324a58a5. --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a90d01c..e29131d 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "@asayukiii/discord-giveaways-super", - "version": "1.1.1", + "name": "discord-giveaways-super", + "version": "1.1.0", "description": "Create and manage giveaways in Discord.", "types": "./types/src/index.d.ts", - "main": "./dist/src/index.js", + "main": "dist/src/index.js", "scripts": { "prep": "npx husky install && npm run commitlint:install", "lint": "eslint ./src", From 0965c9b04b13161bf67cf01a84631d7042095fd0 Mon Sep 17 00:00:00 2001 From: Asayukiii Date: Mon, 7 Oct 2024 19:23:46 +0000 Subject: [PATCH 4/5] chore: enable dist to be ignored by git --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ae323f9..1d32c14 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ node_modules/ .vscode/ # build files -# dist/ +dist/ # compiled types *.d.ts From b92d396230ad3211413fe357feda285aea8a8f1c Mon Sep 17 00:00:00 2001 From: Asayukiii Date: Mon, 7 Oct 2024 19:24:38 +0000 Subject: [PATCH 5/5] remove dist folder --- dist/package.json | 89 - dist/src/Giveaways.js | 1459 ----------------- dist/src/index.js | 38 - dist/src/lib/Giveaway.js | 1034 ------------ dist/src/lib/giveaway.interface.js | 14 - dist/src/lib/managers/CacheManager.js | 135 -- dist/src/lib/managers/DatabaseManager.js | 638 ------- dist/src/lib/util/classes/Emitter.js | 70 - dist/src/lib/util/classes/GiveawaysError.js | 123 -- dist/src/lib/util/classes/JSONParser.js | 136 -- dist/src/lib/util/classes/Logger.js | 121 -- dist/src/lib/util/classes/MessageUtils.js | 229 --- dist/src/lib/util/classes/TypedObject.js | 56 - .../functions/checkConfiguration.function.js | 102 -- .../util/functions/checkUpdates.function.js | 29 - dist/src/lib/util/functions/time.function.js | 44 - .../src/lib/util/functions/typeOf.function.js | 29 - dist/src/structures/defaultConfig.js | 20 - dist/src/structures/giveawayTemplate.js | 33 - dist/src/types/configurations.js | 2 - dist/src/types/databaseStructure.interface.js | 2 - dist/src/types/databaseType.enum.js | 16 - dist/src/types/debug.interface.js | 2 - dist/src/types/giveawaysEvents.interface.js | 2 - dist/src/types/misc/colors.interface.js | 2 - dist/src/types/misc/utils.js | 2 - 26 files changed, 4427 deletions(-) delete mode 100644 dist/package.json delete mode 100644 dist/src/Giveaways.js delete mode 100644 dist/src/index.js delete mode 100644 dist/src/lib/Giveaway.js delete mode 100644 dist/src/lib/giveaway.interface.js delete mode 100644 dist/src/lib/managers/CacheManager.js delete mode 100644 dist/src/lib/managers/DatabaseManager.js delete mode 100644 dist/src/lib/util/classes/Emitter.js delete mode 100644 dist/src/lib/util/classes/GiveawaysError.js delete mode 100644 dist/src/lib/util/classes/JSONParser.js delete mode 100644 dist/src/lib/util/classes/Logger.js delete mode 100644 dist/src/lib/util/classes/MessageUtils.js delete mode 100644 dist/src/lib/util/classes/TypedObject.js delete mode 100644 dist/src/lib/util/functions/checkConfiguration.function.js delete mode 100644 dist/src/lib/util/functions/checkUpdates.function.js delete mode 100644 dist/src/lib/util/functions/time.function.js delete mode 100644 dist/src/lib/util/functions/typeOf.function.js delete mode 100644 dist/src/structures/defaultConfig.js delete mode 100644 dist/src/structures/giveawayTemplate.js delete mode 100644 dist/src/types/configurations.js delete mode 100644 dist/src/types/databaseStructure.interface.js delete mode 100644 dist/src/types/databaseType.enum.js delete mode 100644 dist/src/types/debug.interface.js delete mode 100644 dist/src/types/giveawaysEvents.interface.js delete mode 100644 dist/src/types/misc/colors.interface.js delete mode 100644 dist/src/types/misc/utils.js diff --git a/dist/package.json b/dist/package.json deleted file mode 100644 index 521a906..0000000 --- a/dist/package.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "name": "discord-giveaways-super", - "version": "1.1.0", - "description": "Create and manage giveaways in Discord.", - "types": "./types/src/index.d.ts", - "main": "dist/src/index.js", - "scripts": { - "prep": "npx husky install && npm run commitlint:install", - "lint": "eslint ./src", - "lint:fix": "eslint ./src --fix", - "build": "tsc -p tsconfig.json", - "docs:generate": "bash ./scripts/docgen.sh", - "commitlint:install": "npm i -g @commitlint/cli @commitlint/config-conventional -f" - }, - "keywords": [ - "discord", - "bot", - "bots", - "discord-bot", - "discord bot", - "discord-bots", - "discord bots", - "giveaway", - "giveaways", - "giveaway-bot", - "giveaways-bot", - "giveaways-bots", - "discord-giveaway-bot", - "discord-giveaway-bots", - "easy-giveaway", - "easy-giveaways", - "giveaway-bots", - "discord-giveaways", - "discord-giveaways-super", - "fast", - "easy", - "easy-to-use", - "json", - "mongodb", - "enmap", - "djs", - "discord.js", - "discord.js-v14", - "discord.js-v15", - "djs-v14", - "djs-v15", - "v14", - "v15", - "shadowplay" - ], - "author": "shadowplay1 ", - "license": "MIT", - "dependencies": { - "discord.js": "^14.15.3", - "enmap": "^5.9.8", - "quick-mongo-super": "^1.0.19" - }, - "devDependencies": { - "@babel/core": "^7.22.5", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.7", - "@babel/preset-env": "^7.22.5", - "@babel/preset-typescript": "^7.22.5", - "@distube/docgen": "github:distubejs/docgen", - "@types/node": "^20.14.3", - "@types/node-fetch": "^2.6.4", - "@typescript-eslint/eslint-plugin": "^6.0.0-alpha.163", - "@typescript-eslint/parser": "^6.0.0-alpha.163", - "eslint": "^8.43.0", - "jsdoc-babel": "^0.5.0" - }, - "quick-mongo-super": { - "postinstall": false - }, - "engines": { - "node": ">=16.9.0" - }, - "directories": { - "lib": "./src" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/shadowplay1/discord-giveaways-super.git" - }, - "bugs": { - "url": "https://github.com/shadowplay1/discord-giveaways-super/issues" - }, - "homepage": "https://dgs-docs.js.org" -} diff --git a/dist/src/Giveaways.js b/dist/src/Giveaways.js deleted file mode 100644 index 1609fcc..0000000 --- a/dist/src/Giveaways.js +++ /dev/null @@ -1,1459 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Giveaways = void 0; -const fs_1 = require("fs"); -const promises_1 = require("fs/promises"); -const quick_mongo_super_1 = __importDefault(require("quick-mongo-super")); -const enmap_1 = __importDefault(require("enmap")); -const discord_js_1 = require("discord.js"); -const databaseType_enum_1 = require("./types/databaseType.enum"); -const checkUpdates_function_1 = require("./lib/util/functions/checkUpdates.function"); -const package_json_1 = require("../package.json"); -const GiveawaysError_1 = require("./lib/util/classes/GiveawaysError"); -const Logger_1 = require("./lib/util/classes/Logger"); -const Emitter_1 = require("./lib/util/classes/Emitter"); -const DatabaseManager_1 = require("./lib/managers/DatabaseManager"); -const checkConfiguration_function_1 = require("./lib/util/functions/checkConfiguration.function"); -const Giveaway_1 = require("./lib/Giveaway"); -const giveaway_interface_1 = require("./lib/giveaway.interface"); -const giveawayTemplate_1 = require("./structures/giveawayTemplate"); -const MessageUtils_1 = require("./lib/util/classes/MessageUtils"); -const time_function_1 = require("./lib/util/functions/time.function"); -const TypedObject_1 = require("./lib/util/classes/TypedObject"); -/** - * Main Giveaways class. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. - * - * - `TDatabaseKey` ({@link string}, optional: defaults to `${string}.giveaways`) - - * The type of database key that will be used in database operations. - * - * - `TDatabaseValue` ({@link any}, optional: defaults to {@link IDatabaseStructure}) - - * The type of database content that will be used in database operations. - * - * @extends {Emitter>} - * - * @template TDatabaseType The database type that is used. - * @template TDatabaseKey The type of database key that will be used in database operations. - * @template TDatabaseValue The type of database content that will be used in database operations. - */ -class Giveaways extends Emitter_1.Emitter { - /** - * Discord Client. - * @type {Client} - */ - client; - /** - * {@link Giveaways} ready state. - * @type {boolean} - */ - ready; - /** - * {@link Giveaways} version. - * @type {string} - */ - version; - /** - * Completed, filled and fixed {@link Giveaways} configuration. - * @type {Required>} - */ - options; - /** - * External database instanca (such as Enmap or MongoDB) if used. - * @type {?Database} - */ - db; - /** - * Database Manager. - * @type {DatabaseManager} - */ - database; - /** - * Giveaways logger. - * @type {Logger} - * @private - */ - _logger; - /** - * Message generation utility methods. - * @type {MessageUtils} - * @private - */ - _messageUtils; - /** - * Giveaways ending state checking interval. - * @type {NodeJS.Timeout} - */ - giveawaysCheckingInterval; - /** - * Main {@link Giveaways} constructor. - * @param {Client} client Discord client. - * @param {IGiveawaysConfiguration} options {@link Giveaways} configuration. - */ - constructor(client, options) { - super(); - /** - * Discord Client. - * @type {Client} - */ - this.client = client; - /** - * {@link Giveaways} ready state. - * @type {boolean} - */ - this.ready = false; - /** - * {@link Giveaways} version. - * @type {string} - */ - this.version = package_json_1.version; - /** - * {@link Giveaways} logger. - * @type {Logger} - * @private - */ - this._logger = new Logger_1.Logger(options.debug || false); - this._logger.debug('Giveaways version: ' + this.version, 'lightcyan'); - this._logger.debug(`Database type is ${options.database}.`, 'lightcyan'); - this._logger.debug('Debug mode is enabled.', 'lightcyan'); - this._logger.sendDevVersionWarning(); - this._logger.debug('Checking the configuration...'); - /** - * Completed, filled and fixed {@link Giveaways} configuration. - * @type {Required>} - */ - this.options = (0, checkConfiguration_function_1.checkConfiguration)(options, options.configurationChecker); - /** - * External database instance (such as Enmap or MongoDB) if used. - * @type {?Database} - */ - this.db = null; // specifying 'null' to just initialize the property; for docs purposes - /** - * Database Manager. - * @type {DatabaseManager} - */ - this.database = null; // specifying 'null' to just initialize the property; for docs purposes - /** - * {@link Giveaways} ending state checking interval. - * @type {NodeJS.Timeout} - */ - this.giveawaysCheckingInterval = null; // specifying 'null' to just initialize the property; for docs purposes - /** - * Message utils instance. - * @type {MessageUtils} - * @private - */ - this._messageUtils = new MessageUtils_1.MessageUtils(this); - this._init(); - } - /** - * Initialize the database connection and initialize the {@link Giveaways} module. - * @returns {Promise} - * @private - */ - async _init() { - this._logger.debug('Giveaways starting process launched.', 'lightgreen'); - if (!this.client) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.GiveawaysErrorCodes.NO_DISCORD_CLIENT); - } - if (!this.options.database) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_CONFIG_OPTION_MISSING('database'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_CONFIG_OPTION_MISSING); - } - if (!this.options.connection) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_CONFIG_OPTION_MISSING('connection'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_CONFIG_OPTION_MISSING); - } - const isDatabaseCorrect = Object.keys(databaseType_enum_1.DatabaseType) - .map(databaseType => databaseType.toLowerCase()) - .includes(this.options.database.toLowerCase()); - if (!isDatabaseCorrect) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('"database"', 'value from "DatabaseType" enum: either "JSON", "MONGODB" or "Enmap"', typeof this.options.database), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (typeof this.options.connection !== 'object') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('connection', 'object', typeof this.options.connection), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - const requiredIntents = [ - discord_js_1.GatewayIntentBits.Guilds, - discord_js_1.GatewayIntentBits.GuildMembers, - discord_js_1.GatewayIntentBits.GuildMessages, - discord_js_1.GatewayIntentBits.GuildMessageReactions - ]; - const clientIntents = new discord_js_1.IntentsBitField(this.client.options.intents); - for (const requiredIntent of requiredIntents) { - if (!clientIntents.has(requiredIntent)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INTENT_MISSING(discord_js_1.GatewayIntentBits[requiredIntent]), GiveawaysError_1.GiveawaysErrorCodes.INTENT_MISSING); - } - } - switch (this.options.database) { - case databaseType_enum_1.DatabaseType.JSON: { - this._logger.debug('Checking the database file...'); - const databaseOptions = this.options.connection; - const isFileExists = (0, fs_1.existsSync)(databaseOptions.path); - if (!isFileExists) { - await (0, promises_1.writeFile)(databaseOptions.path, '{}'); - } - if (databaseOptions.checkDatabase) { - try { - setInterval(async () => { - const isFileExists = (0, fs_1.existsSync)(databaseOptions.path); - if (!isFileExists) { - await (0, promises_1.writeFile)(databaseOptions.path, '{}'); - } - const databaseFile = await (0, promises_1.readFile)(databaseOptions.path, 'utf-8'); - JSON.parse(databaseFile); - }, databaseOptions.checkingInterval); - } - catch (err) { - if (err.message.includes('Unexpected token') || err.message.includes('Unexpected end')) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.DATABASE_ERROR(databaseType_enum_1.DatabaseType.JSON, 'malformed'), GiveawaysError_1.GiveawaysErrorCodes.DATABASE_ERROR); - } - if (err.message.includes('no such file')) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.DATABASE_ERROR(databaseType_enum_1.DatabaseType.JSON, 'notFound'), GiveawaysError_1.GiveawaysErrorCodes.DATABASE_ERROR); - } - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.DATABASE_ERROR(databaseType_enum_1.DatabaseType.JSON), GiveawaysError_1.GiveawaysErrorCodes.DATABASE_ERROR); - } - } - this.emit('databaseConnect'); - break; - } - case databaseType_enum_1.DatabaseType.MONGODB: { - this._logger.debug('Connecting to MongoDB...'); - const databaseOptions = this.options.connection; - const mongo = new quick_mongo_super_1.default(databaseOptions); - const connectionStartDate = Date.now(); - await mongo.connect(); - this.db = mongo; - this._logger.debug(`MongoDB connection established in ${Date.now() - connectionStartDate}ms`, 'lightgreen'); - this.emit('databaseConnect'); - break; - } - case databaseType_enum_1.DatabaseType.ENMAP: { - this._logger.debug('Initializing Enmap...'); - const databaseOptions = this.options.connection; - this.db = new enmap_1.default(databaseOptions); - this.emit('databaseConnect'); - break; - } - default: { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_DATABASE); - } - } - this.database = new DatabaseManager_1.DatabaseManager(this); - await this._sendUpdateMessage(); - this._logger.debug('Waiting for client to be ready...'); - const clientReadyInterval = setInterval(() => { - if (this.client.isReady()) { - clearInterval(clientReadyInterval); - const giveawayCheckingInterval = setInterval(() => { - this._checkGiveaways(); - }, this.options.giveawaysCheckingInterval); - this.giveawaysCheckingInterval = giveawayCheckingInterval; - this.ready = true; - this.emit('ready', this); - this._logger.debug('Giveaways module is ready!', 'lightgreen'); - } - }, 100); - this.client.on('interactionCreate', async (interaction) => { - if (interaction.isButton()) { - const interactionMessage = interaction.message; - if (interaction.customId == 'joinGiveawayButton') { - const guildGiveaways = this.getGuildGiveaways(interactionMessage.guild.id); - const giveaway = guildGiveaways.find(giveaway => giveaway.messageID == interactionMessage.id); - if (giveaway) { - const isUserJoined = giveaway.entries.has(interaction.user.id); - const restrictedMessages = giveaway.messageProps?.embeds.restrictionsMessages; - for (const messageObjectName of TypedObject_1.TypedObject.keys(restrictedMessages || {})) { - const messageObject = restrictedMessages[messageObjectName]; - for (const [key, value] of TypedObject_1.TypedObject.entries(messageObject || {})) { - messageObject[key] = value - ?.toString() - ?.replaceAll('{memberMention}', interaction.user.toString()); - } - } - const memberRestrictionMessage = restrictedMessages?.memberRestricted || {}; - const hasNoRequiredRolesMessage = restrictedMessages?.hasNoRequiredRoles || {}; - const hasRestrictedRolesMessage = restrictedMessages?.hasRestrictedRoles || {}; - if (giveaway.participantsFilter?.restrictedMembers?.length) { - if (!(interaction.member instanceof discord_js_1.GuildMember)) - return; - const restrictedMembers = giveaway.participantsFilter?.restrictedMembers - .map(role => role.replaceAll('<@', '').replaceAll('>', '')); - if (restrictedMembers.includes(interaction.user.id)) { - if (!Object.keys(memberRestrictionMessage).length) { - memberRestrictionMessage.messageContent = 'You **cannot** participate in this giveaway.'; - } - const memberRestrictedEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, memberRestrictionMessage); - interaction.reply({ - content: memberRestrictionMessage?.messageContent, - embeds: Object.keys(memberRestrictionMessage).length == 1 && - memberRestrictionMessage?.messageContent - ? [] : [memberRestrictedEmbed], - ephemeral: true - }); - return; - } - } - if (giveaway.participantsFilter?.requiredRoles?.length) { - if (!(interaction.member instanceof discord_js_1.GuildMember)) - return; - const requiredRoles = giveaway.participantsFilter?.requiredRoles - .map(role => role.replaceAll('<@&', '').replaceAll('>', '')); - let memberHasAtLeastOneRequiredRole = false; - for (const roleID of interaction.member.roles.cache.keys()) { - if (requiredRoles.includes(roleID)) { - memberHasAtLeastOneRequiredRole = true; - } - } - if (!memberHasAtLeastOneRequiredRole) { - if (!Object.keys(hasNoRequiredRolesMessage).length) { - hasNoRequiredRolesMessage.messageContent = - 'You **don\'t** have any of the **required** roles' + - `to join this giveaway: ${giveaway.participantsFilter.requiredRoles.join(', ')}.`; - } - const hasNoRequiredRolesEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, hasNoRequiredRolesMessage); - interaction.reply({ - content: hasNoRequiredRolesMessage?.messageContent, - embeds: Object.keys(hasNoRequiredRolesMessage).length == 1 && - hasNoRequiredRolesMessage?.messageContent - ? [] : [hasNoRequiredRolesEmbed], - ephemeral: true - }); - return; - } - } - if (giveaway.participantsFilter?.restrictedRoles?.length) { - if (!(interaction.member instanceof discord_js_1.GuildMember)) - return; - const restrictedRoles = giveaway.participantsFilter?.restrictedRoles - .map(role => role.replaceAll('<@&', '').replaceAll('>', '')); - for (const restrictedRole of restrictedRoles) { - const memberHasRestrictedRole = interaction.member.roles.cache.has(restrictedRole); - if (memberHasRestrictedRole) { - if (!Object.keys(hasRestrictedRolesMessage).length) { - hasRestrictedRolesMessage.messageContent = - 'You **cannot** have any of these roles to join this giveaway: ' + - `${giveaway.participantsFilter.restrictedRoles.join(', ')}.`; - } - const hasRestrictedRolesEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, hasRestrictedRolesMessage); - interaction.reply({ - content: hasRestrictedRolesMessage?.messageContent, - embeds: Object.keys(hasRestrictedRolesMessage).length == 1 && - hasRestrictedRolesMessage?.messageContent - ? [] : [hasRestrictedRolesEmbed], - ephemeral: true - }); - return; - } - } - } - if (!isUserJoined) { - const giveawayJoinMessage = giveaway.messageProps?.embeds?.joinGiveawayMessage || {}; - const giveawayLeaveEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, giveawayJoinMessage); - const newGiveaway = giveaway.addEntry(interaction.guild.id, interaction.user.id); - if (!Object.keys(giveawayJoinMessage).length) { - giveawayJoinMessage.messageContent = 'You have joined the giveaway!'; - } - interaction.reply({ - content: giveawayJoinMessage?.messageContent, - embeds: Object.keys(giveawayJoinMessage).length == 1 && - giveawayJoinMessage?.messageContent - ? [] : [giveawayLeaveEmbed], - ephemeral: true - }).catch((err) => { - // catching the "unknown interaction" error - // while still sending the response on the button click somehow - if (!err.message.toLowerCase().includes('interaction')) { - throw new GiveawaysError_1.GiveawaysError('Cannot join the giveaway: ' + err, GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_ERROR); - } - }); - this._messageUtils.editEntryGiveawayMessage(newGiveaway); - } - else { - const giveawayLeaveMessage = giveaway.messageProps?.embeds?.leaveGiveawayMessage || {}; - const giveawayLeaveEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, giveawayLeaveMessage); - const newGiveaway = giveaway.removeEntry(interaction.guild.id, interaction.user.id); - if (!Object.keys(giveawayLeaveMessage).length) { - giveawayLeaveMessage.messageContent = 'You have left the giveaway!'; - } - interaction.reply({ - content: giveawayLeaveMessage?.messageContent, - embeds: Object.keys(giveawayLeaveMessage).length == 1 && - giveawayLeaveMessage?.messageContent - ? [] : [giveawayLeaveEmbed], - ephemeral: true - }).catch((err) => { - // catching the "unknown interaction" error - // while still sending the responce on the button click somehow - if (!err.message.toLowerCase().includes('interaction')) { - throw new GiveawaysError_1.GiveawaysError('Cannot leave the giveaway: ' + err, GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_ERROR); - } - }); - this._messageUtils.editEntryGiveawayMessage(newGiveaway); - } - } - else { - throw new GiveawaysError_1.GiveawaysError('Cannot join the giveaway: ' + GiveawaysError_1.errorMessages.UNKNOWN_GIVEAWAY(interactionMessage.id), GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_GIVEAWAY); - } - } - if (interaction.customId == 'rerollButton') { - const guildGiveaways = this.getGuildGiveaways(interactionMessage.guild.id); - const giveaway = guildGiveaways.find(giveaway => giveaway.messageID == interactionMessage.id); - const rerollEmbedStrings = giveaway?.messageProps?.embeds?.reroll; - if (giveaway) { - if (interaction.user.id !== giveaway?.host.id) { - const onlyHostCanReroll = rerollEmbedStrings?.onlyHostCanReroll || {}; - const rerollErroredMessageContent = onlyHostCanReroll?.messageContent; - const errorEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, onlyHostCanReroll); - interaction.reply({ - content: rerollErroredMessageContent, - embeds: Object.keys(onlyHostCanReroll).length == 1 && rerollErroredMessageContent ? [] : [errorEmbed], - ephemeral: true - }).catch((err) => { - // catching the "unknown interaction" error - // while still sending the responce on the button click somehow - if (!err.message.toLowerCase().includes('interaction')) { - throw new GiveawaysError_1.GiveawaysError('Cannot reply to the button: ' + err, GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_ERROR); - } - }); - } - else { - const rerollSuccess = rerollEmbedStrings?.successMessage || {}; - const rerollSuccessfulMessageCreate = rerollSuccess?.messageContent; - giveaway.reroll(); - const successEmbed = this._messageUtils.buildGiveawayEmbed(giveaway.raw, rerollSuccess); - interaction.reply({ - content: rerollSuccessfulMessageCreate, - embeds: Object.keys(rerollSuccess).length == 1 && rerollSuccessfulMessageCreate ? [] : [successEmbed], - ephemeral: true - }).catch((err) => { - // catching the "unknown interaction" error - // while still sending the responce on the button click somehow - if (!err.message.toLowerCase().includes('interaction')) { - throw new GiveawaysError_1.GiveawaysError('Cannot reroll the winners: ' + err, GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_ERROR); - } - }); - } - } - else { - throw new GiveawaysError_1.GiveawaysError('Cannot reroll the winners: ' + GiveawaysError_1.errorMessages.UNKNOWN_GIVEAWAY(interactionMessage.id), GiveawaysError_1.GiveawaysErrorCodes.UNKNOWN_GIVEAWAY); - } - } - } - }); - } - /** - * Sends the {@link Giveaways} module update state in the console. - * @returns {Promise} - * @private - */ - async _sendUpdateMessage() { - /* eslint-disable max-len */ - if (this.options.updatesChecker?.checkUpdates) { - const result = await (0, checkUpdates_function_1.checkUpdates)(); - if (!result.updated) { - console.log('\n\n'); - console.log(this._logger.colors.green + '╔═════════════════════════════════════════════════════════════════════╗'); - console.log(this._logger.colors.green + '║ @ discord-giveaways-super - [] X ║'); - console.log(this._logger.colors.green + '║═════════════════════════════════════════════════════════════════════║'); - console.log(this._logger.colors.yellow + `║ The module is ${this._logger.colors.red}out of date!${this._logger.colors.yellow} ║`); - console.log(this._logger.colors.magenta + '║ New version is available! ║'); - console.log(this._logger.colors.blue + `║ ${result.installedVersion} --> ${result.availableVersion} ║`); - console.log(this._logger.colors.cyan + '║ Run "npm i discord-giveaways-super@latest" ║'); - console.log(this._logger.colors.cyan + '║ to update! ║'); - console.log(this._logger.colors.white + '║ View the full changelog here: ║'); - console.log(this._logger.colors.red + `║ https://dgs-docs.js.org/#/docs/main/${result.availableVersion}/general/changelog ║`); - console.log(this._logger.colors.green + '╚═════════════════════════════════════════════════════════════════════╝\x1b[37m'); - console.log('\n\n'); - } - else { - if (this.options.updatesChecker?.upToDateMessage) { - console.log('\n\n'); - console.log(this._logger.colors.green + '╔═════════════════════════════════════════════════════════════════╗'); - console.log(this._logger.colors.green + '║ @ discord-giveaways-super - [] X ║'); - console.log(this._logger.colors.green + '║═════════════════════════════════════════════════════════════════║'); - console.log(this._logger.colors.yellow + `║ The module is ${this._logger.colors.cyan}up to date!${this._logger.colors.yellow} ║`); - console.log(this._logger.colors.magenta + '║ No updates are available. ║'); - console.log(this._logger.colors.blue + `║ Current version is ${result.availableVersion}. ║`); - console.log(this._logger.colors.cyan + '║ Enjoy! ║'); - console.log(this._logger.colors.white + '║ View the full changelog here: ║'); - console.log(this._logger.colors.red + `║ https://dgs-docs.js.org/#/docs/main/${result.availableVersion}/general/changelog ║`); - console.log(this._logger.colors.green + '╚═════════════════════════════════════════════════════════════════╝\x1b[37m'); - console.log('\n\n'); - } - } - } - } - /** - * Starts the giveaway. - * @param {IGiveawayStartConfig} giveawayOptions {@link Giveaway} options. - * @returns {Promise>>} Created {@link Giveaway} instance. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid, `INVALID_TIME` - if invalid time string was specified. - */ - async start(giveawayOptions) { - const { channelID, guildID, hostMemberID, prize, time, winnersCount, defineEmbedStrings, buttons, participantsFilter } = giveawayOptions; - if (!channelID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('channelID', 'Giveaways.start'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (!guildID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaways.start'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (!hostMemberID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('hostMemberID', 'Giveaways.start'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (!prize) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('prize', 'Giveaways.start'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof channelID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.channelID', 'string', channelID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (typeof guildID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (typeof hostMemberID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.hostMemberID', 'string', hostMemberID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (typeof prize !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.prize', 'string', prize), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (typeof time !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.time', 'string', time), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (isNaN(winnersCount)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.winnersCount', 'number', winnersCount), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (buttons && typeof buttons !== 'object') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.buttons', 'object', buttons), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (typeof defineEmbedStrings !== 'function') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayOptions.defineEmbedStrings', 'function', defineEmbedStrings), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (!(0, time_function_1.isTimeStringValid)(time)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.GiveawaysErrorCodes.INVALID_TIME); - } - const joinGiveawayButton = buttons?.joinGiveawayButton; - const rerollButton = buttons?.rerollButton; - const goToMessageButton = buttons?.goToMessageButton; - const guildGiveaways = this.getGuildGiveaways(guildID); - const newGiveaway = { - id: ((guildGiveaways.at(-1)?.id || 0)) + 1, - hostMemberID, - guildID, - channelID, - messageID: '', - prize, - startTimestamp: Math.floor(Date.now() / 1000), - endTimestamp: Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(time)) / 1000), - endedTimestamp: 0, - time: time || '1d', - state: giveaway_interface_1.GiveawayState.STARTED, - winnersCount: winnersCount || 1, - entriesCount: 0, - entries: [], - winners: [], - participantsFilter: participantsFilter || {}, - isEnded: false - }; - const hostMember = await this.client.users.fetch(hostMemberID).catch(console.error); - if (!hostMember) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.USER_NOT_FOUND(hostMemberID), GiveawaysError_1.GiveawaysErrorCodes.USER_NOT_FOUND); - } - const definedEmbedStrings = defineEmbedStrings ? defineEmbedStrings(giveawayTemplate_1.giveawayTemplate, hostMember, newGiveaway.participantsFilter) : {}; - const startEmbedStrings = definedEmbedStrings?.start || {}; - const finish = definedEmbedStrings?.finish; - const reroll = definedEmbedStrings?.reroll; - const restrictionsMessages = definedEmbedStrings?.restrictionsMessages; - const channel = this.client.channels.cache.get(channelID); - const giveawayEmbed = this._messageUtils.buildGiveawayEmbed(newGiveaway, startEmbedStrings); - const buttonsRow = this._messageUtils.buildButtonsRow(joinGiveawayButton); - const [finishEmbedStrings, rerollEmbedStrings, restrictionsMessagesStrings] = [ - finish ? finish('{winnersString}', winnersCount) : {}, - reroll ? reroll('{winnersString}', winnersCount) : {}, - restrictionsMessages ? restrictionsMessages('{memberMention}') : {} - ]; - const message = await channel.send({ - content: startEmbedStrings?.messageContent, - embeds: Object.keys(startEmbedStrings).length == 1 && startEmbedStrings?.messageContent ? [] : [giveawayEmbed], - components: [buttonsRow] - }); - newGiveaway.messageID = message.id; - newGiveaway.messageURL = message.url; - newGiveaway.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(newGiveaway.time)) / 1000); - newGiveaway.messageProps = { - embeds: { - start: startEmbedStrings, - joinGiveawayMessage: definedEmbedStrings?.joinGiveawayMessage, - leaveGiveawayMessage: definedEmbedStrings?.leaveGiveawayMessage, - finish: finishEmbedStrings, - reroll: rerollEmbedStrings, - restrictionsMessages: restrictionsMessagesStrings - }, - buttons: { - joinGiveawayButton, - rerollButton, - goToMessageButton - } - }; - this.database.push(`${guildID}.giveaways`, newGiveaway); - const startedGiveaway = new Giveaway_1.Giveaway(this, newGiveaway); - this.emit('giveawayStart', startedGiveaway); - return startedGiveaway; - } - /** - * Finds the giveaway in all giveaways database by its ID. - * @param {number} giveawayID Giveaway ID to find the giveaway by. - * @returns {Maybe>>} Giveaway instance. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid. - */ - get(giveawayID) { - if (!giveawayID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('giveawayID', 'Giveaways.get'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (isNaN(giveawayID)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveawayID', 'number', giveawayID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - const result = this.find(giveaway => giveaway.id == giveawayID) || null; - return result; - } - /** - * Finds the giveaway in all giveaways database by the specified callback function. - * - * @param {FindCallback>} cb - * The callback function to find the giveaway in the giveaways database. - * - * @returns {Maybe>>} Giveaway instance. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid. - */ - find(cb) { - if (!cb) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('cb', 'Giveaways.find'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof cb !== 'function') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('cb', 'function', cb), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - const giveaways = this.getAll(); - const giveaway = giveaways.find(cb) || null; - return giveaway; - } - /** - * Returns the mapped giveaways array based on the specified callback function. - * - * Type parameters: - * - * - `TReturnType` - the type being returned in a callback function. - * - * @param {FindCallback>} cb - * The callback function to call on the giveaway. - * - * @returns {TReturnType[]} Mapped giveaways array. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid. - */ - map(cb) { - if (!cb) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('cb', 'Giveaways.find'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof cb !== 'function') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('cb', 'function', cb), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - const giveaways = this.getAll(); - const giveaway = giveaways.map(cb); - return giveaway; - } - /** - * Gets all the giveaways from the specified guild in database. - * @param {DiscordID} guildID Guild ID to get the giveaways from. - * @returns {Array>>} Giveaways array from the specified guild in database. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid. - */ - getGuildGiveaways(guildID) { - if (!guildID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaways.getGuildGiveaways'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof guildID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - const giveaways = this.database.get(`${guildID}.giveaways`) || []; - return giveaways.map(giveaway => new Giveaway_1.Giveaway(this, giveaway)); - } - /** - * Gets all the giveaways from all the guilds in database. - * @returns {Array>} Giveaways array from all the guilds in database. - */ - getAll() { - const giveaways = []; - const guildIDs = this.database.getKeys(); - for (const guildID of guildIDs.filter(guildID => !isNaN(parseInt(guildID)))) { - const databaseGiveaways = this.database.get(`${guildID}.giveaways`) || []; - for (const databaseGiveaway of databaseGiveaways) { - giveaways.push(databaseGiveaway); - } - } - return giveaways.map(giveaway => new Giveaway_1.Giveaway(this, giveaway)); - } - /** - * Checks for all giveaways to be finished and end them if they are. - * @returns {void} - * @private - */ - _checkGiveaways() { - const giveaways = this.getAll(); - for (const giveaway of giveaways) { - if (giveaway.isFinished && !giveaway.isEnded) { - giveaway.end(); - } - } - } -} -exports.Giveaways = Giveaways; -// For documentation purposes -/** - * An object that contains an information about a giveaway. - * @typedef {object} IGiveaway - * @prop {number} id The ID of the giveaway. - * @prop {string} prize The prize of the giveaway. - * @prop {string} time The time of the giveaway. - * @prop {GiveawayState} state The state of the giveaway. - * @prop {number} winnersCount The number of possible winners in the giveaway. - * @prop {number} startTimestamp The timestamp when the giveaway started. - * @prop {boolean} isEnded Determines if the giveaway was ended in the database. - * @prop {number} endTimestamp The timestamp when the giveaway ended. - * @prop {DiscordID} hostMemberID The ID of the host member. - * @prop {DiscordID} channelID The ID of the channel where the giveaway is held. - * @prop {DiscordID} messageID The ID of the giveaway message. - * @prop {string} messageURL The URL of the giveaway message. - * @prop {DiscordID} guildID The ID of the guild where the giveaway is held. - * @prop {Array>} entries The array of user Set of IDs of users who have joined the giveaway. - * @prop {Array>} winners Array of used ID who have won in the giveaway. - * - * Don't confuse this property with `winnersCount`, the setting that dertermines how many users can win in the giveaway. - * @prop {number} entriesCount The number of users who have joined the giveaway. - * @prop {Partial} participantsFilter An object with conditions for members to join the giveaway. - * @prop {IGiveawayMessageProps} messageProps The message data properties for embeds and buttons. - * - * @template TDatabaseType The database type that is used. - */ -/** - * An object with conditions for members to join the giveaway. - * @typedef {object} IParticipantsFilter - * @prop {Array>} [requiredRoles] - * Array of role IDs that the user *required* to have in order to participate in a giveaway. - * - * @prop {Array>} [restrictedRoles] - * Array of role IDs that the user *cannot have* in order to participate in a giveaway. - * - * @prop {Array>} [restrictedMembers] - * Array of member IDs of the users who *cannot participate* in the giveaway. - */ -/** - * An interface containing embed objects for various giveaway reroll cases. - * @typedef {object} IGiveawayRerollEmbeds - * @prop {IGiveawayEmbedOptions} onlyHostCanReroll The options for the embed when only the host can reroll. - * @prop {IGiveawayEmbedOptions} newGiveawayMessage The options for the embed when sending a new giveaway message. - * @prop {IGiveawayEmbedOptions} successMessage The options for the embed when the giveaway is successful. - */ -/** - * An interface containing embed objects for various giveaway finish cases. - * @typedef {object} IGiveawayFinishEmbeds - * @prop {IGiveawayEmbedOptions} newGiveawayMessage The options for the embed when sending a new giveaway message. - * @prop {IGiveawayEmbedOptions} endMessage The options for the embed when the giveaway has ended. - * @prop {IGiveawayEmbedOptions} noWinnersNewGiveawayMessage The options for the embed when there are no winners for the giveaway. - * - * @prop {IGiveawayEmbedOptions} noWinnersEndMessage - * The options for the embed when there are no winners for the giveaway and it has ended. - */ -/** - * An interface that contains the data properties for embeds and buttons. - * @typedef {object} IGiveawayMessageProps - * @prop {IGiveawayEmbeds} embeds The embed objects for the giveaway message. - * @prop {IGiveawayButtons} buttons The button objects for the giveaway message. - */ -/** - * An interface containing different types of giveaway embeds in the IGiveaways class. - * @typedef {object} IGiveawayEmbeds - * @prop {IGiveawayEmbedOptions} start Message embed data for cases when the giveaway has started. - * @prop {IGiveawayEmbedOptions} joinGiveawayMessage The message to reply to user with when they join the giveaway. - * - * @prop {IGiveawayEmbedOptions} leaveGiveawayMejoinGiveawayMessage - * The message to reply to user with when they leave the giveaway. - * - * @prop {IGiveawayRerollEmbeds} reroll Message embed data for cases when rerolling the giveaway. - * @prop {IGiveawayFinishEmbeds} finish Message embed data for cases when the giveaway has finished. - * - * @prop {IGiveawayJoinRestrictionsMessages} restrictionsMessages - * Message embed data for all the giveaway joining restrictions cases. - */ -/** - * An object that contains messages that are sent in various giveaway cases, such as end with winners or without winners. - * @typedef {object} IGiveawayFinishMessages - * - * @prop {IGiveawayEmbedOptions} newGiveawayMessage - * The separated message to be sent in the giveaway channel when giveaway ends. - * - * @prop {IGiveawayEmbedOptions} endMessage - * The separated message to be sent in the giveaway channel when a giveaway ends with winners. - * @prop {IGiveawayEmbedOptions} noWinnersNewGiveawayMessage - * The message that will be set to the original giveaway message if there are no winners in the giveaway. - * - * @prop {IGiveawayEmbedOptions} noWinnersEndMessage - * The separated message to be sent in the giveaway channel if there are no winners in the giveaway. - */ -/** - * A function that is called when giveaway is finished. - * @callback GiveawayFinishCallback - * @param {string} winnersString A string that contains the users who won the giveaway separated with comma. - * @param {number} winnersCount Number of winners that were picked. - * @returns {IGiveawayFinishMessages} Giveaway message object. - */ -/** - * An object that contains messages that are sent in various giveaway cases, such as end with winners or without winners. - * @typedef {object} IGiveawayRerollMessages - * - * @prop {IGiveawayEmbedOptions} onlyHostCanReroll - * The message to reply to user with when not a giveaway host tries to do a reroll. - * - * @prop {IGiveawayEmbedOptions} newGiveawayMessage - * The message that will be set to the original giveaway message after the reroll. - * - * @prop {IGiveawayEmbedOptions} successMessage - * The separated message to be sent in the giveaway channel when the reroll is successful. - */ -/** - * A function that is called when giveaway winners are rerolled. - * @callback GiveawayRerollCallback - * - * @param {string} winnersMentionsString - * A string that contains the mentions of users who won the giveaway, separated with comma. - * - * @param {number} winnersCount Number of winners that were picked. - * @returns {IGiveawayRerollMessages} Giveaway message object. - */ -/** - * An object that contains the giveaway buttons that may be set up. - * @typedef {object} IGiveawayMessageButtons - * @prop {IGiveawayButtonOptions} joinGiveawayButton The options for the join giveaway button. - * @prop {IGiveawayButtonOptions} rerollButton The options for the reroll button. - * @prop {IGiveawayButtonOptions} goToMessageButton The options for the go to message button. - */ -/** - * An object that contains an information about a giveaway without internal props. - * @typedef {object} GiveawayWithoutInternalProps - * @prop {number} id The ID of the giveaway. - * @prop {string} prize The prize of the giveaway. - * @prop {string} time The time of the giveaway. - * @prop {number} winnersCount The number of possible winners in the giveaway. - * @prop {number} startTimestamp The timestamp when the giveaway started. - * @prop {number} endTimestamp The timestamp when the giveaway ended. - * @prop {DiscordID} hostMemberID The ID of the host member. - * @prop {DiscordID} channelID The ID of the channel where the giveaway is held. - * @prop {DiscordID} messageID The ID of the giveaway message. - * @prop {string} messageURL The URL of the giveaway message. - * @prop {DiscordID} guildID The ID of the guild where the giveaway is held. - * @prop {Array>} entries The array of user Set of IDs of users who have joined the giveaway. - * @prop {IGiveawayMessageProps} messageProps The message data properties for embeds and buttons. - */ -/** - * A type that contains all giveaway properties that may be safely edited. - * @typedef {'prize' | 'winnersCount' | 'hostMemberID'} EditableGiveawayProperties - */ -/** - * The type that returns the property's value type based on the specified {@link Giveaway} property in `TProperty`. - * - * Type parameters: - * - * - `TProperty` ({@link EditableGiveawayProperties}) - {@link Giveaway} property to get its value type. - * - * @typedef {object} GiveawayPropertyValue - * @template TProperty {@link Giveaway} property to get its value type. - */ -/** - * An enum that determines the state of a giveaway. - * @typedef {number} GiveawayState - * @prop {number} STARTED The giveaway has started. - * @prop {number} ENDED The giveaway has ended. - */ -/** - * Full {@link Giveaways} class configuration object. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - Database type that will - * determine which connection configuration should be used. - * - * @typedef {object} IGiveawaysConfiguration - * @prop {DatabaseType} database Database type to use. - * @prop {DatabaseConnectionOptions} connection Database type to use. - * - * @prop {?number} [giveawaysCheckingInterval=1000] - * Determines how often the giveaways ending state will be checked (in ms). Default: 1000. - * - * @prop {?boolean} [debug=false] Determines if debug mode is enabled. Default: false. - * @prop {?number} [minGiveawayEntries=1] Determines the minimum required giveaway entries to draw the winner. Default: 1 - * @prop {Partial} [updatesChecker] Updates checker configuration. - * @prop {Partial} [configurationChecker] Giveaways config checker configuration. - * - * @template TDatabaseType - * The database type that will determine which connection configuration should be used. - */ -/** - * Optional configuration for the {@link Giveaways} class. - * @typedef {object} IGiveawaysOptionalConfiguration - * - * @prop {?number} [giveawaysCheckingInterval=1000] - * Determines how often the giveaways ending state will be checked (in ms). Default: 1000. - * - * @prop {?boolean} [debug=false] Determines if debug mode is enabled. Default: false. - * @prop {?number} [minGiveawayEntries=1] Determines the minimum required giveaway entries to draw the winner. Default: 1 - * @prop {Partial} [updatesChecker] Updates checker configuration. - * @prop {Partial} [configurationChecker] Giveaways config checker configuration. - */ -/** - * Configuration for the updates checker. - * @typedef {object} IUpdateCheckerConfiguration - * @prop {?boolean} [checkUpdates=true] Sends the update state message in console on start. Default: true. - * @prop {?boolean} [upToDateMessage=false] Sends the message in console on start if module is up to date. Default: false. - */ -/** - * Configuration for the configuration checker. - * @typedef {object} IGiveawaysConfigCheckerConfiguration - * @prop {?boolean} ignoreInvalidTypes Allows the method to ignore the options with invalid types. Default: false. - * @prop {?boolean} ignoreUnspecifiedOptions Allows the method to ignore the unspecified options. Default: true. - * @prop {?boolean} ignoreInvalidOptions Allows the method to ignore the unexisting options. Default: false. - * @prop {?boolean} showProblems Allows the method to show all the problems in the console. Default: true. - * @prop {?boolean} sendLog Allows the method to send the result in the console. - * Requires the 'showProblems' or 'sendLog' options to set. Default: true. - * @prop {?boolean} sendSuccessLog Allows the method to send the result if no problems were found. Default: false. - */ -/** - * JSON database configuration. - * @typedef {object} IJSONDatabaseConfiguration - * @prop {?string} [path='./giveaways.json'] Full path to a JSON storage file. Default: './giveaways.json'. - * @prop {?boolean} [checkDatabase=true] Enables the error checking for database file. Default: true - * @prop {?number} [checkingInterval=1000] Determines how often the database file will be checked (in ms). Default: 1000. - */ -/** - * An object that contains an information about a giveaway that is required fo starting. - * @typedef {object} IGiveawayData - * @prop {string} prize The prize of the giveaway. - * @prop {string} time The time of the giveaway. - * @prop {number} winnersCount The number of possible winners in the giveaway. - * @prop {DiscordID} hostMemberID The ID of the host member. - * @prop {DiscordID} channelID The ID of the channel where the giveaway is held. - * @prop {DiscordID} guildID The ID of the guild where the giveaway is held. - */ -/** - * Giveaway start config. - * @typedef {object} IGiveawayStartConfig - * @prop {string} prize The prize of the giveaway. - * @prop {string} time The time of the giveaway. - * @prop {number} winnersCount The number of possible winners in the giveaway. - * @prop {DiscordID} hostMemberID The ID of the host member. - * @prop {DiscordID} channelID The ID of the channel where the giveaway is held. - * @prop {DiscordID} guildID The ID of the guild where the giveaway is held. - * @prop {IGiveawayButtons} [buttons] Giveaway buttons object. - * @prop {DefineEmbedStringsCallback} [defineEmbedStrings] A function that defines the embed strings used in the giveaway. - */ -/** - * Giveaway buttons that may be specified. - * @typedef {object} IGiveawayButtons - * @prop {?IGiveawayButtonOptions} [joinGiveawayButton] Button object for the "join giveaway" button. - * @prop {?IGiveawayButtonOptions} [rerollButton] Button object for the "reroll" button. - * @prop {?ILinkButton} [goToMessageButton] Link button object for the "go to message" button. - */ -/** - * Link button object. - * - * Please note that URL is not required as it's being applied after starting the giveaway. - * @typedef {object} ILinkButton - * @prop {string} [text] Button text string. - * @prop {string} [emoji] Emoji string. - * @prop {ButtonStyle} url URL that the button will take to. - */ -/** - * A function that defines the embed strings used in the giveaway. - * @callback DefineEmbedStringsCallback - * @param {IGiveaway} giveaway - An object containing information about the giveaway. - * @param {User} giveawayHost - The host of the giveaway. - * @returns {Partial>} - An object containing the defined embed strings. - */ -/** - * Giveaway start options. - * @typedef {object} IGiveawayStartOptions - * @prop {IGiveawayButtons} [buttons] Giveaway buttons object. - * @prop {DefineEmbedStringsCallback} [defineEmbedStrings] A function that defines the embed strings used in the giveaway. - */ -/** - * Object containing embed string definitions used in the IGiveaways class. - * - * Type parameters: - * - * - `IsTemplate` ({@link boolean}) - Determine if the specified giveaway object is a template object. - * - * @typedef {object} IEmbedStringsDefinitions - * - * @prop {IGiveawayEmbedOptions} start - * This object is used in the original giveaway message that people will use to join the giveaway. - * - * @prop {GiveawayFinishCallback} finish - * This function is called and all returned message objects are extracted when the giveaway is finished. - * - * @prop {GiveawayRerollCallback} reroll - * This function is called and all returned message objects are extracted when the giveaway winners are rerolled. - * - * @prop {GiveawayJoinRestrictionsCallback} restrictionsMessages - * This function is called and all returned message objects are extracted when any case - * of the user not being able to participate in a giveaway has triggered - * (such as not having the required role or being completely restricted). - * - * @template IsTemplate Determine if the specified giveaway object is a template object. - */ -/** - * A function that is called when the member cannot join the giveaway - * due to participants filter being set up. - * - * @callback GiveawayJoinRestrictionsCallback - * - * @param {string} memberMention The mention of the user who attempted to join the giveaway. - * @returns {Partial} Giveaway join restrictions messages object. - * - * @template IsTemplate Determine if the specified giveaway object is a template object. - */ -/** - * The object where all the giveaway restrictions messages may be specified. - * @typedef {object} IGiveawayJoinRestrictionsMessages - * @prop {IGiveawayEmbedOptions} memberRestricted - * The message to reply with if the member is restricted from participating in the giveaway. - * - * @prop {IGiveawayEmbedOptions} hasNoRequiredRoles - * The message to reply with if the member doesn't have at least one - * of the **required** roles to participate in the giveaway. - * - * @prop {IGiveawayEmbedOptions} hasRestrictedRoles - * The message to reply with if the member has at least one - * of the **restricted** roles that are not allowing to participate in the giveaway. - */ -/** - * Button object. - * @typedef {object} IGiveawayButtonOptions - * @prop {?string} [text] Button text string. - * @prop {?string} [emoji] Emoji string. - * @prop {?ButtonStyle} [style] Button style. - */ -/** - * Message embed options. - * @typedef {object} IGiveawayEmbedOptions - * - * @prop {?string} [messageContent] - * Message content to specify in the message. - * If only message content is specified, it will be sent without the embed. - * - * @prop {?string} [title] The title of the embed. - * @prop {?string} [titleIcon] The icon of the title in the embed. - * @prop {?string} [titleURL] The url of the icon of the title in the embed. - * @prop {?string} [description] The description of the embed. - * @prop {?string} [footer] The footer of the embed. - * @prop {?string} [footerIcon] The icon of the footer in the embed. - * @prop {?string} [thumbnailURL] Embed thumbnail. - * @prop {?string} [imageURL] Embed Image URL. - * @prop {?ColorResolvable} [color] The color of the embed. - * @prop {?number} [timestamp] The embed timestamp to set. - */ -/** - * JSON database configuration. - * @typedef {object} IJSONDatabaseConfiguration - * @prop {?string} [path='./giveaways.json'] Full path to a JSON storage file. Default: './giveaways.json'. - * @prop {?boolean} [checkDatabase=true] Enables the error checking for database file. Default: true - * @prop {?number} [checkingInterval=1000] Determines how often the database file will be checked (in ms). Default: 1000. - */ -/** - * Database connection options based on the used database type. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - Database type that will - * determine which connection configuration should be used. - * - * @typedef {( - * Partial | EnmapOptions | IMongoConnectionOptions - * )} DatabaseConnectionOptions - * - * @see Partial - JSON configuration. - * - * @see EnmapOptions - Enmap configuration. - * - * @see IMongoConnectionOptions - MongoDB connection configuration. - * - * @template TDatabaseType - * The database type that will determine which connection configuration should be used. - */ -/** - * External database object based on the used database type. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - Database type that will determine - * which connection configuration should be used. - * - * - `TKey` ({@link string}) - The type of database key that will be used. - * - `TValue` ({@link any}) - The type of database values that will be used. - * - * @typedef {( - * null | Enmap | Mongo - * )} Database - * - * @see null - JSON database management object - `null` - * is because it's not an external database - JSON is being parsed by the module itself. - * - * @see Enmap - Enmap database. - * - * @see Mongo - MongoDB database. - * - * @template TDatabaseType - * The database type that will determine which external database management object should be used. - * @template TKey The type of database key that will be used. - * @template TValue The type of database values that will be used. - */ -/** - * An interface containing the structure of the database used in the IGiveaways class. - * @typedef {object} IDatabaseStructure - * @prop {DiscordID} guildID Guild ID that stores the giveaways array - * @prop {IGiveaway[]} giveaways Giveaways array property inside the [guildID] object in database. - */ -/** - * The giveaway data that stored in database, - * @typedef {object} IDatabaseArrayGiveaway - * @prop {IGiveaway} giveaway Giveaway object. - * @prop {number} giveawayIndex Giveaway index in the guild giveaways array. - */ -/** - * A type containing all the {@link Giveaways} events and their return types. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. - * - * - `TDatabaseKey` ({@link string}, optional: defaults to `${string}.giveaways`) - - * The type of database key that will be used in database operations. - * - * - `TDatabaseValue` ({@link any}, optional: defaults to {@link IDatabaseStructure}) - - * The type of database content that will be used in database operations. - * - * @typedef {object} IGiveawaysEvents - * @prop {Giveaways} ready Emits when the {@link Giveaways} module is ready. - * @prop {void} databaseConnect Emits when the connection to the database is established. - * @prop {Giveaway} giveawayStart Emits when the giveaway is started. - * @prop {Giveaway} giveawayRestart Emits when the giveaway is restarted. - * @prop {Giveaway} giveawayEnd Emits when the giveaway is ended. - * @prop {IGiveawayRerollEvent} giveawayReroll Emits when the giveaway winners are rerolled. - * @prop {IGiveawayEditEvent} giveawayEdit Emits when the giveaway info was edited. - * - * @template TDatabaseType The database type that is used. - * @template TDatabaseKey The type of database key that will be used in database operations. - * @template TDatabaseValue The type of database content that will be used in database operations. - */ -/** - * Giveaway reroll event object. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. - * - * @typedef {object} IGiveawayRerollEvent - * @prop {Giveaway} giveaway Giveaway instance. - * @prop {string} newWinners Array of the new picked winners after reroll. - * - * @template TDatabaseType The database type that is used. - */ -/** - * Giveaway time change event object. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. - * - * @typedef {object} IGiveawayTimeChangeEvent - * @prop {string} time The time that affected the giveaway's length. - * @prop {Giveaway} giveaway Giveaway instance. - * - * @template TDatabaseType The database type that is used. - */ -/** - * An interface containing different colors that may be used in a logger. - * @typedef {object} ILoggerColors - * @prop {string} red The color red. - * @prop {string} green The color green. - * @prop {string} yellow The color yellow. - * @prop {string} blue The color blue. - * @prop {string} magenta The color magenta. - * @prop {string} cyan The color cyan. - * @prop {string} white The color white. - * @prop {string} reset The reset color. - * @prop {string} black The color black. - * @prop {string} lightgray The color light gray. - * @prop {string} default The default color. - * @prop {string} darkgray The color dark gray. - * @prop {string} lightred The color light red. - * @prop {string} lightgreen The color light green. - * @prop {string} lightyellow The color light yellow. - * @prop {string} lightblue The color light blue. - * @prop {string} lightmagenta The color light magenta. - * @prop {string} lightcyan The color light cyan. - */ -/** - * An object containing the data about available module updates. - * @typedef {object} IUpdateState - * @prop {boolean} updated Whether an update is available or not. - * @prop {string} installedVersion The currently installed version. - * @prop {string} availableVersion The available version, if any. - */ -// Utility types -/** - * Represents the `if` statement on a type level. - * - * Type parameters: - * - * - `T` ({@link boolean}) - The boolean type to compare with. - * - `IfTrue` ({@link any}) - The type that will be returned if `T` is `true`. - * - `IfFalse` ({@link any}) - The type that will be returned if `T` is `false`. - * - * @typedef {IfTrue | IfFalse} If - * - * @template T The boolean type to compare with. - * @template IfTrue The type that will be returned if `T` is `true`. - * @template IfFalse The type that will be returned if `T` is `false`. - */ -/** - * Makes the specified properties in `K` from the object in `T` optional. - * - * Type parameters: - * - * - `T` ({@link object}) - The object to get the properties from. - * - `K` (keyof T) - The properties to make optional. - * - * @typedef {object} OptionalProps - * - * @template T - The object to get the properties from. - * @template K - The properties to make optional. - */ -/** - * Makes the specified properties in `K` from the object in `T` required. - * - * Type parameters: - * - * - `T` ({@link object}) - The object to get the properties from. - * - `K` (keyof T) - The properties to make required. - * - * @template T - The object to get the properties from. - * @template K - The properties to make required. - * - * @typedef {object} RequiredProps - */ -/** - * A callback function that calls when finding an element in array. - * - * Type parameters: - * - * - `T` ({@link any}) - The type of item to be passed to the callback function. - * - * @callback FindCallback - * @template T The type of item to be passed to the callback function. - * - * @param {T} item The item to be passed to the callback function. - * @returns {boolean} The boolean value returned by the callback function. - */ -/** - * A callback function that calls when mapping the array using the {@link Array.prototype.map} method. - * - * Type parameters: - * - * - `T` ({@link any}) - The type of item to be passed to the callback function. - * - `TReturnType` - ({@link any}) The type of value returned by the callback function. - * - * @callback MapCallback - * - * @template T The type of item to be passed to the callback function. - * @template TReturnType The type of value returned by the callback function. - * - * @param {T} item The item to be passed to the callback function. - * @returns {TReturnType} The value returned by the callback function. - */ -/** - * A type that represents any value with "null" possible to be returned. - * - * Type parameters: - * - * - `T` ({@link any}) - The type to attach. - * - * @template T The type to attach. - * @typedef {any} Maybe - */ -/** - * Adds a prefix at the beginning of a string literal type. - * - * Type parameters: - * - * - `TWord` ({@link string}) The string literal type or union type of them to add the prefix to. - * - `TPrefix` ({@link string}) The string literal type of the prefix to use. - * - * @template TWord The string literal type or union type of them to add the prefix to. - * @template TPrefix The string literal type of the prefix to use. - * - * @typedef {string} AddPrefix - */ -/** -* Constructs an object type with prefixed properties and specified value for each of them. -* -* Type parameters: -* -* - `TWords` ({@link string}) The union type of string literals to add the prefix to. -* - `TPrefix` ({@link string}) The string literal type of the prefix to use. -* - `Value` ({@link any}) Any value to assign as value of each property of the constructed object. -* -* @template TWords The union type of string literals to add the prefix to. -* @template TPrefix The string literal type of the prefix to use. -* @template Value Any value to assign as value of each property of the constructed object. -* -* @typedef {string} PrefixedObject -*/ -/** - * Compares the values on type level and returns a boolean value. - * - * Type parameters: - * - * - `ToCompare` ({@link any}) - The type to compare. - * - `CompareWith` ({@link any}) - The type to compare with. - * - * @template ToCompare The type to compare. - * @template CompareWith The type to compare with. - * - * @typedef {boolean} Equals - */ -/** - * Considers the specified giveaway is running and that is safe to edit its data. - * - * Unlocks the following {@link Giveaway} methods - after performing the {@link Giveaway.isRunning()} type-guard check: - * - * - {@link Giveaway.end()} - * - {@link Giveaway.edit()} - * - {@link Giveaway.extend()} - * - {@link Giveaway.reduce()} - * - {@link Giveaway.setPrize()} - * - {@link Giveaway.setWinnersCount()} - * - {@link Giveaway.setTime()} - * - {@link Giveaway.setHostMemberID()} - * - * Type parameters: - * - * - `TGiveaway` ({@link Giveaway} | {@link UnsafeGiveaway>}) - The giveaway to be considered as safe. - * - * @typedef {SafeGiveaway} - * @template TGiveaway The giveaway to be considered as safe. - */ -/** -* Considers the specified giveaway 'that may be ended' and that is *not* safe to edit its data. -* -* Marks the following {@link Giveaway} methods as 'possibly undefined' to prevent them from running -* before performing the {@link Giveaway.isRunning()} type-guard check: -* -* - {@link Giveaway.end()} -* - {@link Giveaway.edit()} -* - {@link Giveaway.extend()} -* - {@link Giveaway.reduce()} -* - {@link Giveaway.setPrize()} -* - {@link Giveaway.setWinnersCount()} -* - {@link Giveaway.setTime()} -* - {@link Giveaway.setHostMemberID()} -* -* Type parameters: -* -* - `TGiveaway` ({@link Giveaway} | {@link SafeGiveaway>}) - The giveaway to be considered as unsafe. -* -* @typedef {UnsafeGiveaway} -* @template TGiveaway The giveaway to be considered as unsafe. -*/ -/** - * Returns a length of a string on type level. - * - * Type parameters: - * - * - `S` ({@link string}) - The string to check the length of. - * - * @template S The string to check the length of. - * @typedef {number} StringLength - */ -/** -* Conditional type that will return the specified string if it matches the specified length. -* -* Type parameters: -* -* - `N` ({@link number}) - The string length to match to. -* - `S` ({@link string}) - The string to check the length of. -* -* @template N The string length to match to. -* @template S The string to check the length of. -* -* @typedef {number} ExactLengthString -*/ -/** -* Conditional type that will return the specified string if it matches any of the possible Discord ID string lengths. -* -* Type parameters: -* -* - `S` ({@link string}) - The string to check the length of. -* -* @template S The string to check the length of. -* @typedef {number} DiscordID -*/ -/** - * Extracts the type that was passed into `Promise<...>` type. - * - * Type parameters: - * - * - `P` ({@link Promise}) - The Promise to extract the type from. - * - * @template P The Promise to extract the type from. - * @typedef {any} ExtractPromisedType

- */ -// Events, for documentation purposes -/** - * Emits when the {@link Giveaways} module is ready. - * @event Giveaways#ready - * @param {Giveaways} giveaways Initialized {@link Giveaways} instance. - */ -/** - * Emits when the {@link Giveaways} module establishes the database connection. - * @event Giveaways#databaseConnect - * @param {void} databaseConnect Initialized {@link Giveaways} instance. - */ -/** - * Emits when a giveaway is started. - * @event Giveaways#giveawayStart - * @param {Giveaway} giveaway {@link Giveaway} that started. - */ -/** - * Emits when a giveaway is restarted. - * @event Giveaways#giveawayRestart - * @param {Giveaway} giveaway {@link Giveaway} that restarted. - */ -/** - * Emits when a giveaway is ended. - * @event Giveaways#giveawayEnd - * @param {Giveaway} giveaway {@link Giveaway} that ended. - */ -/** - * Emits when a giveaway is rerolled. - * @event Giveaways#giveawayReroll - * @param {IGiveawayRerollEvent} giveaway {@link Giveaway} that was rerolled. - */ diff --git a/dist/src/index.js b/dist/src/index.js deleted file mode 100644 index 2004942..0000000 --- a/dist/src/index.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -__exportStar(require("./types/misc/utils"), exports); -__exportStar(require("./types/misc/colors.interface"), exports); -__exportStar(require("./types/configurations"), exports); -__exportStar(require("./types/giveawaysEvents.interface"), exports); -__exportStar(require("./lib/managers/DatabaseManager"), exports); -__exportStar(require("./types/databaseStructure.interface"), exports); -__exportStar(require("./types/databaseType.enum"), exports); -__exportStar(require("./structures/defaultConfig"), exports); -__exportStar(require("./structures/giveawayTemplate"), exports); -__exportStar(require("./lib/util/functions/checkConfiguration.function"), exports); -__exportStar(require("./lib/util/functions/checkUpdates.function"), exports); -__exportStar(require("./lib/util/functions/typeOf.function"), exports); -__exportStar(require("./lib/util/functions/time.function"), exports); -__exportStar(require("./lib/util/classes/Logger"), exports); -__exportStar(require("./lib/util/classes/JSONParser"), exports); -__exportStar(require("./lib/util/classes/MessageUtils"), exports); -__exportStar(require("./lib/util/classes/Emitter"), exports); -__exportStar(require("./lib/util/classes/GiveawaysError"), exports); -__exportStar(require("./lib/util/classes/TypedObject"), exports); -__exportStar(require("./Giveaways"), exports); -__exportStar(require("./lib/Giveaway"), exports); -__exportStar(require("./lib/giveaway.interface"), exports); diff --git a/dist/src/lib/Giveaway.js b/dist/src/lib/Giveaway.js deleted file mode 100644 index 99b4229..0000000 --- a/dist/src/lib/Giveaway.js +++ /dev/null @@ -1,1034 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Giveaway = void 0; -const giveaway_interface_1 = require("./giveaway.interface"); -const time_function_1 = require("./util/functions/time.function"); -const MessageUtils_1 = require("./util/classes/MessageUtils"); -const GiveawaysError_1 = require("./util/classes/GiveawaysError"); -const giveawayTemplate_1 = require("../structures/giveawayTemplate"); -/** - * Class that represents the Giveaway object. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - The database type that is used. - * - * @implements {Omit} - * @template TDatabaseType The database type that is used. - */ -class Giveaway { - /** - * {@link Giveaways} instance. - * @type {Giveaways} - * @private - */ - _giveaways; - /** - * Message utils instance. - * @type {MessageUtils} - * @private - */ - _messageUtils; - /** - * Input giveaway object. - * @type {IGiveaway} - * @private - */ - _inputGiveaway; - /** - * Giveaway ID. - * @type {number} - */ - id; - /** - * Giveaway prize. - * @type {string} - */ - prize; - /** - * Giveaway time. - * @type {string} - */ - time; - /** - * Giveaway state. - * @type {GiveawayState} - */ - state; - /** - * Number of possible winnersIDs in the giveaway. - * @type {number} - */ - winnersCount; - /** - * Giveaway start timestamp. - * @type {number} - */ - startTimestamp; - /** - * Giveaway end timestamp. - * @type {number} - */ - endTimestamp; - /** - * Timestamp when the giveaway was ended. - * @type {number} - */ - endedTimestamp; - /** - * Giveaway message ID. - * @type {DiscordID} - */ - messageID; - /** - * Giveaway message URL. - * @type {string} - */ - messageURL; - /** - * Guild where the giveaway was created. - * @type {Guild} - */ - guild; - /** - * User who created the giveaway. - * @type {User} - */ - host; - /** - * Channel where the giveaway was created. - * @type {TextChannel} - */ - channel; - /** - * Number of users who have joined the giveaway. - * @type {number} - */ - entriesCount; - /** - * Set of IDs of users who have joined the giveaway. - * @type {Set>} - */ - entries = new Set(); - /** - * Array of used ID who have won in the giveaway. - * - * Don't confuse this property with `winnersCount`, the setting that dertermines how many users can win in the giveaway. - * @type {Array>} - */ - winners = []; - /** - * Determines if the giveaway was ended in database. - * @type {boolean} - */ - isEnded; - /** - * An object with conditions for members to join the giveaway. - * @type {?IParticipantsFilter} - */ - participantsFilter = {}; - /** - * Message data properties for embeds and buttons. - * @type {?IGiveawayMessageProps} - */ - messageProps; - /** - * Giveaway constructor. - * @param {Giveaways} giveaways {@link Giveaways} instance. - * @param {IGiveaway} giveaway Input {@link Giveaway} object. - */ - constructor(giveaways, giveaway) { - /** - * {@link Giveaways} instance. - * @type {Giveaways} - * @private - */ - this._giveaways = giveaways; - /** - * Message utils instance. - * @type {MessageUtils} - * @private - */ - this._messageUtils = new MessageUtils_1.MessageUtils(giveaways); - /** - * Input giveaway object. - * @type {IGiveaway} - */ - this._inputGiveaway = giveaway; - /** - * Giveaway ID. - * @type {number} - */ - this.id = giveaway.id; - /** - * Giveaway prize. - * @type {string} - */ - this.prize = giveaway.prize; - /** - * Giveaway time. - * @type {string} - */ - this.time = giveaway.time; - /** - * Giveaway state. - * @type {GiveawayState} - */ - this.state = giveaway.state; - /** - * Number of possible winners in the giveaway. - * @type {number} - */ - this.winnersCount = giveaway.winnersCount; - /** - * Giveaway start timestamp. - * @type {number} - */ - this.startTimestamp = giveaway.startTimestamp; - /** - * Giveaway end timestamp. - * @type {number} - */ - this.endTimestamp = giveaway.endTimestamp; - /** - * Giveaway end timestamp. - * @type {number} - */ - this.endedTimestamp = giveaway.endedTimestamp; - /** - * Giveaway message ID. - * @type {DiscordID} - */ - this.messageID = giveaway.messageID; - /** - * Guild where the giveaway was created. - * @type {Guild} - */ - this.guild = this._giveaways.client.guilds.cache.get(giveaway.guildID); - /** - * User who created the giveaway. - * @type {User} - */ - this.host = this._giveaways.client.users.cache.get(giveaway.hostMemberID); - /** - * Channel where the giveaway was created. - * @type {TextChannel} - */ - this.channel = this._giveaways.client.channels.cache.get(giveaway.channelID); - /** - * Giveaway message URL. - * @type {string} - */ - this.messageURL = giveaway.messageURL || ''; - /** - * Determines if the giveaway was ended in database. - * @type {boolean} - */ - this.isEnded = giveaway.isEnded || false; - /** - * Set of IDs of users who have joined the giveaway. - * @type {Set>} - */ - this.entries = new Set(giveaway.entries); - /** - * Array of used ID who have won in the giveaway. - * - * Don't confuse this property with `winnersCount`, - * the setting that dertermines how many users can win in the giveaway. - * @type {Array>} - */ - this.winners = giveaway.winners || []; - /** - * Number of users who have joined the giveaway. - * @type {number} - */ - this.entriesCount = giveaway.entries.length; - /** - * An object with conditions for members to join the giveaway. - * @type {?IParticipantsFilter} - */ - this.participantsFilter = giveaway.participantsFilter; - /** - * Message data properties for embeds and buttons. - * @type {IGiveawayMessageProps} - */ - this.messageProps = giveaway.messageProps || { - embeds: { - start: {}, - joinGiveawayMessage: {}, - leaveGiveawayMessage: {}, - finish: { - endMessage: {}, - newGiveawayMessage: {}, - noWinnersNewGiveawayMessage: {}, - noWinnersEndMessage: {} - }, - reroll: { - newGiveawayMessage: {}, - onlyHostCanReroll: {}, - rerollMessage: {}, - successMessage: {} - }, - restrictionsMessages: { - hasNoRequiredRoles: {}, - hasRestrictedRoles: {}, - memberRestricted: {} - } - }, - buttons: { - joinGiveawayButton: {}, - goToMessageButton: {}, - rerollButton: {} - } - }; - } - /** - * Determines if the giveaway's time is up or if the giveaway was ended forcefully. - * @type {boolean} - */ - get isFinished() { - return (this.state !== giveaway_interface_1.GiveawayState.STARTED || Date.now() > this.endTimestamp * 1000) || this.isEnded; - } - /** - * Raw giveaway object. - * @type {IGiveaway} - */ - get raw() { - const entries = [...this.entries]; - this._inputGiveaway.entries = entries; - return this._inputGiveaway; - } - /** - * [TYPE GUARD FUNCTION] - Determines if the giveaway is running - * and allows to perform actions if it is. - * @returns {boolean} Whether the giveaway is running. - * - * @example - * - * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) - * - * // we don't know if the giveaway is running, - * // so the method is unsafe to run - `extend` will be marked as "possibly undefined" - * // to prevent it from running before the check below - * giveaway.extend('10s') - * - * // checking if the giveaway is running - * if (!giveaway.isRunning()) { - * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) - * } - * - * giveaway.extend('10s') // we know that giveaway is running - the method is safe to run - */ - isRunning() { - return !this.isFinished; - } - /** - * Restarts the giveaway. - * @returns {Promise} - */ - async restart() { - const { giveawayIndex } = this._getFromCache(this.guild.id); - this.isEnded = false; - this.raw.isEnded = false; - this.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(this.time)) / 1000); - this.raw.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(this.time)) / 1000); - const strings = this.messageProps; - const startEmbedStrings = strings?.embeds.start || {}; - const embed = this._messageUtils.buildGiveawayEmbed(this.raw, startEmbedStrings); - const buttonsRow = this._messageUtils.buildButtonsRow(strings?.buttons.joinGiveawayButton || {}); - const message = await this.channel.messages.fetch(this.messageID); - this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); - message.edit({ - content: startEmbedStrings?.messageContent, - embeds: Object.keys(startEmbedStrings).length == 1 - && startEmbedStrings?.messageContent ? [] : [embed], - components: [buttonsRow] - }); - this._giveaways.emit('giveawayRestart', this); - } - /** - * Extends the giveaway length. - * - * [!!!] To be able to run this method, you need to perform a type-guard check - * - * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) - * - * @param {string} extensionTime The time to extend the giveaway length by. - * @returns {Promise} - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid, `INVALID_TIME` - if invalid time string was specified, - * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. - * - * @example - * - * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) - * - * // we don't know if the giveaway is running, - * // so the method is unsafe to run - `extend` will be marked as "possibly undefined" - * // to prevent it from running before the check below - * giveaway.extend('10s') - * - * // checking if the giveaway is running - * if (!giveaway.isRunning()) { - * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) - * } - * - * giveaway.extend('10s') // we know that giveaway is running - the method is safe to run - */ - async extend(extensionTime) { - const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id); - if (!extensionTime) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('extensionTime', 'Giveaways.extend'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof extensionTime !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('extensionTime', 'string', extensionTime), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (this.isEnded) { - throw new GiveawaysError_1.GiveawaysError('Cannot extend the giveaway\'s length: ' - + GiveawaysError_1.errorMessages.GIVEAWAY_ALREADY_ENDED(giveaway.prize, giveaway.id), GiveawaysError_1.GiveawaysErrorCodes.GIVEAWAY_ALREADY_ENDED); - } - this.endTimestamp = this.endTimestamp + this._timeToSeconds(extensionTime); - this.raw.endTimestamp = this.endTimestamp + this._timeToSeconds(extensionTime); - const strings = this.messageProps; - const startEmbedStrings = strings?.embeds.start || {}; - const embed = this._messageUtils.buildGiveawayEmbed(this.raw, startEmbedStrings); - const buttonsRow = this._messageUtils.buildButtonsRow(strings?.buttons.joinGiveawayButton || {}); - const message = await this.channel.messages.fetch(this.messageID); - this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); - message.edit({ - content: startEmbedStrings?.messageContent, - embeds: Object.keys(startEmbedStrings).length == 1 - && startEmbedStrings?.messageContent ? [] : [embed], - components: [buttonsRow] - }); - this._giveaways.emit('giveawayLengthExtend', { - time: extensionTime, - giveaway: this - }); - } - /** - * Reduces the giveaway length. - * - * [!!!] To be able to run this method, you need to perform a type-guard check - * - * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) - * - * @param {string} reductionTime The time to reduce the giveaway length by. - * @returns {Promise} - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid, `INVALID_TIME` - if invalid time string was specified, - * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. - * - * @example - * - * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) - * - * // we don't know if the giveaway is running, - * // so the method is unsafe to run - `reduce` will be marked as "possibly undefined" - * // to prevent it from running before the check below - * giveaway.reduce('10s') - * - * // checking if the giveaway is running - * if (!giveaway.isRunning()) { - * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) - * } - * - * giveaway.reduce('10s') // we know that giveaway is running - the method is safe to run - */ - async reduce(reductionTime) { - const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id); - if (!reductionTime) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('reductionTime', 'Giveaways.extend'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof reductionTime !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('reductionTime', 'string', reductionTime), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (this.isEnded) { - throw new GiveawaysError_1.GiveawaysError('Cannot reduce the giveaway\'s length: ' - + GiveawaysError_1.errorMessages.GIVEAWAY_ALREADY_ENDED(giveaway.prize, giveaway.id), GiveawaysError_1.GiveawaysErrorCodes.GIVEAWAY_ALREADY_ENDED); - } - this.endTimestamp = this.endTimestamp - this._timeToSeconds(reductionTime); - this.raw.endTimestamp = this.endTimestamp - this._timeToSeconds(reductionTime); - const strings = this.messageProps; - const startEmbedStrings = strings?.embeds.start || {}; - const embed = this._messageUtils.buildGiveawayEmbed(this.raw, startEmbedStrings); - const buttonsRow = this._messageUtils.buildButtonsRow(strings?.buttons.joinGiveawayButton || {}); - const message = await this.channel.messages.fetch(this.messageID); - this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); - message.edit({ - content: startEmbedStrings?.messageContent, - embeds: Object.keys(startEmbedStrings).length == 1 - && startEmbedStrings?.messageContent ? [] : [embed], - components: [buttonsRow] - }); - this._giveaways.emit('giveawayLengthReduce', { - time: reductionTime, - giveaway: this - }); - } - /** - * Ends the giveaway. - * - * [!!!] To be able to run this method, you need to perform a type-guard check - * - * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) - * - * @returns {Promise} - * - * @throws {GiveawaysError} `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. - * - * @example - * - * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) - * - * // we don't know if the giveaway is running, - * // so the method is unsafe to run - `end` will be marked as "possibly undefined" - * // to prevent it from running before the check below - * giveaway.end() - * - * // checking if the giveaway is running - * if (!giveaway.isRunning()) { - * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) - * } - * - * giveaway.end() // we know that giveaway is running - the method is safe to run - */ - async end() { - const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id); - const winnersIDs = this._pickWinners(giveaway); - if (this.isEnded) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.GIVEAWAY_ALREADY_ENDED(giveaway.prize, giveaway.id), GiveawaysError_1.GiveawaysErrorCodes.GIVEAWAY_ALREADY_ENDED); - } - const endedTimestamp = Date.now(); - this.isEnded = true; - this.raw.isEnded = true; - this.winners = winnersIDs.map(winnerID => winnerID.slice(2, -1)); - this.raw.winners = winnersIDs.map(winnerID => winnerID.slice(2, -1)); - this.endedTimestamp = endedTimestamp; - this.raw.endedTimestamp = endedTimestamp; - this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); - this._messageUtils.editFinishGiveawayMessage(this.raw, winnersIDs, this.messageProps?.embeds.finish?.newGiveawayMessage); - this._giveaways.emit('giveawayEnd', this); - } - /** - * Redraws the giveaway winners - * @returns {Promise} Rerolled winners users IDs. - */ - async reroll() { - const { giveaway, giveawayIndex } = this._getFromCache(this.guild.id); - const winnersIDs = this._pickWinners(giveaway); - const rerollEmbedStrings = giveaway.messageProps?.embeds?.reroll; - const rerollMessage = rerollEmbedStrings?.rerollMessage || {}; - for (const key in rerollMessage) { - rerollMessage[key] = (0, giveawayTemplate_1.replaceGiveawayKeys)(rerollMessage[key], this, winnersIDs); - } - const rerolledEmbed = this._messageUtils.buildGiveawayEmbed(this.raw, rerollMessage, winnersIDs); - const giveawayMessage = await this.channel.messages.fetch(this.messageID); - this.winners = winnersIDs.map(winnerID => winnerID.slice(2, -1)); - this.raw.winners = winnersIDs.map(winnerID => winnerID.slice(2, -1)); - this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); - this._messageUtils.editFinishGiveawayMessage(this.raw, winnersIDs, rerollEmbedStrings?.newGiveawayMessage, false, rerollEmbedStrings?.successMessage); - giveawayMessage.reply({ - content: rerollMessage?.messageContent, - embeds: Object.keys(rerollMessage).length === 1 && rerollMessage?.messageContent ? [] : [rerolledEmbed] - }); - this._giveaways.emit('giveawayReroll', { - newWinners: winnersIDs, - giveaway: this - }); - return winnersIDs; - } - /** - * Adds the user ID into the giveaway entries. - * @param {DiscordID} guildID The guild ID where the giveaway is hosted. - * @param {DiscordID} userID The user ID to add. - * @returns {IGiveaway} Updated giveaway object. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid. - */ - addEntry(guildID, userID) { - if (!guildID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaway.addEntry'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (!userID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('userID', 'Giveaway.addEntry'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof guildID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (typeof userID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('userID', 'string', userID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - const { giveaway, giveawayIndex } = this._getFromCache(guildID); - this.entries.add(userID); - giveaway.entries.push(userID); - giveaway.entriesCount = this.entries.size; - this._giveaways.database.pull(`${guildID}.giveaways`, giveawayIndex, this.raw); - return giveaway; - } - /** - * Adds the user ID into the giveaway entries. - * @param {DiscordID} guildID The guild ID where the giveaway is hosted. - * @param {DiscordID} userID The user ID to add. - * @returns {IGiveaway} Updated giveaway object. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid. - */ - removeEntry(guildID, userID) { - if (!guildID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaway.removeEntry'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (!userID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('userID', 'Giveaway.removeEntry'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof guildID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (typeof userID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('userID', 'string', userID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - const { giveaway, giveawayIndex } = this._getFromCache(guildID); - this.entries.delete(userID); - giveaway.entries.splice(giveaway.entries.indexOf(userID), 1); - giveaway.entriesCount = this.entries.size; - this.sync(giveaway); - this._giveaways.database.pull(`${guildID}.giveaways`, giveawayIndex, this.raw); - return giveaway; - } - /** - * Changes the giveaway's prize and edits the giveaway message. - * - * [!!!] To be able to run this method, you need to perform a type-guard check - * - * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) - * - * @param {string} prize The new prize to set. - * @returns {Promise>} Updated {@link Giveaway} instance. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid, - * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. - * - * @example - * - * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) - * - * // we don't know if the giveaway is running, - * // so the method is unsafe to run - `setPrize` will be marked as "possibly undefined" - * // to prevent it from running before the check below - * giveaway.setPrize('My New Prize') - * - * // checking if the giveaway is running - * if (!giveaway.isRunning()) { - * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) - * } - * - * giveaway.setPrize('My New Prize') // we know that giveaway is running - the method is safe to run - */ - async setPrize(prize) { - if (!prize) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('prize', 'Giveaways.setPrize'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof prize !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('prize', 'string', prize), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - return this.edit('prize', prize); - } - /** - * Changes the giveaway's winners count and edits the giveaway message. - * - * [!!!] To be able to run this method, you need to perform a type-guard check - * - * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) - * - * @param {string} winnersCount The new winners count to set. - * @returns {Promise>} Updated {@link Giveaway} instance. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid, `INVALID_INPUT` - when the input value is bad or invalid, - * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. - * - * @example - * - * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) - * - * // we don't know if the giveaway is running, - * // so the method is unsafe to run - `setWinnersCount` will be marked as "possibly undefined" - * // to prevent it from running before the check below - * giveaway.setWinnersCount(2) - * - * // checking if the giveaway is running - * if (!giveaway.isRunning()) { - * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) - * } - * - * giveaway.setWinnersCount(2) // we know that giveaway is running - the method is safe to run - */ - async setWinnersCount(winnersCount) { - if (winnersCount == null || winnersCount == undefined) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('winnersCount', 'Giveaways.setWinnersCount'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (isNaN(winnersCount)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('winnersCount', 'number', winnersCount.toString()), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (winnersCount <= 0) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_INPUT('winnersCount', 'Giveaways.setWinnersCount', 'winners count cannot be less or equal 0'), GiveawaysError_1.GiveawaysErrorCodes.INVALID_INPUT); - } - return this.edit('winnersCount', winnersCount); - } - /** - * Changes the giveaway's host member ID and edits the giveaway message. - * - * [!!!] To be able to run this method, you need to perform a type-guard check - * - * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) - * - * @param {DiscordID} hostMemberID The new host member ID to set. - * @returns {Promise>} Updated {@link Giveaway} instance. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid, - * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. - * - * @example - * - * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) - * - * // we don't know if the giveaway is running, - * // so the method is unsafe to run - `setHostMemberID` will be marked as "possibly undefined" - * // to prevent it from running before the check below - * giveaway.setHostMemberID('123456789012345678') - * - * // checking if the giveaway is running - * if (!giveaway.isRunning()) { - * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) - * } - * - * giveaway.setHostMemberID('123456789012345678') // we know that giveaway is running - the method is safe to run - */ - async setHostMemberID(hostMemberID) { - if (!hostMemberID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('hostMemberID', 'Giveaways.setHostMemberID'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof hostMemberID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('hostMemberID', 'string', hostMemberID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - return this.edit('hostMemberID', hostMemberID); - } - /** - * Changes the giveaway's time and edits the giveaway message. - * - * [!!!] To be able to run this method, you need to perform a type-guard check - * - * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) - * - * @param {string} time The new time to set. - * @returns {Promise>} Updated {@link Giveaway} instance. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid, - * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. - * - * @example - * - * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) - * - * // we don't know if the giveaway is running, - * // so the method is unsafe to run - `setTime` will be marked as "possibly undefined" - * // to prevent it from running before the check below - * giveaway.setTime('10s') - * - * // checking if the giveaway is running - * if (!giveaway.isRunning()) { - * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) - * } - * - * giveaway.setTime('10s') // we know that giveaway is running - the method is safe to run - */ - async setTime(time) { - if (!time) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('time', 'Giveaways.setTime'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof time !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('time', 'string', time), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - return this.edit('time', time); - } - /** - * Sets the specified value to the specified giveaway property and edits the giveaway message. - * - * Type parameters: - * - * - `TProperty` ({@link EditableGiveawayProperties}) - Giveaway property to pass in. - * - * [!!!] To be able to run this method, you need to perform a type-guard check - * - * [!!!] using the {@link Giveaway.isRunning()} method. (see the example below) - * - * @param {string} key The key of the giveaway object to set. - * @param {string} value The value to set. - * @returns {Promise>} Updated {@link Giveaway} instance. - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid, - * `GIVEAWAY_ALREADY_ENDED` - if the target giveaway has already ended. - * - * @template TProperty Giveaway property to pass in. - * - * @example - * - * const giveaway = giveaways.get(parseInt(giveawayOrMessageID)) || giveaways.find(giveaway => giveaway.id == giveawayID) - * - * // we don't know if the giveaway is running, - * // so the method is unsafe to run - `edit` will be marked as "possibly undefined" - * // to prevent it from running before the check below - * giveaway.edit('prize', 'My New Prize') - * - * // checking if the giveaway is running - * if (!giveaway.isRunning()) { - * return console.log(`Giveaway "${giveaway.prize}" has already ended.`) - * } - * - * giveaway.edit('prize', 'My New Prize') // we know that giveaway is running - the method is safe to run - */ - async edit(key, value) { - const { giveawayIndex } = this._getFromCache(this.guild.id); - if (!key) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('key', 'Giveaways.edit'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (!value) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('value', 'Giveaways.edit'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof key !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('key', 'string', key), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - if (this.isEnded) { - throw new GiveawaysError_1.GiveawaysError('Cannot edit the giveaway: ' - + GiveawaysError_1.errorMessages.GIVEAWAY_ALREADY_ENDED(this.prize, this.id), GiveawaysError_1.GiveawaysErrorCodes.GIVEAWAY_ALREADY_ENDED); - } - const strings = this.messageProps; - const startEmbedStrings = strings?.embeds.start || {}; - const oldRawGiveaway = { ...this.raw }; - const oldValue = oldRawGiveaway[key]; - if (key == 'hostMemberID') { - const newGiveawayHostUserID = value; - const newGiveawayHost = this._giveaways.client.users.cache.get(newGiveawayHostUserID); - if (!newGiveawayHost) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.USER_NOT_FOUND(newGiveawayHostUserID), GiveawaysError_1.GiveawaysErrorCodes.USER_NOT_FOUND); - } - for (const key in startEmbedStrings) { - if (typeof startEmbedStrings[key] == 'string') { - startEmbedStrings[key] = startEmbedStrings[key] - .replaceAll(this.host.username, newGiveawayHost.username) - .replaceAll(this.host.discriminator, newGiveawayHost.discriminator) - .replaceAll(this.host.tag, newGiveawayHost.tag) - .replaceAll(this.host.avatar, newGiveawayHost.avatar) - .replaceAll(this.host.defaultAvatarURL, newGiveawayHost.defaultAvatarURL) - .replaceAll(this.host.bot, newGiveawayHost.bot) - .replaceAll(this.host.system, newGiveawayHost.system) - .replaceAll(this.host.banner, newGiveawayHost.banner) - .replaceAll(this.host.createdAt, newGiveawayHost.createdAt) - .replaceAll(this.host.createdTimestamp, newGiveawayHost.createdTimestamp) - .replaceAll(this.host.id, newGiveawayHost.id); - } - } - this.host = newGiveawayHost; - this.raw.hostMemberID = newGiveawayHostUserID; - } - else if (key == 'time') { - const time = value; - this.time = time; - this.raw.time = time; - this.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(time)) / 1000); - this.raw.endTimestamp = Math.floor((Date.now() + (0, time_function_1.convertTimeToMilliseconds)(time)) / 1000); - } - else { - const that = this; - that[key] = value; - this.raw[key] = value; - } - const embed = this._messageUtils.buildGiveawayEmbed(this.raw, startEmbedStrings); - const buttonsRow = this._messageUtils.buildButtonsRow(strings?.buttons.joinGiveawayButton || {}); - const message = await this.channel.messages.fetch(this.messageID); - this._giveaways.database.pull(`${this.guild.id}.giveaways`, giveawayIndex, this.raw); - message.edit({ - content: startEmbedStrings?.messageContent, - embeds: Object.keys(startEmbedStrings).length == 1 - && startEmbedStrings?.messageContent ? [] : [embed], - components: [buttonsRow] - }); - this._giveaways.emit('giveawayEdit', { - key, - oldValue, - newValue: value, - giveaway: this - }); - return this; - } - /** - * Deletes the giveaway from database and deletes its message. - * @returns {Promise>} Deleted {@link Giveaway} instance. - */ - async delete() { - const { giveawayIndex } = this._getFromCache(this.guild.id); - const giveawayMessage = await this.channel.messages.fetch(this.messageID); - if (giveawayMessage.deletable) { - giveawayMessage.delete(); - } - else { - giveawayMessage.edit({ - content: '', - embeds: [], - components: [] - }); - } - this._giveaways.database.pop(`${this.guild.id}.giveaways`, giveawayIndex); - return this; - } - /** - * Syncs the constructor properties with specified raw giveaway object. - * @param {IGiveaway} giveaway Giveaway object to sync the constructor properties with. - * @returns {void} - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid. - */ - sync(giveaway) { - const that = this; - const specifiedGiveaway = giveaway; - if (!giveaway) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('giveaway', 'Giveaway.sync'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof giveaway !== 'object') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('giveaway', 'object', giveaway), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - for (const key in giveaway) { - that[key] = specifiedGiveaway[key]; - if (key == 'entries') { - that[key] = new Set(specifiedGiveaway[key]); - } - } - for (const key in giveaway) { - that.raw[key] = specifiedGiveaway[key]; - if (key == 'entries') { - that[key] = new Set(specifiedGiveaway[key]); - } - } - } - /** - * Shuffles all the giveaway entries, randomly picks the winner user IDs and converts them into mentions. - * @param {IGiveaway} [giveawayToSyncWith] The giveaway object to sync the {@link Giveaway} instance with. - * @returns {string[]} Array of mentions of users who were picked as the winners. - * @private - */ - _pickWinners(giveawayToSyncWith) { - const winnersIDs = []; - if (giveawayToSyncWith) { - this.sync(giveawayToSyncWith); - } - if (!this.entries.size) { - return []; - } - const shuffledEntries = this._shuffleArray([...this.entries]); - for (let i = 0; i < this.winnersCount; i++) { - const recursiveShuffle = () => { - const randomEntryIndex = Math.floor(Math.random() * shuffledEntries.length); - const winnerUserID = shuffledEntries[randomEntryIndex]; - if (winnersIDs.includes(winnerUserID)) { - if (winnersIDs.length !== this.entries.size) { - recursiveShuffle(); - } - } - else { - winnersIDs.push(winnerUserID); - } - }; - recursiveShuffle(); - } - return winnersIDs.map(winnerID => `<@${winnerID}>`); - } - /** - * Shuffles an array and returns it. - * - * Type parameters: - * - * - `T` - The type of array to shuffle. - * - * @param {any[]} arrayToShuffle The array to shuffle. - * @returns {any[]} Shuffled array. - * @private - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid. - * - * @template T The type of array to shuffle. - */ - _shuffleArray(arrayToShuffle) { - if (!arrayToShuffle) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('arrayToShuffle', 'Giveaway._shuffleArray'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (!Array.isArray(arrayToShuffle)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('arrayToShuffle', 'array', arrayToShuffle), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - const shuffledArray = [...arrayToShuffle]; - for (let i = shuffledArray.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]]; - } - return shuffledArray; - } - /** - * Gets the giveaway data and its index in guild giveaways array from database. - * @param {DiscordID} guildID Guild ID to get the giveaways array from. - * @returns {IDatabaseArrayGiveaway} Database giveaway object. - * @private - * - * @throws {GiveawaysError} `REQUIRED_ARGUMENT_MISSING` - when required argument is missing, - * `INVALID_TYPE` - when argument type is invalid. - */ - _getFromCache(guildID) { - if (!guildID) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.REQUIRED_ARGUMENT_MISSING('guildID', 'Giveaway._getFromCache'), GiveawaysError_1.GiveawaysErrorCodes.REQUIRED_ARGUMENT_MISSING); - } - if (typeof guildID !== 'string') { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TYPE('guildID', 'string', guildID), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TYPE); - } - const giveaways = this._giveaways.database.get(`${guildID}.giveaways`) || []; - const giveawayIndex = giveaways.findIndex(giveaway => giveaway.id == this.id); - const giveaway = giveaways[giveawayIndex]; - this.sync(giveaway); - return { - giveaway, - giveawayIndex - }; - } - /** - * Converts the time string into seconds. - * @param {string} time The time string to convert. - * @returns {number} Converted time string into seconds. - * @private - * - * @throws {GiveawaysError} `INVALID_TIME` - if invalid time string was specified. - */ - _timeToSeconds(time) { - try { - const milliseconds = (0, time_function_1.convertTimeToMilliseconds)(time); - return Math.floor(milliseconds / 1000 / 2); - } - catch { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.GiveawaysErrorCodes.INVALID_TIME); - } - } - /** - * Converts the {@link Giveaway} instance to a plain object representation. - * @returns {IGiveaway} Plain object representation of {@link Giveaway} instance. - */ - toJSON() { - return this.raw; - } -} -exports.Giveaway = Giveaway; diff --git a/dist/src/lib/giveaway.interface.js b/dist/src/lib/giveaway.interface.js deleted file mode 100644 index d2d7ad9..0000000 --- a/dist/src/lib/giveaway.interface.js +++ /dev/null @@ -1,14 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.GiveawayState = void 0; -/** - * An enum that determines the state of a giveaway. - * @typedef {number} GiveawayState - * @prop {number} STARTED The giveaway has started. - * @prop {number} ENDED The giveaway has ended. - */ -var GiveawayState; -(function (GiveawayState) { - GiveawayState[GiveawayState["STARTED"] = 1] = "STARTED"; - GiveawayState[GiveawayState["ENDED"] = 2] = "ENDED"; -})(GiveawayState || (exports.GiveawayState = GiveawayState = {})); diff --git a/dist/src/lib/managers/CacheManager.js b/dist/src/lib/managers/CacheManager.js deleted file mode 100644 index 00ab223..0000000 --- a/dist/src/lib/managers/CacheManager.js +++ /dev/null @@ -1,135 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CacheManager = void 0; -const typeOf_function_1 = require("../util/functions/typeOf.function"); -/** - * Cache manager class. - * - * Type parameters: - * - * - `K` ({@link any}) - The cache map key type. - * - `V` ({@link any}) - The cache map value type. - * - * @template K The cache map key type. - * @template V The cache map value type. - */ -class CacheManager { - /** - * Database cache. - * @type {Map} - * @private - */ - _cache; - /** - * Cache manager constructor. - */ - constructor() { - /** - * Database cache. - * @type {Map} - * @private - */ - this._cache = new Map(); - } - /** - * Gets the cache map as an object. - * - * Type parameters: - * - * - `V` ({@link any}) - The type of cache object to return. - * - * @returns {any} Object representation of the cache map. - * @template V The type of cache object to return. - */ - getCacheObject() { - const mapData = {}; - for (const [key, value] of this._cache.entries()) { - mapData[key] = value; - } - return mapData; - } - /** - * Parses the key and fetches the value from cache map. - * - * Type parameters: - * - * - `V` ({@link any}) - The type of data being returned. - * - * @param {K} key The key in cache map. - * @returns {V} The data from cache map. - * - * @template V The type of data being returned. - */ - get(key) { - let data = this.getCacheObject(); - let parsedData = data; - const keys = key.split('.'); - for (let i = 0; i < keys.length; i++) { - if (keys.length - 1 == i) { - data = parsedData?.[keys[i]] || null; - } - parsedData = parsedData?.[keys[i]]; - } - return data; - } - /** - * Parses the key and sets the value in cache map. - * - * Type parameters: - * - * - `TValue` ({@link any}) - The type of data being set. - * - `R` ({@link any}) - The type of data being returned. - * - * @param {K} key The key in cache map. - * @returns {R} The data from cache map. - * - * @template TValue The type of data being set. - * @template R The type of data being returned. - */ - set(key, value) { - const data = this.getCacheObject(); - let updatedData = data; - const keys = key.split('.'); - for (let i = 0; i < keys.length; i++) { - if (keys.length - 1 == i) { - updatedData[keys[i]] = value; - } - else if (!(0, typeOf_function_1.isObject)(data[keys[i]])) { - updatedData[keys[i]] = {}; - } - updatedData = updatedData?.[keys[i]]; - } - this._cache.set(keys[0], data[keys[0]]); - return data; - } - /** - * Parses the key and deletes it from cache map. - * @param {K} key The key in cache map. - * @returns {boolean} `true` if deleted successfully. - */ - delete(key) { - const data = this.getCacheObject(); - let updatedData = data; - const keys = key.split('.'); - for (let i = 0; i < keys.length; i++) { - if (keys.length - 1 == i) { - delete updatedData[keys[i]]; - } - else if (!(0, typeOf_function_1.isObject)(data[keys[i]])) { - updatedData[keys[i]] = {}; - } - updatedData = updatedData?.[keys[i]]; - } - this._cache.set(keys[0], data[keys[0]]); - return true; - } - /** - * Clears the cache. - * @returns {boolean} `true` if cleared successfully. - */ - clear() { - this._cache.clear(); - return true; - } -} -exports.CacheManager = CacheManager; diff --git a/dist/src/lib/managers/DatabaseManager.js b/dist/src/lib/managers/DatabaseManager.js deleted file mode 100644 index 77de522..0000000 --- a/dist/src/lib/managers/DatabaseManager.js +++ /dev/null @@ -1,638 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.DatabaseManager = void 0; -const databaseType_enum_1 = require("../../types/databaseType.enum"); -const GiveawaysError_1 = require("../util/classes/GiveawaysError"); -const JSONParser_1 = require("../util/classes/JSONParser"); -const Logger_1 = require("../util/classes/Logger"); -const CacheManager_1 = require("./CacheManager"); -/** - * Database manager class. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - The database type that will determine - * which connection configuration should be used. - * - * - `TKey` ({@link string}) - The type of database key that will be used. - * - `TValue` ({@link any}) - The type of database values that will be used. - * - * @template TDatabaseType - * The database type that will determine which connection configuration should be used. - * @template TKey The type of database key that will be used. - * @template TValue The type of database values that will be used. - */ -class DatabaseManager { - /** - * Database cache manager. - * @type {CacheManager} - * @private - */ - _cache; - /** - * Giveaways logger. - * @type {Logger} - * @private - */ - _logger; - /** - * {@link Giveaways} instance. - * @type {Giveaways} - */ - giveaways; - /** - * Database instance. - * @type {Database} - */ - db; - /** - * Database type. - * @type {DatabaseType} - */ - databaseType; - /** - * JSON parser instance. - * @type {JSONParser} - */ - jsonParser; - /** - * Database manager constructor. - * @param {Giveaways} giveaways {@link Giveaways} instance. - */ - constructor(giveaways) { - /** - * Database cache. - * @type {CacheManager} - * @private - */ - this._cache = new CacheManager_1.CacheManager(); - /** - * Giveaways logger. - * @type {Logger} - * @private - */ - this._logger = new Logger_1.Logger(giveaways.options.debug); - /** - * {@link Giveaways} instance. - * @type {Giveaways} - */ - this.giveaways = giveaways; - /** - * Database instance. - * @type {Database} - */ - this.db = giveaways.db; - /** - * Database type. - * @type {DatabaseType} - */ - this.databaseType = giveaways.options.database; - /** - * JSON parser instance. - * @type {?JSONParser} - */ - this.jsonParser = null; - this._init(); - } - /** - * Initializes the database manager. - * @returns {Promise} - */ - async _init() { - if (this.isJSON()) { - this.jsonParser = new JSONParser_1.JSONParser(this.giveaways.options.connection.path); - } - this._logger.debug('Loading the cache...'); - await this._loadCache(); - } - /** - * Evaluates a database operation ands sends a debug log in the console. - * - * Type parameters: - * - * - `F` ({@link Function}) - The function type to be passed as database operation callback. - * - * @param {string} operation The database operation to put in the debug log. - * @param {string} key The key of the database the operation was performed on. - * @param {Function} toDebug The database operation callback function to call. - * @param {boolean} [sendDebugLog=true] Whether the debug log should be sent in the console if debug mode is enabled. - * - * @returns {Promise>>>} - * Return type of the database callback operation function. - * - * @template F The function type to be passed as database operation callback. - * @private - */ - async _debug(operation, key, toDebug, sendDebugLog = true) { - try { - const callbackResult = await toDebug(); - if (this.giveaways.options.debug && sendDebugLog) { - this._logger.debug(`Performed "${operation}" operation on key "${key}".`); - } - return { - error: null, - result: callbackResult - }; - } - catch (err) { - if (this.giveaways.options.debug && sendDebugLog) { - this._logger.error(`Failed to perform "${operation}" operation on key "${key}": ${err.stack}`); - } - return { - error: err, - result: null - }; - } - } - /** - * [TYPE GUARD FUNCTION] - Determines if the databse type is JSON. - * @returns {boolean} Whether the database type is JSON. - */ - isJSON() { - return this.giveaways.options.database == databaseType_enum_1.DatabaseType.JSON; - } - /** - * [TYPE GUARD FUNCTION] - Determines if the databse type is MongoDB. - * @returns {boolean} Whether the database type is MongoDB. - */ - isMongoDB() { - return this.giveaways.options.database == databaseType_enum_1.DatabaseType.MONGODB; - } - /** - * [TYPE GUARD FUNCTION] - Determines if the databse type is Enmap. - * @returns {boolean} Whether the database type is Enmap. - */ - isEnmap() { - return this.giveaways.options.database == databaseType_enum_1.DatabaseType.ENMAP; - } - /** - * Gets the object keys in database root or in object by specified key. - * @param {TKey} [key] The key in database. Omitting this argument will get the keys from the root of database. - * @returns {string[]} Database object keys array. - */ - getKeys(key) { - const database = key == undefined - ? this.all() - : this.get(key); - return Object.keys(database); - } - /** - * Gets the value from the **cache** by specified key. - * - * Type parameters: - * - * - `V` - The type of data being returned. - * - * @param {TKey} key The key in database. - * @returns {V} Value from database. - * - * @template V The type of data being returned. - */ - get(key) { - const data = this._cache.get(key); - return data; - } - /** - * Gets the value from **database** by specified key. - * @param {TKey} key The key in database. - * @returns {V} Value from database. - * @template V The type that represents the returning value in the method. - */ - async getFromDatabase(key) { - if (this.isJSON()) { - const data = await this.jsonParser.get(key); - return data; - } - if (this.isMongoDB()) { - const data = await this.db.get(key); - return data; - } - if (this.isEnmap()) { - const data = this.db.get(key); - return data; - } - return {}; - } - /** - * Gets the value from the **cache** by specified key. - * - * - This method is an alias to {@link DatabaseManager.get()} method. - * - * Type parameters: - * - * - `V` - The type of data being returned. - * - * @param {TKey} key The key in database. - * @returns {V} Value from database. - * - * @template V The type of data being returned. - */ - fetch(key) { - return this.get(key); - } - /** - * Determines if specified key exists in database. - * @param {TKey} key The key in database. - * @returns {boolean} Boolean value that determines if specified key exists in database. - */ - has(key) { - const data = this.get(key); - return !!data; - } - /** - * Determines if specified key exists in database. - * - * - This method is an alias to {@link DatabaseManager.has()} method. - * - * @param {TKey} key The key in database. - * @returns {boolean} Boolean value that determines if specified key exists in database. - */ - includes(key) { - return this.has(key); - } - /** - * Sets data in database. - * - * Type parameters: - * - * - `V` - The type of data being set. - * - `R` - The type of data being returned. - * - * @param {TKey} key The key in database. - * @param {V} value Any data to set. - * @param {boolean} [sendDebugLog=true] Whether the debug log should be sent in the console if debug mode is enabled. - * @returns {Promise>} The data from the database. - * - * @template V The type of data being set. - * @template R The type of data being returned. - */ - async set(key, value, sendDebugLog = true) { - return this._debug('set', key, async () => { - this._cache.set(key, value); - if (this.isJSON()) { - const data = await this.jsonParser.set(key, value); - return data; - } - if (this.isMongoDB()) { - const data = await this.db.set(key, value); - return data; - } - if (this.isEnmap()) { - this.db.set(key, value); - const data = this.db.get(key); - return data; - } - return {}; - }, sendDebugLog); - } - /** - * Clears the database. - * @param {boolean} [sendDebugLog=true] Whether the debug log should be sent in the console if debug mode is enabled. - * @returns {Promise} `true` if cleared successfully, `false` otherwise. - */ - async clear(sendDebugLog = true) { - this._cache.clear(); - if (this.giveaways.options.debug && sendDebugLog) { - this._logger.debug('Performed "clear" operation on all database.'); - } - if (this.isJSON()) { - await this.jsonParser.clearDatabase(); - return true; - } - if (this.isMongoDB()) { - await this.db.clear(); - return true; - } - if (this.isEnmap()) { - this.db.deleteAll(); - return true; - } - return false; - } - /** - * Clears the database. - * - * - This method is an alias to {@link DatabaseManager.clear()} method. - * @returns {Promise} `true` if set successfully, `false` otherwise. - */ - async deleteAll() { - if (this.giveaways.options.debug) { - this._logger.debug('Performed "deleteAll" operation on all database.'); - } - return this.clear(false); - } - /** - * Adds a number to the data in database. - * @param {TKey} key The key in database. - * @param {number} numberToAdd Any number to add. - * @returns {Promise>} `true` if added successfully, `false` otherwise. - */ - async add(key, numberToAdd) { - return this._debug('add', key, async () => { - const targetNumber = this._cache.get(key); - if (!isNaN(targetNumber)) { - this._cache.set(key, targetNumber + numberToAdd); - } - if (this.isJSON()) { - const targetNumber = await this.jsonParser.get(key); - if (isNaN(targetNumber)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - await this.jsonParser.set(key, targetNumber + numberToAdd); - return true; - } - if (this.isMongoDB()) { - const targetNumber = await this.db.get(key); - if (isNaN(targetNumber)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - await this.db.set(key, targetNumber + numberToAdd); - return true; - } - if (this.isEnmap()) { - const targetNumber = this.db.get(key); - if (isNaN(targetNumber)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - this.db.set(key, targetNumber + numberToAdd); - return true; - } - return false; - }); - } - /** - * Subtracts a number to the data in database. - * @param {TKey} key The key in database. - * @param {number} numberToSubtract Any number to subtract. - * @returns {Promise>} `true` if subtracted successfully, `false` otherwise. - */ - async subtract(key, numberToSubtract) { - return this._debug('subtract', key, async () => { - const targetNumber = this._cache.get(key); - if (!isNaN(targetNumber)) { - this._cache.set(key, targetNumber + numberToSubtract); - } - if (this.isJSON()) { - const targetNumber = await this.jsonParser.get(key); - if (isNaN(targetNumber)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - await this.jsonParser.set(key, targetNumber - numberToSubtract); - return true; - } - if (this.isMongoDB()) { - const targetNumber = await this.db.get(key); - if (isNaN(targetNumber)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - await this.db.set(key, targetNumber - numberToSubtract); - return true; - } - if (this.isEnmap()) { - const targetNumber = this.db.get(key); - if (isNaN(targetNumber)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('number', targetNumber), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - this.db.set(key, targetNumber - numberToSubtract); - return true; - } - return false; - }); - } - /** - * Deletes the data from database. - * @param {TKey} key The key in database. - * @returns {Promise>} `true` if deleted successfully, `false` otherwise. - */ - async delete(key) { - return this._debug('delete', key, async () => { - this._cache.delete(key); - if (this.isJSON()) { - await this.jsonParser.delete(key); - return true; - } - if (this.isMongoDB()) { - await this.db.delete(key); - return true; - } - if (this.isEnmap()) { - this.db.delete(key); - return true; - } - return false; - }); - } - /** - * Pushes a value into specified array in database. - * - * Type parameters: - * - * - `V` - The type of data being pushed. - * - * @param {TKey} key The key in database. - * @param {V} value Any value to push into database array. - * @returns {Promise>} `true` if pushed successfully, `false` otherwise. - * - * @template V The type of data being pushed. - */ - async push(key, value) { - return this._debug('push', key, async () => { - const targetArray = this._cache.get(key) || []; - if (Array.isArray(targetArray)) { - targetArray.push(value); - this._cache.set(key, targetArray); - } - if (this.isJSON()) { - const targetArray = await this.jsonParser.get(key) || []; - if (!Array.isArray(targetArray)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - targetArray.push(value); - await this.jsonParser.set(key, targetArray); - return true; - } - if (this.isMongoDB()) { - const targetArray = (await this.db.get(key)) || []; - if (!Array.isArray(targetArray)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - targetArray.push(value); - await this.db.set(key, targetArray); - return true; - } - if (this.isEnmap()) { - const targetArray = (this.db.get(key) || []); - if (!Array.isArray(targetArray)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - targetArray.push(value); - this.db.set(key, targetArray); - return true; - } - return false; - }); - } - /** - * Changes the specified element's value in a specified array in the database. - * - * Type parameters: - * - * - `V` - The type of data being pulled. - * - * @param {TKey} key The key in database. - * @param {number} index The index in the target array. - * @param {V} newValue The new value to set. - * @returns {Promise>} `true` if pulled successfully, `false` otherwise. - * - * @template V The type of data being pulled. - */ - async pull(key, index, newValue) { - return this._debug('pull', key, async () => { - const targetArray = this._cache.get(key) || []; - if (Array.isArray(targetArray)) { - targetArray.splice(index, 1, newValue); - this._cache.set(key, targetArray); - } - if (this.isJSON()) { - const targetArray = await this.jsonParser.get(key) || []; - if (!Array.isArray(targetArray)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - targetArray.splice(index, 1, newValue); - await this.jsonParser.set(key, targetArray); - return true; - } - if (this.isMongoDB()) { - const targetArray = (await this.db.get(key)) || []; - if (!Array.isArray(targetArray)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - targetArray.splice(index, 1, newValue); - await this.db.set(key, targetArray); - return true; - } - if (this.isEnmap()) { - const targetArray = (this.db.get(key) || []); - if (!Array.isArray(targetArray)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - targetArray.splice(index, 1, newValue); - this.db.set(key, targetArray); - return true; - } - return false; - }); - } - /** - * Removes an element from a specified array in the database. - * @param {TKey} key The key in database. - * @param {number} index The index in the target array. - * @returns {Promise>} `true` if popped successfully, `false` otherwise. - */ - async pop(key, index) { - return this._debug('pop', key, async () => { - const targetArray = this._cache.get(key) || []; - if (Array.isArray(targetArray)) { - targetArray.splice(index, 1); - this._cache.set(key, targetArray); - } - if (this.isJSON()) { - const targetArray = await this.jsonParser.get(key) || []; - if (!Array.isArray(targetArray)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - targetArray.splice(index, 1); - await this.jsonParser.set(key, targetArray); - return true; - } - if (this.isMongoDB()) { - const targetArray = (await this.db.get(key)) || []; - if (!Array.isArray(targetArray)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - targetArray.splice(index, 1); - await this.db.set(key, targetArray); - return true; - } - if (this.isEnmap()) { - const targetArray = this.db.get(key) || []; - if (!Array.isArray(targetArray)) { - throw new GiveawaysError_1.GiveawaysError(GiveawaysError_1.errorMessages.INVALID_TARGET_TYPE('array', targetArray), GiveawaysError_1.GiveawaysErrorCodes.INVALID_TARGET_TYPE); - } - targetArray.splice(index, 1); - this.db.set(key, targetArray); - return true; - } - return false; - }); - } - /** - * Gets all the data in database. - * - * Type parameters: - * - * - `V` - The type of database object to return. - * - * @returns {V} Database object. - * @template V The type of database object to return - */ - all() { - const data = this._cache.getCacheObject(); - return data; - } - /** - * Gets the whole database object by making a direct database request. - * - * Type parameters: - * - * - `V` - The type of database object to return. - * - * @returns {Promise} Database object. - * @private - * - * @template V The type of database object to return. - */ - async _allFromDatabase() { - if (this.isJSON()) { - const data = await this.jsonParser.fetchDatabaseFile(); - return data; - } - if (this.isMongoDB()) { - const data = await this.db.all(); - return data; - } - if (this.isEnmap()) { - const allData = {}; - for (const databaseKey of this.db.keys()) { - const keys = databaseKey.split('.'); - let currentObject = allData; - for (let i = 0; i < keys.length; i++) { - const currentKey = keys[i]; - if (keys.length - 1 === i) { - currentObject[currentKey] = this.db.get(databaseKey) || null; - } - else { - if (!currentObject[currentKey]) { - currentObject[currentKey] = {}; - } - currentObject = currentObject[currentKey]; - } - } - } - return allData; - } - return {}; - } - /** - * Loads the database into cache. - * @returns {Promise} - */ - async _loadCache() { - const database = await this._allFromDatabase(); - for (const guildID in database) { - const guildDatabase = database[guildID]; - this._cache.set(guildID, guildDatabase); - } - } -} -exports.DatabaseManager = DatabaseManager; diff --git a/dist/src/lib/util/classes/Emitter.js b/dist/src/lib/util/classes/Emitter.js deleted file mode 100644 index 403afd1..0000000 --- a/dist/src/lib/util/classes/Emitter.js +++ /dev/null @@ -1,70 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Emitter = void 0; -const events_1 = require("events"); -/** - * Represents an event emitter for {@link Giveaways} events. - * - * Type parameters: - * - * - `E` ({@link object}) - The object whose **keys** will be used as event names and **values** for events' return types. - * - * @template E The object whose **keys** will be used as event names and **values** for events' return types. - * @private - */ -class Emitter { - _emitter = new events_1.EventEmitter({ - captureRejections: true - }); - /** - * Listens to the event. - * - * Type parameters: - * - * - `T` (keyof E) - Event name to get the callback function type for. - * - * @param {GiveawaysEvents} event Event name. - * @param {Function} listener Callback function. - * @returns {Emitter} Emitter instance. - * - * @template T Event name to get the callback function type for. - */ - on(event, listener) { - this._emitter.on(event, listener); - return this; - } - /** - * Listens to the event only for once. - * - * Type parameters: - * - * - `T` (keyof E) - Event name to get the callback function type for. - * - * @param {IGiveawaysEvents} event Event name. - * @param {Function} listener Callback function. - * @returns {Emitter} Emitter instance. - * - * @template T Event name to get the callback function type for. - */ - once(event, listener) { - this._emitter.once(event, listener); - return this; - } - /** - * Emits the event. - * - * Type parameters: - * - * - `T` (keyof E) - Event name to get the event argument type for. - * - * @param {IGiveawaysEvents} event Event name. - * @param {any} args Arguments to emit the event with. - * @returns {boolean} If event emitted successfully: true, otherwise - false. - * - * @template T Event name to get the event argument type for. - */ - emit(event, ...args) { - return this._emitter.emit(event, args); - } -} -exports.Emitter = Emitter; diff --git a/dist/src/lib/util/classes/GiveawaysError.js b/dist/src/lib/util/classes/GiveawaysError.js deleted file mode 100644 index a67eb6f..0000000 --- a/dist/src/lib/util/classes/GiveawaysError.js +++ /dev/null @@ -1,123 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.errorMessages = exports.GiveawaysErrorCodes = exports.GiveawaysError = void 0; -const databaseType_enum_1 = require("../../../types/databaseType.enum"); -const typeOf_function_1 = require("../functions/typeOf.function"); -/** - * Giveaways error class. - * @extends {Error} - */ -class GiveawaysError extends Error { - /** - * Error code. - * @type {GiveawaysErrorCodes} - */ - code; - /** - * Giveaways error constructor. - * @param {GiveawaysErrorCodes} errorCode Error code to throw. - */ - constructor(error, errorCode) { - const errorMsg = exports.errorMessages; - const isErrorCode = errorMsg[error]; - super(isErrorCode ? errorMsg[error] : error); - /** - * Error name. - * @type {string} - */ - this.name = `GiveawaysError${isErrorCode - ? ` [${error}]` - : errorCode ? ` [${errorCode}]` : ''}`; - /** - * Error code. - * @type {GiveawaysErrorCodes} - */ - this.code = isErrorCode - ? error - : errorCode ? errorCode : GiveawaysErrorCodes.MODULE_ERROR; - } -} -exports.GiveawaysError = GiveawaysError; -/** - * An enum representing the error codes for the Giveaways module. - * @typedef {string} GiveawaysErrorCodes - * @prop {string} UNKNOWN_ERROR An unknown error occurred. - * @prop {string} UNKNOWN_DATABASE The database is unknown or inaccessible. - * @prop {string} NO_DISCORD_CLIENT No Discord client was provided. - * @prop {string} DATABASE_ERROR There was an error with the database. - * @prop {string} MODULE_ERROR There was an error with the Giveaways module. - * @prop {string} INTENT_MISSING The required intent is missing. - * @prop {string} REQUIRED_ARGUMENT_MISSING A required argument is missing. - * @prop {string} REQUIRED_CONFIG_OPTION_MISSING A required configuration option is missing. - * @prop {string} INVALID_TYPE The type is invalid. - * @prop {string} INVALID_TARGET_TYPE The target type is invalid. - * @prop {string} UNKNOWN_GIVEAWAY The giveaway is unknown. - * @prop {string} INVALID_TIME The time is invalid. - * @prop {string} GIVEAWAY_ALREADY_ENDED Giveaway already ended. - * @prop {string} USER_NOT_FOUND User not found. - * @prop {string} INVALID_INPUT Invalid input. - */ -var GiveawaysErrorCodes; -(function (GiveawaysErrorCodes) { - GiveawaysErrorCodes["UNKNOWN_ERROR"] = "UNKNOWN_ERROR"; - GiveawaysErrorCodes["UNKNOWN_DATABASE"] = "UNKNOWN_DATABASE"; - GiveawaysErrorCodes["NO_DISCORD_CLIENT"] = "NO_DISCORD_CLIENT"; - GiveawaysErrorCodes["DATABASE_ERROR"] = "DATABASE_ERROR"; - GiveawaysErrorCodes["MODULE_ERROR"] = "MODULE_ERROR"; - GiveawaysErrorCodes["INTENT_MISSING"] = "INTENT_MISSING"; - GiveawaysErrorCodes["REQUIRED_ARGUMENT_MISSING"] = "REQUIRED_ARGUMENT_MISSING"; - GiveawaysErrorCodes["REQUIRED_CONFIG_OPTION_MISSING"] = "REQUIRED_CONFIG_OPTION_MISSING"; - GiveawaysErrorCodes["INVALID_TYPE"] = "INVALID_TYPE"; - GiveawaysErrorCodes["INVALID_TARGET_TYPE"] = "INVALID_TARGET_TYPE"; - GiveawaysErrorCodes["UNKNOWN_GIVEAWAY"] = "UNKNOWN_GIVEAWAY"; - GiveawaysErrorCodes["INVALID_TIME"] = "INVALID_TIME"; - GiveawaysErrorCodes["GIVEAWAY_ALREADY_ENDED"] = "GIVEAWAY_ALREADY_ENDED"; - GiveawaysErrorCodes["USER_NOT_FOUND"] = "USER_NOT_FOUND"; - GiveawaysErrorCodes["INVALID_INPUT"] = "INVALID_INPUT"; -})(GiveawaysErrorCodes || (exports.GiveawaysErrorCodes = GiveawaysErrorCodes = {})); -exports.errorMessages = { - UNKNOWN_ERROR: 'Unknown error.', - UNKNOWN_DATABASE: 'Unknown database type was specified.', - NO_DISCORD_CLIENT: 'Discord Client should be specified.', - INVALID_TIME: 'Invalid giveaway time was specified.', - DATABASE_ERROR(databaseType, errorType) { - if (databaseType == databaseType_enum_1.DatabaseType.JSON) { - if (errorType == 'malformed') { - return 'Database file is malformed.'; - } - if (errorType == 'notFound') { - return 'Database file path contains unexisting directories.'; - } - } - return `Unknown ${databaseType} error.`; - }, - UNKNOWN_GIVEAWAY(giveawayMessageID) { - return `Unknown giveaway with message ID ${giveawayMessageID}.`; - }, - GIVEAWAY_ALREADY_ENDED(giveawayPrize, giveawayID) { - return `Giveaway "${giveawayPrize}" (ID: ${giveawayID}) has already ended.`; - }, - INTENT_MISSING(missingIntent) { - return `Required intent "${missingIntent}" is missing.`; - }, - INVALID_TYPE(parameter, requiredType, receivedType) { - return `${parameter} must be a ${requiredType}. Received type: ${(0, typeOf_function_1.typeOf)(`${receivedType}`)}.`; - }, - INVALID_TARGET_TYPE(requiredType, receivedType) { - return `Target must be ${requiredType.toLowerCase().startsWith('a') ? 'an' : 'a'} ${requiredType}. ` + - `Received type: ${(0, typeOf_function_1.typeOf)(receivedType)}.`; - }, - // `method` parameter should be specified in format: `{ManagerName}.{methodName}` - REQUIRED_ARGUMENT_MISSING(parameter, method) { - return `${parameter} must be specified in '${method}()' method.`; - }, - INVALID_INPUT(parameter, method, issue) { - return `Invaid value of ${parameter} parameter in ${method} method: ${issue}`; - }, - REQUIRED_CONFIG_OPTION_MISSING(requiredConfigOption) { - return `Required configuration option "${requiredConfigOption}" is missing.`; - }, - USER_NOT_FOUND(userID) { - return `Specified user with ID ${userID} was not found.`; - } -}; diff --git a/dist/src/lib/util/classes/JSONParser.js b/dist/src/lib/util/classes/JSONParser.js deleted file mode 100644 index 703c71c..0000000 --- a/dist/src/lib/util/classes/JSONParser.js +++ /dev/null @@ -1,136 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.JSONParser = void 0; -const promises_1 = require("fs/promises"); -const typeOf_function_1 = require("../functions/typeOf.function"); -/** - * JSON parser class. - */ -class JSONParser { - /** - * JSON database file path. - * @type {string} - */ - jsonFilePath; - /** - * Minifies the JSON content in database file to save some space. - * @type {boolean} - */ - minifyJSON; - /** - * JSON parser constructor. - * @param {string} jsonFilePath JSON database file path. - * @param {boolean} minifyJSON Minifies the JSON content in database file to save some space. - */ - constructor(jsonFilePath, minifyJSON = false) { - /** - * JSON database file path. - * @type {string} - */ - this.jsonFilePath = jsonFilePath; - /** - * Minifies the JSON content in database file to save some space. - * @type {boolean} - */ - this.minifyJSON = minifyJSON; - } - /** - * Fetches the JSON database object from specified file. - * - * Type parameters: - * - * - `V` - The type of database object to return. - * - * @returns {Promise} JSON database file object. - * - * @template V The type of database object to return. - */ - async fetchDatabaseFile() { - const fileContent = await (0, promises_1.readFile)(this.jsonFilePath, 'utf-8'); - return JSON.parse(fileContent); - } - /** - * Parses the key and fetches the value from JSON database. - * - * Type parameters: - * - * - `V` - The type of data being returned. - * - * @param {string} key The key in JSON database. - * @returns {Promise} The data from JSON database. - * - * @template V The type of data being returned. - */ - async get(key) { - let data = await this.fetchDatabaseFile(); - let parsedData = data; - const keys = key.split('.'); - for (let i = 0; i < keys.length; i++) { - if (keys.length - 1 == i) { - data = parsedData?.[keys[i]] || null; - } - parsedData = parsedData?.[keys[i]]; - } - return data; - } - /** - * Parses the key and sets the value in JSON database. - * - * Type parameters: - * - * - `V` - The type of data being set. - * - `R` - The type of data being returned. - * - * @param {string} key The key in JSON database. - * @returns {Promise} The data from JSON database. - * - * @template V The type of data being set. - * @template R The type of data being returned. - */ - async set(key, value) { - const data = await this.fetchDatabaseFile(); - let updatedData = data; - const keys = key.split('.'); - for (let i = 0; i < keys.length; i++) { - if (keys.length - 1 == i) { - updatedData[keys[i]] = value; - } - else if (!(0, typeOf_function_1.isObject)(data[keys[i]])) { - updatedData[keys[i]] = {}; - } - updatedData = updatedData?.[keys[i]]; - } - await (0, promises_1.writeFile)(this.jsonFilePath, JSON.stringify(data, null, this.minifyJSON ? undefined : '\t')); - return data; - } - /** - * Parses the key and deletes it from JSON database. - * @param {string} key The key in JSON database. - * @returns {Promise} `true` if deleted successfully. - */ - async delete(key) { - const data = await this.fetchDatabaseFile(); - let updatedData = data; - const keys = key.split('.'); - for (let i = 0; i < keys.length; i++) { - if (keys.length - 1 == i) { - delete updatedData[keys[i]]; - } - else if (!(0, typeOf_function_1.isObject)(data[keys[i]])) { - updatedData[keys[i]] = {}; - } - updatedData = updatedData?.[keys[i]]; - } - await (0, promises_1.writeFile)(this.jsonFilePath, JSON.stringify(data, null, this.minifyJSON ? undefined : '\t')); - return true; - } - /** - * Clears the database. - * @returns {Promise} `true` if cleared successfully. - */ - async clearDatabase() { - await (0, promises_1.writeFile)(this.jsonFilePath, '{}'); - return true; - } -} -exports.JSONParser = JSONParser; diff --git a/dist/src/lib/util/classes/Logger.js b/dist/src/lib/util/classes/Logger.js deleted file mode 100644 index 87ebffa..0000000 --- a/dist/src/lib/util/classes/Logger.js +++ /dev/null @@ -1,121 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Logger = void 0; -const package_json_1 = require("../../../../package.json"); -/** - * Logger class. - */ -class Logger { - /** - * Logger colors. - * @type {ILoggerColors} - */ - colors; - /** - * Debug mode state. - */ - debugMode; - /** - * Logger constructor. - * @param {boolean} debug Determines if debug mode is enabled. - */ - constructor(debug) { - /** - * Logger colors object. - * @type {ILoggerColors} - */ - this.colors = { - black: '\x1b[30m', - red: '\x1b[31m', - green: '\x1b[32m', - yellow: '\x1b[33m', - blue: '\x1b[34m', - magenta: '\x1b[35m', - cyan: '\x1b[36m', - lightgray: '\x1b[37m', - default: '\x1b[39m', - darkgray: '\x1b[90m', - lightred: '\x1b[91m', - lightgreen: '\x1b[92m', - lightyellow: '\x1b[93m', - lightblue: '\x1b[94m', - lightmagenta: '\x1b[95m', - lightcyan: '\x1b[96m', - white: '\x1b[97m', - reset: '\x1b[0m', - }; - this.debugMode = debug; - } - /** - * Sends an info message to the console. - * @param {string} message A message to send. - * @param {string} [color='red'] Message color to use. - * @returns {void} - */ - info(message, color = 'cyan') { - console.log(`${this.colors[color]}[Giveaways] ${message}${this.colors.reset}`); - } - /** - * Sends an error message to the console. - * @param {string} message A message to send. - * @param {string} [color='red'] Message color to use. - * @returns {void} - */ - error(message, color = 'red') { - console.error(`${this.colors[color]}[Giveaways - Error] ${message}${this.colors.reset}`); - } - /** - * Sends a debug message to the console. - * @param {string} message A message to send. - * @param {string} [color='yellow'] Message color to use. - * @returns {void} - */ - debug(message, color = 'yellow') { - if (!this.debugMode) - return; - console.log(`${this.colors[color]}[Giveaways] ${message}${this.colors.reset}`); - } - /** - * Sends a warning message to the console. - * @param {string} message A message to send. - * @param {string} [color='lightyellow'] Message color to use. - * @returns {void} - */ - warn(message, color = 'lightyellow') { - console.log(`${this.colors[color]}[Giveaways - Warning] ${message}${this.colors.reset}`); - } - /** - * Sends a debug log for the optional parameter in method not specified. - * @param {string} method The method (e.g. "ShopItem.use") to set. - * @param {string} parameterName Parameter name to set. - * @param {any} defaultValue Default value to set. - * @returns {void} - */ - optionalParamNotSpecified(method, parameterName, defaultValue) { - this.debug(`${method} - "${parameterName}" optional parameter is not specified - defaulting to "${defaultValue}".`, 'lightcyan'); - } - /** - * Checks if the module version is development version and sends the corresponding warning in the console. - * @returns {void} - */ - sendDevVersionWarning() { - if (package_json_1.version.includes('dev')) { - console.log(); - this.warn('You are using a DEVELOPMENT version of Giveaways, which provides an early access ' + - 'to all the new unfinished features and bug fixes.', 'lightmagenta'); - this.warn('Unlike the stable builds, dev builds DO NOT guarantee that the provided changes are bug-free ' + - 'and won\'t result any degraded performance or crashes. ', 'lightmagenta'); - this.warn('Anything may break at any new commit, so it\'s really important to check ' + - 'the module for new development updates.', 'lightmagenta'); - this.warn('They may include the bug fixes from the ' + - 'old ones and include some new features.', 'lightmagenta'); - this.warn('Use development versions at your own risk.', 'lightmagenta'); - console.log(); - this.warn('Need help? Join the Support Server - https://discord.gg/4pWKq8vUnb.', 'lightmagenta'); - this.warn('Please provide the full version of Giveaways you have installed ' + - '(check in your package.json) when asking for support.', 'lightmagenta'); - console.log(); - } - } -} -exports.Logger = Logger; diff --git a/dist/src/lib/util/classes/MessageUtils.js b/dist/src/lib/util/classes/MessageUtils.js deleted file mode 100644 index 003cc8c..0000000 --- a/dist/src/lib/util/classes/MessageUtils.js +++ /dev/null @@ -1,229 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.MessageUtils = void 0; -const discord_js_1 = require("discord.js"); -const giveawayTemplate_1 = require("../../../structures/giveawayTemplate"); -/** - * Message utils class. - */ -class MessageUtils { - /** - * Discord Client. - * @type {Client} - */ - _client; - /** - * {@link Giveaways} instance. - * @type {Giveaways} - */ - _giveaways; - /** - * Message utils class constructor. - * @param {Giveaways} giveaways {@link Giveaways} instance. - */ - constructor(giveaways) { - /** - * Discord Client. - * @type {Client} - */ - this._giveaways = giveaways; - /** - * {@link Giveaways} instance. - * @type {Giveaways} - */ - this._client = giveaways.client; - } - /** - * Creates a new message embed based on giveaway and specified embed strings. - * @param {IGiveaway} giveaway Raw giveaway object to get the values from. - * @param {IGiveawayEmbedOptions} newEmbedStrings String values object to use in the embed. - * @param {string[]} winners Array of winners to replace the {winners} statements with. - * @returns {EmbedBuilder} Generated message embed. - */ - buildGiveawayEmbed(giveaway, newEmbedStrings, winners) { - const embedStrings = newEmbedStrings - ? { ...newEmbedStrings } - : { ...giveaway.messageProps?.embeds?.start || {} }; - for (const stringKey in embedStrings) { - const strings = embedStrings; - strings[stringKey] = (0, giveawayTemplate_1.replaceGiveawayKeys)(strings[stringKey], giveaway, winners); - } - if (embedStrings.timestamp) { - embedStrings.timestamp = parseInt(embedStrings.timestamp); - } - const { title, titleIcon, color, titleURL, description, footer, footerIcon, imageURL, thumbnailURL, timestamp, } = embedStrings; - const embed = new discord_js_1.EmbedBuilder() - .setAuthor({ - // discord.js is only accepting "null" for empty value - // even though here it's missing in library's typings :/ - name: title || null, - iconURL: titleIcon, - url: titleURL - }) - .setDescription(description || `**${giveaway.prize}** giveaway has started with **${giveaway.entriesCount}** entries! ` + - 'Press the button below to join!') - .setColor(color || '#d694ff') - .setImage(imageURL || null) - .setThumbnail(thumbnailURL || null) - .setFooter({ - // discord.js is only accepting "null" for empty value - // even though here it's missing in library's typings :/ - text: footer || null, - iconURL: footerIcon - }); - if (timestamp) { - const date = new Date(timestamp); - if (date.getFullYear() < 2000) { - embed.setTimestamp(timestamp * 1000); - } - else { - embed.setTimestamp(timestamp); - } - } - return embed; - } - /** - * Creates a buttons row based on the specified "join giveaway" button object. - * @param {IGiveawayButtonOptions} joinGiveawayButton String values object to use in the button. - * @returns {ActionRowBuilder} Generated buttons row. - */ - buildButtonsRow(joinGiveawayButton) { - const buttonsRow = new discord_js_1.ActionRowBuilder() - .addComponents(new discord_js_1.ButtonBuilder({ - customId: 'joinGiveawayButton', - label: joinGiveawayButton?.text || 'Join the giveaway', - emoji: joinGiveawayButton?.emoji || '🎉', - style: joinGiveawayButton?.style || discord_js_1.ButtonStyle.Primary - })); - return buttonsRow; - } - /** - * Creates a buttons row based on the specified "reroll" and "go to message" button objects. - * @param {IGiveawayButtonOptions} rerollButton String values object to use in the "reroll" button. - * @param {ILinkButton} [goToMessageButton] String values object to use in the "go to message" button. - * @param {string} [giveawayMessageURL] Giveaway message URL to be set in the "go to message" button. - * @returns {ActionRowBuilder} Generated buttons row. - */ - buildGiveawayFinishedButtonsRow(rerollButton, goToMessageButton, giveawayMessageURL) { - const buttonsRow = new discord_js_1.ActionRowBuilder(); - goToMessageButton ? buttonsRow.addComponents(new discord_js_1.ButtonBuilder({ - customId: 'rerollButton', - label: rerollButton?.text || 'Reroll', - emoji: rerollButton?.emoji || '🔁', - style: rerollButton?.style || discord_js_1.ButtonStyle.Primary - }), new discord_js_1.ButtonBuilder({ - label: goToMessageButton?.text || 'Go to Message', - emoji: goToMessageButton?.emoji || '↗️', - style: discord_js_1.ButtonStyle.Link, - url: giveawayMessageURL - })) : buttonsRow.addComponents(new discord_js_1.ButtonBuilder({ - customId: 'rerollButton', - label: rerollButton?.text || 'Reroll', - emoji: rerollButton?.emoji || '🔁', - style: rerollButton?.style || discord_js_1.ButtonStyle.Primary - })); - return buttonsRow; - } - /** - * Creates a buttons row based on the specified "reroll" and "go to message" button objects. - * @param {IGiveawayButtonOptions} rerollButton String values object to use in the "reroll" button. - * @returns {ActionRowBuilder} Generated buttons row. - */ - buildGiveawayRerollButtonRow(rerollButton) { - const buttonsRow = new discord_js_1.ActionRowBuilder() - .addComponents(new discord_js_1.ButtonBuilder({ - customId: 'rerollButton', - label: rerollButton?.text || 'Reroll', - emoji: rerollButton?.emoji || '🔁', - style: rerollButton?.style || discord_js_1.ButtonStyle.Primary - })); - return buttonsRow; - } - /** - * Creates a buttons row based on the specified "go to message" button objects. - * @param {ILinkButton} goToMessageButton String values object to use in the "go to message" link button. - * @param {string} giveawayMessageURL Giveaway message URL to be set in the "go to message" button. - * @returns {ActionRowBuilder} Generated buttons row. - */ - buildGiveawayFinishedButtonsRowWithoutRerollButton(goToMessageButton, giveawayMessageURL) { - const buttonsRow = new discord_js_1.ActionRowBuilder() - .addComponents(new discord_js_1.ButtonBuilder({ - label: goToMessageButton?.text || 'Go to Message', - emoji: goToMessageButton?.emoji || '↗️', - style: discord_js_1.ButtonStyle.Link, - url: giveawayMessageURL - })); - return buttonsRow; - } - /** - * Edits the giveaway message on giveaway entry. - * @param {IGiveaway} giveaway Raw giveaway object. - * @returns {Promise} - */ - async editEntryGiveawayMessage(giveaway) { - const embedStrings = giveaway.messageProps?.embeds?.start || {}; - const channel = this._client.channels.cache.get(giveaway.channelID); - const embed = this.buildGiveawayEmbed(giveaway); - const buttonsRow = this.buildButtonsRow(giveaway.messageProps?.buttons.joinGiveawayButton); - const message = await channel.messages.fetch(giveaway.messageID); - await message.edit({ - content: embedStrings?.messageContent, - embeds: Object.keys(embedStrings).length == 1 && - embedStrings?.messageContent - ? [] : [embed], - components: [buttonsRow] - }); - } - /** - * Edits the giveaway message on giveaway finish. - * @param {IGiveaway} giveaway Raw giveaway object. - * @param {string[]} winners Array of giveaway winners. - * @param {IGiveawayEmbedOptions} customEmbedStrings Embed options to use instead of `finish` embed. - * @param {boolean} sendWinnersMessage Determines if the separated winners message should be sent. - * @returns {Promise} - */ - async editFinishGiveawayMessage(giveaway, winners, customEmbedStrings, sendWinnersMessage = true, endEmbedStrings) { - const embedStrings = giveaway.messageProps?.embeds?.finish; - const finishEmbedStrings = customEmbedStrings || embedStrings?.newGiveawayMessage || {}; - const noWinnersEmbedStrings = giveaway.messageProps?.embeds?.finish - ?.noWinnersNewGiveawayMessage || {}; - const channel = this._client.channels.cache.get(giveaway.channelID); - const finishDefaultedEmbedStrings = { - ...finishEmbedStrings, - color: finishEmbedStrings?.color || '#d694ff', - description: finishEmbedStrings?.description || 'Giveaway is over!' - }; - const noWinnersDefaultedEmbedStrings = { - ...finishEmbedStrings, - color: noWinnersEmbedStrings?.color || '#d694ff', - description: noWinnersEmbedStrings?.description || 'There are no winners in this giveaway!' - }; - const winnersCondition = winners.length >= this._giveaways.options.minGiveawayEntries; - const defaultedEmbedStrings = winnersCondition ? finishDefaultedEmbedStrings : noWinnersDefaultedEmbedStrings; - const finishEmbed = this.buildGiveawayEmbed(giveaway, defaultedEmbedStrings, winners); - const giveawayEndEmbed = this.buildGiveawayEmbed(giveaway, winnersCondition ? (embedStrings?.endMessage || embedStrings?.newGiveawayMessage || {}) : embedStrings?.noWinnersEndMessage, winners); - const goToMessageButtonRow = this.buildGiveawayFinishedButtonsRowWithoutRerollButton(giveaway.messageProps?.buttons.goToMessageButton, giveaway.messageURL); - const rerollButtonRow = this.buildGiveawayRerollButtonRow(giveaway.messageProps?.buttons.rerollButton); - const message = await channel.messages.fetch(giveaway.messageID); - const giveawayMessageContent = defaultedEmbedStrings.messageContent; - const finishMessageContent = (0, giveawayTemplate_1.replaceGiveawayKeys)(winnersCondition - ? endEmbedStrings?.messageContent || embedStrings?.endMessage.messageContent - : embedStrings?.noWinnersEndMessage?.messageContent, giveaway, winners); - const finishInputObjectKeys = winnersCondition - ? Object.keys(embedStrings?.endMessage || {}) - : Object.keys(embedStrings?.noWinnersEndMessage || {}); - await message.edit({ - content: giveawayMessageContent, - embeds: Object.keys(defaultedEmbedStrings).length == 1 && giveawayMessageContent ? [] : [finishEmbed], - components: winnersCondition ? [rerollButtonRow] : [] - }); - if (sendWinnersMessage) { - await message.reply({ - content: finishMessageContent, - embeds: finishInputObjectKeys.length == 1 && finishMessageContent ? [] : [giveawayEndEmbed], - components: [goToMessageButtonRow] - }); - } - } -} -exports.MessageUtils = MessageUtils; diff --git a/dist/src/lib/util/classes/TypedObject.js b/dist/src/lib/util/classes/TypedObject.js deleted file mode 100644 index 26e3eb7..0000000 --- a/dist/src/lib/util/classes/TypedObject.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.TypedObject = void 0; -/** - * Utility class for working with objects. - * - * Provides **static** methods for retrieving keys and values of an object. - * - * This class enhances type safety by providing better typings for object keys and values. - */ -class TypedObject { - /** - * Returns the names of the enumerable string properties and methods of an object. - * - * Type parameters: - * - * - `TObject` (`Record`) - The object to get the object keys types from. - * - * @param {any} obj Object that contains the properties and methods. - * - * @returns {Array>} - * Array of names of the enumerable string properties and methods of the specified object. - */ - static keys(obj) { - return Object.keys(obj || {}); - } - /** - * Returns an array of values of the enumerable properties of an object. - * - * Type parameters: - * - * - `TObject` (`Record`) - The object to get the object values types from. - * - * @param {any} obj Object that contains the properties and methods. - * - * @returns {Array>} - * Array of values of the enumerable properties of the specified object. - */ - static values(obj) { - return Object.values(obj || {}); - } - /** - * Returns an array of key-value pairs of the enumerable properties of an object. - * - * Type parameters: - * - * - `TObject` (`Record`) - The object to get the object key-value pairs types from. - * - * @param {any} obj Object that contains the properties and methods. - * @returns {Array} Array of key-value pairs of the enumerable properties of the specified object. - */ - static entries(obj) { - return Object.entries(obj || {}); - } -} -exports.TypedObject = TypedObject; diff --git a/dist/src/lib/util/functions/checkConfiguration.function.js b/dist/src/lib/util/functions/checkConfiguration.function.js deleted file mode 100644 index 3fb9b96..0000000 --- a/dist/src/lib/util/functions/checkConfiguration.function.js +++ /dev/null @@ -1,102 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.checkConfiguration = void 0; -const TypedObject_1 = require("../classes/TypedObject"); -const defaultConfig_1 = require("../../../structures/defaultConfig"); -/** - * Completes, fills and fixes the {@link Giveaways} configuration. - * - * Type parameters: - * - * - `TDatabaseType` ({@link DatabaseType}) - The database type that will - * determine which connection configuration should be used. - * - * @callback checkConfiguration - * - * @param {IGiveawaysConfiguration} configurationToCheck The {@link Giveaways} configuration object to check. - * @param {Partial} [checkerConfiguration] Config checker configuration object. - * - * @returns {Required>} Completed, filled and fixed {@link Giveaways} configuration. - * - * @template TDatabaseType - * The database type that will determine which connection configuration should be used. - */ -const checkConfiguration = (configurationToCheck, checkerConfiguration = {}) => { - const problems = []; - const defaultConfiguration = defaultConfig_1.defaultConfig; - const output = { - ...configurationToCheck, - ...defaultConfiguration - }; - if (!checkerConfiguration.ignoreUnspecifiedOptions) { - checkerConfiguration.ignoreUnspecifiedOptions = true; - } - if (!checkerConfiguration.sendLog) { - checkerConfiguration.sendLog = true; - } - if (!checkerConfiguration.showProblems) { - checkerConfiguration.showProblems = true; - } - for (const key of TypedObject_1.TypedObject.keys(configurationToCheck)) { - const config = configurationToCheck; - const defaultValue = defaultConfiguration[key]; - const value = config[key]; - if (key !== 'database' && key !== 'connection') { - if (value == undefined) { - output[key] = defaultValue; - if (!checkerConfiguration.ignoreUnspecifiedOptions) { - problems.push(`options.${key} is not specified.`); - } - } - else if (typeof value !== typeof defaultValue) { - if (!checkerConfiguration.ignoreInvalidTypes) { - problems.push(`options.${key} is not a ${typeof defaultValue}. Received type: ${typeof value}.`); - output[key] = defaultValue; - } - } - else { - output[key] = value; - } - } - } - const checkNestedOptionsObjects = (config, defaultConfig, prefix) => { - for (const key in defaultConfig) { - const defaultValue = defaultConfig[key]; - const value = config[key]; - const fullKey = prefix ? `${prefix}.${key}` : key; - if (value == undefined) { - if (config[key] == undefined) { - config[key] = defaultValue; - } - if (!checkerConfiguration.ignoreUnspecifiedOptions) { - problems.push(`${fullKey} is not specified.`); - } - } - else if (typeof value !== typeof defaultValue) { - if (!checkerConfiguration.ignoreInvalidTypes && (key !== 'database' && key !== 'connection')) { - problems.push(`${fullKey} is not a ${typeof defaultValue}. Received type: ${typeof value}.`); - config[key] = defaultValue; - } - } - else if (typeof value == 'object' && value !== null) { - checkNestedOptionsObjects(value, defaultValue, fullKey); - } - } - }; - checkNestedOptionsObjects(configurationToCheck, defaultConfig_1.defaultConfig, ''); - if (checkerConfiguration.sendLog) { - const problemsCount = problems.length; - if (checkerConfiguration.showProblems) { - if (checkerConfiguration.sendSuccessLog || problemsCount) { - console.log(`Checked the configuration: ${problemsCount} ${problemsCount == 1 ? 'problem' : 'problems'} found.`); - } - if (problemsCount) { - console.log(problems.join('\n')); - } - } - } - output.database = configurationToCheck.database; - output.connection = configurationToCheck.connection; - return output; -}; -exports.checkConfiguration = checkConfiguration; diff --git a/dist/src/lib/util/functions/checkUpdates.function.js b/dist/src/lib/util/functions/checkUpdates.function.js deleted file mode 100644 index fa17d03..0000000 --- a/dist/src/lib/util/functions/checkUpdates.function.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.checkUpdates = void 0; -const node_fetch_1 = __importDefault(require("node-fetch")); -const package_json_1 = require("../../../../package.json"); -/** - * Checks the latest available module version and compares it with installed one. - * @returns {Promise} Update checking results - */ -const checkUpdates = async () => { - const packageData = await (0, node_fetch_1.default)(`https://registry.npmjs.com/${package_json_1.name}`) - .then(text => text.json()); - const latestVersion = packageData['dist-tags']?.latest || '1.0.0'; - if (package_json_1.version == latestVersion) - return { - updated: true, - installedVersion: package_json_1.version, - availableVersion: latestVersion - }; - return { - updated: false, - installedVersion: package_json_1.version, - availableVersion: latestVersion - }; -}; -exports.checkUpdates = checkUpdates; diff --git a/dist/src/lib/util/functions/time.function.js b/dist/src/lib/util/functions/time.function.js deleted file mode 100644 index a715dbd..0000000 --- a/dist/src/lib/util/functions/time.function.js +++ /dev/null @@ -1,44 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.convertTimeToMilliseconds = exports.isTimeStringValid = exports.timeRegex = void 0; -exports.timeRegex = /^(\d+d)?\s?(((\d+h)\s?(\d+m))?|\d+h|\d+m)\s?(\d+s)?$/; -/** - * Checks if specified time string is valid. - * @param {string} timeString The time string to check. - * @returns {boolean} Whether the specified time string is valid. - */ -const isTimeStringValid = (timeString) => { - return exports.timeRegex.test(timeString) && timeString !== ''; -}; -exports.isTimeStringValid = isTimeStringValid; -/** - * Converts a time string to milliseconds. - * @param {string} timeString The time string to convert. - * - * @returns {Maybe} - * The total number of milliseconds in the time string, or `null` if the time string is invalid or empty. - */ -const convertTimeToMilliseconds = (timeString) => { - if (!(0, exports.isTimeStringValid)(timeString)) { - return null; - } - const days = timeString.match(/\d+d/); - const hours = timeString.match(/\d+h/); - const minutes = timeString.match(/\d+m/); - const seconds = timeString.match(/\d+s/); - let totalMilliseconds = 0; - if (days) { - totalMilliseconds += parseInt(days[0].replace('d', '')) * 24 * 60 * 60 * 1000; - } - if (hours) { - totalMilliseconds += parseInt(hours[0].replace('h', '')) * 60 * 60 * 1000; - } - if (minutes) { - totalMilliseconds += parseInt(minutes[0].replace('m', '')) * 60 * 1000; - } - if (seconds) { - totalMilliseconds += parseInt(seconds[0].replace('s', '')) * 1000; - } - return totalMilliseconds; -}; -exports.convertTimeToMilliseconds = convertTimeToMilliseconds; diff --git a/dist/src/lib/util/functions/typeOf.function.js b/dist/src/lib/util/functions/typeOf.function.js deleted file mode 100644 index a4c18bf..0000000 --- a/dist/src/lib/util/functions/typeOf.function.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.isObject = exports.typeOf = void 0; -/** - * Returns the exact type of the specified input. Utilility function. - * @param {any} input The input to check. - * @returns {string} Input exact type. - */ -const typeOf = (input) => { - if ((typeof input == 'object' || typeof input == 'function') && input?.prototype) { - return input.name; - } - if (input == null || input == undefined || (typeof input == 'number' && isNaN(input))) { - return `${input}`; - } - return input.constructor.name; -}; -exports.typeOf = typeOf; -/** - * Checks for is the item object and returns it. - * @param {any} item The item to check. - * @returns {boolean} Is the item object or not. -*/ -const isObject = (item) => { - return !Array.isArray(item) - && typeof item == 'object' - && item !== null; -}; -exports.isObject = isObject; diff --git a/dist/src/structures/defaultConfig.js b/dist/src/structures/defaultConfig.js deleted file mode 100644 index d1d50f8..0000000 --- a/dist/src/structures/defaultConfig.js +++ /dev/null @@ -1,20 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.defaultConfig = void 0; -exports.defaultConfig = { - giveawaysCheckingInterval: 1000, - minGiveawayEntries: 1, - updatesChecker: { - checkUpdates: true, - upToDateMessage: false - }, - configurationChecker: { - ignoreInvalidTypes: false, - ignoreUnspecifiedOptions: true, - ignoreInvalidOptions: false, - showProblems: true, - sendLog: true, - sendSuccessLog: false - }, - debug: false -}; diff --git a/dist/src/structures/giveawayTemplate.js b/dist/src/structures/giveawayTemplate.js deleted file mode 100644 index c27d979..0000000 --- a/dist/src/structures/giveawayTemplate.js +++ /dev/null @@ -1,33 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.replaceGiveawayKeys = exports.giveawayTemplate = void 0; -exports.giveawayTemplate = { - id: '{id}', - hostMemberID: '{hostMemberID}', - guildID: '{guildID}', - channelID: '{channelID}', - messageID: '{messageID}', - prize: '{prize}', - startTimestamp: '{startTimestamp}', - endTimestamp: '{endTimestamp}', - endedTimestamp: '{endedTimestamp}', - time: '{time}', - winnersCount: '{winnersCount}', - entriesCount: '{entriesCount}', - messageURL: '{messageURL}', - messageProps: '{messageProps}', - numberOfWinners: '{numberOfWinners}', - winnersString: '{winnersString}', - isEnded: '{isEnded}', - state: '{state}', - entries: '' -}; -function replaceGiveawayKeys(input, giveawayObject, winners = []) { - for (const key in exports.giveawayTemplate) { - input = input?.replaceAll(`{${key}}`, key == 'numberOfWinners' - ? winners?.length - : key == 'winnersString' ? winners?.join(', ') : giveawayObject[key]); - } - return input; -} -exports.replaceGiveawayKeys = replaceGiveawayKeys; diff --git a/dist/src/types/configurations.js b/dist/src/types/configurations.js deleted file mode 100644 index c8ad2e5..0000000 --- a/dist/src/types/configurations.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/databaseStructure.interface.js b/dist/src/types/databaseStructure.interface.js deleted file mode 100644 index c8ad2e5..0000000 --- a/dist/src/types/databaseStructure.interface.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/databaseType.enum.js b/dist/src/types/databaseType.enum.js deleted file mode 100644 index 5707a82..0000000 --- a/dist/src/types/databaseType.enum.js +++ /dev/null @@ -1,16 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.DatabaseType = void 0; -/** - * An enum containing the possible database types. - * @typedef {string} DatabaseType - * @prop {string} JSON - The JSON database type. - * @prop {string} MONGODB - The MongoDB database type. - * @prop {string} ENMAP - The Enmap database type. - */ -var DatabaseType; -(function (DatabaseType) { - DatabaseType["JSON"] = "JSON"; - DatabaseType["MONGODB"] = "MongoDB"; - DatabaseType["ENMAP"] = "Enmap"; -})(DatabaseType || (exports.DatabaseType = DatabaseType = {})); diff --git a/dist/src/types/debug.interface.js b/dist/src/types/debug.interface.js deleted file mode 100644 index c8ad2e5..0000000 --- a/dist/src/types/debug.interface.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/giveawaysEvents.interface.js b/dist/src/types/giveawaysEvents.interface.js deleted file mode 100644 index c8ad2e5..0000000 --- a/dist/src/types/giveawaysEvents.interface.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/misc/colors.interface.js b/dist/src/types/misc/colors.interface.js deleted file mode 100644 index c8ad2e5..0000000 --- a/dist/src/types/misc/colors.interface.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/dist/src/types/misc/utils.js b/dist/src/types/misc/utils.js deleted file mode 100644 index c8ad2e5..0000000 --- a/dist/src/types/misc/utils.js +++ /dev/null @@ -1,2 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true });