From bc2c8a6d94134d357dc2ca55b33ede5234ad4cd3 Mon Sep 17 00:00:00 2001 From: zeroclutch <35671110+zeroclutch@users.noreply.github.com> Date: Tue, 26 Sep 2023 22:45:39 -0700 Subject: [PATCH] Feature: End message configuration (#80) Allows the end message to be determined by a field in the database. This allows the end message to be configurable without a restart and allows for events to be widely broadcasted. This also implements the &phrase command, which allows for these phrases to be edited by commands. --- commands/dev/phrase.js | 136 ++++++++++++++++++++++ games/Chess/classes/Chess.js | 8 +- games/Connect Four/classes/ConnectFour.js | 4 +- games/_Game/classes/Game.js | 5 +- scripts/clientSetup.js | 34 ++++-- 5 files changed, 172 insertions(+), 15 deletions(-) create mode 100644 commands/dev/phrase.js diff --git a/commands/dev/phrase.js b/commands/dev/phrase.js new file mode 100644 index 0000000..7f471b8 --- /dev/null +++ b/commands/dev/phrase.js @@ -0,0 +1,136 @@ +import BotCommand from '../../types/command/BotCommand.js' +import { GAMEBOT_PERMISSIONS } from '../../config/types.js' +import { ActionRowBuilder, ApplicationCommandOptionType, ButtonStyle, Message } from 'discord.js' +import options from '../../config/options.js' +import { ButtonBuilder } from '@discordjs/builders' + +export default new BotCommand({ + name: 'phrase', + aliases: ['phrases'], + description: 'Sets the end message', + category: 'dev', + permissions: [GAMEBOT_PERMISSIONS.OWNER], + dmCommand: true, + args: [{ + name: 'action', + type: ApplicationCommandOptionType.String, + required: true, + description: `The action to take: +- \`list\` lists the current active messages +- \`add\` lists the current active messages +- \`delete\` deletes an item from the list +- \`broadcast\` sets the global broadcast message`, + }], + /** + * @param {Message} msg + * @param {Array} args + */ + run: async function(msg, args) { + let action = args[0] + let phrase = args.slice(1).join(' ') + + const collection = msg.client.database.collection('status') + let list = await collection.findOne({ type: 'phrases' }) + + const confirm = (message) => { + return new Promise((resolve, reject) => { + const filter = i => i.customId && i.user.id === msg.author.id + msg.reply({ + content: message, + components: [ + new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId('confirm') + .setLabel('Confirm') + .setStyle(ButtonStyle.Success), + new ButtonBuilder() + .setCustomId('cancel') + .setLabel('Cancel') + .setStyle(ButtonStyle.Secondary), + ) + ], + }) + .then(m => m.awaitMessageComponent({ filter, time: 60_000 })) + .then(resolve) + }) + } + + switch(action.toLowerCase()) { + default: + case 'list': + msg.reply({ + embeds: [{ + title: 'End Phrases', + fields: [{ + name: 'Phrase list', + value: list.phrases.map((phrase, i) => `**[${i + 1}]** ${phrase}`).join('\n\n') + }, + { + name: 'Broadcast phrase', + value: list.broadcastPhrase || '*No broadcast phrase set*' + }], + color: options.colors.info + }] + }) + break + case 'add': + confirm(`Are you sure you'd like to add the following phrase?\n\n"${phrase}"`) + .then(i => { + // Confirm action + if(i.customId === 'confirm') { + collection.updateOne( + { type: 'phrases' }, + { $push: { phrases: phrase } } + ).then(() => i.reply('Added phrase!')) + } else if(i.customId === 'cancel') { + i.reply('Phrase not added.') + } + }) + + break + case 'delete': + let index = parseInt(args[1]) - 1 + + if(!isNaN(index) || list?.phrases[index]) { + confirm(`Are you sure you'd like to delete the following phrase?\n\n"${list.phrases[index]}"`) + .then(i => { + if(i.customId === 'confirm') { + // Remove and update the database + list.phrases.splice(index, 1) + collection.updateOne( + { type: 'phrases' }, + { $set: { phrases: list.phrases } } + ).then(() => i.reply('Deleted phrase!')) + } else if(i.customId === 'cancel') { + i.reply('Phrase not deleted.') + } + }) + } else { + msg.channel.send(`Invalid phrase index to delete.`) + } + + break + case 'broadcast': + let confirmationMessage = `Are you sure you'd like to broadcast the following phrase?\n\n"${phrase}"` + if(!phrase) { + confirmationMessage = `Are you sure you'd like to reset the broadcast message?` + } + + confirm(confirmationMessage) + .then(i => { + // Confirm action + if(i.customId === 'confirm') { + collection.updateOne( + { type: 'phrases' }, + { $set: { broadcastPhrase: phrase } } + ).then(() => i.reply('Broadcast phrase set!')) + } else if(i.customId === 'cancel') { + i.reply('Phrase not broadcast.') + } + }) + + break + } + } +}) \ No newline at end of file diff --git a/games/Chess/classes/Chess.js b/games/Chess/classes/Chess.js index 10e2f45..d4e86c6 100644 --- a/games/Chess/classes/Chess.js +++ b/games/Chess/classes/Chess.js @@ -316,10 +316,10 @@ export default class Chess extends Game { })) } - analyzeBoard(side) { + async analyzeBoard(side) { if(this.status.isStalemate || this.status.isRepetition) { this.importGameToLichess('draw') - this.end(undefined, `The game is a draw.\nTo play games with the community, [join our server](${options.serverInvite}?ref=gameEnd)!`) + this.end(undefined, `The game is a draw.\n${(await this.client.getEndPhrase())}`) this.over = true } @@ -351,8 +351,8 @@ export default class Chess extends Game { } while(!this.over) } - finish(id) { + async finish(id) { let winner = this.players.find(player => player.id == id) - this.end(winner, `${winner.user} has won!\nTo play games with the community, [join our server](${options.serverInvite}?ref=gameEnd)!`) + this.end(winner, `${winner.user} has won!\n${(await this.client.getEndPhrase())}`) } } diff --git a/games/Connect Four/classes/ConnectFour.js b/games/Connect Four/classes/ConnectFour.js index bb9492f..b4b8eb0 100644 --- a/games/Connect Four/classes/ConnectFour.js +++ b/games/Connect Four/classes/ConnectFour.js @@ -219,9 +219,9 @@ export default class ConnectFour extends Game { this.finish(this.getWinner()) } - finish(id) { + async finish(id) { let winner = this.players.find(player => player.id == id) - this.end(winner, `${winner.user} has won! ${this.renderBoard()}\nTo play games with the community, [join our server](${options.serverInvite}?ref=gameEnd)!`) + this.end(winner, `${winner.user} has won! ${this.renderBoard()}\n${(await this.client.getEndPhrase())}`) } diff --git a/games/_Game/classes/Game.js b/games/_Game/classes/Game.js index 48445c3..5388d64 100644 --- a/games/_Game/classes/Game.js +++ b/games/_Game/classes/Game.js @@ -894,7 +894,7 @@ export default class Game extends EventEmitter { * @param {Object|Array} winners The game winner * @param {String} endPhrase The message to be sent at the end of the game. */ - end(winners, endPhrase) { + async end(winners, endPhrase) { this.beforeEnd() this.ending = true this.stage = 'over' @@ -925,7 +925,8 @@ export default class Game extends EventEmitter { endPhrase = '' } - endPhrase += `\nTo play games with the community, [join our server](${options.serverInvite}?ref=gameEnd)!` + // Add game end message + endPhrase += `\n${(await this.client.getEndPhrase())}` } const gameEmbed = { diff --git a/scripts/clientSetup.js b/scripts/clientSetup.js index dbdd5ab..ec7ac44 100644 --- a/scripts/clientSetup.js +++ b/scripts/clientSetup.js @@ -3,6 +3,8 @@ import fs from 'fs' import path from 'path' import logger from 'gamebot/logger' +import options from '../config/options.js' + // import GameManager from '../types/games/GameManager.js' import CommandHandler from '../types/command/CommandHandler.js' import TournamentManager from '../types/games/TournamentManager.js' @@ -131,15 +133,33 @@ const database = async client => { // configure downtime notifications client.getTimeToDowntime = () => { return new Promise((resolve, reject) => { - client.database.collection('status').findOne( { type: 'downtime' }).then((data, err) => { - if(err || !data) { - reject(logger.error(err)) - return - } - resolve(data.downtimeStart - Date.now()) - }) + client.database.collection('status').findOne( { type: 'downtime' }).then((data, err) => { + if(err || !data) { + reject(logger.error(err)) + return + } + resolve(data.downtimeStart - Date.now()) + }) }) } + + // configure end phrases + Object.defineProperty(client, 'getEndPhrase', { + value: async () => { + const DEFAULT_END_PHRASE = `To play games with the community, [join our server](${options.serverInvite}?ref=gameEnd)!` + + const data = await client.database.collection('status').findOne({ type: 'phrases' }) + let phrase = DEFAULT_END_PHRASE + + if(data?.phrases) phrase = data.phrases[Math.floor(Math.random() * data.phrases.length)] + if(data?.broadcastPhrase && data.broadcastPhrase.length > 0) phrase = data.broadcastPhrase + + return phrase + }, + writable: false, + enumerable: true + }) + } export default {