mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2024-12-23 00:03:09 +02:00
Survey responses!
and some small changes/fixes
This commit is contained in:
parent
053fcdb4b8
commit
20ac8bff73
@ -38,6 +38,7 @@
|
||||
"keyv": "^4.0.3",
|
||||
"leeks.js": "^0.2.2",
|
||||
"leekslazylogger-fastify": "^0.1.1",
|
||||
"mustache": "^4.2.0",
|
||||
"node-emoji": "^1.10.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"semver": "^7.3.4",
|
||||
|
@ -13,6 +13,7 @@ specifiers:
|
||||
leeks.js: ^0.2.2
|
||||
leekslazylogger-fastify: ^0.1.1
|
||||
mariadb: ^2.5.2
|
||||
mustache: ^4.2.0
|
||||
mysql2: ^2.2.5
|
||||
node-emoji: ^1.10.0
|
||||
node-fetch: ^2.6.1
|
||||
@ -38,6 +39,7 @@ dependencies:
|
||||
keyv: 4.0.3
|
||||
leeks.js: 0.2.2
|
||||
leekslazylogger-fastify: 0.1.1
|
||||
mustache: 4.2.0
|
||||
node-emoji: 1.10.0
|
||||
node-fetch: 2.6.1
|
||||
semver: 7.3.4
|
||||
@ -1625,6 +1627,11 @@ packages:
|
||||
/ms/2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
/mustache/4.2.0:
|
||||
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/mysql2/2.2.5:
|
||||
resolution: {integrity: sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g==}
|
||||
engines: {node: '>= 8.0'}
|
||||
|
@ -82,7 +82,7 @@ module.exports = class BlacklistCommand extends Command {
|
||||
const member_or_role = is_role ? 'role' : 'member';
|
||||
const index = settings.blacklist.findIndex(element => element === id);
|
||||
|
||||
const new_blacklist = [ ...settings.blacklist ];
|
||||
const new_blacklist = [...settings.blacklist];
|
||||
|
||||
if (index === -1) {
|
||||
new_blacklist.push(id);
|
||||
|
@ -66,7 +66,6 @@
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"name_format",
|
||||
"opening_message",
|
||||
"roles"
|
||||
]
|
67
src/commands/extra/survey.template.html
Normal file
67
src/commands/extra/survey.template.html
Normal file
@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>{{survey}} Survey Responses | Discord Tickets</title>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1, user-scalable=no'>
|
||||
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
|
||||
|
||||
<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css'>
|
||||
<link rel='stylesheet' href='https://jenil.github.io/bulmaswatch/darkly/bulmaswatch.min.css'>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<section class='section'>
|
||||
<container class='container box has-text-centered'>
|
||||
<div class='content'>
|
||||
<h1>{{survey}} survey responses</h1>
|
||||
</div>
|
||||
|
||||
<div class='level'>
|
||||
<div class='level-item has-text-centered'>
|
||||
<div class='box'>
|
||||
<p class='title'>{{count.responses}}</p>
|
||||
<p class='heading'>Responses</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class='level-item has-text-centered'>
|
||||
<div class='box'>
|
||||
<p class='title'>{{count.users}}</p>
|
||||
<p class='heading'>Users</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='table-container'>
|
||||
<table class='table is-bordered is-striped is-hoverable is-fullwidth'>
|
||||
<thead>
|
||||
<tr>
|
||||
{{#columns}}
|
||||
<th>{{.}}</th>
|
||||
{{/columns}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#responses}}
|
||||
<tr>
|
||||
{{#.}}
|
||||
<td>{{.}}</td>
|
||||
{{/.}}
|
||||
</tr>
|
||||
{{/responses}}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
{{#columns}}
|
||||
<th>{{.}}</th>
|
||||
{{/columns}}
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</container>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -19,8 +19,7 @@ module.exports = class SettingsCommand extends Command {
|
||||
permissions: ['MANAGE_GUILD']
|
||||
});
|
||||
|
||||
this.schema = require('../settings.schema.json');
|
||||
|
||||
this.schema = require('./extra/settings.schema.json');
|
||||
this.v = new Validator();
|
||||
}
|
||||
|
||||
@ -33,7 +32,7 @@ module.exports = class SettingsCommand extends Command {
|
||||
const settings = await message.guild.settings;
|
||||
const i18n = this.client.i18n.getLocale(settings.locale);
|
||||
|
||||
const attachments = [ ...message.attachments.values() ];
|
||||
const attachments = [...message.attachments.values()];
|
||||
|
||||
if (attachments.length >= 1) {
|
||||
|
||||
@ -207,7 +206,7 @@ module.exports = class SettingsCommand extends Command {
|
||||
`Settings for ${message.guild.name}.json`
|
||||
);
|
||||
|
||||
message.channel.send({
|
||||
return await message.channel.send({
|
||||
files: [attachment]
|
||||
});
|
||||
}
|
||||
|
112
src/commands/survey.js
Normal file
112
src/commands/survey.js
Normal file
@ -0,0 +1,112 @@
|
||||
const Command = require('../modules/commands/command');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { MessageAttachment, MessageEmbed, Message } = require('discord.js');
|
||||
const fsp = require('fs').promises;
|
||||
const { path } = require('../utils/fs');
|
||||
const mustache = require('mustache');
|
||||
|
||||
module.exports = class SurveyCommand extends Command {
|
||||
constructor(client) {
|
||||
const i18n = client.i18n.getLocale(client.config.locale);
|
||||
super(client, {
|
||||
internal: true,
|
||||
name: i18n('commands.survey.name'),
|
||||
description: i18n('commands.survey.description'),
|
||||
aliases: [
|
||||
i18n('commands.survey.aliases.surveys')
|
||||
],
|
||||
process_args: false,
|
||||
args: [
|
||||
{
|
||||
name: i18n('commands.survey.args.survey.name'),
|
||||
description: i18n('commands.survey.args.survey.description'),
|
||||
example: i18n('commands.survey.args.survey.example'),
|
||||
required: false,
|
||||
}
|
||||
],
|
||||
staff_only: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @param {string} args
|
||||
* @returns {Promise<void|any>}
|
||||
*/
|
||||
async execute(message, args) {
|
||||
const settings = await message.guild.settings;
|
||||
const i18n = this.client.i18n.getLocale(settings.locale);
|
||||
|
||||
const survey = await this.client.db.models.Survey.findOne({
|
||||
where: {
|
||||
name: args,
|
||||
guild: message.guild.id
|
||||
}
|
||||
});
|
||||
|
||||
if (survey) {
|
||||
const { rows: responses, count } = await this.client.db.models.SurveyResponse.findAndCountAll({
|
||||
where: {
|
||||
survey: survey.id
|
||||
}
|
||||
});
|
||||
|
||||
const users = new Set();
|
||||
|
||||
|
||||
for (const i in responses) {
|
||||
const ticket = await this.client.db.models.Ticket.findOne({
|
||||
where: {
|
||||
id: responses[i].ticket
|
||||
}
|
||||
});
|
||||
users.add(ticket.creator);
|
||||
const answers = responses[i].answers.map(a => this.client.cryptr.decrypt(a));
|
||||
answers.unshift(ticket.number);
|
||||
responses[i] = answers;
|
||||
}
|
||||
|
||||
let template = await fsp.readFile(path('./src/commands/extra/survey.template.html'), {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
template = template.replace(/\n|\t/, '');
|
||||
|
||||
survey.questions.unshift('Ticket #');
|
||||
|
||||
const html = mustache.render(template, {
|
||||
survey: survey.name.charAt(0).toUpperCase() + survey.name.slice(1),
|
||||
count: {
|
||||
responses: count,
|
||||
users: users.size
|
||||
},
|
||||
columns: survey.questions,
|
||||
responses
|
||||
});
|
||||
|
||||
const attachment = new MessageAttachment(
|
||||
Buffer.from(html),
|
||||
`${survey.name}.html`
|
||||
);
|
||||
|
||||
return await message.channel.send({
|
||||
files: [attachment]
|
||||
});
|
||||
} else {
|
||||
const surveys = await this.client.db.models.Survey.findAll({
|
||||
where: {
|
||||
guild: message.guild.id
|
||||
}
|
||||
});
|
||||
|
||||
const list = surveys.map(s => `❯ **\`${s.name}\`**`);
|
||||
return await message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(settings.colour)
|
||||
.setTitle(i18n('commands.survey.response.list.title'))
|
||||
.setDescription(list.join('\n'))
|
||||
.setFooter(settings.footer, message.guild.iconURL())
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
@ -359,6 +359,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"survey": {
|
||||
"aliases": {
|
||||
"surveys": "surveys"
|
||||
},
|
||||
"args": {
|
||||
"survey": {
|
||||
"description": "The name of the survey to view responses of",
|
||||
"example": "support",
|
||||
"name": "survey"
|
||||
}
|
||||
},
|
||||
"description": "View survey responses",
|
||||
"name": "survey",
|
||||
"response": {
|
||||
"list": {
|
||||
"title": "📃 Surveys"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tag": {
|
||||
"aliases": {
|
||||
"faq": "faq",
|
||||
@ -460,6 +479,16 @@
|
||||
"released": {
|
||||
"description": "%s has released this ticket.",
|
||||
"title": "✅ Ticket released"
|
||||
},
|
||||
"survey": {
|
||||
"complete": {
|
||||
"description": "Thank you for your feedback.",
|
||||
"title": "✅ Thank you"
|
||||
},
|
||||
"start": {
|
||||
"description": "Hey, %s. Before this channel is deleted, would you mind completing a quick %d-question survey? React with ✅ to start, or ignore this message.",
|
||||
"title": "❔ Feedback"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -51,16 +51,16 @@ module.exports = class TicketManager extends EventEmitter {
|
||||
})) + 1;
|
||||
|
||||
const guild = this.client.guilds.cache.get(guild_id);
|
||||
const member = await guild.members.fetch(creator_id);
|
||||
const creator = await guild.members.fetch(creator_id);
|
||||
const name = cat_row.name_format
|
||||
.replace(/{+\s?(user)?name\s?}+/gi, member.displayName)
|
||||
.replace(/{+\s?(user)?name\s?}+/gi, creator.displayName)
|
||||
.replace(/{+\s?num(ber)?\s?}+/gi, number);
|
||||
|
||||
const t_channel = await guild.channels.create(name, {
|
||||
type: 'text',
|
||||
topic: `${member}${topic.length > 0 ? ` | ${topic}` : ''}`,
|
||||
topic: `${creator}${topic.length > 0 ? ` | ${topic}` : ''}`,
|
||||
parent: category_id,
|
||||
reason: `${member.user.tag} requested a new ticket channel`
|
||||
reason: `${creator.user.tag} requested a new ticket channel`
|
||||
});
|
||||
|
||||
t_channel.updateOverwrite(creator_id, {
|
||||
@ -68,7 +68,7 @@ module.exports = class TicketManager extends EventEmitter {
|
||||
READ_MESSAGE_HISTORY: true,
|
||||
SEND_MESSAGES: true,
|
||||
ATTACH_FILES: true
|
||||
}, `Ticket channel created by ${member.user.tag}`);
|
||||
}, `Ticket channel created by ${creator.user.tag}`);
|
||||
|
||||
const t_row = await this.client.db.models.Ticket.create({
|
||||
id: t_channel.id,
|
||||
@ -102,17 +102,17 @@ module.exports = class TicketManager extends EventEmitter {
|
||||
}
|
||||
|
||||
const description = cat_row.opening_message
|
||||
.replace(/{+\s?(user)?name\s?}+/gi, member.displayName)
|
||||
.replace(/{+\s?(tag|ping|mention)?\s?}+/gi, member.user.toString());
|
||||
.replace(/{+\s?(user)?name\s?}+/gi, creator.displayName)
|
||||
.replace(/{+\s?(tag|ping|mention)?\s?}+/gi, creator.user.toString());
|
||||
const embed = new MessageEmbed()
|
||||
.setColor(settings.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setAuthor(creator.user.username, creator.user.displayAvatarURL())
|
||||
.setDescription(description)
|
||||
.setFooter(settings.footer, guild.iconURL());
|
||||
|
||||
if (topic) embed.addField(i18n('ticket.opening_message.fields.topic'), topic);
|
||||
|
||||
const sent = await t_channel.send(member.user.toString(), embed);
|
||||
const sent = await t_channel.send(creator.user.toString(), embed);
|
||||
await sent.pin({ reason: 'Ticket opening message' });
|
||||
|
||||
await t_row.update({
|
||||
@ -158,11 +158,11 @@ module.exports = class TicketManager extends EventEmitter {
|
||||
await t_row.update({
|
||||
topic: this.client.cryptr.encrypt(topic)
|
||||
});
|
||||
await t_channel.setTopic(`${member} | ${topic}`, { reason: 'User updated ticket topic' });
|
||||
await t_channel.setTopic(`${creator} | ${topic}`, { reason: 'User updated ticket topic' });
|
||||
await sent.edit(
|
||||
new MessageEmbed()
|
||||
.setColor(settings.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setAuthor(creator.user.username, creator.user.displayAvatarURL())
|
||||
.setDescription(description)
|
||||
.addField(i18n('ticket.opening_message.fields.topic'), topic)
|
||||
.setFooter(settings.footer, guild.iconURL())
|
||||
@ -196,7 +196,7 @@ module.exports = class TicketManager extends EventEmitter {
|
||||
}
|
||||
})();
|
||||
|
||||
this.client.log.info(`${member.user.tag} created a new ticket in "${guild.name}"`);
|
||||
this.client.log.info(`${creator.user.tag} created a new ticket in "${guild.name}"`);
|
||||
|
||||
this.emit('create', t_row.id, creator_id);
|
||||
|
||||
@ -222,32 +222,38 @@ module.exports = class TicketManager extends EventEmitter {
|
||||
const i18n = this.client.i18n.getLocale(settings.locale);
|
||||
const channel = await this.client.channels.fetch(t_row.id);
|
||||
|
||||
const close = async () => {
|
||||
const pinned = await channel.messages.fetchPinned();
|
||||
await t_row.update({
|
||||
open: false,
|
||||
closed_by: closer_id || null,
|
||||
closed_reason: reason ? this.client.cryptr.encrypt(reason) : null,
|
||||
pinned_messages: [...pinned.keys()]
|
||||
});
|
||||
|
||||
if (closer_id) {
|
||||
const member = await guild.members.fetch(closer_id);
|
||||
const closer = await guild.members.fetch(closer_id);
|
||||
|
||||
await this.archives.updateMember(ticket_id, member);
|
||||
await this.archives.updateMember(ticket_id, closer);
|
||||
|
||||
if (channel) {
|
||||
const description = reason
|
||||
? i18n('ticket.closed_by_member_with_reason.description', member.user.toString(), reason)
|
||||
: i18n('ticket.closed_by_member.description', member.user.toString());
|
||||
? i18n('ticket.closed_by_member_with_reason.description', closer.user.toString(), reason)
|
||||
: i18n('ticket.closed_by_member.description', closer.user.toString());
|
||||
await channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(settings.success_colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setAuthor(closer.user.username, closer.user.displayAvatarURL())
|
||||
.setTitle(i18n('ticket.closed.title'))
|
||||
.setDescription(description)
|
||||
.setFooter(settings.footer, guild.iconURL())
|
||||
);
|
||||
|
||||
setTimeout(async () => {
|
||||
await channel.delete(`Ticket channel closed by ${member.user.tag}${reason ? `: "${reason}"` : ''}`);
|
||||
await channel.delete(`Ticket channel closed by ${closer.user.tag}${reason ? `: "${reason}"` : ''}`);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
this.client.log.info(`${member.user.tag} closed a ticket (${ticket_id})${reason ? `: "${reason}"` : ''}`);
|
||||
this.client.log.info(`${closer.user.tag} closed a ticket (${ticket_id})${reason ? `: "${reason}"` : ''}`);
|
||||
} else {
|
||||
if (channel) {
|
||||
const description = reason
|
||||
? i18n('ticket.closed_with_reason.description')
|
||||
: i18n('ticket.closed.description');
|
||||
@ -262,20 +268,100 @@ module.exports = class TicketManager extends EventEmitter {
|
||||
setTimeout(async () => {
|
||||
await channel.delete(`Ticket channel closed${reason ? `: "${reason}"` : ''}`);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
this.client.log.info(`A ticket was closed (${ticket_id})${reason ? `: "${reason}"` : ''}`);
|
||||
}
|
||||
};
|
||||
|
||||
const pinned = await channel.messages.fetchPinned();
|
||||
if (channel) {
|
||||
const creator = await guild.members.fetch(t_row.creator);
|
||||
|
||||
await t_row.update({
|
||||
open: false,
|
||||
closed_by: closer_id || null,
|
||||
closed_reason: reason ? this.client.cryptr.encrypt(reason) : null,
|
||||
pinned_messages: [...pinned.keys()]
|
||||
const cat_row = await this.client.db.models.Category.findOne({
|
||||
where: {
|
||||
id: t_row.category
|
||||
}
|
||||
});
|
||||
|
||||
if (creator && cat_row.survey) {
|
||||
const survey = await this.client.db.models.Survey.findOne({
|
||||
where: {
|
||||
guild: t_row.guild,
|
||||
name: cat_row.survey
|
||||
}
|
||||
});
|
||||
|
||||
if (survey) {
|
||||
const r_collector_message = await channel.send(
|
||||
creator.toString(),
|
||||
new MessageEmbed()
|
||||
.setColor(settings.colour)
|
||||
.setTitle(i18n('ticket.survey.start.title'))
|
||||
.setDescription(i18n('ticket.survey.start.description', creator.toString(), survey.questions.length))
|
||||
.setFooter(i18n('collector_expires_in', 60))
|
||||
);
|
||||
|
||||
await r_collector_message.react('✅');
|
||||
|
||||
const collector_filter = (reaction, user) => {
|
||||
return user.id === creator.user.id && reaction.emoji.name === '✅';
|
||||
};
|
||||
|
||||
const r_collector = r_collector_message.createReactionCollector(collector_filter, {
|
||||
time: 60000
|
||||
});
|
||||
|
||||
r_collector.on('collect', async () => {
|
||||
r_collector.stop();
|
||||
const filter = message => message.author.id === creator.id;
|
||||
let answers = [];
|
||||
let number = 1;
|
||||
for (const question of survey.questions) {
|
||||
await channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(settings.colour)
|
||||
.setTitle(`${number++}/${survey.questions.length}`)
|
||||
.setDescription(question)
|
||||
.setFooter(i18n('collector_expires_in', 60))
|
||||
);
|
||||
|
||||
try {
|
||||
const collected = await channel.awaitMessages(filter, { max: 1, time: 60000, errors: ['time'] });
|
||||
answers.push(collected.first().content);
|
||||
} catch (collected) {
|
||||
return await close();
|
||||
}
|
||||
}
|
||||
|
||||
await channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(settings.success_colour)
|
||||
.setTitle(i18n('ticket.survey.complete.title'))
|
||||
.setDescription(i18n('ticket.survey.complete.description'))
|
||||
.setFooter(settings.footer, guild.iconURL())
|
||||
);
|
||||
|
||||
answers = answers.map(a => this.client.cryptr.encrypt(a));
|
||||
await this.client.db.models.SurveyResponse.create({
|
||||
answers,
|
||||
survey: survey.id,
|
||||
ticket: t_row.id
|
||||
});
|
||||
|
||||
await close();
|
||||
|
||||
});
|
||||
|
||||
r_collector.on('end', async (collected) => {
|
||||
if (collected.size === 0) {
|
||||
await close();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await close();
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('close', ticket_id);
|
||||
return t_row;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user