mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2025-01-21 14:56:27 +02:00
feat: feedback, start of close requests
This commit is contained in:
parent
d7e1b05586
commit
8bf01aa520
@ -101,10 +101,9 @@ model Feedback {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
||||||
guildId String @db.VarChar(19)
|
guildId String @db.VarChar(19)
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
rating Int
|
rating Int
|
||||||
ticket Ticket @relation(fields: [ticketId], references: [id])
|
ticket Ticket @relation(fields: [ticketId], references: [id])
|
||||||
ticketId String @unique @db.VarChar(19)
|
ticketId String @id @db.VarChar(19)
|
||||||
user User? @relation(fields: [userId], references: [id])
|
user User? @relation(fields: [userId], references: [id])
|
||||||
userId String? @db.VarChar(19)
|
userId String? @db.VarChar(19)
|
||||||
|
|
||||||
@ -199,10 +198,9 @@ model Ticket {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id])
|
createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id])
|
||||||
createdById String @db.VarChar(19)
|
createdById String @db.VarChar(19)
|
||||||
feedback Feedback?
|
|
||||||
feedbackId Int?
|
|
||||||
firstResponseAt DateTime?
|
|
||||||
deleted Boolean @default(false)
|
deleted Boolean @default(false)
|
||||||
|
feedback Feedback?
|
||||||
|
firstResponseAt DateTime?
|
||||||
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
||||||
guildId String @db.VarChar(19)
|
guildId String @db.VarChar(19)
|
||||||
id String @id @db.VarChar(19)
|
id String @id @db.VarChar(19)
|
||||||
|
@ -101,10 +101,9 @@ model Feedback {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
||||||
guildId String @db.VarChar(19)
|
guildId String @db.VarChar(19)
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
rating Int
|
rating Int
|
||||||
ticket Ticket @relation(fields: [ticketId], references: [id])
|
ticket Ticket @relation(fields: [ticketId], references: [id])
|
||||||
ticketId String @unique @db.VarChar(19)
|
ticketId String @id @db.VarChar(19)
|
||||||
user User? @relation(fields: [userId], references: [id])
|
user User? @relation(fields: [userId], references: [id])
|
||||||
userId String? @db.VarChar(19)
|
userId String? @db.VarChar(19)
|
||||||
|
|
||||||
@ -199,10 +198,9 @@ model Ticket {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id])
|
createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id])
|
||||||
createdById String @db.VarChar(19)
|
createdById String @db.VarChar(19)
|
||||||
feedback Feedback?
|
|
||||||
feedbackId Int?
|
|
||||||
firstResponseAt DateTime?
|
|
||||||
deleted Boolean @default(false)
|
deleted Boolean @default(false)
|
||||||
|
feedback Feedback?
|
||||||
|
firstResponseAt DateTime?
|
||||||
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
||||||
guildId String @db.VarChar(19)
|
guildId String @db.VarChar(19)
|
||||||
id String @id @db.VarChar(19)
|
id String @id @db.VarChar(19)
|
||||||
|
@ -101,10 +101,9 @@ model Feedback {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
||||||
guildId String
|
guildId String
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
rating Int
|
rating Int
|
||||||
ticket Ticket @relation(fields: [ticketId], references: [id])
|
ticket Ticket @relation(fields: [ticketId], references: [id])
|
||||||
ticketId String @unique
|
ticketId String @id
|
||||||
user User? @relation(fields: [userId], references: [id])
|
user User? @relation(fields: [userId], references: [id])
|
||||||
userId String?
|
userId String?
|
||||||
|
|
||||||
@ -199,10 +198,9 @@ model Ticket {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id])
|
createdBy User @relation(name: "TicketsCreatedByUser", fields: [createdById], references: [id])
|
||||||
createdById String
|
createdById String
|
||||||
feedback Feedback?
|
|
||||||
feedbackId Int?
|
|
||||||
firstResponseAt DateTime?
|
|
||||||
deleted Boolean @default(false)
|
deleted Boolean @default(false)
|
||||||
|
feedback Feedback?
|
||||||
|
firstResponseAt DateTime?
|
||||||
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
guild Guild @relation(fields: [guildId], references: [id], onDelete: Cascade)
|
||||||
guildId String
|
guildId String
|
||||||
id String @id
|
id String @id
|
||||||
|
@ -43,8 +43,8 @@
|
|||||||
"@fastify/http-proxy": "^8.4.0",
|
"@fastify/http-proxy": "^8.4.0",
|
||||||
"@fastify/jwt": "^5.0.1",
|
"@fastify/jwt": "^5.0.1",
|
||||||
"@fastify/oauth2": "^5.1.0",
|
"@fastify/oauth2": "^5.1.0",
|
||||||
"@prisma/client": "^4.8.0",
|
"@prisma/client": "^4.8.1",
|
||||||
"cryptr": "^6.0.3",
|
"cryptr": "^6.1.0",
|
||||||
"discord.js": "^14.7.1",
|
"discord.js": "^14.7.1",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
@ -60,7 +60,7 @@
|
|||||||
"node-emoji": "^1.11.0",
|
"node-emoji": "^1.11.0",
|
||||||
"object-diffy": "^1.0.4",
|
"object-diffy": "^1.0.4",
|
||||||
"pad": "^3.2.0",
|
"pad": "^3.2.0",
|
||||||
"prisma": "^4.8.0",
|
"prisma": "^4.8.1",
|
||||||
"semver": "^7.3.8",
|
"semver": "^7.3.8",
|
||||||
"terminal-link": "^2.1.1",
|
"terminal-link": "^2.1.1",
|
||||||
"yaml": "^1.10.2"
|
"yaml": "^1.10.2"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
const { Button } = require('@eartharoid/dbf');
|
const { Button } = require('@eartharoid/dbf');
|
||||||
|
const { isStaff } = require('../lib/users');
|
||||||
|
|
||||||
module.exports = class CloseButton extends Button {
|
module.exports = class CloseButton extends Button {
|
||||||
constructor(client, options) {
|
constructor(client, options) {
|
||||||
@ -16,6 +17,29 @@ module.exports = class CloseButton extends Button {
|
|||||||
/** @type {import("client")} */
|
/** @type {import("client")} */
|
||||||
const client = this.client;
|
const client = this.client;
|
||||||
|
|
||||||
await interaction.deferReply();
|
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 },
|
||||||
|
where: { id: interaction.channel.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -2,8 +2,8 @@ const { Button } = require('@eartharoid/dbf');
|
|||||||
const {
|
const {
|
||||||
ActionRowBuilder,
|
ActionRowBuilder,
|
||||||
ModalBuilder,
|
ModalBuilder,
|
||||||
SelectMenuBuilder,
|
StringSelectMenuBuilder,
|
||||||
SelectMenuOptionBuilder,
|
StringSelectMenuOptionBuilder,
|
||||||
TextInputBuilder,
|
TextInputBuilder,
|
||||||
TextInputStyle,
|
TextInputStyle,
|
||||||
} = require('discord.js');
|
} = require('discord.js');
|
||||||
@ -86,14 +86,14 @@ module.exports = class EditButton extends Button {
|
|||||||
} else if (a.question.type === 'MENU') {
|
} else if (a.question.type === 'MENU') {
|
||||||
return new ActionRowBuilder()
|
return new ActionRowBuilder()
|
||||||
.setComponents(
|
.setComponents(
|
||||||
new SelectMenuBuilder()
|
new StringSelectMenuBuilder()
|
||||||
.setCustomId(a.question.id)
|
.setCustomId(a.question.id)
|
||||||
.setPlaceholder(a.question.placeholder || a.question.label)
|
.setPlaceholder(a.question.placeholder || a.question.label)
|
||||||
.setMaxValues(a.question.maxLength)
|
.setMaxValues(a.question.maxLength)
|
||||||
.setMinValues(a.question.minLength)
|
.setMinValues(a.question.minLength)
|
||||||
.setOptions(
|
.setOptions(
|
||||||
a.question.options.map((o, i) => {
|
a.question.options.map((o, i) => {
|
||||||
const builder = new SelectMenuOptionBuilder()
|
const builder = new StringSelectMenuOptionBuilder()
|
||||||
.setValue(String(i))
|
.setValue(String(i))
|
||||||
.setLabel(o.label);
|
.setLabel(o.label);
|
||||||
if (o.description) builder.setDescription(o.description);
|
if (o.description) builder.setDescription(o.description);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const { FrameworkClient } = require('@eartharoid/dbf');
|
const { FrameworkClient } = require('@eartharoid/dbf');
|
||||||
const {
|
const {
|
||||||
GatewayIntentBits, Partials,
|
GatewayIntentBits,
|
||||||
|
Partials,
|
||||||
} = require('discord.js');
|
} = require('discord.js');
|
||||||
const { PrismaClient } = require('@prisma/client');
|
const { PrismaClient } = require('@prisma/client');
|
||||||
const Keyv = require('keyv');
|
const Keyv = require('keyv');
|
||||||
|
@ -30,6 +30,8 @@ module.exports = class CloseSlashCommand extends SlashCommand {
|
|||||||
* @param {import("discord.js").ChatInputCommandInteraction} interaction
|
* @param {import("discord.js").ChatInputCommandInteraction} interaction
|
||||||
*/
|
*/
|
||||||
async run(interaction) {
|
async run(interaction) {
|
||||||
|
/** @type {import("client")} */
|
||||||
|
const client = this.client;
|
||||||
|
await client.tickets.beforeRequestClose(interaction);
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -228,7 +228,5 @@ module.exports = class ForceCloseSlashCommand extends SlashCommand {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: close (reason)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -85,8 +85,8 @@ module.exports = class MoveSlashCommand extends SlashCommand {
|
|||||||
where: { id: ticket.id },
|
where: { id: ticket.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
const $oldCategory = client.tickets.$.categories[ticket.categoryId];
|
const $oldCategory = client.tickets.$count.categories[ticket.categoryId];
|
||||||
const $newCategory = client.tickets.$.categories[newCategory.id];
|
const $newCategory = client.tickets.$count.categories[newCategory.id];
|
||||||
|
|
||||||
$oldCategory.total--;
|
$oldCategory.total--;
|
||||||
$oldCategory[ticket.createdById]--;
|
$oldCategory[ticket.createdById]--;
|
||||||
|
@ -44,7 +44,7 @@ module.exports = class TransferSlashCommand extends SlashCommand {
|
|||||||
|
|
||||||
let ticket = await client.prisma.ticket.findUnique({ where: { id: interaction.channel.id } });
|
let ticket = await client.prisma.ticket.findUnique({ where: { id: interaction.channel.id } });
|
||||||
const from = ticket.createdById;
|
const from = ticket.createdById;
|
||||||
console.log(1)
|
|
||||||
ticket = await client.prisma.ticket.update({
|
ticket = await client.prisma.ticket.update({
|
||||||
data: {
|
data: {
|
||||||
createdBy: {
|
createdBy: {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
buttons:
|
buttons:
|
||||||
|
accept_close_request:
|
||||||
|
emoji: ✅
|
||||||
|
text: Accept
|
||||||
cancel:
|
cancel:
|
||||||
emoji: 🚫
|
emoji: ✖️
|
||||||
text: Cancel
|
text: Cancel
|
||||||
claim:
|
claim:
|
||||||
emoji: 🙌
|
emoji: 🙌
|
||||||
@ -17,6 +20,9 @@ buttons:
|
|||||||
edit:
|
edit:
|
||||||
emoji: ✏️
|
emoji: ✏️
|
||||||
text: Edit
|
text: Edit
|
||||||
|
reject_close_request:
|
||||||
|
emoji: ✖️
|
||||||
|
text: Reject
|
||||||
unclaim:
|
unclaim:
|
||||||
emoji: ♻️
|
emoji: ♻️
|
||||||
text: Release
|
text: Release
|
||||||
@ -328,7 +334,13 @@ misc:
|
|||||||
title: ❌ That ticket category doesn't exist
|
title: ❌ That ticket category doesn't exist
|
||||||
modals:
|
modals:
|
||||||
feedback:
|
feedback:
|
||||||
title: Feedback
|
comment:
|
||||||
|
label: Comment
|
||||||
|
placeholder: Do you have any additional feedback?
|
||||||
|
rating:
|
||||||
|
label: Rating
|
||||||
|
placeholder: 1-5
|
||||||
|
title: How did we do?
|
||||||
topic:
|
topic:
|
||||||
label: Topic
|
label: Topic
|
||||||
placeholder: What is this ticket about?
|
placeholder: What is this ticket about?
|
||||||
@ -336,6 +348,20 @@ ticket:
|
|||||||
answers:
|
answers:
|
||||||
no_value: "*No response*"
|
no_value: "*No response*"
|
||||||
claimed: 🙌 {user} has claimed this ticket.
|
claimed: 🙌 {user} has claimed this ticket.
|
||||||
|
close:
|
||||||
|
forbidden:
|
||||||
|
description: You don't have permission to close this ticket.
|
||||||
|
title: ❌ Error
|
||||||
|
staff_request:
|
||||||
|
archived: |
|
||||||
|
|
||||||
|
The messages in this channel will be archived for future reference.
|
||||||
|
description: |
|
||||||
|
{requestedBy} wants to close this ticket.
|
||||||
|
Click "Accept" to close it now, or "Reject" if you still need help.
|
||||||
|
title: ❓ Can this ticket be closed?
|
||||||
|
user_request:
|
||||||
|
title: ❓ {requestedBy} wants to close this ticket
|
||||||
created:
|
created:
|
||||||
description: "Your ticket channel has been created: {channel}."
|
description: "Your ticket channel has been created: {channel}."
|
||||||
title: ✅ Ticket created
|
title: ✅ Ticket created
|
||||||
|
@ -22,10 +22,10 @@ module.exports = async client => {
|
|||||||
let cooldowns = 0;
|
let cooldowns = 0;
|
||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
ticketCount += category.tickets.length;
|
ticketCount += category.tickets.length;
|
||||||
client.tickets.$.categories[category.id] = { total: category.tickets.length };
|
client.tickets.$count.categories[category.id] = { total: category.tickets.length };
|
||||||
for (const ticket of category.tickets) {
|
for (const ticket of category.tickets) {
|
||||||
if (client.tickets.$.categories[category.id][ticket.createdById]) client.tickets.$.categories[category.id][ticket.createdById]++;
|
if (client.tickets.$count.categories[category.id][ticket.createdById]) client.tickets.$count.categories[category.id][ticket.createdById]++;
|
||||||
else client.tickets.$.categories[category.id][ticket.createdById] = 1;
|
else client.tickets.$count.categories[category.id][ticket.createdById] = 1;
|
||||||
/** @type {import("discord.js").Guild} */
|
/** @type {import("discord.js").Guild} */
|
||||||
const guild = client.guilds.cache.get(ticket.guildId);
|
const guild = client.guilds.cache.get(ticket.guildId);
|
||||||
if (guild && guild.available && !client.channels.cache.has(ticket.id)) {
|
if (guild && guild.available && !client.channels.cache.has(ticket.id)) {
|
||||||
|
@ -16,6 +16,7 @@ const emoji = require('node-emoji');
|
|||||||
const ms = require('ms');
|
const ms = require('ms');
|
||||||
const ExtendedEmbedBuilder = require('../embed');
|
const ExtendedEmbedBuilder = require('../embed');
|
||||||
const { logTicketEvent } = require('../logging');
|
const { logTicketEvent } = require('../logging');
|
||||||
|
const { isStaff } = require('../users');
|
||||||
const { Collection } = require('discord.js');
|
const { Collection } = require('discord.js');
|
||||||
const Cryptr = require('cryptr');
|
const Cryptr = require('cryptr');
|
||||||
const { encrypt } = new Cryptr(process.env.ENCRYPTION_KEY);
|
const { encrypt } = new Cryptr(process.env.ENCRYPTION_KEY);
|
||||||
@ -30,14 +31,21 @@ module.exports = class TicketManager {
|
|||||||
/** @type {import("client")} */
|
/** @type {import("client")} */
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.archiver = new TicketArchiver(client);
|
this.archiver = new TicketArchiver(client);
|
||||||
this.$ = { categories: {} };
|
this.$count = { categories: {} };
|
||||||
|
this.$stale = new Collection();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCategory(categoryId) {
|
/**
|
||||||
|
* Retrieve cached category data
|
||||||
|
* @param {string} categoryId the category ID
|
||||||
|
* @param {boolean} force bypass & update the cache?
|
||||||
|
* @returns {Promise<CategoryGuildQuestions>}
|
||||||
|
*/
|
||||||
|
async getCategory(categoryId, force) {
|
||||||
const cacheKey = `cache/category+guild+questions:${categoryId}`;
|
const cacheKey = `cache/category+guild+questions:${categoryId}`;
|
||||||
/** @type {CategoryGuildQuestions} */
|
/** @type {CategoryGuildQuestions} */
|
||||||
let category = await this.client.keyv.get(cacheKey);
|
let category = await this.client.keyv.get(cacheKey);
|
||||||
if (!category) {
|
if (!category || force) {
|
||||||
category = await this.client.prisma.category.findUnique({
|
category = await this.client.prisma.category.findUnique({
|
||||||
include: {
|
include: {
|
||||||
guild: true,
|
guild: true,
|
||||||
@ -45,16 +53,16 @@ module.exports = class TicketManager {
|
|||||||
},
|
},
|
||||||
where: { id: categoryId },
|
where: { id: categoryId },
|
||||||
});
|
});
|
||||||
this.client.keyv.set(cacheKey, category, ms('5m'));
|
await this.client.keyv.set(cacheKey, category, ms('12h'));
|
||||||
}
|
}
|
||||||
return category;
|
return category;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: update when a ticket is closed or moved
|
// TODO: update when a ticket is closed or moved
|
||||||
async getTotalCount(categoryId) {
|
async getTotalCount(categoryId) {
|
||||||
const category = this.$.categories[categoryId];
|
const category = this.$count.categories[categoryId];
|
||||||
if (!category) this.$.categories[categoryId] = {};
|
if (!category) this.$count.categories[categoryId] = {};
|
||||||
let count = this.$.categories[categoryId].total;
|
let count = this.$count.categories[categoryId].total;
|
||||||
if (!count) {
|
if (!count) {
|
||||||
count = await this.client.prisma.ticket.count({
|
count = await this.client.prisma.ticket.count({
|
||||||
where: {
|
where: {
|
||||||
@ -62,16 +70,16 @@ module.exports = class TicketManager {
|
|||||||
open: true,
|
open: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.$.categories[categoryId].total = count;
|
this.$count.categories[categoryId].total = count;
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: update when a ticket is closed or moved
|
// TODO: update when a ticket is closed or moved
|
||||||
async getMemberCount(categoryId, memberId) {
|
async getMemberCount(categoryId, memberId) {
|
||||||
const category = this.$.categories[categoryId];
|
const category = this.$count.categories[categoryId];
|
||||||
if (!category) this.$.categories[categoryId] = {};
|
if (!category) this.$count.categories[categoryId] = {};
|
||||||
let count = this.$.categories[categoryId][memberId];
|
let count = this.$count.categories[categoryId][memberId];
|
||||||
if (!count) {
|
if (!count) {
|
||||||
count = await this.client.prisma.ticket.count({
|
count = await this.client.prisma.ticket.count({
|
||||||
where: {
|
where: {
|
||||||
@ -80,7 +88,7 @@ module.exports = class TicketManager {
|
|||||||
open: true,
|
open: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.$.categories[categoryId][memberId] = count;
|
this.$count.categories[categoryId][memberId] = count;
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
@ -308,17 +316,15 @@ module.exports = class TicketManager {
|
|||||||
}) {
|
}) {
|
||||||
await interaction.deferReply({ ephemeral: true });
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
|
||||||
const cacheKey = `cache/category+guild+questions:${categoryId}`;
|
const category = await this.getCategory(categoryId);
|
||||||
/** @type {CategoryGuildQuestions} */
|
|
||||||
const category = await this.client.keyv.get(cacheKey);
|
|
||||||
|
|
||||||
let answers;
|
let answers;
|
||||||
if (interaction.isModalSubmit()) {
|
if (interaction.isModalSubmit()) {
|
||||||
if (action === 'questions') {
|
if (action === 'questions') {
|
||||||
answers = category.questions.map(q => ({
|
answers = category.questions.filter(q => q.type === 'TEXT').map(q => ({
|
||||||
questionId: q.id,
|
questionId: q.id,
|
||||||
userId: interaction.user.id,
|
userId: interaction.user.id,
|
||||||
value: interaction.fields.getTextInputValue(q.id) ? cryptr.encrypt(interaction.fields.getTextInputValue(q.id)) : '',
|
value: interaction.fields.getTextInputValue(q.id) ? encrypt(interaction.fields.getTextInputValue(q.id)) : '',
|
||||||
}));
|
}));
|
||||||
if (category.customTopic) topic = interaction.fields.getTextInputValue(category.customTopic);
|
if (category.customTopic) topic = interaction.fields.getTextInputValue(category.customTopic);
|
||||||
} else if (action === 'topic') {
|
} else if (action === 'topic') {
|
||||||
@ -568,7 +574,7 @@ module.exports = class TicketManager {
|
|||||||
id: channel.id,
|
id: channel.id,
|
||||||
number,
|
number,
|
||||||
openingMessageId: sent.id,
|
openingMessageId: sent.id,
|
||||||
topic: topic ? cryptr.encrypt(topic) : null,
|
topic: topic ? encrypt(topic) : null,
|
||||||
};
|
};
|
||||||
if (referencesTicketId) data.referencesTicket = { connect: { id: referencesTicketId } };
|
if (referencesTicketId) data.referencesTicket = { connect: { id: referencesTicketId } };
|
||||||
if (answers) data.questionAnswers = { createMany: { data: answers } };
|
if (answers) data.questionAnswers = { createMany: { data: answers } };
|
||||||
@ -587,8 +593,8 @@ module.exports = class TicketManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const ticket = await this.client.prisma.ticket.create({ data });
|
const ticket = await this.client.prisma.ticket.create({ data });
|
||||||
this.$.categories[categoryId].total++;
|
this.$count.categories[categoryId].total++;
|
||||||
this.$.categories[categoryId][creator.id]++;
|
this.$count.categories[categoryId][creator.id]++;
|
||||||
|
|
||||||
if (category.cooldown) {
|
if (category.cooldown) {
|
||||||
const cacheKey = `cooldowns/category-member:${category.id}-${ticket.createdById}`;
|
const cacheKey = `cooldowns/category-member:${category.id}-${ticket.createdById}`;
|
||||||
@ -805,15 +811,170 @@ module.exports = class TicketManager {
|
|||||||
/**
|
/**
|
||||||
* @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction
|
* @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction} interaction
|
||||||
*/
|
*/
|
||||||
async requestClose(interaction) {
|
async beforeRequestClose(interaction) {
|
||||||
const ticket = await this.client.prisma.ticket.findUnique({
|
const ticket = await this.client.prisma.ticket.findUnique({
|
||||||
include: {
|
include: {
|
||||||
category: true,
|
category: { select: { enableFeedback: true } },
|
||||||
|
feedback: { select: { id: true } },
|
||||||
guild: true,
|
guild: true,
|
||||||
},
|
},
|
||||||
where: { id: interaction.channel.id },
|
where: { id: interaction.channel.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!ticket) {
|
||||||
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
const {
|
||||||
|
errorColour,
|
||||||
|
locale,
|
||||||
|
} = await this.client.prisma.guild.findUnique({
|
||||||
|
select: {
|
||||||
|
errorColour: true,
|
||||||
|
locale: true,
|
||||||
|
},
|
||||||
|
where: { id: interaction.guild.id },
|
||||||
|
});
|
||||||
|
const getMessage = this.client.i18n.getLocale(locale);
|
||||||
|
return await interaction.editReply({
|
||||||
|
embeds: [
|
||||||
|
new ExtendedEmbedBuilder()
|
||||||
|
.setColor(errorColour)
|
||||||
|
.setTitle(getMessage('misc.not_ticket.title'))
|
||||||
|
.setDescription(getMessage('misc.not_ticket.description')),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const getMessage = this.client.i18n.getLocale(ticket.guild.locale);
|
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)
|
||||||
|
|
||||||
|
if (ticket.createdById !== interaction.user.id && !staff) {
|
||||||
|
return await interaction.editReply({
|
||||||
|
embeds: [
|
||||||
|
new ExtendedEmbedBuilder()
|
||||||
|
.setColor(ticket.guild.errorColour)
|
||||||
|
.setTitle(getMessage('ticket.close.forbidden.title'))
|
||||||
|
.setDescription(getMessage('ticket.close.forbidden.description')),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// defer asap
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
|
// if the creator isn't in the guild , close the ticket immediately
|
||||||
|
// (although leaving should cause the ticket to be closed anyway)
|
||||||
|
try {
|
||||||
|
await interaction.guild.members.fetch(ticket.createdById);
|
||||||
|
} catch {
|
||||||
|
return this.close(ticket.id, true, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.requestClose(interaction, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("discord.js").ChatInputCommandInteraction|import("discord.js").ButtonInteraction|import("discord.js").ModalSubmitInteraction} interaction
|
||||||
|
*/
|
||||||
|
async requestClose(interaction, reason) {
|
||||||
|
// interaction could be command, button. or modal
|
||||||
|
const ticket = await this.client.prisma.ticket.findUnique({
|
||||||
|
include: { guild: true },
|
||||||
|
where: { id: interaction.channel.id },
|
||||||
|
});
|
||||||
|
const getMessage = this.client.i18n.getLocale(ticket.guild.locale);
|
||||||
|
const staff = await isStaff(interaction.guild, interaction.user.id);
|
||||||
|
const closeButtonId = {
|
||||||
|
action: 'close',
|
||||||
|
expect: staff ? 'user' : 'staff',
|
||||||
|
};
|
||||||
|
const embed = new ExtendedEmbedBuilder()
|
||||||
|
.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') : ''),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sent = await interaction.editReply({
|
||||||
|
components: [
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(JSON.stringify({
|
||||||
|
accepted: true,
|
||||||
|
...closeButtonId,
|
||||||
|
}))
|
||||||
|
.setStyle(ButtonStyle.Success)
|
||||||
|
.setEmoji(getMessage('buttons.accept_close_request.emoji'))
|
||||||
|
.setLabel(getMessage('buttons.accept_close_request.text')),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId(JSON.stringify({
|
||||||
|
accepted: false,
|
||||||
|
...closeButtonId,
|
||||||
|
}))
|
||||||
|
.setStyle(ButtonStyle.Danger)
|
||||||
|
.setEmoji(getMessage('buttons.reject_close_request.emoji'))
|
||||||
|
.setLabel(getMessage('buttons.reject_close_request.text')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
content: staff ? `<@${ticket.createdById}>` : '', // ticket.category.pingRoles.map(r => `<@&${r}>`).join(' ')
|
||||||
|
embeds: [embed],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$stale.set(ticket.id, {
|
||||||
|
closeAt: ticket.guild.autoClose ? Date.now() + ticket.guild.autoClose : null,
|
||||||
|
closedBy: interaction.user.id, // null if set as stale due to inactivity
|
||||||
|
message: sent,
|
||||||
|
reason,
|
||||||
|
staleSince: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ticket.priority && ticket.priority !== 'LOW') {
|
||||||
|
await this.client.prisma.ticket.update({
|
||||||
|
data: { priority: 'LOW' },
|
||||||
|
where: { id: ticket.id },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -822,10 +983,11 @@ module.exports = class TicketManager {
|
|||||||
* @param {boolean} skip
|
* @param {boolean} skip
|
||||||
* @param {string} reason
|
* @param {string} reason
|
||||||
*/
|
*/
|
||||||
async final(ticketId, skip, reason) {
|
async close(ticketId, skip, reason) {
|
||||||
// TODO: update cache/cat count
|
// TODO: update cache/cat count
|
||||||
// TODO: update cache/member count
|
// TODO: update cache/member count
|
||||||
// TODO: set messageCount on ticket
|
// TODO: set messageCount on ticket
|
||||||
|
// TODO: pinnedMessages, closedBy, closedAt
|
||||||
// delete
|
// delete
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -108,5 +108,13 @@ module.exports = class extends Listener {
|
|||||||
send();
|
send();
|
||||||
setInterval(() => send(), ms('12h'));
|
setInterval(() => send(), ms('12h'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
// TODO: check lastMessageAt and set stale
|
||||||
|
|
||||||
|
for (const [ticketId, $] of client.tickets.$stale) {
|
||||||
|
// ⌛
|
||||||
|
}
|
||||||
|
}, ms('5m'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -8,5 +8,24 @@ module.exports = class FeedbackModal extends Modal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(id, interaction) { }
|
async run(id, interaction) {
|
||||||
|
/** @type {import("client")} */
|
||||||
|
const client = this.client;
|
||||||
|
|
||||||
|
await interaction.deferReply();
|
||||||
|
await client.prisma.ticket.update({
|
||||||
|
data: {
|
||||||
|
feedback: {
|
||||||
|
create: {
|
||||||
|
comment: interaction.fields.getTextInputValue('comment'),
|
||||||
|
guild: { connect: { id: interaction.guild.id } },
|
||||||
|
rating: parseInt(interaction.fields.getTextInputValue('rating')) || null,
|
||||||
|
user: { connect: { id: interaction.user.id } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
where: { id: interaction.channel.id },
|
||||||
|
});
|
||||||
|
await client.tickets.requestClose(interaction, id.reason);
|
||||||
|
}
|
||||||
};
|
};
|
Loading…
Reference in New Issue
Block a user