diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts index 43e9dd2..a1b4b1a 100644 --- a/src/actions/tickets/delete.ts +++ b/src/actions/tickets/delete.ts @@ -9,7 +9,6 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI if (!interaction.guild) return; const config = await client.database.guilds.read(interaction.guild.id); const { log, NucleusColors, entry, renderUser, renderChannel, renderDelta } = client.logger; - //FIXME const ticketChannel = config.tickets.category; if (!("parent" in interaction.channel!)) { return await interaction.reply({ @@ -84,7 +83,8 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI embeds: [ new EmojiEmbed() .setTitle("Archived Ticket") - .setDescription(`This ticket has been Archived. Type ${getCommandMentionByName("ticket/close")} to delete it.` + + .setDescription(`This ticket has been Archived. Type ${getCommandMentionByName("ticket/close")} to delete it.\n` + + "Creating a transcript will delete all messages in this ticket" + await client.database.premium.hasPremium(interaction.guild.id) ? `\n\nFor more info on transcripts, check ${getCommandMentionByName("privacy")}` : "") diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index ed4f225..b31accb 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -1,4 +1,4 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, GuildMember, StringSelectMenuBuilder } from "discord.js"; +import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CommandInteraction, StringSelectMenuBuilder } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; @@ -10,7 +10,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("premium").setDescription("Information about Nucleus Premium"); //TODO: Allow User to remove Premium -const dmcallback = async (interaction: CommandInteraction, member: GuildMember, dbUser: PremiumSchema | null, firstDescription: string): Promise => { +const dmcallback = async (interaction: CommandInteraction, dbUser: PremiumSchema | null, firstDescription: string): Promise => { if(!dbUser) { await interaction.editReply({embeds: [ @@ -103,7 +103,7 @@ const callback = async (interaction: CommandInteraction): Promise => { premium = `**You can't give servers premium anymore because your subscription ended or was cancelled.** To get premium again please subscribe in the Clicks server` count = 0; } - if(!interaction.guild) return await dmcallback(interaction, member, dbMember, firstDescription); + if(!interaction.guild) return await dmcallback(interaction, dbMember, firstDescription); const hasPremium = await client.database.premium.hasPremium(interaction.guild!.id); let premiumGuild = "" if (hasPremium) { diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index acd641b..20b790a 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -8,7 +8,8 @@ import { TextChannel, ButtonStyle, User, - ComponentType + ComponentType, + ThreadChannel } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; @@ -78,14 +79,21 @@ interface Transcript { type: "ticket" | "purge" guild: string; channel: string; + for: TranscriptAuthor messages: TranscriptMessage[]; createdTimestamp: number; createdBy: TranscriptAuthor; } +const noTopic = new EmojiEmbed() + .setTitle("User not found") + .setDescription("There is no user associated with this ticket.") + .setStatus("Danger") + .setEmoji("CONTROL.BLOCKCROSS") + export default async function (interaction: CommandInteraction | MessageComponentInteraction) { if (interaction.channel === null) return; - if (!(interaction.channel instanceof TextChannel)) return; + if (!(interaction.channel instanceof TextChannel || interaction.channel instanceof ThreadChannel)) return; const { log, NucleusColors, entry, renderUser, renderDelta } = client.logger; let messages: Message[] = []; @@ -98,6 +106,13 @@ export default async function (interaction: CommandInteraction | MessageComponen messages = messages.concat(Array.from(deleted.values() as Iterable)); if (messages.length === 1) messageException(interaction.guild!.id, interaction.channel.id, messages[0]!.id) } while (deletedCount === 100); + messages = messages.filter(message => !( + message.components.some( + component => component.components.some( + child => child.customId?.includes("transcript") ?? false + ) + ) + )); let out = ""; messages.reverse().forEach((message) => { @@ -116,8 +131,30 @@ export default async function (interaction: CommandInteraction | MessageComponen const interactionMember = await interaction.guild?.members.fetch(interaction.user.id) + let topic + let member: GuildMember | null = null; + if (interaction.channel instanceof TextChannel) { + topic = interaction.channel.topic; + if (topic === null) return await interaction.reply({ embeds: [noTopic] }); + member = interaction.guild!.members.cache.get(topic.split(" ")[1]!) ?? null; + } else { + topic = interaction.channel.name; + const split = topic.split("-").map(p => p.trim()) as [string, string, string]; + member = interaction.guild!.members.cache.get(split[1]) ?? null; + if (member === null) return await interaction.reply({ embeds: [noTopic] }); + } + + const newOut: Transcript = { type: "ticket", + for: { + username: member!.user.username, + discriminator: parseInt(member!.user.discriminator), + id: member!.user.id, + topRole: { + color: member!.roles.highest.color + } + }, guild: interaction.guild!.id, channel: interaction.channel!.id, messages: [], @@ -184,14 +221,8 @@ export default async function (interaction: CommandInteraction | MessageComponen newOut.messages.push(msg); }); - console.log(newOut); - - const topic = interaction.channel.topic; - let member: GuildMember | null = null; - if (topic !== null) { - const part = topic.split(" ")[0] ?? null; - if (part !== null) member = interaction.guild!.members.cache.get(part) ?? null; - } + const code = await client.database.transcripts.create(newOut); + if(!code) return await interaction.reply({embeds: [new EmojiEmbed().setTitle("Error").setDescription("An error occurred while creating the transcript.").setStatus("Danger").setEmoji("CONTROL.BLOCKCROSS")]}) let m: Message; if (out !== "") { const url = await pbClient.createPaste({ @@ -200,7 +231,7 @@ export default async function (interaction: CommandInteraction | MessageComponen name: `Ticket Transcript ${ member ? "for " + member.user.username + "#" + member.user.discriminator + " " : "" - }` + `(Created at ${new Date(interaction.channel.createdTimestamp).toDateString()})`, + }` + `(Created at ${new Date(interaction.channel.createdTimestamp!).toDateString()})`, publicity: Publicity.Unlisted }); const guildConfig = await client.database.guilds.read(interaction.guild!.id); diff --git a/src/utils/client.ts b/src/utils/client.ts index 857fb1d..00f8c05 100644 --- a/src/utils/client.ts +++ b/src/utils/client.ts @@ -2,7 +2,7 @@ import Discord, { Client, Interaction, AutocompleteInteraction, Collection } fro import { Logger } from "../utils/log.js"; import Memory from "../utils/memory.js"; import type { VerifySchema } from "../reflex/verify.js"; -import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache } from "../utils/database.js"; +import { Guilds, History, ModNotes, Premium, PerformanceTest, ScanCache, Transcript } from "../utils/database.js"; import EventScheduler from "../utils/eventScheduler.js"; import type { RoleMenuSchema } from "../actions/roleMenu.js"; import config from "../config/main.js"; @@ -23,6 +23,7 @@ class NucleusClient extends Client { eventScheduler: EventScheduler; performanceTest: PerformanceTest; scanCache: ScanCache; + transcripts: Transcript }; preloadPage: Record = {}; // e.g. { channelID: { command: privacy, page: 3}} commands: Record; + + constructor() { + this.transcripts = database.collection("transcripts"); + } + + async create(transcript: Omit) { + let code; + do { + code = Math.random().toString(36).substring(2, 16) + Math.random().toString(36).substring(2, 16); + } while (await this.transcripts.findOne({ code: code })); + + const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code })); + if(doc.acknowledged) return code; + else return null; + } + + async read(code: string) { + return await this.transcripts.findOne({ code: code }); + } +} + export class History { histories: Collection;