Development (#18)
diff --git a/src/commands/settings/autopublish.ts b/src/commands/settings/autopublish.ts
index 1dc97e0..a21657f 100644
--- a/src/commands/settings/autopublish.ts
+++ b/src/commands/settings/autopublish.ts
@@ -1,9 +1,10 @@
-import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js";
+import { ActionRowBuilder, APIMessageComponentEmoji, ButtonBuilder, ButtonStyle, ChannelSelectMenuBuilder, ChannelType, CommandInteraction, SlashCommandSubcommandBuilder } from "discord.js";
import type Discord from "discord.js";
import client from "../../utils/client.js";
import { LoadingEmbed } from "../../utils/defaults.js";
import compare from "lodash"
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
+import getEmojiByName from "../../utils/getEmojiByName.js";
export const command = new SlashCommandSubcommandBuilder()
.setName("autopublish")
@@ -24,14 +25,14 @@
.addComponents(
new ButtonBuilder()
.setCustomId("switch")
- .setLabel(data.enabled ? "Disabled" : "Enabled")
- .setStyle(data.enabled ? ButtonStyle.Danger : ButtonStyle.Success)
- .setEmoji(data.enabled ? "✅" : "❌"),
+ .setLabel(data.enabled ? "Enabled" : "Disabled")
+ .setStyle(data.enabled ? ButtonStyle.Success : ButtonStyle.Danger)
+ .setEmoji(getEmojiByName("CONTROL." + (data.enabled ? "TICK" : "CROSS"), "id") as APIMessageComponentEmoji),
new ButtonBuilder()
.setCustomId("save")
.setLabel("Save")
.setStyle(ButtonStyle.Success)
- .setEmoji("💾")
+ .setEmoji(getEmojiByName("ICONS.SAVE", "id") as APIMessageComponentEmoji)
.setDisabled(compare.isEqual(data, config.autoPublish))
);
@@ -41,11 +42,18 @@
.setCustomId("channel")
.setPlaceholder("Select a channel")
.setMinValues(1)
+ .setChannelTypes(ChannelType.GuildAnnouncement, ChannelType.AnnouncementThread)
);
- const embed = new EmojiEmbed()
+ const current = data.channels.map((c) => `> <#${c}>`).join("\n") || "*None set*";
- await interaction.editReply({
+ const embed = new EmojiEmbed()
+ .setTitle("Auto Publish")
+ .setDescription("Currently enabled in:\n" + current)
+ .setStatus('Success')
+ .setEmoji("ICONS.PUBLISH")
+
+ await interaction.editReply({
embeds: [embed],
components: [channelSelect, buttons]
});
diff --git a/src/config/emojis.json b/src/config/emojis.json
index ecf1858..d1398cb 100644
--- a/src/config/emojis.json
+++ b/src/config/emojis.json
@@ -26,6 +26,7 @@
"LOGGING": "999613304446144562",
"SAVE": "1065722246322200586",
"REORDER": "1069323453909454890",
+ "PUBLISH": "1081691380004421743",
"NOTIFY": {
"ON": "1000726394579464232",
"OFF": "1078058136092541008"
diff --git a/src/premium/createTranscript.ts b/src/premium/createTranscript.ts
index fa5ec84..cdbc7b9 100644
--- a/src/premium/createTranscript.ts
+++ b/src/premium/createTranscript.ts
@@ -79,7 +79,7 @@
"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.`
- : ".")
+ : ". It will be automatically deleted after 30 days if a logging channel is not set.")
)
.setStatus("Success")
.setEmoji("CONTROL.DOWNLOAD")
diff --git a/src/utils/database.ts b/src/utils/database.ts
index d000340..c1728c3 100644
--- a/src/utils/database.ts
+++ b/src/utils/database.ts
@@ -186,11 +186,20 @@
createdBy: TranscriptAuthor;
}
+interface findDocSchema { channelID:string, messageID: string; transcript: string }
+
export class Transcript {
transcripts: Collection<TranscriptSchema>;
+ messageToTranscript: Collection<findDocSchema>;
constructor() {
this.transcripts = database.collection<TranscriptSchema>("transcripts");
+ this.messageToTranscript = database.collection<findDocSchema>("messageToTranscript");
+ }
+
+ async upload(data: findDocSchema) {
+ // console.log("Transcript upload")
+ await this.messageToTranscript.insertOne(data);
}
async create(transcript: Omit<TranscriptSchema, "code">) {
@@ -209,21 +218,16 @@
}
const doc = await this.transcripts.insertOne(Object.assign(transcript, { code: code }), collectionOptions);
- if(doc.acknowledged) return [code, key, iv];
+ if(doc.acknowledged) {
+ client.database.eventScheduler.schedule("deleteTranscript", (Date.now() + 1000 * 60 * 60 * 24 * 7).toString(), { guild: transcript.guild, code: code, iv: iv, key: key });
+ return [code, key, iv];
+ }
else return [null, null, null];
}
- async read(code: string, key: string, iv: string) {
- // console.log("Transcript read")
- const doc = await this.transcripts.findOne({ code: code });
- if(!doc) return null;
- for(const message of doc.messages) {
- if(message.content) {
- const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
- message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
- }
- }
- return doc;
+ async delete(code: string) {
+ // console.log("Transcript delete")
+ await this.transcripts.deleteOne({ code: code });
}
async deleteAll(guild: string) {
@@ -234,6 +238,74 @@
}
}
+ async readEncrypted(code: string) {
+ // console.log("Transcript read")
+ let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
+ let findDoc: findDocSchema | null = null;
+ if(!doc) findDoc = (await this.messageToTranscript.findOne({ transcript: code }));
+ if(findDoc) {
+ const message = await ((client.channels.cache.get(findDoc.channelID)) as Discord.TextBasedChannel | null)?.messages.fetch(findDoc.messageID);
+ if(!message) return null;
+ const attachment = message.attachments.first();
+ if(!attachment) return null;
+ const transcript = (await fetch(attachment.url)).body;
+ if(!transcript) return null;
+ const reader = transcript.getReader();
+ let data: Uint8Array | null = null;
+ let allPacketsReceived = false;
+ while (!allPacketsReceived) {
+ const { value, done } = await reader.read();
+ if (done) {allPacketsReceived = true; continue;}
+ if(!data) {
+ data = value;
+ } else {
+ data = new Uint8Array(Buffer.concat([data, value]));
+ }
+ }
+ if(!data) return null;
+ doc = JSON.parse(Buffer.from(data).toString());
+ }
+ if(!doc) return null;
+ return doc;
+ }
+
+ async read(code: string, key: string, iv: string) {
+ // console.log("Transcript read")
+ let doc: TranscriptSchema | null = await this.transcripts.findOne({ code: code });
+ let findDoc: findDocSchema | null = null;
+ if(!doc) findDoc = (await this.messageToTranscript.findOne({ transcript: code }));
+ if(findDoc) {
+ const message = await ((client.channels.cache.get(findDoc.channelID)) as Discord.TextBasedChannel | null)?.messages.fetch(findDoc.messageID);
+ if(!message) return null;
+ const attachment = message.attachments.first();
+ if(!attachment) return null;
+ const transcript = (await fetch(attachment.url)).body;
+ if(!transcript) return null;
+ const reader = transcript.getReader();
+ let data: Uint8Array | null = null;
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
+ while(true) {
+ const { value, done } = await reader.read();
+ if (done) break;
+ if(!data) {
+ data = value;
+ } else {
+ data = new Uint8Array(Buffer.concat([data, value]));
+ }
+ }
+ if(!data) return null;
+ doc = JSON.parse(Buffer.from(data).toString());
+ }
+ if(!doc) return null;
+ for(const message of doc.messages) {
+ if(message.content) {
+ const decCipher = crypto.createDecipheriv("AES-256-CBC", key, iv);
+ message.content = decCipher.update(message.content, "base64", "utf8") + decCipher.final("utf8");
+ }
+ }
+ return doc;
+ }
+
async createTranscript(messages: Message[], interaction: MessageComponentInteraction | CommandInteraction, member: GuildMember) {
const interactionMember = await interaction.guild?.members.fetch(interaction.user.id)
const newOut: Omit<TranscriptSchema, "code"> = {
diff --git a/src/utils/eventScheduler.ts b/src/utils/eventScheduler.ts
index a79a260..f3b7a00 100644
--- a/src/utils/eventScheduler.ts
+++ b/src/utils/eventScheduler.ts
@@ -3,6 +3,7 @@
import * as fs from "fs";
import * as path from "path";
import config from "../config/main.js";
+import { TextChannel } from "discord.js";
class EventScheduler {
private agenda: Agenda;
@@ -21,6 +22,19 @@
if (role) await user.roles.remove(role);
await job.remove();
});
+ this.agenda.define("uploadTranscript", async (job) => {
+ const channelID: string | null = (await client.database.guilds.read(job.attrs.data.guild)).logging.logs.channel;
+ if (!channelID) return;
+ const channel = await client.channels.fetch(channelID);
+ if(!channel || !(channel instanceof TextChannel)) return;
+ const transcript = await client.database.transcripts.read(job.attrs.data.transcript, job.attrs.data.key, job.attrs.data.iv);
+ await client.database.transcripts.delete(job.attrs.data.transcript);
+ const file = Buffer.from(JSON.stringify(transcript), "base64");
+ const fileName = `${job.attrs.data.transcript}.json`;
+ const m = await channel.send({ files: [{ attachment: file, name: fileName }] });
+ await client.database.transcripts.upload({ channelID: channel.id, messageID: m.id, transcript: job.attrs.data.transcript})
+ await job.remove();
+ });
this.agenda.define("deleteFile", async (job) => {
fs.rm(path.resolve("dist/utils/temp", job.attrs.data.fileName), (e) => { client.emit("error", e as Error); });
await job.remove();