mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2024-12-23 00:03:09 +02:00
more on closing and feedback (WIP)
This commit is contained in:
parent
8bf01aa520
commit
2a8c1603f2
@ -1,4 +1,5 @@
|
||||
const { Button } = require('@eartharoid/dbf');
|
||||
const ExtendedEmbedBuilder = require('../lib/embed');
|
||||
const { isStaff } = require('../lib/users');
|
||||
|
||||
module.exports = class CloseButton extends Button {
|
||||
@ -17,28 +18,55 @@ module.exports = class CloseButton extends Button {
|
||||
/** @type {import("client")} */
|
||||
const client = this.client;
|
||||
|
||||
// the close button on th opening message, the same as using /close
|
||||
if (id.accepted === undefined) {
|
||||
await client.tickets.beforeRequestClose(interaction);
|
||||
} else {
|
||||
// {
|
||||
// action: 'close',
|
||||
// expect: staff ? 'user' : 'staff',
|
||||
// reason: interaction.options?.getString('reason', false) || null, // ?. because it could be a button interaction
|
||||
// requestedBy: interaction.user.id,
|
||||
// }
|
||||
|
||||
await interaction.deferReply();
|
||||
const ticket = await client.prisma.ticket.findUnique({
|
||||
include: { guild: true },
|
||||
include: {
|
||||
category: true,
|
||||
guild: true,
|
||||
},
|
||||
where: { id: interaction.channel.id },
|
||||
});
|
||||
const getMessage = client.i18n.getLocale(ticket.guild.locale);
|
||||
const staff = await isStaff(interaction.guild, interaction.user.id);
|
||||
|
||||
if (id.expect === 'staff' && !await isStaff(interaction.guild, interaction.user.id)) {
|
||||
return;
|
||||
} else if (interaction.user.id !== ticket.createdById) {
|
||||
return;
|
||||
// if user and expect user (or is creator), feedback modal (if enabled)
|
||||
// otherwise add "Give feedback" button in DM message (if enabled)
|
||||
if (id.expect === 'staff' && !staff) {
|
||||
return; // TODO: please wait for staff to close the ticket
|
||||
} else if (id.expect === 'user' && staff) {
|
||||
return; // TODO: please wait for the user to respond
|
||||
} else {
|
||||
if (id.accepted) {
|
||||
if (
|
||||
ticket.createdById === interaction.user.id &&
|
||||
ticket.category.enableFeedback &&
|
||||
!ticket.feedback
|
||||
) {
|
||||
return await interaction.showModal(client.tickets.buildFeedbackModal(ticket.guild.locale, { next: 'acceptClose' }));
|
||||
} else {
|
||||
await client.tickets.acceptClose(interaction);
|
||||
}
|
||||
} else {
|
||||
if (client.tickets.$stale.has(ticket.id)) {
|
||||
await interaction.channel.messages.edit(
|
||||
client.tickets.$stale.get(ticket.id).message.id,
|
||||
{
|
||||
components: [],
|
||||
embeds: [
|
||||
new ExtendedEmbedBuilder({
|
||||
iconURL: interaction.guild.iconURL(),
|
||||
text: ticket.guild.footer,
|
||||
})
|
||||
.setColor(ticket.guild.errorColour)
|
||||
.setDescription(getMessage('ticket.close.rejected', { user: interaction.user.toString() })),
|
||||
],
|
||||
},
|
||||
);
|
||||
client.tickets.$stale.delete(ticket.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand {
|
||||
await interaction.deferReply();
|
||||
|
||||
const settings = await client.prisma.guild.findUnique({ where: { id: interaction.guild.id } });
|
||||
const getMessage = this.client.i18n.getLocale(settings.locale);
|
||||
const getMessage = client.i18n.getLocale(settings.locale);
|
||||
let ticket;
|
||||
|
||||
if (!(await isStaff(interaction.guild, interaction.user.id))) { // if user is not staff
|
||||
|
@ -352,6 +352,7 @@ ticket:
|
||||
forbidden:
|
||||
description: You don't have permission to close this ticket.
|
||||
title: ❌ Error
|
||||
rejected: ✋ {user} rejected a request to close this ticket.
|
||||
staff_request:
|
||||
archived: |
|
||||
|
||||
@ -368,6 +369,7 @@ ticket:
|
||||
edited:
|
||||
description: Your changes have been saved.
|
||||
title: ✅ Ticket updated
|
||||
feedback: Thank you for your feedback.
|
||||
opening_message:
|
||||
content: |
|
||||
{staff}
|
||||
|
@ -30,7 +30,7 @@ module.exports = async client => {
|
||||
const guild = client.guilds.cache.get(ticket.guildId);
|
||||
if (guild && guild.available && !client.channels.cache.has(ticket.id)) {
|
||||
deleted += 0;
|
||||
await client.tickets.close(ticket.id, true, 'channel deleted');
|
||||
await client.tickets.finallyClose(ticket.id, { reason: 'channel deleted' });
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -807,6 +807,40 @@ module.exports = class TicketManager {
|
||||
});
|
||||
}
|
||||
|
||||
buildFeedbackModal(locale, id) {
|
||||
const getMessage = this.client.i18n.getLocale(locale);
|
||||
return new ModalBuilder()
|
||||
.setCustomId(JSON.stringify({
|
||||
action: 'feedback',
|
||||
...id,
|
||||
}))
|
||||
.setTitle(getMessage('modals.feedback.title'))
|
||||
.setComponents(
|
||||
new ActionRowBuilder()
|
||||
.setComponents(
|
||||
new TextInputBuilder()
|
||||
.setCustomId('rating')
|
||||
.setLabel(getMessage('modals.feedback.rating.label'))
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setMaxLength(3)
|
||||
.setMinLength(1)
|
||||
.setPlaceholder(getMessage('modals.feedback.rating.placeholder'))
|
||||
.setRequired(true),
|
||||
),
|
||||
new ActionRowBuilder()
|
||||
.setComponents(
|
||||
new TextInputBuilder()
|
||||
.setCustomId('comment')
|
||||
.setLabel(getMessage('modals.feedback.comment.label'))
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setMaxLength(1000)
|
||||
.setMinLength(4)
|
||||
.setPlaceholder(getMessage('modals.feedback.comment.placeholder'))
|
||||
.setRequired(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction
|
||||
@ -815,7 +849,7 @@ module.exports = class TicketManager {
|
||||
const ticket = await this.client.prisma.ticket.findUnique({
|
||||
include: {
|
||||
category: { select: { enableFeedback: true } },
|
||||
feedback: { select: { id: true } },
|
||||
feedback: true,
|
||||
guild: true,
|
||||
},
|
||||
where: { id: interaction.channel.id },
|
||||
@ -836,7 +870,10 @@ module.exports = class TicketManager {
|
||||
const getMessage = this.client.i18n.getLocale(locale);
|
||||
return await interaction.editReply({
|
||||
embeds: [
|
||||
new ExtendedEmbedBuilder()
|
||||
new ExtendedEmbedBuilder({
|
||||
iconURL: interaction.guild.iconURL(),
|
||||
text: ticket.guild.footer,
|
||||
})
|
||||
.setColor(errorColour)
|
||||
.setTitle(getMessage('misc.not_ticket.title'))
|
||||
.setDescription(getMessage('misc.not_ticket.description')),
|
||||
@ -846,7 +883,7 @@ module.exports = class TicketManager {
|
||||
|
||||
const getMessage = this.client.i18n.getLocale(ticket.guild.locale);
|
||||
const staff = await isStaff(interaction.guild, interaction.user.id);
|
||||
const reason = interaction.options?.getString('reason', false) || null; // ?. because it could be a button interaction)
|
||||
const reason = interaction.options?.getString('reason', false) || null; // ?. because it could be a button interaction
|
||||
|
||||
if (ticket.createdById !== interaction.user.id && !staff) {
|
||||
return await interaction.editReply({
|
||||
@ -859,42 +896,19 @@ module.exports = class TicketManager {
|
||||
});
|
||||
}
|
||||
|
||||
if (ticket.createdById === interaction.user.id && ticket.category.enableFeedback && !ticket.feedback) {
|
||||
return await interaction.showModal(
|
||||
new ModalBuilder()
|
||||
.setCustomId(JSON.stringify({
|
||||
action: 'feedback',
|
||||
reason,
|
||||
}))
|
||||
.setTitle(getMessage('modals.feedback.title'))
|
||||
.setComponents(
|
||||
new ActionRowBuilder()
|
||||
.setComponents(
|
||||
new TextInputBuilder()
|
||||
.setCustomId('rating')
|
||||
.setLabel(getMessage('modals.feedback.rating.label'))
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setMaxLength(3)
|
||||
.setMinLength(1)
|
||||
.setPlaceholder(getMessage('modals.feedback.rating.placeholder'))
|
||||
.setRequired(false),
|
||||
),
|
||||
new ActionRowBuilder()
|
||||
.setComponents(
|
||||
new TextInputBuilder()
|
||||
.setCustomId('comment')
|
||||
.setLabel(getMessage('modals.feedback.comment.label'))
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setMaxLength(1000)
|
||||
.setMinLength(4)
|
||||
.setPlaceholder(getMessage('modals.feedback.comment.placeholder'))
|
||||
.setRequired(false),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (
|
||||
ticket.createdById === interaction.user.id &&
|
||||
ticket.category.enableFeedback &&
|
||||
!ticket.feedback
|
||||
) {
|
||||
return await interaction.showModal(this.buildFeedbackModal(ticket.guild.locale, {
|
||||
next: 'requestClose',
|
||||
reason, // known issue: a reason longer than a few words will cause an error due to 100 character ID limit
|
||||
}));
|
||||
}
|
||||
|
||||
// not showing feedback, so send the close request
|
||||
|
||||
// defer asap
|
||||
await interaction.deferReply();
|
||||
|
||||
@ -903,7 +917,7 @@ module.exports = class TicketManager {
|
||||
try {
|
||||
await interaction.guild.members.fetch(ticket.createdById);
|
||||
} catch {
|
||||
return this.close(ticket.id, true, reason);
|
||||
return this.finallyClose(ticket.id, { reason });
|
||||
}
|
||||
|
||||
await this.requestClose(interaction, reason);
|
||||
@ -911,6 +925,7 @@ module.exports = class TicketManager {
|
||||
|
||||
/**
|
||||
* @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction|import("discord.js").ModalSubmitInteraction} interaction
|
||||
* @param {string} reason
|
||||
*/
|
||||
async requestClose(interaction, reason) {
|
||||
// interaction could be command, button. or modal
|
||||
@ -924,14 +939,17 @@ module.exports = class TicketManager {
|
||||
action: 'close',
|
||||
expect: staff ? 'user' : 'staff',
|
||||
};
|
||||
const embed = new ExtendedEmbedBuilder()
|
||||
const embed = new ExtendedEmbedBuilder({
|
||||
iconURL: interaction.guild.iconURL(),
|
||||
text: ticket.guild.footer,
|
||||
})
|
||||
.setColor(ticket.guild.primaryColour)
|
||||
.setTitle(getMessage(`ticket.close.${staff ? 'staff' : 'user'}_request.title`, { requestedBy: interaction.member.displayName }));
|
||||
|
||||
if (staff) {
|
||||
embed.setDescription(
|
||||
getMessage('ticket.close.staff_request.description', { requestedBy: interaction.user.toString() }) +
|
||||
(ticket.guild.archive ? getMessage('ticket.close.staff_request.archived') : ''),
|
||||
(ticket.guild.archive ? getMessage('ticket.close.staff_request.archived') : ''),
|
||||
);
|
||||
}
|
||||
|
||||
@ -965,6 +983,7 @@ module.exports = class TicketManager {
|
||||
closeAt: ticket.guild.autoClose ? Date.now() + ticket.guild.autoClose : null,
|
||||
closedBy: interaction.user.id, // null if set as stale due to inactivity
|
||||
message: sent,
|
||||
messages: 0,
|
||||
reason,
|
||||
staleSince: Date.now(),
|
||||
});
|
||||
@ -977,13 +996,19 @@ module.exports = class TicketManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction|import("discord.js").ModalSubmitInteraction} interaction
|
||||
*/
|
||||
async acceptClose(interaction) {}
|
||||
|
||||
/**
|
||||
* close a ticket
|
||||
* @param {string} ticketId
|
||||
* @param {boolean} skip
|
||||
* @param {string} reason
|
||||
*/
|
||||
async close(ticketId, skip, reason) {
|
||||
async finallyClose(ticketId, {
|
||||
closedBy,
|
||||
reason,
|
||||
}) {
|
||||
// TODO: update cache/cat count
|
||||
// TODO: update cache/member count
|
||||
// TODO: set messageCount on ticket
|
||||
|
@ -19,7 +19,7 @@ module.exports = class extends Listener {
|
||||
});
|
||||
if (!ticket) return;
|
||||
|
||||
await client.tickets.close(ticket.id, true, 'channel deleted');
|
||||
await client.tickets.finallyClose(ticket.id, { reason: 'channel deleted' });
|
||||
this.client.log.info(`Closed ticket ${ticket.id} because the channel was deleted`);
|
||||
}
|
||||
};
|
||||
|
@ -26,7 +26,7 @@ module.exports = class extends Listener {
|
||||
});
|
||||
|
||||
for (const ticket of tickets) {
|
||||
await client.tickets.close(ticket.id, true, 'user left server');
|
||||
await client.tickets.finallyClose(ticket.id, { reason: 'user left server' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -218,8 +218,14 @@ module.exports = class extends Listener {
|
||||
|
||||
// if the ticket was set as stale, unset it
|
||||
if (client.tickets.$stale.has(ticket.id)) {
|
||||
await message.channel.messages.delete(client.tickets.$stale.get(ticket.id).message.id);
|
||||
client.tickets.$stale.delete(ticket.id);
|
||||
const $ticket = client.tickets.$stale.get(ticket.id);
|
||||
$ticket.messages++;
|
||||
if ($ticket.messages >= 5) {
|
||||
await message.channel.messages.delete($ticket.message.id);
|
||||
client.tickets.$stale.delete(ticket.id);
|
||||
} else {
|
||||
client.tickets.$stale.set(ticket.id, $ticket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const { Modal } = require('@eartharoid/dbf');
|
||||
|
||||
const ExtendedEmbedBuilder = require('../lib/embed');
|
||||
module.exports = class FeedbackModal extends Modal {
|
||||
constructor(client, options) {
|
||||
super(client, {
|
||||
@ -8,24 +8,52 @@ module.exports = class FeedbackModal extends Modal {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} id
|
||||
* @param {import("discord.js").ModalSubmitInteraction} interaction
|
||||
*/
|
||||
async run(id, interaction) {
|
||||
/** @type {import("client")} */
|
||||
const client = this.client;
|
||||
|
||||
await interaction.deferReply();
|
||||
await client.prisma.ticket.update({
|
||||
|
||||
const comment = interaction.fields.getTextInputValue('comment');
|
||||
const rating = parseInt(interaction.fields.getTextInputValue('rating')) || null;
|
||||
|
||||
const ticket = await client.prisma.ticket.update({
|
||||
data: {
|
||||
feedback: {
|
||||
create: {
|
||||
comment: interaction.fields.getTextInputValue('comment'),
|
||||
comment,
|
||||
guild: { connect: { id: interaction.guild.id } },
|
||||
rating: parseInt(interaction.fields.getTextInputValue('rating')) || null,
|
||||
rating,
|
||||
user: { connect: { id: interaction.user.id } },
|
||||
},
|
||||
},
|
||||
},
|
||||
include: { guild: true },
|
||||
where: { id: interaction.channel.id },
|
||||
});
|
||||
await client.tickets.requestClose(interaction, id.reason);
|
||||
|
||||
if (id.next === 'requestClose') await client.tickets.requestClose(interaction, id.reason);
|
||||
else if (id.next === 'acceptClose') await client.tickets.acceptClose(interaction);
|
||||
|
||||
const getMessage = client.i18n.getLocale(ticket.guild.locale);
|
||||
|
||||
// `followUp` must go after `reply`/`editReply` (the above)
|
||||
if (comment?.length > 0 && rating !== null) {
|
||||
await interaction.followUp({
|
||||
embeds: [
|
||||
new ExtendedEmbedBuilder({
|
||||
iconURL: interaction.guild.iconURL(),
|
||||
text: ticket.guild.footer,
|
||||
})
|
||||
.setColor(ticket.guild.primaryColour)
|
||||
.setDescription(getMessage('ticket.feedback')),
|
||||
],
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user