Initial commit

This commit is contained in:
Kaylee Sachs 2025-09-29 03:04:38 +02:00
commit 3b3068efaa
18 changed files with 850 additions and 0 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
node_modules/
.env.local
.git
.gitignore

3
.env Normal file
View file

@ -0,0 +1,3 @@
DISCORD_TOKEN=
DISCORD_CLIENT_ID=
# permissions for bot : 412854249536

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# Configuratrion
.env.local
# Libraries
node_modules/
# Save data
data/**/*.json

17
.vscode/launch.json vendored Normal file
View file

@ -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": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\index.js"
}
]
}

16
Dockerfile Normal file
View file

@ -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"]

24
commands/check.js Normal file
View file

@ -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```',
});
};

56
commands/create.js Normal file
View file

@ -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}>.`,
});
};

56
commands/delete.js Normal file
View file

@ -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}>.`,
});
}
};

23
commands/index.js Normal file
View file

@ -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;

15
config.js Normal file
View file

@ -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,
};

0
data/roles/.gitkeep Normal file
View file

157
db.js Normal file
View file

@ -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);
},
},
}

48
deploy-commands.js Normal file
View file

@ -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);
}
}

14
docker-compose.yml Normal file
View file

@ -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

40
index.js Normal file
View file

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

42
logic.js Normal file
View file

@ -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<void>} 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 };

321
package-lock.json generated Normal file
View file

@ -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
}
}
}
}
}

6
package.json Normal file
View file

@ -0,0 +1,6 @@
{
"dependencies": {
"discord.js": "^14.16.2",
"dotenv": "^17.2.2"
}
}