From 9c51a7ee53ba3a62c04cfe002f0b8c0681f09ffb Mon Sep 17 00:00:00 2001 From: TheCodedProf Date: Mon, 27 Feb 2023 17:11:13 -0500 Subject: [PATCH] made premium check faster. Added transcript endpoint, toHumanReadable function. --- package.json | 15 +- src/actions/tickets/delete.ts | 15 +- src/api/index.ts | 10 + src/commands/mod/purge.ts | 4 +- src/commands/nucleus/premium.ts | 4 +- src/commands/server/buttons.ts | 6 +- src/commands/settings/logs/attachment.ts | 1 + src/premium/attachmentLogs.ts | 1 + src/premium/createTranscript.ts | 271 ++++------------------ src/utils/client.ts | 2 +- src/utils/commandRegistration/register.ts | 4 +- src/utils/database.ts | 151 +++++++++++- src/utils/eventScheduler.ts | 1 - src/utils/logTranscripts.ts | 64 ----- 14 files changed, 223 insertions(+), 326 deletions(-) delete mode 100644 src/utils/logTranscripts.ts diff --git a/package.json b/package.json index 624e4b3..f28878f 100644 --- a/package.json +++ b/package.json @@ -3,30 +3,22 @@ "@discordjs/rest": "^0.2.0-canary.0", "@hokify/agenda": "^6.2.12", "@tsconfig/node18-strictest-esm": "^1.0.0", - "@types/node-cron": "^3.0.1", "@ungap/structured-clone": "^1.0.1", "agenda": "^4.3.0", - "ansi-styles": "^6.1.0", "body-parser": "^1.20.0", - "chalk": "^5.0.0", "discord.js": "^14.7.1", "eslint": "^8.21.0", "express": "^4.18.1", - "form-data": "^4.0.0", "fuse.js": "^6.6.2", "humanize-duration": "^3.27.1", "immutable": "^4.1.0", "lodash": "^4.17.21", "mongodb": "^4.7.0", - "node-cron": "^3.0.0", "node-fetch": "^3.3.0", "node-tesseract-ocr": "^2.2.1", - "pastebin-api": "^5.1.1", "structured-clone": "^0.2.2", - "systeminformation": "^5.17.3", - "typescript": "^4.9.4", - "uuid": "^8.3.2" - }, + "systeminformation": "^5.17.3" + }, "resolutions": { "discord-api-types": "0.37.23" }, @@ -68,6 +60,7 @@ "eslint-config-prettier": "^8.5.0", "prettier": "^2.7.1", "prettier-eslint": "^15.0.1", - "tsc-suppress": "^1.0.7" + "tsc-suppress": "^1.0.7", + "typescript": "^4.9.4" } } diff --git a/src/actions/tickets/delete.ts b/src/actions/tickets/delete.ts index a1b4b1a..be891ec 100644 --- a/src/actions/tickets/delete.ts +++ b/src/actions/tickets/delete.ts @@ -4,6 +4,7 @@ import client from "../../utils/client.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import { preloadPage } from '../../utils/createTemporaryStorage.js'; +import { LoadingEmbed } from '../../utils/defaults.js'; export default async function (interaction: Discord.CommandInteraction | ButtonInteraction) { if (!interaction.guild) return; @@ -68,8 +69,9 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI await channel.delete(); } else if (status === "Active") { - // Close the ticket - + await interaction.reply({embeds: LoadingEmbed, fetchReply: true}); + // Archive the ticket + await interaction.channel.fetch() if (channel.isThread()) { channel.setName(`${channel.name.replace("Active", "Archived")}`); channel.members.remove(channel.name.split(" - ")[1]!); @@ -79,13 +81,14 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI await channel.permissionOverwrites.delete(channel.topic!.split(" ")[0]!); } preloadPage(interaction.channel.id, "privacy", "2") - await interaction.reply({ + const hasPremium = await client.database.premium.hasPremium(interaction.guild.id); + await interaction.editReply({ embeds: [ new EmojiEmbed() .setTitle("Archived Ticket") .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) ? + hasPremium ? `\n\nFor more info on transcripts, check ${getCommandMentionByName("privacy")}` : "") .setStatus("Warning") @@ -100,7 +103,7 @@ export default async function (interaction: Discord.CommandInteraction | ButtonI .setCustomId("closeticket") .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) ].concat( - await client.database.premium.hasPremium(interaction.guild.id) + hasPremium ? [ new ButtonBuilder() .setLabel("Create Transcript and Delete") @@ -198,4 +201,4 @@ async function purgeByUser(member: string, guild: string) { log(data); } -export { purgeByUser }; \ No newline at end of file +export { purgeByUser }; diff --git a/src/api/index.ts b/src/api/index.ts index bfe9d18..6a90c48 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -149,6 +149,16 @@ const runServer = (client: NucleusClient) => { return res.sendStatus(404); }); + app.get("/transcript/:code", jsonParser, async function (req: express.Request, res: express.Response) { + const code = req.params.code; + if (code === undefined) return res.status(400).send("No code provided"); + const entry = await client.database.transcripts.read(code); + if (entry === null) return res.status(404).send("Could not find a transcript by that code"); + // Convert to a human readable format + const data = client.database.transcripts.toHumanReadable(entry); + return res.status(200).send(data); + }); + app.listen(port); }; diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 8d49c3b..5e0a795 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -1,4 +1,3 @@ -import { JSONTranscriptFromMessageArray, JSONTranscriptToHumanReadable } from '../../utils/logTranscripts.js'; import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel, ButtonStyle, ButtonBuilder } from "discord.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; @@ -161,7 +160,8 @@ const callback = async (interaction: CommandInteraction): Promise => { } }; log(data); - const transcript = JSONTranscriptToHumanReadable(JSONTranscriptFromMessageArray(deleted)!); + const newOut = await client.database.transcripts.createTranscript(deleted, interaction, interaction.member as GuildMember); + const transcript = client.database.transcripts.toHumanReadable(newOut); const attachmentObject = { attachment: Buffer.from(transcript), name: `purge-${channel.id}-${Date.now()}.txt`, diff --git a/src/commands/nucleus/premium.ts b/src/commands/nucleus/premium.ts index b31accb..325c360 100644 --- a/src/commands/nucleus/premium.ts +++ b/src/commands/nucleus/premium.ts @@ -67,7 +67,7 @@ const dmcallback = async (interaction: CommandInteraction, dbUser: PremiumSchema } const callback = async (interaction: CommandInteraction): Promise => { - + if (interaction.guild) client.database.premium.hasPremium(interaction.guild.id).finally(() => {}); await interaction.reply({embeds: LoadingEmbed, ephemeral: true}) const member = await (await interaction.client.guilds.fetch("684492926528651336")).members.fetch(interaction.user.id).catch(() => { interaction.editReply({ embeds: [ @@ -172,7 +172,7 @@ const callback = async (interaction: CommandInteraction): Promise => { components: [] }); } else { - client.database.premium.addPremium(interaction.user.id, guild.id); + await client.database.premium.addPremium(interaction.user.id, guild.id); interaction.editReply({ embeds: [ new EmojiEmbed() diff --git a/src/commands/server/buttons.ts b/src/commands/server/buttons.ts index e4bda0d..3297616 100644 --- a/src/commands/server/buttons.ts +++ b/src/commands/server/buttons.ts @@ -32,7 +32,7 @@ const colors: Record = { const buttonNames: Record = { verifybutton: "Verify", rolemenu: "Role Menu", - createticket: "Ticket" + createticket: "Create Ticket" } export const callback = async (interaction: CommandInteraction): Promise => { @@ -195,8 +195,8 @@ export const callback = async (interaction: CommandInteraction): Promise = continue; } if (!out || out.isButton()) continue - data.title = out.fields.getTextInputValue("title"); - data.description = out.fields.getTextInputValue("description"); + data.title = out.fields.getTextInputValue("title").length === 0 ? null : out.fields.getTextInputValue("title"); + data.description = out.fields.getTextInputValue("description").length === 0 ? null : out.fields.getTextInputValue("description"); break; } case "send": { diff --git a/src/commands/settings/logs/attachment.ts b/src/commands/settings/logs/attachment.ts index c04c7cf..238b8b9 100644 --- a/src/commands/settings/logs/attachment.ts +++ b/src/commands/settings/logs/attachment.ts @@ -12,6 +12,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => .setDescription("Where attachments should be logged to (Premium only)") const callback = async (interaction: CommandInteraction): Promise => { + if (interaction.guild) client.database.premium.hasPremium(interaction.guild.id).finally(() => {}); await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, diff --git a/src/premium/attachmentLogs.ts b/src/premium/attachmentLogs.ts index 3c583f2..b2c8391 100644 --- a/src/premium/attachmentLogs.ts +++ b/src/premium/attachmentLogs.ts @@ -8,6 +8,7 @@ import addPlural from "../utils/plurals.js"; import type { GuildTextBasedChannel, Message } from "discord.js"; export default async function logAttachment(message: Message): Promise { + if (message.guild) client.database.premium.hasPremium(message.guild.id).finally(() => {}); if (!message.guild) throw new Error("Tried to log an attachment in a non-guild message"); const { renderUser, renderChannel, renderDelta } = client.logger; const attachments = []; diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts index 20b790a..85e059f 100644 --- a/src/premium/createTranscript.ts +++ b/src/premium/createTranscript.ts @@ -8,83 +8,13 @@ import { TextChannel, ButtonStyle, User, - ComponentType, ThreadChannel } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; -import { PasteClient, Publicity, ExpireDate } from "pastebin-api"; import client from "../utils/client.js"; import { messageException } from '../utils/createTemporaryStorage.js'; -const pbClient = new PasteClient(client.config.pastebinApiKey); - -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 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.") @@ -114,175 +44,58 @@ export default async function (interaction: CommandInteraction | MessageComponen ) )); - let out = ""; - messages.reverse().forEach((message) => { - if (!message.author.bot) { - const sentDate = new Date(message.createdTimestamp); - out += `${message.author.username}#${message.author.discriminator} (${ - message.author.id - }) [${sentDate.toUTCString()}]\n`; - const lines = message.content.split("\n"); - lines.forEach((line) => { - out += `> ${line}\n`; - }); - out += "\n\n"; - } - }); - - const interactionMember = await interaction.guild?.members.fetch(interaction.user.id) - let topic - let member: GuildMember | null = null; + let member: GuildMember = interaction.guild?.members.me!; 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; + const mem = interaction.guild!.members.cache.get(topic.split(" ")[1]!); + if (mem) member = mem; } 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 mem = interaction.guild!.members.cache.get(split[1]) + if (mem) member = mem; } - - 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: [], - createdTimestamp: Date.now(), - createdBy: { - username: interaction.user.username, - discriminator: parseInt(interaction.user.discriminator), - id: interaction.user.id, - topRole: { - color: interactionMember?.roles.highest.color ?? 0x000000 - } - } - } - if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!; - messages.reverse().forEach((message) => { - const msg: TranscriptMessage = { - id: message.id, - author: { - username: message.author.username, - discriminator: parseInt(message.author.discriminator), - id: message.author.id, - topRole: { - color: message.member!.roles.highest.color - } - }, - createdTimestamp: message.createdTimestamp - }; - if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!; - if (message.content) msg.content = message.content; - if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => { - const obj: TranscriptEmbed = {}; - if (embed.title) obj.title = embed.title; - if (embed.description) obj.description = embed.description; - if (embed.fields.length > 0) obj.fields = embed.fields.map(field => { - return { - name: field.name, - value: field.value, - inline: field.inline ?? false - } - }); - if (embed.footer) obj.footer = { - text: embed.footer.text, - }; - if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL; - return obj; - }); - if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => { - const obj: TranscriptComponent = { - type: child.type - } - if (child.type === ComponentType.Button) { - obj.style = child.style; - obj.label = child.label ?? ""; - } else if (child.type > 2) { - obj.placeholder = child.placeholder ?? ""; - } - return obj - })); - if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp; - msg.flags = message.flags.toArray(); - - if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url); - if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""]; - newOut.messages.push(msg); - }); + const newOut = await client.database.transcripts.createTranscript(messages, interaction, member); 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({ - code: out, - expireDate: ExpireDate.Never, - name: - `Ticket Transcript ${ - member ? "for " + member.user.username + "#" + member.user.discriminator + " " : "" - }` + `(Created at ${new Date(interaction.channel.createdTimestamp!).toDateString()})`, - publicity: Publicity.Unlisted - }); - const guildConfig = await client.database.guilds.read(interaction.guild!.id); - m = (await interaction.reply({ - embeds: [ - new EmojiEmbed() - .setTitle("Transcript") - .setDescription( - "You can view the transcript using the link below. You can save the link for later" + - (guildConfig.logging.logs.channel - ? ` or find it in <#${guildConfig.logging.logs.channel}> once you press delete below. After this the channel will be deleted.` - : ".") - ) - .setStatus("Success") - .setEmoji("CONTROL.DOWNLOAD") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(url), - new ButtonBuilder() - .setLabel("Delete") - .setStyle(ButtonStyle.Danger) - .setCustomId("close") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - ]) - ], - fetchReply: true - })) as Message; - } else { - m = (await interaction.reply({ - embeds: [ - new EmojiEmbed() - .setTitle("Transcript") - .setDescription( - "The transcript was empty, so no changes were made. To delete this ticket, press the delete button below." - ) - .setStatus("Success") - .setEmoji("CONTROL.DOWNLOAD") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Delete") - .setStyle(ButtonStyle.Danger) - .setCustomId("close") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - ]) - ], - fetchReply: true - })) as Message; - } + if(!code) return await interaction.reply({ + embeds: [ + new EmojiEmbed() + .setTitle("Error") + .setDescription("An error occurred while creating the transcript.") + .setStatus("Danger") + .setEmoji("CONTROL.BLOCKCROSS") + ] + }) + const guildConfig = await client.database.guilds.read(interaction.guild!.id); + const m: Message = (await interaction.reply({ + embeds: [ + new EmojiEmbed() + .setTitle("Transcript") + .setDescription( + "You can view the transcript using the link below. You can save the link for later" + + (guildConfig.logging.logs.channel + ? ` or find it in <#${guildConfig.logging.logs.channel}> once you press delete below. After this the channel will be deleted.` + : ".") + ) + .setStatus("Success") + .setEmoji("CONTROL.DOWNLOAD") + ], + components: [ + new ActionRowBuilder().addComponents([ + new ButtonBuilder().setLabel("View").setStyle(ButtonStyle.Link).setURL(`https://clicks.codes/nucleus/transcript?code=${code}`), + new ButtonBuilder() + .setLabel("Delete") + .setStyle(ButtonStyle.Danger) + .setCustomId("close") + .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) + ]) + ], + fetchReply: true + })) as Message; let i; try { i = await m.awaitMessageComponent({ @@ -303,7 +116,7 @@ export default async function (interaction: CommandInteraction | MessageComponen timestamp: Date.now() }, list: { - ticketFor: member ? entry(member.id, renderUser(member.user)) : entry(null, "*Unknown*"), + ticketFor: entry(member.id, renderUser(member.user)), deletedBy: entry(interaction.member!.user.id, renderUser(interaction.member!.user as User)), deleted: entry(Date.now().toString(), renderDelta(Date.now())) }, diff --git a/src/utils/client.ts b/src/utils/client.ts index 00f8c05..7e84716 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, Transcript } 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"; diff --git a/src/utils/commandRegistration/register.ts b/src/utils/commandRegistration/register.ts index 78e3b0f..d88a13a 100644 --- a/src/utils/commandRegistration/register.ts +++ b/src/utils/commandRegistration/register.ts @@ -225,6 +225,6 @@ export default async function register() { console.log(`${colors.green}Registered commands, events and context menus${colors.none}`) console.log( (config.enableDevelopment ? `${colors.purple}Bot started in Development mode` : - `${colors.blue}Bot started in Production mode`) + colors.none) - // console.log(client.commands) + `${colors.blue}Bot started in Production mode`) + colors.none + ) }; diff --git a/src/utils/database.ts b/src/utils/database.ts index 7e80f96..b7971c3 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -1,4 +1,4 @@ -import type { ButtonStyle, GuildMember } from "discord.js"; +import { ButtonStyle, CommandInteraction, ComponentType, GuildMember, Message, MessageComponentInteraction } from "discord.js"; import type Discord from "discord.js"; import { Collection, MongoClient } from "mongodb"; import config from "../config/main.js"; @@ -154,7 +154,7 @@ interface TranscriptMessage { flags?: string[]; attachments?: TranscriptAttachment[]; stickerURLs?: string[]; - referencedMessage?: string | [string, string, string]; + referencedMessage?: string | [string, string, string]; // the message id, the channel id, the guild id } interface TranscriptSchema { @@ -189,6 +189,134 @@ export class Transcript { async read(code: string) { return await this.transcripts.findOne({ code: code }); } + + async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) { + const interactionMember = await interaction.guild?.members.fetch(interaction.user.id) + const newOut: Omit = { + 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: [], + createdTimestamp: Date.now(), + createdBy: { + username: interaction.user.username, + discriminator: parseInt(interaction.user.discriminator), + id: interaction.user.id, + topRole: { + color: interactionMember?.roles.highest.color ?? 0x000000 + } + } + } + if(interactionMember?.roles.icon) newOut.createdBy.topRole.badgeURL = interactionMember.roles.icon.iconURL()!; + messages.reverse().forEach((message) => { + const msg: TranscriptMessage = { + id: message.id, + author: { + username: message.author.username, + discriminator: parseInt(message.author.discriminator), + id: message.author.id, + topRole: { + color: message.member!.roles.highest.color + } + }, + createdTimestamp: message.createdTimestamp + }; + if (message.member!.roles.icon) msg.author.topRole.badgeURL = message.member!.roles.icon.iconURL()!; + if (message.content) msg.content = message.content; + if (message.embeds.length > 0) msg.embeds = message.embeds.map(embed => { + const obj: TranscriptEmbed = {}; + if (embed.title) obj.title = embed.title; + if (embed.description) obj.description = embed.description; + if (embed.fields.length > 0) obj.fields = embed.fields.map(field => { + return { + name: field.name, + value: field.value, + inline: field.inline ?? false + } + }); + if (embed.footer) obj.footer = { + text: embed.footer.text, + }; + if (embed.footer?.iconURL) obj.footer!.iconURL = embed.footer.iconURL; + return obj; + }); + if (message.components.length > 0) msg.components = message.components.map(component => component.components.map(child => { + const obj: TranscriptComponent = { + type: child.type + } + if (child.type === ComponentType.Button) { + obj.style = child.style; + obj.label = child.label ?? ""; + } else if (child.type > 2) { + obj.placeholder = child.placeholder ?? ""; + } + return obj + })); + if (message.editedTimestamp) msg.editedTimestamp = message.editedTimestamp; + msg.flags = message.flags.toArray(); + + if (message.stickers.size > 0) msg.stickerURLs = message.stickers.map(sticker => sticker.url); + if (message.reference) msg.referencedMessage = [message.reference.guildId ?? "", message.reference.channelId, message.reference.messageId ?? ""]; + newOut.messages.push(msg); + }); + return newOut; + } + + toHumanReadable(transcript: Omit): string { + let out = ""; + for (const message of transcript.messages) { + if (message.referencedMessage) { + if (Array.isArray(message.referencedMessage)) { + out += `> [Crosspost From] ${message.referencedMessage[0]} in ${message.referencedMessage[1]} in ${message.referencedMessage[2]}\n`; + } + else out += `> [Reply To] ${message.referencedMessage}\n`; + } + out += `${message.author.nickname ?? message.author.username}#${message.author.discriminator} (${message.author.id}) (${message.id}) `; + out += `[${new Date(message.createdTimestamp).toISOString()}] `; + if (message.editedTimestamp) out += `[Edited: ${new Date(message.editedTimestamp).toISOString()}] `; + out += "\n"; + if (message.content) out += `[Content]\n${message.content}\n\n`; + if (message.embeds) { + for (const embed of message.embeds) { + out += `[Embed]\n`; + if (embed.title) out += `| Title: ${embed.title}\n`; + if (embed.description) out += `| Description: ${embed.description}\n`; + if (embed.fields) { + for (const field of embed.fields) { + out += `| Field: ${field.name} - ${field.value}\n`; + } + } + if (embed.footer) { + out += `|Footer: ${embed.footer.text}\n`; + } + out += "\n"; + } + } + if (message.components) { + for (const component of message.components) { + out += `[Component]\n`; + for (const button of component) { + out += `| Button: ${button.label ?? button.description}\n`; + } + out += "\n"; + } + } + if (message.attachments) { + for (const attachment of message.attachments) { + out += `[Attachment] ${attachment.filename} (${attachment.size} bytes) ${attachment.url}\n`; + } + } + } + return out + } } export class History { @@ -317,9 +445,12 @@ export class ModNotes { export class Premium { premium: Collection; + cache: Map; // Date indicates the time one hour after it was created + cacheTimeout = 1000 * 60 * 60; // 1 hour constructor() { this.premium = database.collection("premium"); + this.cache = new Map(); } async updateUser(user: string, level: number) { @@ -337,8 +468,11 @@ export class Premium { } async hasPremium(guild: string): Promise<[boolean, string, number, boolean] | null> { + // [Has premium, user giving premium, level, is mod: if given automatically] + const cached = this.cache.get(guild); + if (cached && cached[4].getTime() < Date.now()) return [cached[0], cached[1], cached[2], cached[3]]; const entries = await this.premium.find({}).toArray(); - const members = await (await client.guilds.fetch(guild)).members.fetch() + const members = (await client.guilds.fetch(guild)).members.cache for(const {user} of entries) { const member = members.get(user); if(member) { //TODO: Notify user if they've given premium to a server that has since gotten premium via a mod. @@ -355,7 +489,10 @@ export class Premium { member.permissions.has("ManageMessages") || member.permissions.has("ManageThreads") const entry = entries.find(e => e.user === member.id); - if(entry && (entry.level === 3) && modPerms) return [true, member.id, entry.level, true]; + if(entry && (entry.level === 3) && modPerms) { + this.cache.set(guild, [true, member.id, entry.level, true, new Date(Date.now() + this.cacheTimeout)]); + return [true, member.id, entry.level, true]; + } } } const entry = await this.premium.findOne({ @@ -365,6 +502,7 @@ export class Premium { } } }); + this.cache.set(guild, [entry ? true : false, entry?.user ?? "", entry?.level ?? 0, false, new Date(Date.now() + this.cacheTimeout)]); return entry ? [true, entry.user, entry.level, false] : null; } @@ -427,11 +565,14 @@ export class Premium { } } - addPremium(user: string, guild: string) { + async addPremium(user: string, guild: string) { + const { level } = (await this.fetchUser(user))!; + this.cache.set(guild, [true, user, level, false, new Date(Date.now() + this.cacheTimeout)]); return this.premium.updateOne({ user: user }, { $addToSet: { appliesTo: guild } }, { upsert: true }); } removePremium(user: string, guild: string) { + this.cache.set(guild, [false, "", 0, false, new Date(Date.now() + this.cacheTimeout)]); return this.premium.updateOne({ user: user }, { $pull: { appliesTo: guild } }); } } diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts index bdd0d21..5c461ad 100644 --- a/src/utils/eventScheduler.ts +++ b/src/utils/eventScheduler.ts @@ -14,7 +14,6 @@ class EventScheduler { collection: "eventScheduler" } }); - this.agenda.define("unmuteRole", async (job) => { const guild = await client.guilds.fetch(job.attrs.data.guild); const user = await guild.members.fetch(job.attrs.data.user); diff --git a/src/utils/logTranscripts.ts b/src/utils/logTranscripts.ts deleted file mode 100644 index 0950664..0000000 --- a/src/utils/logTranscripts.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type Discord from 'discord.js'; - -export interface JSONTranscriptSchema { - messages: { - content: string | null; - attachments: { - url: string; - name: string; - size: number; - }[]; - authorID: string; - authorUsername: string; - authorUsernameColor: string; - timestamp: string; - id: string; - edited: boolean; - }[]; - channel: string; - guild: string; - timestamp: string; -} - - -export const JSONTranscriptFromMessageArray = (messages: Discord.Message[]): JSONTranscriptSchema | null => { - if (messages.length === 0) return null; - return { - guild: messages[0]!.guild!.id, - channel: messages[0]!.channel.id, - timestamp: Date.now().toString(), - messages: messages.map((message: Discord.Message) => { - return { - content: message.content, - attachments: message.attachments.map((attachment: Discord.Attachment) => { - return { - url: attachment.url, - name: attachment.name!, - size: attachment.size, - }; - }), - authorID: message.author.id, - authorUsername: message.author.username + "#" + message.author.discriminator, - authorUsernameColor: message.member!.displayHexColor.toString(), - timestamp: message.createdTimestamp.toString(), - id: message.id, - edited: message.editedTimestamp ? true : false, - }; - }) - }; -} - -export const JSONTranscriptToHumanReadable = (data: JSONTranscriptSchema): string => { - let out = ""; - - for (const message of data.messages) { - const date = new Date(parseInt(message.timestamp)); - out += `${message.authorUsername} (${message.authorID}) [${date}]`; - if (message.edited) out += " (edited)"; - if (message.content) out += "\nContent:\n" + message.content.split("\n").map((line: string) => `\n> ${line}`).join(""); - if (message.attachments.length > 0) out += "\nAttachments:\n" + message.attachments.map((attachment: { url: string; name: string; size: number; }) => `\n> [${attachment.name}](${attachment.url}) (${attachment.size} bytes)`).join("\n"); - - out += "\n\n"; - } - return out; -} \ No newline at end of file