commit 3b3068efaa9bd7bc4e8d94b1b2558d4e6940f538 Author: Kaylee Sachs Date: Mon Sep 29 03:04:38 2025 +0200 Initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1e55bd5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +node_modules/ +.env.local +.git +.gitignore diff --git a/.env b/.env new file mode 100644 index 0000000..11c5349 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +DISCORD_TOKEN= +DISCORD_CLIENT_ID= +# permissions for bot : 412854249536 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a9aa5d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Configuratrion +.env.local + +# Libraries +node_modules/ + +# Save data +data/**/*.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..70234aa --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\index.js" + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..35d14c3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM node:latest + +# Create the directory! +RUN mkdir -p /roleautoremover +WORKDIR /roleautoremover + +# Copy and Install our bot +COPY package.json /roleautoremover +COPY package-lock.json /roleautoremover +RUN npm install + +# Our precious bot +COPY . /roleautoremover + +# Start me! +CMD ["node", "index.js"] diff --git a/commands/check.js b/commands/check.js new file mode 100644 index 0000000..ec90127 --- /dev/null +++ b/commands/check.js @@ -0,0 +1,24 @@ +const { CommandInteraction, SlashCommandBuilder } = require("discord.js"); +const { dbGetAll, dbSerialize } = require("../db"); +const { PermissionFlagsBits } = require('discord-api-types/v10'); + +module.exports.name = "role-remover-check"; + +module.exports.data = new SlashCommandBuilder() + .setName(module.exports.name) + .setDescription("Gets the list of roles that are auto removed.") + .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles); + +/** + * @param {CommandInteraction} interaction + */ +module.exports.execute = async function(interaction) { + const guildId = interaction.guildId; + const settings = dbGetAll("roles") + const sameGuild = settings.filter(s => s.guildId === guildId) + + // Done + return interaction.reply({ + content: '# Auto removed roles:\n```json\n' + dbSerialize(sameGuild).substring(0, 1900) + '\n```', + }); +}; diff --git a/commands/create.js b/commands/create.js new file mode 100644 index 0000000..1d9208d --- /dev/null +++ b/commands/create.js @@ -0,0 +1,56 @@ +const { CommandInteraction, SlashCommandBuilder, Role } = require("discord.js"); +const { dbGet, dbWrite } = require("../db"); +const { PermissionFlagsBits } = require('discord-api-types/v10'); + +module.exports.name = "role-remover-create"; + +module.exports.data = new SlashCommandBuilder() + .setName(module.exports.name) + .setDescription("Adds a role to be auto removed.") + .addRoleOption(option => { + option.setName("when"); + option.setDescription("Check for the absence of this role."); + option.setRequired(true); + return option; + }) + .addRoleOption(option => { + option.setName("remove"); + option.setDescription("Remove this role if missing."); + option.setRequired(true); + return option; + }) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles); + + +/** + * @param {CommandInteraction} interaction + */ +module.exports.execute = async function(interaction) { + // Get settings + /** @type {Role} */ + const optWhen = interaction.options.getRole("when"); + /** @type {Role} */ + const optRemove = interaction.options.getRole("remove"); + const guildId = interaction.guildId; + + let roles = dbGet("roles", optWhen.id); + if (roles === null) { + roles = {} + roles.roleId = optWhen.id; + roles.roleName = optWhen.name; + roles.guildId = guildId; + roles.remove = {}; + } + + roles.remove[optRemove.id] = { + removeId: optRemove.id, + removeName: optRemove.name, + }; + + dbWrite("roles", optWhen.id, roles) + + // Done + return interaction.reply({ + content: `Will now automatically remove role <@&${optRemove.id}> from members without <@&${optWhen.id}>.`, + }); +}; diff --git a/commands/delete.js b/commands/delete.js new file mode 100644 index 0000000..2bf4275 --- /dev/null +++ b/commands/delete.js @@ -0,0 +1,56 @@ +const { CommandInteraction, SlashCommandBuilder, Role } = require("discord.js"); +const { dbGet, dbWrite, dbDelete } = require("../db"); +const { PermissionFlagsBits } = require('discord-api-types/v10'); + +module.exports.name = "role-remover-delete"; + +module.exports.data = new SlashCommandBuilder() + .setName(module.exports.name) + .setDescription("Disables a role to be auto removed.") + .addRoleOption(option => { + option.setName("when"); + option.setDescription("Check for the absence of this role."); + option.setRequired(true); + return option; + }) + .addRoleOption(option => { + option.setName("remove"); + option.setDescription("No longer remove this role if missing."); + option.setRequired(true); + return option; + }) + .setDefaultMemberPermissions(PermissionFlagsBits.ManageRoles); + + +/** + * @param {CommandInteraction} interaction + */ +module.exports.execute = async function(interaction) { + // Get settings + /** @type {Role} */ + const optWhen = interaction.options.getRole("when"); + /** @type {Role} */ + const optRemove = interaction.options.getRole("remove"); + + const settings = dbGet("roles", optWhen.id); + if (!settings) { + return interaction.reply({ + content: "No auto remove settings for this role.", + ephemeral: true, + }); + } + + delete settings.remove[optRemove.id]; + // Done + if (Object.keys(settings.remove).length === 0) { + dbDelete("roles", optWhen.id); + return interaction.reply({ + content: `Disabled all role removal for <@&${optWhen.id}>.`, + }); + } else { + dbWrite("roles", optWhen.id, settings) + return interaction.reply({ + content: `Will no longer automatically remove role <@&${optRemove.id}> from members without <@&${optWhen.id}>.`, + }); + } +}; diff --git a/commands/index.js b/commands/index.js new file mode 100644 index 0000000..bf55b0f --- /dev/null +++ b/commands/index.js @@ -0,0 +1,23 @@ +const fs = require('fs'); +const path = require('path'); + +const commands = {}; + +const basename = path.basename(__filename); +fs + .readdirSync(__dirname) + .filter(file => { + return ( + file.indexOf('.') !== 0 && + file !== basename && + file.slice(-3) === '.js' && + file.indexOf('.test.js') === -1 + ); + }) + .forEach(file => { + const command = require(path.join(__dirname, file)); + console.log('Found command', command) + commands[command.name] = command; + }); + +module.exports.commands = commands; diff --git a/config.js b/config.js new file mode 100644 index 0000000..7cb5201 --- /dev/null +++ b/config.js @@ -0,0 +1,15 @@ +const dotenv = require("dotenv"); + +dotenv.config(); +dotenv.config({ path: ".env.local", override: true }); + +const { DISCORD_TOKEN, DISCORD_CLIENT_ID } = process.env; + +if (!DISCORD_TOKEN || !DISCORD_CLIENT_ID) { + throw new Error("Missing environment variables"); +} + +module.exports = { + DISCORD_TOKEN, + DISCORD_CLIENT_ID, +}; diff --git a/data/roles/.gitkeep b/data/roles/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/db.js b/db.js new file mode 100644 index 0000000..4e9e696 --- /dev/null +++ b/db.js @@ -0,0 +1,157 @@ +const fs = require('fs'); +const path = require('path'); + +const cache = {}; + +function tableCache(table) { + let tableCache = cache[table]; + if (!tableCache) { + tableCache = {}; + cache[table] = tableCache; + } + return tableCache; +} + +function dbSerialize(data) { + return JSON.stringify(data, replacer, 2); +} +module.exports.dbSerialize = dbSerialize; + +function dbDeserialize(data) { + return JSON.parse(data, reviver); +} +module.exports.dbDeserialize = dbDeserialize; + +function dbDelete(table, id) { + const file = dbFile(table, id); + if (fs.existsSync(file)) { + fs.unlinkSync(file); + } + delete tableCache(table)[id]; +} +module.exports.dbDelete = dbDelete; + +function dbWrite(table, id, data) { + const file = dbFile(table, id); + fs.writeFileSync(file, dbSerialize(data)); + tableCache(table)[id] = data; +} +module.exports.dbWrite = dbWrite; + +/** + * Gets an entry from the database. + * @param {string} table The table name + * @param {string} id The record ID. + * @returns {object | null} The entry + */ +function dbGet(table, id) { + const file = dbFile(table, id); + let data = tableCache(table)[id]; + if (data === undefined) { + if (fs.existsSync(file)) { + data = dbDeserialize(fs.readFileSync(file)); + } else { + data = null; + } + tableCache(table)[id] = data; + } + return data; +} +module.exports.dbGet = dbGet; + +/** + * Gets all entires of a given table from the database. + * @param {string} table The table name + * @returns {object[]} The entries + */ +function dbGetAll(table) { + const files = fs.readdirSync(dbDir(table)); + const all = []; + for (const file of files) { + const id = file.split('.')[0]; + const data = dbGet(table, id); + if (!data) { + continue; + } + all.push(data); + } + return all; +} +module.exports.dbGetAll = dbGetAll; + +function dbDir(table) { + return path.join('data', dbSafe(table)); +} + +function dbFile(table, id) { + return path.join('data', dbSafe(table), `${dbSafe(id)}.json`); +} + +function dbSafe(str) { + return str.replace(/[^a-z0-9]/gi, '_').toLowerCase(); +} + +function reviver(key, value) { + if (typeof value === "object" && value != null) { + for (const customTypeName in customTypes) { + if (value.hasOwnProperty(customTypeName)) { + const customTypeValue = value[customTypeName]; + const customType = customTypes[customTypeName]; + return customType.reviver(customTypeValue); + } + } + } + return value; +} + +function replacer(key, value) { + const rawValue = this[key]; + for (const customTypeName in customTypes) { + const customType = customTypes[customTypeName]; + if (customType.condition(rawValue)) { + return { [customTypeName]: customType.replacer(rawValue) }; + } + } + return value; +} + +/** + * Custom types support for JSON files. Make sure that no custom type names conflict with actual possible JSON keys. + * + * When serializing: + * - Custom types work by running the `condition` function of every value to be serialized. + * - If it returns `true`, the `replacer` function is called with the value. + * - The return value of the `replacer` will be saved to JSON wrapped in an object using the key of the custom type. (e.g. `{"$myType": "hello"}`). + * + * When deserializing: + * - Each value is checked if it is an object with a key of any custom type. + * - If one is found, the `reviver` is called for the value of this key. + * - The return value is used as the actual value. + */ +const customTypes = { + $date: { + condition: function (value) { + return value instanceof Date; + }, + replacer: function (value) { + return value.toISOString(); + }, + reviver: function (value) { + return new Date(value); + }, + }, + $set: { + /** @param {Set} value */ + condition: function (value) { + return value instanceof Set; + }, + /** @param {Set} value */ + replacer: function (value) { + return [...value]; + }, + /** @param {Array} value */ + reviver: function (value) { + return new Set(value); + }, + }, +} \ No newline at end of file diff --git a/deploy-commands.js b/deploy-commands.js new file mode 100644 index 0000000..b8917c1 --- /dev/null +++ b/deploy-commands.js @@ -0,0 +1,48 @@ +const { REST, Routes, CommandInteraction } = require("discord.js"); +const { commands } = require("./commands"); +const config = require("./config"); + +const commandsData = Object.values(commands).map((command) => command.data); + +const rest = new REST({ version: "10" }).setToken(config.DISCORD_TOKEN); + +/** + * @typedef DeployCommandsProps + * @property {string} guildId + */ + +/** + * @param {DeployCommandsProps} + */ +module.exports.deployCommands = async function deployCommands({ guildId }) { + try { + console.log("Started refreshing commands in %s.", guildId); + + await rest.put( + Routes.applicationGuildCommands(config.DISCORD_CLIENT_ID, guildId), + { + body: commandsData, + } + ); + + console.log("Successfully reloaded commands in %s.", guildId); + } catch (error) { + console.error(error); + } +} + +/** + * @param {CommandInteraction} interaction + */ +module.exports.handleCommand = async function handleCommand(interaction) { + try { + /** @type {{commandName: keyof commands}} */ + const { commandName } = interaction; + console.log("Handle command %s in %s", commandName, interaction.guildId) + if (commands[commandName]) { + commands[commandName].execute(interaction); + } + } catch (error) { + console.error(error); + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..157d06b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.5' + +services: + imaptodiscord: + container_name: roleautoremover + image: kayleesachs/roleautoremover:latest + volumes: + - /root/roleautoremover/env:/roleautoremover/.env.local + - /root/roleautoremover/db:/roleautoremover/db + logging: + driver: journald + options: + tag: "{{.ImageName}}/{{.Name}}/{{.ID}}" + restart: unless-stopped diff --git a/index.js b/index.js new file mode 100644 index 0000000..77bc9f1 --- /dev/null +++ b/index.js @@ -0,0 +1,40 @@ +const { Client, GatewayIntentBits } = require("discord.js"); +const { deployCommands, handleCommand } = require("./deploy-commands"); +const config = require("./config"); +const { maybeUpdateRoles } = require("./logic"); + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, + ], +}); + +client.once("clientReady", () => { + console.log("Discord bot is ready! 🤖"); +}); + +client.on("guildAvailable", async (guild) => { + await deployCommands({ guildId: guild.id }); +}); + +client.on('messageCreate', async (message) => { + if (message.channel != null) { + maybeRepost(message.channel, message).catch(console.error); + } +}); + +client.on("guildMemberUpdate", async (oldMember, newMember) => { + await maybeUpdateRoles(oldMember, newMember); +}); + +client.on("interactionCreate", async (interaction) => { + if (interaction.isCommand()) { + try { + await handleCommand(interaction); + } catch (error) { + console.error(error); + } + } +}); +client.login(config.DISCORD_TOKEN); diff --git a/logic.js b/logic.js new file mode 100644 index 0000000..611a863 --- /dev/null +++ b/logic.js @@ -0,0 +1,42 @@ +const { GuildMember } = require("discord.js"); +const { dbGetAll } = require("./db"); + +/** + * Checks and removes roles. + * @param {GuildMember} oldMember The old member. + * @param {GuildMember?} newMember The new member. + * @returns {Promise} Once done + */ +async function maybeUpdateRoles(oldMember, newMember) { + const settings = dbGetAll("roles") + for (const setting of settings) { + if (setting.guildId !== newMember.guild.id) { + continue; + } + + // Still has the role + if (newMember.roles.cache.has(setting.roleId)) { + continue; + } + // The user never had the role + if (!oldMember.roles.cache.has(setting.roleId)) { + continue; + } + + for (const removeSetting in setting.remove) { + const role = newMember.roles.cache.get(removeSetting); + if (!role) { + continue; + } + + try { + await newMember.roles.remove(role, "Auto remover: " + setting.roleName); + } catch(error) { + console.log(`Failed to remove ${role} from ${newMember}`, error); + } + } + } +} + + +module.exports = { maybeUpdateRoles }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..bfe4a1c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,321 @@ +{ + "name": "DiscordRoleAutoRemover", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "discord.js": "^14.16.2", + "dotenv": "^17.2.2" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.11.3.tgz", + "integrity": "sha512-p3kf5eV49CJiRTfhtutUCeivSyQ/l2JlKodW1ZquRwwvlOWmG9+6jFShX6x8rUiYhnP6wKI96rgN/SXMy5e5aw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.1", + "@discordjs/util": "^1.1.1", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.16", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz", + "integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.1" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", + "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.16", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", + "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@types/node": { + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", + "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/discord-api-types": { + "version": "0.38.26", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.26.tgz", + "integrity": "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, + "node_modules/discord.js": { + "version": "14.22.1", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.22.1.tgz", + "integrity": "sha512-3k+Kisd/v570Jr68A1kNs7qVhNehDwDJAPe4DZ2Syt+/zobf9zEcuYFvsfIaAOgCa0BiHMfOOKQY4eYINl0z7w==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/builders": "^1.11.2", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.1", + "@discordjs/rest": "^2.6.0", + "@discordjs/util": "^1.1.1", + "@discordjs/ws": "^1.2.3", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.38.16", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" + }, + "node_modules/magic-bytes.js": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", + "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", + "license": "MIT" + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4ec98eb --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "discord.js": "^14.16.2", + "dotenv": "^17.2.2" + } +}