feat: inactivity warnings and automatic closure (closes #299 and #305)

This commit is contained in:
Isaac 2023-05-27 01:56:29 +01:00
parent 621c49c4e6
commit 3a47a7df3f
No known key found for this signature in database
GPG Key ID: 0DE40AE37BBA5C33
2 changed files with 112 additions and 14 deletions

View File

@ -399,6 +399,11 @@ ticket:
title: ❓ {requestedBy} wants to close this ticket
wait_for_staff: ✋ Please wait for staff to close this ticket.
wait_for_user: ✋ Please wait for the user to respond.
closing_soon:
description: |
This ticket will be closed due to inactivity <t:{timestamp}:R>.
Send a message to cancel this automation.
title: ⌛ This ticket will be closed soon
created:
description: "Your ticket channel has been created: {channel}."
title: ✅ Ticket created
@ -406,6 +411,11 @@ ticket:
description: Your changes have been saved.
title: ✅ Ticket updated
feedback: Thank you for your feedback.
inactive:
description: |
There hasn't been any activity in this channel since <t:{timestamp}:R>.
Please continue the conversation or {close} the ticket.
title: ⏰ This ticket is inactive
offline:
description:
There aren't any staff members available at the moment, so it may

View File

@ -9,6 +9,13 @@ const { version } = require('../../../package.json');
const { msToMins } = require('../../lib/misc');
const sync = require('../../lib/sync');
const checkForUpdates = require('../../lib/updates');
const { isStaff } = require('../../lib/users');
const {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
} = require('discord.js');
const ExtendedEmbedBuilder = require('../../lib/embed');
module.exports = class extends Listener {
constructor(client, options) {
@ -137,20 +144,101 @@ module.exports = class extends Listener {
setInterval(() => checkForUpdates(client), ms('1w'));
}
setInterval(() => {
// TODO: check lastMessageAt and set stale
// this.$stale.set(ticket.id, {
// closeAt: ticket.guild.autoClose ? Date.now() + ticket.guild.autoClose : null,
// closedBy: null, // null if set as stale due to inactivity
// message: sent,
// messages: 0,
// reason: 'inactivity',
// staleSince: Date.now(),
// });
// send inactivity warnings and close stale tickets
const staleInterval = ms('5m');
setInterval(async () => {
// close stale tickets
for (const [ticketId, $] of client.tickets.$stale) {
const autoCloseAfter = $.closeAt - $.staleSince;
const halfway = $.closeAt - (autoCloseAfter / 2);
if (Date.now() >= halfway && Date.now() < halfway + staleInterval) {
const channel = client.channels.cache.get(ticketId);
if (!channel) continue;
const { guild } = await client.prisma.ticket.findUnique({
select: { guild: true },
where: { id: ticketId },
});
const getMessage = client.i18n.getLocale(guild.locale);
await channel.send({
embeds: [
new ExtendedEmbedBuilder()
.setColor(guild.primaryColour)
.setTitle(getMessage('ticket.closing_soon.title'))
.setDescription(getMessage('ticket.closing_soon.description', { timestamp: Math.floor($.closeAt / 1000) })),
],
});
} else if ($.closeAt < Date.now()) {
client.tickets.finallyClose(ticketId, $.reason);
}
}
// for (const [ticketId, $] of client.tickets.$stale) {
// // ⌛
// }
}, ms('5m'));
const guilds = await client.prisma.guild.findMany({
include: {
tickets: {
include: { category: true },
where: { open: true },
},
},
// where: { staleAfter: { not: null } },
where: { staleAfter: { gte: staleInterval } },
});
// set inactive tickets as stale
for (const guild of guilds) {
for (const ticket of guild.tickets) {
// if (ticket.lastMessageAt && ticket.lastMessageAt < Date.now() - guild.staleAfter)
if (ticket.lastMessageAt && Date.now() - ticket.lastMessageAt > guild.staleAfter) {
/** @type {import("discord.js").TextChannel} */
const channel = client.channels.cache.get(ticket.id);
const messages = (await channel.messages.fetch({ limit: 5 })).filter(m => m.author.id !== client.user.id);
let ping = '';
if (messages.size > 0) {
const lastMessage = messages.first();
const staff = await isStaff(channel.guild, lastMessage.author.id);
if (staff) ping = lastMessage.author.toString();
else ping = ticket.category.pingRoles.map(r => `<@&${r}>`).join(' ');
}
const getMessage = client.i18n.getLocale(guild.locale);
const closeComamnd = client.application.commands.cache.find(c => c.name === 'close');
const sent = await channel.send({
components: [
new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId(JSON.stringify({ action: 'close' }))
.setStyle(ButtonStyle.Danger)
.setEmoji(getMessage('buttons.close.emoji'))
.setLabel(getMessage('buttons.close.text')),
),
],
content: ping,
embeds: [
new ExtendedEmbedBuilder({
iconURL: channel.guild.iconURL(),
text: guild.footer,
})
.setColor(guild.primaryColour)
.setTitle(getMessage('ticket.inactive.title'))
.setDescription(getMessage('ticket.inactive.description', {
close: `</${closeComamnd.name}:${closeComamnd.id}>`,
timestamp: Math.floor(ticket.lastMessageAt.getTime() / 1000),
})),
],
});
client.tickets.$stale.set(ticket.id, {
closeAt: guild.autoClose ? Date.now() + guild.autoClose : null,
closedBy: null,
message: sent,
messages: 0,
reason: 'inactivity',
staleSince: Date.now(),
});
}
}
}
}, staleInterval);
}
};