Settings command, many small changes, improvements, and fixes

This commit is contained in:
Isaac 2021-03-23 23:32:24 +00:00
parent e6fa1d067f
commit ffc415354a
9 changed files with 182 additions and 45 deletions

View File

@ -1,4 +1,6 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
const fetch = require('node-fetch');
const { MessageAttachment } = require('discord.js');
module.exports = class SettingsCommand extends Command { module.exports = class SettingsCommand extends Command {
constructor(client) { constructor(client) {
@ -12,11 +14,108 @@ module.exports = class SettingsCommand extends Command {
}); });
} }
async execute({ guild, member, channel, args }, message) { async execute({ guild, channel, member }, message) {
let settings = await guild.settings; let settings = await guild.settings;
const i18n = this.client.i18n.get(settings.locale); const i18n = this.client.i18n.get(settings.locale);
channel.send('Settings!'); let attachments = [ ...message.attachments.values() ];
if (attachments.length >= 1) {
// load settings from json
let data = await (await fetch(attachments[0].url)).json();
settings.colour = data.colour;
settings.error_colour = data.error_colour;
settings.locale = data.locale;
settings.log_messages = data.log_messages;
settings.success_colour = data.success_colour;
await settings.save();
for (let c of data.categories) {
let permissions = [
...[
{
id: guild.roles.everyone,
deny: ['VIEW_CHANNEL']
}
],
...c.roles.map(r => {
return {
id: r,
allow: ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'ATTACH_FILES']
};
})
];
if (c.id) {
// existing category
let category = await this.client.db.models.Category.findOne({
where: {
id: c.id
}
});
category.name = c.name;
category.roles = c.roles;
category.save();
let cat_channel = await this.client.channels.fetch(c.id);
await cat_channel.edit({
name: c.name, // await cat_channel.setName(c.name);
permissionOverwrites: permissions // await cat_channel.overwritePermissions(permissions);
},
`Tickets category updated by ${member.user.tag}`
);
} else {
// create a new category
let created = await guild.channels.create(c.name, {
type: 'category',
reason: `Tickets category created by ${member.user.tag}`,
permissionOverwrites: permissions
});
await this.client.db.models.Category.create({
id: created.id,
name: c.name,
guild: guild.id,
roles: c.roles
});
}
}
channel.send(`\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``);
} else {
// upload settings as json to be modified
let data = {
categories: [],
colour: settings.colour,
error_colour: settings.error_colour,
locale: settings.locale,
log_messages: settings.log_messages,
success_colour: settings.success_colour,
};
let categories = await this.client.db.models.Category.findAll({
where: {
guild: guild.id
}
});
for (let c of categories) {
data.categories.push({
id: c.id,
name: c.name,
roles: c.roles
});
}
let attachment = new MessageAttachment(
Buffer.from(JSON.stringify(data, null, 2)),
`Settings for ${guild.name}.json`
);
channel.send(i18n('commands.settings.response'), {
files: [attachment]
});
}
} }
}; };

View File

@ -5,7 +5,6 @@ const {
const { path } = require('../utils/fs'); const { path } = require('../utils/fs');
const config = require('../../user/config'); const config = require('../../user/config');
const types = require('./dialects'); const types = require('./dialects');
const supported = Object.keys(types);
module.exports = async (log) => { module.exports = async (log) => {
@ -21,6 +20,7 @@ module.exports = async (log) => {
let type = (DB_TYPE || 'sqlite').toLowerCase(); let type = (DB_TYPE || 'sqlite').toLowerCase();
const supported = Object.keys(types);
if (!supported.includes(type)) { if (!supported.includes(type)) {
log.error(new Error(`DB_TYPE (${type}) is not a valid type`)); log.error(new Error(`DB_TYPE (${type}) is not a valid type`));
return process.exit(); return process.exit();
@ -68,10 +68,22 @@ module.exports = async (log) => {
primaryKey: true, primaryKey: true,
allowNull: false, allowNull: false,
}, },
locale: {
type: DataTypes.STRING,
defaultValue: config.locale
},
colour: { colour: {
type: DataTypes.STRING, type: DataTypes.STRING,
defaultValue: config.defaults.colour defaultValue: config.defaults.colour
}, },
success_colour: {
type: DataTypes.STRING,
defaultValue: 'GREEN'
},
error_colour: {
type: DataTypes.STRING,
defaultValue: 'RED'
},
log_messages: { log_messages: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
defaultValue: config.defaults.log_messages defaultValue: config.defaults.log_messages
@ -100,6 +112,9 @@ module.exports = async (log) => {
}, },
unique: 'name_guild' unique: 'name_guild'
}, },
roles: {
type: DataTypes.JSON
}
}, { }, {
tableName: DB_TABLE_PREFIX + 'categories' tableName: DB_TABLE_PREFIX + 'categories'
}); });

View File

@ -5,7 +5,7 @@ module.exports = {
switch (interaction.type) { switch (interaction.type) {
case 1: case 1:
client.log.debug('Received interaction ping, responding with pong'); client.log.info('Received interaction ping, responding with pong');
await client.api.interactions(interaction.id, interaction.token).callback.post({ await client.api.interactions(interaction.id, interaction.token).callback.post({
data: { data: {
type: 1, // PONG type: 1, // PONG
@ -13,7 +13,7 @@ module.exports = {
}); });
break; break;
case 2: case 2:
client.commands.handle(interaction); client.commands.handle(interaction, true);
break; break;
} }

View File

@ -2,13 +2,6 @@ module.exports = {
event: 'messageDelete', event: 'messageDelete',
execute: async (client, message) => { execute: async (client, message) => {
if (message.partial)
try {
await message.fetch();
} catch (err) {
return client.log.error(err);
}
let settings = await message.guild?.settings; let settings = await message.guild?.settings;
if (settings?.log_messages) { if (settings?.log_messages) {

View File

@ -2,12 +2,13 @@ module.exports = {
event: 'msgUpdate', event: 'msgUpdate',
execute: async (client, oldm, newm) => { execute: async (client, oldm, newm) => {
if (newm.partial) if (newm.partial) {
try { try {
await newm.fetch(); await newm.fetch();
} catch (err) { } catch (err) {
return client.log.error(err); return client.log.error(err);
} }
}
let settings = await newm.guild?.settings; let settings = await newm.guild?.settings;

View File

@ -23,6 +23,7 @@
"description": "Configure Discord Tickets" "description": "Configure Discord Tickets"
} }
}, },
"must_be_slash": "❌ This command must be invoked by a slash command interaction (`/%s`).",
"no_perm": "❌ You do not have the permissions required to use this command:\n%s", "no_perm": "❌ You do not have the permissions required to use this command:\n%s",
"support_only": "❌ You must be a member of staff to use this command." "support_only": "❌ You must be a member of staff to use this command."
} }

View File

@ -70,7 +70,7 @@ module.exports = class CommandManager {
if (typeof data.description !== 'string') if (typeof data.description !== 'string')
throw new TypeError(`Expected type of command description to be a string, got ${typeof data.description}`); throw new TypeError(`Expected type of command description to be a string, got ${typeof data.description}`);
if (data.name.length < 1 || data.name.length > 100) if (data.description.length < 1 || data.description.length > 100)
throw new TypeError('Length of description must be 3-32'); throw new TypeError('Length of description must be 3-32');
if (typeof data.options !== 'undefined' && !(data.options instanceof Array)) if (typeof data.options !== 'undefined' && !(data.options instanceof Array))
@ -139,9 +139,9 @@ module.exports = class CommandManager {
/** /**
* Execute a command * Execute a command
* @param {(Interaction|Message)} interaction - Command interaction, or message * @param {(Interaction|Message)} interaction_or_message - Command interaction or message
*/ */
async handle(interaction, slash) { async handle(interaction_or_message, slash) {
slash = slash === false ? false : true; slash = slash === false ? false : true;
let cmd_name, let cmd_name,
args = {}, args = {},
@ -151,54 +151,62 @@ module.exports = class CommandManager {
member_id; member_id;
if (slash) { if (slash) {
cmd_name = interaction.data.name; cmd_name = interaction_or_message.data.name;
guild_id = interaction.guild_id; guild_id = interaction_or_message.guild_id;
channel_id = interaction.channel_id; channel_id = interaction_or_message.channel_id;
member_id = interaction.member.user.id; member_id = interaction_or_message.member.user.id;
if (interaction.data.options) if (interaction_or_message.data.options)
interaction.data.options.forEach(({ name, value }) => args[name] = value); interaction_or_message.data.options.forEach(({ name, value }) => args[name] = value);
} else { } else {
cmd_name = interaction.content.match(/^tickets\/(\S+)/mi); cmd_name = interaction_or_message.content.match(/^tickets\/(\S+)/mi);
if (cmd_name) cmd_name = cmd_name[1]; if (cmd_name) cmd_name = cmd_name[1];
guild_id = interaction.guild.id; guild_id = interaction_or_message.guild.id;
channel_id = interaction.channel.id; channel_id = interaction_or_message.channel.id;
member_id = interaction.author.id; member_id = interaction_or_message.author.id;
} }
if (cmd_name === null || !this.commands.has(cmd_name)) if (cmd_name === null || !this.commands.has(cmd_name))
throw new Error(`Received "${cmd_name}" command invocation, but the command manager does not have a "${cmd_name}" command`); return this.client.log.warn(`Received "${cmd_name}" command invocation, but the command manager does not have a "${cmd_name}" command registered`);
data.args = args; data.args = args;
data.guild = await this.client.guilds.fetch(guild_id); data.guild = await this.client.guilds.fetch(guild_id);
data.channel = await this.client.channels.fetch(channel_id), data.channel = await this.client.channels.fetch(channel_id),
data.member = await data.guild.members.fetch(member_id); data.member = await data.guild.members.fetch(member_id);
const cmd = this.commands.get(cmd_name);
let settings = await data.guild.settings; let settings = await data.guild.settings;
if (!settings) settings = await data.guild.createSettings(); if (!settings) settings = await data.guild.createSettings();
const i18n = this.client.i18n.get(settings.locale); const i18n = this.client.i18n.get(settings.locale);
// if (cmd.staff_only) {} const cmd = this.commands.get(cmd_name);
if (cmd.slash && !slash) {
this.client.log.commands(`Blocking command execution for the "${cmd_name}" command as it was invoked by a message, not a slash command interaction_or_message.`);
try {
data.channel.send(i18n('must_be_slash', cmd_name)); // interaction_or_message.reply
} catch (err) {
this.client.log.warn('Failed to reply to blocked command invocation message');
}
return;
}
const no_perm = cmd.permissions instanceof Array const no_perm = cmd.permissions instanceof Array
&& !data.member.hasPermission(cmd.permissions); && !data.member.hasPermission(cmd.permissions);
if (no_perm) { if (no_perm) {
let perms = cmd.permissions.map(p => `\`${p}\``).join(', '); let perms = cmd.permissions.map(p => `\`${p}\``).join(', ');
let msg = i18n('no_perm', perms); let msg = i18n('no_perm', perms);
if (slash) return await cmd.sendResponse(interaction, msg, true); if (slash) return await cmd.respond(interaction_or_message, msg, true);
else return await interaction.channel.send(msg); else return await interaction_or_message.channel.send(msg);
} }
try { try {
if (slash) await cmd.acknowledge(interaction, true); // respond to discord if (slash) await cmd.acknowledge(interaction_or_message, true); // respond to discord
this.client.log.commands(`Executing "${cmd_name}" command (invoked by ${data.member.user.tag})`); this.client.log.commands(`Executing "${cmd_name}" command (invoked by ${data.member.user.tag})`);
await cmd.execute(data, interaction); // run the command await cmd.execute(data, interaction_or_message); // run the command
} catch (e) { } catch (e) {
this.client.log.warn(`(COMMANDS) An error occurred whilst executed the ${cmd_name} command`); this.client.log.warn(`An error occurred whilst executing the ${cmd_name} command`);
this.client.log.error(e); this.client.log.error(e);
} }

View File

@ -39,16 +39,36 @@ module.exports = class TicketManager extends EventEmitter {
/** /**
* Close a ticket * Close a ticket
* @param {string} ticket - The channel ID, or the ticket number * @param {string} ticket - The channel ID, or the ticket number
* @param {string} [closer] - ID of the member who is closing the ticket
*/ */
async close(ticket) { async close(ticket, closer) {
if (!this.client.channels.resolve(ticket)) {
let row = await this.client.db.Models.Ticket.findOne({
where: {
number: ticket
}
});
if (!row) throw new Error(`Could not find a ticket with number ${ticket}`);
ticket = row.id;
}
let row = await this.client.db.Models.Ticket.findOne({
where: {
id: ticket
}
});
if (!row) throw new Error(`Could not find a ticket with ID ${ticket}`);
this.emit('beforeClose', ticket, closer);
/**
*
*
* for each message in table, create entities
*
*
*/
} }
/**
* Close multiple tickets
* @param {string[]} tickets - An array of channel IDs to close **(does not accept ticket numbers)**
*/
async closeMultiple(tickets) {
}
}; };

View File

@ -25,12 +25,12 @@
module.exports = { module.exports = {
debug: false, debug: false,
defaults: { defaults: {
colour: '#009999', colour: '#009999', // https://discord.js.org/#/docs/main/stable/typedef/ColorResolvable
log_messages: true, // transcripts/archives will be empty if false log_messages: true, // transcripts/archives will be empty if false
prefix: '-', prefix: '-',
ticket_welcome: 'Hello {{name}}, thank you for creating a ticket. A member of staff will soon be available to assist you.\n\n__All messages in this channel are stored for future reference.__', ticket_welcome: 'Hello {{name}}, thank you for creating a ticket. A member of staff will soon be available to assist you.\n\n__All messages in this channel are stored for future reference.__',
}, },
locale: 'en-GB', locale: 'en-GB', // used for globals (such as commands) and the default guild locale
logs: { logs: {
enabled: true, enabled: true,
keep_for: 30 keep_for: 30