diff --git a/src/commands/admin/create-ticket-system.ts b/src/commands/admin/create-ticket-system.ts index 0741c25..db32b8b 100644 --- a/src/commands/admin/create-ticket-system.ts +++ b/src/commands/admin/create-ticket-system.ts @@ -9,8 +9,9 @@ import { GuildTextThreadCreateOptions, MessageCreateOptions, Role, - roleMention, ThreadChannel, - userMention + roleMention, + ThreadChannel, + userMention, } from 'discord.js' import { db, DBTableEnum } from '../../db' @@ -25,6 +26,8 @@ import { } from '../../utils' import { logger } from '../../lib' +type Ticket = { user: string; channel: string } + @Discord() export class CreateTicketSystem { @Slash({ @@ -124,15 +127,15 @@ export class CreateTicketSystem { return } - // prevent creating ticket for users with 3 or more tickets - const allThreads = await interaction.channel.threads.fetch() - const ownedThreads = allThreads.threads.reduce((total, thread) => { - if (thread.ownerId === interaction.user.id && (!thread.archived || !thread.locked)) total += 1 - return total - }, 0) - if (ownedThreads >= 3 && interaction.user.id !== interaction.guild.ownerId) { - logger.action('Denied ticket creation', `User: ${interaction.user.username}(${interaction.user.id})\nActive tickets: ${ownedThreads}`) - await interaction.editReply('❌ You already have too many tickets') + const hasOpenedTicket = await db + .get(DBTableEnum.TICKET_OWNERS) + .then(r => + Array.isArray(r) + ? !!r.find(ticket => ticket.user === interaction.user.id) + : false, + ) + if (hasOpenedTicket) { + await interaction.editReply(`❌ You already have an active ticket`) return } @@ -143,7 +146,7 @@ export class CreateTicketSystem { // create ticket channel const threadChannelSettings: GuildTextThreadCreateOptions = { - name: `${interaction.user.username}-${(new Date).toLocaleDateString().replaceAll('/', '-')}`, + name: `${interaction.user.username}-${new Date().toLocaleDateString().replaceAll('/', '-')}`, type: ChannelType.PrivateThread, invitable: false, } @@ -155,6 +158,22 @@ export class CreateTicketSystem { return } + await db + .push(DBTableEnum.TICKET_OWNERS, { + user: interaction.user.id, + channel: threadChannel.id, + }) + .then(() => { + logger.database( + 'Updated active tickets', + `Added User: ${interaction.user.id}\n Channel: ${threadChannel.id}`, + ) + }) + logger.database( + 'Updated active tickets', + `Removed channel ${interaction.channel.id}`, + ) + // welcoming message in ticket const threadChannelMessage: MessageCreateOptions = { content: `${userMention(interaction.user.id)} ${roleMention(ticketRole)}`, @@ -186,7 +205,10 @@ export class CreateTicketSystem { await threadChannel.members.add(user) await threadChannel.members.add(owner) - logger.action('Created ticket', `User: ${user.user.username}(${user.id})\nChannel: ${threadChannel.name}(${threadChannel.id})`) + logger.action( + 'Created ticket', + `User: ${user.user.username}(${user.id})\nChannel: ${threadChannel.name}(${threadChannel.id})`, + ) // close interaction await interaction.editReply({ @@ -203,7 +225,10 @@ export class CreateTicketSystem { async closeBtn(interaction: ButtonInteraction): Promise { await interaction.deferReply() - logger.action('Ticket close attempt', `User: ${interaction.user.username}(${interaction.user.id})\nChannel: ${interaction.channel?.id}`) + logger.action( + 'Ticket close attempt', + `User: ${interaction.user.username}(${interaction.user.id})\nChannel: ${interaction.channel?.id}`, + ) if (!interaction.channel || !interaction.guild) { await interaction.editReply('❌ Ticket channel does not exist') @@ -216,18 +241,42 @@ export class CreateTicketSystem { await interaction.editReply('Closing ticket..') try { - // remove ticket owner if exists - const ticketOwner = interaction.channel.ownerId ? await interaction.guild.members.fetch(interaction.channel.ownerId) : null - ticketOwner ? await interaction.channel.members.remove(ticketOwner) : null - // lock + archive thread - await interaction.channel.setLocked(true, `Locked by ${userMention(interaction.user.id)}(${interaction.user.id})`) - await interaction.channel.setArchived(true, `Archived by ${userMention(interaction.user.id)}(${interaction.user.id})`) + await interaction.channel.setLocked( + true, + `Locked by ${userMention(interaction.user.id)}(${interaction.user.id})`, + ) + await interaction.channel.setArchived( + true, + `Archived by ${userMention(interaction.user.id)}(${interaction.user.id})`, + ) } catch (e) { logger.error('Closing ticket', e) return } - logger.action('Ticket close attempt successful', `User: ${interaction.user.username}(${interaction.user.id})\nChannel: ${interaction.channel?.id}`) + const tickets = await db.get(DBTableEnum.TICKET_OWNERS) + if (tickets) { + const res = tickets.reduce((res, ticket) => { + if ( + interaction.channel && + ticket.channel === interaction.channel.id + ) { + res.push(ticket) + } + return res + }, []) + await db.set(DBTableEnum.TICKET_OWNERS, res).then(() => { + logger.database( + 'Updated active tickets', + `Removed channel ${interaction.channel?.id}`, + ) + }) + } + + logger.action( + 'Ticket close attempt successful', + `User: ${interaction.user.username}(${interaction.user.id})\nChannel: ${interaction.channel?.id}`, + ) } } diff --git a/src/commands/admin/help.ts b/src/commands/admin/help.ts new file mode 100644 index 0000000..a181bf6 --- /dev/null +++ b/src/commands/admin/help.ts @@ -0,0 +1,44 @@ +import { Discord, Slash } from 'discordx' +import { CommandInteraction, EmbedBuilder } from 'discord.js' + +@Discord() +export class Payments { + @Slash({ + name: 'payments', + description: 'See available payments', + defaultMemberPermissions: 'Administrator', + }) + async payments(interaction: CommandInteraction) { + await interaction.deferReply({ ephemeral: true }) + + const embed = new EmbedBuilder().setTitle(`Commands`).setFields([ + { + name: 'Users', + value: '```/feedback\n' + '/payments```', + }, + { + name: 'Setters', + value: + '```/set-banner-ticket\n' + + '/set-banner-welcome\n' + + '/set-feedback-channel\n' + + '/set-portfolio-channel\n' + + '/set-price-channel\n' + + '/set-order-channel\n' + + '/set-welcome-channel\n' + + '/set-status\n```', + }, + { + name: 'Admin', + value: + '```/ping - потому что могу\n\n' + + '/help - без понятия\n\n' + + '/create-ticket-system - Создать систему тикетов отталкиваясь от текущего канала.\n' + + 'Требует /set-banner-ticket /set-prices-channel /set-status\n\n' + + '/remove-active-ticket - удалить из базы все активные тикеты пользователя если что-то произошло и они не удалились при архивации тикета```', + }, + ]) + + await interaction.editReply({ embeds: [embed] }) + } +} diff --git a/src/commands/admin/remove-active-ticket.ts b/src/commands/admin/remove-active-ticket.ts new file mode 100644 index 0000000..71bf87f --- /dev/null +++ b/src/commands/admin/remove-active-ticket.ts @@ -0,0 +1,59 @@ +import { Discord, Slash, SlashChoice, SlashOption } from 'discordx' +import { + ApplicationCommandOptionType, + CommandInteraction, + User, +} from 'discord.js' + +import { db, DBTableEnum } from '../../db' +import { logger } from '../../lib' +import { feedbackEmbed } from '../../utils' + +type Ticket = { + user: string + channel: string +} + +@Discord() +export class RemoveActiveTicket { + @Slash({ + name: 'remove-active-ticket', + description: 'Remove user from database in case of need', + defaultMemberPermissions: 'Administrator', + }) + async feedback( + @SlashOption({ + name: 'user', + description: 'User to be removed', + required: true, + type: ApplicationCommandOptionType.User, + }) + user: User, + interaction: CommandInteraction, + ) { + await interaction.deferReply({ ephemeral: true }) + + const activeTickets = await db.get(DBTableEnum.TICKET_OWNERS) + if (!activeTickets || !activeTickets.length) { + await interaction.editReply('❌ No active tickets found for server') + return + } + + const res = activeTickets.reduce((r, ticket) => { + if (ticket.user !== user.id) { + r.push(ticket) + } + return r + }, []) + await db + .set(DBTableEnum.TICKET_OWNERS, res) + .then(() => + logger.database( + DBTableEnum.TICKET_OWNERS, + `Removed ${user.id}\nCaller: ${interaction.user.id}`, + ), + ) + + await interaction.editReply('✔️ User tickets cleared successfully') + } +} diff --git a/src/commands/admin/set-banner-ticket.ts b/src/commands/setters/set-banner-ticket.ts similarity index 100% rename from src/commands/admin/set-banner-ticket.ts rename to src/commands/setters/set-banner-ticket.ts diff --git a/src/commands/admin/set-banner-welcome.ts b/src/commands/setters/set-banner-welcome.ts similarity index 100% rename from src/commands/admin/set-banner-welcome.ts rename to src/commands/setters/set-banner-welcome.ts diff --git a/src/commands/admin/set-feedback-channel.ts b/src/commands/setters/set-feedback-channel.ts similarity index 100% rename from src/commands/admin/set-feedback-channel.ts rename to src/commands/setters/set-feedback-channel.ts diff --git a/src/commands/admin/set-order-channel.ts b/src/commands/setters/set-order-channel.ts similarity index 100% rename from src/commands/admin/set-order-channel.ts rename to src/commands/setters/set-order-channel.ts diff --git a/src/commands/admin/set-portfolio-channel.ts b/src/commands/setters/set-portfolio-channel.ts similarity index 100% rename from src/commands/admin/set-portfolio-channel.ts rename to src/commands/setters/set-portfolio-channel.ts diff --git a/src/commands/admin/set-price-channel.ts b/src/commands/setters/set-price-channel.ts similarity index 100% rename from src/commands/admin/set-price-channel.ts rename to src/commands/setters/set-price-channel.ts diff --git a/src/commands/admin/set-status.ts b/src/commands/setters/set-status.ts similarity index 100% rename from src/commands/admin/set-status.ts rename to src/commands/setters/set-status.ts diff --git a/src/commands/admin/set-welcome-channel.ts b/src/commands/setters/set-welcome-channel.ts similarity index 100% rename from src/commands/admin/set-welcome-channel.ts rename to src/commands/setters/set-welcome-channel.ts diff --git a/src/db.ts b/src/db.ts index 71541c3..4dac3db 100644 --- a/src/db.ts +++ b/src/db.ts @@ -18,4 +18,5 @@ export enum DBTableEnum { WORKLOAD_MESSAGE = 'WORKLOAD_MESSAGE', WORKLOAD_CHANNEL = 'WORKLOAD_CHANNEL', TICKET_ROLE = 'TICKET_ROLE', + TICKET_OWNERS = 'TICKET_OWNERS' }