require("dotenv").config(); const { DISCORD_TOKEN, MATRIX_BASE_URL, MATRIX_SERVER_NAME, MATRIX_ADMIN_TOKEN } = process.env; const colors = require("colors"); const Path = require("path") const Discord = require("discord.js"); const Client = new Discord.Client({ intents: [] }); const { REST, Routes } = require('discord.js'); const rest = new REST({ version: '10' }).setToken(DISCORD_TOKEN); var whoami; var matrixHeaders = { "Authorization": `Bearer ${MATRIX_ADMIN_TOKEN}` }; Client.on("ready", async () => { console.log(`${colors.cyan("[Info]")} Logged in as ${Client.user.displayName}`); const commands = [ { "name": "matrix-register", "description": "Create an account on matrix.litenet.tel", "type": 1, "options": [ { "name": "username", "description": "@:litenet.tel. Be warned, you can't change this once it's set.", "type": Discord.ApplicationCommandOptionType.String, "required": true } ] } ] //Register commands await (async () => { try { console.log(`${colors.cyan("[Info]")} Registering Commands...`) //Global await rest.put(Routes.applicationCommands(Client.user.id), { body: commands }) console.log(`${colors.cyan("[Info]")} Successfully registered commands.`); } catch (error) { console.error(error); } })(); fetch(`${MATRIX_BASE_URL}/_matrix/client/v3/account/whoami`, { headers: matrixHeaders }).then(async (response) => { whoami = await response.json(); }) }); const usernameFilter = /^[a-z0-9._=\-\/+]+$/; const formatUsername = (input) => { if (!usernameFilter.test(input)) throw new Error("Username does not match allowed characters"); return `@${input}:${MATRIX_SERVER_NAME}` } function generatePassword(length) { // Define the character set const characters = 'abcdefghijklmnopqrstuvwxyz0123456789._=-/+'; // Ensure the length is valid if (length <= 0) { throw new Error('Password length must be greater than 0'); } let password = ''; // Generate the password for (let i = 0; i < length; i++) { const randomIndex = Math.floor(Math.random() * characters.length); password += characters[randomIndex]; } return password; } async function uploadMediaToMatrix(url, filename, mimetype) { var data = await fetch(url); const response = await fetch(`${MATRIX_BASE_URL}/_matrix/media/v3/upload?filename=${filename}`, { method: "POST", headers: { "Authorization": `Bearer ${MATRIX_ADMIN_TOKEN}`, "Content-Type": mimetype }, body: await data.arrayBuffer() }); const json = await response.json(); return json; } Client.on("interactionCreate", async (interaction) => { if (!interaction.isCommand()) return; switch (interaction.commandName) { case "matrix-register": const inputUsername = interaction.options.getString("username"); if (!usernameFilter.test(inputUsername)) return interaction.reply({ephemeral: true, content: "Your username contains invalid characters.\nYour username MUST contain only the characters `a-z`, `0-9`, `.`, `_`, `=`, `-`, `/`, and `+`."}); const mxid = formatUsername(inputUsername); // Check if the user exists const userCheck = await fetch(`${MATRIX_BASE_URL}/_synapse/admin/v2/users/${mxid}`, { headers: { "Authorization": `Bearer ${MATRIX_ADMIN_TOKEN}` } }); if (userCheck.ok) return interaction.reply({ephemeral: true, content: "That username is taken! Please choose a different one."}); // Check if user already has a matrix account const dataStoreCheck = await fetch(`${MATRIX_BASE_URL}/_matrix/client/v3/user/${whoami.user_id}/account_data/tel.litenet.discordids.${interaction.user.id}`,{ headers: matrixHeaders }) if(dataStoreCheck.status !== 404) return interaction.reply({ephemeral: true, content: `You already have an account dingus! It's \`${(await dataStoreCheck.json()).matrix_id}\`\nIf you believe this is an error, please contact an admin!`}); const newPassword = generatePassword(32); // Emma@Rory&: Upload avatar to matrix: const avatar = interaction.user.displayAvatarURL({size: 4096}); var filename = Path.basename(avatar); var mimetype; switch (filename.split(".").pop()) { case "png": mimetype = "image/png"; break; case "jpg": case "jpeg": mimetype = "image/jpeg"; break; case "gif": mimetype = "image/gif"; break; case "webp": mimetype = "image/webp"; break; default: mimetype = "image/png"; break; } let mxcUri = undefined; await uploadMediaToMatrix(avatar, filename, mimetype).then((response) => { mxcUri = response.content_uri; }).catch((error) => { console.error("Error while uploading discord avatar to matrix:", error); }); // Create the user const createUser = await fetch(`${MATRIX_BASE_URL}/_synapse/admin/v2/users/${mxid}`, { method: "PUT", headers: { "Authorization": `Bearer ${MATRIX_ADMIN_TOKEN}` }, body: JSON.stringify({ "password": newPassword, "displayname": interaction.user.displayName, avatar_url: mxcUri }) }); if (!userCheck.status.toString() == "201") { const createUserError = await createUser.json() return interaction.reply({ ephemeral: true, content: `An error has occured!\n${createUserError.errcode}: ${createUserError.error}` }) } // Send the user their credentials interaction.user.send({content: `Your matrix account has been created!\nUsername: \`${mxid}\`\nPassword: ||\`${newPassword}\`||\nServer: ${MATRIX_SERVER_NAME}\n\nYou can find a list of Matrix clients [here!](https://matrix.org/ecosystem/clients/)`}).then(() => { interaction.reply({ephemeral: true, content: "User created! Check your DMs!"}); }).catch((error) => { interaction.reply({ephemeral: true, content: `Your matrix account has been created!\nUsername: \`${mxid}\`\nPassword: ||\`${newPassword}\`||\nServer: ${MATRIX_SERVER_NAME}\n\nYou can find a list of Matrix clients [here!](https://matrix.org/ecosystem/clients/)\n\nYour DMs were disabled, so we weren't able to send you these credentials!\n***MAKE SURE YOU SAVE THEM***`}); }) // Store the discord id and matrix id fetch(`${MATRIX_BASE_URL}/_matrix/client/v3/user/${whoami.user_id}/account_data/tel.litenet.discordids.${interaction.user.id}`,{ headers: matrixHeaders, body: JSON.stringify({ matrix_id: mxid, discord_id: interaction.user.id }), method: "PUT" }) break; } }) Client.login(DISCORD_TOKEN);