diff --git a/package.json b/package.json
index dde55d311..13017b792 100644
--- a/package.json
+++ b/package.json
@@ -34,11 +34,13 @@
"@sapphi-red/web-noise-suppressor": "0.3.3",
"@vap/core": "0.0.12",
"@vap/shiki": "0.10.5",
+ "axios": "^1.6.7",
"eslint-plugin-simple-header": "^1.0.2",
"fflate": "^0.7.4",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.43.0",
"nanoid": "^4.0.2",
+ "node-forge": "^1.3.1",
"virtual-merge": "^1.0.1"
},
"devDependencies": {
@@ -46,6 +48,7 @@
"@types/diff": "^5.0.3",
"@types/lodash": "^4.14.194",
"@types/node": "^18.16.3",
+ "@types/node-forge": "^1.3.11",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/yazl": "^2.4.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 43866f50b..a0cc197b9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,5 +1,9 @@
lockfileVersion: '6.0'
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
patchedDependencies:
eslint-plugin-path-alias@1.0.0:
hash: m6sma4g6bh67km3q6igf6uxaja
@@ -18,6 +22,9 @@ dependencies:
'@vap/shiki':
specifier: 0.10.5
version: 0.10.5
+ axios:
+ specifier: ^1.6.7
+ version: 1.6.7
eslint-plugin-simple-header:
specifier: ^1.0.2
version: 1.0.2
@@ -33,6 +40,9 @@ dependencies:
nanoid:
specifier: ^4.0.2
version: 4.0.2
+ node-forge:
+ specifier: ^1.3.1
+ version: 1.3.1
virtual-merge:
specifier: ^1.0.1
version: 1.0.1
@@ -50,6 +60,9 @@ devDependencies:
'@types/node':
specifier: ^18.16.3
version: 18.16.3
+ '@types/node-forge':
+ specifier: ^1.3.11
+ version: 1.3.11
'@types/react':
specifier: ^18.2.0
version: 18.2.0
@@ -572,6 +585,12 @@ packages:
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
dev: true
+ /@types/node-forge@1.3.11:
+ resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
+ dependencies:
+ '@types/node': 18.16.3
+ dev: true
+
/@types/node@18.16.3:
resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==}
dev: true
@@ -613,8 +632,8 @@ packages:
resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
dev: true
- /@types/yauzl@2.10.0:
- resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
+ /@types/yauzl@2.10.3:
+ resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
requiresBuild: true
dependencies:
'@types/node': 18.16.3
@@ -883,12 +902,26 @@ packages:
resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==}
dev: true
+ /asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+ dev: false
+
/atob@2.1.2:
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
engines: {node: '>= 4.5.0'}
hasBin: true
dev: true
+ /axios@1.6.7:
+ resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==}
+ dependencies:
+ follow-redirects: 1.15.5
+ form-data: 4.0.0
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+ dev: false
+
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
@@ -1067,6 +1100,13 @@ packages:
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
dev: true
+ /combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ delayed-stream: 1.0.0
+ dev: false
+
/component-emitter@1.3.0:
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
dev: true
@@ -1197,6 +1237,11 @@ packages:
isobject: 3.0.1
dev: true
+ /delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+ dev: false
+
/devtools-protocol@0.0.1107588:
resolution: {integrity: sha512-yIR+pG9x65Xko7bErCUSQaDLrO/P1p3JUzEk7JCU4DowPcGHkTGUGQapcfcLc4qj0UaALwZ+cr0riFgiqpixcg==}
dev: true
@@ -1699,7 +1744,7 @@ packages:
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
- '@types/yauzl': 2.10.0
+ '@types/yauzl': 2.10.3
transitivePeerDependencies:
- supports-color
dev: true
@@ -1790,11 +1835,30 @@ packages:
resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
dev: true
+ /follow-redirects@1.15.5:
+ resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+ dev: false
+
/for-in@1.0.2:
resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==}
engines: {node: '>=0.10.0'}
dev: true
+ /form-data@4.0.0:
+ resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+ engines: {node: '>= 6'}
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.35
+ dev: false
+
/fragment-cache@0.2.1:
resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
engines: {node: '>=0.10.0'}
@@ -1810,8 +1874,8 @@ packages:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
- /fsevents@2.3.2:
- resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ /fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
@@ -2364,6 +2428,18 @@ packages:
picomatch: 2.3.1
dev: true
+ /mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-db: 1.52.0
+ dev: false
+
/min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
@@ -2467,6 +2543,11 @@ packages:
whatwg-url: 5.0.0
dev: true
+ /node-forge@1.3.1:
+ resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
+ engines: {node: '>= 6.13.0'}
+ dev: false
+
/normalize-package-data@2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
@@ -2678,7 +2759,6 @@ packages:
/proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
- dev: true
/pump@3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
@@ -3227,7 +3307,7 @@ packages:
'@esbuild-kit/core-utils': 3.1.0
'@esbuild-kit/esm-loader': 2.5.5
optionalDependencies:
- fsevents: 2.3.2
+ fsevents: 2.3.3
dev: true
/type-check@0.4.0:
@@ -3464,7 +3544,3 @@ packages:
name: gifenc
version: 1.0.3
dev: false
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false
diff --git a/src/plugins/Encryptcord/index.tsx b/src/plugins/Encryptcord/index.tsx
new file mode 100644
index 000000000..0ec99cc85
--- /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 {
+ 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 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: [
+ {
+ id: 761777382041714690n,
+ name: "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: 'await $self.joinGroup(C);$&'
+ }
+ }
+ ],
+ async joinGroup(interaction) {
+ const sender = await UserUtils.getUser(interaction.application_id).catch(() => null);
+ if (!sender || sender.bot == true) return;
+ if (interaction.data.component_type == 2 && interaction.data.custom_id == "acceptGroup") {
+ await sendTempMessage(interaction.application_id, `${await DataStore.get("encryptcordPublicKey")}`, "join");
+ }
+ },
+ 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);
+}