Finish "new" command, and other stuff

This commit is contained in:
Isaac
2021-04-05 18:38:00 +01:00
parent 01519df0cf
commit 4b3ba238bd
15 changed files with 369 additions and 255 deletions

33
src/commands/close.js Normal file
View File

@@ -0,0 +1,33 @@
const { MessageEmbed } = require('discord.js');
const Command = require('../modules/commands/command');
const { footer } = require('../utils/discord');
module.exports = class CloseCommand extends Command {
constructor(client) {
const i18n = client.i18n.get(client.config.locale);
super(client, {
internal: true,
name: i18n('commands.close.name'),
description: i18n('commands.close.description'),
aliases: [
i18n('commands.close.aliases.delete'),
],
process_args: false,
args: [
{
name: i18n('commands.close.args.ticket.name'),
description: i18n('commands.close.args.ticket.description'),
example: i18n('commands.close.args.ticket.example'),
required: false,
}
]
});
}
async execute(message, args) {
let settings = await message.guild.settings;
const i18n = this.client.i18n.get(settings.locale);
}
};

View File

@@ -1,5 +1,7 @@
const { MessageEmbed } = require('discord.js');
const Command = require('../modules/commands/command');
const { footer } = require('../utils/discord');
const { letters } = require('../utils/emoji');
module.exports = class NewCommand extends Command {
constructor(client) {
@@ -29,27 +31,131 @@ module.exports = class NewCommand extends Command {
let settings = await message.guild.settings;
const i18n = this.client.i18n.get(settings.locale);
let { count: cat_count, rows: categories } = await this.client.db.models.Category.findAndCountAll({
const editOrSend = async (msg, content) => {
if (msg) return await msg.edit(content);
else return await message.channel.send(content);
};
const create = async (cat_row, response) => {
let tickets = await this.client.db.models.Ticket.findAndCountAll({
where: {
category: cat_row.id,
creator: message.author.id,
open: true
}
});
if (tickets.count >= cat_row.max_per_member) {
if (cat_row.max_per_member === 1) {
response = await editOrSend(response,
new MessageEmbed()
.setColor(settings.error_colour)
.setAuthor(message.author.username, message.author.displayAvatarURL())
.setTitle(i18n('commands.new.response.has_a_ticket.title'))
.setDescription(i18n('commands.new.response.has_a_ticket.description', tickets.rows[0].id))
.setFooter(footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
);
} else {
let list = tickets.rows.map(row => {
if (row.topic) {
let description = row.topic.substring(0, 30);
let ellipses = description.length > 30 ? '...' : '';
return `<#${row.id}>: \`${description}${ellipses}\``;
} else {
return `<#${row.id}>`;
}
});
response = await editOrSend(response,
new MessageEmbed()
.setColor(settings.error_colour)
.setAuthor(message.author.username, message.author.displayAvatarURL())
.setTitle(i18n('commands.new.response.max_tickets.title', tickets.count))
.setDescription(i18n('commands.new.response.max_tickets.description', settings.command_prefix, list.join('\n')))
.setFooter(footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
);
}
} else {
let t_row = await this.client.tickets.create(message.guild.id, message.author.id, cat_row.id, args);
response = await editOrSend(response,
new MessageEmbed()
.setColor(settings.success_colour)
.setAuthor(message.author.username, message.author.displayAvatarURL())
.setTitle(i18n('commands.new.response.created.title'))
.setDescription(i18n('commands.new.response.created.description', `<#${t_row.id}>`))
.setFooter(footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
);
}
setTimeout(async () => {
await response.delete();
await message.delete();
}, 15000);
};
let categories = await this.client.db.models.Category.findAndCountAll({
where: {
guild: message.guild.id
}
});
switch (cat_count) {
case 0:
if (categories.count === 0) {
return await message.channel.send(
new MessageEmbed()
.setColor(settings.error_colour)
.setAuthor(message.author.username, message.author.displayAvatarURL())
.setTitle(i18n('commands.new.response.no_categories.title'))
.setDescription(i18n('commands.new.response.no_categories.description'))
.setFooter(settings.footer, message.guild.iconURL())
);
case 1:
break;
default:
} else if (categories.count === 1) {
create(categories.rows[0]);
} else {
let letters_array = Object.values(letters);
let category_list = categories.rows.map((category, i) => `${letters_array[i]} » ${category.name}`);
let collector_message = await message.channel.send(
new MessageEmbed()
.setColor(settings.colour)
.setAuthor(message.author.username, message.author.displayAvatarURL())
.setTitle(i18n('commands.new.response.select_category.title'))
.setDescription(i18n('commands.new.response.select_category.description', category_list.join('\n')))
.setFooter(footer(settings.footer, i18n('collector_expires_in', 30)), message.guild.iconURL())
);
for (let i in categories.rows) {
await collector_message.react(letters_array[i]);
}
const collector_filter = (reaction, user) => {
let allowed = letters_array.slice(0, categories.count);
return user.id === message.author.id && allowed.includes(reaction.emoji.name);
};
let collector = collector_message.createReactionCollector(collector_filter, {
time: 30000
});
collector.on('collect', async (reaction) => {
let index = letters_array.findIndex(value => value === reaction.emoji.name);
if (index === -1) return await collector_message.delete({ timeout: 15000 });
await collector_message.reactions.removeAll();
create(categories.rows[index], collector_message);
});
collector.on('end', async (collected) => {
if (collected.size === 0) {
await collector_message.reactions.removeAll();
await collector_message.edit(
new MessageEmbed()
.setColor(settings.error_colour)
.setAuthor(message.author.username, message.author.displayAvatarURL())
.setTitle(i18n('commands.new.response.select_category_timeout.title'))
.setDescription(i18n('commands.new.response.select_category_timeout.description', category_list.join('\n')))
.setFooter(footer(settings.footer, i18n('message_will_be_deleted_in', 15)), message.guild.iconURL())
);
collector_message.delete({ timeout: 15000 });
}
});
}
// this.client.tickets.create(message.guild.id, message.member.id, '825861413687787560', args.topic);
}
};

View File

@@ -31,6 +31,7 @@ module.exports = class SettingsCommand extends Command {
settings.colour = data.colour;
settings.command_prefix = data.command_prefix;
settings.error_colour = data.error_colour;
settings.footer = data.footer;
settings.locale = data.locale;
settings.log_messages = data.log_messages;
settings.success_colour = data.success_colour;
@@ -114,6 +115,7 @@ module.exports = class SettingsCommand extends Command {
colour: settings.colour,
command_prefix: settings.command_prefix,
error_colour: settings.error_colour,
footer: settings.footer,
locale: settings.locale,
log_messages: settings.log_messages,
success_colour: settings.success_colour,

View File

@@ -6,7 +6,7 @@ const { path } = require('../utils/fs');
const config = require('../../user/config');
const types = require('./dialects');
module.exports = async (log) => {
module.exports = async (client) => {
const {
DB_TYPE,
@@ -22,7 +22,7 @@ module.exports = async (log) => {
const supported = Object.keys(types);
if (!supported.includes(type)) {
log.error(new Error(`DB_TYPE (${type}) is not a valid type`));
client.log.error(new Error(`DB_TYPE (${type}) is not a valid type`));
return process.exit();
}
@@ -30,35 +30,35 @@ module.exports = async (log) => {
types[type].packages.forEach(pkg => require(pkg));
} catch {
let required = types[type].packages.map(i => `"${i}"`).join(' and ');
log.error(new Error(`Please install the package(s) for your selected database type: ${required}`));
client.log.error(new Error(`Please install the package(s) for your selected database type: ${required}`));
return process.exit();
}
let sequelize;
if (type === 'sqlite') {
log.info('Using SQLite storage');
client.log.info('Using SQLite storage');
sequelize = new Sequelize({
dialect: types[type].dialect,
storage: path('./user/database.sqlite'),
logging: text => log.debug(text)
logging: text => client.log.debug(text)
});
} else {
log.info(`Connecting to ${types[type].name} database...`);
client.log.info(`Connecting to ${types[type].name} database...`);
sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASS, {
dialect: types[type].dialect,
host: DB_HOST,
port: DB_PORT,
logging: text => log.debug(text)
logging: text => client.log.debug(text)
});
}
try {
await sequelize.authenticate();
log.success('Connected to database successfully');
client.log.success('Connected to database successfully');
} catch (error) {
log.warn('Failed to connect to database');
log.error(error);
client.log.warn('Failed to connect to database');
client.log.error(error);
return process.exit();
}
@@ -92,6 +92,10 @@ module.exports = async (log) => {
type: DataTypes.BOOLEAN,
defaultValue: config.defaults.log_messages
},
footer: {
type: DataTypes.STRING,
defaultValue: 'Discord Tickets by eartharoid'
},
}, {
tableName: DB_TABLE_PREFIX + 'guilds'
});
@@ -160,6 +164,10 @@ module.exports = async (log) => {
key: 'id'
},
},
topic: {
type: DataTypes.STRING,
allowNull: true,
},
creator: {
type: DataTypes.CHAR(18),
allowNull: false,

View File

@@ -97,15 +97,15 @@ class Bot extends Client {
/** The global bot configuration */
this.config = config;
/** A sequelize instance */
this.db = await require('./database')(log), // this.db.models.Ticket...
/** A leekslazylogger instance */
this.log = log;
/** An @eartharoid/i18n instance */
this.i18n = new I18n(path('./src/locales'), 'en-GB');
/** A sequelize instance */
this.db = await require('./database')(this), // this.db.models.Ticket...
this.setMaxListeners(this.config.max_listeners); // set the max listeners for each event
require('./updater')(this); // check for updates

View File

@@ -4,18 +4,26 @@
},
"cmd_usage": {
"args": {
"description": "**Description:**",
"example": "**Example:**"
"description": "**Description:** %s",
"example": "**Example:** `%s`"
},
"description": "**Usage:**\n`%s`\n\n**Example:**\n`%s`\n\nRequired arguments are prefixed with `❗`.",
"named_args": "This command uses named arguments.\n\n",
"title": "`%s` command usage"
},
"collector_expires_in": "Expires in %d seconds",
"commands": {
"close": {
"aliases": {
"delete": "delete"
},
"args": {
"ticket": {
"name": "ticket",
"description": "The number or a channel mention of the ticket to close",
"example": "217"
}
},
"description": "Close a ticket channel",
"name": "close",
"response": {
@@ -31,10 +39,6 @@
"open": "open"
},
"args": {
"category": {
"name": "category",
"description": "The category you would like to create a new ticket for"
},
"topic": {
"name": "topic",
"description": "The topic of the ticket",
@@ -46,11 +50,27 @@
"response": {
"created": {
"title": "✅ Ticket created",
"description": ""
"description": "Your ticket has been created: %s."
},
"has_a_ticket": {
"title": "❌ You already have an open ticket",
"description": "Please use your existing ticket (<#%s>) or close it before creating another."
},
"max_tickets": {
"title": "❌ You already have %d open tickets",
"description": "Please use `%sclose` to close any unneeded tickets.\n\n%s"
},
"no_categories": {
"title": "❌ Can't create ticket",
"description": "A server administrator must create at least one ticket category before a new ticket can be opened."
},
"select_category": {
"title": "Please select the ticket category",
"description": "Select the category most relevant to your ticket's topic:\n\n%s"
},
"select_category_timeout": {
"title": "❌ Reaction time expired",
"description": "You took too long to select the ticket category."
}
}
},
@@ -67,8 +87,9 @@
},
"command_execution_error": {
"title": "⚠️",
"description": "An unexpected error occurred during command execution.\nPlease ask a server administrator to check the console output / logs for details."
"description": "An unexpected error occurred during command execution.\nPlease ask an administrator to check the console output / logs for details."
},
"message_will_be_deleted_in": "This message will be deleted in %d seconds",
"missing_perms": {
"title": "❌",
"description": "You do not have the permissions required to use this command:\n%s"

View File

@@ -1,3 +1,5 @@
const { MessageEmbed } = require('discord.js');
/**
* A command
*/
@@ -115,4 +117,47 @@ module.exports = class Command {
*/
async execute(message, args) { } // eslint-disable-line no-unused-vars
/**
* Send a message with the command usage
* @param {TextChannel} channel - The channel to send the message to
* @param {string} [cmd_name] - The command alias
* @returns {Message}
*/
async sendUsage(channel, cmd_name) {
let settings = await channel.guild.settings;
if (!cmd_name) cmd_name = this.name;
const prefix = settings.command_prefix;
const i18n = this.client.i18n.get(settings.locale);
const addArgs = (embed, arg) => {
let required = arg.required ? '`❗` ' : '';
embed.addField(required + arg.name, `» ${i18n('cmd_usage.args.description', arg.description)}}\n» ${i18n('cmd_usage.args.example', arg.example)}`);
};
let usage,
example,
embed;
if (this.process_args) {
usage = `${prefix + cmd_name} ${this.args.map(arg => arg.required ? `<${arg.name};>` : `[${arg.name};]`).join(' ')}`;
example = `${prefix + cmd_name} ${this.args.map(arg => `${arg.name}: ${arg.example};`).join(' ')}`;
embed = new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('cmd_usage.title', cmd_name))
.setDescription(i18n('cmd_usage.named_args') + i18n('cmd_usage.description', usage, example));
} else {
usage = `${prefix + cmd_name} ${this.args.map(arg => arg.required ? `<${arg.name}>` : `[${arg.name}]`).join(' ')}`;
example = `${prefix + cmd_name} ${this.args.map(arg => `${arg.example}`).join(' ')}`;
embed = new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('cmd_usage.title', cmd_name))
.setDescription(i18n('cmd_usage.description', usage, example));
}
this.args.forEach(arg => addArgs(embed, arg));
return await channel.send(embed);
}
};

View File

@@ -83,39 +83,20 @@ module.exports = class CommandManager {
let args = raw_args;
const addArgs = (embed, arg) => {
let required = arg.required ? '`❗` ' : '';
embed.addField(required + arg.name, `» ${i18n('cmd_usage.args.description')} ${arg.description}\n» ${i18n('cmd_usage.args.example')} \`${arg.example}\``);
};
if (cmd.process_args) {
args = {};
let data = [...raw_args.matchAll(/(?<key>\w+)\??\s?:\s?(?<value>([^;]|;{2})*);/gmi)];
data.forEach(arg => args[arg.groups.key] = arg.groups.value.replace(/;{2}/gm, ';'));
for (let arg of cmd.args) {
if (arg.required && !args[arg]) {
let usage = `${prefix + cmd_name} ${cmd.args.map(arg => arg.required ? `<${arg.name};>` : `[${arg.name};]`).join(' ')}`;
let example = `${prefix + cmd_name} ${cmd.args.map(arg => `${arg.name}: ${arg.example};`).join(' ')}`;
let embed = new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('cmd_usage.title', cmd_name))
.setDescription(i18n('cmd_usage.named_args') + i18n('cmd_usage.description', usage, example));
cmd.args.forEach(a => addArgs(embed, a));
return message.channel.send(embed);
return await cmd.sendUsage(message.channel, cmd_name);
}
}
} else {
const args_num = raw_args.split(' ').filter(arg => arg.length !== 0).length;
const required_args = cmd.args.reduce((acc, arg) => arg.required ? acc + 1 : acc, 0);
if (args_num < required_args) {
let usage = `${prefix + cmd_name} ${cmd.args.map(arg => arg.required ? `<${arg.name}>` : `[${arg.name}]`).join(' ')}`;
let example = `${prefix + cmd_name} ${cmd.args.map(arg => `${arg.example}`).join(' ')}`;
let embed = new MessageEmbed()
.setColor(settings.error_colour)
.setTitle(i18n('cmd_usage.title', cmd_name))
.setDescription(i18n('cmd_usage.description', usage, example));
cmd.args.forEach(a => addArgs(embed, a));
return message.channel.send(embed);
return await cmd.sendUsage(message.channel, cmd_name);
}
}
@@ -158,7 +139,6 @@ module.exports = class CommandManager {
} catch (e) {
this.client.log.warn(`An error occurred whilst executing the ${cmd.name} command`);
this.client.log.error(e);
// await message.channel.send(i18n('command_execution_error'));
await message.channel.send(
new MessageEmbed()
.setColor('ORANGE')

View File

@@ -21,9 +21,6 @@ module.exports = class TicketArchives {
});
if (t_row) {
let embeds = [];
for (let embed in message.embeds) embeds.push({ ...message.embeds[embed] });
await this.client.db.models.Message.create({
id: message.id,
ticket: t_row.id,
@@ -31,7 +28,9 @@ module.exports = class TicketArchives {
data: {
content: message.content,
// time: message.createdTimestamp,
embeds,
embeds: message.embeds.map(embed => {
return { embed };
}),
attachments: [...message.attachments.values()]
}
});
@@ -48,14 +47,11 @@ module.exports = class TicketArchives {
});
if (m_row) {
let embeds = [];
for (let embed in message.embeds) embeds.push({ ...message.embeds[embed] });
m_row.data = {
content: message.content,
// time: message.editedTimestamp,
embeds: message.embeds.map(embed => {
return { ...message.embeds[embed] };
return { embed };
}),
attachments: [...message.attachments.values()]
};
@@ -90,77 +86,77 @@ module.exports = class TicketArchives {
}
});
if (m_row) {
// message author
let u_model_data = {
user: message.author.id,
if (!m_row) return;
// message author
let u_model_data = {
user: message.author.id,
ticket: message.channel.id
};
let [u_row] = await this.client.db.models.UserEntity.findOrCreate({
where: u_model_data,
defaults: u_model_data
});
await u_row.update({
avatar: message.author.displayAvatarURL(),
username: message.author.username,
discriminator: message.author.discriminator,
display_name: message.member.displayName,
colour: message.member.displayColor === 0 ? null : int2hex(message.member.displayColor),
bot: message.author.bot
});
// mentioned members
message.mentions.members.forEach(async member => {
let m_model_data = {
user: member.user.id,
ticket: message.channel.id
};
let [u_row] = await this.client.db.models.UserEntity.findOrCreate({
where: u_model_data,
defaults: u_model_data
});
await u_row.update({
avatar: message.author.displayAvatarURL(),
username: message.author.username,
discriminator: message.author.discriminator,
display_name: message.member.displayName,
colour: message.member.displayColor === 0 ? null : int2hex(message.member.displayColor),
bot: message.author.bot
let [m_row] = await this.client.db.models.UserEntity.findOrCreate({
where: m_model_data,
defaults: m_model_data
});
// mentioned members
message.mentions.members.forEach(async member => {
let m_model_data = {
user: member.user.id,
ticket: message.channel.id
};
let [m_row] = await this.client.db.models.UserEntity.findOrCreate({
where: m_model_data,
defaults: m_model_data
});
await m_row.update({
avatar: member.user.displayAvatarURL(),
username: member.user.username,
discriminator: member.user.discriminator,
display_name: member.displayName,
colour: member.displayColor === 0 ? null : int2hex(member.displayColor),
bot: member.user.bot
});
await m_row.update({
avatar: member.user.displayAvatarURL(),
username: member.user.username,
discriminator: member.user.discriminator,
display_name: member.displayName,
colour: member.displayColor === 0 ? null : int2hex(member.displayColor),
bot: member.user.bot
});
});
// mentioned channels
message.mentions.channels.forEach(async channel => {
let c_model_data = {
channel: channel.id,
ticket: message.channel.id
};
let [c_row] = await this.client.db.models.ChannelEntity.findOrCreate({
where: c_model_data,
defaults: c_model_data
});
await c_row.update({
name: channel.name
});
// mentioned channels
message.mentions.channels.forEach(async channel => {
let c_model_data = {
channel: channel.id,
ticket: message.channel.id
};
let [c_row] = await this.client.db.models.ChannelEntity.findOrCreate({
where: c_model_data,
defaults: c_model_data
});
await c_row.update({
name: channel.name
});
});
// mentioned roles
message.mentions.roles.forEach(async role => {
let r_model_data = {
role: role.id,
ticket: message.channel.id
};
let [r_row] = await this.client.db.models.RoleEntity.findOrCreate({
where: r_model_data,
defaults: r_model_data
});
await r_row.update({
name: role.name,
colour: role.color === 0 ? '7289DA' : int2hex(role.color) // 7289DA = 7506394
});
// mentioned roles
message.mentions.roles.forEach(async role => {
let r_model_data = {
role: role.id,
ticket: message.channel.id
};
let [r_row] = await this.client.db.models.RoleEntity.findOrCreate({
where: r_model_data,
defaults: r_model_data
});
}
await r_row.update({
name: role.name,
colour: role.color === 0 ? '7289DA' : int2hex(role.color) // 7289DA = 7506394
});
});
}

View File

@@ -75,6 +75,7 @@ module.exports = class TicketManager extends EventEmitter {
});
this.emit('create', t_row.id, creator_id);
return t_row;
}
/**
@@ -144,6 +145,7 @@ module.exports = class TicketManager extends EventEmitter {
}
this.emit('close', ticket_id);
return t_row;
}
/**

View File

@@ -3,9 +3,19 @@ const config = require('../../user/config');
let current_presence = -1;
module.exports = {
/**
*
* @param {string} text
* @param {string} [additional]
* @returns {string}
*/
footer: (text, additional) => {
if (text && additional) return `${text} | ${additional}`;
else return text || additional || '';
},
/**
* Select a presence from the config
* @returns {Discord.PresenceData}
* @returns {PresenceData}
*/
selectPresence: () => {
let length = config.presence.presences.length;

43
src/utils/emoji.js Normal file
View File

@@ -0,0 +1,43 @@
module.exports = {
letters: {
A: '🇦',
B: '🇧',
C: '🇨',
D: '🇩',
E: '🇪',
F: '🇫',
G: '🇬',
H: '🇭',
I: '🇮',
J: '🇯',
K: '🇰',
L: '🇱',
M: '🇲',
N: '🇳',
O: '🇴',
P: '🇵',
Q: '🇶',
R: '🇷',
S: '🇸',
T: '🇹',
U: '🇺',
V: '🇻',
W: '🇼',
X: '🇽',
Y: '🇾',
Z: '🇿',
},
numbers: {
0: '0⃣',
1: '1⃣',
2: '2⃣',
3: '3⃣',
4: '4⃣',
5: '5⃣',
6: '6⃣',
7: '7⃣',
8: '8⃣',
9: '9⃣',
10: '🔟',
}
};