This commit is contained in:
Isaac 2021-05-22 00:04:18 +01:00
parent 777719d1b3
commit 32f43ac18f
No known key found for this signature in database
GPG Key ID: F6812DBC6719B4E3
58 changed files with 837 additions and 926 deletions

View File

@ -5,10 +5,73 @@ module.exports = {
'node': true 'node': true
}, },
'extends': 'eslint:recommended', 'extends': 'eslint:recommended',
'parserOptions': { 'parserOptions': { 'ecmaVersion': 12 },
'ecmaVersion': 12
},
'rules': { 'rules': {
'array-bracket-newline': [
'error',
'consistent'
],
'array-bracket-spacing': [
'error',
'never'
],
'array-element-newline': [
'error',
'consistent'
],
'arrow-body-style': [
'error',
'as-needed'
],
'arrow-parens': [
'error',
'as-needed'
],
'block-spacing': [
'error',
'always'
],
'brace-style': [
'error',
'1tbs'
],
'comma-dangle': [
'error',
'never'
],
'comma-spacing': [
'error',
{
'after': true,
'before': false
}
],
'comma-style': [
'error',
'last'
],
'computed-property-spacing': [
'error',
'never'
],
'curly': [
'error',
'multi-line', // 'multi'
'consistent'
],
'default-case-last': [
'error'
],
'dot-location': [
'error',
'property'
],
'dot-notation': [
'error'
],
'eqeqeq': [
'error'
],
'indent': [ 'indent': [
'error', 'error',
'tab' 'tab'
@ -17,6 +80,74 @@ module.exports = {
'off', 'off',
'windows' 'windows'
], ],
'max-depth': [
'warn',
{ 'max': 5 }
],
'max-len': [
'warn',
{
'code': 150,
'ignoreRegExpLiterals': true,
'ignoreStrings': true,
'ignoreTemplateLiterals': true,
'ignoreTrailingComments': true,
'ignoreUrls': true
}
],
'max-lines': [
'warn'
],
'max-statements-per-line': [
'error'
],
'multiline-comment-style': [
'warn'
],
'no-console': [
'warn'
],
'no-return-assign': [
'error'
],
'no-template-curly-in-string': [
'warn'
],
'no-trailing-spaces': [
'error'
],
'no-underscore-dangle': [
'error'
],
'no-unneeded-ternary': [
'error'
],
'no-var': [
'error'
],
'no-whitespace-before-property': [
'error'
],
'object-curly-newline': [
'error',
{
'minProperties': 2,
'multiline': true
}
],
'object-curly-spacing': [
'error',
'always'
],
'object-property-newline': [
'error'
],
'operator-linebreak': [
'error'
],
'prefer-arrow-callback': [
'error'
],
'prefer-const': [ 'prefer-const': [
'error', 'error',
{ {
@ -32,5 +163,14 @@ module.exports = {
'error', 'error',
'always' 'always'
], ],
'sort-keys': [
'error',
'asc',
{ 'natural': true }
],
'space-in-parens': [
'error',
'never'
]
} }
}; };

View File

@ -1,26 +1,17 @@
<img src='https://discordtickets.app/img/logo-small-circle.png' align='left' width='180px' height='180px' style='margin: 30px 40px 0 0'/>
<!-- <img align='left' width='0' height='192px' hspace='10'/> -->
<!-- omit in toc -->
# [Discord Tickets](https://discordtickets.app)
[![GitHub stars](https://img.shields.io/github/stars/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers) [![GitHub stars](https://img.shields.io/github/stars/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers)
[![GitHub forks](https://img.shields.io/github/forks/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers) [![GitHub forks](https://img.shields.io/github/forks/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/stargazers)
[![License](https://img.shields.io/github/license/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/blob/master/LICENSE) [![License](https://img.shields.io/github/license/discord-tickets/bot?style=flat-square)](https://github.com/discord-tickets/bot/blob/master/LICENSE)
![Codacy grade](https://img.shields.io/codacy/grade/14e6851c85444424b75b8bc3f93e93db?logo=codacy&style=flat-square) [![Codacy](https://img.shields.io/codacy/grade/b974eb5f984c40868e07d82c968bd02d?logo=codacy&style=flat-square)](https://www.codacy.com/gh/discord-tickets/bot/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=discord-tickets/bot&amp;utm_campaign=Badge_Grade)
[![Discord](https://img.shields.io/discord/451745464480432129?label=discord&color=7289DA&style=flat-square)](https://discord.gg/pXc9vyC) [![Discord](https://img.shields.io/discord/451745464480432129?label=discord&color=7289DA&style=flat-square)](https://go.eartharoid.me/discord)
[![Crowdin](https://badges.crowdin.net/discord-tickets/localized.svg)](https://i18n.discordtickets.app/project/discord-tickets) [![Crowdin](https://badges.crowdin.net/discord-tickets/localized.svg)](https://i18n.discordtickets.app/project/discord-tickets)
<br>
![Discord Tickets](https://ik.imagekit.io/eartharoid/discord-tickets/wordmark/gradient_HTC7nqZr9.png)
An open-source ticket management bot for Discord - a free alternative to the premium and white-label plans of other popular ticketing bots. An open-source ticket management bot for Discord - a free alternative to the premium and white-label plans of other popular ticketing bots.
<br><br>
⚠️
**The code on the master branch is for v3 which is not ready. Please go to the [v2 branch](https://github.com/discord-tickets/bot/tree/v2) for the current version of the code and [documentation](https://github.com/discord-tickets/bot/wiki)**.
⚠️
**The following information is about v3. Please read the README file on the [v2 branch](https://github.com/discord-tickets/bot/tree/v2) for information that is about the current version.**
## What is this? ## What is this?
Discord Tickets is a Discord bot for creating and managing "support ticket" channels. It is a free and open-source alternative to the popular paid "premium" and "white-label" ticketing bots, such as [Ticket Tool](https://tickettool.xyz/), [TicketsBot](https://ticketsbot.net/), [Tickety](https://tickety.net/), [Helper.gg](https://helper.gg/), [Helper](https://helper.wtf), and others. Discord Tickets is a Discord bot for creating and managing "support ticket" channels. It is a free and open-source alternative to the popular paid "premium" and "white-label" ticketing bots, such as [Ticket Tool](https://tickettool.xyz/), [TicketsBot](https://ticketsbot.net/), [Tickety](https://tickety.net/), [Helper.gg](https://helper.gg/), [Helper](https://helper.wtf), and others.
@ -65,7 +56,7 @@ If you choose SQLite, which is the default as it is the easiest, you don't need
### Screenshots ### Screenshots
> screenshot of a ticket channel ![The opening message of a ticket channel](https://i.imgur.com/2T5eSH0.png)
<!-- --> <!-- -->
> screenshot of a panel > screenshot of a panel
@ -117,6 +108,6 @@ Does your community or company use Discord Tickets? Sponsor the project to get y
Discord Tickets is licensed under the [GPLv3 license](https://github.com/discord-tickets/bot/blob/master/LICENSE). Discord Tickets is licensed under the [GPLv3 license](https://github.com/discord-tickets/bot/blob/master/LICENSE).
Discord Tickets is not related to Discord Inc. This is not an official Discord product. It is not affiliated with nor endorsed by Discord Inc.
© 2021 Isaac Saunders © 2021 Isaac Saunders

View File

@ -1,5 +0,0 @@
{
"include": [
"src/**/*.js"
]
}

View File

@ -1,7 +1,10 @@
/* eslint-disable no-console */
const link = require('terminal-link'); const link = require('terminal-link');
const leeks = require('leeks.js'); const leeks = require('leeks.js');
const { version, homepage } = require('../package.json'); const {
version, homepage
} = require('../package.json');
module.exports = () => { module.exports = () => {
console.log(leeks.colours.cyan(` console.log(leeks.colours.cyan(`

View File

@ -1,30 +1,32 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
module.exports = class AddCommand extends Command { module.exports = class AddCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.add.name'),
description: i18n('commands.add.description'),
aliases: [], aliases: [],
process_args: false,
args: [ args: [
{ {
name: i18n('commands.add.args.member.name'),
description: i18n('commands.add.args.member.description'), description: i18n('commands.add.args.member.description'),
example: i18n('commands.add.args.member.example'), example: i18n('commands.add.args.member.example'),
required: true, name: i18n('commands.add.args.member.name'),
required: true
}, },
{ {
name: i18n('commands.add.args.ticket.name'),
description: i18n('commands.add.args.ticket.description'), description: i18n('commands.add.args.ticket.description'),
example: i18n('commands.add.args.ticket.example'), example: i18n('commands.add.args.ticket.example'),
required: false, name: i18n('commands.add.args.ticket.name'),
required: false
} }
] ],
description: i18n('commands.add.description'),
internal: true,
name: i18n('commands.add.name'),
process_args: false
}); });
} }
@ -93,10 +95,10 @@ module.exports = class AddCommand extends Command {
); );
await ticket.updateOverwrite(member, { await ticket.updateOverwrite(member, {
VIEW_CHANNEL: true, ATTACH_FILES: true,
READ_MESSAGE_HISTORY: true, READ_MESSAGE_HISTORY: true,
SEND_MESSAGES: true, SEND_MESSAGES: true,
ATTACH_FILES: true VIEW_CHANNEL: true
}, `${message.author.tag} added ${member.user.tag} to the ticket`); }, `${message.author.tag} added ${member.user.tag} to the ticket`);
await this.client.tickets.archives.updateMember(ticket.id, member); await this.client.tickets.archives.updateMember(ticket.id, member);

View File

@ -1,27 +1,29 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
module.exports = class BlacklistCommand extends Command { module.exports = class BlacklistCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.blacklist.name'),
description: i18n('commands.blacklist.description'),
aliases: [ aliases: [
i18n('commands.blacklist.aliases.unblacklist'), i18n('commands.blacklist.aliases.unblacklist')
], ],
process_args: false,
args: [ args: [
{ {
name: i18n('commands.blacklist.args.member_or_role.name'),
description: i18n('commands.blacklist.args.member_or_role.description'), description: i18n('commands.blacklist.args.member_or_role.description'),
example: i18n('commands.blacklist.args.member_or_role.example'), example: i18n('commands.blacklist.args.member_or_role.example'),
required: false, name: i18n('commands.blacklist.args.member_or_role.name'),
required: false
} }
], ],
permissions: ['MANAGE_GUILD'] description: i18n('commands.blacklist.description'),
internal: true,
name: i18n('commands.blacklist.name'),
permissions: ['MANAGE_GUILD'],
process_args: false
}); });
} }
@ -51,10 +53,13 @@ module.exports = class BlacklistCommand extends Command {
let id; let id;
const input = args.trim().split(/\s/g)[0]; const input = args.trim().split(/\s/g)[0];
if (member) id = member.id; if (member) {
else if (role) id = role.id; id = member.id;
else if (/\d{17,19}/.test(input)) id = input; } else if (role) {
else if (settings.blacklist?.length === 0) { id = role.id;
} else if (/\d{17,19}/.test(input)) {
id = input;
} else if (settings.blacklist?.length === 0) {
return await message.channel.send( return await message.channel.send(
new MessageEmbed() new MessageEmbed()
.setColor(settings.colour) .setColor(settings.colour)

View File

@ -1,6 +1,10 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const { MessageEmbed, MessageMentions, Message } = require('discord.js'); const {
Message, // eslint-disable-line no-unused-vars
MessageEmbed,
MessageMentions
} = require('discord.js');
const { Op } = require('sequelize'); const { Op } = require('sequelize');
const toTime = require('to-time-monthsfork'); const toTime = require('to-time-monthsfork');
const { footer } = require('../utils/discord'); const { footer } = require('../utils/discord');
@ -9,43 +13,40 @@ module.exports = class CloseCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.close.name'),
description: i18n('commands.close.description'),
aliases: [ aliases: [
i18n('commands.close.aliases.delete'), i18n('commands.close.aliases.delete'),
i18n('commands.close.aliases.lock'), i18n('commands.close.aliases.lock')
], ],
process_args: true,
args: [ args: [
{ {
name: i18n('commands.close.args.ticket.name'), alias: i18n('commands.close.args.ticket.alias'),
description: i18n('commands.close.args.ticket.description'), description: i18n('commands.close.args.ticket.description'),
example: i18n('commands.close.args.ticket.example'), example: i18n('commands.close.args.ticket.example'),
name: i18n('commands.close.args.ticket.name'),
required: false, required: false,
// for arg parsing
alias: i18n('commands.close.args.ticket.alias'),
type: String type: String
}, },
{ {
name: i18n('commands.close.args.reason.name'), alias: i18n('commands.close.args.reason.alias'),
description: i18n('commands.close.args.reason.description'), description: i18n('commands.close.args.reason.description'),
example: i18n('commands.close.args.reason.example'), example: i18n('commands.close.args.reason.example'),
name: i18n('commands.close.args.reason.name'),
required: false, required: false,
// for arg parsing
alias: i18n('commands.close.args.reason.alias'),
type: String type: String
}, },
{ {
name: i18n('commands.close.args.time.name'), alias: i18n('commands.close.args.time.alias'),
description: i18n('commands.close.args.time.description'), description: i18n('commands.close.args.time.description'),
example: i18n('commands.close.args.time.example'), example: i18n('commands.close.args.time.example'),
name: i18n('commands.close.args.time.name'),
required: false, required: false,
// for arg parsing
alias: i18n('commands.close.args.time.alias'),
type: String type: String
} }
] ],
description: i18n('commands.close.description'),
internal: true,
name: i18n('commands.close.name'),
process_args: true
}); });
} }
@ -79,10 +80,8 @@ module.exports = class CloseCommand extends Command {
const tickets = await this.client.db.models.Ticket.findAndCountAll({ const tickets = await this.client.db.models.Ticket.findAndCountAll({
where: { where: {
last_message: { guild: message.guild.id,
[Op.lte]: new Date(Date.now() - period) last_message: { [Op.lte]: new Date(Date.now() - period) }
},
guild: message.guild.id
} }
}); });
@ -105,13 +104,9 @@ module.exports = class CloseCommand extends Command {
await collector_message.react('✅'); await collector_message.react('✅');
const collector_filter = (reaction, user) => { const collector_filter = (reaction, user) => user.id === message.author.id && reaction.emoji.name === '✅';
return user.id === message.author.id && reaction.emoji.name === '✅';
};
const collector = collector_message.createReactionCollector(collector_filter, { const collector = collector_message.createReactionCollector(collector_filter, { time: 30000 });
time: 30000
});
collector.on('collect', async () => { collector.on('collect', async () => {
await collector_message.reactions.removeAll(); await collector_message.reactions.removeAll();
@ -130,7 +125,7 @@ module.exports = class CloseCommand extends Command {
}); });
collector.on('end', async (collected) => { collector.on('end', async collected => {
if (collected.size === 0) { if (collected.size === 0) {
await collector_message.reactions.removeAll(); await collector_message.reactions.removeAll();
await collector_message.edit( await collector_message.edit(
@ -169,11 +164,7 @@ module.exports = class CloseCommand extends Command {
); );
} }
} else { } else {
t_row = await this.client.db.models.Ticket.findOne({ t_row = await this.client.db.models.Ticket.findOne({ where: { id: message.channel.id } });
where: {
id: message.channel.id
}
});
if (!t_row) { if (!t_row) {
return await message.channel.send( return await message.channel.send(
@ -196,13 +187,9 @@ module.exports = class CloseCommand extends Command {
await collector_message.react('✅'); await collector_message.react('✅');
const collector_filter = (reaction, user) => { const collector_filter = (reaction, user) => user.id === message.author.id && reaction.emoji.name === '✅';
return user.id === message.author.id && reaction.emoji.name === '✅';
};
const collector = collector_message.createReactionCollector(collector_filter, { const collector = collector_message.createReactionCollector(collector_filter, { time: 30000 });
time: 30000
});
collector.on('collect', async () => { collector.on('collect', async () => {
collector.stop(); collector.stop();
@ -223,7 +210,7 @@ module.exports = class CloseCommand extends Command {
await this.client.tickets.close(t_row.id, message.author.id, message.guild.id, args[arg_reason]); await this.client.tickets.close(t_row.id, message.author.id, message.guild.id, args[arg_reason]);
}); });
collector.on('end', async (collected) => { collector.on('end', async collected => {
if (collected.size === 0) { if (collected.size === 0) {
await collector_message.reactions.removeAll(); await collector_message.reactions.removeAll();
await collector_message.edit( await collector_message.edit(

View File

@ -1,27 +1,29 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
module.exports = class HelpCommand extends Command { module.exports = class HelpCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.help.name'),
description: i18n('commands.help.description'),
aliases: [ aliases: [
i18n('commands.help.aliases.command'), i18n('commands.help.aliases.command'),
i18n('commands.help.aliases.commands'), i18n('commands.help.aliases.commands')
], ],
process_args: false,
args: [ args: [
{ {
name: i18n('commands.help.args.command.name'),
description: i18n('commands.help.args.command.description'), description: i18n('commands.help.args.command.description'),
example: i18n('commands.help.args.command.example'), example: i18n('commands.help.args.command.example'),
required: false, name: i18n('commands.help.args.command.name'),
required: false
} }
] ],
description: i18n('commands.help.description'),
internal: true,
name: i18n('commands.help.name'),
process_args: false
}); });
} }
@ -54,9 +56,7 @@ module.exports = class HelpCommand extends Command {
new MessageEmbed() new MessageEmbed()
.setColor(settings.colour) .setColor(settings.colour)
.setTitle(i18n('commands.help.response.list.title')) .setTitle(i18n('commands.help.response.list.title'))
.setDescription(i18n('commands.help.response.list.description', { .setDescription(i18n('commands.help.response.list.description', { prefix: settings.command_prefix }))
prefix: settings.command_prefix,
}))
.addField(i18n('commands.help.response.list.fields.commands'), list.join('\n')) .addField(i18n('commands.help.response.list.fields.commands'), list.join('\n'))
.setFooter(settings.footer, message.guild.iconURL()) .setFooter(settings.footer, message.guild.iconURL())
); );

View File

@ -1,6 +1,8 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
const { footer } = require('../utils/discord'); const { footer } = require('../utils/discord');
const { letters } = require('../utils/emoji'); const { letters } = require('../utils/emoji');
const { wait } = require('../utils'); const { wait } = require('../utils');
@ -9,23 +11,23 @@ module.exports = class NewCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.new.name'),
description: i18n('commands.new.description'),
aliases: [ aliases: [
i18n('commands.new.aliases.create'), i18n('commands.new.aliases.create'),
i18n('commands.new.aliases.open'), i18n('commands.new.aliases.open'),
i18n('commands.new.aliases.ticket'), i18n('commands.new.aliases.ticket')
], ],
process_args: false,
args: [ args: [
{ {
name: i18n('commands.new.args.topic.name'),
description: i18n('commands.new.args.topic.description'), description: i18n('commands.new.args.topic.description'),
example: i18n('commands.new.args.topic.example'), example: i18n('commands.new.args.topic.example'),
required: false, name: i18n('commands.new.args.topic.name'),
required: false
} }
] ],
description: i18n('commands.new.description'),
internal: true,
name: i18n('commands.new.name'),
process_args: false
}); });
} }
@ -114,11 +116,7 @@ module.exports = class NewCommand extends Command {
}, 15000); }, 15000);
}; };
const categories = await this.client.db.models.Category.findAndCountAll({ const categories = await this.client.db.models.Category.findAndCountAll({ where: { guild: message.guild.id } });
where: {
guild: message.guild.id
}
});
if (categories.count === 0) { if (categories.count === 0) {
return await message.channel.send( return await message.channel.send(
@ -153,21 +151,21 @@ module.exports = class NewCommand extends Command {
return user.id === message.author.id && allowed.includes(reaction.emoji.name); return user.id === message.author.id && allowed.includes(reaction.emoji.name);
}; };
const collector = collector_message.createReactionCollector(collector_filter, { const collector = collector_message.createReactionCollector(collector_filter, { time: 30000 });
time: 30000
});
collector.on('collect', async (reaction) => { collector.on('collect', async reaction => {
collector.stop(); collector.stop();
const index = letters_array.findIndex(value => value === reaction.emoji.name); // find where the letter is in the alphabet const index = letters_array.findIndex(value => value === reaction.emoji.name); // find where the letter is in the alphabet
if (index === -1) return setTimeout(async () => { if (index === -1) {
await collector_message.delete(); return setTimeout(async () => {
}, 15000); await collector_message.delete();
}, 15000);
}
await collector_message.reactions.removeAll(); await collector_message.reactions.removeAll();
create(categories.rows[index], collector_message); // create the ticket, passing the existing response message to be edited instead of creating a new one create(categories.rows[index], collector_message); // create the ticket, passing the existing response message to be edited instead of creating a new one
}); });
collector.on('end', async (collected) => { collector.on('end', async collected => {
if (collected.size === 0) { if (collected.size === 0) {
await collector_message.reactions.removeAll(); await collector_message.reactions.removeAll();
await collector_message.edit( await collector_message.edit(

View File

@ -1,59 +1,59 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
const { some, wait } = require('../utils'); MessageEmbed
} = require('discord.js');
const {
some, wait
} = require('../utils');
const { emojify } = require('node-emoji'); const { emojify } = require('node-emoji');
module.exports = class PanelCommand extends Command { module.exports = class PanelCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.panel.name'),
description: i18n('commands.panel.description'),
aliases: [], aliases: [],
process_args: true,
args: [ args: [
{ {
name: i18n('commands.panel.args.title.name'), alias: i18n('commands.panel.args.title.alias'),
description: i18n('commands.panel.args.title.description'), description: i18n('commands.panel.args.title.description'),
example: i18n('commands.panel.args.title.example'), example: i18n('commands.panel.args.title.example'),
name: i18n('commands.panel.args.title.name'),
required: false, required: false,
// for arg parsing
alias: i18n('commands.panel.args.title.alias'),
type: String type: String
}, },
{ {
name: i18n('commands.panel.args.description.name'), alias: i18n('commands.panel.args.description.alias'),
description: i18n('commands.panel.args.description.description'), description: i18n('commands.panel.args.description.description'),
example: i18n('commands.panel.args.description.example'), example: i18n('commands.panel.args.description.example'),
name: i18n('commands.panel.args.description.name'),
required: true, required: true,
// for arg parsing
alias: i18n('commands.panel.args.description.alias'),
type: String type: String
}, },
{ {
name: i18n('commands.panel.args.emoji.name'), alias: i18n('commands.panel.args.emoji.alias'),
description: i18n('commands.panel.args.emoji.description'), description: i18n('commands.panel.args.emoji.description'),
example: i18n('commands.panel.args.emoji.example'), example: i18n('commands.panel.args.emoji.example'),
required: false,
// for arg parsing
alias: i18n('commands.panel.args.emoji.alias'),
type: String,
multiple: true, multiple: true,
name: i18n('commands.panel.args.emoji.name'),
required: false,
type: String
}, },
{ {
name: i18n('commands.panel.args.categories.name'), alias: i18n('commands.panel.args.categories.alias'),
description: i18n('commands.panel.args.categories.description'), description: i18n('commands.panel.args.categories.description'),
example: i18n('commands.panel.args.categories.example'), example: i18n('commands.panel.args.categories.example'),
required: true,
// for arg parsing
alias: i18n('commands.panel.args.categories.alias'),
type: String,
multiple: true, multiple: true,
name: i18n('commands.panel.args.categories.name'),
required: true,
type: String
} }
], ],
permissions: ['MANAGE_GUILD'] description: i18n('commands.panel.description'),
internal: true,
name: i18n('commands.panel.name'),
permissions: ['MANAGE_GUILD'],
process_args: true
}); });
} }
@ -72,16 +72,15 @@ module.exports = class PanelCommand extends Command {
const settings = await message.guild.getSettings(); const settings = await message.guild.getSettings();
const i18n = this.client.i18n.getLocale(settings.locale); const i18n = this.client.i18n.getLocale(settings.locale);
if (!args[arg_emoji]) if (!args[arg_emoji]) args[arg_emoji] = [];
args[arg_emoji] = [];
args[arg_emoji] = args[arg_emoji].map(emoji => emojify(emoji.replace(/\\/g, ''))); args[arg_emoji] = args[arg_emoji].map(emoji => emojify(emoji.replace(/\\/g, '')));
const invalid_category = await some(args[arg_categories], async id => { const invalid_category = await some(args[arg_categories], async id => {
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({
where: { where: {
id: id, guild: message.guild.id,
guild: message.guild.id id
} }
}); });
return !cat_row; return !cat_row;
@ -106,27 +105,26 @@ module.exports = class PanelCommand extends Command {
.setColor(settings.colour) .setColor(settings.colour)
.setFooter(settings.footer, message.guild.iconURL()); .setFooter(settings.footer, message.guild.iconURL());
if (args[arg_title]) if (args[arg_title]) embed.setTitle(args[arg_title]);
embed.setTitle(args[arg_title]);
if (args[arg_emoji].length === 0) { if (args[arg_emoji].length === 0) {
// reaction-less panel // reaction-less panel
panel_channel = await message.guild.channels.create('create-a-ticket', { panel_channel = await message.guild.channels.create('create-a-ticket', {
type: 'text',
rateLimitPerUser: 30,
permissionOverwrites: [ permissionOverwrites: [
{ {
id: message.guild.roles.everyone,
allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'READ_MESSAGE_HISTORY'], allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'READ_MESSAGE_HISTORY'],
deny: ['ATTACH_FILES', 'EMBED_LINKS', 'ADD_REACTIONS'] deny: ['ATTACH_FILES', 'EMBED_LINKS', 'ADD_REACTIONS'],
id: message.guild.roles.everyone
}, },
{ {
id: this.client.user.id, allow: ['EMBED_LINKS'],
allow: ['EMBED_LINKS'] id: this.client.user.id
} }
], ],
position: 1, position: 1,
reason: `${message.author.tag} created a new reaction-less panel` rateLimitPerUser: 30,
reason: `${message.author.tag} created a new reaction-less panel`,
type: 'text'
}); });
embed.setDescription(args[arg_description]); embed.setDescription(args[arg_description]);
@ -145,20 +143,20 @@ module.exports = class PanelCommand extends Command {
); );
} else { } else {
panel_channel = await message.guild.channels.create('create-a-ticket', { panel_channel = await message.guild.channels.create('create-a-ticket', {
type: 'text',
permissionOverwrites: [ permissionOverwrites: [
{ {
id: message.guild.roles.everyone,
allow: ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY'], allow: ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY'],
deny: ['SEND_MESSAGES', 'ADD_REACTIONS'] deny: ['SEND_MESSAGES', 'ADD_REACTIONS'],
id: message.guild.roles.everyone
}, },
{ {
id: this.client.user.id, allow: ['SEND_MESSAGES', 'EMBED_LINKS'],
allow: ['SEND_MESSAGES', 'EMBED_LINKS'] id: this.client.user.id
} }
], ],
position: 1, position: 1,
reason: `${message.author.tag} created a new panel` reason: `${message.author.tag} created a new panel`,
type: 'text'
}); });
if (args[arg_emoji].length === 1) { if (args[arg_emoji].length === 1) {
@ -177,8 +175,8 @@ module.exports = class PanelCommand extends Command {
categories_map[args[arg_emoji][i]] = args[arg_categories][i]; categories_map[args[arg_emoji][i]] = args[arg_categories][i];
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({
where: { where: {
id: args[arg_categories][i], guild: message.guild.id,
guild: message.guild.id id: args[arg_categories][i]
} }
}); });
description += `\n> ${args[arg_emoji][i]} | ${cat_row.name}`; description += `\n> ${args[arg_emoji][i]} | ${cat_row.name}`;
@ -204,7 +202,7 @@ module.exports = class PanelCommand extends Command {
categories: categories_map, categories: categories_map,
channel: panel_channel.id, channel: panel_channel.id,
guild: message.guild.id, guild: message.guild.id,
message: panel_message.id, message: panel_message.id
}); });
} }
}; };

View File

@ -1,30 +1,32 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
module.exports = class RemoveCommand extends Command { module.exports = class RemoveCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.remove.name'),
description: i18n('commands.remove.description'),
aliases: [], aliases: [],
process_args: false,
args: [ args: [
{ {
name: i18n('commands.remove.args.member.name'),
description: i18n('commands.remove.args.member.description'), description: i18n('commands.remove.args.member.description'),
example: i18n('commands.remove.args.member.example'), example: i18n('commands.remove.args.member.example'),
required: true, name: i18n('commands.remove.args.member.name'),
required: true
}, },
{ {
name: i18n('commands.remove.args.ticket.name'),
description: i18n('commands.remove.args.ticket.description'), description: i18n('commands.remove.args.ticket.description'),
example: i18n('commands.remove.args.ticket.example'), example: i18n('commands.remove.args.ticket.example'),
required: false, name: i18n('commands.remove.args.ticket.name'),
required: false
} }
] ],
description: i18n('commands.remove.description'),
internal: true,
name: i18n('commands.remove.name'),
process_args: false
}); });
} }

View File

@ -1,22 +1,24 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
const fetch = require('node-fetch'); const fetch = require('node-fetch');
// eslint-disable-next-line no-unused-vars const {
const { MessageAttachment, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageAttachment
} = require('discord.js');
const { Validator } = require('jsonschema'); const { Validator } = require('jsonschema');
module.exports = class SettingsCommand extends Command { module.exports = class SettingsCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
aliases: [
i18n('commands.settings.aliases.config')
],
args: [],
description: i18n('commands.settings.description'),
internal: true, internal: true,
name: i18n('commands.settings.name'), name: i18n('commands.settings.name'),
description: i18n('commands.settings.description'), permissions: ['MANAGE_GUILD'],
aliases: [ process_args: false
i18n('commands.settings.aliases.config'),
],
process_args: false,
args: [],
permissions: ['MANAGE_GUILD']
}); });
this.schema = require('./extra/settings.schema.json'); this.schema = require('./extra/settings.schema.json');
@ -40,7 +42,9 @@ module.exports = class SettingsCommand extends Command {
this.client.log.info(`Downloading settings for "${message.guild.name}"`); this.client.log.info(`Downloading settings for "${message.guild.name}"`);
const data = await (await fetch(attachments[0].url)).json(); const data = await (await fetch(attachments[0].url)).json();
const { valid, errors } = this.v.validate(data, this.schema); const {
valid, errors
} = this.v.validate(data, this.schema);
if (!valid) { if (!valid) {
this.client.log.warn('Settings validation error'); this.client.log.warn('Settings validation error');
@ -60,11 +64,7 @@ module.exports = class SettingsCommand extends Command {
for (const c of data.categories) { for (const c of data.categories) {
if (c.id) { if (c.id) {
// existing category // existing category
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({ where: { id: c.id } });
where: {
id: c.id
}
});
cat_row.claiming = c.claiming; cat_row.claiming = c.claiming;
cat_row.image = c.image; cat_row.image = c.image;
cat_row.max_per_member = c.max_per_member; cat_row.max_per_member = c.max_per_member;
@ -81,15 +81,14 @@ module.exports = class SettingsCommand extends Command {
const cat_channel = await this.client.channels.fetch(c.id); const cat_channel = await this.client.channels.fetch(c.id);
if (cat_channel) { if (cat_channel) {
if (cat_channel.name !== c.name) if (cat_channel.name !== c.name) await cat_channel.setName(c.name, `Tickets category updated by ${message.author.tag}`);
await cat_channel.setName(c.name, `Tickets category updated by ${message.author.tag}`);
for (const r of c.roles) { for (const r of c.roles) {
await cat_channel.updateOverwrite(r, { await cat_channel.updateOverwrite(r, {
VIEW_CHANNEL: true, ATTACH_FILES: true,
READ_MESSAGE_HISTORY: true, READ_MESSAGE_HISTORY: true,
SEND_MESSAGES: true, SEND_MESSAGES: true,
ATTACH_FILES: true VIEW_CHANNEL: true
}, `Tickets category updated by ${message.author.tag}`); }, `Tickets category updated by ${message.author.tag}`);
} }
} }
@ -97,33 +96,31 @@ module.exports = class SettingsCommand extends Command {
// create a new category // create a new category
const allowed_permissions = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES']; const allowed_permissions = ['VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', 'SEND_MESSAGES', 'EMBED_LINKS', 'ATTACH_FILES'];
const cat_channel = await message.guild.channels.create(c.name, { const cat_channel = await message.guild.channels.create(c.name, {
type: 'category',
reason: `Tickets category created by ${message.author.tag}`,
position: 1,
permissionOverwrites: [ permissionOverwrites: [
...[ ...[
{ {
id: message.guild.roles.everyone, deny: ['VIEW_CHANNEL'],
deny: ['VIEW_CHANNEL'] id: message.guild.roles.everyone
}, },
{ {
id: this.client.user.id, allow: allowed_permissions,
allow: allowed_permissions id: this.client.user.id
} }
], ],
...c.roles.map(r => { ...c.roles.map(r => ({
return { allow: allowed_permissions,
id: r, id: r
allow: allowed_permissions }))
}; ],
}) position: 1,
] reason: `Tickets category created by ${message.author.tag}`,
type: 'category'
}); });
await this.client.db.models.Category.create({ await this.client.db.models.Category.create({
id: cat_channel.id,
claiming: c.claiming, claiming: c.claiming,
guild: message.guild.id, guild: message.guild.id,
id: cat_channel.id,
image: c.image, image: c.image,
max_per_member: c.max_per_member, max_per_member: c.max_per_member,
name: c.name, name: c.name,
@ -141,11 +138,11 @@ module.exports = class SettingsCommand extends Command {
for (const survey in data.surveys) { for (const survey in data.surveys) {
const survey_data = { const survey_data = {
guild: message.guild.id, guild: message.guild.id,
name: survey, name: survey
}; };
const [s_row] = await this.client.db.models.Survey.findOrCreate({ const [s_row] = await this.client.db.models.Survey.findOrCreate({
where: survey_data, defaults: survey_data,
defaults: survey_data where: survey_data
}); });
s_row.questions = data.surveys[survey]; s_row.questions = data.surveys[survey];
await s_row.save(); await s_row.save();
@ -156,35 +153,25 @@ module.exports = class SettingsCommand extends Command {
} else { } else {
// upload settings as json to be edited // upload settings as json to be edited
const categories = await this.client.db.models.Category.findAll({ const categories = await this.client.db.models.Category.findAll({ where: { guild: message.guild.id } });
where: {
guild: message.guild.id
}
});
const surveys = await this.client.db.models.Survey.findAll({ const surveys = await this.client.db.models.Survey.findAll({ where: { guild: message.guild.id } });
where: {
guild: message.guild.id
}
});
const data = { const data = {
categories: categories.map(c => { categories: categories.map(c => ({
return { claiming: c.claiming,
id: c.id, id: c.id,
claiming: c.claiming, image: c.image,
image: c.image, max_per_member: c.max_per_member,
max_per_member: c.max_per_member, name: c.name,
name: c.name, name_format: c.name_format,
name_format: c.name_format, opening_message: c.opening_message,
opening_message: c.opening_message, opening_questions: c.opening_questions,
opening_questions: c.opening_questions, ping: c.ping,
ping: c.ping, require_topic: c.require_topic,
require_topic: c.require_topic, roles: c.roles,
roles: c.roles, survey: c.survey
survey: c.survey })),
};
}),
colour: settings.colour, colour: settings.colour,
command_prefix: settings.command_prefix, command_prefix: settings.command_prefix,
error_colour: settings.error_colour, error_colour: settings.error_colour,
@ -197,7 +184,9 @@ module.exports = class SettingsCommand extends Command {
}; };
for (const survey in surveys) { for (const survey in surveys) {
const { name, questions } = surveys[survey]; const {
name, questions
} = surveys[survey];
data.surveys[name] = questions; data.surveys[name] = questions;
} }
@ -206,9 +195,7 @@ module.exports = class SettingsCommand extends Command {
`Settings for ${message.guild.name}.json` `Settings for ${message.guild.name}.json`
); );
return await message.channel.send({ return await message.channel.send({ files: [attachment] });
files: [attachment]
});
} }
} }
}; };

View File

@ -1,24 +1,24 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
const Keyv = require('keyv'); const Keyv = require('keyv');
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
module.exports = class StatsCommand extends Command { module.exports = class StatsCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
aliases: [],
args: [],
description: i18n('commands.stats.description'),
internal: true, internal: true,
name: i18n('commands.stats.name'), name: i18n('commands.stats.name'),
description: i18n('commands.stats.description'),
aliases: [],
process_args: false, process_args: false,
args: [],
staff_only: true staff_only: true
}); });
this.cache = new Keyv({ this.cache = new Keyv({ namespace: 'cache.commands.stats' });
namespace: 'cache.commands.stats'
});
} }
/** /**
@ -35,27 +35,19 @@ module.exports = class StatsCommand extends Command {
let stats = await this.cache.get(message.guild.id); let stats = await this.cache.get(message.guild.id);
if (!stats) { if (!stats) {
const tickets = await this.client.db.models.Ticket.findAndCountAll({ const tickets = await this.client.db.models.Ticket.findAndCountAll({ where: { guild: message.guild.id } });
where: {
guild: message.guild.id
}
});
stats = { // maths stats = { // maths
tickets: tickets.count,
messages: settings.log_messages messages: settings.log_messages
? await messages.rows ? await messages.rows
.reduce(async (acc, row) => (await this.client.db.models.Ticket.findOne({ .reduce(async (acc, row) => (await this.client.db.models.Ticket.findOne({ where: { id: row.ticket } }))
where: { .guild === message.guild.id
id: row.ticket
}
})).guild === message.guild.id
? await acc + 1 ? await acc + 1
: await acc, 0) : await acc, 0)
: null, : null,
response_time: Math.floor(tickets.rows.reduce((acc, row) => row.first_response response_time: Math.floor(tickets.rows.reduce((acc, row) => row.first_response
? acc + ((Math.abs(new Date(row.createdAt) - new Date(row.first_response)) / 1000) / 60) ? acc + ((Math.abs(new Date(row.createdAt) - new Date(row.first_response)) / 1000) / 60)
: acc, 0) / tickets.count) : acc, 0) / tickets.count),
tickets: tickets.count
}; };
await this.cache.set(message.guild.id, stats, 60 * 60 * 1000); // cache for an hour await this.cache.set(message.guild.id, stats, 60 * 60 * 1000); // cache for an hour
} }

View File

@ -1,6 +1,10 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const { MessageAttachment, MessageEmbed, Message } = require('discord.js'); const {
Message, // eslint-disable-line no-unused-vars
MessageAttachment,
MessageEmbed
} = require('discord.js');
const fsp = require('fs').promises; const fsp = require('fs').promises;
const { path } = require('../utils/fs'); const { path } = require('../utils/fs');
const mustache = require('mustache'); const mustache = require('mustache');
@ -9,21 +13,21 @@ module.exports = class SurveyCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.survey.name'),
description: i18n('commands.survey.description'),
aliases: [ aliases: [
i18n('commands.survey.aliases.surveys') i18n('commands.survey.aliases.surveys')
], ],
process_args: false,
args: [ args: [
{ {
name: i18n('commands.survey.args.survey.name'),
description: i18n('commands.survey.args.survey.description'), description: i18n('commands.survey.args.survey.description'),
example: i18n('commands.survey.args.survey.example'), example: i18n('commands.survey.args.survey.example'),
required: false, name: i18n('commands.survey.args.survey.name'),
required: false
} }
], ],
description: i18n('commands.survey.description'),
internal: true,
name: i18n('commands.survey.name'),
process_args: false,
staff_only: true staff_only: true
}); });
} }
@ -39,49 +43,41 @@ module.exports = class SurveyCommand extends Command {
const survey = await this.client.db.models.Survey.findOne({ const survey = await this.client.db.models.Survey.findOne({
where: { where: {
name: args, guild: message.guild.id,
guild: message.guild.id name: args
} }
}); });
if (survey) { if (survey) {
const { rows: responses, count } = await this.client.db.models.SurveyResponse.findAndCountAll({ const {
where: { rows: responses, count
survey: survey.id } = await this.client.db.models.SurveyResponse.findAndCountAll({ where: { survey: survey.id } });
}
});
const users = new Set(); const users = new Set();
for (const i in responses) { for (const i in responses) {
const ticket = await this.client.db.models.Ticket.findOne({ const ticket = await this.client.db.models.Ticket.findOne({ where: { id: responses[i].ticket } });
where: {
id: responses[i].ticket
}
});
users.add(ticket.creator); users.add(ticket.creator);
const answers = responses[i].answers.map(a => this.client.cryptr.decrypt(a)); const answers = responses[i].answers.map(a => this.client.cryptr.decrypt(a));
answers.unshift(ticket.number); answers.unshift(ticket.number);
responses[i] = answers; responses[i] = answers;
} }
let template = await fsp.readFile(path('./src/commands/extra/survey.template.html'), { let template = await fsp.readFile(path('./src/commands/extra/survey.template.html'), { encoding: 'utf8' });
encoding: 'utf8'
});
template = template.replace(/[\r\n\t]/g, ''); template = template.replace(/[\r\n\t]/g, '');
survey.questions.unshift('Ticket #'); survey.questions.unshift('Ticket #');
const html = mustache.render(template, { const html = mustache.render(template, {
survey: survey.name.charAt(0).toUpperCase() + survey.name.slice(1), columns: survey.questions,
count: { count: {
responses: count, responses: count,
users: users.size users: users.size
}, },
columns: survey.questions, responses,
responses survey: survey.name.charAt(0).toUpperCase() + survey.name.slice(1)
}); });
const attachment = new MessageAttachment( const attachment = new MessageAttachment(
@ -89,15 +85,9 @@ module.exports = class SurveyCommand extends Command {
`${survey.name}.html` `${survey.name}.html`
); );
return await message.channel.send({ return await message.channel.send({ files: [attachment] });
files: [attachment]
});
} else { } else {
const surveys = await this.client.db.models.Survey.findAll({ const surveys = await this.client.db.models.Survey.findAll({ where: { guild: message.guild.id } });
where: {
guild: message.guild.id
}
});
const list = surveys.map(s => ` **\`${s.name}\`**`); const list = surveys.map(s => ` **\`${s.name}\`**`);
return await message.channel.send( return await message.channel.send(

View File

@ -1,6 +1,8 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
const { parseArgsStringToArgv: argv } = require('string-argv'); const { parseArgsStringToArgv: argv } = require('string-argv');
const parseArgs = require('command-line-args'); const parseArgs = require('command-line-args');
@ -8,23 +10,23 @@ module.exports = class TagCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.tag.name'),
description: i18n('commands.tag.description'),
aliases: [ aliases: [
i18n('commands.tag.aliases.faq'), i18n('commands.tag.aliases.faq'),
i18n('commands.tag.aliases.t'), i18n('commands.tag.aliases.t'),
i18n('commands.tag.aliases.tags'), i18n('commands.tag.aliases.tags')
], ],
process_args: false,
args: [ args: [
{ {
name: i18n('commands.tag.args.tag.name'),
description: i18n('commands.tag.args.command.description'), description: i18n('commands.tag.args.command.description'),
example: i18n('commands.tag.args.tag.example'), example: i18n('commands.tag.args.tag.example'),
required: false, name: i18n('commands.tag.args.tag.name'),
required: false
} }
], ],
description: i18n('commands.tag.description'),
internal: true,
name: i18n('commands.tag.name'),
process_args: false,
staff_only: true staff_only: true
}); });
} }
@ -38,11 +40,7 @@ module.exports = class TagCommand extends Command {
const settings = await message.guild.getSettings(); const settings = await message.guild.getSettings();
const i18n = this.client.i18n.getLocale(settings.locale); const i18n = this.client.i18n.getLocale(settings.locale);
const t_row = await this.client.db.models.Ticket.findOne({ const t_row = await this.client.db.models.Ticket.findOne({ where: { id: message.channel.id } });
where: {
id: message.channel.id
}
});
args = args.split(/\s/g); // convert to an array args = args.split(/\s/g); // convert to an array
const tag_name = args.shift(); // shift the first element const tag_name = args.shift(); // shift the first element
@ -63,14 +61,12 @@ module.exports = class TagCommand extends Command {
); );
} }
let expected = placeholders const expected = placeholders
.filter(p => p.startsWith(':')) .filter(p => p.startsWith(':'))
.map(p => { .map(p => ({
return { name: p.substr(1, p.length),
name: p.substr(1, p.length), type: String
type: String, }));
};
});
if (expected.length >= 1) { if (expected.length >= 1) {
try { try {
@ -107,7 +103,7 @@ module.exports = class TagCommand extends Command {
} }
// note that this regex is slightly different to the other // note that this regex is slightly different to the other
const text = tag.replace(/(?<!\\){{1,2}\s?:?([A-Za-z0-9._]+)\s?(?<!\\)}{1,2}/gi, ($, $1) => this.client.i18n.resolve(args, $1)); const text = tag.replace(/(?<!\\){{1,2}\s?:?([A-Za-z0-9._]+)\s?(?<!\\)}{1,2}/gi, (_$, $1) => this.client.i18n.resolve(args, $1));
return await message.channel.send( return await message.channel.send(
new MessageEmbed() new MessageEmbed()
.setColor(settings.colour) .setColor(settings.colour)

View File

@ -1,24 +1,26 @@
const Command = require('../modules/commands/command'); const Command = require('../modules/commands/command');
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
module.exports = class TopicCommand extends Command { module.exports = class TopicCommand extends Command {
constructor(client) { constructor(client) {
const i18n = client.i18n.getLocale(client.config.locale); const i18n = client.i18n.getLocale(client.config.locale);
super(client, { super(client, {
internal: true,
name: i18n('commands.topic.name'),
description: i18n('commands.topic.description'),
aliases: [], aliases: [],
process_args: false,
args: [ args: [
{ {
name: i18n('commands.topic.args.new_topic.name'),
description: i18n('commands.topic.args.new_topic.description'), description: i18n('commands.topic.args.new_topic.description'),
example: i18n('commands.topic.args.new_topic.example'), example: i18n('commands.topic.args.new_topic.example'),
required: true, name: i18n('commands.topic.args.new_topic.name'),
required: true
} }
] ],
description: i18n('commands.topic.description'),
internal: true,
name: i18n('commands.topic.name'),
process_args: false
}); });
} }
@ -31,11 +33,7 @@ module.exports = class TopicCommand extends Command {
const settings = await message.guild.getSettings(); const settings = await message.guild.getSettings();
const i18n = this.client.i18n.getLocale(settings.locale); const i18n = this.client.i18n.getLocale(settings.locale);
const t_row = await this.client.db.models.Ticket.findOne({ const t_row = await this.client.db.models.Ticket.findOne({ where: { id: message.channel.id } });
where: {
id: message.channel.id
}
});
if (!t_row) { if (!t_row) {
return await message.channel.send( return await message.channel.send(
@ -47,18 +45,12 @@ module.exports = class TopicCommand extends Command {
); );
} }
await t_row.update({ await t_row.update({ topic: this.client.cryptr.encrypt(args) });
topic: this.client.cryptr.encrypt(args)
});
const member = await message.guild.members.fetch(t_row.creator); const member = await message.guild.members.fetch(t_row.creator);
/* await */message.channel.setTopic(`${member} | ${args}`, { reason: 'User updated ticket topic' }); /* await */message.channel.setTopic(`${member} | ${args}`, { reason: 'User updated ticket topic' });
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
where: {
id: t_row.category
}
});
const description = cat_row.opening_message const description = cat_row.opening_message
.replace(/{+\s?(user)?name\s?}+/gi, member.displayName) .replace(/{+\s?(user)?name\s?}+/gi, member.displayName)
.replace(/{+\s?(tag|ping|mention)?\s?}+/gi, member.user.toString()); .replace(/{+\s?(tag|ping|mention)?\s?}+/gi, member.user.toString());

View File

@ -1,42 +1,42 @@
module.exports = { module.exports = {
sqlite: {
name: 'SQLite',
dialect: 'sqlite',
packages: ['sqlite3'],
},
mysql: {
name: 'MySQL',
dialect: 'mysql',
packages: ['mysql2']
},
maria: { maria: {
name: 'MariaDB',
dialect: 'mariadb', dialect: 'mariadb',
name: 'MariaDB',
package: ['mariadb'] package: ['mariadb']
}, },
mariadb: { mariadb: {
name: 'MariaDB',
dialect: 'mariadb', dialect: 'mariadb',
name: 'MariaDB',
package: ['mariadb'] package: ['mariadb']
}, },
microsoft: {
dialect: 'mssql',
name: 'Microsoft SQL',
packages: ['tedious']
},
mysql: {
dialect: 'mysql',
name: 'MySQL',
packages: ['mysql2']
},
postgre: { // this is wrong postgre: { // this is wrong
name: 'PostgreSQL',
dialect: 'postgres', dialect: 'postgres',
name: 'PostgreSQL',
packages: ['pg', 'pg-hstore'] packages: ['pg', 'pg-hstore']
}, },
postgres: { postgres: {
name: 'PostgreSQL',
dialect: 'postgres', dialect: 'postgres',
name: 'PostgreSQL',
packages: ['pg', 'pg-hstore'] packages: ['pg', 'pg-hstore']
}, },
postgresql: { postgresql: {
name: 'PostgreSQL',
dialect: 'postgres', dialect: 'postgres',
name: 'PostgreSQL',
packages: ['pg', 'pg-hstore'] packages: ['pg', 'pg-hstore']
}, },
microsoft: { sqlite: {
name: 'Microsoft SQL', dialect: 'sqlite',
dialect: 'mssql', name: 'SQLite',
packages: ['tedious'] packages: ['sqlite3']
}, }
}; };

View File

@ -3,7 +3,7 @@ const fs = require('fs');
const { path } = require('../utils/fs'); const { path } = require('../utils/fs');
const types = require('./dialects'); const types = require('./dialects');
module.exports = async (client) => { module.exports = async client => {
const { const {
DB_TYPE, DB_TYPE,
@ -36,8 +36,8 @@ module.exports = async (client) => {
client.log.info('Using SQLite storage'); client.log.info('Using SQLite storage');
sequelize = new Sequelize({ sequelize = new Sequelize({
dialect: types[type].dialect, dialect: types[type].dialect,
storage: path('./user/database.sqlite'), logging: text => client.log.debug(text),
logging: text => client.log.debug(text) storage: path('./user/database.sqlite')
}); });
client.log.warn('SQLite is not sufficient for a production environment if you want to use ticket archives. You should disable "log_messages" in your servers\' settings or use a different database.'); client.log.warn('SQLite is not sufficient for a production environment if you want to use ticket archives. You should disable "log_messages" in your servers\' settings or use a different database.');
} else { } else {
@ -45,8 +45,8 @@ module.exports = async (client) => {
sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASS, { sequelize = new Sequelize(DB_NAME, DB_USER, DB_PASS, {
dialect: types[type].dialect, dialect: types[type].dialect,
host: DB_HOST, host: DB_HOST,
port: DB_PORT, logging: text => client.log.debug(text),
logging: text => client.log.debug(text) port: DB_PORT
}); });
} }
@ -66,11 +66,7 @@ module.exports = async (client) => {
require(`./models/${model}`)(client, sequelize); require(`./models/${model}`)(client, sequelize);
} }
sequelize.sync({ sequelize.sync({ alter: { drop: false } });
alter: {
drop: false
}
});
return sequelize; return sequelize;
}; };

View File

@ -2,67 +2,65 @@ const { DataTypes } = require('sequelize');
module.exports = ({ config }, sequelize) => { module.exports = ({ config }, sequelize) => {
const { DB_TABLE_PREFIX } = process.env; const { DB_TABLE_PREFIX } = process.env;
sequelize.define('Category', { sequelize.define('Category', {
id: {
type: DataTypes.CHAR(19),
primaryKey: true,
allowNull: false,
},
claiming: { claiming: {
type: DataTypes.BOOLEAN,
defaultValue: false, defaultValue: false,
type: DataTypes.BOOLEAN
}, },
guild: { guild: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
references: { references: {
model: DB_TABLE_PREFIX + 'guilds', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'guilds'
}, },
type: DataTypes.CHAR(19),
unique: 'name-guild' unique: 'name-guild'
}, },
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.CHAR(19)
},
image: { image: {
type: DataTypes.STRING,
allowNull: true, allowNull: true,
type: DataTypes.STRING
}, },
max_per_member: { max_per_member: {
type: DataTypes.INTEGER, defaultValue: 1,
defaultValue: 1 type: DataTypes.INTEGER
}, },
name: { name: {
type: DataTypes.STRING,
allowNull: false, allowNull: false,
type: DataTypes.STRING,
unique: 'name-guild' unique: 'name-guild'
}, },
name_format: { name_format: {
type: DataTypes.STRING,
allowNull: false, allowNull: false,
defaultValue: config.defaults.name_format defaultValue: config.defaults.name_format,
type: DataTypes.STRING
}, },
opening_message: { opening_message: {
type: DataTypes.STRING,
defaultValue: config.defaults.opening_message, defaultValue: config.defaults.opening_message,
type: DataTypes.STRING
}, },
opening_questions: { opening_questions: {
type: DataTypes.JSON,
allowNull: true, allowNull: true,
type: DataTypes.JSON
}, },
ping: { ping: {
type: DataTypes.JSON,
defaultValue: [], defaultValue: [],
type: DataTypes.JSON
}, },
require_topic: { require_topic: {
type: DataTypes.BOOLEAN,
defaultValue: false, defaultValue: false,
type: DataTypes.BOOLEAN
}, },
roles: { roles: {
type: DataTypes.JSON,
allowNull: false, allowNull: false,
type: DataTypes.JSON
}, },
survey: { survey: {
type: DataTypes.STRING,
allowNull: true, allowNull: true,
type: DataTypes.STRING
} }
}, { }, { tableName: DB_TABLE_PREFIX + 'categories' });
tableName: DB_TABLE_PREFIX + 'categories'
});
}; };

View File

@ -1,23 +1,21 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
module.exports = (client, sequelize) => { module.exports = (_client, sequelize) => {
const { DB_TABLE_PREFIX } = process.env; const { DB_TABLE_PREFIX } = process.env;
sequelize.define('ChannelEntity', { sequelize.define('ChannelEntity', {
channel: { channel: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
type: DataTypes.CHAR(19),
unique: 'channel-ticket' unique: 'channel-ticket'
}, },
name: DataTypes.TEXT, name: DataTypes.TEXT,
ticket: { ticket: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
unique: 'channel-ticket',
references: { references: {
model: DB_TABLE_PREFIX + 'tickets', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'tickets'
}, },
}, type: DataTypes.CHAR(19),
}, { unique: 'channel-ticket'
tableName: DB_TABLE_PREFIX + 'channel_entities' }
}); }, { tableName: DB_TABLE_PREFIX + 'channel_entities' });
}; };

View File

@ -2,48 +2,47 @@ const { DataTypes } = require('sequelize');
module.exports = ({ config }, sequelize) => { module.exports = ({ config }, sequelize) => {
const { DB_TABLE_PREFIX } = process.env; const { DB_TABLE_PREFIX } = process.env;
sequelize.define('Guild', { sequelize.define('Guild', {
id: {
type: DataTypes.CHAR(19),
primaryKey: true,
allowNull: false,
},
blacklist: { blacklist: {
type: DataTypes.JSON,
defaultValue: [], defaultValue: [],
type: DataTypes.JSON
}, },
colour: { colour: {
type: DataTypes.STRING, defaultValue: config.defaults.colour,
defaultValue: config.defaults.colour type: DataTypes.STRING
}, },
command_prefix: { command_prefix: {
type: DataTypes.STRING, defaultValue: config.defaults.command_prefix,
defaultValue: config.defaults.command_prefix type: DataTypes.STRING
}, },
error_colour: { error_colour: {
type: DataTypes.STRING, defaultValue: 'RED',
defaultValue: 'RED' type: DataTypes.STRING
}, },
footer: { footer: {
type: DataTypes.STRING, defaultValue: 'Discord Tickets by eartharoid',
defaultValue: 'Discord Tickets by eartharoid' type: DataTypes.STRING
},
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.CHAR(19)
}, },
locale: { locale: {
type: DataTypes.STRING, defaultValue: config.locale,
defaultValue: config.locale type: DataTypes.STRING
}, },
log_messages: { log_messages: {
type: DataTypes.BOOLEAN, defaultValue: config.defaults.log_messages,
defaultValue: config.defaults.log_messages type: DataTypes.BOOLEAN
}, },
success_colour: { success_colour: {
type: DataTypes.STRING, defaultValue: 'GREEN',
defaultValue: 'GREEN' type: DataTypes.STRING
}, },
tags: { tags: {
type: DataTypes.JSON, defaultValue: {},
defaultValue: {} type: DataTypes.JSON
} }
}, { }, { tableName: DB_TABLE_PREFIX + 'guilds' });
tableName: DB_TABLE_PREFIX + 'guilds'
});
}; };

View File

@ -1,37 +1,36 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
module.exports = (client, sequelize) => { module.exports = (_client, sequelize) => {
const { DB_TABLE_PREFIX } = process.env; const { DB_TABLE_PREFIX } = process.env;
sequelize.define('Message', { sequelize.define('Message', {
id: {
type: DataTypes.CHAR(19),
primaryKey: true,
allowNull: false,
},
author: { author: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
type: DataTypes.CHAR(19)
}, },
data: { data: {
type: DataTypes.TEXT,
allowNull: false, allowNull: false,
type: DataTypes.TEXT
}, },
deleted: { deleted: {
type: DataTypes.BOOLEAN,
defaultValue: false, defaultValue: false,
type: DataTypes.BOOLEAN
}, },
edited: { edited: {
type: DataTypes.BOOLEAN,
defaultValue: false, defaultValue: false,
type: DataTypes.BOOLEAN
},
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.CHAR(19)
}, },
ticket: { ticket: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
references: { references: {
model: DB_TABLE_PREFIX + 'tickets', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'tickets'
}, },
}, type: DataTypes.CHAR(19)
}, { }
tableName: DB_TABLE_PREFIX + 'messages' }, { tableName: DB_TABLE_PREFIX + 'messages' });
});
}; };

View File

@ -3,26 +3,24 @@ module.exports = (client, sequelize) => {
const { DB_TABLE_PREFIX } = process.env; const { DB_TABLE_PREFIX } = process.env;
sequelize.define('Panel', { sequelize.define('Panel', {
categories: { categories: {
type: DataTypes.JSON, allowNull: false,
allowNull: false type: DataTypes.JSON
}, },
channel: { channel: {
type: DataTypes.CHAR(19), allowNull: false,
allowNull: false type: DataTypes.CHAR(19)
}, },
guild: { guild: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
references: { references: {
model: DB_TABLE_PREFIX + 'guilds', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'guilds'
} },
type: DataTypes.CHAR(19)
}, },
message: { message: {
type: DataTypes.CHAR(19), allowNull: false,
allowNull: false type: DataTypes.CHAR(19)
} }
}, { }, { tableName: DB_TABLE_PREFIX + 'panels' });
tableName: DB_TABLE_PREFIX + 'panels'
});
}; };

View File

@ -3,25 +3,23 @@ module.exports = (client, sequelize) => {
const { DB_TABLE_PREFIX } = process.env; const { DB_TABLE_PREFIX } = process.env;
sequelize.define('RoleEntity', { sequelize.define('RoleEntity', {
colour: { colour: {
type: DataTypes.CHAR(6),
defaultValue: '7289DA', defaultValue: '7289DA',
type: DataTypes.CHAR(6)
}, },
name: DataTypes.TEXT, name: DataTypes.TEXT,
role: { role: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
type: DataTypes.CHAR(19),
unique: 'role-ticket' unique: 'role-ticket'
}, },
ticket: { ticket: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
unique: 'role-ticket',
references: { references: {
model: DB_TABLE_PREFIX + 'tickets', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'tickets'
}, },
}, type: DataTypes.CHAR(19),
}, { unique: 'role-ticket'
tableName: DB_TABLE_PREFIX + 'role_entities' }
}); }, { tableName: DB_TABLE_PREFIX + 'role_entities' });
}; };

View File

@ -3,24 +3,22 @@ module.exports = (client, sequelize) => {
const { DB_TABLE_PREFIX } = process.env; const { DB_TABLE_PREFIX } = process.env;
sequelize.define('Survey', { sequelize.define('Survey', {
guild: { guild: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
references: { references: {
model: DB_TABLE_PREFIX + 'guilds', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'guilds'
}, },
type: DataTypes.CHAR(19),
unique: 'name-guild' unique: 'name-guild'
}, },
name: { name: {
type: DataTypes.STRING,
allowNull: false, allowNull: false,
type: DataTypes.STRING,
unique: 'name-guild' unique: 'name-guild'
}, },
questions: { questions: {
type: DataTypes.JSON,
allowNull: true, allowNull: true,
}, type: DataTypes.JSON
}, { }
tableName: DB_TABLE_PREFIX + 'surveys' }, { tableName: DB_TABLE_PREFIX + 'surveys' });
});
}; };

View File

@ -3,28 +3,26 @@ module.exports = (client, sequelize) => {
const { DB_TABLE_PREFIX } = process.env; const { DB_TABLE_PREFIX } = process.env;
sequelize.define('SurveyResponse', { sequelize.define('SurveyResponse', {
answers: { answers: {
type: DataTypes.JSON,
allowNull: true, allowNull: true,
type: DataTypes.JSON
}, },
survey: { survey: {
type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
unique: 'survey-ticket',
references: { references: {
model: DB_TABLE_PREFIX + 'surveys', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'surveys'
}, },
type: DataTypes.INTEGER,
unique: 'survey-ticket'
}, },
ticket: { ticket: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
unique: 'survey-ticket',
references: { references: {
model: DB_TABLE_PREFIX + 'tickets', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'tickets'
}, },
}, type: DataTypes.CHAR(19),
}, { unique: 'survey-ticket'
tableName: DB_TABLE_PREFIX + 'survey_responses' }
}); }, { tableName: DB_TABLE_PREFIX + 'survey_responses' });
}; };

View File

@ -1,75 +1,73 @@
const { DataTypes } = require('sequelize'); const { DataTypes } = require('sequelize');
module.exports = (client, sequelize) => { module.exports = (_client, sequelize) => {
const { DB_TABLE_PREFIX } = process.env; const { DB_TABLE_PREFIX } = process.env;
sequelize.define('Ticket', { sequelize.define('Ticket', {
id: {
type: DataTypes.CHAR(19),
primaryKey: true,
allowNull: false,
},
category: { category: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
references: { references: {
model: DB_TABLE_PREFIX + 'categories', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'categories'
}, },
type: DataTypes.CHAR(19)
}, },
claimed_by: { claimed_by: {
type: DataTypes.CHAR(19),
allowNull: true, allowNull: true,
type: DataTypes.CHAR(19)
}, },
closed_by: { closed_by: {
type: DataTypes.CHAR(19),
allowNull: true, allowNull: true,
type: DataTypes.CHAR(19)
}, },
closed_reason: { closed_reason: {
type: DataTypes.STRING,
allowNull: true, allowNull: true,
type: DataTypes.STRING
}, },
creator: { creator: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
type: DataTypes.CHAR(19)
}, },
first_response: { first_response: {
type: DataTypes.DATE,
allowNull: true, allowNull: true,
type: DataTypes.DATE
}, },
guild: { guild: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
references: { references: {
model: DB_TABLE_PREFIX + 'guilds', key: 'id',
key: 'id' model: DB_TABLE_PREFIX + 'guilds'
}, },
type: DataTypes.CHAR(19),
unique: 'number-guild' unique: 'number-guild'
}, },
id: {
allowNull: false,
primaryKey: true,
type: DataTypes.CHAR(19)
},
last_message: { last_message: {
type: DataTypes.DATE,
allowNull: true, allowNull: true,
type: DataTypes.DATE
}, },
number: { number: {
type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
type: DataTypes.INTEGER,
unique: 'number-guild' unique: 'number-guild'
}, },
open: { open: {
type: DataTypes.BOOLEAN, defaultValue: true,
defaultValue: true type: DataTypes.BOOLEAN
}, },
opening_message: { opening_message: {
type: DataTypes.CHAR(19),
allowNull: true, allowNull: true,
type: DataTypes.CHAR(19)
}, },
pinned_messages: { pinned_messages: {
type: DataTypes.JSON, defaultValue: [],
defaultValue: [] type: DataTypes.JSON
}, },
topic: { topic: {
type: DataTypes.TEXT,
allowNull: true, allowNull: true,
}, type: DataTypes.TEXT
}, { }
tableName: DB_TABLE_PREFIX + 'tickets' }, { tableName: DB_TABLE_PREFIX + 'tickets' });
});
}; };

View File

@ -6,30 +6,29 @@ module.exports = (client, sequelize) => {
bot: DataTypes.BOOLEAN, bot: DataTypes.BOOLEAN,
discriminator: DataTypes.STRING, discriminator: DataTypes.STRING,
display_name: DataTypes.TEXT, display_name: DataTypes.TEXT,
ticket: { role: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
unique: 'user-ticket',
references: { references: {
model: DB_TABLE_PREFIX + 'tickets', key: 'role',
key: 'id' model: DB_TABLE_PREFIX + 'role_entities'
}, },
type: DataTypes.CHAR(19)
}, },
user: { ticket: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
references: {
key: 'id',
model: DB_TABLE_PREFIX + 'tickets'
},
type: DataTypes.CHAR(19),
unique: 'user-ticket' unique: 'user-ticket'
}, },
username: DataTypes.TEXT, user: {
role: {
type: DataTypes.CHAR(19),
allowNull: false, allowNull: false,
references: { type: DataTypes.CHAR(19),
model: DB_TABLE_PREFIX + 'role_entities', unique: 'user-ticket'
key: 'role'
},
}, },
}, { username: DataTypes.TEXT
tableName: DB_TABLE_PREFIX + 'user_entities'
}); }, { tableName: DB_TABLE_PREFIX + 'user_entities' });
}; };

View File

@ -1,3 +1,4 @@
/* eslint-disable no-console */
/** /**
* Discord Tickets * Discord Tickets
* Copyright (C) 2021 Isaac Saunders * Copyright (C) 2021 Isaac Saunders
@ -24,8 +25,7 @@
process.title = 'Discord Tickets'; process.title = 'Discord Tickets';
const node_version = Number(process.versions.node.split('.')[0]); const node_version = Number(process.versions.node.split('.')[0]);
if (node_version < 14) if (node_version < 14) return console.log(`\x07Error: Discord Tickets does not work on Node v${node_version}. Please upgrade to v14 or above.`);
return console.log(`\x07Error: Discord Tickets does not work on Node v${node_version}. Please upgrade to v14 or above.`);
const leeks = require('leeks.js'); const leeks = require('leeks.js');
const fs = require('fs'); const fs = require('fs');
@ -55,9 +55,7 @@ if (!checkFile('./.env', './example.env')) {
.randomBytes(24) .randomBytes(24)
.toString('hex'); .toString('hex');
let data = fs.readFileSync(file, { let data = fs.readFileSync(file, { encoding: 'utf-8' });
encoding: 'utf-8'
});
data = data.replace(key, key + value); data = data.replace(key, key + value);
fs.writeFileSync(file, data); fs.writeFileSync(file, data);
@ -69,9 +67,7 @@ if (!checkFile('./.env', './example.env')) {
process.exit(); process.exit();
} }
require('dotenv').config({ require('dotenv').config({ path: path('./.env') });
path: path('./.env')
});
require('./banner')(); require('./banner')();
@ -119,9 +115,7 @@ class Bot extends Client {
'REACTION' 'REACTION'
], ],
presence: selectPresence(), presence: selectPresence(),
ws: { ws: { intents: Intents.NON_PRIVILEGED }
intents: Intents.NON_PRIVILEGED,
}
}); });
(async () => { (async () => {
@ -144,9 +138,7 @@ class Bot extends Client {
fs.readdirSync(path('./src/locales')) fs.readdirSync(path('./src/locales'))
.filter(file => file.endsWith('.json')) .filter(file => file.endsWith('.json'))
.forEach(file => { .forEach(file => {
const data = fs.readFileSync(path(`./src/locales/${file}`), { const data = fs.readFileSync(path(`./src/locales/${file}`), { encoding: 'utf8' });
encoding: 'utf8'
});
const name = file.slice(0, file.length - 5); const name = file.slice(0, file.length - 5);
locales[name] = JSON.parse(data); locales[name] = JSON.parse(data);
}); });
@ -191,17 +183,13 @@ class Bot extends Client {
*/ */
if (this.config.super_secret_setting) { // you can disable it if you really want if (this.config.super_secret_setting) { // you can disable it if you really want
const tickets = await this.db.models.Ticket.count(); const tickets = await this.db.models.Ticket.count();
await fetch(`https://stats.discordtickets.app/client?id=${this.user.id}&tickets=${tickets}`, { await fetch(`https://stats.discordtickets.app/client?id=${this.user.id}&tickets=${tickets}`, { method: 'post' }).catch(e => {
method: 'post',
}).catch(e => {
this.log.warn('Failed to post tickets count to stats server (you can disable sending stats by setting "super_secret_setting" to false)'); this.log.warn('Failed to post tickets count to stats server (you can disable sending stats by setting "super_secret_setting" to false)');
this.log.debug(e); this.log.debug(e);
}); });
this.guilds.cache.forEach(async g => { this.guilds.cache.forEach(async g => {
const members = (await g.fetch()).approximateMemberCount; const members = (await g.fetch()).approximateMemberCount;
await fetch(`https://stats.discordtickets.app/guild?id=${g.id}&members=${members}`, { await fetch(`https://stats.discordtickets.app/guild?id=${g.id}&members=${members}`, { method: 'post' }).catch(e => {
method: 'post',
}).catch(e => {
// don't spam a warning for each server // don't spam a warning for each server
this.log.debug(e); this.log.debug(e);
}); });

View File

@ -2,9 +2,7 @@ const EventListener = require('../modules/listeners/listener');
module.exports = class DebugEventListener extends EventListener { module.exports = class DebugEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'debug' });
event: 'debug'
});
} }
async execute(data) { async execute(data) {

View File

@ -2,9 +2,7 @@ const EventListener = require('../modules/listeners/listener');
module.exports = class ErrorEventListener extends EventListener { module.exports = class ErrorEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'error' });
event: 'error'
});
} }
async execute(error) { async execute(error) {

View File

@ -2,9 +2,7 @@ const EventListener = require('../modules/listeners/listener');
module.exports = class GuildCreateEventListener extends EventListener { module.exports = class GuildCreateEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'guildCreate' });
event: 'guildCreate'
});
} }
async execute(guild) { async execute(guild) {

View File

@ -2,9 +2,7 @@ const EventListener = require('../modules/listeners/listener');
module.exports = class GuildDeleteEventListener extends EventListener { module.exports = class GuildDeleteEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'guildDelete' });
event: 'guildDelete'
});
} }
async execute(guild) { async execute(guild) {

View File

@ -2,9 +2,7 @@ const EventListener = require('../modules/listeners/listener');
module.exports = class GuildMemberRemoveEventListener extends EventListener { module.exports = class GuildMemberRemoveEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'guildMemberRemove' });
event: 'guildMemberRemove'
});
} }
async execute(member) { async execute(member) {

View File

@ -5,9 +5,7 @@ const { footer } = require('../utils/discord');
module.exports = class MessageEventListener extends EventListener { module.exports = class MessageEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'message' });
event: 'message'
});
} }
async execute(message) { async execute(message) {
@ -16,11 +14,7 @@ module.exports = class MessageEventListener extends EventListener {
const settings = await message.guild.getSettings(); const settings = await message.guild.getSettings();
const i18n = this.client.i18n.getLocale(settings.locale); const i18n = this.client.i18n.getLocale(settings.locale);
const t_row = await this.client.db.models.Ticket.findOne({ const t_row = await this.client.db.models.Ticket.findOne({ where: { id: message.channel.id } });
where: {
id: message.channel.id
}
});
if (t_row) { if (t_row) {
if (settings.log_messages && !message.system) this.client.tickets.archives.addMessage(message); // add the message to the archives (if it is in a ticket channel) if (settings.log_messages && !message.system) this.client.tickets.archives.addMessage(message); // add the message to the archives (if it is in a ticket channel)
@ -33,22 +27,14 @@ module.exports = class MessageEventListener extends EventListener {
} else { } else {
if (message.author.bot) return; if (message.author.bot) return;
const p_row = await this.client.db.models.Panel.findOne({ const p_row = await this.client.db.models.Panel.findOne({ where: { channel: message.channel.id } });
where: {
channel: message.channel.id
}
});
if (p_row && typeof p_row.categories === 'string') { if (p_row && typeof p_row.categories === 'string') {
// handle reaction-less panel // handle reaction-less panel
await message.delete(); await message.delete();
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({ where: { id: p_row.categories } });
where: {
id: p_row.categories
}
});
const tickets = await this.client.db.models.Ticket.findAndCountAll({ const tickets = await this.client.db.models.Ticket.findAndCountAll({
where: { where: {

View File

@ -2,9 +2,7 @@ const EventListener = require('../modules/listeners/listener');
module.exports = class MessageDeleteEventListener extends EventListener { module.exports = class MessageDeleteEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'messageDelete' });
event: 'messageDelete'
});
} }
async execute(message) { async execute(message) {

View File

@ -5,9 +5,7 @@ const { footer } = require('../utils/discord');
module.exports = class MessageReactionAddEventListener extends EventListener { module.exports = class MessageReactionAddEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'messageReactionAdd' });
event: 'messageReactionAdd'
});
} }
async execute(r, u) { async execute(r, u) {
@ -49,34 +47,20 @@ module.exports = class MessageReactionAddEventListener extends EventListener {
}); });
} }
const t_row = await this.client.db.models.Ticket.findOne({ const t_row = await this.client.db.models.Ticket.findOne({ where: { id: channel.id } });
where: {
id: channel.id
}
});
if (t_row && t_row.opening_message === r.message.id) { if (t_row && t_row.opening_message === r.message.id) {
if (r.emoji.name === '🙌' && await member.isStaff()) { if (r.emoji.name === '🙌' && await member.isStaff()) {
// ticket claiming // ticket claiming
await t_row.update({ await t_row.update({ claimed_by: member.user.id });
claimed_by: member.user.id
});
await channel.updateOverwrite(member.user.id, { await channel.updateOverwrite(member.user.id, { VIEW_CHANNEL: true }, `Ticket claimed by ${member.user.tag}`);
VIEW_CHANNEL: true,
}, `Ticket claimed by ${member.user.tag}`);
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
where: {
id: t_row.category
}
});
for (const role of cat_row.roles) { for (const role of cat_row.roles) {
await channel.updateOverwrite(role, { await channel.updateOverwrite(role, { VIEW_CHANNEL: false }, `Ticket claimed by ${member.user.tag}`);
VIEW_CHANNEL: false,
}, `Ticket claimed by ${member.user.tag}`);
} }
this.client.log.info(`${member.user.tag} has claimed "${channel.name}" in "${guild.name}"`); this.client.log.info(`${member.user.tag} has claimed "${channel.name}" in "${guild.name}"`);
@ -93,11 +77,7 @@ module.exports = class MessageReactionAddEventListener extends EventListener {
await r.users.remove(u.id); await r.users.remove(u.id);
} }
} else { } else {
const p_row = await this.client.db.models.Panel.findOne({ const p_row = await this.client.db.models.Panel.findOne({ where: { message: r.message.id } });
where: {
message: r.message.id
}
});
if (p_row && typeof p_row.categories !== 'string') { if (p_row && typeof p_row.categories !== 'string') {
// panels // panels
@ -106,11 +86,7 @@ module.exports = class MessageReactionAddEventListener extends EventListener {
const category_id = p_row.categories[r.emoji.name]; const category_id = p_row.categories[r.emoji.name];
if (!category_id) return; if (!category_id) return;
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({ where: { id: category_id } });
where: {
id: category_id
}
});
const tickets = await this.client.db.models.Ticket.findAndCountAll({ const tickets = await this.client.db.models.Ticket.findAndCountAll({
where: { where: {

View File

@ -4,9 +4,7 @@ const { MessageEmbed } = require('discord.js');
module.exports = class MessageReactionRemoveEventListener extends EventListener { module.exports = class MessageReactionRemoveEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'messageReactionRemove' });
event: 'messageReactionRemove'
});
} }
async execute(r, u) { async execute(r, u) {
@ -38,34 +36,22 @@ module.exports = class MessageReactionRemoveEventListener extends EventListener
const channel = r.message.channel; const channel = r.message.channel;
const member = await guild.members.fetch(u.id); const member = await guild.members.fetch(u.id);
const t_row = await this.client.db.models.Ticket.findOne({ const t_row = await this.client.db.models.Ticket.findOne({ where: { id: channel.id } });
where: {
id: channel.id
}
});
if (t_row && t_row.opening_message === r.message.id) { if (t_row && t_row.opening_message === r.message.id) {
if (r.emoji.name === '🙌' && await member.isStaff()) { if (r.emoji.name === '🙌' && await member.isStaff()) {
// ticket claiming // ticket claiming
await t_row.update({ await t_row.update({ claimed_by: null });
claimed_by: null
});
await channel.permissionOverwrites await channel.permissionOverwrites
.get(member.user.id) .get(member.user.id)
?.delete(`Ticket released by ${member.user.tag}`); ?.delete(`Ticket released by ${member.user.tag}`);
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
where: {
id: t_row.category
}
});
for (const role of cat_row.roles) { for (const role of cat_row.roles) {
await channel.updateOverwrite(role, { await channel.updateOverwrite(role, { VIEW_CHANNEL: true }, `Ticket released by ${member.user.tag}`);
VIEW_CHANNEL: true,
}, `Ticket released by ${member.user.tag}`);
} }
this.client.log.info(`${member.user.tag} has released "${channel.name}" in "${guild.name}"`); this.client.log.info(`${member.user.tag} has released "${channel.name}" in "${guild.name}"`);

View File

@ -2,9 +2,7 @@ const EventListener = require('../modules/listeners/listener');
module.exports = class MessageUpdateEventListener extends EventListener { module.exports = class MessageUpdateEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'messageUpdate' });
event: 'messageUpdate'
});
} }
async execute(oldm, newm) { async execute(oldm, newm) {

View File

@ -2,9 +2,7 @@ const EventListener = require('../modules/listeners/listener');
module.exports = class RateLimitEventListener extends EventListener { module.exports = class RateLimitEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'rateLimit' });
event: 'rateLimit'
});
} }
async execute(limit) { async execute(limit) {

View File

@ -2,9 +2,7 @@ const EventListener = require('../modules/listeners/listener');
module.exports = class WarnEventListener extends EventListener { module.exports = class WarnEventListener extends EventListener {
constructor(client) { constructor(client) {
super(client, { super(client, { event: 'warn' });
event: 'warn'
});
} }
async execute(warning) { async execute(warning) {

View File

@ -2,60 +2,38 @@ const { path } = require('./utils/fs');
const config = require('../user/config'); const config = require('../user/config');
const Logger = require('leekslazylogger-fastify'); const Logger = require('leekslazylogger-fastify');
module.exports = new Logger({ module.exports = new Logger({
name: 'Discord Tickets by eartharoid',
debug: config.debug, debug: config.debug,
logToFile: config.logs.enabled,
splitFile: config.logs.split,
directory: path('./logs/'), directory: path('./logs/'),
keepFor: config.logs.keep_for, keepFor: config.logs.keep_for,
timestamp: 'YYYY-MM-DD HH:mm:ss',
levels: { levels: {
_logger: { _logger: { format: '&f&!7{timestamp}&r [LOGGER] {text}' },
format: '&f&!7{timestamp}&r [LOGGER] {text}' basic: { format: '&f&!7{timestamp} {text}' },
},
basic: {
format: '&f&!7{timestamp} {text}'
},
console: {
format: '&f&!7{timestamp} [INFO] {text}'
},
info: {
format: '&f&!7{timestamp}&r &3[INFO] &b{text}'
},
success: {
format: '&f&!7{timestamp}&r &2[SUCCESS] &a{text}'
},
debug: {
format: '&f&!7{timestamp}&r &1[DEBUG] &9{text}'
},
notice: {
format: '&f&!7{timestamp}&r &0&!6[NOTICE] {text}'
},
warn: {
format: '&f&!7{timestamp}&r &6[WARN] &e{text}'
},
error: {
format: '&f&!7{timestamp}&r &4[ERROR] &c{text}'
},
commands: { commands: {
type: 'info', format: '&f&!7{timestamp}&r &3[INFO] &d(COMMANDS)&r {text}',
format: '&f&!7{timestamp}&r &3[INFO] &d(COMMANDS)&r {text}' type: 'info'
},
plugins: {
type: 'info',
format: '&f&!7{timestamp}&r &3[INFO] &d(PLUGINS)&r {text}'
},
tickets: {
type: 'info',
format: '&f&!7{timestamp}&r &3[INFO] &d(TICKETS)&r {text}'
}, },
console: { format: '&f&!7{timestamp} [INFO] {text}' },
debug: { format: '&f&!7{timestamp}&r &1[DEBUG] &9{text}' },
error: { format: '&f&!7{timestamp}&r &4[ERROR] &c{text}' },
http: { http: {
type: 'info', format: '&f&!7{timestamp}&r &3[INFO] &d(HTTP)&r {text}',
format: '&f&!7{timestamp}&r &3[INFO] &d(HTTP)&r {text}' type: 'info'
}, },
info: { format: '&f&!7{timestamp}&r &3[INFO] &b{text}' },
notice: { format: '&f&!7{timestamp}&r &0&!6[NOTICE] {text}' },
plugins: {
format: '&f&!7{timestamp}&r &3[INFO] &d(PLUGINS)&r {text}',
type: 'info'
},
success: { format: '&f&!7{timestamp}&r &2[SUCCESS] &a{text}' },
warn: { format: '&f&!7{timestamp}&r &6[WARN] &e{text}' },
ws: { ws: {
type: 'info', format: '&f&!7{timestamp}&r &3[INFO] &d(WS)&r {text}',
format: '&f&!7{timestamp}&r &3[INFO] &d(WS)&r {text}' type: 'info'
} }
} },
logToFile: config.logs.enabled,
name: 'Discord Tickets by eartharoid',
splitFile: config.logs.split,
timestamp: 'YYYY-MM-DD HH:mm:ss'
}); });

View File

@ -1,5 +1,7 @@
// eslint-disable-next-line no-unused-vars const {
const { MessageEmbed, Message } = require('discord.js'); Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
/** /**
* A command * A command
@ -53,7 +55,7 @@ module.exports = class Command {
/** /**
* The command description * The command description
* @type {string} * @type {string}
*/ */
this.description = data.description; this.description = data.description;
/** /**
@ -61,12 +63,12 @@ module.exports = class Command {
* @type {boolean} * @type {boolean}
* @default false * @default false
*/ */
this.staff_only = data.staff_only === true ? true : false; this.staff_only = data.staff_only === true;
/** /**
* Array of permissions needed for a user to use this command * Array of permissions needed for a user to use this command
* @type {string[]} * @type {string[]}
*/ */
this.permissions = data.permissions ?? []; this.permissions = data.permissions ?? [];
/** /**
@ -74,7 +76,7 @@ module.exports = class Command {
* @type {boolean} * @type {boolean}
* @default false * @default false
*/ */
this.process_args = data.process_args === true ? true : false; this.process_args = data.process_args === true;
/** /**
* The command options * The command options
@ -86,7 +88,7 @@ module.exports = class Command {
* True if command is internal, false if it is from a plugin * True if command is internal, false if it is from a plugin
* @type {boolean} * @type {boolean}
*/ */
this.internal = data.internal === true ? true : false; this.internal = data.internal === true;
if (!this.internal) { if (!this.internal) {
/** /**

View File

@ -1,5 +1,10 @@
// eslint-disable-next-line no-unused-vars
const { Collection, Client, Message, MessageEmbed } = require('discord.js'); const {
Client, // eslint-disable-line no-unused-vars
Collection,
Message, // eslint-disable-line no-unused-vars
MessageEmbed
} = require('discord.js');
const fs = require('fs'); const fs = require('fs');
const { path } = require('../../utils/fs'); const { path } = require('../../utils/fs');
@ -49,14 +54,12 @@ module.exports = class CommandManager {
if (is_internal) { if (is_internal) {
const plugin = this.client.plugins.plugins.find(p => p.commands.includes(cmd.name)); const plugin = this.client.plugins.plugins.find(p => p.commands.includes(cmd.name));
if (plugin) if (plugin) this.client.log.commands(`The "${plugin.name}" plugin has overridden the internal "${cmd.name}" command`);
this.client.log.commands(`The "${plugin.name}" plugin has overridden the internal "${cmd.name}" command`); else this.client.log.commands(`An unknown plugin has overridden the internal "${cmd.name}" command`);
else
this.client.log.commands(`An unknown plugin has overridden the internal "${cmd.name}" command`);
if(cmd.internal) return; if(cmd.internal) return;
} } else if (exists) {
else if (exists)
throw new Error(`A non-internal command with the name "${cmd.name}" already exists`); throw new Error(`A non-internal command with the name "${cmd.name}" already exists`);
}
this.commands.set(cmd.name, cmd); this.commands.set(cmd.name, cmd);
this.client.log.commands(`Loaded "${cmd.name}" command`); this.client.log.commands(`Loaded "${cmd.name}" command`);
@ -114,7 +117,7 @@ module.exports = class CommandManager {
'MANAGE_CHANNELS', 'MANAGE_CHANNELS',
'MANAGE_MESSAGES', 'MANAGE_MESSAGES',
'READ_MESSAGE_HISTORY', 'READ_MESSAGE_HISTORY',
'SEND_MESSAGES', 'SEND_MESSAGES'
]; ];
if (!bot_permissions.has(required_bot_permissions)) { if (!bot_permissions.has(required_bot_permissions)) {

View File

@ -23,10 +23,8 @@ module.exports = class ListenerLoader {
file = require(`../../listeners/${file}`); file = require(`../../listeners/${file}`);
const listener = new file(this.client); const listener = new file(this.client);
const on = listener.once ? 'once' : 'on'; const on = listener.once ? 'once' : 'on';
if (listener.raw) if (listener.raw) this.client.ws[on](listener.event, (...data) => listener.execute(...data));
this.client.ws[on](listener.event, (...data) => listener.execute(...data)); else this.client[on](listener.event, (...data) => listener.execute(...data));
else
this.client[on](listener.event, (...data) => listener.execute(...data));
} catch (e) { } catch (e) {
this.client.log.warn('An error occurred whilst loading a listener'); this.client.log.warn('An error occurred whilst loading a listener');
this.client.log.error(e); this.client.log.error(e);

View File

@ -29,8 +29,7 @@ module.exports = class PluginManager {
} }
handleError(id) { handleError(id) {
if (!this.official.includes(id)) if (!this.official.includes(id)) this.client.log.notice(`"${id}" is NOT an official plugin, please do not ask for help with it in the Discord Tickets support server, seek help from the plugin author instead.`);
this.client.log.notice(`"${id}" is NOT an official plugin, please do not ask for help with it in the Discord Tickets support server, seek help from the plugin author instead.`);
} }
/** /**
@ -56,10 +55,10 @@ module.exports = class PluginManager {
} }
const about = { const about = {
id,
version,
author, author,
description description,
id,
version
}; };
try { try {

View File

@ -5,6 +5,5 @@ module.exports = () => {
const files = fs.readdirSync(path('./src/structures')) const files = fs.readdirSync(path('./src/structures'))
.filter(file => file.endsWith('.js')); .filter(file => file.endsWith('.js'));
for (const file of files) for (const file of files) require(`../structures/${file}`);
require(`../structures/${file}`);
}; };

View File

@ -20,26 +20,22 @@ module.exports = class TicketArchives {
try { try {
// await this.client.db.transaction(async t => { // await this.client.db.transaction(async t => {
const t_row = await this.client.db.models.Ticket.findOne({ const t_row = await this.client.db.models.Ticket.findOne({
where: { where: { id: message.channel.id }
id: message.channel.id
},
/* transaction: t */ /* transaction: t */
}); });
if (t_row) { if (t_row) {
await this.client.db.models.Message.create({ await this.client.db.models.Message.create({
id: message.id,
ticket: t_row.id,
author: message.author.id, author: message.author.id,
createdAt: new Date(message.createdTimestamp),
data: this.encrypt(JSON.stringify({ data: this.encrypt(JSON.stringify({
attachments: [...message.attachments.values()],
content: message.content, content: message.content,
embeds: message.embeds.map(embed => { embeds: message.embeds.map(embed => ({ embed }))
return { embed };
}),
attachments: [...message.attachments.values()]
})), })),
createdAt: new Date(message.createdTimestamp) id: message.id,
}, /* { transaction: t } */); ticket: t_row.id
} /* { transaction: t } */);
await this.updateEntities(message); await this.updateEntities(message);
} }
@ -54,19 +50,15 @@ module.exports = class TicketArchives {
try { try {
// await this.client.db.transaction(async t => { // await this.client.db.transaction(async t => {
const m_row = await this.client.db.models.Message.findOne({ const m_row = await this.client.db.models.Message.findOne({
where: { where: { id: message.id }
id: message.id
},
/* transaction: t */ /* transaction: t */
}); });
if (m_row) { if (m_row) {
m_row.data = this.encrypt(JSON.stringify({ m_row.data = this.encrypt(JSON.stringify({
attachments: [...message.attachments.values()],
content: message.content, content: message.content,
embeds: message.embeds.map(embed => { embeds: message.embeds.map(embed => ({ embed }))
return { embed };
}),
attachments: [...message.attachments.values()]
})); }));
if (message.editedTimestamp) { if (message.editedTimestamp) {
@ -87,9 +79,7 @@ module.exports = class TicketArchives {
try { try {
// await this.client.db.transaction(async t => { // await this.client.db.transaction(async t => {
const msg = await this.client.db.models.Message.findOne({ const msg = await this.client.db.models.Message.findOne({
where: { where: { id: message.id }
id: message.id
},
/* transaction: t */ /* transaction: t */
}); });
@ -130,27 +120,27 @@ module.exports = class TicketArchives {
try { try {
// await this.client.db.transaction(async t => { // await this.client.db.transaction(async t => {
const u_model_data = { const u_model_data = {
user: member.user.id, ticket: ticket_id,
ticket: ticket_id user: member.user.id
}; };
const [u_row] = await this.client.db.models.UserEntity.findOrCreate({ const [u_row] = await this.client.db.models.UserEntity.findOrCreate({
where: u_model_data,
defaults: { defaults: {
...u_model_data, ...u_model_data,
role: member.roles.highest.id role: member.roles.highest.id
}, },
where: u_model_data
/* transaction: t */ /* transaction: t */
}); });
await u_row.update({ await u_row.update({
avatar: member.user.avatar, avatar: member.user.avatar,
username: this.encrypt(member.user.username), bot: member.user.bot,
discriminator: member.user.discriminator, discriminator: member.user.discriminator,
display_name: this.encrypt(member.displayName), display_name: this.encrypt(member.displayName),
role: member.roles.highest.id, role: member.roles.highest.id,
bot: member.user.bot username: this.encrypt(member.user.username)
}, /* { transaction: t } */); } /* { transaction: t } */);
return u_row; return u_row;
// }); // });
@ -168,14 +158,12 @@ module.exports = class TicketArchives {
ticket: ticket_id ticket: ticket_id
}; };
const [c_row] = await this.client.db.models.ChannelEntity.findOrCreate({ const [c_row] = await this.client.db.models.ChannelEntity.findOrCreate({
where: c_model_data,
defaults: c_model_data, defaults: c_model_data,
where: c_model_data
/* transaction: t */ /* transaction: t */
}); });
await c_row.update({ await c_row.update({ name: this.encrypt(channel.name) } /* { transaction: t } */);
name: this.encrypt(channel.name)
}, /* { transaction: t } */);
return c_row; return c_row;
// }); // });
@ -193,15 +181,15 @@ module.exports = class TicketArchives {
ticket: ticket_id ticket: ticket_id
}; };
const [r_row] = await this.client.db.models.RoleEntity.findOrCreate({ const [r_row] = await this.client.db.models.RoleEntity.findOrCreate({
where: r_model_data,
defaults: r_model_data, defaults: r_model_data,
where: r_model_data
/* transaction: t */ /* transaction: t */
}); });
await r_row.update({ await r_row.update({
name: this.encrypt(role.name), colour: role.color === 0 ? '7289DA' : int2hex(role.color), // 7289DA = 7506394
colour: role.color === 0 ? '7289DA' : int2hex(role.color) // 7289DA = 7506394 name: this.encrypt(role.name)
}, /* { transaction: t } */); } /* { transaction: t } */);
return r_row; return r_row;
// }); // });

View File

@ -1,3 +1,4 @@
/* eslint-disable max-lines */
const EventEmitter = require('events'); const EventEmitter = require('events');
const TicketArchives = require('./archives'); const TicketArchives = require('./archives');
const { MessageEmbed } = require('discord.js'); const { MessageEmbed } = require('discord.js');
@ -30,25 +31,15 @@ module.exports = class TicketManager extends EventEmitter {
async create(guild_id, creator_id, category_id, topic) { async create(guild_id, creator_id, category_id, topic) {
if (!topic) topic = ''; if (!topic) topic = '';
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({ where: { id: category_id } });
where: {
id: category_id
}
});
if (!cat_row) if (!cat_row) throw new Error('Ticket category does not exist');
throw new Error('Ticket category does not exist');
const cat_channel = await this.client.channels.fetch(category_id); const cat_channel = await this.client.channels.fetch(category_id);
if (cat_channel.children.size >= 50) if (cat_channel.children.size >= 50) throw new Error('Ticket category has reached child channel limit (50)');
throw new Error('Ticket category has reached child channel limit (50)');
const number = (await this.client.db.models.Ticket.count({ const number = (await this.client.db.models.Ticket.count({ where: { guild: guild_id } })) + 1;
where: {
guild: guild_id
}
})) + 1;
const guild = this.client.guilds.cache.get(guild_id); const guild = this.client.guilds.cache.get(guild_id);
const creator = await guild.members.fetch(creator_id); const creator = await guild.members.fetch(creator_id);
@ -57,25 +48,25 @@ module.exports = class TicketManager extends EventEmitter {
.replace(/{+\s?num(ber)?\s?}+/gi, number); .replace(/{+\s?num(ber)?\s?}+/gi, number);
const t_channel = await guild.channels.create(name, { const t_channel = await guild.channels.create(name, {
type: 'text',
topic: `${creator}${topic.length > 0 ? ` | ${topic}` : ''}`,
parent: category_id, parent: category_id,
reason: `${creator.user.tag} requested a new ticket channel` reason: `${creator.user.tag} requested a new ticket channel`,
topic: `${creator}${topic.length > 0 ? ` | ${topic}` : ''}`,
type: 'text'
}); });
t_channel.updateOverwrite(creator_id, { t_channel.updateOverwrite(creator_id, {
VIEW_CHANNEL: true, ATTACH_FILES: true,
READ_MESSAGE_HISTORY: true, READ_MESSAGE_HISTORY: true,
SEND_MESSAGES: true, SEND_MESSAGES: true,
ATTACH_FILES: true VIEW_CHANNEL: true
}, `Ticket channel created by ${creator.user.tag}`); }, `Ticket channel created by ${creator.user.tag}`);
const t_row = await this.client.db.models.Ticket.create({ const t_row = await this.client.db.models.Ticket.create({
id: t_channel.id,
number,
guild: guild_id,
category: category_id, category: category_id,
creator: creator_id, creator: creator_id,
guild: guild_id,
id: t_channel.id,
number,
topic: topic.length === 0 ? null : this.client.cryptr.encrypt(topic) topic: topic.length === 0 ? null : this.client.cryptr.encrypt(topic)
}); });
@ -115,9 +106,7 @@ module.exports = class TicketManager extends EventEmitter {
const sent = await t_channel.send(creator.user.toString(), embed); const sent = await t_channel.send(creator.user.toString(), embed);
await sent.pin({ reason: 'Ticket opening message' }); await sent.pin({ reason: 'Ticket opening message' });
await t_row.update({ await t_row.update({ opening_message: sent.id });
opening_message: sent.id
});
const pinned = t_channel.messages.cache.last(); const pinned = t_channel.messages.cache.last();
@ -147,17 +136,13 @@ module.exports = class TicketManager extends EventEmitter {
.setFooter(footer(settings.footer, i18n('collector_expires_in', 120)), guild.iconURL()) .setFooter(footer(settings.footer, i18n('collector_expires_in', 120)), guild.iconURL())
); );
const collector_filter = (message) => message.author.id === t_row.creator; const collector_filter = message => message.author.id === t_row.creator;
const collector = t_channel.createMessageCollector(collector_filter, { const collector = t_channel.createMessageCollector(collector_filter, { time: 120000 });
time: 120000
});
collector.on('collect', async (message) => { collector.on('collect', async message => {
topic = message.content; topic = message.content;
await t_row.update({ await t_row.update({ topic: this.client.cryptr.encrypt(topic) });
topic: this.client.cryptr.encrypt(topic)
});
await t_channel.setTopic(`${creator} | ${topic}`, { reason: 'User updated ticket topic' }); await t_channel.setTopic(`${creator} | ${topic}`, { reason: 'User updated ticket topic' });
await sent.edit( await sent.edit(
new MessageEmbed() new MessageEmbed()
@ -225,9 +210,9 @@ module.exports = class TicketManager extends EventEmitter {
const close = async () => { const close = async () => {
const pinned = await channel.messages.fetchPinned(); const pinned = await channel.messages.fetchPinned();
await t_row.update({ await t_row.update({
open: false,
closed_by: closer_id || null, closed_by: closer_id || null,
closed_reason: reason ? this.client.cryptr.encrypt(reason) : null, closed_reason: reason ? this.client.cryptr.encrypt(reason) : null,
open: false,
pinned_messages: [...pinned.keys()] pinned_messages: [...pinned.keys()]
}); });
@ -276,11 +261,7 @@ module.exports = class TicketManager extends EventEmitter {
if (channel) { if (channel) {
const creator = await guild.members.fetch(t_row.creator); const creator = await guild.members.fetch(t_row.creator);
const cat_row = await this.client.db.models.Category.findOne({ const cat_row = await this.client.db.models.Category.findOne({ where: { id: t_row.category } });
where: {
id: t_row.category
}
});
if (creator && cat_row.survey) { if (creator && cat_row.survey) {
const survey = await this.client.db.models.Survey.findOne({ const survey = await this.client.db.models.Survey.findOne({
@ -302,13 +283,9 @@ module.exports = class TicketManager extends EventEmitter {
await r_collector_message.react('✅'); await r_collector_message.react('✅');
const collector_filter = (reaction, user) => { const collector_filter = (reaction, user) => user.id === creator.user.id && reaction.emoji.name === '✅';
return user.id === creator.user.id && reaction.emoji.name === '✅';
};
const r_collector = r_collector_message.createReactionCollector(collector_filter, { const r_collector = r_collector_message.createReactionCollector(collector_filter, { time: 60000 });
time: 60000
});
r_collector.on('collect', async () => { r_collector.on('collect', async () => {
r_collector.stop(); r_collector.stop();
@ -325,7 +302,11 @@ module.exports = class TicketManager extends EventEmitter {
); );
try { try {
const collected = await channel.awaitMessages(filter, { max: 1, time: 60000, errors: ['time'] }); const collected = await channel.awaitMessages(filter, {
errors: ['time'],
max: 1,
time: 60000
});
answers.push(collected.first().content); answers.push(collected.first().content);
} catch (collected) { } catch (collected) {
return await close(); return await close();
@ -351,7 +332,7 @@ module.exports = class TicketManager extends EventEmitter {
}); });
r_collector.on('end', async (collected) => { r_collector.on('end', async collected => {
if (collected.size === 0) { if (collected.size === 0) {
await close(); await close();
} }
@ -375,16 +356,12 @@ module.exports = class TicketManager extends EventEmitter {
let t_row; let t_row;
if (this.client.channels.resolve(ticket_id)) { if (this.client.channels.resolve(ticket_id)) {
t_row = await this.client.db.models.Ticket.findOne({ t_row = await this.client.db.models.Ticket.findOne({ where: { id: ticket_id } });
where: {
id: ticket_id
}
});
} else { } else {
t_row = await this.client.db.models.Ticket.findOne({ t_row = await this.client.db.models.Ticket.findOne({
where: { where: {
number: ticket_id, guild: guild_id,
guild: guild_id number: ticket_id
} }
}); });
} }

View File

@ -1,25 +1,21 @@
const { Structures } = require('discord.js'); const { Structures } = require('discord.js');
Structures.extend('Guild', Guild => { Structures.extend('Guild', Guild => class extends Guild {
return class extends Guild { constructor(client, data) {
constructor(client, data) { super(client, data);
super(client, data); }
}
async deleteSettings() { async deleteSettings() {
const row = await this.settings; const row = await this.settings;
return await row.destroy(); return await row.destroy();
} }
async getSettings() { async getSettings() {
const data = { const data = { id: this.id };
id: this.id const [settings] = await this.client.db.models.Guild.findOrCreate({
}; defaults: data,
const [settings] = await this.client.db.models.Guild.findOrCreate({ where: data
defaults: data, });
where: data return settings;
}); }
return settings;
}
};
}); });

View File

@ -1,20 +1,14 @@
const { Structures } = require('discord.js'); const { Structures } = require('discord.js');
Structures.extend('GuildMember', GuildMember => { Structures.extend('GuildMember', GuildMember => class extends GuildMember {
return class extends GuildMember { constructor(client, data, guild) {
constructor(client, data, guild) { super(client, data, guild);
super(client, data, guild); }
}
async isStaff() { async isStaff() {
const guild_categories = await this.client.db.models.Category.findAll({ const guild_categories = await this.client.db.models.Category.findAll({ where: { guild: this.guild.id } });
where: {
guild: this.guild.id
}
});
return guild_categories.some(cat => cat.roles.some(r => this.roles.cache.has(r))); return guild_categories.some(cat => cat.roles.some(r => this.roles.cache.has(r)));
} }
};
}); });

View File

@ -1,3 +1,4 @@
/* eslint-disable no-console */
const fetch = require('node-fetch'); const fetch = require('node-fetch');
const boxen = require('boxen'); const boxen = require('boxen');
const link = require('terminal-link'); const link = require('terminal-link');
@ -26,11 +27,11 @@ module.exports = async client => {
console.log( console.log(
boxen(format(lines.join('\n')), { boxen(format(lines.join('\n')), {
padding: 1,
margin: 1,
align: 'center', align: 'center',
borderColor: 'yellow', borderColor: 'yellow',
borderStyle: 'round' borderStyle: 'round',
margin: 1,
padding: 1
}) })
); );
} }

View File

@ -22,14 +22,15 @@ module.exports = {
if (length === 0) return {}; if (length === 0) return {};
let num; let num;
if (length === 1) if (length === 1) {
num = 0; num = 0;
else if (config.presence.randomise) } else if (config.presence.randomise) {
num = Math.floor(Math.random() * length); num = Math.floor(Math.random() * length);
else { } else {
current_presence = current_presence + 1; // ++ doesn't work on negative numbers current_presence = current_presence + 1; // ++ doesn't work on negative numbers
if (current_presence === length) if (current_presence === length) {
current_presence = 0; current_presence = 0;
}
num = current_presence; num = current_presence;
} }
@ -48,5 +49,5 @@ module.exports = {
}, },
status status
}; };
}, }
}; };

View File

@ -25,7 +25,7 @@ module.exports = {
W: '🇼', W: '🇼',
X: '🇽', X: '🇽',
Y: '🇾', Y: '🇾',
Z: '🇿', Z: '🇿'
}, },
numbers: { numbers: {
0: '0⃣', 0: '0⃣',
@ -38,6 +38,6 @@ module.exports = {
7: '7⃣', 7: '7⃣',
8: '8⃣', 8: '8⃣',
9: '9⃣', 9: '9⃣',
10: '🔟', 10: '🔟'
} }
}; };

View File

@ -6,5 +6,5 @@ module.exports = {
* @param {string} path - A path relative to the root of the project (like "./user/config.js") * @param {string} path - A path relative to the root of the project (like "./user/config.js")
* @returns {string} absolute path * @returns {string} absolute path
*/ */
path: path => join(__dirname, '../../', path), path: path => join(__dirname, '../../', path)
}; };

View File

@ -1,10 +1,10 @@
module.exports = { module.exports = {
int2hex: (int) => int.toString(16).toUpperCase(), int2hex: int => int.toString(16).toUpperCase(),
some: async (array, func) => { some: async (array, func) => {
for (const element of array) { for (const element of array) {
if (await func(element)) return true; if (await func(element)) return true;
} }
return false; return false;
}, },
wait: (time) => new Promise(res => setTimeout(res, time)), wait: time => new Promise(res => setTimeout(res, time))
}; };