diff --git a/src/plugins/encryptcord/index.tsx b/src/plugins/encryptcord/index.tsx
new file mode 100644
index 000000000..da943a9e2
--- /dev/null
+++ b/src/plugins/encryptcord/index.tsx
@@ -0,0 +1,516 @@
+import { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
+import { removeButton } from "@api/MessagePopover";
+import definePlugin, { StartAt } from "@utils/types";
+import * as DataStore from "@api/DataStore";
+import { sleep } from "@utils/misc";
+import { findByPropsLazy } from "@webpack";
+import { addPreSendListener, removePreSendListener, SendListener } from "@api/MessageEvents";
+import { useEffect, useState } from "@webpack/common";
+import { Devs } from "@utils/constants";
+import {
+ RestAPI,
+ SnowflakeUtils,
+ UserUtils,
+ UserStore,
+ MessageActions,
+} from "@webpack/common";
+import {
+ ApplicationCommandInputType,
+ sendBotMessage,
+ ApplicationCommandOptionType,
+ findOption,
+} from "@api/Commands";
+import { Message } from "discord-types/general";
+const MessageCreator = findByPropsLazy("createBotMessage");
+const CloudUtils = findByPropsLazy("CloudUpload");
+import axios from 'axios';
+import { getCurrentChannel } from "@utils/discord";
+import forge from 'node-forge';
+
+let enabled;
+let setEnabled;
+
+// Interface for Message Create
+interface IMessageCreate {
+ type: "MESSAGE_CREATE";
+ optimistic: boolean;
+ isPushNotification: boolean;
+ channelId: string;
+ message: Message;
+}
+
+// Generate RSA key pair
+function generateKeyPair(): { privateKey: string; publicKey: string; } {
+ const keys = forge.pki.rsa.generateKeyPair({ bits: 1024 });
+ const privateKey = forge.pki.privateKeyToPem(keys.privateKey);
+ const publicKey = forge.pki.publicKeyToPem(keys.publicKey);
+
+ return { privateKey, publicKey };
+}
+
+// Encrypt message with public key
+function encrypt(message: string, publicKey): string[] {
+ try {
+ const publicKeyObj = forge.pki.publicKeyFromPem(publicKey);
+ const chunkSize = 62;
+
+ const emojiRegex = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g;
+ message = message.replace(emojiRegex, '');
+
+ const encryptedChunks: string[] = [];
+
+ for (let i = 0; i < message.length; i += chunkSize) {
+ const chunk = message.substring(i, i + chunkSize);
+ const encryptedChunk = publicKeyObj.encrypt(chunk, 'RSA-OAEP', {
+ md: forge.md.sha256.create(),
+ });
+ encryptedChunks.push(forge.util.encode64(encryptedChunk));
+ }
+
+ return encryptedChunks;
+ } catch (error) {
+ return [];
+ }
+}
+
+// Decrypt message with private key
+function decrypt(encryptedMessages: string[], privateKey): string {
+ const privateKeyObj = forge.pki.privateKeyFromPem(privateKey);
+ let decryptedMessages: string[] = [];
+
+ encryptedMessages.forEach((encryptedMessage) => {
+ const encrypted = forge.util.decode64(encryptedMessage);
+ const decrypted = privateKeyObj.decrypt(encrypted, 'RSA-OAEP', {
+ md: forge.md.sha256.create(),
+ });
+ decryptedMessages.push(decrypted);
+ });
+
+ return decryptedMessages.join('');
+}
+
+// Chat Bar Icon Component
+const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
+ [enabled, setEnabled] = useState(false);
+
+ useEffect(() => {
+ const listener: SendListener = async (_, message) => {
+ if (enabled) {
+ const groupChannel = await DataStore.get('encryptcordChannelId');
+ if (getCurrentChannel().id !== groupChannel) {
+ sendBotMessage(getCurrentChannel().id, { content: `You must be in <#${groupChannel}> to send an encrypted message!\n> If you wish to send an unencrypted message, please click the button in the chatbar.` });
+ message.content = "";
+ return;
+ }
+ const trimmedMessage = message.content.trim();
+ await MessageActions.receiveMessage(groupChannel, await createMessage(trimmedMessage, UserStore.getCurrentUser().id, groupChannel, 0));
+ const encryptcordGroupMembers = await DataStore.get('encryptcordGroupMembers');
+ const dmPromises = Object.keys(encryptcordGroupMembers).map(async (memberId) => {
+ const groupMember = await UserUtils.getUser(memberId).catch(() => null);
+ if (!groupMember) return;
+ const encryptedMessage = encrypt(trimmedMessage, encryptcordGroupMembers[memberId]);
+ const encryptedMessageString = JSON.stringify(encryptedMessage);
+ await sendTempMessage(groupMember.id, encryptedMessageString, `message`);
+ });
+
+ await Promise.all(dmPromises);
+ message.content = "";
+ }
+ };
+
+ addPreSendListener(listener);
+ return () => void removePreSendListener(listener);
+ }, [enabled]);
+
+ if (!isMainChat) return null;
+
+ return (
+ {
+ const groupChannel = await DataStore.get('encryptcordChannelId');
+ if (await DataStore.get('encryptcordGroup') == false) {
+ sendBotMessage(getCurrentChannel().id, { content: `You must be in an E2EE group to send an encrypted message!` });
+ return;
+ }
+ if (getCurrentChannel().id !== groupChannel) {
+ sendBotMessage(getCurrentChannel().id, { content: `You must be in the E2EE group channel to send an encrypted message!` });
+ return;
+ }
+ setEnabled(!enabled);
+ }}
+ buttonProps={{
+ "aria-haspopup": "dialog",
+ }}
+ >
+
+
+ );
+};
+
+// Export Plugin
+export default definePlugin({
+ name: "Encryptcord",
+ description: "End-to-end encryption in Discord!",
+ authors: [Devs.Inbestigator],
+ dependencies: ["CommandsAPI"],
+ patches: [
+ {
+ find: "executeMessageComponentInteraction:",
+ replacement: {
+ match: /await\s+l\.default\.post\({\s*url:\s*A\.Endpoints\.INTERACTIONS,\s*body:\s*C,\s*timeout:\s*3e3\s*},\s*t\s*=>\s*{\s*h\(T,\s*p,\s*f,\s*t\)\s*}\s*\)/,
+ replace: 'if(await $self.joinGroup(C))return;$&'
+ }
+ }
+ ],
+ async joinGroup(interaction) {
+ const sender = await UserUtils.getUser(interaction.application_id).catch(() => null);
+ if (!sender || sender.bot == true) return false;
+ if (interaction.data.component_type == 2 && interaction.data.custom_id == "acceptGroup") {
+ await sendTempMessage(interaction.application_id, `${await DataStore.get("encryptcordPublicKey")}`, "join");
+ }
+ return true;
+ },
+ flux: {
+ async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) {
+ if (optimistic || type !== "MESSAGE_CREATE") return;
+ if (message.state === "SENDING") return;
+ if (!message.content) return;
+ const encryptcordGroupMembers = await DataStore.get('encryptcordGroupMembers');
+ if (!Object.keys(encryptcordGroupMembers).some(key => key == message.author.id)) {
+ const encryptcordGroupJoinList = await DataStore.get('encryptcordGroupJoinList');
+ if (!encryptcordGroupJoinList.includes(message.author.id)) {
+ switch (message.content.split("/")[0].toLowerCase()) {
+ case "e2eeinvite":
+ const inviteMessage = `I've invited you to an [end-to-end encrypted]() group in <#${message.content.split("/")[1]}>.`;
+ await MessageActions.receiveMessage(channelId, {
+ ...await createMessage(inviteMessage, message.author.id, channelId, 0), components: [{
+ type: 1,
+ components: [{
+ type: 2,
+ style: 3,
+ label: 'Accept!',
+ custom_id: 'acceptGroup'
+ }]
+ }]
+ });
+ break;
+ case "groupdata":
+ const groupdata = (await axios.get(message.attachments[0].url)).data;
+ await handleGroupData(groupdata);
+ break;
+ default:
+ break;
+ }
+ return;
+ };
+ if (message.content.toLowerCase() !== "join") return;
+ const sender = await UserUtils.getUser(message.author.id).catch(() => null);
+ if (!sender) return;
+ const userKey = (await axios.get(message.attachments[0].url)).data;
+ await handleJoin(sender.id, userKey, encryptcordGroupMembers);
+ return;
+ }
+ const dmChannelId = await RestAPI.post({
+ url: `/users/@me/channels`,
+ body: {
+ recipient_id: message.author.id,
+ },
+ }).then((response) => response.body.id);
+ if (channelId !== dmChannelId) return;
+ const sender = await UserUtils.getUser(message.author.id).catch(() => null);
+ if (!sender) return;
+ const groupChannel = await DataStore.get('encryptcordChannelId');
+ switch (message.content.toLowerCase()) {
+ case "leaving":
+ handleLeaving(sender.id, encryptcordGroupMembers, groupChannel);
+ break;
+ case "message":
+ const messagedata = (await axios.get(message.attachments[0].url)).data;
+ await handleMessage(messagedata, sender.id, groupChannel);
+ break;
+ case "groupdata":
+ const groupdata = (await axios.get(message.attachments[0].url)).data;
+ await handleGroupData(groupdata);
+ break;
+ default:
+ break;
+ }
+ },
+ },
+ commands: [
+ {
+ name: "encryptcord",
+ description: "End-to-end encryption in Discord!",
+ options: [
+ {
+ name: "leave",
+ description: "Leave current group",
+ options: [],
+ type: ApplicationCommandOptionType.SUB_COMMAND,
+ },
+ {
+ name: "start",
+ description: "Start an E2EE group",
+ options: [],
+ type: ApplicationCommandOptionType.SUB_COMMAND,
+ },
+ {
+ name: "invite",
+ description: "Invite a user to your group",
+ options: [
+ {
+ name: "user",
+ description: "Who to invite",
+ required: true,
+ type: ApplicationCommandOptionType.USER,
+ },
+ ],
+ type: ApplicationCommandOptionType.SUB_COMMAND,
+ },
+ ],
+ inputType: ApplicationCommandInputType.BOT,
+ execute: (opts, ctx) => {
+ switch (opts[0].name) {
+ case "start":
+ startGroup(opts[0].options, ctx);
+ break;
+ case "invite":
+ invite(opts[0].options, ctx);
+ break;
+ case "leave":
+ leave(opts[0].options, ctx);
+ break;
+ }
+ },
+ },
+ ],
+ startAt: StartAt.DOMContentLoaded,
+ async start() {
+ addChatBarButton("Encryptcord", ChatBarIcon);
+ const pair = generateKeyPair();
+ await DataStore.set('encryptcordPublicKey', pair.publicKey);
+ await DataStore.set('encryptcordPrivateKey', pair.privateKey);
+ if (await DataStore.get("encryptcordGroup") == true) {
+ await leave("", { channel: { id: await DataStore.get("encryptcordChannelId") } });
+ }
+ await DataStore.set('encryptcordGroup', false);
+ await DataStore.set('encryptcordChannelId', "");
+ await DataStore.set('encryptcordGroupMembers', {});
+ await DataStore.set('encryptcordGroupJoinList', []);
+ },
+ stop() {
+ removeButton("Encryptcord");
+ },
+});
+
+// Send Temporary Message
+async function sendTempMessage(recipientId: string, attachment: string, content: string) {
+ if (recipientId == UserStore.getCurrentUser().id) return;
+
+ const dmChannelId = await RestAPI.post({
+ url: `/users/@me/channels`,
+ body: {
+ recipient_id: recipientId,
+ },
+ }).then((response) => response.body.id);
+
+ if (attachment && attachment != "") {
+ const upload = await new CloudUtils.CloudUpload({
+ file: new File([new Blob([attachment])], "file.text", { type: "text/plain; charset=utf-8" }),
+ isClip: false,
+ isThumbnail: false,
+ platform: 1,
+ }, dmChannelId, false, 0);
+ upload.on("complete", async () => {
+ const messageId = await RestAPI.post({
+ url: `/channels/${dmChannelId}/messages`,
+ body: {
+ content,
+ attachments: [{
+ id: "0",
+ filename: upload.filename,
+ uploaded_filename: upload.uploadedFilename,
+ }],
+ nonce: SnowflakeUtils.fromTimestamp(Date.now()),
+ },
+ }).then((response) => response.body.id);
+
+ await sleep(500);
+ RestAPI.delete({
+ url: `/channels/${dmChannelId}/messages/${messageId}`
+ });
+ });
+ await upload.upload();
+ return;
+ }
+
+ const messageId = await RestAPI.post({
+ url: `/channels/${dmChannelId}/messages`,
+ body: {
+ content,
+ nonce: SnowflakeUtils.fromTimestamp(Date.now()),
+ },
+ }).then((response) => response.body.id);
+
+ await sleep(500);
+ RestAPI.delete({
+ url: `/channels/${dmChannelId}/messages/${messageId}`
+ });
+}
+
+// Handle leaving group
+async function handleLeaving(senderId: string, encryptcordGroupMembers: object, groupChannel: string) {
+ const updatedMembers = Object.keys(encryptcordGroupMembers).reduce((result, memberId) => {
+ if (memberId !== senderId) {
+ result[memberId] = encryptcordGroupMembers[memberId];
+ }
+ return result;
+ }, {});
+
+ await DataStore.set('encryptcordGroupMembers', updatedMembers);
+
+ await MessageActions.receiveMessage(groupChannel, await createMessage("", senderId, groupChannel, 2));
+}
+
+// Handle receiving message
+async function handleMessage(message, senderId: string, groupChannel: string) {
+ const decryptedMessage = decrypt(message, await DataStore.get("encryptcordPrivateKey"));
+ await MessageActions.receiveMessage(groupChannel, await createMessage(decryptedMessage, senderId, groupChannel, 0));
+}
+
+// Handle receiving group data
+async function handleGroupData(groupData) {
+ await DataStore.set('encryptcordChannelId', groupData.channel);
+ await DataStore.set('encryptcordGroupMembers', groupData.members);
+ await DataStore.set('encryptcordGroup', true);
+ await MessageActions.receiveMessage(groupData.channel, await createMessage("", UserStore.getCurrentUser().id, groupData.channel, 7));
+ setEnabled(true);
+}
+
+// Handle joining group
+async function handleJoin(senderId: string, senderKey: string, encryptcordGroupMembers: object) {
+ const encryptcordGroupJoinList = await DataStore.get('encryptcordGroupJoinList');
+ const updatedMembers = encryptcordGroupJoinList.filter(memberId => memberId !== senderId);
+ await DataStore.set('encryptcordGroupJoinList', updatedMembers);
+
+ encryptcordGroupMembers[senderId] = senderKey;
+ await DataStore.set('encryptcordGroupMembers', encryptcordGroupMembers);
+ const groupChannel = await DataStore.get('encryptcordChannelId');
+ const newMember = await UserUtils.getUser(senderId).catch(() => null);
+ if (!newMember) return;
+
+ const membersData = {};
+ Object.entries(encryptcordGroupMembers)
+ .forEach(([memberId, value]) => {
+ membersData[memberId] = value;
+ });
+
+ const membersDataString = JSON.stringify({ members: membersData, channel: groupChannel });
+
+ const dmPromises = Object.keys(encryptcordGroupMembers).map(async (memberId) => {
+ const groupMember = await UserUtils.getUser(memberId).catch(() => null);
+ if (!groupMember) return;
+ await sendTempMessage(groupMember.id, membersDataString, `groupdata`);
+ });
+
+ await Promise.all(dmPromises);
+ await MessageActions.receiveMessage(groupChannel, await createMessage("", senderId, groupChannel, 7));
+}
+
+// Create message for group
+async function createMessage(message: string, senderId: string, channelId: string, type: number) {
+ const messageStart = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] });
+ const sender = await UserUtils.getUser(senderId).catch(() => null);
+ if (!sender) return;
+ return { ...messageStart, content: message, author: sender, type, flags: 0 };
+}
+
+// Start E2EE Group
+async function startGroup(opts, ctx) {
+ const channelId = ctx.channel.id;
+ await DataStore.set('encryptcordChannelId', channelId);
+ await DataStore.set('encryptcordGroupMembers', {
+ [UserStore.getCurrentUser().id]: await DataStore.get("encryptcordPublicKey")
+ });
+ await DataStore.set('encryptcordGroupJoinList', []);
+ await DataStore.set('encryptcordGroup', true);
+ sendBotMessage(channelId, { content: "Group created!" });
+ await MessageActions.receiveMessage(channelId, await createMessage("", UserStore.getCurrentUser().id, channelId, 7));
+ setEnabled(true);
+}
+
+// Invite User to Group
+async function invite(opts, ctx) {
+ const invitedUser = await UserUtils.getUser(findOption(opts, "user", "")).catch(() => null);
+ if (!invitedUser) return;
+
+ const channelId = ctx.channel.id;
+ if (!(await DataStore.get('encryptcordGroup'))) {
+ sendBotMessage(channelId, { content: `You're not in a group!` });
+ return;
+ }
+
+ const encryptcordGroupMembers = await DataStore.get('encryptcordGroupMembers');
+ if (Object.keys(encryptcordGroupMembers).some(key => key == invitedUser.id)) {
+ sendBotMessage(channelId, { content: `<@${invitedUser.id}> is already in the group.` });
+ return;
+ }
+
+ const encryptcordGroupJoinList = await DataStore.get('encryptcordGroupJoinList');
+ if (encryptcordGroupJoinList.includes(invitedUser.id)) {
+ sendBotMessage(channelId, { content: `<@${invitedUser.id}> is already in the join list.` });
+ return;
+ }
+
+ encryptcordGroupJoinList.push(invitedUser.id);
+ await DataStore.set('encryptcordGroupJoinList', encryptcordGroupJoinList);
+
+ await sendTempMessage(invitedUser.id, "", `e2eeinvite/${await DataStore.get('encryptcordChannelId')}`);
+
+ sendBotMessage(channelId, { content: `<@${invitedUser.id}> invited successfully.` });
+}
+
+// Leave the Group
+async function leave(opts, ctx) {
+ const channelId = ctx.channel.id;
+ if (!(await DataStore.get('encryptcordGroup'))) {
+ sendBotMessage(channelId, { content: `You're not in a group!` });
+ return;
+ }
+ const user = UserStore.getCurrentUser();
+ const encryptcordGroupMembers = await DataStore.get('encryptcordGroupMembers');
+
+ const dmPromises = Object.keys(encryptcordGroupMembers).map(async (memberId) => {
+ const groupMember = await UserUtils.getUser(memberId).catch(() => null);
+ if (!groupMember) return;
+ await sendTempMessage(groupMember.id, "", `leaving`);
+ });
+
+ await Promise.all(dmPromises);
+ await DataStore.set('encryptcordGroup', false);
+ await DataStore.set('encryptcordChannelId', "");
+ await DataStore.set('encryptcordGroupMembers', {});
+ await DataStore.set('encryptcordGroupJoinList', []);
+ await MessageActions.receiveMessage(channelId, await createMessage("", user.id, channelId, 2));
+ setEnabled(false);
+}