From 180c472d51798f0cf5fd73069a38105081ebe7c5 Mon Sep 17 00:00:00 2001 From: elijaholmos Date: Tue, 12 Oct 2021 08:24:48 -0700 Subject: [PATCH 01/10] chore: post-release updates --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f83574c..ee27bdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "leyline-discord", - "version": "2.4.0", + "version": "2.5.0-dev", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "2.4.0", + "version": "2.5.0-dev", "license": "ISC", "dependencies": { "@google-cloud/pubsub": "^2.16.1", diff --git a/package.json b/package.json index 863ee94..2ce1ee3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leyline-discord", - "version": "2.4.0", + "version": "2.5.0-dev", "description": "Leyline Discord bot", "main": "index.js", "type": "module", From ff65cce3099dd2daafa4fa36661ad79d5dd21c6f Mon Sep 17 00:00:00 2001 From: elijaholmos Date: Tue, 12 Oct 2021 18:03:17 -0700 Subject: [PATCH 02/10] allow for viewing of my punishment history this was unintentional, I swear --- commands/admin/punish.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/admin/punish.js b/commands/admin/punish.js index a90ba0c..eba00d7 100644 --- a/commands/admin/punish.js +++ b/commands/admin/punish.js @@ -234,7 +234,7 @@ class punish extends Command { : null; //easter egg - if(user.id === '139120967208271872') + if(type !== 'HISTORY' && user.id === '139120967208271872') return bot.intrReply({intr, embed: new EmbedBase(bot, { title: 'Nice try!', image: { From 9c49370e9e994c7655f8cce2d23ed4395c5ba655 Mon Sep 17 00:00:00 2001 From: elijaholmos <35435704+elijaholmos@users.noreply.github.com> Date: Sun, 17 Oct 2021 22:58:15 -0700 Subject: [PATCH 03/10] Justice system changes (#107) * rebrand punishment to sentence/justice * rearrange embeds, remove all messages in 24h on ban * swap mod-log and bot-log --- classes/LeylineBot.js | 6 +- classes/components/EmbedBase.js | 2 +- classes/index.js | 2 +- ...unishmentService.js => SentenceService.js} | 245 +++++++++--------- commands/admin/{punish.js => sentence.js} | 96 +++---- commands/development/embedtest.js | 4 +- config.js | 4 +- index.js | 18 +- 8 files changed, 189 insertions(+), 188 deletions(-) rename classes/services/{PunishmentService.js => SentenceService.js} (69%) rename commands/admin/{punish.js => sentence.js} (70%) diff --git a/classes/LeylineBot.js b/classes/LeylineBot.js index 555c719..4126858 100644 --- a/classes/LeylineBot.js +++ b/classes/LeylineBot.js @@ -116,13 +116,13 @@ export class LeylineBot extends Client { } /** - * Sends a discord message on the bot's behalf to a public log channel, specific for punishments + * Sends a discord message on the bot's behalf to a public log channel, specific for sentences * @param {Object} args * @param {EmbedBase} args.embed Singular embed object to be sent in message * @returns {Promise} Promise which resolves to the sent message */ - async logPunishment({embed, ...options}) { - return (await this.channels.fetch(this.config.channels.punishment_log)).send({ + async logSentence({embed, ...options}) { + return (await this.channels.fetch(this.config.channels.mod_log)).send({ embeds: [embed], ...options, }); diff --git a/classes/components/EmbedBase.js b/classes/components/EmbedBase.js index 54cbaaa..fdc32c6 100644 --- a/classes/components/EmbedBase.js +++ b/classes/components/EmbedBase.js @@ -54,7 +54,7 @@ export class EmbedBase extends MessageEmbed { return this; } - Punish() { + Sentence() { this.color = 0xe3da32; return this; } diff --git a/classes/index.js b/classes/index.js index 101fdd2..072c3df 100644 --- a/classes/index.js +++ b/classes/index.js @@ -12,6 +12,6 @@ export * from './components/EmbedBase.js'; export * from './events/DiscordEvent.js'; export * from './events/FirebaseEvent.js'; export * from './services/XPService.js'; -export * from './services/PunishmentService'; +export * from './services/SentenceService'; export * from './CloudConfig'; export * from './LeylineBot'; diff --git a/classes/services/PunishmentService.js b/classes/services/SentenceService.js similarity index 69% rename from classes/services/PunishmentService.js rename to classes/services/SentenceService.js index bf75e97..b05f8c0 100644 --- a/classes/services/PunishmentService.js +++ b/classes/services/SentenceService.js @@ -2,9 +2,9 @@ import admin from 'firebase-admin'; import { scheduleJob } from 'node-schedule'; import { EmbedBase } from '..'; -export class PunishmentService { - static COLLECTION_PATH = 'discord/bot/punishments'; - static PUNISHMENT_TYPES = { +export class SentenceService { + static COLLECTION_PATH = 'discord/bot/sentences'; + static SENTENCE_TYPES = { WARN: 'WARN', MUTE: 'MUTE', KICK: 'KICK', @@ -12,18 +12,18 @@ export class PunishmentService { }; /** - * Record a punishment in firestore + * Record a sentence in firestore * @param {Object} args Destructured args * @param {string} args.uid Target user id - * @param {User} args.mod `User` object of staff that is issuing punishment - * @param {string} args.type See `PUNISHMENT_TYPES`. Type of punishment - * @param {number} [args.expires] Unix timestamp for when punishment should expire. `null` if no expiration - * @param {string} [args.reason] Mod-provided reason for why punishment was issued. `null` if no reason - * @param {number} [args.timestamp] Unix timestamp of when punishment was issued. Defaults to `Date.now()` + * @param {User} args.mod `User` object of staff that is issuing sentence + * @param {string} args.type See `SENTENCE_TYPES`. Type of sentence + * @param {number} [args.expires] Unix timestamp for when sentence should expire. `null` if no expiration + * @param {string} [args.reason] Mod-provided reason for why sentence was issued. `null` if no reason + * @param {number} [args.timestamp] Unix timestamp of when sentence was issued. Defaults to `Date.now()` * @param {Object} [args.metadata] Metadata to be included in Firestore doc * @returns Resolves to added doc */ - static async recordPunishment({uid, mod, type, expires=null, reason=null, timestamp=Date.now(), metadata=null} = {}) { + static async recordSentence({uid, mod, type, expires=null, reason=null, timestamp=Date.now(), metadata=null} = {}) { return await admin.firestore() .collection(this.COLLECTION_PATH) .add({ @@ -39,22 +39,22 @@ export class PunishmentService { } /** - * Log a punishment in the appropriate Discord channels + * Log a sentence in the appropriate Discord channels * AND send a message to the end user * @param {Object} args Destructured args * @param {LeylineBot} args.bot bot - * @param {User} args.user `User` object of user that is being punished - * @param {User} args.mod `User` object of staff that is issuing punishment - * @param {string} args.type See `PUNISHMENT_TYPES`. Type of punishment - * @param {number} [args.expires] Unix timestamp for when punishment should expire. `null` if no expiration - * @param {string} [args.reason] Mod-provided reason for why punishment was issued. `null` if no reason - * @param {number} [args.timestamp] Unix timestamp of when punishment was issued. Defaults to `Date.now()` + * @param {User} args.user `User` object of user that is being sentenced + * @param {User} args.mod `User` object of staff that is issuing sentence + * @param {string} args.type See `SENTENCE_TYPES`. Type of sentence + * @param {number} [args.expires] Unix timestamp for when sentence should expire. `null` if no expiration + * @param {string} [args.reason] Mod-provided reason for why sentence was issued. `null` if no reason + * @param {number} [args.timestamp] Unix timestamp of when sentence was issued. Defaults to `Date.now()` * @returns Resolves to added doc */ - static async logPunishment({bot, user, mod, type, expires=null, reason=null, timestamp=Date.now()} = {}) { - const { PUNISHMENT_TYPES } = this; + static async logSentence({bot, user, mod, type, expires=null, reason=null, timestamp=Date.now()} = {}) { + const { SENTENCE_TYPES: SENTENCE_TYPES } = this; const embed = new EmbedBase(bot, { - title: 'Punishment Issued', + title: 'Justice Served', fields: [ { name: 'Type', @@ -68,13 +68,13 @@ export class PunishmentService { }, { name: '\u200b', value: '\u200b', inline: true }, { - name: 'Reason', - value: reason ?? 'No reason given', + name: 'Expires', + value: !!expires ? bot.formatTimestamp(expires) : 'No expiration', inline: true, }, { - name: 'Expires', - value: !!expires ? bot.formatTimestamp(expires) : 'No expiration', + name: 'Reason', + value: reason ?? 'No reason given', inline: true, }, { name: '\u200b', value: '\u200b', inline: true }, @@ -82,7 +82,7 @@ export class PunishmentService { name: 'Current Warnings', value: `${(await this.getHistory({ user, - types: [PUNISHMENT_TYPES.WARN], + types: [SENTENCE_TYPES.WARN], })).length} Warnings`, inline: true, }, @@ -94,42 +94,42 @@ export class PunishmentService { { name: '\u200b', value: '\u200b', inline: true } ], timestamp, - }).Punish(); + }).Sentence(); //Message user await bot.sendDM({send_disabled_msg: false, user, embed}); //log publicly - if(type === PUNISHMENT_TYPES.BAN) - await bot.logPunishment({embed}); + if(type === SENTENCE_TYPES.BAN) + await bot.logDiscord({embed}); //log privately - await bot.logDiscord({embed}); + await bot.logSentence({embed}); } /** - * Schedule removal of a temporary punishment. + * Schedule removal of a temporary sentence. * * Data should be pre-fetched Firestore doc data, * or locally cached data reconstructed to match params * @param {Object} args Destructured args * @param {LeylineBot} args.bot bot - * @param {string} args.id Punishment ID retrieved from the Firestore `DocumentSnapshot` - * @param {Object} args.data Pre-fetched punishment data - * @param {string} args.data.type See `PUNISHMENT_TYPES`. Type of punishment + * @param {string} args.id Sentence ID retrieved from the Firestore `DocumentSnapshot` + * @param {Object} args.data Pre-fetched sentence data + * @param {string} args.data.type See `SENTENCE_TYPES`. Type of sentence * @param {string} args.data.uid Target Discord user ID - * @param {string} args.data.issued_by ID of Discord Mod that issued the punishment - * @param {number} args.data.expires Unix timestamp of punishment expiration - * @param {string} [args.data.reason] Reason punishment was issued - * @returns {PunishmentService} Resolves to this class + * @param {string} args.data.issued_by ID of Discord Mod that issued the sentence + * @param {number} args.data.expires Unix timestamp of sentence expiration + * @param {string} [args.data.reason] Reason sentence was issued + * @returns {SentenceService} Resolves to this class */ static scheduleRemoval({bot, id, data}) { - const { PUNISHMENT_TYPES: types } = this; + const { SENTENCE_TYPES: types } = this; // If no expiration, exit - // Check punishment type + // Check sentence type // Create scheduled job if(!data.expires) return; const job = scheduleJob(new Date(data.expires), (fire_date) => { - bot.logger.debug(`un${data.type} for punishment ${id} scheduled for ${fire_date} triggered at ${new Date()}`); + bot.logger.debug(`un${data.type} for sentence ${id} scheduled for ${fire_date} triggered at ${new Date()}`); switch(data.type) { case types.BAN: { this.unbanUser({bot, id, ...data}); @@ -141,32 +141,32 @@ export class PunishmentService { } } }); - bot.logger.log(`un${data.type} for punishment ${id} scheduled for ${job.nextInvocation()}`); + bot.logger.log(`un${data.type} for sentence ${id} scheduled for ${job.nextInvocation()}`); return this; } /** - * Issue a WARN punishment to a user. + * Issue a WARN sentence to a user. * Discord logging should be handeled separately * @param {Object} args Destructured args * @param {LeylineBot} args.bot bot - * @param {User} args.user `User` object of user that is being punished - * @param {User} args.mod `User` object of staff that is issuing punishment - * @param {string} [args.reason] Mod-provided reason for why punishment was issued. `null` if no reason + * @param {User} args.user `User` object of user that is being sentenced + * @param {User} args.mod `User` object of staff that is issuing sentence + * @param {string} [args.reason] Mod-provided reason for why sentence was issued. `null` if no reason * @returns {Promise} Resolves to true if successfully executed */ static async warnUser({bot, user, mod, reason=null} = {}) { - const type = this.PUNISHMENT_TYPES.WARN; + const type = this.SENTENCE_TYPES.WARN; const member = await bot.leyline_guild.members.fetch({ user, force: true, }); if(!member) throw new Error(`I could not find member ${user.id} in the server!`); - //issue punishment (log in cloud) - //store punishment in cloud - await this.recordPunishment({ + //issue sentence (log in cloud) + //store sentence in cloud + await this.recordSentence({ uid: user.id, mod, type, @@ -179,19 +179,19 @@ export class PunishmentService { } /** - * Issue a MUTE punishment to a user. + * Issue a MUTE sentence to a user. * Discord logging should be handeled separately * @param {Object} args Destructured args * @param {LeylineBot} args.bot bot - * @param {User} args.user `User` object of user that is being punished - * @param {User} args.mod `User` object of staff that is issuing punishment - * @param {number} [args.expires] Unix timestamp for when punishment should expire. `null` if no expiration - * @param {string} [args.reason] Mod-provided reason for why punishment was issued. `null` if no reason + * @param {User} args.user `User` object of user that is being sentenced + * @param {User} args.mod `User` object of staff that is issuing sentence + * @param {number} [args.expires] Unix timestamp for when sentence should expire. `null` if no expiration + * @param {string} [args.reason] Mod-provided reason for why sentence was issued. `null` if no reason * @returns {Promise} Resolves to true if successfully executed */ static async muteUser({bot, user, mod, expires=null, reason=null} = {}) { const MUTED_ROLE = bot.config.muted_role; - const type = this.PUNISHMENT_TYPES.MUTE; + const type = this.SENTENCE_TYPES.MUTE; const member = await bot.leyline_guild.members.fetch({ user, @@ -199,13 +199,13 @@ export class PunishmentService { }); if(!member) throw new Error(`I could not find member ${user.id} in the server!`); - //issue punishment + //issue sentence if(member.roles.cache.has(MUTED_ROLE)) throw new Error(`Member ${member.id} is already muted!`); - await member.roles.add(MUTED_ROLE, `Punishment issued by ${mod.tag}`); + await member.roles.add(MUTED_ROLE, `Justice Served by ${mod.tag}`); - //store punishment in cloud - const doc = await this.recordPunishment({ + //store sentence in cloud + const doc = await this.recordSentence({ uid: user.id, mod, type, @@ -230,28 +230,28 @@ export class PunishmentService { } /** - * Issue a KICK punishment to a user. + * Issue a KICK sentence to a user. * Discord logging should be handeled separately * @param {Object} args Destructured args * @param {LeylineBot} args.bot bot - * @param {User} args.user `User` object of user that is being punished - * @param {User} args.mod `User` object of staff that is issuing punishment - * @param {string} [args.reason] Mod-provided reason for why punishment was issued. `null` if no reason + * @param {User} args.user `User` object of user that is being sentenced + * @param {User} args.mod `User` object of staff that is issuing sentence + * @param {string} [args.reason] Mod-provided reason for why sentence was issued. `null` if no reason * @returns {Promise} Resolves to true if successfully executed */ static async kickUser({bot, user, mod, reason=null} = {}) { - const type = this.PUNISHMENT_TYPES.KICK; + const type = this.SENTENCE_TYPES.KICK; const member = await bot.leyline_guild.members.fetch({ user, force: true, }); if(!member) throw new Error(`I could not find member ${user.id} in the server!`); - //issue punishment - await member.kick(`Punishment issued by ${mod.tag}`); + //issue sentence + await member.kick(`Justice Served by ${mod.tag}`); - //store punishment in cloud - await this.recordPunishment({ + //store sentence in cloud + await this.recordSentence({ uid: user.id, mod, type, @@ -264,31 +264,32 @@ export class PunishmentService { } /** - * Issue a BAN punishment to a user. + * Issue a BAN sentence to a user. * Discord logging should be handeled separately * @param {Object} args Destructured args * @param {LeylineBot} args.bot bot - * @param {User} args.user `User` object of user that is being punished - * @param {User} args.mod `User` object of staff that is issuing punishment - * @param {number} [args.expires] Unix timestamp for when punishment should expire. `null` if no expiration - * @param {string} [args.reason] Mod-provided reason for why punishment was issued. `null` if no reason + * @param {User} args.user `User` object of user that is being sentenced + * @param {User} args.mod `User` object of staff that is issuing sentence + * @param {number} [args.expires] Unix timestamp for when sentence should expire. `null` if no expiration + * @param {string} [args.reason] Mod-provided reason for why sentence was issued. `null` if no reason * @returns {Promise} Resolves to true if successfully executed */ static async banUser({bot, user, mod, expires=null, reason=null} = {}) { - const type = this.PUNISHMENT_TYPES.BAN; + const type = this.SENTENCE_TYPES.BAN; const member = await bot.leyline_guild.members.fetch({ user, force: true, }); if(!member) throw new Error(`I could not find member ${user.id} in the server!`); - //issue punishment + //issue sentence await member.ban({ - reason: `Punishment issued by ${mod.tag}`, + reason: `Justice Served by ${mod.tag}`, + days: 1, }); - //store punishment in cloud - const doc = await this.recordPunishment({ + //store sentence in cloud + const doc = await this.recordSentence({ uid: user.id, mod, type, @@ -313,14 +314,14 @@ export class PunishmentService { } /** - * Retrieve all the punishments issued for a given user + * Retrieve all the sentences issued for a given user * @param {Object} args Destructured args - * @param {User} args.user user to query punishment history for - * @param {Array} [args.types] Array of valid `PUNISHMENT_TYPE`s to be filtered. Defaults to all types + * @param {User} args.user user to query sentence history for + * @param {Array} [args.types] Array of valid `SENTENCE_TYPE`s to be filtered. Defaults to all types * @returns {Promise[]>} - * Array of documents for the user's punishment history, sorted by date issued, descending + * Array of documents for the user's sentence history, sorted by date issued, descending */ - static async getHistory({user, types=Object.values(this.PUNISHMENT_TYPES)}) { + static async getHistory({user, types=Object.values(this.SENTENCE_TYPES)}) { return (await admin.firestore() .collection(this.COLLECTION_PATH) .where('uid', '==', user.id) @@ -329,14 +330,14 @@ export class PunishmentService { } /** - * Retrieve all the punishments issued by a moderator + * Retrieve all the sentences issued by a moderator * @param {Object} args Destructured args - * @param {User} args.user moderator to query punishment history for - * @param {Array} [args.types] Array of valid `PUNISHMENT_TYPE`s to be filtered. Defaults to all types + * @param {User} args.user moderator to query sentence history for + * @param {Array} [args.types] Array of valid `SENTENCE_TYPE`s to be filtered. Defaults to all types * @returns {Promise[]>} - * Array of documents for the moderator's punishment history, sorted by date issued, descending + * Array of documents for the moderator's sentence history, sorted by date issued, descending */ - static async getModHistory({user, types=Object.values(this.PUNISHMENT_TYPES)}) { + static async getModHistory({user, types=Object.values(this.SENTENCE_TYPES)}) { return (await admin.firestore() .collection(this.COLLECTION_PATH) .where('issued_by', '==', user.id) @@ -345,23 +346,23 @@ export class PunishmentService { } /** - * Generate a punishment history embed for a given user + * Generate a sentence history embed for a given user * @param {Object} args Destructured args * @param {LeylineBot} args.bot Leyline Bot class - * @param {User} args.user user to query punishment history for + * @param {User} args.user user to query sentence history for * @param {Array} args.history_docs Documents retrieved from `getHistory()` * @param {boolean} [args.mod] Whether the embed is for a mod issued history - * @returns {EmbedBase} embed with punishment history + * @returns {EmbedBase} embed with sentence history */ static generateHistoryEmbed({bot, user, history_docs, mod=false}) { const embed = new EmbedBase(bot, { title: mod - ? `Punishments Issued by ${user.tag} (${user.id})` - : `Punishment History for ${user.tag} (${user.id})`, - description: `**Total punishments: ${history_docs.length}**`, - }).Punish(); + ? `Sentences Issued by ${user.tag} (${user.id})` + : `Sentence History for ${user.tag} (${user.id})`, + description: `**Total sentences: ${history_docs.length}**`, + }).Sentence(); - //add each individual punishment to embed (25 fields max) + //add each individual sentence to embed (25 fields max) for(const doc of history_docs.slice(0, 25)) { const data = doc.data(); embed.fields.push({ @@ -382,14 +383,14 @@ export class PunishmentService { } /** - * Reverse a MUTE punishment to a user. + * Reverse a MUTE sentence to a user. * Discord logging is handeled in this function * @param {Object} args Destructured args * @param {LeylineBot} args.bot bot - * @param {string} args.id Punishment ID (Firestore doc id) + * @param {string} args.id Sentence ID (Firestore doc id) * @param {string} args.uid Target Discord user ID - * @param {string} args.issued_by ID of Discord Mod that issued the punishment - * @param {string} [args.reason] Reason punishment was issued + * @param {string} args.issued_by ID of Discord Mod that issued the sentence + * @param {string} [args.reason] Reason sentence was issued * @returns {Promise} Resolves to true if successfully executed */ static async unmuteUser({bot, id, uid, issued_by, reason=null}= {}) { @@ -404,11 +405,11 @@ export class PunishmentService { //generate embed to modify it later const embed = new EmbedBase(bot, { - title: 'Punishment Expired', + title: 'Sentence Expired', fields: [ { name: 'Type', - value: this.PUNISHMENT_TYPES.MUTE, + value: this.SENTENCE_TYPES.MUTE, inline: true, }, { @@ -418,18 +419,18 @@ export class PunishmentService { }, { name: '\u200b', value: '\u200b', inline: true }, { - name: 'Reason', - value: reason ?? 'No reason given', + name: 'Target', + value: bot.formatUser(member?.user), inline: true, }, { - name: 'Target', - value: bot.formatUser(member?.user), + name: 'Reason', + value: reason ?? 'No reason given', inline: true, }, { name: '\u200b', value: '\u200b', inline: true }, ], - }).Punish(); + }).Sentence(); if(!member) { embed.description = `⚠ I was unable to find the user in the server`; @@ -443,10 +444,10 @@ export class PunishmentService { await bot.logDiscord({embed}); return false; } - //remove punishment + //remove sentence await member.roles.remove( MUTED_ROLE, - `Scheduled unmute for punishment ${id} issued by ${issuer?.tag || 'Unknown User'}` + `Scheduled unmute for sentence ${id} issued by ${issuer?.tag || 'Unknown User'}` ); //log removal @@ -456,32 +457,32 @@ export class PunishmentService { } /** - * Reverse a BAN punishment to a user. + * Reverse a BAN sentence to a user. * Discord logging is handeled in this function * @param {Object} args Destructured args * @param {LeylineBot} args.bot bot - * @param {string} args.id Punishment ID (Firestore doc id) + * @param {string} args.id Sentence ID (Firestore doc id) * @param {string} args.uid Target Discord user ID - * @param {string} args.issued_by ID of Discord Mod that issued the punishment - * @param {string} [args.reason] Reason punishment was issued + * @param {string} args.issued_by ID of Discord Mod that issued the sentence + * @param {string} [args.reason] Reason sentence was issued * @returns {Promise} Resolves to true if successfully executed */ static async unbanUser({bot, id, uid, issued_by, reason=null} = {}) { const issuer = await bot.users.fetch(issued_by); - //remove punishment, store resolved user + //remove sentence, store resolved user const user = await bot.leyline_guild.bans.remove( uid, - `Scheduled unban for punishment ${id} issued by ${issuer?.tag || 'Unknown User'}` + `Scheduled unban for sentence ${id} issued by ${issuer?.tag || 'Unknown User'}` ) || await bot.users.fetch(uid); //log removal await bot.logDiscord({embed: new EmbedBase(bot, { - title: 'Punishment Expired', + title: 'Sentence Expired', fields: [ { name: 'Type', - value: this.PUNISHMENT_TYPES.BAN, + value: this.SENTENCE_TYPES.BAN, inline: true, }, { @@ -491,18 +492,18 @@ export class PunishmentService { }, { name: '\u200b', value: '\u200b', inline: true }, { - name: 'Reason', - value: reason ?? 'No reason given', + name: 'Target', + value: bot.formatUser(user), inline: true, }, { - name: 'Target', - value: bot.formatUser(user), + name: 'Reason', + value: reason ?? 'No reason given', inline: true, }, { name: '\u200b', value: '\u200b', inline: true }, ], - }).Punish()}); + }).Sentence()}); return true; } diff --git a/commands/admin/punish.js b/commands/admin/sentence.js similarity index 70% rename from commands/admin/punish.js rename to commands/admin/sentence.js index eba00d7..4f5a7e2 100644 --- a/commands/admin/punish.js +++ b/commands/admin/sentence.js @@ -1,7 +1,7 @@ -import { Command, EmbedBase, PunishmentService } from '../../classes'; +import { Command, EmbedBase, SentenceService } from '../../classes'; import parse from 'parse-duration'; -class PunishmentSubCommand { +class SentenceSubCommand { constructor({ name, description, @@ -19,19 +19,19 @@ class PunishmentSubCommand { ...(target ? [{ type: 'USER', name: 'target', - description: 'The target user to punish', + description: 'The target user to sentence', required: true, }] : []), ...(duration ? [{ type: 'STRING', name: 'duration', - description: 'The duration of the punishment, eg: 7d. No duration means indefinite', + description: 'The duration of the sentence, eg: 7d. No duration means indefinite', required: false, }] : []), ...(reason ? [{ type: 'STRING', name: 'reason', - description: 'The reason the punishment was issued', + description: 'The reason the sentence was issued', required: false, }] : []), ], @@ -39,37 +39,37 @@ class PunishmentSubCommand { } } -class punish extends Command { +class sentence extends Command { constructor(bot) { super(bot, { - name: 'punish', - description: "Punishment utilities", + name: 'sentence', + description: "Sentence utilities", options: [ - new PunishmentSubCommand({ + new SentenceSubCommand({ name: 'warn', description: 'Issue a written warning to a Discord user', options: { duration: false, }, }), - new PunishmentSubCommand({ + new SentenceSubCommand({ name: 'mute', description: 'Issue a server mute to a Discord user', }), - new PunishmentSubCommand({ + new SentenceSubCommand({ name: 'kick', description: 'Remove a Discord user from the server', options: { duration: false, }, }), - new PunishmentSubCommand({ + new SentenceSubCommand({ name: 'ban', description: 'Issue a temporary or permanent ban to a Discord user', }), - new PunishmentSubCommand({ + new SentenceSubCommand({ name: 'history', - description: 'View the entire recorded punishment history for a Discord user', + description: 'View the entire recorded sentence history for a Discord user', options: { duration: false, reason: false, @@ -84,15 +84,15 @@ class punish extends Command { subcommands = { warn: async ({intr, type, user, reason}) => { const { bot } = this; - //issue punishment - await PunishmentService.warnUser({ + //issue sentence + await SentenceService.warnUser({ bot, mod: intr.user, user, reason, }); - //log punishment - /*await*/ PunishmentService.logPunishment({ + //log sentence + /*await*/ SentenceService.logSentence({ bot, user, mod: intr.user, @@ -100,21 +100,21 @@ class punish extends Command { reason, }); return bot.intrReply({intr, embed: new EmbedBase(bot, { - description: `⚖ **Punishment Successfully Issued**`, - }).Punish(), ephemeral: true}); + description: `⚖ **Sentence Successfully Issued**`, + }).Sentence(), ephemeral: true}); }, mute: async ({intr, type, user, expires, reason}) => { const { bot } = this; - //issue punishment - await PunishmentService.muteUser({ + //issue sentence + await SentenceService.muteUser({ bot, user, mod: intr.user, expires, reason, }); - //log punishment - /*await*/ PunishmentService.logPunishment({ + //log sentence + /*await*/ SentenceService.logSentence({ bot, user, mod: intr.user, @@ -123,20 +123,20 @@ class punish extends Command { reason, }); return bot.intrReply({intr, embed: new EmbedBase(bot, { - description: `⚖ **Punishment Successfully Issued**`, - }).Punish(), ephemeral: true}); + description: `⚖ **Sentence Successfully Issued**`, + }).Sentence(), ephemeral: true}); }, kick: async ({intr, type, user, reason}) => { const { bot } = this; - //issue punishment - await PunishmentService.kickUser({ + //issue sentence + await SentenceService.kickUser({ bot, mod: intr.user, user, reason, }); - //log punishment - /*await*/ PunishmentService.logPunishment({ + //log sentence + /*await*/ SentenceService.logSentence({ bot, user, mod: intr.user, @@ -144,21 +144,21 @@ class punish extends Command { reason, }); return bot.intrReply({intr, embed: new EmbedBase(bot, { - description: `⚖ **Punishment Successfully Issued**`, - }).Punish(), ephemeral: true}); + description: `⚖ **Sentence Successfully Issued**`, + }).Sentence(), ephemeral: true}); }, ban: async ({intr, type, user, expires, reason}) => { const { bot } = this; - //issue punishment - await PunishmentService.banUser({ + //issue sentence + await SentenceService.banUser({ bot, user, mod: intr.user, expires, reason, }); - //log punishment - /*await*/ PunishmentService.logPunishment({ + //log sentence + /*await*/ SentenceService.logSentence({ bot, user, mod: intr.user, @@ -167,21 +167,21 @@ class punish extends Command { reason, }); return bot.intrReply({intr, embed: new EmbedBase(bot, { - description: `⚖ **Punishment Successfully Issued**`, - }).Punish(), ephemeral: true}); + description: `⚖ **Sentence Successfully Issued**`, + }).Sentence(), ephemeral: true}); }, history: async ({intr, user}) => { const { bot } = this; const mod = bot.checkMod(user.id); return bot.intrReply({ intr, - embed: PunishmentService.generateHistoryEmbed({ + embed: SentenceService.generateHistoryEmbed({ bot, user, mod, history_docs: await (mod - ? PunishmentService.getModHistory({user}) - : PunishmentService.getHistory({user})), + ? SentenceService.getModHistory({user}) + : SentenceService.getHistory({user})), }), ephemeral: true }); @@ -190,7 +190,7 @@ class punish extends Command { async run({intr, opts}) { const { bot } = this; - const { PUNISHMENT_TYPES } = PunishmentService; + const { SENTENCE_TYPES: SENTENCE_TYPES } = SentenceService; //gotta store `type` separately to pass into subcmd, thanks 'use strict' :/ const [type, user, duration, reason] = [ @@ -207,7 +207,7 @@ class punish extends Command { }).Error()}); //send confirm prompt - if (Object.keys(PUNISHMENT_TYPES).includes(type)) + if (Object.keys(SENTENCE_TYPES).includes(type)) if (!(await bot.intrConfirm({ intr, ephemeral: true, @@ -215,8 +215,8 @@ class punish extends Command { description: ` ⚠ **Are you sure you want to ${type} ${bot.formatUser(user)} for \`${reason ?? 'No reason given'}\`?** - Is this punishment consistent with the official rules & moderation protocol? - Is this punishment consistent with the other punishments you've issued this past month? + Is this sentence consistent with the official rules & moderation protocol? + Is this sentence consistent with the other sentences you've issued this past month? `, }).Warn(), }))) @@ -224,7 +224,7 @@ class punish extends Command { intr, ephemeral: true, embed: new EmbedBase(bot, { - description: `❌ **Punishment canceled**`, + description: `❌ **Sentence canceled**`, }).Error(), }); @@ -247,7 +247,7 @@ class punish extends Command { user, expires, reason, - type: PUNISHMENT_TYPES[type], + type: SENTENCE_TYPES[type], }).catch(err => { bot.logger.error(err); bot.intrReply({intr, embed: new EmbedBase(bot).ErrorDesc(err.message), ephemeral: true}); @@ -255,4 +255,4 @@ class punish extends Command { } } -export default punish; +export default sentence; diff --git a/commands/development/embedtest.js b/commands/development/embedtest.js index 609a8a1..e4c682f 100644 --- a/commands/development/embedtest.js +++ b/commands/development/embedtest.js @@ -14,7 +14,7 @@ class embedtest extends Command { let expires = false; bot.intrReply({intr, embed: new EmbedBase(bot).ErrorDesc('I ran into an error!')}); //bot.intrReply({intr, embed: new EmbedBase(bot, { - // title: 'Punishment Issued', + // title: 'Justice Served', // fields: [ // { // name: 'Issued By', @@ -32,7 +32,7 @@ class embedtest extends Command { // inline: true, // }, // ], - //}).Punish(), files: ['./images/avatar-default.png']}); + //}).Sentence(), files: ['./images/avatar-default.png']}); } } diff --git a/config.js b/config.js index e982820..cd52ded 100644 --- a/config.js +++ b/config.js @@ -31,7 +31,7 @@ export default { private_log: '843892751276048394', public_log: '810265135419490314', reward_log: '872724760805654538', - punishment_log: '896539306800328734', //public punishment log + mod_log: '896539306800328734', //private mod log ama_vc: '794283967460147221', polls: '790063418898907166', }, @@ -83,7 +83,7 @@ export default { private_log: '858141871788392448', public_log: '858141914841481246', reward_log: '858141836513771550', - punishment_log: '892268882285457439', //public punishment log + mod_log: '892268882285457439', //private mod log ama_vc: '869993145499287604', polls: '877229054456107069', }, diff --git a/index.js b/index.js index 3238c33..a00dc3e 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ import { Intents, Message } from 'discord.js'; import admin from 'firebase-admin'; import klaw from 'klaw'; import path from 'path'; -import { LeylineBot, EmbedBase, CommunityPoll, ReactionCollector, PunishmentService, CloudConfig } from './classes'; +import { LeylineBot, EmbedBase, CommunityPoll, ReactionCollector, SentenceService, CloudConfig } from './classes'; //formally, dotenv shouldn't be used in prod, but because staging and prod share a VM, it's an option I elected to go with for convenience import { config as dotenv_config } from 'dotenv'; dotenv_config(); @@ -241,27 +241,27 @@ const postInit = async function () { return; })(); - //import punishments - await (async function importPunishments() { + //import sentences + await (async function importSentences() { let succesfully_imported = 0; - const punishments = await admin + const sentences = await admin .firestore() - .collection(PunishmentService.COLLECTION_PATH) + .collection(SentenceService.COLLECTION_PATH) .where('expires', '>', Date.now()) .get(); - for (const doc of punishments.docs) { + for (const doc of sentences.docs) { try { - PunishmentService.scheduleRemoval({ + SentenceService.scheduleRemoval({ bot, id: doc.id, data: { ...doc.data() }, }); succesfully_imported++; } catch (err) { - bot.logger.error(`importPunishments error with doc id ${doc.id}: ${err}`); + bot.logger.error(`importSentences error with doc id ${doc.id}: ${err}`); } } - bot.logger.log(`Imported ${succesfully_imported} punishments from Firestore`); + bot.logger.log(`Imported ${succesfully_imported} sentences from Firestore`); return; })(); From da24ffff2c3843044cea66e1124a272c16c8c17a Mon Sep 17 00:00:00 2001 From: elijaholmos <35435704+elijaholmos@users.noreply.github.com> Date: Mon, 25 Oct 2021 17:20:54 -0700 Subject: [PATCH 04/10] Split justice commands (#110) --- classes/{ => commands}/Command.js | 0 classes/commands/JusticeCommand.js | 93 +++++++++++++++++++++++++++++ classes/index.js | 3 +- classes/services/SentenceService.js | 4 +- commands/admin/justice/ban.js | 66 ++++++++++++++++++++ commands/admin/justice/history.js | 63 +++++++++++++++++++ commands/admin/justice/kick.js | 67 +++++++++++++++++++++ commands/admin/justice/mute.js | 66 ++++++++++++++++++++ commands/admin/justice/warn.js | 67 +++++++++++++++++++++ 9 files changed, 426 insertions(+), 3 deletions(-) rename classes/{ => commands}/Command.js (100%) create mode 100644 classes/commands/JusticeCommand.js create mode 100644 commands/admin/justice/ban.js create mode 100644 commands/admin/justice/history.js create mode 100644 commands/admin/justice/kick.js create mode 100644 commands/admin/justice/mute.js create mode 100644 commands/admin/justice/warn.js diff --git a/classes/Command.js b/classes/commands/Command.js similarity index 100% rename from classes/Command.js rename to classes/commands/Command.js diff --git a/classes/commands/JusticeCommand.js b/classes/commands/JusticeCommand.js new file mode 100644 index 0000000..ab0689e --- /dev/null +++ b/classes/commands/JusticeCommand.js @@ -0,0 +1,93 @@ +import { Command, EmbedBase } from ".."; +import parse from 'parse-duration'; + +export class JusticeCommand extends Command { + constructor(bot, { + name, + description, + sentence_type, //uppercase string, see SENTENCE_TYPES + options: { + target=true, + duration=true, + reason=true, + } = {}, + } = {}) { + super(bot, { + name, + description, + options: [ + ...(target ? [{ + type: 'USER', + name: 'target', + description: 'The target user to sentence', + required: true, + }] : []), + ...(duration ? [{ + type: 'STRING', + name: 'duration', + description: 'The duration of the sentence, eg: 7d. No duration means indefinite', + required: false, + }] : []), + ...(reason ? [{ + type: 'STRING', + name: 'reason', + description: 'The reason the sentence was issued', + required: false, + }] : []), + ], + category: 'admin', + }); + this.sentence_type = sentence_type; + } + + parseInput(opts) { + const [user, duration, reason] = [ + opts.getUser('target'), + opts.getString('duration'), + opts.getString('reason'), + ]; + + const parsed_dur = parse(duration); //ms + if(!!duration && !parsed_dur) + throw new Error(`That's not a valid duration`); + + //convert duration to epoch timestamp + const expires = !!duration + ? Date.now() + parsed_dur + : null; + + return {user, duration, reason, expires}; + } + + getModConfirmation({intr, user, reason}) { + const { bot, sentence_type } = this; + return bot.intrConfirm({ + intr, + ephemeral: true, + embed: new EmbedBase(bot, { + description: ` + ⚠ **Are you sure you want to ${sentence_type} ${bot.formatUser(user)} for \`${reason ?? 'No reason given'}\`?** + + Is this sentence consistent with the official rules & moderation protocol? + Is this sentence consistent with the other sentences you've issued this past month? + `, + }).Warn(), + }) + } + + checkEasterEgg({user, intr}) { + const { bot, sentence_type } = this; + return (sentence_type !== 'HISTORY' && user.id === '139120967208271872') + ? bot.intrReply({intr, embed: new EmbedBase(bot, { + title: 'Nice try!', + image: { + url: 'https://i.imgur.com/kAVql0f.jpg', + }, + }).Warn()}) + : false; + } + + executeSentence() { + throw new Error(`command ${this.constructor.name} does not have an execution implemented`); + } +} \ No newline at end of file diff --git a/classes/index.js b/classes/index.js index 072c3df..8d99317 100644 --- a/classes/index.js +++ b/classes/index.js @@ -1,4 +1,3 @@ -export * from './Command.js'; export * from './CommunityPoll.js'; export * from './LeylineUser.js'; export * from './Logger.js'; @@ -7,6 +6,8 @@ export * from './collectors/ReactionCollectorBase.js'; export * from './collectors/GoodActsReactionCollector.js'; export * from './collectors/KindWordsReactionCollector.js'; export * from './collectors/ReactionCollector.js'; +export * from './commands/Command.js'; +export * from './commands/JusticeCommand.js'; export * from './components/ConfirmInteraction.js'; export * from './components/EmbedBase.js'; export * from './events/DiscordEvent.js'; diff --git a/classes/services/SentenceService.js b/classes/services/SentenceService.js index b05f8c0..0bca12a 100644 --- a/classes/services/SentenceService.js +++ b/classes/services/SentenceService.js @@ -51,8 +51,8 @@ export class SentenceService { * @param {number} [args.timestamp] Unix timestamp of when sentence was issued. Defaults to `Date.now()` * @returns Resolves to added doc */ - static async logSentence({bot, user, mod, type, expires=null, reason=null, timestamp=Date.now()} = {}) { - const { SENTENCE_TYPES: SENTENCE_TYPES } = this; + static async logSentence({bot, user, mod, sentence_type: type, expires=null, reason=null, timestamp=Date.now()} = {}) { + const { SENTENCE_TYPES } = this; const embed = new EmbedBase(bot, { title: 'Justice Served', fields: [ diff --git a/commands/admin/justice/ban.js b/commands/admin/justice/ban.js new file mode 100644 index 0000000..fc7cc67 --- /dev/null +++ b/commands/admin/justice/ban.js @@ -0,0 +1,66 @@ +import { JusticeCommand, SentenceService, EmbedBase } from '../../../classes'; + +class ban extends JusticeCommand { + constructor(bot) { + super(bot, { + name: 'ban', + sentence_type: SentenceService.SENTENCE_TYPES.BAN, + description: 'Issue a temporary or permanent ban to a Discord user', + }); + } + + //Override parent + async executeSentence({intr, user, expires, reason}) { + const { bot, sentence_type } = this; + //issue sentence + await SentenceService.banUser({ + bot, + user, + mod: intr.user, + expires, + reason, + }); + //log sentence + /*await*/ SentenceService.logSentence({ + bot, + user, + mod: intr.user, + sentence_type, + expires, + reason, + }); + return bot.intrReply({intr, embed: new EmbedBase(bot, { + description: `⚖ **Sentence Successfully Issued**`, + }).Sentence(), ephemeral: true}); + } + + async run({intr, opts}) { + const { bot, sentence_type } = this; + const { SENTENCE_TYPES } = SentenceService; + + const { user, reason, expires } = super.parseInput(opts); + + //send confirm prompt if this is a sentence in SENTENCE_TYPES + if (Object.keys(SENTENCE_TYPES).includes(sentence_type)) + if (!(await super.getModConfirmation({intr, user, reason}))) + return bot.intrReply({ + intr, + ephemeral: true, + embed: new EmbedBase(bot, { + description: `❌ **Sentence canceled**`, + }).Error(), + }); + + + //easter egg + if(!!super.checkEasterEgg({intr, user})) return; + + this.executeSentence({intr, user, reason, expires}) + .catch(err => { + bot.logger.error(err); + bot.intrReply({intr, embed: new EmbedBase(bot).ErrorDesc(err.message), ephemeral: true}); + }); + } +} + +export default ban; diff --git a/commands/admin/justice/history.js b/commands/admin/justice/history.js new file mode 100644 index 0000000..44e3c39 --- /dev/null +++ b/commands/admin/justice/history.js @@ -0,0 +1,63 @@ +import { JusticeCommand, SentenceService, EmbedBase } from '../../../classes'; + +class history extends JusticeCommand { + constructor(bot) { + super(bot, { + name: 'history', + sentence_type: 'HISTORY', + description: 'View the entire recorded sentence history for a Discord user', + options: { + duration: false, + reason: false, + }, + }); + } + + //Override parent + async executeSentence({intr, user}) { + const { bot } = this; + const mod = bot.checkMod(user.id); //we use this twice below + return bot.intrReply({ + intr, + embed: SentenceService.generateHistoryEmbed({ + bot, + user, + mod, + history_docs: await (mod + ? SentenceService.getModHistory({user}) + : SentenceService.getHistory({user})), + }), + ephemeral: true + }); + } + + async run({intr, opts}) { + const { bot, sentence_type } = this; + const { SENTENCE_TYPES } = SentenceService; + + const { user } = super.parseInput(opts); + + //send confirm prompt if this is a sentence in SENTENCE_TYPES + if (Object.keys(SENTENCE_TYPES).includes(sentence_type)) + if (!(await super.getModConfirmation({intr, user}))) + return bot.intrReply({ + intr, + ephemeral: true, + embed: new EmbedBase(bot, { + description: `❌ **Sentence canceled**`, + }).Error(), + }); + + + //easter egg + if(!!super.checkEasterEgg({intr, user})) return; + + this.executeSentence({intr, user}) + .catch(err => { + bot.logger.error(err); + bot.intrReply({intr, embed: new EmbedBase(bot).ErrorDesc(err.message), ephemeral: true}); + }); + } +} + +export default history; diff --git a/commands/admin/justice/kick.js b/commands/admin/justice/kick.js new file mode 100644 index 0000000..cd5e190 --- /dev/null +++ b/commands/admin/justice/kick.js @@ -0,0 +1,67 @@ +import { JusticeCommand, SentenceService, EmbedBase } from '../../../classes'; + +class kick extends JusticeCommand { + constructor(bot) { + super(bot, { + name: 'kick', + sentence_type: SentenceService.SENTENCE_TYPES.KICK, + description: 'Remove a Discord user from the server', + options: { + duration: false, + }, + }); + } + + //Override parent + async executeSentence({intr, user, reason}) { + const { bot, sentence_type } = this; + //issue sentence + await SentenceService.kickUser({ + bot, + user, + mod: intr.user, + reason, + }); + //log sentence + /*await*/ SentenceService.logSentence({ + bot, + user, + mod: intr.user, + sentence_type, + reason, + }); + return bot.intrReply({intr, embed: new EmbedBase(bot, { + description: `⚖ **Sentence Successfully Issued**`, + }).Sentence(), ephemeral: true}); + } + + async run({intr, opts}) { + const { bot, sentence_type } = this; + const { SENTENCE_TYPES } = SentenceService; + + const { user, reason } = super.parseInput(opts); + + //send confirm prompt if this is a sentence in SENTENCE_TYPES + if (Object.keys(SENTENCE_TYPES).includes(sentence_type)) + if (!(await super.getModConfirmation({intr, user, reason}))) + return bot.intrReply({ + intr, + ephemeral: true, + embed: new EmbedBase(bot, { + description: `❌ **Sentence canceled**`, + }).Error(), + }); + + + //easter egg + if(!!super.checkEasterEgg({intr, user})) return; + + this.executeSentence({intr, user, reason}) + .catch(err => { + bot.logger.error(err); + bot.intrReply({intr, embed: new EmbedBase(bot).ErrorDesc(err.message), ephemeral: true}); + }); + } +} + +export default kick; diff --git a/commands/admin/justice/mute.js b/commands/admin/justice/mute.js new file mode 100644 index 0000000..46fd499 --- /dev/null +++ b/commands/admin/justice/mute.js @@ -0,0 +1,66 @@ +import { JusticeCommand, SentenceService, EmbedBase } from '../../../classes'; + +class mute extends JusticeCommand { + constructor(bot) { + super(bot, { + name: 'mute', + sentence_type: SentenceService.SENTENCE_TYPES.MUTE, + description: 'Issue a server mute to a Discord user', + }); + } + + //Override parent + async executeSentence({intr, user, expires, reason}) { + const { bot, sentence_type } = this; + //issue sentence + await SentenceService.muteUser({ + bot, + user, + mod: intr.user, + expires, + reason, + }); + //log sentence + /*await*/ SentenceService.logSentence({ + bot, + user, + mod: intr.user, + sentence_type, + expires, + reason, + }); + return bot.intrReply({intr, embed: new EmbedBase(bot, { + description: `⚖ **Sentence Successfully Issued**`, + }).Sentence(), ephemeral: true}); + } + + async run({intr, opts}) { + const { bot, sentence_type } = this; + const { SENTENCE_TYPES } = SentenceService; + + const { user, reason, expires } = super.parseInput(opts); + + //send confirm prompt if this is a sentence in SENTENCE_TYPES + if (Object.keys(SENTENCE_TYPES).includes(sentence_type)) + if (!(await super.getModConfirmation({intr, user, reason}))) + return bot.intrReply({ + intr, + ephemeral: true, + embed: new EmbedBase(bot, { + description: `❌ **Sentence canceled**`, + }).Error(), + }); + + + //easter egg + if(!!super.checkEasterEgg({intr, user})) return; + + this.executeSentence({intr, user, reason, expires}) + .catch(err => { + bot.logger.error(err); + bot.intrReply({intr, embed: new EmbedBase(bot).ErrorDesc(err.message), ephemeral: true}); + }); + } +} + +export default mute; diff --git a/commands/admin/justice/warn.js b/commands/admin/justice/warn.js new file mode 100644 index 0000000..168dbf4 --- /dev/null +++ b/commands/admin/justice/warn.js @@ -0,0 +1,67 @@ +import { JusticeCommand, SentenceService, EmbedBase } from '../../../classes'; + +class warn extends JusticeCommand { + constructor(bot) { + super(bot, { + name: 'warn', + sentence_type: SentenceService.SENTENCE_TYPES.WARN, + description: 'Issue a written warning to a Discord user', + options: { + duration: false, + }, + }); + } + + //Override parent + async executeSentence({intr, user, reason}) { + const { bot, sentence_type } = this; + //issue sentence + await SentenceService.warnUser({ + bot, + user, + mod: intr.user, + reason, + }); + //log sentence + /*await*/ SentenceService.logSentence({ + bot, + user, + mod: intr.user, + sentence_type, + reason, + }); + return bot.intrReply({intr, embed: new EmbedBase(bot, { + description: `⚖ **Sentence Successfully Issued**`, + }).Sentence(), ephemeral: true}); + } + + async run({intr, opts}) { + const { bot, sentence_type } = this; + const { SENTENCE_TYPES } = SentenceService; + + const { user, reason } = super.parseInput(opts); + + //send confirm prompt if this is a sentence in SENTENCE_TYPES + if (Object.keys(SENTENCE_TYPES).includes(sentence_type)) + if (!(await super.getModConfirmation({intr, user, reason}))) + return bot.intrReply({ + intr, + ephemeral: true, + embed: new EmbedBase(bot, { + description: `❌ **Sentence canceled**`, + }).Error(), + }); + + + //easter egg + if(!!super.checkEasterEgg({intr, user})) return; + + this.executeSentence({intr, user, reason}) + .catch(err => { + bot.logger.error(err); + bot.intrReply({intr, embed: new EmbedBase(bot).ErrorDesc(err.message), ephemeral: true}); + }); + } +} + +export default warn; From d293ac0c53c429e49de22a324dbc2f130903702e Mon Sep 17 00:00:00 2001 From: elijaholmos <35435704+elijaholmos@users.noreply.github.com> Date: Wed, 27 Oct 2021 15:39:50 -0700 Subject: [PATCH 05/10] ReactionCollectors: Add support for custom emojis (#114) Includes cloud config support --- classes/collectors/GoodActsReactionCollector.js | 2 +- classes/collectors/ReactionCollectorBase.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/classes/collectors/GoodActsReactionCollector.js b/classes/collectors/GoodActsReactionCollector.js index 0525cf8..1738540 100644 --- a/classes/collectors/GoodActsReactionCollector.js +++ b/classes/collectors/GoodActsReactionCollector.js @@ -48,7 +48,7 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { const { bot, msg } = this; try { //store the activity type for LLP award text both locally and in the cloud - msg._activityType = this.MOD_EMOJIS.find(e => e.unicode === reaction.emoji.name)?.keyword || 'Good Act'; + msg._activityType = this.MOD_EMOJIS.find(e => e.emoji_id === reaction.emoji.toString())?.keyword || 'Good Act'; await Firebase.approveCollector({collector: this, user, metadata: { activity_type: msg._activityType, }}); diff --git a/classes/collectors/ReactionCollectorBase.js b/classes/collectors/ReactionCollectorBase.js index 3d2143d..10ee62b 100644 --- a/classes/collectors/ReactionCollectorBase.js +++ b/classes/collectors/ReactionCollectorBase.js @@ -8,7 +8,7 @@ export class ReactionCollectorBase { get REACTION_LLP() { return CloudConfig.get('ReactionCollector').REACTION_LLP; } //LLP awarded for reacting // Emojis allowed in setupModReactionCollector /* Should be of structure { - unicode: String, + emoji_id: String, keyword?: String, add_on_msg?: boolean, } */ @@ -60,7 +60,7 @@ export class ReactionCollectorBase { if(!from_firestore) for (const reaction of this.MOD_EMOJIS) reaction?.add_on_msg !== false && - msg.react(reaction.unicode); + msg.react(reaction.emoji_id); //setup collector this.collector = msg @@ -74,7 +74,7 @@ export class ReactionCollectorBase { return reaction.users.remove(user); //this takes the place of the reactioncollector filter - if(!(bot.checkMod(user.id) && this.MOD_EMOJIS.some(e => e.unicode === reaction.emoji.name))) + if(!(bot.checkMod(user.id) && this.MOD_EMOJIS.some(e => e.emoji_id === reaction.emoji.toString()))) return; await msg.fetchReactions(); From 027bde4cad412990c38ef44fd52d37d4692beddb Mon Sep 17 00:00:00 2001 From: elijaholmos <35435704+elijaholmos@users.noreply.github.com> Date: Wed, 27 Oct 2021 17:50:58 -0700 Subject: [PATCH 06/10] Log submission approvals/rejections separately (#115) --- classes/LeylineBot.js | 13 +++ .../collectors/GoodActsReactionCollector.js | 32 +++++-- .../collectors/KindWordsReactionCollector.js | 16 +--- classes/collectors/ReactionCollectorBase.js | 94 ++++++++++++++++--- classes/components/EmbedBase.js | 2 +- commands/development/embedtest.js | 42 ++++----- config.js | 4 +- events/discord/messageCreate/goodActs.js | 32 +++++-- 8 files changed, 172 insertions(+), 63 deletions(-) diff --git a/classes/LeylineBot.js b/classes/LeylineBot.js index 4126858..070046c 100644 --- a/classes/LeylineBot.js +++ b/classes/LeylineBot.js @@ -128,6 +128,19 @@ export class LeylineBot extends Client { }); } + /** + * Sends a discord message on the bot's behalf to a private log channel, specific for submissions + * @param {Object} args + * @param {EmbedBase} args.embed Singular embed object to be sent in message + * @returns {Promise} Promise which resolves to the sent message + */ + async logSubmission({embed, ...options}) { + return (await this.channels.fetch(this.config.channels.submission_log)).send({ + embeds: [embed], + ...options, + }); + } + sendDisabledDmMessage(user) { this.msgBotChannel({ content: user.toString(), diff --git a/classes/collectors/GoodActsReactionCollector.js b/classes/collectors/GoodActsReactionCollector.js index 1738540..4a45e44 100644 --- a/classes/collectors/GoodActsReactionCollector.js +++ b/classes/collectors/GoodActsReactionCollector.js @@ -69,17 +69,35 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { }), }); - //log approval in bot log - bot.logDiscord({ - embed: new EmbedBase(bot, { + //Privately log approval + this.logApproval({ + user, + embed_data: { fields: [ { - name: `${this.media_type[0].toUpperCase() + this.media_type.slice(1)} Approved`, - value: `${bot.formatUser(user)} approved the [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> by ${bot.formatUser(msg.author)}` + name: 'Channel', + value: `<#${msg.channel.id}>`, + inline: true, + }, + { + name: 'Approved By', + value: bot.formatUser(user), + inline: true, + }, + { name: '\u200b', value: '\u200b', inline: true }, + { + name: 'Category', + value: msg._activityType, + inline: true, }, + { + name: 'Author', + value: bot.formatUser(msg.author), + inline: true, + }, + { name: '\u200b', value: '\u200b', inline: true }, ], - thumbnail: { url: this.media_type === 'photo' ? msg.attachments.first().url : this.media_placeholder }, - }), + }, }); this.setupApprovedCollector(); diff --git a/classes/collectors/KindWordsReactionCollector.js b/classes/collectors/KindWordsReactionCollector.js index 1c9dbac..510dafc 100644 --- a/classes/collectors/KindWordsReactionCollector.js +++ b/classes/collectors/KindWordsReactionCollector.js @@ -34,19 +34,9 @@ export class KindWordsReactionCollector extends ReactionCollectorBase { msg: msg.id, }); - //log approval in bot log - bot.logDiscord({ - embed: new EmbedBase(bot, { - fields: [ - { - name: `${this.media_type[0].toUpperCase() + this.media_type.slice(1)} Approved`, - value: `${bot.formatUser(user)} approved the [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> by ${bot.formatUser(msg.author)}` - }, - ], - }), - }); - - this.setupApprovedCollector(); + //Privately log approval + this.logApproval({user}) + .setupApprovedCollector(); //ensure user is connected to LL const is_author_connected = await Firebase.isUserConnectedToLeyline(msg.author.id); diff --git a/classes/collectors/ReactionCollectorBase.js b/classes/collectors/ReactionCollectorBase.js index 10ee62b..d2b9bca 100644 --- a/classes/collectors/ReactionCollectorBase.js +++ b/classes/collectors/ReactionCollectorBase.js @@ -126,10 +126,88 @@ export class ReactionCollectorBase { return this; } + /** + * Log an approval in a private log channel + * @param {Object} args Destructured arguments + * @param {User} args.user Discord.js `User` that approved the submission + * @param {Object} [args.embed_data] Embed data to be sent in the approval message + */ + logApproval({user, embed_data} = {}) { + const { bot, msg } = this; + + //log rejection using bot method + bot.logSubmission({ + embed: new EmbedBase(bot, { + title: 'Submission Approved', + url: msg.url, + fields: [ + { + name: 'Channel', + value: `<#${msg.channel.id}>`, + inline: true, + }, + { + name: 'Approved By', + value: bot.formatUser(user), + inline: true, + }, + { + name: 'Author', + value: bot.formatUser(msg.author), + inline: true, + }, + ], + thumbnail: { url: this.media_type === 'photo' ? msg.attachments.first().url : this.media_placeholder }, + ...embed_data, + }).Success(), + }); + + return this; + } + + /** + * Log a rejection in a private log channel + * @param {Object} args Destructured arguments + * @param {User} args.user Discord.js `User` that rejected the submission + * @param {Object} [args.embed_data] Embed data to be sent in the rejection message + */ + logRejection({user, embed_data} = {}) { + const { bot, msg } = this; + + //log rejection using bot method + bot.logSubmission({ + embed: new EmbedBase(bot, { + title: 'Submission Rejected', + url: msg.url, + fields: [ + { + name: 'Channel', + value: `<#${msg.channel.id}>`, + inline: true, + }, + { + name: 'Rejected By', + value: bot.formatUser(user), + inline: true, + }, + { + name: 'Author', + value: bot.formatUser(msg.author), + inline: true, + }, + ], + thumbnail: { url: this.media_type === 'photo' ? msg.attachments.first().url : this.media_placeholder }, + ...embed_data, + }).Error(), + }); + + return this; + } + /** * Soft-rejects a submission and logs actions appropriately - * @param {Object} [args] Destructured arguments - * @param {User} [args.user] Discord.js `User` that rejected the submission + * @param {Object} args Destructured arguments + * @param {User} args.user Discord.js `User` that rejected the submission */ rejectSubmission({user}) { const { bot, msg } = this; @@ -144,17 +222,7 @@ export class ReactionCollectorBase { msg.reactions.cache.each(reaction => reaction.users.remove(bot.user)); //log rejection - bot.logDiscord({ - embed: new EmbedBase(bot, { - fields: [ - { - name: `Submission Rejected`, - value: `${bot.formatUser(user)} rejected the [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> by ${bot.formatUser(msg.author)}` - }, - ], - thumbnail: { url: this.media_type === 'photo' ? msg.attachments.first().url : this.media_placeholder }, - }).Error(), - }); + this.logRejection({user}); return this; } diff --git a/classes/components/EmbedBase.js b/classes/components/EmbedBase.js index fdc32c6..eb68f84 100644 --- a/classes/components/EmbedBase.js +++ b/classes/components/EmbedBase.js @@ -50,7 +50,7 @@ export class EmbedBase extends MessageEmbed { } Success() { - this.color = 0x35de2f; + this.color = 0x31d64d; return this; } diff --git a/commands/development/embedtest.js b/commands/development/embedtest.js index e4c682f..2398098 100644 --- a/commands/development/embedtest.js +++ b/commands/development/embedtest.js @@ -12,27 +12,27 @@ class embedtest extends Command { async run({intr, opts}) { const { bot } = this; let expires = false; - bot.intrReply({intr, embed: new EmbedBase(bot).ErrorDesc('I ran into an error!')}); - //bot.intrReply({intr, embed: new EmbedBase(bot, { - // title: 'Justice Served', - // fields: [ - // { - // name: 'Issued By', - // value: bot.formatUser(intr.user), - // inline: true, - // }, - // { - // name: 'Reason', - // value: null ?? 'No reason given', - // inline: true, - // }, - // { - // name: 'Expires', - // value: !!expires ? bot.formatTimestamp(expires) : 'No expiration', - // inline: true, - // }, - // ], - //}).Sentence(), files: ['./images/avatar-default.png']}); + bot.intrReply({intr, embed: new EmbedBase(bot, { + title: 'Justice Served', + fields: [ + { + name: 'Issued By', + value: bot.formatUser(intr.user), + inline: true, + }, + { + name: 'Reason', + value: null ?? 'No reason given', + inline: true, + }, + { + name: 'Expires', + value: !!expires ? bot.formatTimestamp(expires) : 'No expiration', + inline: true, + }, + ], + color: 0x31d64d, + })}); } } diff --git a/config.js b/config.js index cd52ded..1c7aeb4 100644 --- a/config.js +++ b/config.js @@ -32,6 +32,7 @@ export default { public_log: '810265135419490314', reward_log: '872724760805654538', mod_log: '896539306800328734', //private mod log + submission_log: '903056355311644732', //private submission log ama_vc: '794283967460147221', polls: '790063418898907166', }, @@ -83,7 +84,8 @@ export default { private_log: '858141871788392448', public_log: '858141914841481246', reward_log: '858141836513771550', - mod_log: '892268882285457439', //private mod log + mod_log: '892268882285457439', //private mod log + submission_log: '903055896173764659', //private submission log ama_vc: '869993145499287604', polls: '877229054456107069', }, diff --git a/events/discord/messageCreate/goodActs.js b/events/discord/messageCreate/goodActs.js index d065458..e6d30a0 100644 --- a/events/discord/messageCreate/goodActs.js +++ b/events/discord/messageCreate/goodActs.js @@ -25,13 +25,31 @@ export default class extends DiscordEvent { rejectSubmission(msg) { const { bot } = this; - bot.logDiscord({embed: new EmbedBase(bot, { - fields:[{ - name: `Submission Auto-Rejected`, - value: `The [submission](${msg.url} 'click to view message') posted in <#${msg.channel.id}> by ${bot.formatUser(msg.author)} was automatically rejected because it did not contain a description.`, - }], - thumbnail: { url: msg.attachments.first().url }, - }).Error()}); + bot.logSubmission({ + embed: new EmbedBase(bot, { + title: 'Submission Auto-Rejected', + description: 'The submission did not contain a description', + url: msg.url, + fields: [ + { + name: 'Channel', + value: `<#${msg.channel.id}>`, + inline: true, + }, + { + name: 'Rejected By', + value: bot.formatUser(bot.user), + inline: true, + }, + { + name: 'Author', + value: bot.formatUser(msg.author), + inline: true, + }, + ], + thumbnail: { url: msg.attachments.first().url }, + }).Error(), + }); bot.sendDM({ user: msg.author, From 1f4fab8935b4fbed58160bc37c464481bfcd8842 Mon Sep 17 00:00:00 2001 From: elijaholmos <35435704+elijaholmos@users.noreply.github.com> Date: Fri, 29 Oct 2021 14:34:00 -0700 Subject: [PATCH 07/10] Leyline points rebrand to good points (#111) --- api/index.js | 2 +- api/{llp.js => points.js} | 24 +- classes/LeylineUser.js | 8 +- .../collectors/GoodActsReactionCollector.js | 40 +-- .../collectors/KindWordsReactionCollector.js | 24 +- classes/collectors/ReactionCollectorBase.js | 72 ++--- commands/admin/inspect.js | 4 +- commands/admin/sentence.js | 258 ------------------ commands/user/profile.js | 6 +- events/discord/messageCreate/goodActs.js | 2 +- 10 files changed, 91 insertions(+), 349 deletions(-) rename api/{llp.js => points.js} (74%) delete mode 100644 commands/admin/sentence.js diff --git a/api/index.js b/api/index.js index 4c46ef6..7f3e06b 100644 --- a/api/index.js +++ b/api/index.js @@ -1,6 +1,6 @@ export * from './polls'; export * from './collectors'; -export * from './llp'; +export * from './points'; export * from './nfts'; export * from './userConnections'; export * from './items'; diff --git a/api/llp.js b/api/points.js similarity index 74% rename from api/llp.js rename to api/points.js index d64dfe7..2636472 100644 --- a/api/llp.js +++ b/api/points.js @@ -1,12 +1,12 @@ import admin from 'firebase-admin'; /** - * Get the latest LLP balance of a Leyline user + * Get the latest GP balance of a Leyline user * Taken from webapp's api package `userService.ts` * @param {String} uid Leyline UID - * @returns {Promise} User's most up-to-date LLP balance + * @returns {Promise} User's most up-to-date GP balance */ -export const getLLPBalance = async function (uid) { +export const getPointsBalance = async function (uid) { const userDoc = await admin.firestore().doc(`users/${uid}`).get(); const userData = userDoc.data(); @@ -28,12 +28,12 @@ export const getLLPBalance = async function (uid) { } /** - * Get a Leyline user's total LLP earned + * Get a Leyline user's total GP earned * Taken from webapp's api `userService.ts` * @param {String} uid Leyline UID - * @returns {Promise} Total LLP earned up until this point + * @returns {Promise} Total GP earned up until this point */ -export const getTotalEarnedLLP = async function (uid) { +export const getTotalEarnedPoints = async function (uid) { const snapshotRef = await admin.firestore() .collection('leaderboards') .orderBy('snapshot_time', 'desc') @@ -51,11 +51,11 @@ export const getTotalEarnedLLP = async function (uid) { } /** - * Get a Leyline user's total LLP earned for volunteering + * Get a Leyline user's total GP earned for volunteering * @param {String} uid Leyline UID - * @returns {Promise} Approximate total LLP earned for volunteering + * @returns {Promise} Approximate total GP earned for volunteering */ -export const getVolunteerLLP = async function (uid) { +export const getVolunteerPoints = async function (uid) { const snapshot = await admin.firestore() .collection('leyline_points') .where('uid', '==', uid) @@ -66,12 +66,12 @@ export const getVolunteerLLP = async function (uid) { } /** - * Award a specific amount of LLP to a user, with an option to include transaction metadata + * Award a specific amount of GP to a user, with an option to include transaction metadata * @param {String} uid Leyline UID - * @param {Number} amount Amount of LLP to award + * @param {Number} amount Amount of GP to award * @param {Object} [metadata] Metadata for transaction. Should contain a `category` property */ -export const awardLLP = async function (uid, amount, metadata = {}) { +export const awardPoints = async function (uid, amount, metadata = {}) { return await admin.firestore().collection('leyline_points').add({ uid: uid, leyline_points: amount, diff --git a/classes/LeylineUser.js b/classes/LeylineUser.js index fa05a7f..2a6a3b0 100644 --- a/classes/LeylineUser.js +++ b/classes/LeylineUser.js @@ -1,7 +1,7 @@ import * as Firebase from '../api'; export class LeylineUser { - // hours donated, blood donated, llp balance, days slept/exercised + // hours donated, blood donated, gp balance, days slept/exercised // leaderboard positions? for all time // avatar, total items in inventory constructor(uid) { @@ -10,9 +10,9 @@ export class LeylineUser { this.uid = uid; //this.discord_uid = discord_uid || (await Firebase.getDiscordDoc(uid, true)).id; this._username = doc?.username; - this.llp = await Firebase.getLLPBalance(uid); - this.total_llp = await Firebase.getTotalEarnedLLP(uid); - this.volunteer_llp = await Firebase.getVolunteerLLP(uid); + this.gp = await Firebase.getPointsBalance(uid); + this.total_gp = await Firebase.getTotalEarnedPoints(uid); + this.volunteer_gp = await Firebase.getVolunteerPoints(uid); this.rankings = await Firebase.getUserRankings(uid); this.inventory = await Firebase.getInventoryItems(uid); this.profile_url = `https://leyline.gg/profile/${doc?.profile_id}`; diff --git a/classes/collectors/GoodActsReactionCollector.js b/classes/collectors/GoodActsReactionCollector.js index 4a45e44..61399b3 100644 --- a/classes/collectors/GoodActsReactionCollector.js +++ b/classes/collectors/GoodActsReactionCollector.js @@ -5,8 +5,8 @@ const CTA_ROLE = '853414453206188063'; //role to ping when photo is approved export class GoodActsReactionCollector extends ReactionCollectorBase { //override parent properties - get REACTION_LLP() { return CloudConfig.get('ReactionCollector').GoodActs.REACTION_LLP; } - get APPROVAL_LLP() { return CloudConfig.get('ReactionCollector').GoodActs.APPROVAL_LLP; } + get REACTION_GP() { return CloudConfig.get('ReactionCollector').GoodActs.REACTION_GP; } + get APPROVAL_GP() { return CloudConfig.get('ReactionCollector').GoodActs.APPROVAL_GP; } get MOD_EMOJIS() { return CloudConfig.get('ReactionCollector').GoodActs.MOD_EMOJIS; } media_placeholder //unfortunately, there is no easy way to extract the thumbnail from a video posted in discord @@ -29,14 +29,14 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { //check if user who reacted is msg author if(user.id === msg.author.id) return; - //award LLP to msg author + //award GP to msg author if(!(await Firebase.isUserConnectedToLeyline(msg.author.id))) this.handleUnconnectedAccount(msg.author, { - dm: `Your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> received a reaction, but because you have not connected your Discord & Leyline accounts, I couldn't award you any LLP! + dm: `Your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> received a reaction, but because you have not connected your Discord & Leyline accounts, I couldn't award you any GP! [Click here](${bot.connection_tutorial} 'How to connect your accounts') to view the account connection tutorial`, - log: `${bot.formatUser(msg.author)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> recevied a reaction, but I did not award them any LLP because they have not connected their Leyline & Discord accounts`, + log: `${bot.formatUser(msg.author)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> recevied a reaction, but I did not award them any GP because they have not connected their Leyline & Discord accounts`, }); - else await this.awardAuthorReactionLLP({ + else await this.awardAuthorReactionGP({ user: user, pog: `Discord \ ${msg._activityType} ${this.media_type[0].toUpperCase() + this.media_type.slice(1)} Received Reaction`, @@ -47,7 +47,7 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { async approveSubmission({user, reaction}) { const { bot, msg } = this; try { - //store the activity type for LLP award text both locally and in the cloud + //store the activity type for GP award text both locally and in the cloud msg._activityType = this.MOD_EMOJIS.find(e => e.emoji_id === reaction.emoji.toString())?.keyword || 'Good Act'; await Firebase.approveCollector({collector: this, user, metadata: { activity_type: msg._activityType, @@ -64,7 +64,7 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { msg, content: `<@&${CTA_ROLE}> 🚨 **NEW APPROVED ${this.media_type.toUpperCase()}!!** 🚨`, embed: new EmbedBase(bot, { - description: `A new ${this.media_type} was approved! Click [here](${msg.url} 'view message') to view the message.\nBe sure to react within 24 hours to get your LLP!`, + description: `A new ${this.media_type} was approved! Click [here](${msg.url} 'view message') to view the message.\nBe sure to react within 24 hours to get your GP!`, thumbnail: { url: this.media_type === 'photo' ? msg.attachments.first().url : this.media_placeholder }, }), }); @@ -106,21 +106,21 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { const is_author_connected = await Firebase.isUserConnectedToLeyline(msg.author.id); if(!is_author_connected) this.handleUnconnectedAccount(msg.author, { - dm: `Your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, but because you have not connected your Discord & Leyline accounts, I couldn't award you any LLP! + dm: `Your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, but because you have not connected your Discord & Leyline accounts, I couldn't award you any GP! [Click here](${bot.connection_tutorial} 'How to connect your accounts') to view the account connection tutorial`, - log: `${bot.formatUser(msg.author)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, but I did not award them any LLP because they have not connected their Leyline & Discord accounts`, + log: `${bot.formatUser(msg.author)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, but I did not award them any GP because they have not connected their Leyline & Discord accounts`, }); - // I could add some catch statements here and log them to Discord (for the awarding LLP process) + // I could add some catch statements here and log them to Discord (for the awarding GP process) - //award LLP to msg author - else await this.awardApprovalLLP({ + //award GP to msg author + else await this.awardApprovalGP({ user: msg.author, pog: `Discord \ ${msg._activityType} ${this.media_type[0].toUpperCase() + this.media_type.slice(1)} Approved`, }); - // --- Give LLP to the users that have already reacted --- + // --- Give GP to the users that have already reacted --- // --- (this includes the mod that just approved the msg) --- await msg.fetchReactions(); for(const old_reaction of [...msg.reactions.cache.values()]) { @@ -129,11 +129,11 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { //store user's reaction right away, because we do the same in the approved collector await this.storeUserReaction(old_user); - //award LLP to msg author for receiving a reaction (except on his own reaction) + //award GP to msg author for receiving a reaction (except on his own reaction) //(this goes above the continue statement below) is_author_connected && old_user.id !== msg.author.id && - await this.awardAuthorReactionLLP({ + await this.awardAuthorReactionGP({ user: old_user, pog: `Discord \ ${msg._activityType} ${this.media_type[0].toUpperCase() + this.media_type.slice(1)} Received Reaction`, @@ -142,15 +142,15 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { //exit if user is not connected to Leyline if(!(await Firebase.isUserConnectedToLeyline(old_user.id))) { this.handleUnconnectedAccount(old_user, { - dm: `You reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but because you have not connected your Discord & Leyline accounts, I couldn't award you any LLP! + dm: `You reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but because you have not connected your Discord & Leyline accounts, I couldn't award you any GP! [Click here](${bot.connection_tutorial} 'How to connect your accounts') to view the account connection tutorial`, - log: `${bot.formatUser(old_user)} reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but I did not award them any LLP because they have not connected their Leyline & Discord accounts`, + log: `${bot.formatUser(old_user)} reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but I did not award them any GP because they have not connected their Leyline & Discord accounts`, }); continue; } - //award LLP! - await this.awardReactionLLP({user: old_user}); + //award GP! + await this.awardReactionGP({user: old_user}); } } } diff --git a/classes/collectors/KindWordsReactionCollector.js b/classes/collectors/KindWordsReactionCollector.js index 510dafc..e791d83 100644 --- a/classes/collectors/KindWordsReactionCollector.js +++ b/classes/collectors/KindWordsReactionCollector.js @@ -3,8 +3,8 @@ import { EmbedBase, XPService, ReactionCollectorBase, CloudConfig } from '..'; export class KindWordsReactionCollector extends ReactionCollectorBase { //override parent properties - get REACTION_LLP() { return CloudConfig.get('ReactionCollector').KindWords.REACTION_LLP; } - get APPROVAL_LLP() { return CloudConfig.get('ReactionCollector').KindWords.APPROVAL_LLP; } + get REACTION_GP() { return CloudConfig.get('ReactionCollector').KindWords.REACTION_GP; } + get APPROVAL_GP() { return CloudConfig.get('ReactionCollector').KindWords.APPROVAL_GP; } get MOD_EMOJIS() { return CloudConfig.get('ReactionCollector').KindWords.MOD_EMOJIS; } constructor(bot, { msg, @@ -42,20 +42,20 @@ export class KindWordsReactionCollector extends ReactionCollectorBase { const is_author_connected = await Firebase.isUserConnectedToLeyline(msg.author.id); if(!is_author_connected) this.handleUnconnectedAccount(msg.author, { - dm: `Your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, but because you have not connected your Discord & Leyline accounts, I couldn't award you any LLP! + dm: `Your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, but because you have not connected your Discord & Leyline accounts, I couldn't award you any GP! [Click here](${bot.connection_tutorial} 'How to connect your accounts') to view the account connection tutorial`, - log: `${bot.formatUser(msg.author)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, but I did not award them any LLP because they have not connected their Leyline & Discord accounts`, + log: `${bot.formatUser(msg.author)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, but I did not award them any GP because they have not connected their Leyline & Discord accounts`, }); - // I could add some catch statements here and log them to Discord (for the awarding LLP process) + // I could add some catch statements here and log them to Discord (for the awarding GP process) - //award LLP to msg author - else await this.awardApprovalLLP({ + //award GP to msg author + else await this.awardApprovalGP({ user: msg.author, pog: `Discord Kind Words Shared`, }); - // --- Give LLP to the users that have already reacted --- + // --- Give GP to the users that have already reacted --- // --- (this includes the mod that just approved the msg) --- await msg.fetchReactions(); for (const old_reaction of [...msg.reactions.cache.values()]) { @@ -67,15 +67,15 @@ export class KindWordsReactionCollector extends ReactionCollectorBase { //exit if user is not connected to Leyline if(!(await Firebase.isUserConnectedToLeyline(old_user.id))) { this.handleUnconnectedAccount(old_user, { - dm: `You reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but because you have not connected your Discord & Leyline accounts, I couldn't award you any LLP! + dm: `You reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but because you have not connected your Discord & Leyline accounts, I couldn't award you any GP! [Click here](${bot.connection_tutorial} 'How to connect your accounts') to view the account connection tutorial`, - log: `${bot.formatUser(old_user)} reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but I did not award them any LLP because they have not connected their Leyline & Discord accounts`, + log: `${bot.formatUser(old_user)} reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but I did not award them any GP because they have not connected their Leyline & Discord accounts`, }); continue; } - //award LLP! - await this.awardReactionLLP({user: old_user}); + //award GP! + await this.awardReactionGP({user: old_user}); } } } diff --git a/classes/collectors/ReactionCollectorBase.js b/classes/collectors/ReactionCollectorBase.js index d2b9bca..cd27835 100644 --- a/classes/collectors/ReactionCollectorBase.js +++ b/classes/collectors/ReactionCollectorBase.js @@ -4,8 +4,8 @@ import { EmbedBase, CloudConfig } from '../'; export class ReactionCollectorBase { get APPROVAL_WINDOW() { return CloudConfig.get('ReactionCollector').APPROVAL_WINDOW; } //(hours) how long the mods have to approve a photo get REACTION_WINDOW() { return CloudConfig.get('ReactionCollector').REACTION_WINDOW; } //(hours) how long users have to react after collector approval - get APPROVAL_LLP() { return CloudConfig.get('ReactionCollector').APPROVAL_LLP; } //LLP awarded for approved post - get REACTION_LLP() { return CloudConfig.get('ReactionCollector').REACTION_LLP; } //LLP awarded for reacting + get APPROVAL_GP() { return CloudConfig.get('ReactionCollector').APPROVAL_GP; } //GP awarded for approved post + get REACTION_GP() { return CloudConfig.get('ReactionCollector').REACTION_GP; } //GP awarded for reacting // Emojis allowed in setupModReactionCollector /* Should be of structure { emoji_id: String, @@ -30,7 +30,7 @@ export class ReactionCollectorBase { /** * !! MUST BE IMPLEMENTED IN ALL SUBCLASSES !! * Method called after a reaction to an approved submission has been received. - * This method should specify actions in addition to reaction storage and reaction user "Moral Support" LLP awardal + * This method should specify actions in addition to reaction storage and reaction user "Moral Support" GP awardal * @param {Object} args Destructured args * @param {Reaction} args.reaction The received reaction * @param {User} args.user The user associated with the incoming reaction @@ -92,7 +92,7 @@ export class ReactionCollectorBase { } /** - * Sets up a specific ReactionCollector on an approved message that is designed to last for 24hrs and award LLP to users that react + * Sets up a specific ReactionCollector on an approved message that is designed to last for 24hrs and award GP to users that react * @param {Object} [options] Collector options * @param {Number} [options.duration] How long until the collector expires, in `ms` * @returns {ReactionCollectorBase} This class itself @@ -110,13 +110,13 @@ export class ReactionCollectorBase { //ensure user is connected to LL if(!(await Firebase.isUserConnectedToLeyline(user.id))) this.handleUnconnectedAccount(user, { - dm: `You reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but because you have not connected your Discord & Leyline accounts, I couldn't award you any LLP! + dm: `You reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but because you have not connected your Discord & Leyline accounts, I couldn't award you any GP! [Click here](${bot.connection_tutorial} 'How to connect your accounts') to view the account connection tutorial`, - log: `${bot.formatUser(user)} reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but I did not award them any LLP because they have not connected their Leyline & Discord accounts`, + log: `${bot.formatUser(user)} reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, but I did not award them any GP because they have not connected their Leyline & Discord accounts`, }); //this handles the whole awarding process - else await this.awardReactionLLP({user}); + else await this.awardReactionGP({user}); //await in case callback is async await this.reactionReceived({reaction, user}); @@ -249,7 +249,7 @@ export class ReactionCollectorBase { * @param {User} user user that has not connected their accounts * @param {Object} args Destructured args * @param {string} args.dm Specific reason why user should connect their accounts - * @param {string} args.log Discord log content to send under the embed title `LLP NOT Awarded` + * @param {string} args.log Discord log content to send under the embed title `GP NOT Awarded` * @returns */ handleUnconnectedAccount(user, {dm, log} = {}) { @@ -267,7 +267,7 @@ export class ReactionCollectorBase { embed: new EmbedBase(bot, { fields: [ { - name: `LLP __NOT__ Awarded`, + name: `GP __NOT__ Awarded`, value: log, }, ], @@ -277,16 +277,16 @@ export class ReactionCollectorBase { } /** - * Award LLP to a user for having a submission approved, and log the transaction appropriately. + * Award GP to a user for having a submission approved, and log the transaction appropriately. * Assumes all checks have been previously applied. * @param {Object} args Destructured arguments * @param {User} args.user Discord user - * @param {string} args.pog "Proof of good" - message to display in LLP history + * @param {string} args.pog "Proof of good" - message to display in GP history */ - async awardApprovalLLP({user, pog}) { + async awardApprovalGP({user, pog}) { const { bot, msg } = this; - await Firebase.awardLLP(await Firebase.getLeylineUID(user.id), this.APPROVAL_LLP, { + await Firebase.awardPoints(await Firebase.getLeylineUID(user.id), this.APPROVAL_GP, { category: pog, comment: `User's Discord ${this.media_type} (${msg.id}) was approved by ${user.tag}`, }); @@ -295,19 +295,19 @@ export class ReactionCollectorBase { bot.sendDM({user, embed: new EmbedBase(bot, { fields: [ { - name: `🎉 You Earned Some LLP!`, - value: `Your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, and you received **+${this.APPROVAL_LLP} LLP**!` + name: `🎉 You Earned Some GP!`, + value: `Your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, and you received **+${this.APPROVAL_GP} GP**!` }, ], })}); - //log LLP change in bot-log + //log GP change in bot-log bot.logDiscord({ embed: new EmbedBase(bot, { fields: [ { - name: `LLP Awarded`, - value: `${bot.formatUser(user)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, and I gave them **+${this.APPROVAL_LLP} LLP**`, + name: `GP Awarded`, + value: `${bot.formatUser(user)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> was approved, and I gave them **+${this.APPROVAL_GP} GP**`, }, ], }), @@ -316,17 +316,17 @@ export class ReactionCollectorBase { } /** - * Award LLP to a user for reacting to an approved submission, and log the transaction appropriately. + * Award GP to a user for reacting to an approved submission, and log the transaction appropriately. * Assumes all checks have been previously applied. * @param {Object} args Destructured arguments * @param {User} args.user Discord user - * @param {string} [args.pog] "Proof of good" - message to display in LLP history + * @param {string} [args.pog] "Proof of good" - message to display in GP history */ - async awardReactionLLP({user, pog=`Discord Moral Support`}) { + async awardReactionGP({user, pog=`Discord Moral Support`}) { const { bot, msg } = this; - //new user reacted, award LLP - await Firebase.awardLLP(await Firebase.getLeylineUID(user.id), this.REACTION_LLP, { + //new user reacted, award GP + await Firebase.awardPoints(await Firebase.getLeylineUID(user.id), this.REACTION_GP, { category: pog, comment: `User reacted to Discord message (${msg.id})`, }); @@ -334,8 +334,8 @@ export class ReactionCollectorBase { bot.sendDM({user, embed: new EmbedBase(bot, { fields: [ { - name: `🎉 You Earned Some LLP!`, - value: `You reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, and received **+${this.REACTION_LLP} LLP**!` + name: `🎉 You Earned Some GP!`, + value: `You reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, and received **+${this.REACTION_GP} GP**!` }, ], })}); @@ -344,8 +344,8 @@ export class ReactionCollectorBase { embed: new EmbedBase(bot, { fields: [ { - name: `LLP Awarded`, - value: `${bot.formatUser(user)} reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, and I gave them **+${this.REACTION_LLP} LLP**`, + name: `GP Awarded`, + value: `${bot.formatUser(user)} reacted to the [${this.media_type}](${msg.url} 'click to view message') posted by ${bot.formatUser(msg.author)} in <#${msg.channel.id}>, and I gave them **+${this.REACTION_GP} GP**`, }, ], }), @@ -354,16 +354,16 @@ export class ReactionCollectorBase { } /** - * Award LLP to the author of an approved submission when someone else reacts, and log the transaction appropriately. + * Award GP to the author of an approved submission when someone else reacts, and log the transaction appropriately. * Assumes all checks have been previously applied. * @param {Object} args Destructured arguments * @param {User} args.user Discord user - * @param {string} args.pog "Proof of good" - message to display in LLP history + * @param {string} args.pog "Proof of good" - message to display in GP history */ - async awardAuthorReactionLLP({user, pog}) { + async awardAuthorReactionGP({user, pog}) { const { bot, msg } = this; - //new user reacted, award LLP - await Firebase.awardLLP(await Firebase.getLeylineUID(msg.author.id), this.REACTION_LLP, { + //new user reacted, award GP + await Firebase.awardPoints(await Firebase.getLeylineUID(msg.author.id), this.REACTION_GP, { category: pog, comment: `User's Discord ${this.media_type} (${msg.id}) received a reaction from ${user.tag}`, }); @@ -373,8 +373,8 @@ export class ReactionCollectorBase { embed: new EmbedBase(bot, { fields: [ { - name: `🎉 You Earned Some LLP!`, - value: `Someone reacted reacted to your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}>, and you received **+${this.REACTION_LLP} LLP**!` + name: `🎉 You Earned Some GP!`, + value: `Someone reacted reacted to your [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}>, and you received **+${this.REACTION_GP} GP**!` }, ], })}); @@ -383,8 +383,8 @@ export class ReactionCollectorBase { embed: new EmbedBase(bot, { fields: [ { - name: `LLP Awarded`, - value: `${bot.formatUser(msg.author)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> received a reaction, and I gave them **+${this.REACTION_LLP} LLP**`, + name: `GP Awarded`, + value: `${bot.formatUser(msg.author)}'s [${this.media_type}](${msg.url} 'click to view message') posted in <#${msg.channel.id}> received a reaction, and I gave them **+${this.REACTION_GP} GP**`, }, ], }), diff --git a/commands/admin/inspect.js b/commands/admin/inspect.js index 326e69b..2a451dd 100644 --- a/commands/admin/inspect.js +++ b/commands/admin/inspect.js @@ -53,8 +53,8 @@ class inspect extends Command { inline: true }, { - name: 'LLP Balance', - value: `${!!llid ? await Firebase.getLLPBalance(llid) : 'N/A'}`, + name: 'GP Balance', + value: `${!!llid ? await Firebase.getPointsBalance(llid) : 'N/A'}`, inline: true }, ], diff --git a/commands/admin/sentence.js b/commands/admin/sentence.js deleted file mode 100644 index 4f5a7e2..0000000 --- a/commands/admin/sentence.js +++ /dev/null @@ -1,258 +0,0 @@ -import { Command, EmbedBase, SentenceService } from '../../classes'; -import parse from 'parse-duration'; - -class SentenceSubCommand { - constructor({ - name, - description, - options: { - target=true, - duration=true, - reason=true, - } = {}, - } = {}) { - return { - type: 'SUB_COMMAND', - name, - description, - options: [ - ...(target ? [{ - type: 'USER', - name: 'target', - description: 'The target user to sentence', - required: true, - }] : []), - ...(duration ? [{ - type: 'STRING', - name: 'duration', - description: 'The duration of the sentence, eg: 7d. No duration means indefinite', - required: false, - }] : []), - ...(reason ? [{ - type: 'STRING', - name: 'reason', - description: 'The reason the sentence was issued', - required: false, - }] : []), - ], - } - } -} - -class sentence extends Command { - constructor(bot) { - super(bot, { - name: 'sentence', - description: "Sentence utilities", - options: [ - new SentenceSubCommand({ - name: 'warn', - description: 'Issue a written warning to a Discord user', - options: { - duration: false, - }, - }), - new SentenceSubCommand({ - name: 'mute', - description: 'Issue a server mute to a Discord user', - }), - new SentenceSubCommand({ - name: 'kick', - description: 'Remove a Discord user from the server', - options: { - duration: false, - }, - }), - new SentenceSubCommand({ - name: 'ban', - description: 'Issue a temporary or permanent ban to a Discord user', - }), - new SentenceSubCommand({ - name: 'history', - description: 'View the entire recorded sentence history for a Discord user', - options: { - duration: false, - reason: false, - } - }), - ], - category: 'admin', - //deferResponse: true, - }); - } - - subcommands = { - warn: async ({intr, type, user, reason}) => { - const { bot } = this; - //issue sentence - await SentenceService.warnUser({ - bot, - mod: intr.user, - user, - reason, - }); - //log sentence - /*await*/ SentenceService.logSentence({ - bot, - user, - mod: intr.user, - type, - reason, - }); - return bot.intrReply({intr, embed: new EmbedBase(bot, { - description: `⚖ **Sentence Successfully Issued**`, - }).Sentence(), ephemeral: true}); - }, - mute: async ({intr, type, user, expires, reason}) => { - const { bot } = this; - //issue sentence - await SentenceService.muteUser({ - bot, - user, - mod: intr.user, - expires, - reason, - }); - //log sentence - /*await*/ SentenceService.logSentence({ - bot, - user, - mod: intr.user, - type, - expires, - reason, - }); - return bot.intrReply({intr, embed: new EmbedBase(bot, { - description: `⚖ **Sentence Successfully Issued**`, - }).Sentence(), ephemeral: true}); - }, - kick: async ({intr, type, user, reason}) => { - const { bot } = this; - //issue sentence - await SentenceService.kickUser({ - bot, - mod: intr.user, - user, - reason, - }); - //log sentence - /*await*/ SentenceService.logSentence({ - bot, - user, - mod: intr.user, - type, - reason, - }); - return bot.intrReply({intr, embed: new EmbedBase(bot, { - description: `⚖ **Sentence Successfully Issued**`, - }).Sentence(), ephemeral: true}); - }, - ban: async ({intr, type, user, expires, reason}) => { - const { bot } = this; - //issue sentence - await SentenceService.banUser({ - bot, - user, - mod: intr.user, - expires, - reason, - }); - //log sentence - /*await*/ SentenceService.logSentence({ - bot, - user, - mod: intr.user, - type, - expires, - reason, - }); - return bot.intrReply({intr, embed: new EmbedBase(bot, { - description: `⚖ **Sentence Successfully Issued**`, - }).Sentence(), ephemeral: true}); - }, - history: async ({intr, user}) => { - const { bot } = this; - const mod = bot.checkMod(user.id); - return bot.intrReply({ - intr, - embed: SentenceService.generateHistoryEmbed({ - bot, - user, - mod, - history_docs: await (mod - ? SentenceService.getModHistory({user}) - : SentenceService.getHistory({user})), - }), - ephemeral: true - }); - }, - }; - - async run({intr, opts}) { - const { bot } = this; - const { SENTENCE_TYPES: SENTENCE_TYPES } = SentenceService; - - //gotta store `type` separately to pass into subcmd, thanks 'use strict' :/ - const [type, user, duration, reason] = [ - opts.getSubcommand().toUpperCase(), - opts.getUser('target'), - opts.getString('duration'), - opts.getString('reason'), - ]; - const parsed_dur = parse(duration); //ms - - if(!!duration && !parsed_dur) - return bot.intrReply({intr, embed: new EmbedBase(bot, { - description: `❌ **That's not a valid duration**`, - }).Error()}); - - //send confirm prompt - if (Object.keys(SENTENCE_TYPES).includes(type)) - if (!(await bot.intrConfirm({ - intr, - ephemeral: true, - embed: new EmbedBase(bot, { - description: ` - ⚠ **Are you sure you want to ${type} ${bot.formatUser(user)} for \`${reason ?? 'No reason given'}\`?** - - Is this sentence consistent with the official rules & moderation protocol? - Is this sentence consistent with the other sentences you've issued this past month? - `, - }).Warn(), - }))) - return bot.intrReply({ - intr, - ephemeral: true, - embed: new EmbedBase(bot, { - description: `❌ **Sentence canceled**`, - }).Error(), - }); - - //convert duration to epoch timestamp - const expires = !!duration - ? Date.now() + parsed_dur - : null; - - //easter egg - if(type !== 'HISTORY' && user.id === '139120967208271872') - return bot.intrReply({intr, embed: new EmbedBase(bot, { - title: 'Nice try!', - image: { - url: '', //to be updated later - }, - }).Warn()}); - - this.subcommands[type.toLowerCase()]({ - intr, - user, - expires, - reason, - type: SENTENCE_TYPES[type], - }).catch(err => { - bot.logger.error(err); - bot.intrReply({intr, embed: new EmbedBase(bot).ErrorDesc(err.message), ephemeral: true}); - }); - } -} - -export default sentence; diff --git a/commands/user/profile.js b/commands/user/profile.js index efffc4c..0160e0a 100644 --- a/commands/user/profile.js +++ b/commands/user/profile.js @@ -54,8 +54,8 @@ class profile extends Command { }, fields: [ { - name: `${bot.config.emoji.leyline_logo} Lifetime LLP`, - value: `**${user.total_llp}** Leyline Points\n\u200b`, /*newline for spacing*/ + name: `${bot.config.emoji.leyline_logo} Lifetime GP`, + value: `**${user.total_gp}** Good Points\n\u200b`, /*newline for spacing*/ inline: true, }, { @@ -118,7 +118,7 @@ class profile extends Command { }, { name: '👤 Leyline Volunteering', - value: `**${user.volunteer_llp || 0}** Leyline Points\n\u200b`, + value: `**${user.volunteer_gp || 0}** Good Points\n\u200b`, inline: true, }, { diff --git a/events/discord/messageCreate/goodActs.js b/events/discord/messageCreate/goodActs.js index e6d30a0..3cfe301 100644 --- a/events/discord/messageCreate/goodActs.js +++ b/events/discord/messageCreate/goodActs.js @@ -88,7 +88,7 @@ export default class extends DiscordEvent { embed: new EmbedBase(bot, { fields:[{ name: `Thank you for your submission!`, - value: `Please remember to connect your Leyline & Discord accounts so you can receive LLP if your [submission](${msg.url}) is approved! + value: `Please remember to connect your Leyline & Discord accounts so you can receive GP if your [submission](${msg.url}) is approved! [Click here](${bot.connection_tutorial} 'How to connect your accounts') to view the account connection tutorial.`, }], }), From 99c0139f6545871044f96f6d228937bebb96784c Mon Sep 17 00:00:00 2001 From: elijaholmos <35435704+elijaholmos@users.noreply.github.com> Date: Fri, 29 Oct 2021 15:24:32 -0700 Subject: [PATCH 08/10] Migrate to discord.js 13.3.0 (#117) --- .../collectors/GoodActsReactionCollector.js | 2 - .../collectors/KindWordsReactionCollector.js | 2 - classes/collectors/ReactionCollectorBase.js | 2 - commands/admin/awardnft.js | 4 + commands/admin/role.js | 2 +- commands/admin/sudosay.js | 4 + package-lock.json | 238 +++++++++++------- package.json | 2 +- 8 files changed, 151 insertions(+), 105 deletions(-) diff --git a/classes/collectors/GoodActsReactionCollector.js b/classes/collectors/GoodActsReactionCollector.js index 61399b3..580181c 100644 --- a/classes/collectors/GoodActsReactionCollector.js +++ b/classes/collectors/GoodActsReactionCollector.js @@ -170,5 +170,3 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { return super.loadMessageCache(doc); } }; - - diff --git a/classes/collectors/KindWordsReactionCollector.js b/classes/collectors/KindWordsReactionCollector.js index e791d83..ded1103 100644 --- a/classes/collectors/KindWordsReactionCollector.js +++ b/classes/collectors/KindWordsReactionCollector.js @@ -86,5 +86,3 @@ export class KindWordsReactionCollector extends ReactionCollectorBase { } catch(err) { return bot.logger.error(err); } } } - - diff --git a/classes/collectors/ReactionCollectorBase.js b/classes/collectors/ReactionCollectorBase.js index cd27835..6b3c934 100644 --- a/classes/collectors/ReactionCollectorBase.js +++ b/classes/collectors/ReactionCollectorBase.js @@ -472,5 +472,3 @@ export class ReactionCollectorBase { return this; } } - - diff --git a/commands/admin/awardnft.js b/commands/admin/awardnft.js index 55d50b3..616c831 100644 --- a/commands/admin/awardnft.js +++ b/commands/admin/awardnft.js @@ -42,6 +42,10 @@ class awardnft extends Command { name: 'channel', description: 'The voice channel where all members inside it will receive an NFT', required: true, + channelTypes: [ + 'GUILD_VOICE', + 'GUILD_STAGE_VOICE', + ], }, ], }, diff --git a/commands/admin/role.js b/commands/admin/role.js index 15a88fe..92fe3d2 100644 --- a/commands/admin/role.js +++ b/commands/admin/role.js @@ -74,7 +74,7 @@ class role extends Command { async run({intr, opts}) { const { bot } = this; - const mem = (await bot.leyline_guild.members.fetch()).get(opts.getUser('user').id); + const mem = await bot.leyline_guild.members.fetch(opts.getUser('user')); if(!mem) return bot.intrReply({intr, embed: new EmbedBase(bot, { description: `❌ **I couldn't find that user**`, }).Error()}); diff --git a/commands/admin/sudosay.js b/commands/admin/sudosay.js index 63b94de..3f5610e 100644 --- a/commands/admin/sudosay.js +++ b/commands/admin/sudosay.js @@ -11,6 +11,10 @@ class sudosay extends Command { name: 'channel', description: 'The text channel where the bot will send the message', required: true, + channelTypes: [ + 'GUILD_TEXT', + 'GUILD_NEWS', + ], }, { type: 'STRING', diff --git a/package-lock.json b/package-lock.json index ee27bdb..deaad7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@google-cloud/pubsub": "^2.16.1", "chalk": "^4.1.1", "dedent": "^0.7.0", - "discord.js": "^13.2.0-dev.1629202082.9a833b1", + "discord.js": "^13.3.0", "dotenv": "^10.0.0", "firebase-admin": "^9.8.0", "klaw": "^3.0.0", @@ -25,27 +25,28 @@ } }, "node_modules/@discordjs/builders": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.5.0.tgz", - "integrity": "sha512-HP5y4Rqw68o61Qv4qM5tVmDbWi4mdTFftqIOGRo33SNPpLJ1Ga3KEIR2ibKofkmsoQhEpLmopD1AZDs3cKpHuw==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.1.tgz", + "integrity": "sha512-kYJMvZ/BjRD1/6G2t1pQop2yoJNUmYvvKeG4mOBUCHFmfb7WIeBFmN/eSiP3cVSfRx3lbNiyxkdd5JzhjQnGbg==", "dependencies": { - "@sindresorhus/is": "^4.0.1", - "discord-api-types": "^0.22.0", - "ow": "^0.27.0", + "@sindresorhus/is": "^4.2.0", + "discord-api-types": "^0.24.0", + "ow": "^0.28.1", "ts-mixer": "^6.0.0", - "tslib": "^2.3.0" + "tslib": "^2.3.1" }, "engines": { - "node": ">=14.0.0", + "node": ">=16.0.0", "npm": ">=7.0.0" } }, "node_modules/@discordjs/collection": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.2.1.tgz", - "integrity": "sha512-vhxqzzM8gkomw0TYRF3tgx7SwElzUlXT/Aa41O7mOcyN6wIJfj5JmDWaO5XGKsGSsNx7F3i5oIlrucCCWV1Nog==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.3.2.tgz", + "integrity": "sha512-dMjLl60b2DMqObbH1MQZKePgWhsNe49XkKBZ0W5Acl5uVV43SN414i2QfZwRI7dXAqIn8pEWD2+XXQFn9KWxqg==", "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0", + "npm": ">=7.0.0" } }, "node_modules/@discordjs/form-data": { @@ -399,18 +400,18 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "node_modules/@sapphire/async-queue": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.4.tgz", - "integrity": "sha512-fFrlF/uWpGOX5djw5Mu2Hnnrunao75WGey0sP0J3jnhmrJ5TAPzHYOmytD5iN/+pMxS+f+u/gezqHa9tPhRHEA==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.8.tgz", + "integrity": "sha512-Oi4EEi8vOne8RM1tCdQ3kYAtl/J6ztak3Th6wwGFqA2SVNJtedw196LjsLX0bK8Li8cwaljbFf08N+0zeqhkWQ==", "engines": { - "node": ">=14", - "npm": ">=6" + "node": ">=v14.0.0", + "npm": ">=7.0.0" } }, "node_modules/@sindresorhus/is": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", - "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==", "engines": { "node": ">=10" }, @@ -505,6 +506,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.3.tgz", "integrity": "sha512-/WbxFeBU+0F79z9RdEOXH4CsDga+ibi5M8uEYr91u3CkT/pdWcV8MCook+4wDPnZBexRdwWS+PiVZ2xJviAzcQ==" }, + "node_modules/@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, "node_modules/@types/qs": { "version": "6.9.6", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", @@ -525,9 +535,9 @@ } }, "node_modules/@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz", + "integrity": "sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==", "dependencies": { "@types/node": "*" } @@ -810,26 +820,27 @@ } }, "node_modules/discord-api-types": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz", - "integrity": "sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz", + "integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==", "engines": { "node": ">=12" } }, "node_modules/discord.js": { - "version": "13.2.0-dev.1629202082.9a833b1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.2.0-dev.1629202082.9a833b1.tgz", - "integrity": "sha512-EXghzG7XVd/gZy0+Zj3Irz2kw7wwuWOT9WV2Nv7E9ihdJaPniXEf+HKwG2DDKZGttRxvYkloEu0Z5Ky9IS6FPw==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.3.0.tgz", + "integrity": "sha512-kZcDVrQRTuzjRx99/Xl9HF1Kt7xNkiN4Gwvk1hNmLRAn+7Syzw9XTkQZdOPXLpijhbTNsZcdAaMxgvTmtyNdyA==", "dependencies": { - "@discordjs/builders": "^0.5.0", - "@discordjs/collection": "^0.2.1", + "@discordjs/builders": "^0.8.1", + "@discordjs/collection": "^0.3.2", "@discordjs/form-data": "^3.0.1", - "@sapphire/async-queue": "^1.1.4", - "@types/ws": "^7.4.7", - "discord-api-types": "^0.22.0", + "@sapphire/async-queue": "^1.1.8", + "@types/node-fetch": "^2.5.12", + "@types/ws": "^8.2.0", + "discord-api-types": "^0.24.0", "node-fetch": "^2.6.1", - "ws": "^7.5.1" + "ws": "^8.2.3" }, "engines": { "node": ">=16.6.0", @@ -958,6 +969,19 @@ "@google-cloud/storage": "^5.3.0" } }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -1619,15 +1643,15 @@ } }, "node_modules/ow": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz", - "integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.28.1.tgz", + "integrity": "sha512-1EZTywPZeUKac9gD7q8np3Aj+V54kvfIcjNEVNDSbG2Ys5xA5foW2HquvMMqgyWGLqIFMlc0Iq/HmyMHqN48sA==", "dependencies": { - "@sindresorhus/is": "^4.0.1", + "@sindresorhus/is": "^4.2.0", "callsites": "^3.1.0", "dot-prop": "^6.0.1", "lodash.isequal": "^4.5.0", - "type-fest": "^1.2.1", + "type-fest": "^2.3.4", "vali-date": "^1.0.0" }, "engines": { @@ -1917,16 +1941,16 @@ "integrity": "sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==" }, "node_modules/tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.5.2.tgz", + "integrity": "sha512-WMbytmAs5PUTqwGJRE+WoRrD2S0bYFtHX8k4Y/1l18CG5kqA3keJud9pPQ/r30FE9n8XRFCXF9BbccHIZzRYJw==", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2030,11 +2054,11 @@ } }, "node_modules/ws": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", - "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", @@ -2111,21 +2135,21 @@ }, "dependencies": { "@discordjs/builders": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.5.0.tgz", - "integrity": "sha512-HP5y4Rqw68o61Qv4qM5tVmDbWi4mdTFftqIOGRo33SNPpLJ1Ga3KEIR2ibKofkmsoQhEpLmopD1AZDs3cKpHuw==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.8.1.tgz", + "integrity": "sha512-kYJMvZ/BjRD1/6G2t1pQop2yoJNUmYvvKeG4mOBUCHFmfb7WIeBFmN/eSiP3cVSfRx3lbNiyxkdd5JzhjQnGbg==", "requires": { - "@sindresorhus/is": "^4.0.1", - "discord-api-types": "^0.22.0", - "ow": "^0.27.0", + "@sindresorhus/is": "^4.2.0", + "discord-api-types": "^0.24.0", + "ow": "^0.28.1", "ts-mixer": "^6.0.0", - "tslib": "^2.3.0" + "tslib": "^2.3.1" } }, "@discordjs/collection": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.2.1.tgz", - "integrity": "sha512-vhxqzzM8gkomw0TYRF3tgx7SwElzUlXT/Aa41O7mOcyN6wIJfj5JmDWaO5XGKsGSsNx7F3i5oIlrucCCWV1Nog==" + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.3.2.tgz", + "integrity": "sha512-dMjLl60b2DMqObbH1MQZKePgWhsNe49XkKBZ0W5Acl5uVV43SN414i2QfZwRI7dXAqIn8pEWD2+XXQFn9KWxqg==" }, "@discordjs/form-data": { "version": "3.0.1", @@ -2425,14 +2449,14 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" }, "@sapphire/async-queue": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.4.tgz", - "integrity": "sha512-fFrlF/uWpGOX5djw5Mu2Hnnrunao75WGey0sP0J3jnhmrJ5TAPzHYOmytD5iN/+pMxS+f+u/gezqHa9tPhRHEA==" + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.8.tgz", + "integrity": "sha512-Oi4EEi8vOne8RM1tCdQ3kYAtl/J6ztak3Th6wwGFqA2SVNJtedw196LjsLX0bK8Li8cwaljbFf08N+0zeqhkWQ==" }, "@sindresorhus/is": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz", - "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" }, "@tootallnate/once": { "version": "1.1.2", @@ -2518,6 +2542,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.0.3.tgz", "integrity": "sha512-/WbxFeBU+0F79z9RdEOXH4CsDga+ibi5M8uEYr91u3CkT/pdWcV8MCook+4wDPnZBexRdwWS+PiVZ2xJviAzcQ==" }, + "@types/node-fetch": { + "version": "2.5.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz", + "integrity": "sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, "@types/qs": { "version": "6.9.6", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz", @@ -2538,9 +2571,9 @@ } }, "@types/ws": { - "version": "7.4.7", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", - "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz", + "integrity": "sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==", "requires": { "@types/node": "*" } @@ -2741,23 +2774,24 @@ } }, "discord-api-types": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz", - "integrity": "sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg==" + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.24.0.tgz", + "integrity": "sha512-X0uA2a92cRjowUEXpLZIHWl4jiX1NsUpDhcEOpa1/hpO1vkaokgZ8kkPtPih9hHth5UVQ3mHBu/PpB4qjyfJ4A==" }, "discord.js": { - "version": "13.2.0-dev.1629202082.9a833b1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.2.0-dev.1629202082.9a833b1.tgz", - "integrity": "sha512-EXghzG7XVd/gZy0+Zj3Irz2kw7wwuWOT9WV2Nv7E9ihdJaPniXEf+HKwG2DDKZGttRxvYkloEu0Z5Ky9IS6FPw==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.3.0.tgz", + "integrity": "sha512-kZcDVrQRTuzjRx99/Xl9HF1Kt7xNkiN4Gwvk1hNmLRAn+7Syzw9XTkQZdOPXLpijhbTNsZcdAaMxgvTmtyNdyA==", "requires": { - "@discordjs/builders": "^0.5.0", - "@discordjs/collection": "^0.2.1", + "@discordjs/builders": "^0.8.1", + "@discordjs/collection": "^0.3.2", "@discordjs/form-data": "^3.0.1", - "@sapphire/async-queue": "^1.1.4", - "@types/ws": "^7.4.7", - "discord-api-types": "^0.22.0", + "@sapphire/async-queue": "^1.1.8", + "@types/node-fetch": "^2.5.12", + "@types/ws": "^8.2.0", + "discord-api-types": "^0.24.0", "node-fetch": "^2.6.1", - "ws": "^7.5.1" + "ws": "^8.2.3" } }, "dot-prop": { @@ -2862,6 +2896,16 @@ "node-forge": "^0.10.0" } }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -3382,15 +3426,15 @@ } }, "ow": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz", - "integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.28.1.tgz", + "integrity": "sha512-1EZTywPZeUKac9gD7q8np3Aj+V54kvfIcjNEVNDSbG2Ys5xA5foW2HquvMMqgyWGLqIFMlc0Iq/HmyMHqN48sA==", "requires": { - "@sindresorhus/is": "^4.0.1", + "@sindresorhus/is": "^4.2.0", "callsites": "^3.1.0", "dot-prop": "^6.0.1", "lodash.isequal": "^4.5.0", - "type-fest": "^1.2.1", + "type-fest": "^2.3.4", "vali-date": "^1.0.0" }, "dependencies": { @@ -3609,14 +3653,14 @@ "integrity": "sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==" }, "tslib": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" }, "type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.5.2.tgz", + "integrity": "sha512-WMbytmAs5PUTqwGJRE+WoRrD2S0bYFtHX8k4Y/1l18CG5kqA3keJud9pPQ/r30FE9n8XRFCXF9BbccHIZzRYJw==" }, "typedarray-to-buffer": { "version": "3.1.5", @@ -3695,9 +3739,9 @@ } }, "ws": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz", - "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "requires": {} }, "xdg-basedir": { diff --git a/package.json b/package.json index 2ce1ee3..2c37a1a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@google-cloud/pubsub": "^2.16.1", "chalk": "^4.1.1", "dedent": "^0.7.0", - "discord.js": "^13.2.0-dev.1629202082.9a833b1", + "discord.js": "^13.3.0", "dotenv": "^10.0.0", "firebase-admin": "^9.8.0", "klaw": "^3.0.0", From d74271ab3edc8dda9ff0eef5acce9d36fc1ce84b Mon Sep 17 00:00:00 2001 From: elijaholmos <35435704+elijaholmos@users.noreply.github.com> Date: Sat, 30 Oct 2021 13:27:32 -0700 Subject: [PATCH 09/10] Good Acts: manual submission approvals (#118) --- api/collectors.js | 14 +++ classes/LeylineBot.js | 24 +++- .../collectors/GoodActsReactionCollector.js | 20 +++- .../collectors/KindWordsReactionCollector.js | 11 +- classes/collectors/ReactionCollectorBase.js | 30 +++-- classes/commands/Command.js | 6 +- commands/moderator/ApproveGoodAct.js | 98 +++++++++++++++++ config.js | 104 +++++++++++------- .../discord/interactionCreate/contextMenu.js | 35 ++++++ index.js | 11 +- 10 files changed, 287 insertions(+), 66 deletions(-) create mode 100644 commands/moderator/ApproveGoodAct.js create mode 100644 events/discord/interactionCreate/contextMenu.js diff --git a/api/collectors.js b/api/collectors.js index 2c6101b..c44549d 100644 --- a/api/collectors.js +++ b/api/collectors.js @@ -102,3 +102,17 @@ export const getDiscordReactions = async function (uid) { doc.id === uid && res++; return res; } + +/** + * Get the Firestore document for a collector, if it exists + * @param {String} id The collector's id + * @returns {Promise} JSON collector data or undefined if non-existent + */ +export const fetchCollector = async function (id) { + const collector = await admin + .firestore() + .collection('discord/bot/reaction_collectors') + .doc(id) + .get(); + return collector.data(); +} diff --git a/classes/LeylineBot.js b/classes/LeylineBot.js index 070046c..9a34df3 100644 --- a/classes/LeylineBot.js +++ b/classes/LeylineBot.js @@ -1,4 +1,4 @@ -import { Client, Collection } from "discord.js"; +import { Client, Collection, Emoji } from "discord.js"; import config from '../config.js'; import { ConfirmInteraction, EmbedBase, Logger, CloudConfig } from "."; @@ -162,21 +162,21 @@ export class LeylineBot extends Client { * Replies to an interaction * @param {Object} args Destructured arguments * @param {Interaction} args.intr Discord.js `Interaction` - * @param {EmbedBase} [args.embed] Singular embed object to be included in reply + * @param {EmbedBase} [args.embed] Singular embed object to be included in reply. If unspecified, existing embeds are removed * @returns {Promise} The reply that was sent */ - intrReply({intr, embed, ...options}) { + intrReply({intr, embed=null, ...options}) { const payload = { - ...embed && { embeds: [embed] }, + embeds: !!embed ? [embed] : [], fetchReply: true, ...options, }; return (intr.deferred || intr.replied) ? intr.editReply(payload) : intr.reply(payload); } - intrUpdate({intr, embed, ...options}) { + intrUpdate({intr, embed=null, ...options}) { const payload = { - ...embed && { embeds: [embed] }, + embeds: !!embed ? [embed] : [], fetchReply: true, ...options, }; @@ -245,4 +245,16 @@ export class LeylineBot extends Client { return ``; } + /** + * Construct an Discord.js emoji from destructured parameters (such as Firestore data) + * @param {Object} args Destructured arguments, see `Emoji` constructor + * @returns {Emoji} + */ + constructEmoji({name, id, animated=false, ...other} = {}) { + return Object.assign(new Emoji(this, { + name, + id, + animated, + }), other); + } } diff --git a/classes/collectors/GoodActsReactionCollector.js b/classes/collectors/GoodActsReactionCollector.js index 580181c..1667b48 100644 --- a/classes/collectors/GoodActsReactionCollector.js +++ b/classes/collectors/GoodActsReactionCollector.js @@ -7,7 +7,14 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { //override parent properties get REACTION_GP() { return CloudConfig.get('ReactionCollector').GoodActs.REACTION_GP; } get APPROVAL_GP() { return CloudConfig.get('ReactionCollector').GoodActs.APPROVAL_GP; } - get MOD_EMOJIS() { return CloudConfig.get('ReactionCollector').GoodActs.MOD_EMOJIS; } + get MOD_EMOJIS() { + return CloudConfig.get('ReactionCollector').GoodActs.MOD_EMOJIS + .map(this.bot.constructEmoji) + .sort((a, b) => ( + {position: Number.MAX_VALUE, ...a}.position - + {position: Number.MAX_VALUE, ...b}.position + )); + } media_placeholder //unfortunately, there is no easy way to extract the thumbnail from a video posted in discord = 'https://cdn1.iconfinder.com/data/icons/growth-marketing/48/marketing_video_marketing-512.png'; @@ -44,11 +51,11 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { return; } - async approveSubmission({user, reaction}) { + async approveSubmission({user, approval_emoji}) { const { bot, msg } = this; try { //store the activity type for GP award text both locally and in the cloud - msg._activityType = this.MOD_EMOJIS.find(e => e.emoji_id === reaction.emoji.toString())?.keyword || 'Good Act'; + msg._activityType = this.MOD_EMOJIS.find(e => e.toString() === approval_emoji.toString())?.keyword || 'Good Act'; await Firebase.approveCollector({collector: this, user, metadata: { activity_type: msg._activityType, }}); @@ -157,8 +164,11 @@ export class GoodActsReactionCollector extends ReactionCollectorBase { //remove all reactions added by the bot msg.reactions.cache.each(reaction => reaction.users.remove(bot.user)); - return; - } catch(err) { return bot.logger.error(err); } + return this; + } catch(err) { + bot.logger.error(err); + return this; + } } // Overwrite of parent method diff --git a/classes/collectors/KindWordsReactionCollector.js b/classes/collectors/KindWordsReactionCollector.js index ded1103..c1cc368 100644 --- a/classes/collectors/KindWordsReactionCollector.js +++ b/classes/collectors/KindWordsReactionCollector.js @@ -5,7 +5,14 @@ export class KindWordsReactionCollector extends ReactionCollectorBase { //override parent properties get REACTION_GP() { return CloudConfig.get('ReactionCollector').KindWords.REACTION_GP; } get APPROVAL_GP() { return CloudConfig.get('ReactionCollector').KindWords.APPROVAL_GP; } - get MOD_EMOJIS() { return CloudConfig.get('ReactionCollector').KindWords.MOD_EMOJIS; } + get MOD_EMOJIS() { + return CloudConfig.get('ReactionCollector').KindWords.MOD_EMOJIS + .map(this.bot.constructEmoji) + .sort((a, b) => ( + {position: Number.MAX_VALUE, ...a}.position - + {position: Number.MAX_VALUE, ...b}.position + )); + } constructor(bot, { msg, }) { @@ -23,7 +30,7 @@ export class KindWordsReactionCollector extends ReactionCollectorBase { } // Callback specific to this Collector class - async approveSubmission({user, reaction}) { + async approveSubmission({user}) { const { bot, msg } = this; try { await Firebase.approveCollector({collector: this, user}); diff --git a/classes/collectors/ReactionCollectorBase.js b/classes/collectors/ReactionCollectorBase.js index 6b3c934..16041a6 100644 --- a/classes/collectors/ReactionCollectorBase.js +++ b/classes/collectors/ReactionCollectorBase.js @@ -8,11 +8,22 @@ export class ReactionCollectorBase { get REACTION_GP() { return CloudConfig.get('ReactionCollector').REACTION_GP; } //GP awarded for reacting // Emojis allowed in setupModReactionCollector /* Should be of structure { - emoji_id: String, + name: String, + id?: Snowflake, //for custom emoji + animated?: Boolean, keyword?: String, + position?: Number, //lower numbers get added to msg first add_on_msg?: boolean, } */ - get MOD_EMOJIS() { return CloudConfig.get('ReactionCollector').MOD_EMOJIS; } + get MOD_EMOJIS() { + return CloudConfig.get('ReactionCollector').MOD_EMOJIS + .map(this.bot.constructEmoji) + .sort((a, b) => ( + {position: Number.MAX_VALUE, ...a}.position - + {position: Number.MAX_VALUE, ...b}.position + )); + } + media_type = 'submission'; constructor(bot, { @@ -43,10 +54,10 @@ export class ReactionCollectorBase { * !! MUST BE IMPLEMENTED IN ALL SUBCLASSES !! * Method called after a submission has been approved * @param {Object} args Destructured args - * @param {Reaction} args.reaction The reaction that approved the submission + * @param {Emoji} args.approval_emoji The emoji of the reaction that approved the submission * @param {User} args.user The user that approved the submission */ - approveSubmission({reaction, user}) { + approveSubmission({approval_emoji, user}) { throw new Error(`${this.constructor.name} doesn't provide a ${this.reactionReceived.name} method`); } @@ -58,9 +69,9 @@ export class ReactionCollectorBase { //add initial reactions if(!from_firestore) - for (const reaction of this.MOD_EMOJIS) + for (const reaction of this.MOD_EMOJIS) reaction?.add_on_msg !== false && - msg.react(reaction.emoji_id); + msg.react(reaction.toString()); //setup collector this.collector = msg @@ -74,7 +85,7 @@ export class ReactionCollectorBase { return reaction.users.remove(user); //this takes the place of the reactioncollector filter - if(!(bot.checkMod(user.id) && this.MOD_EMOJIS.some(e => e.emoji_id === reaction.emoji.toString()))) + if(!(bot.checkMod(user.id) && this.MOD_EMOJIS.some(e => e.toString() === reaction.emoji.toString()))) return; await msg.fetchReactions(); @@ -86,7 +97,7 @@ export class ReactionCollectorBase { //end this modReactionCollector this.collector.stop(); - this.approveSubmission({reaction, user}); + this.approveSubmission({approval_emoji:reaction.emoji, user}); }); return this; } @@ -118,7 +129,7 @@ export class ReactionCollectorBase { //this handles the whole awarding process else await this.awardReactionGP({user}); - //await in case callback is async + //await in case child method is async await this.reactionReceived({reaction, user}); return; } catch(err) { return bot.logger.error(err); } @@ -233,6 +244,7 @@ export class ReactionCollectorBase { * @returns {boolean} `true` if an attachment was detected & stored, `false` otherwise */ processAttachment(url) { + url ||= ''; if (url.endsWith('.png') || url.endsWith('.jpg') || url.endsWith('.jpeg') || url.endsWith('.webp')) { this.media_type = 'photo'; return true; diff --git a/classes/commands/Command.js b/classes/commands/Command.js index c7b1a6b..ec2e27e 100644 --- a/classes/commands/Command.js +++ b/classes/commands/Command.js @@ -1,10 +1,11 @@ export class Command { constructor(bot, { name = null, - description = "No description provided.", + description = '', //cannot be empty for chat commands options = [], category, deferResponse = false, //for commands that take longer to run + type = 'CHAT_INPUT', ...other }) { this.bot = bot; @@ -12,8 +13,9 @@ export class Command { this.description = description; this.options = options; this.category = category; - this.defaultPermission = (this.category !== 'admin'); //lock admin cmds + this.defaultPermission = (this.category !== 'admin' && this.category !== 'moderator'); //lock admin cmds this.deferResponse = deferResponse; + this.type = type; Object.assign(this, other); } diff --git a/commands/moderator/ApproveGoodAct.js b/commands/moderator/ApproveGoodAct.js new file mode 100644 index 0000000..9b9b1be --- /dev/null +++ b/commands/moderator/ApproveGoodAct.js @@ -0,0 +1,98 @@ +import { Command, EmbedBase, ReactionCollector } from '../../classes'; +import * as Firebase from '../../api'; + +class ApproveGoodAct extends Command { + constructor(bot) { + super(bot, { + name: 'Approve Good Act', + category: 'moderator', + type: 'MESSAGE', + }); + } + + async run({intr, msg}) { + const { bot } = this; + + //Input Validations + const cloud_collector = await Firebase.fetchCollector(msg.id); + if(cloud_collector?.approved) + return bot.intrReply({ + intr, + embed: new EmbedBase(bot).ErrorDesc('This submission has already been approved'), + ephemeral: true, + }); + if(!!cloud_collector?.rejected_by) + return bot.intrReply({ + intr, + embed: new EmbedBase(bot).ErrorDesc('This submission has already been rejected'), + ephemeral: true, + }); + if(cloud_collector?.expires < Date.now()) + return bot.intrReply({ + intr, + embed: new EmbedBase(bot).ErrorDesc('The approval window for this submission has expired'), + ephemeral: true, + }); + + //Send confirmation prompt + if (!(await bot.intrConfirm({ + intr, + embed: new EmbedBase(bot, { + description: `⚠ **Are you sure you want to approve this Good Act?\nIt will permanently be on this user's Proof of Good ledger.**`, + }), + ephemeral: true, + }))) + return bot.intrReply({ + intr, + embed: new EmbedBase(bot, { + description: `❌ **Approval canceled**`, + }).Error(), + ephemeral: true, + }); + + //instantiate collector to get mod_emojis + const collector = new ReactionCollector(bot, { type: ReactionCollector.Collectors.GOOD_ACTS, msg }); + const response_intr = await (await bot.intrReply({ + intr, + content: `Select an approval category`, + components: [{ + components: [ + { + custom_id: 'category-menu', + disabled: false, + placeholder: 'Select a category...', + min_values: 1, + max_values: 1, + options: collector.MOD_EMOJIS + .filter(e => e.name !== '❌') + .map(emoji => ({ + label: emoji.keyword, + value: emoji?.id || emoji.name, + emoji: { + name: emoji.name, + id: emoji?.id, + animated: emoji?.animated, + }, + }) + ), + type: 3, + }, + ], + type: 1, + }], + })).awaitInteractionFromUser({user: intr.user}); + + //parse emoji and setup collector + const emoji = bot.emojis.resolve(response_intr.values[0]) ?? response_intr.values[0]; + await Firebase.createCollector(collector); //needs to be performed first + await collector.approveSubmission({user: intr.user, approval_emoji: emoji}); + collector.createThread(); + msg.react(emoji.toString()); + + return bot.intrReply({intr, embed: new EmbedBase(bot, { + description: `✅ **Good Act Approved**`, + }).Success(), content: '\u200b', components: [], ephemeral: true}); + } +} + +export default ApproveGoodAct; diff --git a/config.js b/config.js index 1c7aeb4..37e38c4 100644 --- a/config.js +++ b/config.js @@ -3,29 +3,49 @@ export default { get production() { return { // Which users/roles get access to all commands - command_perms: [ - { // Admin - id: '784875278593818694', - type: 'ROLE', - permission: true, - }, - { // Moderator - id: '752363863441145866', - type: 'ROLE', - permission: true, - }, - { // Leyline staff - id: '751919243062411385', - type: 'ROLE', - permission: true, - }, - { - // Ollog10 - id: '139120967208271872', - type: 'USER', - permission: true, - }, - ], + command_perms: { + moderator: [ + { // Admin + id: '784875278593818694', + type: 'ROLE', + permission: true, + }, + { // Moderator + id: '752363863441145866', + type: 'ROLE', + permission: true, + }, + { + // Ollog10 + id: '139120967208271872', + type: 'USER', + permission: true, + }, + ], + admin: [ + { // Admin + id: '784875278593818694', + type: 'ROLE', + permission: true, + }, + { // Moderator + id: '752363863441145866', + type: 'ROLE', + permission: true, + }, + { + // Ollog10 + id: '139120967208271872', + type: 'USER', + permission: true, + }, + { // Leyline staff + id: '751919243062411385', + type: 'ROLE', + permission: true, + }, + ], + }, leyline_guild_id: '751913089271726160', channels: { private_log: '843892751276048394', @@ -66,24 +86,34 @@ export default { get development() { return { // Which users/roles get access to all commands - command_perms: [ - { // Leyline staff - id: '858144532318519326', - type: 'ROLE', - permission: true, - }, - { - // Ollog10 - id: '139120967208271872', - type: 'USER', - permission: true, - }, - ], + command_perms: { + moderator: [ + { + // Moderator + id: '904095889558212660', + type: 'ROLE', + permission: true, + }, + ], + admin: [ + { + // Moderator + id: '904095889558212660', + type: 'ROLE', + permission: true, + }, + { // Leyline staff + id: '858144532318519326', + type: 'ROLE', + permission: true, + }, + ], + }, leyline_guild_id: '857839180608307210', channels: { private_log: '858141871788392448', public_log: '858141914841481246', - reward_log: '858141836513771550', + reward_log: '904081593029759006', mod_log: '892268882285457439', //private mod log submission_log: '903055896173764659', //private submission log ama_vc: '869993145499287604', diff --git a/events/discord/interactionCreate/contextMenu.js b/events/discord/interactionCreate/contextMenu.js new file mode 100644 index 0000000..7f33cdb --- /dev/null +++ b/events/discord/interactionCreate/contextMenu.js @@ -0,0 +1,35 @@ +import { DiscordEvent, EmbedBase } from "../../../classes"; + +export default class extends DiscordEvent { + constructor(bot) { + super(bot, { + name: 'contextMenu', + description: 'Receive, parse, and execute context menu commands', + event_type: 'interactionCreate', + }); + } + + async run(intr) { + const { bot } = this; + + if(!intr.isContextMenu()) return; + // Ignore commands sent by other bots or sent in DM + if(intr.user.bot || !intr.inGuild()) return; + + const command = bot.commands.get(intr.commandName.replaceAll(' ', '')); + + //defer reply because some commands take > 3 secs to run + command.deferResponse && + await intr.deferReply({fetchReply: true}); + + try { + bot.logger.cmd(`${intr.user.tag} (${intr.user.id}) ran context menu option ${intr.commandName}`); + await command.run({intr, user: intr.options.getMember('user'), msg: intr.options.getMessage('message')}); + } catch (err) { + bot.logger.error(`Error with ctx menu cmd ${intr.commandName}: ${err}`); + bot.intrReply({intr, embed: new EmbedBase(bot, { + description: `❌ **I ran into an error while trying to run that command**`, + }).Error()}); + } + } +}; diff --git a/index.js b/index.js index a00dc3e..d42a4c0 100644 --- a/index.js +++ b/index.js @@ -171,13 +171,14 @@ const postInit = async function () { const cmds = await bot.leyline_guild.commands.set(bot.commands.map(({ run, ...data }) => data)) .catch(err => bot.logger.error(`registerCommands err: ${err}`)); //turn each Command into an ApplicationCommand - cmds.forEach(cmd => bot.commands.get(cmd.name).setApplicationCommand(cmd)); + cmds.forEach(cmd => bot.commands.get(cmd.name.replaceAll(' ', '')).setApplicationCommand(cmd)); //Register command permissions await bot.leyline_guild.commands.permissions.set({ - fullPermissions: bot.commands.filter(c => c.category === 'admin') - .map(cmd => ({ - id: cmd.id, - permissions: bot.config.command_perms, + fullPermissions: bot.commands + .filter(c => Object.keys(bot.config.command_perms).includes(c.category)) + .map(({id, category}) => ({ + id, + permissions: bot.config.command_perms[category], })), }).catch(err => bot.logger.error(`registerCommands err: ${err}`)); bot.logger.log(`Registered ${cmds.size} out of ${bot.commands.size} commands to Discord`); From abd77dc826414dc164400ab7127c4c9edb54085f Mon Sep 17 00:00:00 2001 From: elijaholmos Date: Sat, 30 Oct 2021 14:20:12 -0700 Subject: [PATCH 10/10] chore: pre-release --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 815d527..30b4464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +# [2.5.0](https://github.com/Leyline-gg/leyline-discord/releases/tag/v2.5.0) (2021-10-31) + +## Dev Notes +The custom punishment system introduced previously has been rebranded to Justice System to better reflect the core values of Leyline. All "punish" wording has been changed to either "justice" or "sentence". +A lot of what we're doing at Leyline involves making new paths and breaking away from traditions and creating our own standard. Trial & error is a big component of this process, which means rebrands and revisions are more common than with a well-established company. In addition to the renaming of the Discord justice system, Leyline Points are also being renamed across the platform to Good Points. For more info on any of the changes we make, whether on Discord or not, the weekly AMA on Saturday is a great place to ask your questions in a casual environment and get responses from the core Leyline team. + +## New Features +- Good Acts submissions can now be manually approved by Moderators + - This feature makes use of Discord's context menus + - It can be used to manually approve any message as a Good Act + - When a message is manually approved, the same process for automatic approval occurs (see previous changelogs for details) + - Any users that reacted to the message prior to its approval will receive GP + - Any users that react to the message within 24h of its approval will receive GP + - The approved act will be recorded on the user's Proof of Good ledger +- Good Acts and Kind Words submissions now support custom emoji for moderator reactions +- Good Acts and Kind Words submission approvals & rejections are now logged in a private staff-only channel + - The log information displayed in the embeds has been reformatted + +## Existing Feature Changes +- All `punish` subcommands have been split into their own commands + - Example: `/punish warn` is now `/warn` +- All uses of the phrase "punish" have been renamed to either "justice" or "sentence" +- All front-facing references to LLP have been renamed to GP. This includes: + - `inspect` command + - `profile` command + - all logs and DMs associated with user submissions + - the LLP tag has been changed to GP +- Good Acts and Kind Words submission approvals & rejections are no longer logged in #bot-log +- `awardnft channel` subcommand now only takes voice or stage channels for the `channel` parameter +- `sudosay` command now only takes non-thread text channels for the `channel` parameter +- The green color for success embeds has been darkened slightly + +## Bug Fixes +- `profile` command displaying an incorrect GP value + # [2.4.0](https://github.com/Leyline-gg/leyline-discord/releases/tag/v2.4.0) (2021-10-12) ## Dev Notes diff --git a/package-lock.json b/package-lock.json index deaad7a..85bfff4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "leyline-discord", - "version": "2.5.0-dev", + "version": "2.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "2.5.0-dev", + "version": "2.5.0", "license": "ISC", "dependencies": { "@google-cloud/pubsub": "^2.16.1", diff --git a/package.json b/package.json index 2c37a1a..c3da30f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leyline-discord", - "version": "2.5.0-dev", + "version": "2.5.0", "description": "Leyline Discord bot", "main": "index.js", "type": "module",