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();