yees
Co-authored-by: PineappleFan <PineaFan@users.noreply.github.com>
Co-authored-by: Skyler <skyler3665@gmail.com>
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 @@
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 @@
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 @@
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<void> => {
+const dmcallback = async (interaction: CommandInteraction, dbUser: PremiumSchema | null, firstDescription: string): Promise<void> => {
if(!dbUser) {
await interaction.editReply({embeds: [
@@ -103,7 +103,7 @@
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 @@
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 @@
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 @@
messages = messages.concat(Array.from(deleted.values() as Iterable<Message>));
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 @@
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 @@
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 @@
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 { 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 @@
eventScheduler: EventScheduler;
performanceTest: PerformanceTest;
scanCache: ScanCache;
+ transcripts: Transcript
};
preloadPage: Record<string, {command: string, argument: string}> = {}; // e.g. { channelID: { command: privacy, page: 3}}
commands: Record<string, [{
@@ -46,7 +47,8 @@
premium: new Premium(),
eventScheduler: new EventScheduler(),
performanceTest: new PerformanceTest(),
- scanCache: new ScanCache()
+ scanCache: new ScanCache(),
+ transcripts: new Transcript()
});
export default client;
diff --git a/src/utils/database.ts b/src/utils/database.ts
index 48d0077..7e80f96 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -1,4 +1,4 @@
-import type { GuildMember } from "discord.js";
+import type { ButtonStyle, GuildMember } from "discord.js";
import type Discord from "discord.js";
import { Collection, MongoClient } from "mongodb";
import config from "../config/main.js";
@@ -101,6 +101,96 @@
}
}
+interface TranscriptEmbed {
+ title?: string;
+ description?: string;
+ fields?: {
+ name: string;
+ value: string;
+ inline: boolean;
+ }[];
+ footer?: {
+ text: string;
+ iconURL?: string;
+ };
+}
+
+interface TranscriptComponent {
+ type: number;
+ style?: ButtonStyle;
+ label?: string;
+ description?: string;
+ placeholder?: string;
+ emojiURL?: string;
+}
+
+interface TranscriptAuthor {
+ username: string;
+ discriminator: number;
+ nickname?: string;
+ id: string;
+ iconURL?: string;
+ topRole: {
+ color: number;
+ badgeURL?: string;
+ }
+}
+
+interface TranscriptAttachment {
+ url: string;
+ filename: string;
+ size: number;
+ log?: string;
+}
+
+interface TranscriptMessage {
+ id: string;
+ author: TranscriptAuthor;
+ content?: string;
+ embeds?: TranscriptEmbed[];
+ components?: TranscriptComponent[][];
+ editedTimestamp?: number;
+ createdTimestamp: number;
+ flags?: string[];
+ attachments?: TranscriptAttachment[];
+ stickerURLs?: string[];
+ referencedMessage?: string | [string, string, string];
+}
+
+interface TranscriptSchema {
+ code: string;
+ for: TranscriptAuthor;
+ type: "ticket" | "purge"
+ guild: string;
+ channel: string;
+ messages: TranscriptMessage[];
+ createdTimestamp: number;
+ createdBy: TranscriptAuthor;
+}
+
+export class Transcript {
+ transcripts: Collection<TranscriptSchema>;
+
+ constructor() {
+ this.transcripts = database.collection<TranscriptSchema>("transcripts");
+ }
+
+ async create(transcript: Omit<TranscriptSchema, "code">) {
+ 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<HistorySchema>;