Compare commits

...

10 Commits

Author SHA1 Message Date
hllywluis 94494c1ca5 feat: update AI model in AskCommand and add Docker support files for improved deployment 2025-12-17 19:31:03 -05:00
hllywluis 0cd3b5c179 fix: standardize command import paths to lowercase for consistency 2025-05-01 17:56:55 -04:00
hllywluis 748d438706 fix: update @babel/runtime to version 7.27.0 for improved stability and performance 2025-04-19 19:37:32 -04:00
hllywluis e5124d33d4 chore: update ESLint configuration and dependencies
- Reordered ESLint extends in .eslintrc.js for better clarity.
- Updated ESLint version from 9.23.0 to 8.57.1 in package.json and package-lock.json.
- Adjusted import/extensions rule to ignorePackages in .eslintrc.js.
- Updated various dependencies in package-lock.json to maintain compatibility and improve performance.
2025-04-19 19:36:10 -04:00
hllywluis 5bc0d98334 refactor: implement command class structure for better organization and error handling; update existing commands to extend from the new base class 2025-03-25 11:17:26 -04:00
hllywluis 69dc616668 fix: update AI assistant description for clarity and accuracy; bump dependencies for improved performance and security 2025-03-24 20:11:02 -04:00
hllywluis d4e01a3cab Merge branch 'master' of https://github.com/hllywluis/kekbot-js 2025-03-18 14:38:15 -04:00
hllywluis bff1625c29 Switch to using free Gemma model 2025-03-18 14:36:52 -04:00
hllywluis bf6dcae288 Create PRIVACY.md 2025-03-12 11:22:38 -04:00
hllywluis 99ceb22019 Create TERMS.md 2025-03-12 11:19:50 -04:00
19 changed files with 785 additions and 409 deletions
+27
View File
@@ -0,0 +1,27 @@
node_modules
npm-debug.log
coverage
.git
.gitignore
.env
.env.local
.env.*.local
*.md
README.md
LICENSE
TESTING.md
PRIVACY.md
TERMS.md
SECURITY.md
ansible-README.md
__tests__
jest.config.js
jest.setup.js
.eslintrc.*
.prettierrc
.vscode
.idea
*.log
.DS_Store
inventory.ini
playbook.yml
+4 -4
View File
@@ -7,12 +7,12 @@ module.exports = {
es6: true,
},
extends: [
'airbnb-base',
'eslint:recommended',
'prettier',
'plugin:prettier/recommended',
'airbnb-base',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:prettier/recommended',
'prettier',
],
parserOptions: {
ecmaVersion: 2023,
@@ -30,7 +30,7 @@ module.exports = {
'arrow-body-style': ['error', 'as-needed'],
'import/extensions': [
'error',
'always',
'ignorePackages',
{
js: 'never',
jsx: 'never',
+155
View File
@@ -0,0 +1,155 @@
# Docker Setup Guide
## Prerequisites
- Docker installed on your system
- Docker Compose (optional, but recommended)
## Configuration
1. Create a `.env` file in the project root with your Discord bot credentials:
```bash
cp .env.example .env
```
2. Edit the `.env` file and add your Discord bot token and other credentials:
```
DISCORD_TOKEN=your_discord_bot_token_here
CLIENT_ID=your_client_id_here
OPENROUTER_API_KEY=your_openrouter_api_key_here
```
## Running with Docker Compose (Recommended)
### Start the bot:
```bash
docker-compose up -d
```
### View logs:
```bash
docker-compose logs -f
```
### Stop the bot:
```bash
docker-compose down
```
### Restart the bot:
```bash
docker-compose restart
```
### Rebuild and start (after code changes):
```bash
docker-compose up -d --build
```
## Running with Docker directly
### Build the image:
```bash
docker build -t kekbot:latest .
```
### Run the container:
```bash
docker run -d \
--name kekbot \
--env-file .env \
--restart unless-stopped \
kekbot:latest
```
### View logs:
```bash
docker logs -f kekbot
```
### Stop the container:
```bash
docker stop kekbot
docker rm kekbot
```
## Deploy Commands
Before running the bot for the first time, you may need to deploy slash commands:
```bash
# Using Docker Compose
docker-compose run --rm kekbot npm run deploy
# Using Docker directly
docker run --rm --env-file .env kekbot:latest npm run deploy
```
## Troubleshooting
### Check if container is running:
```bash
docker ps
```
### View all containers (including stopped):
```bash
docker ps -a
```
### Access container shell:
```bash
docker exec -it kekbot sh
```
### View container resource usage:
```bash
docker stats kekbot
```
## Production Considerations
1. **Security**: Ensure your `.env` file is never committed to version control
2. **Logging**: Configure log rotation if using volume mounts for logs
3. **Updates**: Regularly update the base image and dependencies
4. **Monitoring**: Consider adding monitoring tools like Prometheus or Grafana
5. **Backups**: If you add persistent data, implement backup strategies
## Multi-stage Build (Alternative)
For a smaller production image, you can use the multi-stage Dockerfile variant:
```dockerfile
# Development stage
FROM node:20-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# Production stage
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
USER nodejs
CMD ["npm", "start"]
```
+28
View File
@@ -0,0 +1,28 @@
# Use Node.js LTS version
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production
# Copy application files
COPY . .
# Create a non-root user to run the application
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001 && \
chown -R nodejs:nodejs /app
# Switch to non-root user
USER nodejs
# Expose port (if needed for health checks)
EXPOSE 3000
# Start the bot
CMD ["npm", "start"]
+17
View File
@@ -0,0 +1,17 @@
# Privacy Policy
**Effective Date:** March 12, 2025
**Kekbot-js** ("we," "our," or "us") is committed to respecting your privacy. This Privacy Policy outlines our practices regarding the collection, use, and disclosure of information when you use our software.
**Information Collection and Use**
We do not collect, store, or process any personal information from users of **Kekbot-js**.
**Changes to This Privacy Policy**
We may update our Privacy Policy from time to time. Any changes will be reflected on this page, and we encourage you to review this policy periodically.
**Contact Us**
If you have any questions or concerns about this Privacy Policy, please contact us at luis@kleptonix.com.
+9
View File
@@ -0,0 +1,9 @@
# Terms and Conditions
**Kekbot-js** is an open-source project licensed under the [GNU General Public License v3.0](LICENSE). By using, copying, modifying, or distributing this software, you agree to the terms and conditions of this license.
**Disclaimer of Warranty**
**Kekbot-js** is provided "as-is," without any express or implied warranty. In no event shall the authors or copyright holders be held liable for any damages arising from the use of this software.
For detailed licensing information, please refer to the [LICENSE](LICENSE) file in this repository.
+16 -10
View File
@@ -53,18 +53,24 @@ client.on(Events.InteractionCreate, async interaction => {
try {
await command.execute(interaction);
} catch (error) {
logger.error(error);
if (interaction.replied || interaction.deferred) {
await interaction.followUp({
content: 'There was an error while executing this command!',
// Log detailed error context
logger.error(
`Command "${interaction.commandName}" failed for user ${interaction.user.tag}:`,
error,
);
// Prepare error response
const isProduction = process.env.NODE_ENV === 'production';
const errorMessage = isProduction
? `❌ Command failed. Please try again later.`
: `❌ Command failed: ${error.message}\n\n${error.stack}`;
const responseMethod = interaction.replied || interaction.deferred ? 'followUp' : 'reply';
await interaction[responseMethod]({
content: errorMessage,
ephemeral: true,
});
} else {
await interaction.reply({
content: 'There was an error while executing this command!',
ephemeral: true,
});
}
}
});
+20 -53
View File
@@ -8,6 +8,7 @@
import { SlashCommandBuilder } from 'discord.js';
import axios from 'axios';
import Command from '../utils/command.js';
const config = {
webSearch: {
@@ -16,8 +17,9 @@ const config = {
},
};
export default {
data: new SlashCommandBuilder()
export default class AskCommand extends Command {
defineCommand() {
return new SlashCommandBuilder()
.setName('ask')
.setDescription('Ask a question to the AI')
.addStringOption(option =>
@@ -28,9 +30,10 @@ export default {
.setName('websearch')
.setDescription('Enable web search for more up-to-date information')
.setRequired(false),
),
);
}
async execute(interaction) {
async run(interaction) {
await interaction.deferReply();
const prompt = interaction.options.getString('prompt');
@@ -42,20 +45,15 @@ export default {
? userWebSearchOption // Use user's choice if override is allowed and option was provided
: config.webSearch.enabled; // Otherwise use default config
try {
const response = await axios.post(
'https://openrouter.ai/api/v1/chat/completions',
{
model: webSearchEnabled
? 'google/gemini-2.0-flash-001:online'
: 'google/gemini-2.0-flash-001',
model: webSearchEnabled ? 'xiaomi/mimo-v2-flash:free:online' : 'xiaomi/mimo-v2-flash:free',
messages: [
{
role: 'system',
content:
'You are a helpful AI assistant. Provide clear, concise, and accurate responses. ' +
'Keep your answers brief while ensuring they are informative and to the point. ' +
'Avoid unnecessary elaboration or repetition. ',
'You are kekbot, a highly celebrated and knowledgeable computer scientist with decades of experience in various fields of computing. You are known for your ability to explain complex topics in a clear, concise, and insightful manner. Provide direct and to-the-point answers, avoiding unnecessary elaboration or repetition. Focus on delivering accurate and valuable information efficiently.',
},
{ role: 'user', content: prompt },
],
@@ -132,54 +130,23 @@ export default {
await chunks.reduce(
(promise, chunk) =>
promise.then(async () => {
try {
await interaction.followUp(chunk);
return undefined;
} catch (error) {
console.error('Error sending message chunk:', {
chunkLength: chunk.content.length,
error: error.message,
});
// Attempt to send error notification
try {
await interaction.followUp({
content: 'Failed to send complete response. Please try again.',
ephemeral: true,
});
} catch (e) {
// If even the error notification fails, log it
console.error('Failed to send error notification:', e.message);
}
// Reject to stop processing remaining chunks
return Promise.reject(error);
}
}),
Promise.resolve(),
);
}
} catch (error) {
// Log detailed error information
console.error('Error in ask command:', {
message: error.message,
response: error.response?.data,
status: error.response?.status,
stack: error.stack,
});
}
// Provide more specific error messages to users
let errorMessage = 'Sorry, there was an error processing your request.';
getErrorMessage(error) {
if (error.response?.status === 429) {
errorMessage = 'The AI service is currently busy. Please try again in a few moments.';
} else if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
errorMessage = 'The request timed out. Please try again.';
} else if (error.response?.status === 400) {
errorMessage = 'Invalid request. Please try rephrasing your question.';
return 'The AI service is currently busy. Please try again in a few moments.';
}
await interaction.followUp({
content: errorMessage,
ephemeral: true,
});
if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
return 'The request timed out. Please try again.';
}
},
};
if (error.response?.status === 400) {
return 'Invalid request. Please try rephrasing your question.';
}
return 'Sorry, there was an error processing your request.';
}
}
+9 -5
View File
@@ -7,10 +7,14 @@
// (at your option) any later version.
import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
import Command from '../utils/command.js';
export default {
data: new SlashCommandBuilder().setName('help').setDescription('Lists all available commands'),
async execute(interaction) {
export default class HelpCommand extends Command {
defineCommand() {
return new SlashCommandBuilder().setName('help').setDescription('Lists all available commands');
}
async run(interaction) {
const { commands } = interaction.client;
const helpEmbed = new EmbedBuilder()
.setColor('#5dc67b')
@@ -26,5 +30,5 @@ export default {
});
await interaction.reply({ embeds: [helpEmbed], ephemeral: true });
},
};
}
}
+12 -25
View File
@@ -7,49 +7,36 @@
// (at your option) any later version.
import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
import Command from '../utils/command.js';
// eslint-disable-next-line import/extensions
import logger from '../logger.js';
export default {
data: new SlashCommandBuilder()
export default class KickCommand extends Command {
defineCommand() {
return new SlashCommandBuilder()
.setName('kick')
.setDescription('Kick a user from the server')
.addUserOption(option =>
option.setName('target').setDescription('The user to kick').setRequired(true),
)
.addStringOption(option => option.setName('reason').setDescription('Reason for kicking'))
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers),
async execute(interaction) {
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers);
}
async run(interaction) {
const target = interaction.options.getMember('target');
const reason = interaction.options.getString('reason') ?? 'No reason provided';
if (!target) {
return interaction.reply({
content: 'That user is not in this server!',
ephemeral: true,
});
throw new Error('That user is not in this server!');
}
if (!target.kickable) {
return interaction.reply({
content: 'I cannot kick this user! They may have higher permissions than me.',
ephemeral: true,
});
throw new Error('I cannot kick this user! They may have higher permissions than me.');
}
try {
await target.kick(reason);
return await interaction.reply({
await interaction.reply({
content: `Successfully kicked ${target.user.tag}\nReason: ${reason}`,
ephemeral: true,
});
} catch (error) {
logger.error(error);
return interaction.reply({
content: 'There was an error trying to kick this user!',
ephemeral: true,
});
}
},
};
}
+9 -5
View File
@@ -7,10 +7,14 @@
// (at your option) any later version.
import { SlashCommandBuilder } from 'discord.js';
import Command from '../utils/command.js';
export default {
data: new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'),
async execute(interaction) {
export default class PingCommand extends Command {
defineCommand() {
return new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!');
}
async run(interaction) {
await interaction.reply('Pong! 🏓');
},
};
}
}
+9 -14
View File
@@ -7,9 +7,11 @@
// (at your option) any later version.
import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
import Command from '../utils/command.js';
export default {
data: new SlashCommandBuilder()
export default class PruneCommand extends Command {
defineCommand() {
return new SlashCommandBuilder()
.setName('prune')
.setDescription('Prune up to 99 messages.')
.addIntegerOption(option =>
@@ -20,22 +22,15 @@ export default {
.setMaxValue(99)
.setRequired(true),
)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
async execute(interaction) {
const amount = interaction.options.getInteger('amount');
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages);
}
try {
async run(interaction) {
const amount = interaction.options.getInteger('amount');
const deleted = await interaction.channel.bulkDelete(amount, true);
await interaction.reply({
content: `Successfully deleted ${deleted.size} message(s).`,
ephemeral: true,
});
} catch (error) {
console.error(error);
await interaction.reply({
content: 'There was an error trying to prune messages in this channel!',
ephemeral: true,
});
}
},
};
}
+24
View File
@@ -0,0 +1,24 @@
version: '3.8'
services:
kekbot:
build:
context: .
dockerfile: Dockerfile
container_name: kekbot
restart: unless-stopped
env_file:
- .env
volumes:
# Mount logs directory if you want to persist logs
- ./logs:/app/logs
# Uncomment if you need to expose a port for health checks
# ports:
# - "3000:3000"
# Health check (optional)
healthcheck:
test: ["CMD", "node", "-e", "process.exit(0)"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
+56 -5
View File
@@ -1,28 +1,79 @@
import chalk from 'chalk';
/**
* Enhanced logger utility with timestamps and consistent formatting
*/
class Logger {
/**
* Create a new Logger instance
* @param {string} moduleName - Name of the module for context
*/
constructor(moduleName) {
if (typeof moduleName !== 'string') {
throw new Error('Logger requires a string moduleName');
}
this.moduleName = moduleName;
}
/**
* Get formatted timestamp
* @private
*/
#getTimestamp() {
return new Date().toISOString();
}
/**
* Format log message with consistent structure
* @private
* @param {string} emoji - Log level emoji
* @param {string} level - Log level name
* @param {string} message - Message to log
*/
#formatMessage(emoji, level, message) {
const timestamp = this.#getTimestamp();
return `${chalk.gray(timestamp)} ${emoji} ${chalk.cyan(`[${this.moduleName}]`)} ${level}: ${message}`;
}
/**
* Log informational message
* @param {string} message - Message to log
*/
log(message) {
console.log(`${chalk.blue('📝')} ${chalk.blue(`[${this.moduleName}]`)} ${message}`);
console.log(this.#formatMessage(chalk.blue('📝'), 'LOG', message));
}
/**
* Log error message
* @param {string|Error} message - Error message or Error object
*/
error(message) {
console.error(`${chalk.red('❌')} ${chalk.red(`[${this.moduleName}]`)} ${message}`);
const msg = message instanceof Error ? message.stack || message.message : message;
console.error(this.#formatMessage(chalk.red('❌'), 'ERROR', msg));
}
/**
* Log warning message
* @param {string} message - Warning message
*/
warn(message) {
console.warn(`${chalk.yellow('⚠️')} ${chalk.yellow(`[${this.moduleName}]`)} ${message}`);
console.warn(this.#formatMessage(chalk.yellow('⚠️'), 'WARN', message));
}
/**
* Log info message
* @param {string} message - Info message
*/
info(message) {
console.info(`${chalk.green('️')} ${chalk.green(`[${this.moduleName}]`)} ${message}`);
console.info(this.#formatMessage(chalk.green('️'), 'INFO', message));
}
/**
* Log debug message
* @param {string} message - Debug message
*/
debug(message) {
console.debug(`${chalk.gray('🔧')} ${chalk.gray(`[${this.moduleName}]`)} ${message}`);
console.debug(this.#formatMessage(chalk.gray('🔧'), 'DEBUG', message));
}
}
+149 -149
View File
@@ -9,23 +9,23 @@
"version": "1.0.0",
"license": "GPL-3.0",
"dependencies": {
"axios": "^1.7.9",
"axios": "^1.8.4",
"chalk": "^5.4.1",
"discord.js": "^14.17.3",
"dotenv": "^16.3.1"
"discord.js": "^14.18.0",
"dotenv": "^16.4.7"
},
"devDependencies": {
"@babel/core": "^7.26.7",
"@babel/preset-env": "^7.26.7",
"@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@types/jest": "^29.5.14",
"babel-jest": "^29.7.0",
"eslint": "^8.57.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^10.0.1",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-prettier": "^5.2.4",
"jest": "^29.7.0",
"prettier": "^3.4.2"
"prettier": "^3.5.3"
}
},
"node_modules/@ampproject/remapping": {
@@ -58,9 +58,9 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz",
"integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==",
"version": "7.26.8",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz",
"integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -68,22 +68,22 @@
}
},
"node_modules/@babel/core": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz",
"integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==",
"version": "7.26.10",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.26.5",
"@babel/generator": "^7.26.10",
"@babel/helper-compilation-targets": "^7.26.5",
"@babel/helper-module-transforms": "^7.26.0",
"@babel/helpers": "^7.26.7",
"@babel/parser": "^7.26.7",
"@babel/template": "^7.25.9",
"@babel/traverse": "^7.26.7",
"@babel/types": "^7.26.7",
"@babel/helpers": "^7.26.10",
"@babel/parser": "^7.26.10",
"@babel/template": "^7.26.9",
"@babel/traverse": "^7.26.10",
"@babel/types": "^7.26.10",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -99,14 +99,14 @@
}
},
"node_modules/@babel/generator": {
"version": "7.26.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz",
"integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
"integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.26.5",
"@babel/types": "^7.26.5",
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@@ -367,27 +367,27 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz",
"integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.25.9",
"@babel/types": "^7.26.7"
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
"integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.7"
"@babel/types": "^7.27.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -782,15 +782,15 @@
}
},
"node_modules/@babel/plugin-transform-async-generator-functions": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz",
"integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==",
"version": "7.26.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz",
"integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
"@babel/helper-plugin-utils": "^7.26.5",
"@babel/helper-remap-async-to-generator": "^7.25.9",
"@babel/traverse": "^7.25.9"
"@babel/traverse": "^7.26.8"
},
"engines": {
"node": ">=6.9.0"
@@ -1036,13 +1036,13 @@
}
},
"node_modules/@babel/plugin-transform-for-of": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz",
"integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.26.9.tgz",
"integrity": "sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9",
"@babel/helper-plugin-utils": "^7.26.5",
"@babel/helper-skip-transparent-expression-wrappers": "^7.25.9"
},
"engines": {
@@ -1504,13 +1504,13 @@
}
},
"node_modules/@babel/plugin-transform-template-literals": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz",
"integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==",
"version": "7.26.8",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.26.8.tgz",
"integrity": "sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.25.9"
"@babel/helper-plugin-utils": "^7.26.5"
},
"engines": {
"node": ">=6.9.0"
@@ -1603,13 +1603,13 @@
}
},
"node_modules/@babel/preset-env": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.7.tgz",
"integrity": "sha512-Ycg2tnXwixaXOVb29rana8HNPgLVBof8qqtNQ9LE22IoyZboQbGSxI6ZySMdW3K5nAe6gu35IaJefUJflhUFTQ==",
"version": "7.26.9",
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz",
"integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.26.5",
"@babel/compat-data": "^7.26.8",
"@babel/helper-compilation-targets": "^7.26.5",
"@babel/helper-plugin-utils": "^7.26.5",
"@babel/helper-validator-option": "^7.25.9",
@@ -1623,7 +1623,7 @@
"@babel/plugin-syntax-import-attributes": "^7.26.0",
"@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
"@babel/plugin-transform-arrow-functions": "^7.25.9",
"@babel/plugin-transform-async-generator-functions": "^7.25.9",
"@babel/plugin-transform-async-generator-functions": "^7.26.8",
"@babel/plugin-transform-async-to-generator": "^7.25.9",
"@babel/plugin-transform-block-scoped-functions": "^7.26.5",
"@babel/plugin-transform-block-scoping": "^7.25.9",
@@ -1638,7 +1638,7 @@
"@babel/plugin-transform-dynamic-import": "^7.25.9",
"@babel/plugin-transform-exponentiation-operator": "^7.26.3",
"@babel/plugin-transform-export-namespace-from": "^7.25.9",
"@babel/plugin-transform-for-of": "^7.25.9",
"@babel/plugin-transform-for-of": "^7.26.9",
"@babel/plugin-transform-function-name": "^7.25.9",
"@babel/plugin-transform-json-strings": "^7.25.9",
"@babel/plugin-transform-literals": "^7.25.9",
@@ -1666,7 +1666,7 @@
"@babel/plugin-transform-shorthand-properties": "^7.25.9",
"@babel/plugin-transform-spread": "^7.25.9",
"@babel/plugin-transform-sticky-regex": "^7.25.9",
"@babel/plugin-transform-template-literals": "^7.25.9",
"@babel/plugin-transform-template-literals": "^7.26.8",
"@babel/plugin-transform-typeof-symbol": "^7.26.7",
"@babel/plugin-transform-unicode-escapes": "^7.25.9",
"@babel/plugin-transform-unicode-property-regex": "^7.25.9",
@@ -1674,9 +1674,9 @@
"@babel/plugin-transform-unicode-sets-regex": "^7.25.9",
"@babel/preset-modules": "0.1.6-no-external-plugins",
"babel-plugin-polyfill-corejs2": "^0.4.10",
"babel-plugin-polyfill-corejs3": "^0.10.6",
"babel-plugin-polyfill-corejs3": "^0.11.0",
"babel-plugin-polyfill-regenerator": "^0.6.1",
"core-js-compat": "^3.38.1",
"core-js-compat": "^3.40.0",
"semver": "^6.3.1"
},
"engines": {
@@ -1702,9 +1702,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz",
"integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1715,32 +1715,32 @@
}
},
"node_modules/@babel/template": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.25.9",
"@babel/parser": "^7.25.9",
"@babel/types": "^7.25.9"
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz",
"integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
"integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.26.2",
"@babel/generator": "^7.26.5",
"@babel/parser": "^7.26.7",
"@babel/template": "^7.25.9",
"@babel/types": "^7.26.7",
"@babel/generator": "^7.27.0",
"@babel/parser": "^7.27.0",
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -1749,9 +1749,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
"integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1770,15 +1770,15 @@
"license": "MIT"
},
"node_modules/@discordjs/builders": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.0.tgz",
"integrity": "sha512-ikVZsZP+3shmVJ5S1oM+7SveUCK3L9fTyfA8aJ7uD9cNQlTqF+3Irbk2Y22KXTb3C3RNUahRkSInClJMkHrINg==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.1.tgz",
"integrity": "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/formatters": "^0.6.0",
"@discordjs/util": "^1.1.1",
"@sapphire/shapeshift": "^4.0.0",
"discord-api-types": "^0.37.114",
"discord-api-types": "^0.37.119",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.4",
"tslib": "^2.6.3"
@@ -1815,9 +1815,9 @@
}
},
"node_modules/@discordjs/rest": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.2.tgz",
"integrity": "sha512-9bOvXYLQd5IBg/kKGuEFq3cstVxAMJ6wMxO2U3wjrgO+lHv8oNCT+BBRpuzVQh7BoXKvk/gpajceGvQUiRoJ8g==",
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.3.tgz",
"integrity": "sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.1",
@@ -1825,10 +1825,10 @@
"@sapphire/async-queue": "^1.5.3",
"@sapphire/snowflake": "^3.5.3",
"@vladfrangu/async_event_emitter": "^2.4.6",
"discord-api-types": "^0.37.114",
"discord-api-types": "^0.37.119",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.19.8"
"undici": "6.21.1"
},
"engines": {
"node": ">=18"
@@ -1862,18 +1862,18 @@
}
},
"node_modules/@discordjs/ws": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.0.tgz",
"integrity": "sha512-QH5CAFe3wHDiedbO+EI3OOiyipwWd+Q6BdoFZUw/Wf2fw5Cv2fgU/9UEtJRmJa9RecI+TAhdGPadMaEIur5yJg==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.1.tgz",
"integrity": "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.0",
"@discordjs/rest": "^2.4.1",
"@discordjs/rest": "^2.4.3",
"@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.37.114",
"discord-api-types": "^0.37.119",
"tslib": "^2.6.2",
"ws": "^8.17.0"
},
@@ -2572,9 +2572,9 @@
}
},
"node_modules/@pkgr/core": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz",
"integrity": "sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2768,9 +2768,9 @@
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz",
"integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
@@ -2811,9 +2811,9 @@
}
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"dev": true,
"license": "MIT",
"bin": {
@@ -3068,9 +3068,9 @@
}
},
"node_modules/axios": {
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -3166,14 +3166,14 @@
}
},
"node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.10.6",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz",
"integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==",
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz",
"integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.2",
"core-js-compat": "^3.38.0"
"@babel/helper-define-polyfill-provider": "^0.6.3",
"core-js-compat": "^3.40.0"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -3540,13 +3540,13 @@
"license": "MIT"
},
"node_modules/core-js-compat": {
"version": "3.40.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz",
"integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==",
"version": "3.41.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz",
"integrity": "sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==",
"dev": true,
"license": "MIT",
"dependencies": {
"browserslist": "^4.24.3"
"browserslist": "^4.24.4"
},
"funding": {
"type": "opencollective",
@@ -3783,23 +3783,23 @@
"license": "MIT"
},
"node_modules/discord.js": {
"version": "14.17.3",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.17.3.tgz",
"integrity": "sha512-8/j8udc3CU7dz3Eqch64UaSHoJtUT6IXK4da5ixjbav4NAXJicloWswD/iwn1ImZEMoAV3LscsdO0zhBh6H+0Q==",
"version": "14.18.0",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.18.0.tgz",
"integrity": "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/builders": "^1.10.0",
"@discordjs/builders": "^1.10.1",
"@discordjs/collection": "1.5.3",
"@discordjs/formatters": "^0.6.0",
"@discordjs/rest": "^2.4.2",
"@discordjs/rest": "^2.4.3",
"@discordjs/util": "^1.1.1",
"@discordjs/ws": "^1.2.0",
"@discordjs/ws": "^1.2.1",
"@sapphire/snowflake": "3.5.3",
"discord-api-types": "^0.37.114",
"discord-api-types": "^0.37.119",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
"tslib": "^2.6.3",
"undici": "6.19.8"
"undici": "6.21.1"
},
"engines": {
"node": ">=18"
@@ -4126,13 +4126,13 @@
}
},
"node_modules/eslint-config-prettier": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz",
"integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==",
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz",
"integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==",
"dev": true,
"license": "MIT",
"bin": {
"eslint-config-prettier": "build/bin/cli.js"
"eslint-config-prettier": "bin/cli.js"
},
"peerDependencies": {
"eslint": ">=7.0.0"
@@ -4246,14 +4246,14 @@
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.3.tgz",
"integrity": "sha512-qJ+y0FfCp/mQYQ/vWQ3s7eUlFEL4PyKfAJxsnYTJ4YT73nsJBWqmEpFryxV9OeUiqmsTsYJ5Y+KDNaeP31wrRw==",
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.4.tgz",
"integrity": "sha512-SFtuYmnhwYCtuCDTKPoK+CEzCnEgKTU2qTLwoCxvrC0MFBTIXo1i6hDYOI4cwHaE5GZtlWmTN3YfucYi7KJwPw==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.0",
"synckit": "^0.9.1"
"synckit": "^0.10.2"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -4590,9 +4590,9 @@
"license": "MIT"
},
"node_modules/fastq": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz",
"integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==",
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -4665,9 +4665,9 @@
}
},
"node_modules/flatted": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true,
"license": "ISC"
},
@@ -7250,9 +7250,9 @@
}
},
"node_modules/prettier": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
@@ -7578,9 +7578,9 @@
}
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8068,14 +8068,14 @@
}
},
"node_modules/synckit": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz",
"integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==",
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.10.3.tgz",
"integrity": "sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.1.0",
"tslib": "^2.6.2"
"@pkgr/core": "^0.2.0",
"tslib": "^2.8.1"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -8308,9 +8308,9 @@
}
},
"node_modules/undici": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz",
"integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==",
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
"license": "MIT",
"engines": {
"node": ">=18.17"
@@ -8586,9 +8586,9 @@
}
},
"node_modules/ws": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
+10 -9
View File
@@ -9,28 +9,29 @@
"deploy": "node deploy-commands.js",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
"test:coverage": "jest --coverage",
"deploy:ansible": "ansible-playbook -i inventory.ini playbook.yml"
},
"keywords": [],
"author": "",
"license": "GPL-3.0",
"dependencies": {
"axios": "^1.7.9",
"axios": "^1.8.4",
"chalk": "^5.4.1",
"discord.js": "^14.17.3",
"dotenv": "^16.3.1"
"discord.js": "^14.18.0",
"dotenv": "^16.4.7"
},
"devDependencies": {
"@babel/core": "^7.26.7",
"@babel/preset-env": "^7.26.7",
"@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@types/jest": "^29.5.14",
"babel-jest": "^29.7.0",
"eslint": "^8.57.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^10.0.1",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-prettier": "^5.2.4",
"jest": "^29.7.0",
"prettier": "^3.4.2"
"prettier": "^3.5.3"
}
}
+13 -1
View File
@@ -4,12 +4,23 @@
become: yes # This enables sudo privileges
vars:
app_dir: /opt/kekbot
node_version: "20.x" # Latest LTS version
node_version: "22.x" # Latest LTS version
tasks:
- name: Check if Node.js, npm, and git are installed
command: "which {{ item }}"
register: check_commands
with_items:
- node
- npm
- git
ignore_errors: yes
changed_when: false
- name: Install Node.js repository
shell: |
curl -fsSL https://deb.nodesource.com/setup_{{ node_version }} | bash -
when: check_commands.results | select('failed') | list | length > 0
- name: Install Node.js, npm, and git
apt:
@@ -19,6 +30,7 @@
- git
state: present
update_cache: yes
when: check_commands.results | select('failed') | list | length > 0
- name: Create application directory
file:
+69
View File
@@ -0,0 +1,69 @@
// utils/command.js - Base command class for Discord bot commands
// Copyright (C) 2025 Luis Bauza
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
import { SlashCommandBuilder } from 'discord.js';
import logger from '../logger.js';
export default class Command {
constructor() {
if (this.constructor === Command) {
throw new Error('Abstract class Command cannot be instantiated');
}
this._data = this.defineCommand();
this._logger = new logger(this.constructor.name);
}
get data() {
return this._data;
}
// Abstract method - must be implemented by subclasses
defineCommand() {
throw new Error('Method defineCommand() must be implemented');
}
// Common execute method with standardized error handling
async execute(interaction) {
try {
this._logger.info(`Executing command: ${interaction.commandName}`);
await this.run(interaction);
} catch (error) {
this.handleError(interaction, error);
}
}
// Abstract method - must be implemented by subclasses
async run(interaction) {
throw new Error('Method run() must be implemented');
}
// Standardized error handling
handleError(interaction, error) {
this._logger.error(`Command ${interaction.commandName} failed:`, error);
const response = interaction.deferred ? 'followUp' : 'reply';
const errorMessage = this.getErrorMessage(error);
interaction[response]({
content: errorMessage,
ephemeral: true,
});
}
// Customizable error message handling
getErrorMessage(error) {
if (error.response?.status === 429) {
return 'The service is currently busy. Please try again later.';
}
if (error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT') {
return 'The request timed out. Please try again.';
}
return `Command failed: ${error.message}`;
}
}
+30 -10
View File
@@ -1,6 +1,19 @@
import { readdirSync } from 'node:fs';
import { join } from 'node:path';
/**
* Asynchronously loads all command modules from a directory
* @async
* @param {string} commandsPath - Path to the commands directory
* @param {Logger} logger - Logger instance for logging loading progress
* @returns {Promise<Array<Object>>} Array of command objects with:
* @property {SlashCommandBuilder} data - Command definition
* @property {Function} execute - Command execution function
* @throws {TypeError} If commandsPath is not a string
* @example
* // Load all commands from './commands'
* const commands = await loadCommands('./commands', logger);
*/
export async function loadCommands(commandsPath, logger) {
const commands = [];
const commandFiles = readdirSync(commandsPath).filter(file => file.endsWith('.js'));
@@ -10,21 +23,28 @@ export async function loadCommands(commandsPath, logger) {
try {
const filePath = join(commandsPath, file);
const commandModule = await import(filePath);
const command = commandModule.default;
const Command = commandModule.default;
if (!command?.data || !command?.execute) {
logger.warn(
`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`
);
const CommandBase = (await import('./command.js')).default;
if (Command.prototype instanceof CommandBase) {
// New style - class extending Command
const commandInstance = new Command();
// Wrap run() in execute() for backward compatibility
commandInstance.execute = commandInstance.run.bind(commandInstance);
commands.push(commandInstance);
logger.log(`Loaded command: ${commandInstance.data.name}`);
} else if (Command?.data && Command?.execute) {
// Old style - plain object (maintain backward compatibility)
commands.push(Command);
logger.log(`Loaded command: ${Command.data.name}`);
} else {
logger.warn(`[WARNING] The command at ${filePath} is missing required properties.`);
return;
}
commands.push(command);
logger.log(`Loaded command: ${command.data.name}`);
} catch (error) {
logger.error(`Error loading command from ${file}:`, error);
logger.error(`Error loading command from ${file}: ${error.stack}`);
}
})
}),
);
return commands;