Make edit button work

This commit is contained in:
Isaac 2022-09-05 12:43:27 +01:00
parent 44ee84d8f7
commit 34a4e071b5
No known key found for this signature in database
GPG Key ID: 0DE40AE37BBA5C33
7 changed files with 304 additions and 22 deletions

View File

@ -10,6 +10,7 @@ const env = {
HTTP_BIND: 8080, HTTP_BIND: 8080,
HTTP_EXTERNAL: 'http://localhost:8080', HTTP_EXTERNAL: 'http://localhost:8080',
PORTAL: '', PORTAL: '',
PUBLIC: false,
SUPER: '319467558166069248', SUPER: '319467558166069248',
}; };

View File

@ -1,4 +1,13 @@
const { Button } = require('@eartharoid/dbf'); const { Button } = require('@eartharoid/dbf');
const {
ActionRowBuilder,
ModalBuilder,
SelectMenuBuilder,
SelectMenuOptionBuilder,
TextInputBuilder,
TextInputStyle,
} = require('discord.js');
const emoji = require('node-emoji');
module.exports = class EditButton extends Button { module.exports = class EditButton extends Button {
constructor(client, options) { constructor(client, options) {
@ -8,5 +17,93 @@ module.exports = class EditButton extends Button {
}); });
} }
async run(id, interaction) { } async run(id, interaction) {
/** @type {import("client")} */
const client = this.client;
const ticket = await client.prisma.ticket.findUnique({
select: {
category: { select: { name: true } },
guild: { select: { locale: true } },
questionAnswers: { include: { question: true } },
topic: true,
},
where: { id: interaction.channel.id },
});
const getMessage = client.i18n.getLocale(ticket.guild.locale);
if (ticket.questionAnswers.length === 0) {
await interaction.showModal(
new ModalBuilder()
.setCustomId(JSON.stringify({
action: 'topic',
edit: true,
}))
.setTitle(ticket.category.name)
.setComponents(
new ActionRowBuilder()
.setComponents(
new TextInputBuilder()
.setCustomId('topic')
.setLabel(getMessage('modals.topic.label'))
.setStyle(TextInputStyle.Paragraph)
.setMaxLength(1000)
.setMinLength(5)
.setPlaceholder(getMessage('modals.topic.placeholder'))
.setRequired(true)
.setValue(ticket.topic || ''),
),
),
);
} else {
await interaction.showModal(
new ModalBuilder()
.setCustomId(JSON.stringify({
action: 'questions',
edit: true,
}))
.setTitle(ticket.category.name)
.setComponents(
ticket.questionAnswers
.filter(a => a.question.type === 'TEXT') // TODO: remove this when modals support select menus
.map(a => {
if (a.question.type === 'TEXT') {
return new ActionRowBuilder()
.setComponents(
new TextInputBuilder()
.setCustomId(String(a.id))
.setLabel(a.question.label)
.setStyle(a.question.style)
.setMaxLength(Math.min(a.question.maxLength, 1000))
.setMinLength(a.question.minLength)
.setPlaceholder(a.question.placeholder)
.setRequired(a.question.required)
.setValue(a.value || a.question.value),
);
} else if (a.question.type === 'MENU') {
return new ActionRowBuilder()
.setComponents(
new SelectMenuBuilder()
.setCustomId(a.question.id)
.setPlaceholder(a.question.placeholder || a.question.label)
.setMaxValues(a.question.maxLength)
.setMinValues(a.question.minLength)
.setOptions(
a.question.options.map((o, i) => {
const builder = new SelectMenuOptionBuilder()
.setValue(String(i))
.setLabel(o.label);
if (o.description) builder.setDescription(o.description);
if (o.emoji) builder.setEmoji(emoji.hasEmoji(o.emoji) ? emoji.get(o.emoji) : { id: o.emoji });
return builder;
}),
),
);
}
}),
),
);
}
}
}; };

View File

@ -169,6 +169,7 @@ log:
claim: claimed claim: claimed
close: closed close: closed
unclaim: released unclaim: released
update: updated
menus: menus:
category: category:
placeholder: Select a ticket category placeholder: Select a ticket category
@ -224,6 +225,9 @@ ticket:
title: ✅ Ticket created title: ✅ Ticket created
answers: answers:
no_value: '*No response*' no_value: '*No response*'
edited:
description: Your changes have been saved.
title: ✅ Ticket updated
opening_message: opening_message:
content: | content: |
{staff} {staff}

View File

@ -129,7 +129,7 @@ async function logAdminEvent(client, {
* @param {string} details.action * @param {string} details.action
*/ */
async function logTicketEvent(client, { async function logTicketEvent(client, {
userId, action, target, userId, action, target, diff,
}) { }) {
const ticket = await client.prisma.ticket.findUnique({ const ticket = await client.prisma.ticket.findUnique({
include: { guild: true }, include: { guild: true },
@ -143,7 +143,8 @@ async function logTicketEvent(client, {
if (!ticket.guild.logChannel) return; if (!ticket.guild.logChannel) return;
const colour = action === 'create' const colour = action === 'create'
? 'Aqua' : action === 'close' ? 'Aqua' : action === 'close'
? 'DarkAqua' : action === 'claim' ? 'DarkAqua' : action === 'update'
? 'Purple' : action === 'claim'
? 'LuminousVividPink' : action === 'unclaim' ? 'LuminousVividPink' : action === 'unclaim'
? 'DarkVividPink' : 'Default'; ? 'DarkVividPink' : 'Default';
const getMessage = client.i18n.getLocale(ticket.guild.locale); const getMessage = client.i18n.getLocale(ticket.guild.locale);
@ -160,14 +161,8 @@ async function logTicketEvent(client, {
iconURL: member.displayAvatarURL(), iconURL: member.displayAvatarURL(),
name: member.displayName, name: member.displayName,
}) })
.setTitle(getMessage('log.ticket.title', { .setTitle(getMessage('log.ticket.title', i18nOptions))
...i18nOptions, .setDescription(getMessage('log.ticket.description', i18nOptions))
verb: getMessage(`log.ticket.verb.${action}`),
}))
.setDescription(getMessage('log.ticket.description', {
...i18nOptions,
verb: getMessage(`log.ticket.verb.${action}`),
}))
.addFields([ .addFields([
{ {
name: getMessage('log.ticket.ticket'), name: getMessage('log.ticket.ticket'),
@ -176,6 +171,15 @@ async function logTicketEvent(client, {
]), ]),
]; ];
if (diff && diff.original) {
embeds.push(
new EmbedBuilder()
.setColor(colour)
.setTitle(getMessage('log.admin.changes'))
.setFields(makeDiff(diff)),
);
}
return await channel.send({ embeds }); return await channel.send({ embeds });
} }

View File

@ -1,4 +1,7 @@
const { Modal } = require('@eartharoid/dbf'); const { Modal } = require('@eartharoid/dbf');
const { EmbedBuilder } = require('discord.js');
const ExtendedEmbedBuilder = require('../lib/embed');
const { logTicketEvent } = require('../lib/logging');
module.exports = class QuestionsModal extends Modal { module.exports = class QuestionsModal extends Modal {
constructor(client, options) { constructor(client, options) {
@ -14,9 +17,103 @@ module.exports = class QuestionsModal extends Modal {
* @param {import("discord.js").ModalSubmitInteraction} interaction * @param {import("discord.js").ModalSubmitInteraction} interaction
*/ */
async run(id, interaction) { async run(id, interaction) {
/** @type {import("client")} */
const client = this.client;
if (id.edit) {
await interaction.deferReply({ ephemeral: true });
const { category } = await client.prisma.ticket.findUnique({
select: { category: { select: { customTopic: true } } },
where: { id: interaction.channel.id },
});
let topic;
if (category.customTopic) topic = interaction.fields.getTextInputValue(category.customTopic);
const select = {
createdById: true,
guild: {
select: {
footer: true,
locale: true,
successColour: true,
},
},
id: true,
openingMessageId: true,
questionAnswers: { include: { question: true } },
};
const original = await client.prisma.ticket.findUnique({
select,
where: { id: interaction.channel.id },
});
const ticket = await client.prisma.ticket.update({
data: {
questionAnswers: {
update: interaction.fields.fields.map(f => ({
data: { value: f.value },
where: { id: Number(f.customId) },
})),
},
topic,
},
select,
where: { id: interaction.channel.id },
});
const getMessage = client.i18n.getLocale(ticket.guild.locale);
if (topic) await interaction.channel.setTopic(`<@${ticket.createdById}> | ${topic}`);
const opening = await interaction.channel.messages.fetch(ticket.openingMessageId);
if (opening && opening.embeds.length >= 2) {
const embeds = [...opening.embeds];
embeds[1] = new EmbedBuilder(embeds[1].data)
.setFields(
ticket.questionAnswers
.map(a => ({
name: a.question.label,
value: a.value || getMessage('ticket.answers.no_value'),
})),
);
await opening.edit({ embeds });
}
await interaction.editReply({
embeds: [
new ExtendedEmbedBuilder({
iconURL: interaction.guild.iconURL(),
text: ticket.guild.footer,
})
.setColor(ticket.guild.successColour)
.setTitle(getMessage('ticket.edited.title'))
.setDescription(getMessage('ticket.edited.description')),
],
});
/** @param {ticket} ticket */
const makeDiff = ticket => {
const diff = {};
ticket.questionAnswers.forEach(a => {
diff[a.question.label] = a.value || getMessage('ticket.answers.no_value');
});
return diff;
};
logTicketEvent(this.client, {
action: 'update',
diff: {
original: makeDiff(original),
updated: makeDiff(ticket),
},
target: {
id: ticket.id,
name: `<#${ticket.id}>`,
},
userId: interaction.user.id,
});
} else {
await this.client.tickets.postQuestions({ await this.client.tickets.postQuestions({
...id, ...id,
interaction, interaction,
}); });
} }
}
}; };

View File

@ -1,4 +1,7 @@
const { Modal } = require('@eartharoid/dbf'); const { Modal } = require('@eartharoid/dbf');
const { EmbedBuilder } = require('discord.js');
const ExtendedEmbedBuilder = require('../lib/embed');
const { logTicketEvent } = require('../lib/logging');
module.exports = class TopicModal extends Modal { module.exports = class TopicModal extends Modal {
constructor(client, options) { constructor(client, options) {
@ -9,9 +12,84 @@ module.exports = class TopicModal extends Modal {
} }
async run(id, interaction) { async run(id, interaction) {
/** @type {import("client")} */
const client = this.client;
if (id.edit) {
await interaction.deferReply({ ephemeral: true });
const topic = interaction.fields.getTextInputValue('topic');
const select = {
createdById: true,
guild: {
select: {
footer: true,
locale: true,
successColour: true,
},
},
id: true,
openingMessageId: true,
};
const original = await client.prisma.ticket.findUnique({
select,
where: { id: interaction.channel.id },
});
const ticket = await client.prisma.ticket.update({
data: { topic },
select,
where: { id: interaction.channel.id },
});
const getMessage = client.i18n.getLocale(ticket.guild.locale);
if (topic) await interaction.channel.setTopic(`<@${ticket.createdById}> | ${topic}`);
const opening = await interaction.channel.messages.fetch(ticket.openingMessageId);
if (opening && opening.embeds.length >= 2) {
const embeds = [...opening.embeds];
embeds[1] = new EmbedBuilder(embeds[1].data)
.setFields({
name: getMessage('ticket.opening_message.fields.topic'),
value: topic,
});
await opening.edit({ embeds });
}
await interaction.editReply({
embeds: [
new ExtendedEmbedBuilder({
iconURL: interaction.guild.iconURL(),
text: ticket.guild.footer,
})
.setColor(ticket.guild.successColour)
.setTitle(getMessage('ticket.edited.title'))
.setDescription(getMessage('ticket.edited.description')),
],
});
/** @param {ticket} ticket */
const makeDiff = ticket => {
const diff = {};
diff[getMessage('ticket.opening_message.fields.topic')] = ticket.topic;
return diff;
};
logTicketEvent(this.client, {
action: 'update',
diff: {
original: makeDiff(original),
updated: makeDiff(ticket),
},
target: {
id: ticket.id,
name: `<#${ticket.id}>`,
},
userId: interaction.user.id,
});
} else {
await this.client.tickets.postQuestions({ await this.client.tickets.postQuestions({
...id, ...id,
interaction, interaction,
}); });
} }
}
}; };

View File

@ -21,9 +21,10 @@ module.exports.get = () => ({
discriminator: client.user.discriminator, discriminator: client.user.discriminator,
id: client.user.id, id: client.user.id,
portal: process.env.PORTAL || null, portal: process.env.PORTAL || null,
public: !!process.env.PUBLIC,
stats: { stats: {
activatedUsers: users.length, activatedUsers: users.length,
archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they get deleted archivedMessages: users.reduce((total, user) => total + user.messageCount, 0), // don't count archivedMessage table rows, they can be deleted
avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), avgResolutionTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.closedAt - ticket.createdAt), 0) ?? 1 / closedTickets.length),
avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length), avgResponseTime: ms(closedTickets.reduce((total, ticket) => total + (ticket.firstResponseAt - ticket.createdAt), 0) ?? 1 / closedTickets.length),
categories: await client.prisma.category.count(), categories: await client.prisma.category.count(),