diff --git a/.env.example b/.env.example
deleted file mode 100644
index c33ec5472c..0000000000
--- a/.env.example
+++ /dev/null
@@ -1,40 +0,0 @@
-# PostgreSQL Environment Configuration Template
-# Copy this file to .env and update with your actual values
-
-# Discord Bot Configuration
-DISCORD_TOKEN=your_discord_bot_token_here
-CLIENT_ID=your_discord_client_id_here
-GUILD_ID=your_discord_guild_id_here
-
-# PostgreSQL Configuration (Primary Database)
-POSTGRES_URL=postgresql://titanbot:yourpassword@localhost:5432/titanbot
-POSTGRES_HOST=localhost
-POSTGRES_PORT=5432
-POSTGRES_DB=titanbot
-POSTGRES_USER=titanbot
-POSTGRES_PASSWORD=yourpassword
-POSTGRES_SSL=false
-
-# Migration Settings
-AUTO_MIGRATE=false
-
-# Bot Configuration
-NODE_ENV=development
-LOG_LEVEL=info
-
-# Production Configuration
-# Set NODE_ENV=production for production deployment
-# Set LOG_LEVEL=warn for production (only warnings and errors in console)
-# Available log levels: error, warn, info, http, verbose, debug, silly
-
-# Optional: Additional PostgreSQL Settings
-# POSTGRES_MAX_CONNECTIONS=20
-# POSTGRES_MIN_CONNECTIONS=2
-# POSTGRES_CONNECTION_TIMEOUT=10000
-# POSTGRES_IDLE_TIMEOUT=30000
-
-# Optional: Bot Features
-# ENABLE_ECONOMY=true
-# ENABLE_LEVELING=true
-# ENABLE_WELCOME=true
-# ENABLE_TICKETS=true
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 55ba236f60..0000000000
--- a/.gitignore
+++ /dev/null
@@ -1,142 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-.pnpm-debug.log*
-
-# Local development files
-.local/
-attached_assets/
-
-# Configuration and cache files
-.config/
-.git.backup.*
-
-# Diagnostic reports (https:
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-*.lcov
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (https:
-.grunt
-
-# Bower dependency directory (https:
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https:
-build/Release
-
-# Dependency directories
-node_modules/
-jspm_packages/
-
-# Snowpack dependency directory (https:
-web_modules/
-
-# TypeScript cache
-*.tsbuildinfo
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Optional stylelint cache
-.stylelintcache
-
-# Microbundle cache
-.rpt2_cache/
-.rts2_cache_cjs/
-.rts2_cache_es/
-.rts2_cache_umd/
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variable files
-.env
-.env.development.local
-.env.test.local
-.env.production.local
-.env.local
-
-# parcel-bundler cache (https:
-.cache
-.parcel-cache
-
-# Next.js build output
-.next
-out
-
-# Nuxt.js build / generate output
-.nuxt
-dist
-
-# Gatsby files
-.cache/
-# Comment in the public line in if your project uses Gatsby and not Next.js
-# https:
-# public
-
-# vuepress build output
-.vuepress/dist
-
-# vuepress v2.x temp and cache directory
-.temp
-.cache
-
-# Docusaurus cache and generated files
-.docusaurus
-
-# Serverless directories
-.serverless/
-
-# FuseBox cache
-.fusebox/
-
-# DynamoDB Local files
-.dynamodb/
-
-# TernJS port file
-.tern-port
-
-# Stores VSCode versions used for testing VSCode extensions
-.vscode-test
-
-# yarn v2
-.yarn/cache
-.yarn/unplugged
-.yarn/build-state.yml
-.yarn/install-state.gz
-.pnp.*
-
-# Replit debugger
-.breakpoints
-.git.backup.*
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 8df7f6c941..0000000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2026 codebymitch
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
deleted file mode 100644
index bea8f1294b..0000000000
--- a/README.md
+++ /dev/null
@@ -1,241 +0,0 @@
-# 🤖 TitanBot - Ultimate Discord Bot
-
-**TitanBot** is a powerful, feature-rich Discord bot designed to enhance your server experience with comprehensive moderation tools, engaging economy systems, utility features, and much more. Built with modern Discord.js v14 and PostgreSQL for optimal performance and data persistence.
-
-[](https://discord.gg/YOUR_INVITE)
-[](https://www.npmjs.com/package/discord.js)
-
-
-## 📚 Table of Contents
-
-- [🌟 Features Overview](#-features-overview)
-- [🚀 Quick Setup](#-video-tutorial)
-- [💡 Manual Installation Steps](#installation-steps)
-- [🗄️ Database System](#️-database-system)
-- [🌐 Support Server](#-support-server)
-- [🏗️ Bot Architecture](#-bot-architecture)
-- [🤝 Contributing](#-contributing)
-
-## 🌟 Features Overview
-
-TitanBot offers a complete suite of tools for Discord server management and community engagement:
-
-
-
-
-
-### 🛡️ Moderation & Administration
-- **Mass Actions** - Bulk ban/kick capabilities
-- **User Notes** - Keep detailed moderation records
-- **Case Management** - View and track all mod actions
-
-### 💰 Economy System
-- **Shop & Inventory** - Buy and manage items
-- **Gambling** - Risk it for rewards
-- **Pay System** - Transfer money between users
-
-### 🎮 Fun & Entertainment
-- **Random Facts** - Learn something new
-- **Wanted Poster** - Create fun wanted images
-- **Text Reversal** - Reverse any text
-
-### 🎫 Advanced Ticket System
-- **Claim & Priority** - Staff ticket management
-- **Ticket Limits** - Prevent spam
-- **Transcript System** - Save ticket history
-
-### 🔢 Server Counters
-- **Member Counter** - Live member count channels
-- **Voice Counters** - Track voice stats
-- **Dynamic Updates** - Real-time channel updates
-
-### 🎭 Reaction Roles
-- **Role Assignment** - Self-assignable roles
-- **Emoji Selection** - Reaction-based system
-- **Multi-role Support** - Multiple role options
-
-
-
-
-### 📊 Leveling & XP System
-- **XP Tracking** - Automatic message-based XP
-- **Level Roles** - Auto-assign roles by level
-- **Custom Configuration** - Personalize leveling
-
-### 🎉 Giveaways & Events
-- **Multiple Winners** - Support multi-winner giveaways
-- **Auto Picking** - Automatic winner selection
-- **Reroll System** - Pick new winners if needed
-
-### 🎂 Birthday System
-- **Birthday Tracking** - Never miss a birthday
-- **Auto Announcements** - Celebrate automatically
-- **Timezone Support** - Accurate worldwide tracking
-
-### 🔧 Utility Tools
-- **Report System** - Report issues to staff
-- **Todo Lists** - Personal task management
-- **First Message** - Jump to channel's first message
-
-### 👋 Welcome System
-- **Welcome Messages** - Greet new members
-- **Auto Roles** - Assign roles on join
-- **Custom Embeds** - Personalized messages
-
-
-
-
-
-## 🚀 Quick Setup (recommend)
-
-### 📹 Video Tutorial
-For a detailed step-by-step setup guide, watch our comprehensive video tutorial:
-[**TitanBot Setup Tutorial**](https://www.youtube.com/watch?v=YOUR_TUTORIAL_ID)
-
-### Prerequisites
-- Node.js 18.0.0 or higher
-- PostgreSQL server (recommended) or memory storage fallback
-- Discord bot application with proper intents
-
-### Installation Steps
-
-1. **Clone the Repository**
- ```bash
- git clone https://github.com/yourusername/TitanBot.git
- cd TitanBot
- ```
-
-2. **Install Dependencies**
- ```bash
- npm install
- ```
-
-3. **Configure Environment Variables**
- ```bash
- cp .env.example .env
- ```
- Edit `.env` with your configuration:
- ```env
- # Discord Bot Configuration
- DISCORD_TOKEN=your_discord_bot_token_here
- CLIENT_ID=your_discord_client_id_here
- GUILD_ID=your_discord_guild_id_here
-
- # PostgreSQL Configuration (Recommended)
- POSTGRES_URL=postgresql://titanbot:yourpassword@localhost:5432/titanbot
- POSTGRES_HOST=localhost
- POSTGRES_PORT=5432
- POSTGRES_DB=titanbot
- POSTGRES_USER=titanbot
- POSTGRES_PASSWORD=yourpassword
- POSTGRES_SSL=false
-
- # Migration Settings
- AUTO_MIGRATE=false
-
- # Bot Configuration
- NODE_ENV=development
- LOG_LEVEL=info
- ```
-
-4. **Setup PostgreSQL Database** (Optional but recommended)
- ```bash
- # Create database and user
- createdb titanbot
- createuser titanbot
- psql -c "ALTER USER titanbot PASSWORD 'yourpassword';"
- psql -c "GRANT ALL PRIVILEGES ON DATABASE titanbot TO titanbot;"
- ```
-
-5. **Test Database Connection**
- ```bash
- npm run test-postgres
- ```
-
-6. **Start the Bot**
- ```bash
- npm start
- ```
-
-## 🗄️ Database System
-
-TitanBot uses **PostgreSQL** as its primary database with intelligent fallback to memory storage:
-
-### PostgreSQL Features
-- **ACID Compliance**: Reliable transactions and data integrity
-- **High Performance**: Optimized queries and connection pooling
-- **Persistence**: Data survives bot restarts and crashes
-- **Complex Queries**: Advanced data analysis capabilities
-- **Scalability**: Better performance for large datasets
-- **TTL Support**: Automatic key expiration for temporary data
-- **Connection Management**: Automatic reconnection with exponential backoff
-- **Automatic Schema Creation**: Tables and indexes created on connection
-
-### Fallback System
-- **Memory Storage**: Automatic fallback when PostgreSQL is unavailable
-- **Graceful Degradation**: Bot continues functioning without database
-- **Backward Compatibility**: Maintains existing API structure
-- **Zero Downtime**: Seamless switching between database and memory
-
-## 🏗️ Bot Architecture
-
-### Technology Stack
-- **Discord.js v14** - Modern Discord API wrapper
-- **Node.js 18+** - JavaScript runtime environment
-- **PostgreSQL** - High-performance relational database
-- **Express.js** - Web server for health checks
-- **Winston** - Advanced logging system
-- **Node-cron** - Scheduled task management
-
-### Bot Intents
-TitanBot requires the following Discord intents:
-- Guilds
-- Guild Messages
-- Message Content
-- Guild Members
-- Guild Message Reactions
-- Guild Voice States
-- Direct Messages
-- And more...
-
-### Required Permissions
-- **Send Messages**
-- **Embed Links**
-- **Attach Files**
-- **Read Message History**
-- **Manage Channels**
-- **Manage Roles**
-- **Manage Nicknames**
-- **Ban Members**
-- **Kick Members**
-- **Manage Messages**
-
-## 🤝 Contributing
-
-We welcome contributions to TitanBot! Here's how you can help:
-
-1. **Fork the repository**
-2. **Create a feature branch**
-3. **Make your changes**
-4. **Test thoroughly**
-5. **Submit a pull request**
-
-### Development Guidelines
-- Follow existing code style
-- Add proper error handling
-- Include documentation for new features
-- Test with PostgreSQL and memory storage
-
-## 📜 License
-
-TitanBot is released under the MIT License. See [LICENSE](LICENSE) for details.
-
-## 💌 Thank You
-
-Thank you for choosing TitanBot for your Discord server! We're constantly working to improve and add new features based on community feedback.
-
-**Made with ❤️**
-
----
-
-*Last updated: January 2026*
diff --git a/SECURITY.md b/SECURITY.md
deleted file mode 100644
index cc4d3a8b46..0000000000
--- a/SECURITY.md
+++ /dev/null
@@ -1,76 +0,0 @@
-# Security Policy — TitanBot
-
-We take security seriously. If you discover a vulnerability, please follow the policy below so we can triage and address it safely.
-
-## Summary / Self-hosting clarity
-TitanBot is distributed as open-source software for self-hosting. We (the maintainers) do not operate self-hosted instances and do not have access to user data or configurations on third-party deployments. Self-hosters are responsible for securing and operating their instances. This policy describes how to report vulnerabilities affecting the project code and guidance for self-hosters who discover security issues in their deployment.
-
-## Reporting a Vulnerability (preferred)
-- Preferred channel: Open a private GitHub Security Advisory for this repository (recommended).
-- If you cannot use GitHub Advisories, open a ticket: https:
-- Do NOT open a public issue with exploit details.
-
-If you found a problem in your self-hosted instance that appears to be due to misconfiguration, please contact the instance operator/host first. If you believe the issue is caused by a vulnerability in TitanBot code, follow the reporting steps above and indicate whether the report comes from a self-hosted deployment.
-
-## Response timelines (what to expect)
-- Acknowledgement: within 72 hours.
-- Triage & severity estimate: within 7 days.
-- Fix/migration plan:
- - Critical: aim to ship fix or mitigation within 7–14 days.
- - High: aim to ship within 30 days.
- - Medium/Low: addressed in a future release; communicated within 90 days.
-- Public disclosure: we will coordinate with the reporter and normally publish an advisory after a patch is released, or within 90 days if unresolved (unless the reporter requests otherwise).
-
-## Safe testing rules (researcher rules of engagement)
-- Only test services you own or have explicit permission to test.
-- Do not exfiltrate, destroy, or modify user data.
-- Do not attempt to escalate to or access Discord user tokens, DMs, or other private user content.
-- Provide minimal, safe PoC that reproduces the issue. Redact sensitive data (tokens, PII).
-- If the vulnerability requires intrusive testing, contact us first to agree on a plan.
-
-## What to include in your report
-- Component affected (e.g., database migration, command parser, OAuth).
-- Clear, minimal steps to reproduce.
-- Environment: commit SHA, release tag, Docker image tag, or version.
-- Whether the issue was observed on a self-hosted deployment (and the deployment configuration).
-- PoC (script, HTTP request, logs, screenshots) — sanitize secrets before sharing.
-- Impact (data exposure, privilege escalation, RCE, etc.).
-- Suggested mitigation (if any).
-- Contact info for follow-up.
-
-## Incident reporting from self-hosted deployments
-If you operate a self-hosted TitanBot instance and suffer a security incident:
-- Immediately rotate any exposed secrets (bot token, DB credentials, API keys).
-- Take a snapshot of logs/configuration for investigation (avoid sharing secrets).
-- If you need upstream help, file a private security advisory and include sanitized reproduction steps and the TitanBot version/commit.
-- The maintainers can only fix vulnerabilities in upstream code; we cannot rotate tokens, restore data, or remediate other hosts' deployments.
-
-## Scope
-- In-scope: this repository's code, authentication flows, webhooks provided by this project, and the intended deployment artifacts (Dockerfiles, scripts).
-- Out-of-scope: third-party services (Discord itself, hosted DB providers), and instances you do not own without owner consent.
-
-## Maintenance & Hardening advice for self-hosters (recommended defaults)
-- Use TLS for all exposed endpoints and webhooks (Let’s Encrypt is fine).
-- Do not expose Postgres to the public internet. Use an internal network, VPC, or SSH tunnels.
-- Run Postgres with authentication, strong passwords, and network restrictions.
-- Keep secrets out of the repository (use env vars, secret managers, or GitHub Secrets for CI).
-- Restrict the bot's Discord intents and permissions to the minimum required.
-- Enable automatic dependency updates (Dependabot) and security alerts.
-- Enable secret-scanning for your repo(s) and enable push protection if available.
-- Rotate bot tokens and API keys when suspected of compromise.
-- Regular backups for Postgres and test restores regularly.
-- Monitor logs and alert on suspicious activity (unexpected config changes, mass deletions).
-
-## Disclosure & Credits
-- We will credit researchers in release notes/advisories unless you request anonymity.
-- We may assign a CVE or coordinate with CERT/other bodies for high-severity issues.
-
-## Privacy & Telemetry
-- TitanBot does not phone home or collect usage data by default. (If you plan to add telemetry, it must be opt-in, documented, and transparent.)
-- Maintainers do not receive data from self-hosted instances. If you choose to enable any telemetry, document what is collected and how to opt out.
-
-## Contact
-- Preferred: GitHub Security Advisory for this repository
-- Ticket: https:
-
-Thank you for helping keep TitanBot safe.
diff --git a/database/models/welcomeSystem.js b/database/models/welcomeSystem.js
deleted file mode 100644
index 17aec34984..0000000000
--- a/database/models/welcomeSystem.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { DataTypes } from 'sequelize';
-import { sequelize } from '../database.js';
-
-const welcomeSystem = sequelize.define('welcomeSystem', {
- guildId: {
- type: DataTypes.STRING,
- primaryKey: true,
- allowNull: false,
- unique: true
- },
- welcomeEnabled: {
- type: DataTypes.BOOLEAN,
- defaultValue: false
- },
- welcomeChannel: {
- type: DataTypes.STRING,
- allowNull: true
- },
- welcomeMessage: {
- type: DataTypes.TEXT,
- allowNull: true,
- defaultValue: 'Welcome {user} to {server}! We now have {memberCount} members!'
- },
- welcomeImage: {
- type: DataTypes.STRING,
- allowNull: true
- },
- welcomePing: {
- type: DataTypes.BOOLEAN,
- defaultValue: false
- },
-
- goodbyeEnabled: {
- type: DataTypes.BOOLEAN,
- defaultValue: false
- },
- goodbyeChannel: {
- type: DataTypes.STRING,
- allowNull: true
- },
- goodbyeMessage: {
- type: DataTypes.TEXT,
- allowNull: true,
- defaultValue: '{user} has left the server. We now have {memberCount} members.'
- },
- goodbyeImage: {
- type: DataTypes.STRING,
- allowNull: true
- },
-
- autoRoles: {
- type: DataTypes.ARRAY(DataTypes.STRING),
- defaultValue: []
- },
-
- createdAt: {
- type: DataTypes.DATE,
- defaultValue: DataTypes.NOW
- },
- updatedAt: {
- type: DataTypes.DATE,
- defaultValue: DataTypes.NOW
- }
-});
-
-welcomeSystem.sync({ alter: true }).catch(console.error);
-
-module.exports = welcomeSystem;
diff --git a/index.js b/index.js
deleted file mode 100644
index e6b036a9d8..0000000000
--- a/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-
-import './src/app.js';
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 06585a0454..0000000000
--- a/package-lock.json
+++ /dev/null
@@ -1,7915 +0,0 @@
-{
- "name": "titan-ultimate-bot",
- "version": "2.0.0",
- "lockfileVersion": 2,
- "requires": true,
- "packages": {
- "": {
- "name": "titan-ultimate-bot",
- "version": "2.0.0",
- "dependencies": {
- "@discordjs/rest": "^2.6.0",
- "@discordjs/voice": "^0.19.0",
- "axios": "^1.13.2",
- "discord.js": "^14.25.1",
- "dotenv": "^17.2.3",
- "express": "^5.1.0",
- "ffmpeg-static": "^5.3.0",
- "firebase": "^12.6.0",
- "glob": "^13.0.0",
- "libsodium-wrappers": "^0.7.15",
- "mathjs": "^15.1.0",
- "mongoose": "^9.0.0",
- "node-cron": "^4.2.1",
- "node-fetch": "^3.3.2",
- "pg": "^8.11.3",
- "play-dl": "^1.9.7",
- "titan-ultimate-bot": "file:",
- "web-streams-polyfill": "^3.3.3",
- "winston": "^3.19.0",
- "winston-daily-rotate-file": "^5.0.0",
- "zod": "^3.25.76"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@babel/runtime": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
- "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@colors/colors": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
- "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
- "engines": {
- "node": ">=0.1.90"
- }
- },
- "node_modules/@dabh/diagnostics": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
- "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
- "dependencies": {
- "@so-ric/colorspace": "^1.1.6",
- "enabled": "2.0.x",
- "kuler": "^2.0.0"
- }
- },
- "node_modules/@derhuerst/http-basic": {
- "version": "8.2.4",
- "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz",
- "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==",
- "license": "MIT",
- "dependencies": {
- "caseless": "^0.12.0",
- "concat-stream": "^2.0.0",
- "http-response-object": "^3.0.1",
- "parse-cache-control": "^1.0.1"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@discordjs/builders": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz",
- "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@discordjs/formatters": "^0.6.2",
- "@discordjs/util": "^1.2.0",
- "@sapphire/shapeshift": "^4.0.0",
- "discord-api-types": "^0.38.33",
- "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": "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/formatters": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
- "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "discord-api-types": "^0.38.33"
- },
- "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/util": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
- "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
- "license": "Apache-2.0",
- "dependencies": {
- "discord-api-types": "^0.38.33"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/discordjs/discord.js?sponsor"
- }
- },
- "node_modules/@discordjs/voice": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.19.0.tgz",
- "integrity": "sha512-UyX6rGEXzVyPzb1yvjHtPfTlnLvB5jX/stAMdiytHhfoydX+98hfympdOwsnTktzr+IRvphxTbdErgYDJkEsvw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@types/ws": "^8.18.1",
- "discord-api-types": "^0.38.16",
- "prism-media": "^1.3.5",
- "tslib": "^2.8.1",
- "ws": "^8.18.3"
- },
- "engines": {
- "node": ">=22.12.0"
- },
- "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/@firebase/ai": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.6.1.tgz",
- "integrity": "sha512-qJd9bpABqsanFnwdbjZEDbKKr1jRtuUZ+cHyNBLWsxobH4pd73QncvuO3XlMq4eKBLlg1f5jNdFpJ3G3ABu2Tg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-check-interop-types": "0.3.3",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x",
- "@firebase/app-types": "0.x"
- }
- },
- "node_modules/@firebase/analytics": {
- "version": "0.10.19",
- "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.19.tgz",
- "integrity": "sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/analytics-compat": {
- "version": "0.2.25",
- "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.25.tgz",
- "integrity": "sha512-fdzoaG0BEKbqksRDhmf4JoyZf16Wosrl0Y7tbZtJyVDOOwziE0vrFjmZuTdviL0yhak+Nco6rMsUUbkbD+qb6Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/analytics": "0.10.19",
- "@firebase/analytics-types": "0.8.3",
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/analytics-types": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz",
- "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/app": {
- "version": "0.14.6",
- "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.6.tgz",
- "integrity": "sha512-4uyt8BOrBsSq6i4yiOV/gG6BnnrvTeyymlNcaN/dKvyU1GoolxAafvIvaNP1RCGPlNab3OuE4MKUQuv2lH+PLQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/app-check": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.0.tgz",
- "integrity": "sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/app-check-compat": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz",
- "integrity": "sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-check": "0.11.0",
- "@firebase/app-check-types": "0.5.3",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/app-check-interop-types": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
- "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/app-check-types": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz",
- "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/app-compat": {
- "version": "0.5.6",
- "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.6.tgz",
- "integrity": "sha512-YYGARbutghQY4zZUWMYia0ib0Y/rb52y72/N0z3vglRHL7ii/AaK9SA7S/dzScVOlCdnbHXz+sc5Dq+r8fwFAg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app": "0.14.6",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/app-types": {
- "version": "0.9.3",
- "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
- "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/auth": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.12.0.tgz",
- "integrity": "sha512-zkvLpsrxynWHk07qGrUDfCSqKf4AvfZGEqJ7mVCtYGjNNDbGE71k0Yn84rg8QEZu4hQw1BC0qDEHzpNVBcSVmA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x",
- "@react-native-async-storage/async-storage": "^2.2.0"
- },
- "peerDependenciesMeta": {
- "@react-native-async-storage/async-storage": {
- "optional": true
- }
- }
- },
- "node_modules/@firebase/auth-compat": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.2.tgz",
- "integrity": "sha512-8UhCzF6pav9bw/eXA8Zy1QAKssPRYEYXaWagie1ewLTwHkXv6bKp/j6/IwzSYQP67sy/BMFXIFaCCsoXzFLr7A==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/auth": "1.12.0",
- "@firebase/auth-types": "0.13.0",
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/auth-interop-types": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
- "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/auth-types": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz",
- "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@firebase/app-types": "0.x",
- "@firebase/util": "1.x"
- }
- },
- "node_modules/@firebase/component": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz",
- "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/data-connect": {
- "version": "0.3.12",
- "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.12.tgz",
- "integrity": "sha512-baPddcoNLj/+vYo+HSJidJUdr5W4OkhT109c5qhR8T1dJoZcyJpkv/dFpYlw/VJ3dV66vI8GHQFrmAZw/xUS4g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/database": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz",
- "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-check-interop-types": "0.3.3",
- "@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "faye-websocket": "0.11.4",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/database-compat": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz",
- "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/database": "1.1.0",
- "@firebase/database-types": "1.0.16",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/database-types": {
- "version": "1.0.16",
- "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz",
- "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-types": "0.9.3",
- "@firebase/util": "1.13.0"
- }
- },
- "node_modules/@firebase/firestore": {
- "version": "4.9.3",
- "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.3.tgz",
- "integrity": "sha512-RVuvhcQzs1sD5Osr2naQS71H0bQMbSnib16uOWAKk3GaKb/WBPyCYSr2Ry7MqlxDP/YhwknUxECL07lw9Rq1nA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "@firebase/webchannel-wrapper": "1.0.5",
- "@grpc/grpc-js": "~1.9.0",
- "@grpc/proto-loader": "^0.7.8",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/firestore-compat": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.3.tgz",
- "integrity": "sha512-1ylF/njF68Pmb6p0erP0U78XQv1w77Wap4bUmqZ7ZVkmN1oMgplyu0TyirWtCBoKFRV2+SUZfWXvIij/z39LYg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/firestore": "4.9.3",
- "@firebase/firestore-types": "3.0.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/firestore-types": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz",
- "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@firebase/app-types": "0.x",
- "@firebase/util": "1.x"
- }
- },
- "node_modules/@firebase/functions": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.1.tgz",
- "integrity": "sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/app-check-interop-types": "0.3.3",
- "@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.7.0",
- "@firebase/messaging-interop-types": "0.2.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/functions-compat": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.1.tgz",
- "integrity": "sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/functions": "0.13.1",
- "@firebase/functions-types": "0.6.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/functions-types": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz",
- "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/installations": {
- "version": "0.6.19",
- "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz",
- "integrity": "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/installations-compat": {
- "version": "0.2.19",
- "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.19.tgz",
- "integrity": "sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/installations-types": "0.5.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/installations-types": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz",
- "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@firebase/app-types": "0.x"
- }
- },
- "node_modules/@firebase/logger": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz",
- "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==",
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/messaging": {
- "version": "0.12.23",
- "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz",
- "integrity": "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/messaging-interop-types": "0.2.3",
- "@firebase/util": "1.13.0",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/messaging-compat": {
- "version": "0.2.23",
- "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz",
- "integrity": "sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/messaging": "0.12.23",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/messaging-interop-types": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz",
- "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/performance": {
- "version": "0.7.9",
- "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.9.tgz",
- "integrity": "sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0",
- "web-vitals": "^4.2.4"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/performance-compat": {
- "version": "0.2.22",
- "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.22.tgz",
- "integrity": "sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/performance": "0.7.9",
- "@firebase/performance-types": "0.2.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/performance-types": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz",
- "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/remote-config": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.7.0.tgz",
- "integrity": "sha512-dX95X6WlW7QlgNd7aaGdjAIZUiQkgWgNS+aKNu4Wv92H1T8Ue/NDUjZHd9xb8fHxLXIHNZeco9/qbZzr500MjQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/remote-config-compat": {
- "version": "0.2.20",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.20.tgz",
- "integrity": "sha512-P/ULS9vU35EL9maG7xp66uljkZgcPMQOxLj3Zx2F289baTKSInE6+YIkgHEi1TwHoddC/AFePXPpshPlEFkbgg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/remote-config": "0.7.0",
- "@firebase/remote-config-types": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/remote-config-types": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz",
- "integrity": "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg==",
- "license": "Apache-2.0"
- },
- "node_modules/@firebase/storage": {
- "version": "0.14.0",
- "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.0.tgz",
- "integrity": "sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app": "0.x"
- }
- },
- "node_modules/@firebase/storage-compat": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.0.tgz",
- "integrity": "sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/component": "0.7.0",
- "@firebase/storage": "0.14.0",
- "@firebase/storage-types": "0.8.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "@firebase/app-compat": "0.x"
- }
- },
- "node_modules/@firebase/storage-types": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz",
- "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@firebase/app-types": "0.x",
- "@firebase/util": "1.x"
- }
- },
- "node_modules/@firebase/util": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz",
- "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "dependencies": {
- "tslib": "^2.1.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@firebase/webchannel-wrapper": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz",
- "integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==",
- "license": "Apache-2.0"
- },
- "node_modules/@grpc/grpc-js": {
- "version": "1.9.15",
- "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
- "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "@grpc/proto-loader": "^0.7.8",
- "@types/node": ">=12.12.47"
- },
- "engines": {
- "node": "^8.13.0 || >=10.10.0"
- }
- },
- "node_modules/@grpc/proto-loader": {
- "version": "0.7.15",
- "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
- "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "lodash.camelcase": "^4.3.0",
- "long": "^5.0.0",
- "protobufjs": "^7.2.5",
- "yargs": "^17.7.2"
- },
- "bin": {
- "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@isaacs/balanced-match": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
- "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/@isaacs/brace-expansion": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
- "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
- "license": "MIT",
- "dependencies": {
- "@isaacs/balanced-match": "^4.0.1"
- },
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/@mongodb-js/saslprep": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz",
- "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==",
- "license": "MIT",
- "dependencies": {
- "sparse-bitfield": "^3.0.3"
- }
- },
- "node_modules/@protobufjs/aspromise": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
- "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/base64": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
- "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/codegen": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
- "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/eventemitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
- "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/fetch": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
- "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.1",
- "@protobufjs/inquire": "^1.1.0"
- }
- },
- "node_modules/@protobufjs/float": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
- "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/inquire": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
- "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/path": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
- "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/pool": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
- "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
- "license": "BSD-3-Clause"
- },
- "node_modules/@protobufjs/utf8": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
- "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
- "license": "BSD-3-Clause"
- },
- "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.5",
- "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz",
- "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==",
- "license": "MIT",
- "engines": {
- "node": ">=v14.0.0",
- "npm": ">=7.0.0"
- }
- },
- "node_modules/@so-ric/colorspace": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
- "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
- "dependencies": {
- "color": "^5.0.2",
- "text-hex": "1.0.x"
- }
- },
- "node_modules/@types/node": {
- "version": "25.0.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
- "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.16.0"
- }
- },
- "node_modules/@types/triple-beam": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
- "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="
- },
- "node_modules/@types/webidl-conversions": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
- "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
- "license": "MIT"
- },
- "node_modules/@types/whatwg-url": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
- "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
- "license": "MIT",
- "dependencies": {
- "@types/webidl-conversions": "*"
- }
- },
- "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.7",
- "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
- "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==",
- "license": "MIT",
- "engines": {
- "node": ">=v14.0.0",
- "npm": ">=7.0.0"
- }
- },
- "node_modules/accepts": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
- "license": "MIT",
- "dependencies": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/async": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
- "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
- },
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "license": "MIT"
- },
- "node_modules/axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
- "license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
- }
- },
- "node_modules/body-parser": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
- "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
- "license": "MIT",
- "dependencies": {
- "bytes": "^3.1.2",
- "content-type": "^1.0.5",
- "debug": "^4.4.3",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.7.0",
- "on-finished": "^2.4.1",
- "qs": "^6.14.1",
- "raw-body": "^3.0.1",
- "type-is": "^2.0.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/bson": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz",
- "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=20.19.0"
- }
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "license": "MIT"
- },
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/caseless": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
- "license": "Apache-2.0"
- },
- "node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/color": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz",
- "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==",
- "dependencies": {
- "color-convert": "^3.1.3",
- "color-string": "^2.1.3"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "license": "MIT"
- },
- "node_modules/color-string": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
- "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==",
- "dependencies": {
- "color-name": "^2.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/color-string/node_modules/color-name": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
- "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
- "engines": {
- "node": ">=12.20"
- }
- },
- "node_modules/color/node_modules/color-convert": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz",
- "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==",
- "dependencies": {
- "color-name": "^2.0.0"
- },
- "engines": {
- "node": ">=14.6"
- }
- },
- "node_modules/color/node_modules/color-name": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
- "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
- "engines": {
- "node": ">=12.20"
- }
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/complex.js": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.3.tgz",
- "integrity": "sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ==",
- "license": "MIT",
- "engines": {
- "node": "*"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/rawify"
- }
- },
- "node_modules/concat-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
- "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
- "engines": [
- "node >= 6.0"
- ],
- "license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.0.2",
- "typedarray": "^0.0.6"
- }
- },
- "node_modules/content-disposition": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
- "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
- "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
- "license": "MIT",
- "engines": {
- "node": ">=6.6.0"
- }
- },
- "node_modules/data-uri-to-buffer": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
- "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
- "license": "MIT",
- "engines": {
- "node": ">= 12"
- }
- },
- "node_modules/debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decimal.js": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
- "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
- "license": "MIT"
- },
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/discord-api-types": {
- "version": "0.38.37",
- "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz",
- "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==",
- "license": "MIT",
- "workspaces": [
- "scripts/actions/documentation"
- ]
- },
- "node_modules/discord.js": {
- "version": "14.25.1",
- "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz",
- "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
- "license": "Apache-2.0",
- "dependencies": {
- "@discordjs/builders": "^1.13.0",
- "@discordjs/collection": "1.5.3",
- "@discordjs/formatters": "^0.6.2",
- "@discordjs/rest": "^2.6.0",
- "@discordjs/util": "^1.2.0",
- "@discordjs/ws": "^1.2.3",
- "@sapphire/snowflake": "3.5.3",
- "discord-api-types": "^0.38.33",
- "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/discord.js/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/discord.js/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/dotenv": {
- "version": "17.2.3",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
- "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://dotenvx.com"
- }
- },
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
- "license": "MIT"
- },
- "node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
- },
- "node_modules/enabled": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
- "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
- },
- "node_modules/encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/env-paths": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
- "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
- "license": "MIT"
- },
- "node_modules/escape-latex": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
- "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==",
- "license": "MIT"
- },
- "node_modules/etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/express": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
- "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
- "license": "MIT",
- "dependencies": {
- "accepts": "^2.0.0",
- "body-parser": "^2.2.1",
- "content-disposition": "^1.0.0",
- "content-type": "^1.0.5",
- "cookie": "^0.7.1",
- "cookie-signature": "^1.2.1",
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "finalhandler": "^2.1.0",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "merge-descriptors": "^2.0.0",
- "mime-types": "^3.0.0",
- "on-finished": "^2.4.1",
- "once": "^1.4.0",
- "parseurl": "^1.3.3",
- "proxy-addr": "^2.0.7",
- "qs": "^6.14.0",
- "range-parser": "^1.2.1",
- "router": "^2.2.0",
- "send": "^1.1.0",
- "serve-static": "^2.2.0",
- "statuses": "^2.0.1",
- "type-is": "^2.0.1",
- "vary": "^1.1.2"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "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/faye-websocket": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
- "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
- "license": "Apache-2.0",
- "dependencies": {
- "websocket-driver": ">=0.5.1"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/fecha": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
- "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
- },
- "node_modules/fetch-blob": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
- "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "paypal",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "node-domexception": "^1.0.0",
- "web-streams-polyfill": "^3.0.3"
- },
- "engines": {
- "node": "^12.20 || >= 14.13"
- }
- },
- "node_modules/ffmpeg-static": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz",
- "integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==",
- "hasInstallScript": true,
- "license": "GPL-3.0-or-later",
- "dependencies": {
- "@derhuerst/http-basic": "^8.2.0",
- "env-paths": "^2.2.0",
- "https-proxy-agent": "^5.0.0",
- "progress": "^2.0.3"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/file-stream-rotator": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz",
- "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==",
- "dependencies": {
- "moment": "^2.29.1"
- }
- },
- "node_modules/finalhandler": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
- "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "on-finished": "^2.4.1",
- "parseurl": "^1.3.3",
- "statuses": "^2.0.1"
- },
- "engines": {
- "node": ">= 18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/firebase": {
- "version": "12.7.0",
- "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.7.0.tgz",
- "integrity": "sha512-ZBZg9jFo8uH4Emd7caOqtalKJfDGHnHQSrCPiqRAdTFQd0wL3ERilUBfhnhBLnlernugkN/o7nJa0p+sE71Izg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@firebase/ai": "2.6.1",
- "@firebase/analytics": "0.10.19",
- "@firebase/analytics-compat": "0.2.25",
- "@firebase/app": "0.14.6",
- "@firebase/app-check": "0.11.0",
- "@firebase/app-check-compat": "0.4.0",
- "@firebase/app-compat": "0.5.6",
- "@firebase/app-types": "0.9.3",
- "@firebase/auth": "1.12.0",
- "@firebase/auth-compat": "0.6.2",
- "@firebase/data-connect": "0.3.12",
- "@firebase/database": "1.1.0",
- "@firebase/database-compat": "2.1.0",
- "@firebase/firestore": "4.9.3",
- "@firebase/firestore-compat": "0.4.3",
- "@firebase/functions": "0.13.1",
- "@firebase/functions-compat": "0.4.1",
- "@firebase/installations": "0.6.19",
- "@firebase/installations-compat": "0.2.19",
- "@firebase/messaging": "0.12.23",
- "@firebase/messaging-compat": "0.2.23",
- "@firebase/performance": "0.7.9",
- "@firebase/performance-compat": "0.2.22",
- "@firebase/remote-config": "0.7.0",
- "@firebase/remote-config-compat": "0.2.20",
- "@firebase/storage": "0.14.0",
- "@firebase/storage-compat": "0.4.0",
- "@firebase/util": "1.13.0"
- }
- },
- "node_modules/fn.name": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
- "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
- },
- "node_modules/follow-redirects": {
- "version": "1.15.11",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
- "node_modules/form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/form-data/node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/form-data/node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/formdata-polyfill": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
- "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
- "license": "MIT",
- "dependencies": {
- "fetch-blob": "^3.1.2"
- },
- "engines": {
- "node": ">=12.20.0"
- }
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fraction.js": {
- "version": "5.3.4",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
- "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
- "license": "MIT",
- "engines": {
- "node": "*"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/rawify"
- }
- },
- "node_modules/fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/glob": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz",
- "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==",
- "dependencies": {
- "minimatch": "^10.1.1",
- "minipass": "^7.1.2",
- "path-scurry": "^2.0.0"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "license": "MIT",
- "dependencies": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
- "license": "MIT",
- "dependencies": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/http-parser-js": {
- "version": "0.5.10",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
- "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
- "license": "MIT"
- },
- "node_modules/http-response-object": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz",
- "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "^10.0.3"
- }
- },
- "node_modules/http-response-object/node_modules/@types/node": {
- "version": "10.17.60",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
- "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
- "license": "MIT"
- },
- "node_modules/https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "license": "MIT",
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz",
- "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/idb": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
- "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
- "license": "ISC"
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "license": "ISC"
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-promise": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
- "license": "MIT"
- },
- "node_modules/is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/javascript-natural-sort": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
- "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
- "license": "MIT"
- },
- "node_modules/kareem": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.0.0.tgz",
- "integrity": "sha512-RKhaOBSPN8L7y4yAgNhDT2602G5FD6QbOIISbjN9D6mjHPeqeg7K+EB5IGSU5o81/X2Gzm3ICnAvQW3x3OP8HA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/kuler": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
- "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
- },
- "node_modules/libsodium": {
- "version": "0.7.16",
- "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.16.tgz",
- "integrity": "sha512-3HrzSPuzm6Yt9aTYCDxYEG8x8/6C0+ag655Y7rhhWZM9PT4NpdnbqlzXhGZlDnkgR6MeSTnOt/VIyHLs9aSf+Q==",
- "license": "ISC"
- },
- "node_modules/libsodium-wrappers": {
- "version": "0.7.16",
- "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.16.tgz",
- "integrity": "sha512-Gtr/WBx4dKjvRL1pvfwZqu7gO6AfrQ0u9vFL+kXihtHf6NfkROR8pjYWn98MFDI3jN19Ii1ZUfPR9afGiPyfHg==",
- "license": "ISC",
- "dependencies": {
- "libsodium": "^0.7.16"
- }
- },
- "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.camelcase": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
- "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
- "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/logform": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
- "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
- "dependencies": {
- "@colors/colors": "1.6.0",
- "@types/triple-beam": "^1.3.2",
- "fecha": "^4.2.0",
- "ms": "^2.1.1",
- "safe-stable-stringify": "^2.3.1",
- "triple-beam": "^1.3.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/long": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
- "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
- "license": "Apache-2.0"
- },
- "node_modules/lru-cache": {
- "version": "11.2.4",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
- "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "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/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/mathjs": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-15.1.0.tgz",
- "integrity": "sha512-HfnAcScQm9drGryodlDqeS3WAl4gUTYGDcOtcqL/8s23MZ28Ib1i8XnYK3ZdjNuaW/L4BAp9lIp8vxAMrcuu1w==",
- "license": "Apache-2.0",
- "dependencies": {
- "@babel/runtime": "^7.26.10",
- "complex.js": "^2.2.5",
- "decimal.js": "^10.4.3",
- "escape-latex": "^1.2.0",
- "fraction.js": "^5.2.1",
- "javascript-natural-sort": "^0.7.1",
- "seedrandom": "^3.0.5",
- "tiny-emitter": "^2.1.0",
- "typed-function": "^4.2.1"
- },
- "bin": {
- "mathjs": "bin/cli.js"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/media-typer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
- "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/memory-pager": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
- "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
- "license": "MIT"
- },
- "node_modules/merge-descriptors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
- "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/mime-db": {
- "version": "1.54.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
- "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
- "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "^1.54.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/minimatch": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
- "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
- "dependencies": {
- "@isaacs/brace-expansion": "^5.0.0"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/moment": {
- "version": "2.30.1",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
- "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/mongodb": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz",
- "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==",
- "license": "Apache-2.0",
- "dependencies": {
- "@mongodb-js/saslprep": "^1.3.0",
- "bson": "^7.0.0",
- "mongodb-connection-string-url": "^7.0.0"
- },
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@aws-sdk/credential-providers": "^3.806.0",
- "@mongodb-js/zstd": "^7.0.0",
- "gcp-metadata": "^7.0.1",
- "kerberos": "^7.0.0",
- "mongodb-client-encryption": ">=7.0.0 <7.1.0",
- "snappy": "^7.3.2",
- "socks": "^2.8.6"
- },
- "peerDependenciesMeta": {
- "@aws-sdk/credential-providers": {
- "optional": true
- },
- "@mongodb-js/zstd": {
- "optional": true
- },
- "gcp-metadata": {
- "optional": true
- },
- "kerberos": {
- "optional": true
- },
- "mongodb-client-encryption": {
- "optional": true
- },
- "snappy": {
- "optional": true
- },
- "socks": {
- "optional": true
- }
- }
- },
- "node_modules/mongodb-connection-string-url": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz",
- "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==",
- "license": "Apache-2.0",
- "dependencies": {
- "@types/whatwg-url": "^13.0.0",
- "whatwg-url": "^14.1.0"
- },
- "engines": {
- "node": ">=20.19.0"
- }
- },
- "node_modules/mongoose": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.1.2.tgz",
- "integrity": "sha512-EdxZ/l+wLIPymy/ODxulg13CaUPpt8RenGmuoNAuRhA5Dgk/QERkUm3U0wOEbAi6ULG08bGDo9sy2tKX0KwxIg==",
- "license": "MIT",
- "dependencies": {
- "kareem": "3.0.0",
- "mongodb": "~7.0",
- "mpath": "0.9.0",
- "mquery": "6.0.0",
- "ms": "2.1.3",
- "sift": "17.1.3"
- },
- "engines": {
- "node": ">=20.19.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mongoose"
- }
- },
- "node_modules/mpath": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
- "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
- "license": "MIT",
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/mquery": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz",
- "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==",
- "license": "MIT",
- "engines": {
- "node": ">=20.19.0"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/negotiator": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/node-cron": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz",
- "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==",
- "license": "ISC",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/node-domexception": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
- "deprecated": "Use your platform's native DOMException instead",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/jimmywarting"
- },
- {
- "type": "github",
- "url": "https://paypal.me/jimmywarting"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=10.5.0"
- }
- },
- "node_modules/node-fetch": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
- "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
- "license": "MIT",
- "dependencies": {
- "data-uri-to-buffer": "^4.0.0",
- "fetch-blob": "^3.1.4",
- "formdata-polyfill": "^4.0.10"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/node-fetch"
- }
- },
- "node_modules/object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "license": "MIT",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/one-time": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
- "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
- "dependencies": {
- "fn.name": "1.x.x"
- }
- },
- "node_modules/parse-cache-control": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
- "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="
- },
- "node_modules/parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/path-scurry": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz",
- "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==",
- "dependencies": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- },
- "engines": {
- "node": "20 || >=22"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/path-to-regexp": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
- "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/pg": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
- "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
- "dependencies": {
- "pg-connection-string": "^2.11.0",
- "pg-pool": "^3.11.0",
- "pg-protocol": "^1.11.0",
- "pg-types": "2.2.0",
- "pgpass": "1.0.5"
- },
- "engines": {
- "node": ">= 16.0.0"
- },
- "optionalDependencies": {
- "pg-cloudflare": "^1.3.0"
- },
- "peerDependencies": {
- "pg-native": ">=3.0.1"
- },
- "peerDependenciesMeta": {
- "pg-native": {
- "optional": true
- }
- }
- },
- "node_modules/pg-cloudflare": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
- "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
- "optional": true
- },
- "node_modules/pg-connection-string": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz",
- "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ=="
- },
- "node_modules/pg-int8": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
- "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/pg-pool": {
- "version": "3.11.0",
- "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz",
- "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==",
- "peerDependencies": {
- "pg": ">=8.0"
- }
- },
- "node_modules/pg-protocol": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz",
- "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="
- },
- "node_modules/pg-types": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
- "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
- "dependencies": {
- "pg-int8": "1.0.1",
- "postgres-array": "~2.0.0",
- "postgres-bytea": "~1.0.0",
- "postgres-date": "~1.0.4",
- "postgres-interval": "^1.1.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/pgpass": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
- "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
- "dependencies": {
- "split2": "^4.1.0"
- }
- },
- "node_modules/play-audio": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",
- "integrity": "sha512-ZAqHUKkQLix2Iga7pPbsf1LpUoBjcpwU93F1l3qBIfxYddQLhxS6GKmS0d3jV8kSVaUbr6NnOEcEMFvuX93SWQ==",
- "license": "GPL-3.0"
- },
- "node_modules/play-dl": {
- "version": "1.9.7",
- "resolved": "https://registry.npmjs.org/play-dl/-/play-dl-1.9.7.tgz",
- "integrity": "sha512-KpgerWxUCY4s9Mhze2qdqPhiqd8Ve6HufpH9mBH3FN+vux55qSh6WJKDabfie8IBHN7lnrAlYcT/UdGax58c2A==",
- "license": "GPL-3.0",
- "dependencies": {
- "play-audio": "^0.5.2"
- },
- "engines": {
- "node": ">=16.0.0"
- }
- },
- "node_modules/postgres-array": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
- "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/postgres-bytea": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
- "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/postgres-date": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
- "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/postgres-interval": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
- "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
- "dependencies": {
- "xtend": "^4.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/prism-media": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz",
- "integrity": "sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==",
- "license": "Apache-2.0",
- "peerDependencies": {
- "@discordjs/opus": ">=0.8.0 <1.0.0",
- "ffmpeg-static": "^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0",
- "node-opus": "^0.3.3",
- "opusscript": "^0.0.8"
- },
- "peerDependenciesMeta": {
- "@discordjs/opus": {
- "optional": true
- },
- "ffmpeg-static": {
- "optional": true
- },
- "node-opus": {
- "optional": true
- },
- "opusscript": {
- "optional": true
- }
- }
- },
- "node_modules/progress": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/protobufjs": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
- "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
- "hasInstallScript": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.2",
- "@protobufjs/base64": "^1.1.2",
- "@protobufjs/codegen": "^2.0.4",
- "@protobufjs/eventemitter": "^1.1.0",
- "@protobufjs/fetch": "^1.1.0",
- "@protobufjs/float": "^1.0.2",
- "@protobufjs/inquire": "^1.1.0",
- "@protobufjs/path": "^1.1.2",
- "@protobufjs/pool": "^1.1.0",
- "@protobufjs/utf8": "^1.1.0",
- "@types/node": ">=13.7.0",
- "long": "^5.0.0"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "license": "MIT",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/qs": {
- "version": "6.14.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
- "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.1.0"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/raw-body": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
- "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
- "license": "MIT",
- "dependencies": {
- "bytes": "~3.1.2",
- "http-errors": "~2.0.1",
- "iconv-lite": "~0.7.0",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/router": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
- "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "is-promise": "^4.0.0",
- "parseurl": "^1.3.3",
- "path-to-regexp": "^8.0.0"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/safe-stable-stringify": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
- "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "license": "MIT"
- },
- "node_modules/seedrandom": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
- "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
- "license": "MIT"
- },
- "node_modules/send": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
- "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.4.3",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.1",
- "mime-types": "^3.0.2",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.2"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/serve-static": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
- "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
- "license": "MIT",
- "dependencies": {
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "parseurl": "^1.3.3",
- "send": "^1.2.0"
- },
- "engines": {
- "node": ">= 18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
- "license": "ISC"
- },
- "node_modules/side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/sift": {
- "version": "17.1.3",
- "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
- "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
- "license": "MIT"
- },
- "node_modules/sparse-bitfield": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
- "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
- "license": "MIT",
- "dependencies": {
- "memory-pager": "^1.0.2"
- }
- },
- "node_modules/split2": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
- "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
- "engines": {
- "node": ">= 10.x"
- }
- },
- "node_modules/stack-trace": {
- "version": "0.0.10",
- "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
- "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
- "node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/text-hex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
- "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
- },
- "node_modules/tiny-emitter": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
- "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
- "license": "MIT"
- },
- "node_modules/titan-ultimate-bot": {
- "resolved": "",
- "link": true
- },
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "license": "MIT",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/tr46": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
- "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
- "license": "MIT",
- "dependencies": {
- "punycode": "^2.3.1"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/triple-beam": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
- "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
- "engines": {
- "node": ">= 14.0.0"
- }
- },
- "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/type-is": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
- "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
- "license": "MIT",
- "dependencies": {
- "content-type": "^1.0.5",
- "media-typer": "^1.1.0",
- "mime-types": "^3.0.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/typed-function": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.2.tgz",
- "integrity": "sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A==",
- "license": "MIT",
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/typedarray": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
- "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
- "license": "MIT"
- },
- "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.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "license": "MIT"
- },
- "node_modules/unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "license": "MIT"
- },
- "node_modules/vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/web-streams-polyfill": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
- "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
- "license": "MIT",
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/web-vitals": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
- "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==",
- "license": "Apache-2.0"
- },
- "node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/websocket-driver": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
- "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
- "license": "Apache-2.0",
- "dependencies": {
- "http-parser-js": ">=0.5.1",
- "safe-buffer": ">=5.1.0",
- "websocket-extensions": ">=0.1.1"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/websocket-extensions": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
- "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/whatwg-url": {
- "version": "14.2.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
- "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
- "license": "MIT",
- "dependencies": {
- "tr46": "^5.1.0",
- "webidl-conversions": "^7.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/winston": {
- "version": "3.19.0",
- "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
- "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
- "dependencies": {
- "@colors/colors": "^1.6.0",
- "@dabh/diagnostics": "^2.0.8",
- "async": "^3.2.3",
- "is-stream": "^2.0.0",
- "logform": "^2.7.0",
- "one-time": "^1.0.0",
- "readable-stream": "^3.4.0",
- "safe-stable-stringify": "^2.3.1",
- "stack-trace": "0.0.x",
- "triple-beam": "^1.3.0",
- "winston-transport": "^4.9.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/winston-daily-rotate-file": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz",
- "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==",
- "dependencies": {
- "file-stream-rotator": "^0.6.1",
- "object-hash": "^3.0.0",
- "triple-beam": "^1.4.1",
- "winston-transport": "^4.7.0"
- },
- "engines": {
- "node": ">=8"
- },
- "peerDependencies": {
- "winston": "^3"
- }
- },
- "node_modules/winston-transport": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
- "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
- "dependencies": {
- "logform": "^2.7.0",
- "readable-stream": "^3.6.2",
- "triple-beam": "^1.3.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "license": "ISC"
- },
- "node_modules/ws": {
- "version": "8.19.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
- "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
- "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
- }
- }
- },
- "node_modules/xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
- "engines": {
- "node": ">=0.4"
- }
- },
- "node_modules/y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
- "license": "MIT",
- "dependencies": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/zod": {
- "version": "3.25.76",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
- "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
- "funding": {
- "url": "https://github.com/sponsors/colinhacks"
- }
- }
- },
- "dependencies": {
- "@babel/runtime": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
- "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="
- },
- "@colors/colors": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
- "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="
- },
- "@dabh/diagnostics": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
- "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
- "requires": {
- "@so-ric/colorspace": "^1.1.6",
- "enabled": "2.0.x",
- "kuler": "^2.0.0"
- }
- },
- "@derhuerst/http-basic": {
- "version": "8.2.4",
- "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz",
- "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==",
- "requires": {
- "caseless": "^0.12.0",
- "concat-stream": "^2.0.0",
- "http-response-object": "^3.0.1",
- "parse-cache-control": "^1.0.1"
- }
- },
- "@discordjs/builders": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz",
- "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==",
- "requires": {
- "@discordjs/formatters": "^0.6.2",
- "@discordjs/util": "^1.2.0",
- "@sapphire/shapeshift": "^4.0.0",
- "discord-api-types": "^0.38.33",
- "fast-deep-equal": "^3.1.3",
- "ts-mixer": "^6.0.4",
- "tslib": "^2.6.3"
- }
- },
- "@discordjs/collection": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
- "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="
- },
- "@discordjs/formatters": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
- "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
- "requires": {
- "discord-api-types": "^0.38.33"
- }
- },
- "@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==",
- "requires": {
- "@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"
- }
- },
- "@discordjs/util": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
- "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
- "requires": {
- "discord-api-types": "^0.38.33"
- }
- },
- "@discordjs/voice": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.19.0.tgz",
- "integrity": "sha512-UyX6rGEXzVyPzb1yvjHtPfTlnLvB5jX/stAMdiytHhfoydX+98hfympdOwsnTktzr+IRvphxTbdErgYDJkEsvw==",
- "requires": {
- "@types/ws": "^8.18.1",
- "discord-api-types": "^0.38.16",
- "prism-media": "^1.3.5",
- "tslib": "^2.8.1",
- "ws": "^8.18.3"
- }
- },
- "@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==",
- "requires": {
- "@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"
- }
- },
- "@firebase/ai": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.6.1.tgz",
- "integrity": "sha512-qJd9bpABqsanFnwdbjZEDbKKr1jRtuUZ+cHyNBLWsxobH4pd73QncvuO3XlMq4eKBLlg1f5jNdFpJ3G3ABu2Tg==",
- "requires": {
- "@firebase/app-check-interop-types": "0.3.3",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/analytics": {
- "version": "0.10.19",
- "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.19.tgz",
- "integrity": "sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/analytics-compat": {
- "version": "0.2.25",
- "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.25.tgz",
- "integrity": "sha512-fdzoaG0BEKbqksRDhmf4JoyZf16Wosrl0Y7tbZtJyVDOOwziE0vrFjmZuTdviL0yhak+Nco6rMsUUbkbD+qb6Q==",
- "requires": {
- "@firebase/analytics": "0.10.19",
- "@firebase/analytics-types": "0.8.3",
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/analytics-types": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz",
- "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg=="
- },
- "@firebase/app": {
- "version": "0.14.6",
- "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.6.tgz",
- "integrity": "sha512-4uyt8BOrBsSq6i4yiOV/gG6BnnrvTeyymlNcaN/dKvyU1GoolxAafvIvaNP1RCGPlNab3OuE4MKUQuv2lH+PLQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/app-check": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.0.tgz",
- "integrity": "sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/app-check-compat": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz",
- "integrity": "sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==",
- "requires": {
- "@firebase/app-check": "0.11.0",
- "@firebase/app-check-types": "0.5.3",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/app-check-interop-types": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
- "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A=="
- },
- "@firebase/app-check-types": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz",
- "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng=="
- },
- "@firebase/app-compat": {
- "version": "0.5.6",
- "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.6.tgz",
- "integrity": "sha512-YYGARbutghQY4zZUWMYia0ib0Y/rb52y72/N0z3vglRHL7ii/AaK9SA7S/dzScVOlCdnbHXz+sc5Dq+r8fwFAg==",
- "requires": {
- "@firebase/app": "0.14.6",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/app-types": {
- "version": "0.9.3",
- "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
- "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="
- },
- "@firebase/auth": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.12.0.tgz",
- "integrity": "sha512-zkvLpsrxynWHk07qGrUDfCSqKf4AvfZGEqJ7mVCtYGjNNDbGE71k0Yn84rg8QEZu4hQw1BC0qDEHzpNVBcSVmA==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/auth-compat": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.2.tgz",
- "integrity": "sha512-8UhCzF6pav9bw/eXA8Zy1QAKssPRYEYXaWagie1ewLTwHkXv6bKp/j6/IwzSYQP67sy/BMFXIFaCCsoXzFLr7A==",
- "requires": {
- "@firebase/auth": "1.12.0",
- "@firebase/auth-types": "0.13.0",
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/auth-interop-types": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
- "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA=="
- },
- "@firebase/auth-types": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz",
- "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==",
- "requires": {}
- },
- "@firebase/component": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz",
- "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==",
- "requires": {
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/data-connect": {
- "version": "0.3.12",
- "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.12.tgz",
- "integrity": "sha512-baPddcoNLj/+vYo+HSJidJUdr5W4OkhT109c5qhR8T1dJoZcyJpkv/dFpYlw/VJ3dV66vI8GHQFrmAZw/xUS4g==",
- "requires": {
- "@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/database": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz",
- "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==",
- "requires": {
- "@firebase/app-check-interop-types": "0.3.3",
- "@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "faye-websocket": "0.11.4",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/database-compat": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz",
- "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/database": "1.1.0",
- "@firebase/database-types": "1.0.16",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/database-types": {
- "version": "1.0.16",
- "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz",
- "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==",
- "requires": {
- "@firebase/app-types": "0.9.3",
- "@firebase/util": "1.13.0"
- }
- },
- "@firebase/firestore": {
- "version": "4.9.3",
- "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.3.tgz",
- "integrity": "sha512-RVuvhcQzs1sD5Osr2naQS71H0bQMbSnib16uOWAKk3GaKb/WBPyCYSr2Ry7MqlxDP/YhwknUxECL07lw9Rq1nA==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "@firebase/webchannel-wrapper": "1.0.5",
- "@grpc/grpc-js": "~1.9.0",
- "@grpc/proto-loader": "^0.7.8",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/firestore-compat": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.3.tgz",
- "integrity": "sha512-1ylF/njF68Pmb6p0erP0U78XQv1w77Wap4bUmqZ7ZVkmN1oMgplyu0TyirWtCBoKFRV2+SUZfWXvIij/z39LYg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/firestore": "4.9.3",
- "@firebase/firestore-types": "3.0.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/firestore-types": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz",
- "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==",
- "requires": {}
- },
- "@firebase/functions": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.1.tgz",
- "integrity": "sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw==",
- "requires": {
- "@firebase/app-check-interop-types": "0.3.3",
- "@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.7.0",
- "@firebase/messaging-interop-types": "0.2.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/functions-compat": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.1.tgz",
- "integrity": "sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/functions": "0.13.1",
- "@firebase/functions-types": "0.6.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/functions-types": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz",
- "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg=="
- },
- "@firebase/installations": {
- "version": "0.6.19",
- "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz",
- "integrity": "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/installations-compat": {
- "version": "0.2.19",
- "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.19.tgz",
- "integrity": "sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/installations-types": "0.5.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/installations-types": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz",
- "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==",
- "requires": {}
- },
- "@firebase/logger": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz",
- "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==",
- "requires": {
- "tslib": "^2.1.0"
- }
- },
- "@firebase/messaging": {
- "version": "0.12.23",
- "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz",
- "integrity": "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/messaging-interop-types": "0.2.3",
- "@firebase/util": "1.13.0",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/messaging-compat": {
- "version": "0.2.23",
- "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz",
- "integrity": "sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/messaging": "0.12.23",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/messaging-interop-types": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz",
- "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q=="
- },
- "@firebase/performance": {
- "version": "0.7.9",
- "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.9.tgz",
- "integrity": "sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0",
- "web-vitals": "^4.2.4"
- }
- },
- "@firebase/performance-compat": {
- "version": "0.2.22",
- "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.22.tgz",
- "integrity": "sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/performance": "0.7.9",
- "@firebase/performance-types": "0.2.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/performance-types": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz",
- "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ=="
- },
- "@firebase/remote-config": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.7.0.tgz",
- "integrity": "sha512-dX95X6WlW7QlgNd7aaGdjAIZUiQkgWgNS+aKNu4Wv92H1T8Ue/NDUjZHd9xb8fHxLXIHNZeco9/qbZzr500MjQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/remote-config-compat": {
- "version": "0.2.20",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.20.tgz",
- "integrity": "sha512-P/ULS9vU35EL9maG7xp66uljkZgcPMQOxLj3Zx2F289baTKSInE6+YIkgHEi1TwHoddC/AFePXPpshPlEFkbgg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/remote-config": "0.7.0",
- "@firebase/remote-config-types": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/remote-config-types": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz",
- "integrity": "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg=="
- },
- "@firebase/storage": {
- "version": "0.14.0",
- "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.0.tgz",
- "integrity": "sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/storage-compat": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.0.tgz",
- "integrity": "sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/storage": "0.14.0",
- "@firebase/storage-types": "0.8.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/storage-types": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz",
- "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==",
- "requires": {}
- },
- "@firebase/util": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz",
- "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==",
- "requires": {
- "tslib": "^2.1.0"
- }
- },
- "@firebase/webchannel-wrapper": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz",
- "integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw=="
- },
- "@grpc/grpc-js": {
- "version": "1.9.15",
- "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
- "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==",
- "requires": {
- "@grpc/proto-loader": "^0.7.8",
- "@types/node": ">=12.12.47"
- }
- },
- "@grpc/proto-loader": {
- "version": "0.7.15",
- "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
- "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
- "requires": {
- "lodash.camelcase": "^4.3.0",
- "long": "^5.0.0",
- "protobufjs": "^7.2.5",
- "yargs": "^17.7.2"
- }
- },
- "@isaacs/balanced-match": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
- "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="
- },
- "@isaacs/brace-expansion": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
- "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
- "requires": {
- "@isaacs/balanced-match": "^4.0.1"
- }
- },
- "@mongodb-js/saslprep": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz",
- "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==",
- "requires": {
- "sparse-bitfield": "^3.0.3"
- }
- },
- "@protobufjs/aspromise": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
- "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
- },
- "@protobufjs/base64": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
- "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
- },
- "@protobufjs/codegen": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
- "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
- },
- "@protobufjs/eventemitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
- "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
- },
- "@protobufjs/fetch": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
- "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
- "requires": {
- "@protobufjs/aspromise": "^1.1.1",
- "@protobufjs/inquire": "^1.1.0"
- }
- },
- "@protobufjs/float": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
- "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
- },
- "@protobufjs/inquire": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
- "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
- },
- "@protobufjs/path": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
- "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
- },
- "@protobufjs/pool": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
- "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
- },
- "@protobufjs/utf8": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
- "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
- },
- "@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=="
- },
- "@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==",
- "requires": {
- "fast-deep-equal": "^3.1.3",
- "lodash": "^4.17.21"
- }
- },
- "@sapphire/snowflake": {
- "version": "3.5.5",
- "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz",
- "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ=="
- },
- "@so-ric/colorspace": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
- "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
- "requires": {
- "color": "^5.0.2",
- "text-hex": "1.0.x"
- }
- },
- "@types/node": {
- "version": "25.0.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
- "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
- "requires": {
- "undici-types": "~7.16.0"
- }
- },
- "@types/triple-beam": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
- "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="
- },
- "@types/webidl-conversions": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
- "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
- },
- "@types/whatwg-url": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
- "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
- "requires": {
- "@types/webidl-conversions": "*"
- }
- },
- "@types/ws": {
- "version": "8.18.1",
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
- "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
- "requires": {
- "@types/node": "*"
- }
- },
- "@vladfrangu/async_event_emitter": {
- "version": "2.4.7",
- "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
- "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="
- },
- "accepts": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
- "requires": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
- }
- },
- "agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "requires": {
- "debug": "4"
- }
- },
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
- },
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "async": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
- "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
- },
- "asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
- },
- "axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
- "requires": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
- }
- },
- "body-parser": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
- "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
- "requires": {
- "bytes": "^3.1.2",
- "content-type": "^1.0.5",
- "debug": "^4.4.3",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.7.0",
- "on-finished": "^2.4.1",
- "qs": "^6.14.1",
- "raw-body": "^3.0.1",
- "type-is": "^2.0.1"
- }
- },
- "bson": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz",
- "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw=="
- },
- "buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
- },
- "bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
- },
- "call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "requires": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- }
- },
- "call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "requires": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- }
- },
- "caseless": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
- },
- "cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "requires": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- }
- },
- "color": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz",
- "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==",
- "requires": {
- "color-convert": "^3.1.3",
- "color-string": "^2.1.3"
- },
- "dependencies": {
- "color-convert": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz",
- "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==",
- "requires": {
- "color-name": "^2.0.0"
- }
- },
- "color-name": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
- "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="
- }
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
- "color-string": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
- "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==",
- "requires": {
- "color-name": "^2.0.0"
- },
- "dependencies": {
- "color-name": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
- "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="
- }
- }
- },
- "combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "requires": {
- "delayed-stream": "~1.0.0"
- }
- },
- "complex.js": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.3.tgz",
- "integrity": "sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ=="
- },
- "concat-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
- "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
- "requires": {
- "buffer-from": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.0.2",
- "typedarray": "^0.0.6"
- }
- },
- "content-disposition": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
- "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="
- },
- "content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
- },
- "cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
- },
- "cookie-signature": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
- "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="
- },
- "data-uri-to-buffer": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
- "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
- },
- "debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "requires": {
- "ms": "^2.1.3"
- }
- },
- "decimal.js": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
- "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="
- },
- "delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
- },
- "depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
- },
- "discord-api-types": {
- "version": "0.38.37",
- "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz",
- "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w=="
- },
- "discord.js": {
- "version": "14.25.1",
- "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz",
- "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
- "requires": {
- "@discordjs/builders": "^1.13.0",
- "@discordjs/collection": "1.5.3",
- "@discordjs/formatters": "^0.6.2",
- "@discordjs/rest": "^2.6.0",
- "@discordjs/util": "^1.2.0",
- "@discordjs/ws": "^1.2.3",
- "@sapphire/snowflake": "3.5.3",
- "discord-api-types": "^0.38.33",
- "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"
- },
- "dependencies": {
- "@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=="
- },
- "@sapphire/snowflake": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
- "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="
- }
- }
- },
- "dotenv": {
- "version": "17.2.3",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
- "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="
- },
- "dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "requires": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- }
- },
- "ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
- },
- "emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
- },
- "enabled": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
- "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
- },
- "encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
- },
- "env-paths": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
- "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
- },
- "es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
- },
- "es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
- },
- "es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "requires": {
- "es-errors": "^1.3.0"
- }
- },
- "es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "requires": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- }
- },
- "escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
- },
- "escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
- },
- "escape-latex": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
- "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
- },
- "etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
- },
- "express": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
- "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
- "requires": {
- "accepts": "^2.0.0",
- "body-parser": "^2.2.1",
- "content-disposition": "^1.0.0",
- "content-type": "^1.0.5",
- "cookie": "^0.7.1",
- "cookie-signature": "^1.2.1",
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "finalhandler": "^2.1.0",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "merge-descriptors": "^2.0.0",
- "mime-types": "^3.0.0",
- "on-finished": "^2.4.1",
- "once": "^1.4.0",
- "parseurl": "^1.3.3",
- "proxy-addr": "^2.0.7",
- "qs": "^6.14.0",
- "range-parser": "^1.2.1",
- "router": "^2.2.0",
- "send": "^1.1.0",
- "serve-static": "^2.2.0",
- "statuses": "^2.0.1",
- "type-is": "^2.0.1",
- "vary": "^1.1.2"
- }
- },
- "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=="
- },
- "faye-websocket": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
- "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
- "requires": {
- "websocket-driver": ">=0.5.1"
- }
- },
- "fecha": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
- "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
- },
- "fetch-blob": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
- "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
- "requires": {
- "node-domexception": "^1.0.0",
- "web-streams-polyfill": "^3.0.3"
- }
- },
- "ffmpeg-static": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz",
- "integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==",
- "requires": {
- "@derhuerst/http-basic": "^8.2.0",
- "env-paths": "^2.2.0",
- "https-proxy-agent": "^5.0.0",
- "progress": "^2.0.3"
- }
- },
- "file-stream-rotator": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz",
- "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==",
- "requires": {
- "moment": "^2.29.1"
- }
- },
- "finalhandler": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
- "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
- "requires": {
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "on-finished": "^2.4.1",
- "parseurl": "^1.3.3",
- "statuses": "^2.0.1"
- }
- },
- "firebase": {
- "version": "12.7.0",
- "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.7.0.tgz",
- "integrity": "sha512-ZBZg9jFo8uH4Emd7caOqtalKJfDGHnHQSrCPiqRAdTFQd0wL3ERilUBfhnhBLnlernugkN/o7nJa0p+sE71Izg==",
- "requires": {
- "@firebase/ai": "2.6.1",
- "@firebase/analytics": "0.10.19",
- "@firebase/analytics-compat": "0.2.25",
- "@firebase/app": "0.14.6",
- "@firebase/app-check": "0.11.0",
- "@firebase/app-check-compat": "0.4.0",
- "@firebase/app-compat": "0.5.6",
- "@firebase/app-types": "0.9.3",
- "@firebase/auth": "1.12.0",
- "@firebase/auth-compat": "0.6.2",
- "@firebase/data-connect": "0.3.12",
- "@firebase/database": "1.1.0",
- "@firebase/database-compat": "2.1.0",
- "@firebase/firestore": "4.9.3",
- "@firebase/firestore-compat": "0.4.3",
- "@firebase/functions": "0.13.1",
- "@firebase/functions-compat": "0.4.1",
- "@firebase/installations": "0.6.19",
- "@firebase/installations-compat": "0.2.19",
- "@firebase/messaging": "0.12.23",
- "@firebase/messaging-compat": "0.2.23",
- "@firebase/performance": "0.7.9",
- "@firebase/performance-compat": "0.2.22",
- "@firebase/remote-config": "0.7.0",
- "@firebase/remote-config-compat": "0.2.20",
- "@firebase/storage": "0.14.0",
- "@firebase/storage-compat": "0.4.0",
- "@firebase/util": "1.13.0"
- }
- },
- "fn.name": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
- "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
- },
- "follow-redirects": {
- "version": "1.15.11",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="
- },
- "form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
- },
- "dependencies": {
- "mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
- },
- "mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "requires": {
- "mime-db": "1.52.0"
- }
- }
- }
- },
- "formdata-polyfill": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
- "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
- "requires": {
- "fetch-blob": "^3.1.2"
- }
- },
- "forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
- },
- "fraction.js": {
- "version": "5.3.4",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
- "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="
- },
- "fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="
- },
- "function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
- },
- "get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
- },
- "get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "requires": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- }
- },
- "get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "requires": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- }
- },
- "glob": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz",
- "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==",
- "requires": {
- "minimatch": "^10.1.1",
- "minipass": "^7.1.2",
- "path-scurry": "^2.0.0"
- }
- },
- "gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
- },
- "has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
- },
- "has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "requires": {
- "has-symbols": "^1.0.3"
- }
- },
- "hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "requires": {
- "function-bind": "^1.1.2"
- }
- },
- "http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
- "requires": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- }
- },
- "http-parser-js": {
- "version": "0.5.10",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
- "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA=="
- },
- "http-response-object": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz",
- "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==",
- "requires": {
- "@types/node": "^10.0.3"
- },
- "dependencies": {
- "@types/node": {
- "version": "10.17.60",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
- "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="
- }
- }
- },
- "https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "requires": {
- "agent-base": "6",
- "debug": "4"
- }
- },
- "iconv-lite": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz",
- "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
- "requires": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- }
- },
- "idb": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
- "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
- },
- "is-promise": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
- },
- "is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
- },
- "javascript-natural-sort": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
- "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
- },
- "kareem": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.0.0.tgz",
- "integrity": "sha512-RKhaOBSPN8L7y4yAgNhDT2602G5FD6QbOIISbjN9D6mjHPeqeg7K+EB5IGSU5o81/X2Gzm3ICnAvQW3x3OP8HA=="
- },
- "kuler": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
- "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
- },
- "libsodium": {
- "version": "0.7.16",
- "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.16.tgz",
- "integrity": "sha512-3HrzSPuzm6Yt9aTYCDxYEG8x8/6C0+ag655Y7rhhWZM9PT4NpdnbqlzXhGZlDnkgR6MeSTnOt/VIyHLs9aSf+Q=="
- },
- "libsodium-wrappers": {
- "version": "0.7.16",
- "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.16.tgz",
- "integrity": "sha512-Gtr/WBx4dKjvRL1pvfwZqu7gO6AfrQ0u9vFL+kXihtHf6NfkROR8pjYWn98MFDI3jN19Ii1ZUfPR9afGiPyfHg==",
- "requires": {
- "libsodium": "^0.7.16"
- }
- },
- "lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
- },
- "lodash.camelcase": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
- "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
- },
- "lodash.snakecase": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
- "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
- },
- "logform": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
- "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
- "requires": {
- "@colors/colors": "1.6.0",
- "@types/triple-beam": "^1.3.2",
- "fecha": "^4.2.0",
- "ms": "^2.1.1",
- "safe-stable-stringify": "^2.3.1",
- "triple-beam": "^1.3.0"
- }
- },
- "long": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
- "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
- },
- "lru-cache": {
- "version": "11.2.4",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
- "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="
- },
- "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=="
- },
- "math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
- },
- "mathjs": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-15.1.0.tgz",
- "integrity": "sha512-HfnAcScQm9drGryodlDqeS3WAl4gUTYGDcOtcqL/8s23MZ28Ib1i8XnYK3ZdjNuaW/L4BAp9lIp8vxAMrcuu1w==",
- "requires": {
- "@babel/runtime": "^7.26.10",
- "complex.js": "^2.2.5",
- "decimal.js": "^10.4.3",
- "escape-latex": "^1.2.0",
- "fraction.js": "^5.2.1",
- "javascript-natural-sort": "^0.7.1",
- "seedrandom": "^3.0.5",
- "tiny-emitter": "^2.1.0",
- "typed-function": "^4.2.1"
- }
- },
- "media-typer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
- "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="
- },
- "memory-pager": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
- "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
- },
- "merge-descriptors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
- "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="
- },
- "mime-db": {
- "version": "1.54.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
- "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="
- },
- "mime-types": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
- "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
- "requires": {
- "mime-db": "^1.54.0"
- }
- },
- "minimatch": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
- "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
- "requires": {
- "@isaacs/brace-expansion": "^5.0.0"
- }
- },
- "minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
- },
- "moment": {
- "version": "2.30.1",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
- "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="
- },
- "mongodb": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz",
- "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==",
- "requires": {
- "@mongodb-js/saslprep": "^1.3.0",
- "bson": "^7.0.0",
- "mongodb-connection-string-url": "^7.0.0"
- }
- },
- "mongodb-connection-string-url": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz",
- "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==",
- "requires": {
- "@types/whatwg-url": "^13.0.0",
- "whatwg-url": "^14.1.0"
- }
- },
- "mongoose": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.1.2.tgz",
- "integrity": "sha512-EdxZ/l+wLIPymy/ODxulg13CaUPpt8RenGmuoNAuRhA5Dgk/QERkUm3U0wOEbAi6ULG08bGDo9sy2tKX0KwxIg==",
- "requires": {
- "kareem": "3.0.0",
- "mongodb": "~7.0",
- "mpath": "0.9.0",
- "mquery": "6.0.0",
- "ms": "2.1.3",
- "sift": "17.1.3"
- }
- },
- "mpath": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
- "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
- },
- "mquery": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz",
- "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw=="
- },
- "ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "negotiator": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="
- },
- "node-cron": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz",
- "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg=="
- },
- "node-domexception": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
- },
- "node-fetch": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
- "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
- "requires": {
- "data-uri-to-buffer": "^4.0.0",
- "fetch-blob": "^3.1.4",
- "formdata-polyfill": "^4.0.10"
- }
- },
- "object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="
- },
- "object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="
- },
- "on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "requires": {
- "ee-first": "1.1.1"
- }
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "requires": {
- "wrappy": "1"
- }
- },
- "one-time": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
- "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
- "requires": {
- "fn.name": "1.x.x"
- }
- },
- "parse-cache-control": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
- "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="
- },
- "parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
- },
- "path-scurry": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz",
- "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==",
- "requires": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- }
- },
- "path-to-regexp": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
- "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="
- },
- "pg": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
- "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
- "requires": {
- "pg-cloudflare": "^1.3.0",
- "pg-connection-string": "^2.11.0",
- "pg-pool": "^3.11.0",
- "pg-protocol": "^1.11.0",
- "pg-types": "2.2.0",
- "pgpass": "1.0.5"
- }
- },
- "pg-cloudflare": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
- "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
- "optional": true
- },
- "pg-connection-string": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz",
- "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ=="
- },
- "pg-int8": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
- "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
- },
- "pg-pool": {
- "version": "3.11.0",
- "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz",
- "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==",
- "requires": {}
- },
- "pg-protocol": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz",
- "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="
- },
- "pg-types": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
- "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
- "requires": {
- "pg-int8": "1.0.1",
- "postgres-array": "~2.0.0",
- "postgres-bytea": "~1.0.0",
- "postgres-date": "~1.0.4",
- "postgres-interval": "^1.1.0"
- }
- },
- "pgpass": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
- "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
- "requires": {
- "split2": "^4.1.0"
- }
- },
- "play-audio": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",
- "integrity": "sha512-ZAqHUKkQLix2Iga7pPbsf1LpUoBjcpwU93F1l3qBIfxYddQLhxS6GKmS0d3jV8kSVaUbr6NnOEcEMFvuX93SWQ=="
- },
- "play-dl": {
- "version": "1.9.7",
- "resolved": "https://registry.npmjs.org/play-dl/-/play-dl-1.9.7.tgz",
- "integrity": "sha512-KpgerWxUCY4s9Mhze2qdqPhiqd8Ve6HufpH9mBH3FN+vux55qSh6WJKDabfie8IBHN7lnrAlYcT/UdGax58c2A==",
- "requires": {
- "play-audio": "^0.5.2"
- }
- },
- "postgres-array": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
- "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
- },
- "postgres-bytea": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
- "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="
- },
- "postgres-date": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
- "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
- },
- "postgres-interval": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
- "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
- "requires": {
- "xtend": "^4.0.0"
- }
- },
- "prism-media": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz",
- "integrity": "sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==",
- "requires": {}
- },
- "progress": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
- },
- "protobufjs": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
- "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
- "requires": {
- "@protobufjs/aspromise": "^1.1.2",
- "@protobufjs/base64": "^1.1.2",
- "@protobufjs/codegen": "^2.0.4",
- "@protobufjs/eventemitter": "^1.1.0",
- "@protobufjs/fetch": "^1.1.0",
- "@protobufjs/float": "^1.0.2",
- "@protobufjs/inquire": "^1.1.0",
- "@protobufjs/path": "^1.1.2",
- "@protobufjs/pool": "^1.1.0",
- "@protobufjs/utf8": "^1.1.0",
- "@types/node": ">=13.7.0",
- "long": "^5.0.0"
- }
- },
- "proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "requires": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- }
- },
- "proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
- },
- "punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
- },
- "qs": {
- "version": "6.14.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
- "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
- "requires": {
- "side-channel": "^1.1.0"
- }
- },
- "range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
- },
- "raw-body": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
- "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
- "requires": {
- "bytes": "~3.1.2",
- "http-errors": "~2.0.1",
- "iconv-lite": "~0.7.0",
- "unpipe": "~1.0.0"
- }
- },
- "readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "requires": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- }
- },
- "require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
- },
- "router": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
- "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
- "requires": {
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "is-promise": "^4.0.0",
- "parseurl": "^1.3.3",
- "path-to-regexp": "^8.0.0"
- }
- },
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
- },
- "safe-stable-stringify": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
- "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="
- },
- "safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
- },
- "seedrandom": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
- "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
- },
- "send": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
- "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
- "requires": {
- "debug": "^4.4.3",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.1",
- "mime-types": "^3.0.2",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.2"
- }
- },
- "serve-static": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
- "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
- "requires": {
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "parseurl": "^1.3.3",
- "send": "^1.2.0"
- }
- },
- "setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
- },
- "side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "requires": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- }
- },
- "side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "requires": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- }
- },
- "side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "requires": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- }
- },
- "side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "requires": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- }
- },
- "sift": {
- "version": "17.1.3",
- "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
- "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
- },
- "sparse-bitfield": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
- "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
- "requires": {
- "memory-pager": "^1.0.2"
- }
- },
- "split2": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
- "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
- },
- "stack-trace": {
- "version": "0.0.10",
- "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
- "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="
- },
- "statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="
- },
- "string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "requires": {
- "safe-buffer": "~5.2.0"
- }
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- },
- "text-hex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
- "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
- },
- "tiny-emitter": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
- "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
- },
- "titan-ultimate-bot": {
- "version": "file:",
- "requires": {
- "@discordjs/rest": "^2.6.0",
- "@discordjs/voice": "^0.19.0",
- "axios": "^1.13.2",
- "discord.js": "^14.25.1",
- "dotenv": "^17.2.3",
- "express": "^5.1.0",
- "ffmpeg-static": "^5.3.0",
- "firebase": "^12.6.0",
- "glob": "^13.0.0",
- "libsodium-wrappers": "^0.7.15",
- "mathjs": "^15.1.0",
- "mongoose": "^9.0.0",
- "node-cron": "^4.2.1",
- "node-fetch": "^3.3.2",
- "pg": "^8.11.3",
- "play-dl": "^1.9.7",
- "titan-ultimate-bot": "file:",
- "web-streams-polyfill": "^3.3.3",
- "winston": "^3.19.0",
- "winston-daily-rotate-file": "^5.0.0",
- "zod": "^3.24.1"
- },
- "dependencies": {
- "@babel/runtime": {
- "version": "7.28.4",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
- "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="
- },
- "@colors/colors": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
- "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA=="
- },
- "@dabh/diagnostics": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
- "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
- "requires": {
- "@so-ric/colorspace": "^1.1.6",
- "enabled": "2.0.x",
- "kuler": "^2.0.0"
- }
- },
- "@derhuerst/http-basic": {
- "version": "8.2.4",
- "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz",
- "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==",
- "requires": {
- "caseless": "^0.12.0",
- "concat-stream": "^2.0.0",
- "http-response-object": "^3.0.1",
- "parse-cache-control": "^1.0.1"
- }
- },
- "@discordjs/builders": {
- "version": "1.13.1",
- "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz",
- "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==",
- "requires": {
- "@discordjs/formatters": "^0.6.2",
- "@discordjs/util": "^1.2.0",
- "@sapphire/shapeshift": "^4.0.0",
- "discord-api-types": "^0.38.33",
- "fast-deep-equal": "^3.1.3",
- "ts-mixer": "^6.0.4",
- "tslib": "^2.6.3"
- }
- },
- "@discordjs/collection": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
- "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="
- },
- "@discordjs/formatters": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz",
- "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==",
- "requires": {
- "discord-api-types": "^0.38.33"
- }
- },
- "@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==",
- "requires": {
- "@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"
- }
- },
- "@discordjs/util": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz",
- "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==",
- "requires": {
- "discord-api-types": "^0.38.33"
- }
- },
- "@discordjs/voice": {
- "version": "0.19.0",
- "resolved": "https://registry.npmjs.org/@discordjs/voice/-/voice-0.19.0.tgz",
- "integrity": "sha512-UyX6rGEXzVyPzb1yvjHtPfTlnLvB5jX/stAMdiytHhfoydX+98hfympdOwsnTktzr+IRvphxTbdErgYDJkEsvw==",
- "requires": {
- "@types/ws": "^8.18.1",
- "discord-api-types": "^0.38.16",
- "prism-media": "^1.3.5",
- "tslib": "^2.8.1",
- "ws": "^8.18.3"
- }
- },
- "@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==",
- "requires": {
- "@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"
- }
- },
- "@firebase/ai": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.6.1.tgz",
- "integrity": "sha512-qJd9bpABqsanFnwdbjZEDbKKr1jRtuUZ+cHyNBLWsxobH4pd73QncvuO3XlMq4eKBLlg1f5jNdFpJ3G3ABu2Tg==",
- "requires": {
- "@firebase/app-check-interop-types": "0.3.3",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/analytics": {
- "version": "0.10.19",
- "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.19.tgz",
- "integrity": "sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/analytics-compat": {
- "version": "0.2.25",
- "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.25.tgz",
- "integrity": "sha512-fdzoaG0BEKbqksRDhmf4JoyZf16Wosrl0Y7tbZtJyVDOOwziE0vrFjmZuTdviL0yhak+Nco6rMsUUbkbD+qb6Q==",
- "requires": {
- "@firebase/analytics": "0.10.19",
- "@firebase/analytics-types": "0.8.3",
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/analytics-types": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz",
- "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg=="
- },
- "@firebase/app": {
- "version": "0.14.6",
- "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.6.tgz",
- "integrity": "sha512-4uyt8BOrBsSq6i4yiOV/gG6BnnrvTeyymlNcaN/dKvyU1GoolxAafvIvaNP1RCGPlNab3OuE4MKUQuv2lH+PLQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/app-check": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.0.tgz",
- "integrity": "sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/app-check-compat": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz",
- "integrity": "sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==",
- "requires": {
- "@firebase/app-check": "0.11.0",
- "@firebase/app-check-types": "0.5.3",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/app-check-interop-types": {
- "version": "0.3.3",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
- "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A=="
- },
- "@firebase/app-check-types": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz",
- "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng=="
- },
- "@firebase/app-compat": {
- "version": "0.5.6",
- "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.6.tgz",
- "integrity": "sha512-YYGARbutghQY4zZUWMYia0ib0Y/rb52y72/N0z3vglRHL7ii/AaK9SA7S/dzScVOlCdnbHXz+sc5Dq+r8fwFAg==",
- "requires": {
- "@firebase/app": "0.14.6",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/app-types": {
- "version": "0.9.3",
- "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
- "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="
- },
- "@firebase/auth": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.12.0.tgz",
- "integrity": "sha512-zkvLpsrxynWHk07qGrUDfCSqKf4AvfZGEqJ7mVCtYGjNNDbGE71k0Yn84rg8QEZu4hQw1BC0qDEHzpNVBcSVmA==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/auth-compat": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.2.tgz",
- "integrity": "sha512-8UhCzF6pav9bw/eXA8Zy1QAKssPRYEYXaWagie1ewLTwHkXv6bKp/j6/IwzSYQP67sy/BMFXIFaCCsoXzFLr7A==",
- "requires": {
- "@firebase/auth": "1.12.0",
- "@firebase/auth-types": "0.13.0",
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/auth-interop-types": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
- "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA=="
- },
- "@firebase/auth-types": {
- "version": "0.13.0",
- "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz",
- "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==",
- "requires": {}
- },
- "@firebase/component": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz",
- "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==",
- "requires": {
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/data-connect": {
- "version": "0.3.12",
- "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.12.tgz",
- "integrity": "sha512-baPddcoNLj/+vYo+HSJidJUdr5W4OkhT109c5qhR8T1dJoZcyJpkv/dFpYlw/VJ3dV66vI8GHQFrmAZw/xUS4g==",
- "requires": {
- "@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/database": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz",
- "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==",
- "requires": {
- "@firebase/app-check-interop-types": "0.3.3",
- "@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "faye-websocket": "0.11.4",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/database-compat": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz",
- "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/database": "1.1.0",
- "@firebase/database-types": "1.0.16",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/database-types": {
- "version": "1.0.16",
- "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz",
- "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==",
- "requires": {
- "@firebase/app-types": "0.9.3",
- "@firebase/util": "1.13.0"
- }
- },
- "@firebase/firestore": {
- "version": "4.9.3",
- "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.3.tgz",
- "integrity": "sha512-RVuvhcQzs1sD5Osr2naQS71H0bQMbSnib16uOWAKk3GaKb/WBPyCYSr2Ry7MqlxDP/YhwknUxECL07lw9Rq1nA==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "@firebase/webchannel-wrapper": "1.0.5",
- "@grpc/grpc-js": "~1.9.0",
- "@grpc/proto-loader": "^0.7.8",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/firestore-compat": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.3.tgz",
- "integrity": "sha512-1ylF/njF68Pmb6p0erP0U78XQv1w77Wap4bUmqZ7ZVkmN1oMgplyu0TyirWtCBoKFRV2+SUZfWXvIij/z39LYg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/firestore": "4.9.3",
- "@firebase/firestore-types": "3.0.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/firestore-types": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz",
- "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==",
- "requires": {}
- },
- "@firebase/functions": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.1.tgz",
- "integrity": "sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw==",
- "requires": {
- "@firebase/app-check-interop-types": "0.3.3",
- "@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.7.0",
- "@firebase/messaging-interop-types": "0.2.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/functions-compat": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.1.tgz",
- "integrity": "sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/functions": "0.13.1",
- "@firebase/functions-types": "0.6.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/functions-types": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz",
- "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg=="
- },
- "@firebase/installations": {
- "version": "0.6.19",
- "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz",
- "integrity": "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/installations-compat": {
- "version": "0.2.19",
- "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.19.tgz",
- "integrity": "sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/installations-types": "0.5.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/installations-types": {
- "version": "0.5.3",
- "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz",
- "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==",
- "requires": {}
- },
- "@firebase/logger": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz",
- "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==",
- "requires": {
- "tslib": "^2.1.0"
- }
- },
- "@firebase/messaging": {
- "version": "0.12.23",
- "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz",
- "integrity": "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/messaging-interop-types": "0.2.3",
- "@firebase/util": "1.13.0",
- "idb": "7.1.1",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/messaging-compat": {
- "version": "0.2.23",
- "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz",
- "integrity": "sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/messaging": "0.12.23",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/messaging-interop-types": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz",
- "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q=="
- },
- "@firebase/performance": {
- "version": "0.7.9",
- "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.9.tgz",
- "integrity": "sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0",
- "web-vitals": "^4.2.4"
- }
- },
- "@firebase/performance-compat": {
- "version": "0.2.22",
- "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.22.tgz",
- "integrity": "sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/performance": "0.7.9",
- "@firebase/performance-types": "0.2.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/performance-types": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz",
- "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ=="
- },
- "@firebase/remote-config": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.7.0.tgz",
- "integrity": "sha512-dX95X6WlW7QlgNd7aaGdjAIZUiQkgWgNS+aKNu4Wv92H1T8Ue/NDUjZHd9xb8fHxLXIHNZeco9/qbZzr500MjQ==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/installations": "0.6.19",
- "@firebase/logger": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/remote-config-compat": {
- "version": "0.2.20",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.20.tgz",
- "integrity": "sha512-P/ULS9vU35EL9maG7xp66uljkZgcPMQOxLj3Zx2F289baTKSInE6+YIkgHEi1TwHoddC/AFePXPpshPlEFkbgg==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/logger": "0.5.0",
- "@firebase/remote-config": "0.7.0",
- "@firebase/remote-config-types": "0.5.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/remote-config-types": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz",
- "integrity": "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg=="
- },
- "@firebase/storage": {
- "version": "0.14.0",
- "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.0.tgz",
- "integrity": "sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/storage-compat": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.0.tgz",
- "integrity": "sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==",
- "requires": {
- "@firebase/component": "0.7.0",
- "@firebase/storage": "0.14.0",
- "@firebase/storage-types": "0.8.3",
- "@firebase/util": "1.13.0",
- "tslib": "^2.1.0"
- }
- },
- "@firebase/storage-types": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz",
- "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==",
- "requires": {}
- },
- "@firebase/util": {
- "version": "1.13.0",
- "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz",
- "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==",
- "requires": {
- "tslib": "^2.1.0"
- }
- },
- "@firebase/webchannel-wrapper": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz",
- "integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw=="
- },
- "@grpc/grpc-js": {
- "version": "1.9.15",
- "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
- "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==",
- "requires": {
- "@grpc/proto-loader": "^0.7.8",
- "@types/node": ">=12.12.47"
- }
- },
- "@grpc/proto-loader": {
- "version": "0.7.15",
- "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
- "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
- "requires": {
- "lodash.camelcase": "^4.3.0",
- "long": "^5.0.0",
- "protobufjs": "^7.2.5",
- "yargs": "^17.7.2"
- }
- },
- "@isaacs/balanced-match": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
- "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="
- },
- "@isaacs/brace-expansion": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
- "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
- "requires": {
- "@isaacs/balanced-match": "^4.0.1"
- }
- },
- "@mongodb-js/saslprep": {
- "version": "1.4.4",
- "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz",
- "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==",
- "requires": {
- "sparse-bitfield": "^3.0.3"
- }
- },
- "@protobufjs/aspromise": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
- "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
- },
- "@protobufjs/base64": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
- "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
- },
- "@protobufjs/codegen": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
- "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
- },
- "@protobufjs/eventemitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
- "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
- },
- "@protobufjs/fetch": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
- "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
- "requires": {
- "@protobufjs/aspromise": "^1.1.1",
- "@protobufjs/inquire": "^1.1.0"
- }
- },
- "@protobufjs/float": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
- "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
- },
- "@protobufjs/inquire": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
- "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
- },
- "@protobufjs/path": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
- "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
- },
- "@protobufjs/pool": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
- "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
- },
- "@protobufjs/utf8": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
- "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
- },
- "@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=="
- },
- "@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==",
- "requires": {
- "fast-deep-equal": "^3.1.3",
- "lodash": "^4.17.21"
- }
- },
- "@sapphire/snowflake": {
- "version": "3.5.5",
- "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz",
- "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ=="
- },
- "@so-ric/colorspace": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
- "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
- "requires": {
- "color": "^5.0.2",
- "text-hex": "1.0.x"
- }
- },
- "@types/node": {
- "version": "25.0.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
- "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
- "requires": {
- "undici-types": "~7.16.0"
- }
- },
- "@types/triple-beam": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
- "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="
- },
- "@types/webidl-conversions": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
- "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
- },
- "@types/whatwg-url": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
- "integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
- "requires": {
- "@types/webidl-conversions": "*"
- }
- },
- "@types/ws": {
- "version": "8.18.1",
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
- "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
- "requires": {
- "@types/node": "*"
- }
- },
- "@vladfrangu/async_event_emitter": {
- "version": "2.4.7",
- "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz",
- "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="
- },
- "accepts": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
- "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
- "requires": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
- }
- },
- "agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "requires": {
- "debug": "4"
- }
- },
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
- },
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "async": {
- "version": "3.2.6",
- "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
- "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="
- },
- "asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
- },
- "axios": {
- "version": "1.13.2",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
- "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
- "requires": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
- }
- },
- "body-parser": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
- "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
- "requires": {
- "bytes": "^3.1.2",
- "content-type": "^1.0.5",
- "debug": "^4.4.3",
- "http-errors": "^2.0.0",
- "iconv-lite": "^0.7.0",
- "on-finished": "^2.4.1",
- "qs": "^6.14.1",
- "raw-body": "^3.0.1",
- "type-is": "^2.0.1"
- }
- },
- "bson": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz",
- "integrity": "sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw=="
- },
- "buffer-from": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
- },
- "bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
- },
- "call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "requires": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
- }
- },
- "call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "requires": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- }
- },
- "caseless": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="
- },
- "cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "requires": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- }
- },
- "color": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz",
- "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==",
- "requires": {
- "color-convert": "^3.1.3",
- "color-string": "^2.1.3"
- },
- "dependencies": {
- "color-convert": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz",
- "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==",
- "requires": {
- "color-name": "^2.0.0"
- }
- },
- "color-name": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
- "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="
- }
- }
- },
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
- "color-string": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
- "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==",
- "requires": {
- "color-name": "^2.0.0"
- },
- "dependencies": {
- "color-name": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
- "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg=="
- }
- }
- },
- "combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "requires": {
- "delayed-stream": "~1.0.0"
- }
- },
- "complex.js": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.3.tgz",
- "integrity": "sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ=="
- },
- "concat-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
- "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
- "requires": {
- "buffer-from": "^1.0.0",
- "inherits": "^2.0.3",
- "readable-stream": "^3.0.2",
- "typedarray": "^0.0.6"
- }
- },
- "content-disposition": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
- "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="
- },
- "content-type": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
- "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="
- },
- "cookie": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
- "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="
- },
- "cookie-signature": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
- "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="
- },
- "data-uri-to-buffer": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
- "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="
- },
- "debug": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
- "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "requires": {
- "ms": "^2.1.3"
- }
- },
- "decimal.js": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
- "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="
- },
- "delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
- },
- "depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
- },
- "discord-api-types": {
- "version": "0.38.37",
- "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz",
- "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w=="
- },
- "discord.js": {
- "version": "14.25.1",
- "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.25.1.tgz",
- "integrity": "sha512-2l0gsPOLPs5t6GFZfQZKnL1OJNYFcuC/ETWsW4VtKVD/tg4ICa9x+jb9bkPffkMdRpRpuUaO/fKkHCBeiCKh8g==",
- "requires": {
- "@discordjs/builders": "^1.13.0",
- "@discordjs/collection": "1.5.3",
- "@discordjs/formatters": "^0.6.2",
- "@discordjs/rest": "^2.6.0",
- "@discordjs/util": "^1.2.0",
- "@discordjs/ws": "^1.2.3",
- "@sapphire/snowflake": "3.5.3",
- "discord-api-types": "^0.38.33",
- "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"
- },
- "dependencies": {
- "@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=="
- },
- "@sapphire/snowflake": {
- "version": "3.5.3",
- "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
- "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="
- }
- }
- },
- "dotenv": {
- "version": "17.2.3",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
- "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="
- },
- "dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "requires": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- }
- },
- "ee-first": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
- "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
- },
- "emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
- },
- "enabled": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
- "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
- },
- "encodeurl": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
- "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="
- },
- "env-paths": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
- "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="
- },
- "es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
- },
- "es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
- },
- "es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "requires": {
- "es-errors": "^1.3.0"
- }
- },
- "es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "requires": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- }
- },
- "escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
- },
- "escape-html": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
- "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
- },
- "escape-latex": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz",
- "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw=="
- },
- "etag": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
- "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
- },
- "express": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
- "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
- "requires": {
- "accepts": "^2.0.0",
- "body-parser": "^2.2.1",
- "content-disposition": "^1.0.0",
- "content-type": "^1.0.5",
- "cookie": "^0.7.1",
- "cookie-signature": "^1.2.1",
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "finalhandler": "^2.1.0",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.0",
- "merge-descriptors": "^2.0.0",
- "mime-types": "^3.0.0",
- "on-finished": "^2.4.1",
- "once": "^1.4.0",
- "parseurl": "^1.3.3",
- "proxy-addr": "^2.0.7",
- "qs": "^6.14.0",
- "range-parser": "^1.2.1",
- "router": "^2.2.0",
- "send": "^1.1.0",
- "serve-static": "^2.2.0",
- "statuses": "^2.0.1",
- "type-is": "^2.0.1",
- "vary": "^1.1.2"
- }
- },
- "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=="
- },
- "faye-websocket": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
- "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
- "requires": {
- "websocket-driver": ">=0.5.1"
- }
- },
- "fecha": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
- "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
- },
- "fetch-blob": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
- "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
- "requires": {
- "node-domexception": "^1.0.0",
- "web-streams-polyfill": "^3.0.3"
- }
- },
- "ffmpeg-static": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz",
- "integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==",
- "requires": {
- "@derhuerst/http-basic": "^8.2.0",
- "env-paths": "^2.2.0",
- "https-proxy-agent": "^5.0.0",
- "progress": "^2.0.3"
- }
- },
- "file-stream-rotator": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz",
- "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==",
- "requires": {
- "moment": "^2.29.1"
- }
- },
- "finalhandler": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
- "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
- "requires": {
- "debug": "^4.4.0",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "on-finished": "^2.4.1",
- "parseurl": "^1.3.3",
- "statuses": "^2.0.1"
- }
- },
- "firebase": {
- "version": "12.7.0",
- "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.7.0.tgz",
- "integrity": "sha512-ZBZg9jFo8uH4Emd7caOqtalKJfDGHnHQSrCPiqRAdTFQd0wL3ERilUBfhnhBLnlernugkN/o7nJa0p+sE71Izg==",
- "requires": {
- "@firebase/ai": "2.6.1",
- "@firebase/analytics": "0.10.19",
- "@firebase/analytics-compat": "0.2.25",
- "@firebase/app": "0.14.6",
- "@firebase/app-check": "0.11.0",
- "@firebase/app-check-compat": "0.4.0",
- "@firebase/app-compat": "0.5.6",
- "@firebase/app-types": "0.9.3",
- "@firebase/auth": "1.12.0",
- "@firebase/auth-compat": "0.6.2",
- "@firebase/data-connect": "0.3.12",
- "@firebase/database": "1.1.0",
- "@firebase/database-compat": "2.1.0",
- "@firebase/firestore": "4.9.3",
- "@firebase/firestore-compat": "0.4.3",
- "@firebase/functions": "0.13.1",
- "@firebase/functions-compat": "0.4.1",
- "@firebase/installations": "0.6.19",
- "@firebase/installations-compat": "0.2.19",
- "@firebase/messaging": "0.12.23",
- "@firebase/messaging-compat": "0.2.23",
- "@firebase/performance": "0.7.9",
- "@firebase/performance-compat": "0.2.22",
- "@firebase/remote-config": "0.7.0",
- "@firebase/remote-config-compat": "0.2.20",
- "@firebase/storage": "0.14.0",
- "@firebase/storage-compat": "0.4.0",
- "@firebase/util": "1.13.0"
- }
- },
- "fn.name": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
- "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
- },
- "follow-redirects": {
- "version": "1.15.11",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="
- },
- "form-data": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
- "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
- },
- "dependencies": {
- "mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
- },
- "mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "requires": {
- "mime-db": "1.52.0"
- }
- }
- }
- },
- "formdata-polyfill": {
- "version": "4.0.10",
- "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
- "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
- "requires": {
- "fetch-blob": "^3.1.2"
- }
- },
- "forwarded": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
- "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
- },
- "fraction.js": {
- "version": "5.3.4",
- "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
- "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="
- },
- "fresh": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
- "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="
- },
- "function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
- },
- "get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
- },
- "get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "requires": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- }
- },
- "get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "requires": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- }
- },
- "glob": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz",
- "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==",
- "requires": {
- "minimatch": "^10.1.1",
- "minipass": "^7.1.2",
- "path-scurry": "^2.0.0"
- }
- },
- "gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
- },
- "has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
- },
- "has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "requires": {
- "has-symbols": "^1.0.3"
- }
- },
- "hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "requires": {
- "function-bind": "^1.1.2"
- }
- },
- "http-errors": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
- "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
- "requires": {
- "depd": "~2.0.0",
- "inherits": "~2.0.4",
- "setprototypeof": "~1.2.0",
- "statuses": "~2.0.2",
- "toidentifier": "~1.0.1"
- }
- },
- "http-parser-js": {
- "version": "0.5.10",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
- "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA=="
- },
- "http-response-object": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz",
- "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==",
- "requires": {
- "@types/node": "^10.0.3"
- },
- "dependencies": {
- "@types/node": {
- "version": "10.17.60",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
- "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw=="
- }
- }
- },
- "https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "requires": {
- "agent-base": "6",
- "debug": "4"
- }
- },
- "iconv-lite": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz",
- "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==",
- "requires": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- }
- },
- "idb": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
- "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "ipaddr.js": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
- "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
- },
- "is-promise": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
- "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
- },
- "is-stream": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
- },
- "javascript-natural-sort": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
- "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
- },
- "kareem": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/kareem/-/kareem-3.0.0.tgz",
- "integrity": "sha512-RKhaOBSPN8L7y4yAgNhDT2602G5FD6QbOIISbjN9D6mjHPeqeg7K+EB5IGSU5o81/X2Gzm3ICnAvQW3x3OP8HA=="
- },
- "kuler": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
- "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
- },
- "libsodium": {
- "version": "0.7.16",
- "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.16.tgz",
- "integrity": "sha512-3HrzSPuzm6Yt9aTYCDxYEG8x8/6C0+ag655Y7rhhWZM9PT4NpdnbqlzXhGZlDnkgR6MeSTnOt/VIyHLs9aSf+Q=="
- },
- "libsodium-wrappers": {
- "version": "0.7.16",
- "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.16.tgz",
- "integrity": "sha512-Gtr/WBx4dKjvRL1pvfwZqu7gO6AfrQ0u9vFL+kXihtHf6NfkROR8pjYWn98MFDI3jN19Ii1ZUfPR9afGiPyfHg==",
- "requires": {
- "libsodium": "^0.7.16"
- }
- },
- "lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
- },
- "lodash.camelcase": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
- "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
- },
- "lodash.snakecase": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
- "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
- },
- "logform": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
- "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
- "requires": {
- "@colors/colors": "1.6.0",
- "@types/triple-beam": "^1.3.2",
- "fecha": "^4.2.0",
- "ms": "^2.1.1",
- "safe-stable-stringify": "^2.3.1",
- "triple-beam": "^1.3.0"
- }
- },
- "long": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
- "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
- },
- "lru-cache": {
- "version": "11.2.4",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz",
- "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="
- },
- "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=="
- },
- "math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
- },
- "mathjs": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-15.1.0.tgz",
- "integrity": "sha512-HfnAcScQm9drGryodlDqeS3WAl4gUTYGDcOtcqL/8s23MZ28Ib1i8XnYK3ZdjNuaW/L4BAp9lIp8vxAMrcuu1w==",
- "requires": {
- "@babel/runtime": "^7.26.10",
- "complex.js": "^2.2.5",
- "decimal.js": "^10.4.3",
- "escape-latex": "^1.2.0",
- "fraction.js": "^5.2.1",
- "javascript-natural-sort": "^0.7.1",
- "seedrandom": "^3.0.5",
- "tiny-emitter": "^2.1.0",
- "typed-function": "^4.2.1"
- }
- },
- "media-typer": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
- "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="
- },
- "memory-pager": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
- "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
- },
- "merge-descriptors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
- "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="
- },
- "mime-db": {
- "version": "1.54.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
- "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="
- },
- "mime-types": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
- "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
- "requires": {
- "mime-db": "^1.54.0"
- }
- },
- "minimatch": {
- "version": "10.1.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz",
- "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==",
- "requires": {
- "@isaacs/brace-expansion": "^5.0.0"
- }
- },
- "minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
- },
- "moment": {
- "version": "2.30.1",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
- "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="
- },
- "mongodb": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz",
- "integrity": "sha512-vG/A5cQrvGGvZm2mTnCSz1LUcbOPl83hfB6bxULKQ8oFZauyox/2xbZOoGNl+64m8VBrETkdGCDBdOsCr3F3jg==",
- "requires": {
- "@mongodb-js/saslprep": "^1.3.0",
- "bson": "^7.0.0",
- "mongodb-connection-string-url": "^7.0.0"
- }
- },
- "mongodb-connection-string-url": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.0.tgz",
- "integrity": "sha512-irhhjRVLE20hbkRl4zpAYLnDMM+zIZnp0IDB9akAFFUZp/3XdOfwwddc7y6cNvF2WCEtfTYRwYbIfYa2kVY0og==",
- "requires": {
- "@types/whatwg-url": "^13.0.0",
- "whatwg-url": "^14.1.0"
- }
- },
- "mongoose": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-9.1.2.tgz",
- "integrity": "sha512-EdxZ/l+wLIPymy/ODxulg13CaUPpt8RenGmuoNAuRhA5Dgk/QERkUm3U0wOEbAi6ULG08bGDo9sy2tKX0KwxIg==",
- "requires": {
- "kareem": "3.0.0",
- "mongodb": "~7.0",
- "mpath": "0.9.0",
- "mquery": "6.0.0",
- "ms": "2.1.3",
- "sift": "17.1.3"
- }
- },
- "mpath": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
- "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="
- },
- "mquery": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/mquery/-/mquery-6.0.0.tgz",
- "integrity": "sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw=="
- },
- "ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "negotiator": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
- "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="
- },
- "node-cron": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz",
- "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg=="
- },
- "node-domexception": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
- "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
- },
- "node-fetch": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
- "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
- "requires": {
- "data-uri-to-buffer": "^4.0.0",
- "fetch-blob": "^3.1.4",
- "formdata-polyfill": "^4.0.10"
- }
- },
- "object-hash": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
- "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="
- },
- "object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="
- },
- "on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "requires": {
- "ee-first": "1.1.1"
- }
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "requires": {
- "wrappy": "1"
- }
- },
- "one-time": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
- "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
- "requires": {
- "fn.name": "1.x.x"
- }
- },
- "parse-cache-control": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
- "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg=="
- },
- "parseurl": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
- "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
- },
- "path-scurry": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz",
- "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==",
- "requires": {
- "lru-cache": "^11.0.0",
- "minipass": "^7.1.2"
- }
- },
- "path-to-regexp": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
- "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="
- },
- "pg": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
- "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
- "requires": {
- "pg-cloudflare": "^1.3.0",
- "pg-connection-string": "^2.11.0",
- "pg-pool": "^3.11.0",
- "pg-protocol": "^1.11.0",
- "pg-types": "2.2.0",
- "pgpass": "1.0.5"
- }
- },
- "pg-cloudflare": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
- "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
- "optional": true
- },
- "pg-connection-string": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz",
- "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ=="
- },
- "pg-int8": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
- "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
- },
- "pg-pool": {
- "version": "3.11.0",
- "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz",
- "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==",
- "requires": {}
- },
- "pg-protocol": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz",
- "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="
- },
- "pg-types": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
- "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
- "requires": {
- "pg-int8": "1.0.1",
- "postgres-array": "~2.0.0",
- "postgres-bytea": "~1.0.0",
- "postgres-date": "~1.0.4",
- "postgres-interval": "^1.1.0"
- }
- },
- "pgpass": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
- "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
- "requires": {
- "split2": "^4.1.0"
- }
- },
- "play-audio": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/play-audio/-/play-audio-0.5.2.tgz",
- "integrity": "sha512-ZAqHUKkQLix2Iga7pPbsf1LpUoBjcpwU93F1l3qBIfxYddQLhxS6GKmS0d3jV8kSVaUbr6NnOEcEMFvuX93SWQ=="
- },
- "play-dl": {
- "version": "1.9.7",
- "resolved": "https://registry.npmjs.org/play-dl/-/play-dl-1.9.7.tgz",
- "integrity": "sha512-KpgerWxUCY4s9Mhze2qdqPhiqd8Ve6HufpH9mBH3FN+vux55qSh6WJKDabfie8IBHN7lnrAlYcT/UdGax58c2A==",
- "requires": {
- "play-audio": "^0.5.2"
- }
- },
- "postgres-array": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
- "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
- },
- "postgres-bytea": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
- "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="
- },
- "postgres-date": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
- "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
- },
- "postgres-interval": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
- "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
- "requires": {
- "xtend": "^4.0.0"
- }
- },
- "prism-media": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.5.tgz",
- "integrity": "sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==",
- "requires": {}
- },
- "progress": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
- "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
- },
- "protobufjs": {
- "version": "7.5.4",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
- "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
- "requires": {
- "@protobufjs/aspromise": "^1.1.2",
- "@protobufjs/base64": "^1.1.2",
- "@protobufjs/codegen": "^2.0.4",
- "@protobufjs/eventemitter": "^1.1.0",
- "@protobufjs/fetch": "^1.1.0",
- "@protobufjs/float": "^1.0.2",
- "@protobufjs/inquire": "^1.1.0",
- "@protobufjs/path": "^1.1.2",
- "@protobufjs/pool": "^1.1.0",
- "@protobufjs/utf8": "^1.1.0",
- "@types/node": ">=13.7.0",
- "long": "^5.0.0"
- }
- },
- "proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
- "requires": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- }
- },
- "proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
- },
- "punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
- },
- "qs": {
- "version": "6.14.1",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
- "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
- "requires": {
- "side-channel": "^1.1.0"
- }
- },
- "range-parser": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
- "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
- },
- "raw-body": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
- "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
- "requires": {
- "bytes": "~3.1.2",
- "http-errors": "~2.0.1",
- "iconv-lite": "~0.7.0",
- "unpipe": "~1.0.0"
- }
- },
- "readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
- "requires": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- }
- },
- "require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
- },
- "router": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
- "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
- "requires": {
- "debug": "^4.4.0",
- "depd": "^2.0.0",
- "is-promise": "^4.0.0",
- "parseurl": "^1.3.3",
- "path-to-regexp": "^8.0.0"
- }
- },
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
- },
- "safe-stable-stringify": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
- "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="
- },
- "safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
- },
- "seedrandom": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
- "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
- },
- "send": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
- "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
- "requires": {
- "debug": "^4.4.3",
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "etag": "^1.8.1",
- "fresh": "^2.0.0",
- "http-errors": "^2.0.1",
- "mime-types": "^3.0.2",
- "ms": "^2.1.3",
- "on-finished": "^2.4.1",
- "range-parser": "^1.2.1",
- "statuses": "^2.0.2"
- }
- },
- "serve-static": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
- "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
- "requires": {
- "encodeurl": "^2.0.0",
- "escape-html": "^1.0.3",
- "parseurl": "^1.3.3",
- "send": "^1.2.0"
- }
- },
- "setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
- },
- "side-channel": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
- "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "requires": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3",
- "side-channel-list": "^1.0.0",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- }
- },
- "side-channel-list": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
- "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
- "requires": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.3"
- }
- },
- "side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "requires": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- }
- },
- "side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "requires": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- }
- },
- "sift": {
- "version": "17.1.3",
- "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
- "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
- },
- "sparse-bitfield": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
- "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
- "requires": {
- "memory-pager": "^1.0.2"
- }
- },
- "split2": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
- "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
- },
- "stack-trace": {
- "version": "0.0.10",
- "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
- "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg=="
- },
- "statuses": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
- "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="
- },
- "string_decoder": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
- "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
- "requires": {
- "safe-buffer": "~5.2.0"
- }
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- },
- "text-hex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
- "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
- },
- "tiny-emitter": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
- "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
- },
- "toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
- },
- "tr46": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
- "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
- "requires": {
- "punycode": "^2.3.1"
- }
- },
- "triple-beam": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
- "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="
- },
- "ts-mixer": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
- "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
- },
- "tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
- },
- "type-is": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
- "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
- "requires": {
- "content-type": "^1.0.5",
- "media-typer": "^1.1.0",
- "mime-types": "^3.0.0"
- }
- },
- "typed-function": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.2.tgz",
- "integrity": "sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A=="
- },
- "typedarray": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
- "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
- },
- "undici": {
- "version": "6.21.3",
- "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
- "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="
- },
- "undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="
- },
- "unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
- },
- "vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
- },
- "web-streams-polyfill": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
- "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="
- },
- "web-vitals": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
- "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="
- },
- "webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
- },
- "websocket-driver": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
- "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
- "requires": {
- "http-parser-js": ">=0.5.1",
- "safe-buffer": ">=5.1.0",
- "websocket-extensions": ">=0.1.1"
- }
- },
- "websocket-extensions": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
- "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="
- },
- "whatwg-url": {
- "version": "14.2.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
- "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
- "requires": {
- "tr46": "^5.1.0",
- "webidl-conversions": "^7.0.0"
- }
- },
- "winston": {
- "version": "3.19.0",
- "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
- "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
- "requires": {
- "@colors/colors": "^1.6.0",
- "@dabh/diagnostics": "^2.0.8",
- "async": "^3.2.3",
- "is-stream": "^2.0.0",
- "logform": "^2.7.0",
- "one-time": "^1.0.0",
- "readable-stream": "^3.4.0",
- "safe-stable-stringify": "^2.3.1",
- "stack-trace": "0.0.x",
- "triple-beam": "^1.3.0",
- "winston-transport": "^4.9.0"
- }
- },
- "winston-daily-rotate-file": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz",
- "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==",
- "requires": {
- "file-stream-rotator": "^0.6.1",
- "object-hash": "^3.0.0",
- "triple-beam": "^1.4.1",
- "winston-transport": "^4.7.0"
- }
- },
- "winston-transport": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
- "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
- "requires": {
- "logform": "^2.7.0",
- "readable-stream": "^3.6.2",
- "triple-beam": "^1.3.0"
- }
- },
- "wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "requires": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- }
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
- },
- "ws": {
- "version": "8.19.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
- "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
- "requires": {}
- },
- "xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
- },
- "y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
- },
- "yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
- "requires": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
- }
- },
- "yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
- },
- "zod": {
- "version": "3.25.76",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
- "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
- }
- }
- },
- "toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
- },
- "tr46": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
- "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
- "requires": {
- "punycode": "^2.3.1"
- }
- },
- "triple-beam": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
- "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg=="
- },
- "ts-mixer": {
- "version": "6.0.4",
- "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
- "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
- },
- "tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
- },
- "type-is": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
- "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
- "requires": {
- "content-type": "^1.0.5",
- "media-typer": "^1.1.0",
- "mime-types": "^3.0.0"
- }
- },
- "typed-function": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.2.tgz",
- "integrity": "sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A=="
- },
- "typedarray": {
- "version": "0.0.6",
- "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
- "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
- },
- "undici": {
- "version": "6.21.3",
- "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
- "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="
- },
- "undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="
- },
- "unpipe": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
- "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
- },
- "vary": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
- "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="
- },
- "web-streams-polyfill": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
- "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="
- },
- "web-vitals": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
- "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="
- },
- "webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
- },
- "websocket-driver": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
- "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
- "requires": {
- "http-parser-js": ">=0.5.1",
- "safe-buffer": ">=5.1.0",
- "websocket-extensions": ">=0.1.1"
- }
- },
- "websocket-extensions": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
- "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="
- },
- "whatwg-url": {
- "version": "14.2.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
- "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
- "requires": {
- "tr46": "^5.1.0",
- "webidl-conversions": "^7.0.0"
- }
- },
- "winston": {
- "version": "3.19.0",
- "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
- "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
- "requires": {
- "@colors/colors": "^1.6.0",
- "@dabh/diagnostics": "^2.0.8",
- "async": "^3.2.3",
- "is-stream": "^2.0.0",
- "logform": "^2.7.0",
- "one-time": "^1.0.0",
- "readable-stream": "^3.4.0",
- "safe-stable-stringify": "^2.3.1",
- "stack-trace": "0.0.x",
- "triple-beam": "^1.3.0",
- "winston-transport": "^4.9.0"
- }
- },
- "winston-daily-rotate-file": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz",
- "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==",
- "requires": {
- "file-stream-rotator": "^0.6.1",
- "object-hash": "^3.0.0",
- "triple-beam": "^1.4.1",
- "winston-transport": "^4.7.0"
- }
- },
- "winston-transport": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
- "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
- "requires": {
- "logform": "^2.7.0",
- "readable-stream": "^3.6.2",
- "triple-beam": "^1.3.0"
- }
- },
- "wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "requires": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- }
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
- },
- "ws": {
- "version": "8.19.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
- "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
- "requires": {}
- },
- "xtend": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
- "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
- },
- "y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
- },
- "yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
- "requires": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
- }
- },
- "yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
- },
- "zod": {
- "version": "3.25.76",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
- "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="
- }
- }
-}
diff --git a/package.json b/package.json
deleted file mode 100644
index cef4531096..0000000000
--- a/package.json
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- "name": "titan-ultimate-bot",
- "version": "2.0.0",
- "description": "Modular Ultimate Community Bot by Touchpoint Support",
- "main": "src/app.js",
- "type": "module",
- "scripts": {
- "start": "node src/app.js",
- "dev": "node src/app.js",
- "migrate-commands": "node scripts/migrate-commands.js",
- "simple-migrate": "node scripts/simple-migrate.js",
- "register-commands": "node scripts/register-commands.js",
- "deploy": "npm run migrate-commands && npm run register-commands",
- "test-postgres": "node scripts/test-postgres.js",
- "railway:start": "NODE_ENV=production node src/app.js"
- },
- "dependencies": {
- "@discordjs/rest": "^2.6.0",
- "@discordjs/voice": "^0.19.0",
- "axios": "^1.13.2",
- "discord.js": "^14.25.1",
- "dotenv": "^17.2.3",
- "express": "^5.1.0",
- "ffmpeg-static": "^5.3.0",
- "firebase": "^12.6.0",
- "glob": "^13.0.0",
- "libsodium-wrappers": "^0.7.15",
- "mathjs": "^15.1.0",
- "mongoose": "^9.0.0",
- "node-cron": "^4.2.1",
- "node-fetch": "^3.3.2",
- "pg": "^8.11.3",
- "play-dl": "^1.9.7",
- "titan-ultimate-bot": "file:",
- "web-streams-polyfill": "^3.3.3",
- "winston": "^3.19.0",
- "winston-daily-rotate-file": "^5.0.0",
- "zod": "^3.25.76"
- },
- "engines": {
- "node": ">=18.0.0"
- }
-}
diff --git a/roadmap.txt b/roadmap.txt
deleted file mode 100644
index 49f5eacc2d..0000000000
--- a/roadmap.txt
+++ /dev/null
@@ -1,99 +0,0 @@
-📄 Root Files
-├── 📄 package.json # Dependencies and project configuration
-├── 📄 index.js # Bot entry point and initialization
-├── 📄 .env.example # Environment variables template
-├── 📄 .env # Your environment variables (gitignored)
-├── 📄 README.md # Project documentation
-├── 📄 roadmap.txt # This file - project structure guide
-└── 📄 scripts/ # Utility scripts
- └── 📄 remove-single-line-comments.js # Code cleanup utility
-
-📁 src/ - Main Application Source Code
-├── 📄 app.js # Main bot application and Discord client
-├── 📁 commands/ # Slash commands organized by category
-│ ├── 📁 Core/ # help.js, ping.js, uptime.js...
-│ ├── 📁 Economy/ # balance.js, work.js, shop.js...
-│ ├── 📁 Fun/ # meme.js, joke.js, trivia.js...
-│ ├── 📁 JoinToCreate/ # jointocreate.js - voice channel management
-│ ├── 📁 Moderation/ # ban.js, kick.js, mute.js...
-│ ├── 📁 Reaction_roles/ # rsetup.js - reaction role management
-│ ├── 📁 Tickets/ # ticket.js, close.js...
-│ ├── 📁 Tools/ # calculate.js, poll.js, randomuser.js...
-│ ├── 📁 Utility/ # userinfo.js, serverinfo.js...
-│ ├── 📁 Verification/ # verification.js, autoVerify.js...
-│ ├── 📁 Voice/ # activity.js - voice channel activities
-│ └── 📁 Welcome/ # welcome.js, goodbye.js, autorole.js...
-├── 📁 config/ # Configuration files
-│ ├── 📄 index.js # Main configuration loader
-│ ├── 📄 bot.js # Bot settings and appearance
-│ ├── 📄 postgres.js # PostgreSQL database configuration
-│ └── 📁 shop/ # Shop item configurations
-│ ├── 📄 index.js # Shop system main
-│ └── 📄 items.js # Shop items definition
-├── 📁 events/ # Discord event handlers
-│ ├── 📄 channelDelete.js # Channel deletion handling
-│ ├── 📄 guildMemberAdd.js # New member welcome
-│ ├── 📄 guildMemberRemove.js # Member leave handling
-│ ├── 📄 interactionCreate.js # Slash command interactions
-│ ├── 📄 messageCreate.js # Message events and auto-moderation
-│ ├── 📄 ready.js # Bot ready event and status setup
-│ └── 📄 voiceStateUpdate.js # Voice channel state changes
-├── 📁 handlers/ # Command and interaction handlers
-│ ├── 📄 commandLoader.js # Dynamic command registration
-│ ├── 📄 commands.js # Command execution logic
-│ ├── 📄 events.js # Event registration and handling
-│ ├── 📄 giveawayButtonLoader.js # Giveaway button interactions
-│ ├── 📄 giveawayButtons.js # Giveaway button handlers
-│ ├── 📄 interactions.js # General interaction handlers
-│ ├── 📁 reactionRoles/ # Reaction role system
-│ │ ├── 📄 reactionRoles.js # Main reaction role logic
-│ │ └── 📄 selectMenus.js # Reaction role select menus
-│ ├── 📄 ticketButtonLoader.js # Ticket button interactions
-│ ├── 📄 ticketButtons.js # Ticket button handlers
-│ ├── 📄 todoButtonLoader.js # Todo system button loader
-│ ├── 📄 todoButtons.js # Todo system button handlers
-│ └── 📄 verificationButtonLoader.js # Verification button loader
-├── 📁 interactions/ # Select menu and modal handlers
-│ ├── 📁 selectMenus/ # Select menu interactions
-│ │ └── 📄 reaction_roles.js # Reaction role select menu
-│ └── 📄 verificationButtonHandler.js # Verification button handler
-├── 📁 services/ # Business logic and data services
-│ ├── 📄 birthdayService.js # Birthday reminder system
-│ ├── 📄 counterService.js # Server counter management
-│ ├── 📄 database.js # Main database wrapper (PostgreSQL + Memory fallback)
-│ ├── 📄 economy.js # Economy system logic
-│ ├── 📄 giveawayService.js # Giveaway management
-│ ├── 📄 guildConfig.js # Guild configuration management
-│ ├── 📄 leveling.js # XP and leveling system
-│ ├── 📄 reactionRoleService.js # Reaction role service
-│ ├── 📄 shopService.js # Shop system service
-│ ├── 📄 ticket.js # Ticket system service
-│ └── 📄 xpSystem.js # Experience calculations
-└── 📁 utils/ # Utility functions and helpers
- ├── 📄 components.js # Discord component builders
- ├── 📄 dateUtils.js # Date and time utilities
- ├── 📄 database.js # Database operations layer
- ├── 📄 economy.js # Economy utility functions
- ├── 📄 embeds.js # Discord embed utilities
- ├── 📄 errorHandler.js # Error handling utilities
- ├── 📄 giveaways.js # Giveaway utilities
- ├── 📄 helpers.js # General helper functions
- ├── 📄 interactionHelper.js # Interaction utilities
- ├── 📄 logger.js # Winston logging system
- ├── 📄 memoryStorage.js # In-memory storage fallback
- ├── 📄 messageTemplates.js # Predefined message templates
- ├── 📄 moderation.js # Moderation utilities
- ├── 📄 postgresDatabase.js # PostgreSQL database implementation
- ├── 📄 ticketLogging.js # Ticket logging utilities
- ├── 📄 tickets.js # Ticket utilities
- └── 📄 welcome.js # Welcome system utilities
-
-📁 database/ - Database Related Files
-└── 📁 models/ # Database models and schemas
- └── 📄 welcomeSystem.js # Welcome system data models
-
-📁 logs/ - Application Logs
-├── 📄 error-*.log # Error logs (rotated daily)
-├── 📄 combined-*.log # General logs (rotated daily)
-└── 📄 exceptions-*.log # Exception logs (rotated daily)
-
diff --git a/src/app.js b/src/app.js
deleted file mode 100644
index 1d48273433..0000000000
--- a/src/app.js
+++ /dev/null
@@ -1,410 +0,0 @@
-import 'dotenv/config';
-import {
- Client,
- Collection,
- GatewayIntentBits,
- ModalBuilder,
- TextInputBuilder,
- TextInputStyle,
- StringSelectMenuBuilder,
- ActionRowBuilder,
- ButtonBuilder,
- ButtonStyle,
- ChannelType
-} from 'discord.js';
-import { REST, Routes } from 'discord.js';
-import { fileURLToPath } from 'url';
-import path from 'path';
-import fs from 'fs/promises';
-import express from 'express';
-import cron from 'node-cron';
-
-import { ReadableStream } from 'web-streams-polyfill';
-if (typeof global.ReadableStream === 'undefined') {
- global.ReadableStream = ReadableStream;
-}
-
-import config from './config/index.js';
-import { initializeDatabase } from './utils/database.js';
-import { getFromDb, setInDb, deleteFromDb } from './utils/database.js';
-import { getGuildConfig } from './services/guildConfig.js';
-import { giveawayKey, getGuildGiveaways } from './utils/giveaways.js';
-import { handleReactionRoles } from './handlers/reactionRoles.js';
-import { createEmbed, errorEmbed, successEmbed } from './utils/embeds.js';
-import { getLevelingConfig, getUserLevelData, saveUserLevelData, getXpForLevel } from './utils/database.js';
-import { addXp } from './services/xpSystem.js';
-import { getServerCounters, saveServerCounters, updateCounter } from './services/counterService.js';
-import { logger } from './utils/logger.js';
-import { checkBirthdays } from './services/birthdayService.js';
-import { checkGiveaways } from './services/giveawayService.js';
-import { loadCommands, registerCommands as registerSlashCommands } from './handlers/commandLoader.js';
-
-class TitanBot extends Client {
- constructor() {
- super({
- intents: [
- // Core functionality
- GatewayIntentBits.Guilds, // Basic guild events (required)
- GatewayIntentBits.GuildMembers, // Member join/leave (welcome system)
-
- // Message handling
- GatewayIntentBits.GuildMessages, // Message create (leveling, commands)
- GatewayIntentBits.MessageContent, // Required to read message content for leveling
- GatewayIntentBits.GuildMessageReactions, // Reaction tracking for giveaways
-
- // Voice
- GatewayIntentBits.GuildVoiceStates, // Voice channel tracking
-
- // Moderation & Logging
- GatewayIntentBits.GuildModeration, // Moderation audit logging
- ],
- });
-
- this.config = config;
- this.commands = new Collection();
- this.events = new Collection();
- this.buttons = new Collection();
- this.selectMenus = new Collection();
- this.modals = new Collection();
- this.cooldowns = new Collection();
- this.db = null;
- this.rest = new REST({ version: '10' }).setToken(config.bot.token);
- }
-
- async start() {
- try {
- logger.info('Starting TitanBot...');
- await new Promise(resolve => setTimeout(resolve, 1000));
-
- logger.info('Initializing database...');
- const dbInstance = await initializeDatabase();
- this.db = dbInstance.db;
-
- // Check database status and report
- const dbStatus = this.db.getStatus();
- if (dbStatus.isDegraded) {
- logger.warn('');
- logger.warn('â•”â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•—');
- logger.warn('â•‘ âš ï¸ DATABASE RUNNING IN DEGRADED MODE â•‘');
- logger.warn('â•‘ â•‘');
- logger.warn('â•‘ Connection: In-Memory Storage (PostgreSQL unavailable)â•‘');
- logger.warn('â•‘ Data Persistence: DISABLED - data lost on restart â•‘');
- logger.warn('â•‘ Action Required: Fix PostgreSQL and restart bot â•‘');
- logger.warn('╚â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•');
- logger.warn('');
- } else {
- logger.info(`✅ Database Status: ${dbStatus.connectionType} (fully operational)`);
- }
-
- logger.info('Starting web server...');
- this.startWebServer();
-
- logger.info('Starting to load commands...');
- await loadCommands(this);
- logger.info(`Command loading completed. Total commands loaded: ${this.commands.size}`);
-
- logger.info('Loading handlers...');
- await this.loadHandlers();
- logger.info('Handlers loaded');
-
- logger.info('Logging into Discord...');
- await this.login(this.config.bot.token);
- logger.info('Discord login successful');
-
- logger.info('Registering commands...');
- await this.registerCommands();
- logger.info('Commands registered');
-
- logger.info('Bot is running!');
-
- this.setupCronJobs();
- } catch (error) {
- logger.error('Failed to start bot:', error);
- process.exit(1);
- }
- }
-
- startWebServer() {
- const app = express();
- const port = this.config.api?.port || process.env.PORT || 3000;
- const corsOrigin = this.config.api?.cors?.origin || '*';
-
- // CORS middleware
- app.use((req, res, next) => {
- const allowedOrigins = Array.isArray(corsOrigin) ? corsOrigin : [corsOrigin];
- const origin = req.headers.origin;
-
- if (allowedOrigins.includes('*') || allowedOrigins.includes(origin)) {
- res.header('Access-Control-Allow-Origin', origin || '*');
- }
- res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
- res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
-
- if (req.method === 'OPTIONS') {
- return res.sendStatus(200);
- }
- next();
- });
-
- // Simple rate limiting middleware (requests per minute per IP)
- const requestCounts = new Map();
- const windowMs = 60000; // 1 minute
- const maxRequests = this.config.api?.rateLimit?.max || 100;
-
- app.use((req, res, next) => {
- const ip = req.ip;
- const now = Date.now();
- const windowStart = now - windowMs;
-
- if (!requestCounts.has(ip)) {
- requestCounts.set(ip, []);
- }
-
- const times = requestCounts.get(ip).filter(t => t > windowStart);
-
- if (times.length >= maxRequests) {
- return res.status(429).json({ error: 'Too many requests' });
- }
-
- times.push(now);
- requestCounts.set(ip, times);
- next();
- });
-
- // Health check endpoint
- app.get('/health', (req, res) => {
- const dbStatus = this.db?.getStatus?.() || { isDegraded: 'unknown' };
- const status = {
- status: 'healthy',
- timestamp: new Date().toISOString(),
- uptime: process.uptime(),
- database: {
- connected: dbStatus.connectionType !== 'none',
- degraded: dbStatus.isDegraded,
- type: dbStatus.connectionType
- }
- };
- res.status(200).json(status);
- });
-
- // Readiness check endpoint (only healthy if DB is available)
- app.get('/ready', (req, res) => {
- const dbStatus = this.db?.getStatus?.() || { isDegraded: true };
- const isReady = this.ready && !dbStatus.isDegraded;
-
- if (isReady) {
- return res.status(200).json({
- ready: true,
- message: 'Bot is ready'
- });
- }
-
- res.status(503).json({
- ready: false,
- reason: !this.ready ? 'Bot not Ready' : 'Database degraded'
- });
- });
-
- // Default endpoint
- app.get('/', (req, res) => {
- res.status(200).json({
- message: 'TitanBot System Online',
- version: '2.0.0',
- timestamp: new Date().toISOString()
- });
- });
-
- app.listen(port, () => {
- logger.info(`✅ Web Server running on port ${port}`);
- logger.info(` Health: http://localhost:${port}/health`);
- logger.info(` Ready: http://localhost:${port}/ready`);
- });
- }
-
- setupCronJobs() {
- cron.schedule('0 6 * * *', () => checkBirthdays(this));
- cron.schedule('* * * * *', () => checkGiveaways(this));
- cron.schedule('*/15 * * * *', () => this.updateAllCounters());
- }
-
- async updateAllCounters() {
- if (!this.db) {
- logger.warn('Database not available for counter updates');
- return;
- }
-
- for (const [guildId, guild] of this.guilds.cache) {
- try {
- const counters = await getServerCounters(this, guildId);
- for (const [counterName, counter] of Object.entries(counters)) {
- if (counter && counter.enabled) {
- await updateCounter(this, guild, counter);
- }
- }
- } catch (error) {
- logger.error(`Error updating counters for guild ${guildId}:`, error);
- }
- }
- }
-
- async loadHandlers() {
-const handlers = ['events', 'interactions'];
-
- for (const handler of handlers) {
- try {
- const { default: loadHandler } = await import(`./handlers/${handler}.js`);
- await loadHandler(this);
- logger.info(`Loaded ${handler} handler`);
- } catch (error) {
- if (error.code !== 'MODULE_NOT_FOUND') {
- logger.error(`Error loading ${handler} handler:`, error);
- }
- }
- }
-
- try {
- const { default: loadTodoButtons } = await import('./handlers/todoButtonLoader.js');
- await loadTodoButtons(this);
- logger.info('Loaded todo button handlers');
- } catch (error) {
- logger.error('Error loading todo button handlers:', error);
- }
-
- try {
- const { default: loadTicketButtons } = await import('./handlers/ticketButtonLoader.js');
- await loadTicketButtons(this);
- logger.info('Loaded ticket button handlers');
- } catch (error) {
- logger.error('Error loading ticket button handlers:', error);
- }
-
- try {
- const { default: loadShopButtons } = await import('./handlers/shopButtonLoader.js');
- await loadShopButtons(this);
- logger.info('Loaded shop button handlers');
- } catch (error) {
- logger.error('Error loading shop button handlers:', error);
- }
-
- try {
- const { loadGiveawayButtons } = await import('./handlers/giveawayButtonLoader.js');
- loadGiveawayButtons(this);
- logger.info('Loaded giveaway button handlers');
- } catch (error) {
- logger.error('Error loading giveaway button handlers:', error);
- }
-
- try {
- const { default: loadHelpButtons } = await import('./handlers/helpButtonLoader.js');
- loadHelpButtons(this);
- logger.info('Loaded help button handlers');
- } catch (error) {
- logger.error('Error loading help button handlers:', error);
- }
-
- try {
- const { default: loadHelpSelectMenus } = await import('./handlers/helpSelectMenuLoader.js');
- loadHelpSelectMenus(this);
- logger.info('Loaded help select menu handlers');
- } catch (error) {
- logger.error('Error loading help select menu handlers:', error);
- }
-
- try {
- const { loadVerificationButtons } = await import('./handlers/verificationButtonLoader.js');
- await loadVerificationButtons(this);
- logger.info('Loaded verification button handlers');
- } catch (error) {
- logger.error('Error loading verification button handlers:', error);
- }
-
- try {
- const { default: loadWipedataButtons } = await import('./handlers/wipedataButtonLoader.js');
- await loadWipedataButtons(this);
- logger.info('Loaded wipedata button handlers');
- } catch (error) {
- logger.error('Error loading wipedata button handlers:', error);
- }
- }
-
- async registerCommands() {
- try {
- await registerSlashCommands(this, this.config.bot.guildId);
- } catch (error) {
- logger.error('Error registering commands:', error);
- }
- }
-
- /**
- * Gracefully shutdown the bot
- * Stops cron jobs, closes database connection, and destroys Discord client
- */
- async shutdown(reason = 'UNKNOWN') {
- logger.info(`\n${'='.repeat(60)}`);
- logger.info(`🛑 Graceful Shutdown Initiated (${reason})`);
- logger.info(`${'='.repeat(60)}`);
-
- try {
- // Stop all cron jobs
- logger.info('Stopping cron jobs...');
- cron.getTasks().forEach(task => task.stop());
- logger.info('✅ Cron jobs stopped');
-
- // Close database connection
- if (this.db && this.db.db) {
- logger.info('Closing database connection...');
- try {
- if (this.db.db.pool) {
- await this.db.db.pool.end();
- logger.info('✅ Database connection closed');
- }
- } catch (error) {
- logger.warn('Error closing database pool:', error.message);
- }
- }
-
- // Destroy Discord client
- logger.info('Destroying Discord client...');
- if (this.isReady()) {
- await this.destroy();
- logger.info('✅ Discord client destroyed');
- }
-
- logger.info('✅ Graceful shutdown complete');
- process.exit(0);
- } catch (error) {
- logger.error('Error during graceful shutdown:', error);
- process.exit(1);
- }
- }
-}
-
-try {
- const bot = new TitanBot();
-
- // Setup graceful shutdown handlers
- const setupShutdown = () => {
- process.on('SIGTERM', () => bot.shutdown('SIGTERM'));
- process.on('SIGINT', () => bot.shutdown('SIGINT'));
-
- // Handle uncaught exceptions
- process.on('uncaughtException', (error) => {
- logger.error('Uncaught Exception:', error);
- bot.shutdown('UNCAUGHT_EXCEPTION');
- });
-
- // Handle unhandled promise rejections
- process.on('unhandledRejection', (reason, promise) => {
- logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
- bot.shutdown('UNHANDLED_REJECTION');
- });
- };
-
- setupShutdown();
- bot.start();
-} catch (error) {
- logger.error('Fatal error during bot startup:', error);
- process.exit(1);
-}
-
-export default TitanBot;
diff --git a/src/commands/Birthday/birthday.js b/src/commands/Birthday/birthday.js
deleted file mode 100644
index 364bd64761..0000000000
--- a/src/commands/Birthday/birthday.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import { SlashCommandBuilder, MessageFlags } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-
-import birthdaySet from './modules/birthday_set.js';
-import birthdayInfo from './modules/birthday_info.js';
-import birthdayList from './modules/birthday_list.js';
-import birthdayRemove from './modules/birthday_remove.js';
-import nextBirthdays from './modules/next_birthdays.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('birthday')
- .setDescription('Birthday system commands')
- .addSubcommand(subcommand =>
- subcommand
- .setName('set')
- .setDescription('Set your birthday')
- .addIntegerOption(option =>
- option
- .setName('month')
- .setDescription('Birth month (1-12)')
- .setRequired(true)
- .setMinValue(1)
- .setMaxValue(12)
- )
- .addIntegerOption(option =>
- option
- .setName('day')
- .setDescription('Birth day (1-31)')
- .setRequired(true)
- .setMinValue(1)
- .setMaxValue(31)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName('info')
- .setDescription('View birthday information')
- .addUserOption(option =>
- option
- .setName('user')
- .setDescription('User to check birthday for')
- .setRequired(false)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName('list')
- .setDescription('List all birthdays in the server')
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName('remove')
- .setDescription('Remove your birthday')
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName('next')
- .setDescription('Show upcoming birthdays')
- ),
-
- async execute(interaction, config, client) {
-try {
- const subcommand = interaction.options.getSubcommand();
-
- switch (subcommand) {
- case 'set':
- return birthdaySet.execute(interaction, config, client);
- case 'info':
- return birthdayInfo.execute(interaction, config, client);
- case 'list':
- return birthdayList.execute(interaction, config, client);
- case 'remove':
- return birthdayRemove.execute(interaction, config, client);
- case 'next':
- return nextBirthdays.execute(interaction, config, client);
- default:
- return interaction.reply({
- embeds: [errorEmbed('Error', 'Unknown subcommand')],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error('Birthday command error:', error);
-
- const errorMessage = {
- embeds: [errorEmbed('System Error', 'Could not process birthday command at this time.')],
- flags: MessageFlags.Ephemeral
- };
-
- if (interaction.deferred || interaction.replied) {
- return interaction.editReply(errorMessage);
- } else {
- return interaction.editReply(errorMessage);
- }
- }
- }
-};
-
diff --git a/src/commands/Birthday/modules/birthday_info.js b/src/commands/Birthday/modules/birthday_info.js
deleted file mode 100644
index 8e7dd49ed7..0000000000
--- a/src/commands/Birthday/modules/birthday_info.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { getGuildBirthdays, getMonthName } from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
-try {
- const targetUser = interaction.options.getUser("user") || interaction.user;
- const userId = targetUser.id;
- const guildId = interaction.guildId;
-
- const birthdays = await getGuildBirthdays(client, guildId);
- const birthdayData = birthdays[userId];
-
- if (!birthdayData) {
- return interaction.reply({
- embeds: [errorEmbed(
- "No Birthday Found",
- targetUser.id === interaction.user.id
- ? "You haven't set your birthday yet. Use `/birthday set` to add it!"
- : `${targetUser.username} hasn't set their birthday yet.`
- )]
- });
- }
-
- const monthName = getMonthName(birthdayData.month);
-
- await interaction.editReply({
- embeds: [createEmbed(
- "🎂 Birthday Information",
- targetUser.id === interaction.user.id ? "Your Birthday" : `${targetUser.username}'s Birthday`,
- `**Date:** ${monthName} ${birthdayData.day}\n**User:** ${targetUser.toString()}`,
- 0xff69b4
- )]
- });
- } catch (error) {
- console.error("Birthday info command error:", error);
- await interaction.editReply({
- embeds: [errorEmbed("Error", "Failed to fetch birthday information.")]
- });
- }
- }
-};
-
diff --git a/src/commands/Birthday/modules/birthday_list.js b/src/commands/Birthday/modules/birthday_list.js
deleted file mode 100644
index 1cc9aa2149..0000000000
--- a/src/commands/Birthday/modules/birthday_list.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { getGuildBirthdays, getMonthName } from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
-try {
- const guildId = interaction.guildId;
- const birthdays = await getGuildBirthdays(client, guildId);
-
- if (!birthdays || Object.keys(birthdays).length === 0) {
- return interaction.reply({
- embeds: [errorEmbed("No Birthdays", "No birthdays have been set in this server yet.")]
- });
- }
-
- const embed = createEmbed("🎂 Server Birthdays", `Found ${Object.keys(birthdays).length} birthdays in ${interaction.guild.name}`);
-
- const sortedBirthdays = Object.entries(birthdays)
- .map(([userId, data]) => ({
- userId,
- month: data.month,
- day: data.day,
- monthName: getMonthName(data.month)
- }))
- .sort((a, b) => {
- if (a.month !== b.month) return a.month - b.month;
- return a.day - b.day;
- });
-
- let birthdayList = "";
- sortedBirthdays.forEach((birthday, index) => {
- const member = interaction.guild.members.cache.get(birthday.userId);
- const userName = member ? member.user.username : `User ${birthday.userId}`;
- birthdayList += `${index + 1}. **${userName}** - ${birthday.monthName} ${birthday.day}\n`;
- });
-
- embed.setDescription(birthdayList || "No birthdays found");
- embed.setFooter({ text: `Total: ${sortedBirthdays.length} birthdays` });
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Birthday list command error:", error);
- await interaction.editReply({
- embeds: [errorEmbed("Error", "Failed to fetch birthday list.")]
- });
- }
- }
-};
-
diff --git a/src/commands/Birthday/modules/birthday_remove.js b/src/commands/Birthday/modules/birthday_remove.js
deleted file mode 100644
index 7ec46ae72a..0000000000
--- a/src/commands/Birthday/modules/birthday_remove.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { deleteBirthday } from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
-try {
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
-
- const success = await deleteBirthday(client, guildId, userId);
-
- if (success) {
- await interaction.reply({
- embeds: [successEmbed(
- "Birthday Removed 🗑ï¸",
- "Your birthday has been successfully removed from the server."
- )]
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed(
- "No Birthday Found",
- "You don't have a birthday set to remove."
- )]
- });
- }
- } catch (error) {
- console.error("Forgot birthday command error:", error);
- await interaction.editReply({
- embeds: [errorEmbed("Error", "Failed to remove your birthday.")]
- });
- }
- }
-};
-
diff --git a/src/commands/Birthday/modules/birthday_set.js b/src/commands/Birthday/modules/birthday_set.js
deleted file mode 100644
index a3e88c2738..0000000000
--- a/src/commands/Birthday/modules/birthday_set.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { getGuildBirthdays, setBirthday, deleteBirthday, getMonthName } from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
-try {
- const month = interaction.options.getInteger("month");
- const day = interaction.options.getInteger("day");
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
-
- const currentYear = new Date().getFullYear();
- const date = new Date(currentYear, month - 1, day);
-
- if (isNaN(date.getTime()) || date.getMonth() !== month - 1 || date.getDate() !== day) {
- return interaction.reply({
- embeds: [errorEmbed("Invalid Date", "Please enter a valid date (e.g., February 29th only exists in leap years).")]
- });
- }
-
- const success = await setBirthday(client, guildId, userId, month, day);
-
- if (success) {
- const monthName = getMonthName(month);
- await interaction.editReply({
- embeds: [successEmbed(
- "Birthday Set! 🎂",
- `Your birthday has been set to **${monthName} ${day}**!`
- )]
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed("Error", "Failed to set your birthday. Please try again.")]
- });
- }
- } catch (error) {
- console.error("Birthday command error:", error);
- await interaction.editReply({
- embeds: [errorEmbed("Error", "An error occurred while setting your birthday.")]
- });
- }
- }
-};
-
diff --git a/src/commands/Birthday/modules/next_birthdays.js b/src/commands/Birthday/modules/next_birthdays.js
deleted file mode 100644
index 744d874a7f..0000000000
--- a/src/commands/Birthday/modules/next_birthdays.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { getGuildBirthdays, getMonthName } from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
-try {
- const birthdays = await getGuildBirthdays(client, interaction.guildId);
-
- if (!birthdays || Object.keys(birthdays).length === 0) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- 'No Birthdays Found',
- 'No birthdays have been set up in this server yet. Use `/birthday set` to add birthdays!'
- )
- ]
- });
- }
-
- const today = new Date();
- const currentYear = today.getFullYear();
-
- const upcomingBirthdays = [];
-
- for (const [userId, userData] of Object.entries(birthdays)) {
- let nextBirthday = new Date(currentYear, userData.month - 1, userData.day);
-
- if (nextBirthday < today) {
- nextBirthday = new Date(currentYear + 1, userData.month - 1, userData.day);
- }
-
- upcomingBirthdays.push({
- userId,
- month: userData.month,
- day: userData.day,
- date: nextBirthday,
- daysUntil: Math.ceil((nextBirthday - today) / (1000 * 60 * 60 * 24))
- });
- }
-
- upcomingBirthdays.sort((a, b) => a.daysUntil - b.daysUntil);
-
- const next5 = upcomingBirthdays.slice(0, 5);
-
- if (next5.length === 0) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- 'No Upcoming Birthdays',
- 'No upcoming birthdays found in the next year.'
- )
- ]
- });
- }
-
- const embed = createEmbed(
- '🎂 Next 5 Upcoming Birthdays',
- `Here are the next 5 birthdays in ${interaction.guild.name}:`
- );
-
- for (let i = 0; i < next5.length; i++) {
- const birthday = next5[i];
- const member = await interaction.guild.members.fetch(birthday.userId).catch(() => null);
- const userName = member ? member.user.username : `User ${birthday.userId}`;
- const monthName = getMonthName(birthday.month);
-
- let timeUntil = '';
- if (birthday.daysUntil === 0) {
- timeUntil = '🎉 **Today!**';
- } else if (birthday.daysUntil === 1) {
- timeUntil = '📅 **Tomorrow!**';
- } else {
- timeUntil = `In ${birthday.daysUntil} day${birthday.daysUntil > 1 ? 's' : ''}`;
- }
-
- embed.addFields({
- name: `${i + 1}. ${userName}`,
- value: `📅 **Date:** ${monthName} ${birthday.day}\nⰠ**Time:** ${timeUntil}`,
- inline: false
- });
- }
-
- embed.setFooter({
- text: 'Use /birthday set to add your birthday!',
- iconURL: interaction.guild.iconURL()
- });
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error('Next birthdays command error:', error);
- await interaction.editReply({ embeds: [errorEmbed('Error', 'Failed to fetch upcoming birthdays.')] });
- }
- }
-};
-
diff --git a/src/commands/Community/app-admin.js b/src/commands/Community/app-admin.js
deleted file mode 100644
index 734275dbd9..0000000000
--- a/src/commands/Community/app-admin.js
+++ /dev/null
@@ -1,884 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import {
- getApplicationSettings,
- saveApplicationSettings,
- getApplication,
- getApplications,
- updateApplication,
- getApplicationRoles,
- saveApplicationRoles
-} from '../../utils/database.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("app-admin")
- .setDescription("Manage staff applications")
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
- .addSubcommand((subcommand) =>
- subcommand
- .setName("setup")
- .setDescription("Configure application settings")
- .addChannelOption((option) =>
- option
- .setName("log-channel")
- .setDescription(
- "Channel where new applications will be logged",
- )
- .setRequired(false),
- )
- .addRoleOption((option) =>
- option
- .setName("manager-role")
- .setDescription(
- "Role that can manage applications (can be used multiple times)",
- )
- .setRequired(false),
- )
- .addBooleanOption((option) =>
- option
- .setName("enabled")
- .setDescription("Enable or disable applications")
- .setRequired(false),
- ),
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("view")
- .setDescription("View a specific application")
- .addStringOption((option) =>
- option
- .setName("id")
- .setDescription("The application ID")
- .setRequired(true),
- ),
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("review")
- .setDescription("Approve or deny an application")
- .addStringOption((option) =>
- option
- .setName("id")
- .setDescription("The application ID")
- .setRequired(true),
- )
- .addStringOption((option) =>
- option
- .setName("action")
- .setDescription("Approve or deny the application")
- .setRequired(true)
- .addChoices(
- { name: "Approve", value: "approve" },
- { name: "Deny", value: "deny" },
- ),
- )
- .addStringOption((option) =>
- option
- .setName("reason")
- .setDescription("Reason for approval/denial")
- .setRequired(false),
- ),
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("list")
- .setDescription("List all applications")
- .addStringOption((option) =>
- option
- .setName("status")
- .setDescription("Filter by status")
- .addChoices(
- { name: "Pending", value: "pending" },
- { name: "Approved", value: "approved" },
- { name: "Denied", value: "denied" },
- ),
- )
- .addStringOption((option) =>
- option.setName("role").setDescription("Filter by role ID"),
- )
- .addUserOption((option) =>
- option.setName("user").setDescription("Filter by user"),
- )
- .addNumberOption((option) =>
- option
- .setName("limit")
- .setDescription(
- "Maximum number of applications to show (default: 10)",
- )
- .setMinValue(1)
- .setMaxValue(25),
- ),
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("roles")
- .setDescription("Manage application roles")
- .addStringOption((option) =>
- option
- .setName("action")
- .setDescription("Action to perform")
- .setRequired(true)
- .addChoices(
- { name: "Add Role", value: "add" },
- { name: "Remove Role", value: "remove" },
- { name: "List Roles", value: "list" }
- )
- )
- .addRoleOption((option) =>
- option
- .setName("role")
- .setDescription("The role to add/remove")
- .setRequired(false)
- )
- .addStringOption((option) =>
- option
- .setName("name")
- .setDescription("Custom name for the application")
- .setRequired(false)
- .setMaxLength(50)
- )
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("questions")
- .setDescription("Configure application questions")
- ),
-
- category: "Community",
-
- async execute(interaction) {
- if (!interaction.inGuild()) {
- return interaction.reply({
- embeds: [errorEmbed("This command can only be used in a server.")],
- flags: ["Ephemeral"],
- });
- }
-const { options, guild, member } = interaction;
- const subcommand = options.getSubcommand();
-
- const settings = await getApplicationSettings(interaction.client, guild.id);
- const isManager =
- member.permissions.has(PermissionFlagsBits.ManageGuild) ||
- (settings.managerRoles &&
- settings.managerRoles.some((roleId) =>
- member.roles.cache.has(roleId),
- ));
-
- if (!isManager) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "You do not have permission to manage applications.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- try {
- if (subcommand === "setup") {
- await handleSetup(interaction, settings);
- } else if (subcommand === "view") {
- await handleView(interaction);
- } else if (subcommand === "review") {
- await handleReview(interaction);
- } else if (subcommand === "list") {
- await handleList(interaction);
- } else if (subcommand === "roles") {
- await handleRoles(interaction);
- } else if (subcommand === "questions") {
- await handleQuestions(interaction, settings);
- }
- } catch (error) {
- console.error("Error in app-admin command:", error);
- interaction.editReply({
- embeds: [
- errorEmbed("An error occurred while processing your request."),
- ],
- flags: ["Ephemeral"],
- });
- }
- }
-};
-
-async function handleSetup(interaction, settings) {
- const logChannel = interaction.options.getChannel("log-channel");
- const managerRole = interaction.options.getRole("manager-role");
- const enabled = interaction.options.getBoolean("enabled");
-
- const updates = {};
-
- if (logChannel) {
- if (!logChannel.isTextBased()) {
- return interaction.editReply({
- embeds: [errorEmbed("The log channel must be a text channel.")],
- flags: ["Ephemeral"],
- });
- }
- updates.logChannelId = logChannel.id;
- }
-
- if (managerRole) {
- const managerRoles = new Set(settings.managerRoles || []);
- if (managerRoles.has(managerRole.id)) {
- managerRoles.delete(managerRole.id);
- updates.managerRoles = Array.from(managerRoles);
- } else {
- managerRoles.add(managerRole.id);
- updates.managerRoles = Array.from(managerRoles);
- }
- }
-
- if (enabled !== null) {
- updates.enabled = enabled;
- }
-
- if (Object.keys(updates).length === 0) {
- return showCurrentSettings(interaction, settings);
- }
-
- await saveApplicationSettings(
- interaction.client,
- interaction.guild.id,
- updates,
- );
- const updatedSettings = { ...settings, ...updates };
-
- await showCurrentSettings(interaction, updatedSettings);
-
-}
-
-async function showCurrentSettings(interaction, settings) {
- const embed = createEmbed({ title: "Application Settings", description: "Current configuration for the application system.", });
-
- embed.addFields(
- {
- name: "Status",
- value: settings.enabled ? "✅ Enabled" : "⌠Disabled",
- inline: true,
- },
- {
- name: "Log Channel",
- value: settings.logChannelId
- ? `<#${settings.logChannelId}>`
- : "Not set",
- inline: true,
- },
- {
- name: "Manager Roles",
- value:
- settings.managerRoles?.length > 0
- ? settings.managerRoles.map((id) => `<@&${id}>`).join(", ")
- : "Server Admins only",
- inline: false,
- },
- {
- name: "Default Questions",
- value:
- `There are ${settings.questions?.length || 0} default questions configured.\n` +
- `Use \`/app-admin questions\` to edit them.`,
- inline: false,
- },
- );
-
- await interaction.editReply({
- embeds: [embed],
- flags: ["Ephemeral"],
- });
-}
-
-async function handleView(interaction) {
- const appId = interaction.options.getString("id");
- const application = await getApplication(
- interaction.client,
- interaction.guild.id,
- appId,
- );
-
- if (!application) {
- return interaction.editReply({
- embeds: [errorEmbed("Application not found.")],
- flags: ["Ephemeral"],
- });
- }
-
- const statusColor = application.status === "approved" ? "#00FF00" : (application.status === "denied" ? "#FF0000" : "#FFFF00");
- const embed = createEmbed({ title: `Application #${application.id} - ${application.roleName}`, description: `**User:** <@${application.userId}> (${application.userId})\n **Status:** ${application.status.charAt(0).toUpperCase() + application.status.slice(1)}\n` +
- (application.reviewer
- ? `**Reviewed by:** <@${application.reviewer}>\n`
- : "") +
- (application.reviewMessage
- ? `**Note:** ${application.reviewMessage}\n`
- : "") +
- `**Submitted on:** ${new Date(application.createdAt).toLocaleString()}`,
- }).setColor(statusColor);
-
- if (application.avatar) {
- embed.setThumbnail(application.avatar);
- }
-
- application.answers.forEach((answer, index) => {
- embed.addFields({
- name: answer.question,
- value:
- answer.answer.length > 1000
- ? answer.answer.substring(0, 997) + "..."
- : answer.answer,
- inline: false,
- });
- });
-
- if (application.status === "pending") {
- const row = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`app_approve_${application.id}`)
- .setLabel("Approve")
- .setStyle(ButtonStyle.Success)
- .setEmoji("✅"),
- new ButtonBuilder()
- .setCustomId(`app_deny_${application.id}`)
- .setLabel("Deny")
- .setStyle(ButtonStyle.Danger)
- .setEmoji("âŒ"),
- );
-
- await interaction.editReply({
- embeds: [embed],
- components: [row],
- flags: ["Ephemeral"],
- });
- } else {
- await interaction.editReply({
- embeds: [embed],
- flags: ["Ephemeral"],
- });
- }
-}
-
-async function handleReview(interaction) {
- const appId = interaction.options.getString("id");
- const action = interaction.options.getString("action");
- const reason =
- interaction.options.getString("reason") || "No reason provided.";
-
- const application = await getApplication(
- interaction.client,
- interaction.guild.id,
- appId,
- );
- if (!application) {
- return interaction.editReply({
- embeds: [errorEmbed("Application not found.")],
- flags: ["Ephemeral"],
- });
- }
-
- if (application.status !== "pending") {
- return interaction.editReply({
- embeds: [
- errorEmbed("This application has already been processed."),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- const status = action === "approve" ? "approved" : "denied";
- const statusColor = status === "approved" ? "#00FF00" : "#FF0000";
-
- await updateApplication(interaction.client, interaction.guild.id, appId, {
- status,
- reviewer: interaction.user.id,
- reviewMessage: reason,
- reviewedAt: new Date().toISOString(),
- });
-
- try {
- const user = await interaction.client.users.fetch(application.userId);
- const dmEmbed = createEmbed(
- `Application ${status.charAt(0).toUpperCase() + status.slice(1)}`,
- `Your application for **${application.roleName}** has been **${status}**.\n` +
- `**Note:** ${reason}\n\n` +
- `Use \`/apply status id:${appId}\` to view details.`,
- statusColor,
- );
-
- await user.send({ embeds: [dmEmbed] });
- } catch (error) {
- console.error("Error sending DM to user:", error);
- }
-
- if (application.logMessageId && application.logChannelId) {
- try {
- const logChannel = interaction.guild.channels.cache.get(
- application.logChannelId,
- );
- if (logChannel) {
- const logMessage = await logChannel.messages.fetch(
- application.logMessageId,
- );
- if (logMessage) {
- const embed = logMessage.embeds[0];
- if (embed) {
- const newEmbed = EmbedBuilder.from(embed)
- .setColor(statusColor)
- .spliceFields(0, 1, {
- name: "Status",
- value:
- status.charAt(0).toUpperCase() +
- status.slice(1),
- });
-
- await logMessage.edit({
- embeds: [newEmbed],
-components: [],
- });
- }
- }
- }
- } catch (error) {
- console.error("Error updating log message:", error);
- }
- }
-
- if (action === "approve") {
- try {
- const member = await interaction.guild.members.fetch(
- application.userId,
- );
- await member.roles.add(application.role);
- } catch (error) {
- console.error("Error assigning role:", error);
- }
- }
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- `Application ${status}`,
- `The application has been ${status}.`,
- ),
- ],
- flags: ["Ephemeral"],
- });
-}
-
-async function handleList(interaction) {
- const status = interaction.options.getString("status");
- const user = interaction.options.getUser("user");
- const limit = interaction.options.getNumber("limit") || 10;
-
- const filters = {};
- if (status) filters.status = status;
-
- let applications = await getApplications(
- interaction.client,
- interaction.guild.id,
- filters,
- );
-
- if (user) {
- applications = applications.filter((app) => app.userId === user.id);
- }
-
- if (applications.length === 0) {
- const applicationRoles = await getApplicationRoles(interaction.client, interaction.guild.id);
-
- if (applicationRoles.length > 0) {
- const embed = createEmbed({
- title: "No Applications Found",
- description: "No submitted applications found matching the specified criteria.\n\nHowever, the following application roles are configured:"
- });
-
- applicationRoles.forEach((appRole, index) => {
- const role = interaction.guild.roles.cache.get(appRole.roleId);
- embed.addFields({
- name: `${index + 1}. ${appRole.name}`,
- value: `**Role:** ${role ? `<@&${appRole.roleId}>` : 'Role not found'}\n**Available for applications:** Yes`,
- inline: false
- });
- });
-
- embed.setFooter({
- text: "Users can apply with /apply submit or see available roles with /apply list"
- });
-
- return interaction.editReply({ embeds: [embed], flags: ["Ephemeral"] });
- } else {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "No applications found and no application roles configured.\n" +
- "Use `/app-admin roles add` to configure application roles first."
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
- }
-
- applications = applications
- .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
- .slice(0, limit);
-
- const embed = createEmbed({ title: "Submitted Applications", description: `Showing ${applications.length} applications.`, });
-
- applications.forEach((app) => {
- const status = app.status.charAt(0).toUpperCase() + app.status.slice(1);
- const statusEmoji =
- {
- Pending: "🟡",
- Approved: "🟢",
- Denied: "🔴",
- }[status] || "⚪";
-
- embed.addFields({
- name: `${statusEmoji} ${app.roleName} - ${app.username}`,
- value:
- `**ID:** \`${app.id}\`\n` +
- `**Status:** ${status}\n` +
- `**Date:** ${new Date(app.createdAt).toLocaleString()}`,
- inline: true,
- });
- });
-
- await interaction.editReply({
- embeds: [embed],
- flags: ["Ephemeral"],
- });
-}
-
-async function handleQuestions(interaction, settings) {
- const modal = new ModalBuilder()
- .setCustomId("edit_questions")
- .setTitle("Edit Application Questions");
-
- const questions =
- settings.questions || ["Question 1", "Question 2"];
-
- for (let i = 0; i < 5; i++) {
- const input = new TextInputBuilder()
- .setCustomId(`q${i}`)
- .setLabel(`Question ${i + 1}`)
- .setStyle(TextInputStyle.Short)
-.setRequired(i === 0)
- .setValue(questions[i] || "");
-
- const row = new ActionRowBuilder().addComponents(input);
- modal.addComponents(row);
- }
-
- await interaction.showModal(modal);
-
- try {
- const modalResponse = await interaction.awaitModalSubmit({
-time: 30 * 60 * 1000,
- filter: (i) =>
- i.customId === "edit_questions" &&
- i.user.id === interaction.user.id,
- });
-
- const newQuestions = [];
- for (let i = 0; i < 5; i++) {
- const question = modalResponse.fields
- .getTextInputValue(`q${i}`)
- .trim();
- if (question) {
- newQuestions.push(question);
- }
- }
-
- if (newQuestions.length === 0) {
- return modalResponse.reply({
- embeds: [errorEmbed("You must provide at least one question.")],
- flags: ["Ephemeral"],
- });
- }
-
- await saveApplicationSettings(
- interaction.client,
- interaction.guild.id,
- {
- questions: newQuestions,
- },
- );
-
- await modalResponse.reply({
- embeds: [
- successEmbed(
- "Questions Updated",
- `The application questions have been updated.\n\n` +
- newQuestions.map((q, i) => `${i + 1}. ${q}`).join("\n"),
- ),
- ],
- flags: ["Ephemeral"],
- });
- } catch (error) {
- if (error.message.includes("timeout")) {
- return;
- }
- console.error("Error processing questions:", error);
- }
-}
-
-async function handleRoles(interaction) {
- const action = interaction.options.getString("action");
- const role = interaction.options.getRole("role");
- const name = interaction.options.getString("name");
-
- try {
- const currentRoles = await getApplicationRoles(interaction.client, interaction.guild.id);
-
- if (action === "list") {
- if (currentRoles.length === 0) {
- return interaction.editReply({
- embeds: [errorEmbed("No application roles have been configured.")],
- flags: ["Ephemeral"],
- });
- }
-
- const embed = createEmbed({
- title: "Application Roles",
- description: "Here are the configured application roles:"
- });
-
- currentRoles.forEach((appRole, index) => {
- const roleObj = interaction.guild.roles.cache.get(appRole.roleId);
- embed.addFields({
- name: `${index + 1}. ${appRole.name}`,
- value: `**Role:** ${roleObj ? `<@&${appRole.roleId}>` : 'Role not found'}\n**ID:** \`${appRole.roleId}\``,
- inline: false
- });
- });
-
- return interaction.editReply({ embeds: [embed], flags: ["Ephemeral"] });
- }
-
- if (action === "add") {
- if (!role) {
- return interaction.editReply({
- embeds: [errorEmbed("You must specify a role to add.")],
- flags: ["Ephemeral"],
- });
- }
-
- const customName = name || role.name;
-
- if (currentRoles.some(appRole => appRole.roleId === role.id)) {
- return interaction.editReply({
- embeds: [errorEmbed("This role is already configured for applications.")],
- flags: ["Ephemeral"],
- });
- }
-
- currentRoles.push({
- roleId: role.id,
- name: customName
- });
-
- await saveApplicationRoles(interaction.client, interaction.guild.id, currentRoles);
-
- return interaction.editReply({
- embeds: [successEmbed(
- "Role Added",
- `**${customName}** has been added to the application system.\nUsers can now apply for this role using \`/apply submit\`.`
- )],
- flags: ["Ephemeral"],
- });
- }
-
- if (action === "remove") {
- if (!role) {
- return interaction.editReply({
- embeds: [errorEmbed("You must specify a role to remove.")],
- flags: ["Ephemeral"],
- });
- }
-
- const roleIndex = currentRoles.findIndex(appRole => appRole.roleId === role.id);
- if (roleIndex === -1) {
- return interaction.editReply({
- embeds: [errorEmbed("This role is not configured for applications.")],
- flags: ["Ephemeral"],
- });
- }
-
- const removedRole = currentRoles.splice(roleIndex, 1)[0];
- await saveApplicationRoles(interaction.client, interaction.guild.id, currentRoles);
-
- return interaction.editReply({
- embeds: [successEmbed(
- "Role Removed",
- `**${removedRole.name}** has been removed from the application system.`
- )],
- flags: ["Ephemeral"],
- });
- }
- } catch (error) {
- console.error("Error handling roles command:", error);
- return interaction.editReply({
- embeds: [errorEmbed("An error occurred while managing application roles.")],
- flags: ["Ephemeral"],
- });
- }
-}
-
-export async function handleApplicationButton(interaction) {
- if (!interaction.isButton()) return;
-
- const customId = interaction.customId;
- if (!customId.startsWith('app_approve_') && !customId.startsWith('app_deny_')) return;
-
- const [action, appId] = customId.split('_');
- const isApprove = action === 'app';
-
- try {
- const application = await getApplication(interaction.client, interaction.guild.id, appId);
- if (!application) {
- return interaction.reply({
- embeds: [errorEmbed('Application not found.')],
- flags: ["Ephemeral"]
- });
- }
-
- if (application.status !== 'pending') {
- return interaction.reply({
- embeds: [errorEmbed('This application has already been processed.')],
- flags: ["Ephemeral"]
- });
- }
-
- const settings = await getApplicationSettings(interaction.client, interaction.guild.id);
- const isManager = interaction.member.permissions.has(PermissionFlagsBits.ManageGuild) ||
- (settings.managerRoles && settings.managerRoles.some(roleId => interaction.member.roles.cache.has(roleId)));
-
- if (!isManager) {
- return interaction.reply({
- embeds: [errorEmbed('You do not have permission to manage applications.')],
- flags: ["Ephemeral"]
- });
- }
-
- const modal = new ModalBuilder()
- .setCustomId(`app_review_${appId}_${isApprove ? 'approve' : 'deny'}`)
- .setTitle(`${isApprove ? 'Approve' : 'Deny'} Application`)
- .addComponents(
- new ActionRowBuilder().addComponents(
- new TextInputBuilder()
- .setCustomId('reason')
- .setLabel('Reason (optional)')
- .setStyle(TextInputStyle.Paragraph)
- .setRequired(false)
- .setMaxLength(500)
- )
- );
-
- await interaction.showModal(modal);
-
- } catch (error) {
- console.error('Error handling application button:', error);
- await interaction.editReply({
- embeds: [errorEmbed('An error occurred while processing the application.')],
- flags: ["Ephemeral"]
- });
- }
-}
-
-export async function handleApplicationReviewModal(interaction) {
- if (!interaction.isModalSubmit()) return;
-
- const customId = interaction.customId;
- if (!customId.startsWith('app_review_')) return;
-
- const [, appId, action] = customId.split('_');
- const reason = interaction.fields.getTextInputValue('reason') || 'No reason provided.';
- const isApprove = action === 'approve';
-
- try {
- const application = await getApplication(interaction.client, interaction.guild.id, appId);
- if (!application) {
- return interaction.reply({
- embeds: [errorEmbed('Application not found.')],
- flags: ["Ephemeral"]
- });
- }
-
- const status = isApprove ? 'approved' : 'denied';
- await updateApplication(interaction.client, interaction.guild.id, appId, {
- status,
- reviewer: interaction.user.id,
- reviewMessage: reason,
- reviewedAt: new Date().toISOString()
- });
-
- try {
- const user = await interaction.client.users.fetch(application.userId);
- const dmEmbed = createEmbed(
- `Application ${status.charAt(0).toUpperCase() + status.slice(1)}`,
- `Your application for **${application.roleName}** has been **${status}**.\n` +
- `**Note:** ${reason}\n\n` +
- `Use \`/apply status id:${appId}\` to view details.`,
- isApprove ? '#00FF00' : '#FF0000'
- );
-
- await user.send({ embeds: [dmEmbed] });
- } catch (error) {
- console.error('Error sending DM to user:', error);
- }
-
- if (application.logMessageId && application.logChannelId) {
- try {
- const logChannel = interaction.guild.channels.cache.get(application.logChannelId);
- if (logChannel) {
- const logMessage = await logChannel.messages.fetch(application.logMessageId);
- if (logMessage) {
- const embed = logMessage.embeds[0];
- if (embed) {
- const newEmbed = EmbedBuilder.from(embed)
- .setColor(isApprove ? '#00FF00' : '#FF0000')
- .spliceFields(0, 1, {
- name: 'Status',
- value: status.charAt(0).toUpperCase() + status.slice(1)
- });
-
- await logMessage.edit({
- embeds: [newEmbed],
- components: []
- });
- }
- }
- }
- } catch (error) {
- console.error('Error updating log message:', error);
- }
- }
-
- if (isApprove) {
- try {
- const member = await interaction.guild.members.fetch(application.userId);
- await member.roles.add(application.role);
- } catch (error) {
- console.error('Error assigning role:', error);
- }
- }
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- `Application ${status}`,
- `The application has been ${status}.`
- )
- ],
- flags: ["Ephemeral"]
- });
-
- } catch (error) {
- console.error('Error processing application review:', error);
- await interaction.editReply({
- embeds: [errorEmbed('An error occurred while processing the application.')],
- flags: ["Ephemeral"]
- });
- }
-}
-
diff --git a/src/commands/Community/apply.js b/src/commands/Community/apply.js
deleted file mode 100644
index 0b9992c246..0000000000
--- a/src/commands/Community/apply.js
+++ /dev/null
@@ -1,348 +0,0 @@
-import { SlashCommandBuilder, ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import {
- getApplicationSettings,
- getUserApplications,
- createApplication,
- getApplication,
- getApplicationRoles,
- updateApplication
-} from '../../utils/database.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("apply")
- .setDescription("Manage role applications")
- .addSubcommand((subcommand) =>
- subcommand
- .setName("submit")
- .setDescription("Submit an application for a role")
- .addStringOption((option) =>
- option
- .setName("application")
- .setDescription("The application you want to submit")
- .setRequired(true)
- ),
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("status")
- .setDescription("Check the status of your application")
- .addStringOption((option) =>
- option
- .setName("id")
- .setDescription("Application ID (leave empty to see all)")
- .setRequired(false),
- ),
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("list")
- .setDescription("List available applications to apply for"),
- ),
-
- category: "Community",
-
- async execute(interaction) {
- if (!interaction.inGuild()) {
- return interaction.reply({
- embeds: [errorEmbed("This command can only be used in a server.")],
- flags: ["Ephemeral"],
- });
- }
-const { options, guild, member } = interaction;
- const subcommand = options.getSubcommand();
-
- try {
- const settings = await getApplicationSettings(
- interaction.client,
- guild.id,
- );
- if (!settings.enabled) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Applications are currently disabled in this server.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- if (subcommand === "submit") {
- await handleSubmit(interaction, settings);
- } else if (subcommand === "status") {
- await handleStatus(interaction);
- } else if (subcommand === "list") {
- await handleList(interaction);
- }
- } catch (error) {
- console.error("Error in apply command:", error);
- interaction.editReply({
- embeds: [
- errorEmbed("An error occurred while processing your request."),
- ],
- flags: ["Ephemeral"],
- });
- }
- }
-};
-
-export async function handleApplicationModal(interaction) {
- if (!interaction.isModalSubmit()) return;
-
- const customId = interaction.customId;
- if (!customId.startsWith('app_modal_')) return;
-
- const roleId = customId.split('_')[2];
-
- const applicationRoles = await getApplicationRoles(interaction.client, interaction.guild.id);
- const applicationRole = applicationRoles.find(appRole => appRole.roleId === roleId);
-
- if (!applicationRole) {
- return interaction.editReply({
- embeds: [errorEmbed('Application configuration not found.')],
- flags: ["Ephemeral"]
- });
- }
-
- const role = interaction.guild.roles.cache.get(roleId);
-
- if (!role) {
- return interaction.editReply({
- embeds: [errorEmbed('Role not found.')],
- flags: ["Ephemeral"]
- });
- }
-
- const answers = [];
- const settings = await getApplicationSettings(interaction.client, interaction.guild.id);
- const questions = settings.questions || ["Why do you want this role?", "What is your experience?"];
-
- for (let i = 0; i < questions.length; i++) {
- const answer = interaction.fields.getTextInputValue(`q${i}`);
- answers.push({
- question: questions[i],
- answer: answer
- });
- }
-
- try {
- const application = await createApplication(interaction.client, {
- guildId: interaction.guild.id,
- userId: interaction.user.id,
- roleId: roleId,
- roleName: applicationRole.name,
- username: interaction.user.tag,
- avatar: interaction.user.displayAvatarURL(),
- answers: answers
- });
-
- const embed = successEmbed(
- 'Application Submitted',
- `Your application for **${applicationRole.name}** has been submitted successfully!\n\n` +
- `Application ID: \`${application.id}\`\n` +
- `You can check the status with \`/apply status id:${application.id}\``
- );
-
- await interaction.editReply({ embeds: [embed], flags: ["Ephemeral"] });
-
- const settings = await getApplicationSettings(interaction.client, interaction.guild.id);
- if (settings.logChannelId) {
- const logChannel = interaction.guild.channels.cache.get(settings.logChannelId);
- if (logChannel) {
- const logEmbed = createEmbed({
- title: '📠New Application',
- description: `**User:** <@${interaction.user.id}> (${interaction.user.tag})\n` +
- `**Application:** ${applicationRole.name}\n` +
- `**Role:** ${role.name}\n` +
- `**Application ID:** \`${application.id}\`\n` +
- `**Status:** Pending`
- }).setColor('#FFFF00');
-
- const logMessage = await logChannel.send({ embeds: [logEmbed] });
-
- await updateApplication(interaction.client, interaction.guild.id, application.id, {
- logMessageId: logMessage.id,
- logChannelId: settings.logChannelId
- });
- }
- }
-
- } catch (error) {
- console.error('Error creating application:', error);
- if (interaction.replied || interaction.deferred) {
- await interaction.followUp({
- embeds: [errorEmbed('Failed to submit application. Please try again later.')],
- flags: ["Ephemeral"]
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed('Failed to submit application. Please try again later.')],
- flags: ["Ephemeral"]
- });
- }
- }
-}
-
-async function handleList(interaction) {
- try {
- const applicationRoles = await getApplicationRoles(interaction.client, interaction.guild.id);
-
- if (applicationRoles.length === 0) {
- return interaction.editReply({
- embeds: [errorEmbed("No applications are currently available.")],
- flags: ["Ephemeral"],
- });
- }
-
- const embed = createEmbed({
- title: "Available Applications",
- description: "Here are the roles you can apply for:"
- });
-
- applicationRoles.forEach((appRole, index) => {
- const role = interaction.guild.roles.cache.get(appRole.roleId);
- embed.addFields({
- name: `${index + 1}. ${appRole.name}`,
- value: `**Role:** ${role ? `<@&${appRole.roleId}>` : 'Role not found'}\n` +
- `**Apply with:** \`/apply submit application:"${appRole.name}"\``,
- inline: false
- });
- });
-
- embed.setFooter({
- text: "Use /apply submit application: to apply for any of these roles."
- });
-
- return interaction.editReply({ embeds: [embed], flags: ["Ephemeral"] });
- } catch (error) {
- console.error('Error listing applications:', error);
- return interaction.editReply({
- embeds: [errorEmbed('Failed to load applications. Please try again later.')],
- flags: ["Ephemeral"]
- });
- }
-}
-
-async function handleSubmit(interaction, settings) {
- const applicationName = interaction.options.getString("application");
- const member = interaction.member;
-
- const applicationRoles = await getApplicationRoles(interaction.client, interaction.guild.id);
-
- const applicationRole = applicationRoles.find(appRole =>
- appRole.name.toLowerCase() === applicationName.toLowerCase()
- );
-
- if (!applicationRole) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Application not found.",
- "Use `/apply list` to see available applications."
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- const userApps = await getUserApplications(
- interaction.client,
- interaction.guild.id,
- interaction.user.id,
- );
- const pendingApp = userApps.find((app) => app.status === "pending");
-
- if (pendingApp) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- `You already have a pending application. Please wait for it to be reviewed.`,
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- const role = interaction.guild.roles.cache.get(applicationRole.roleId);
- if (!role) {
- return interaction.editReply({
- embeds: [errorEmbed('The role for this application no longer exists.')],
- flags: ["Ephemeral"]
- });
- }
-
- const modal = new ModalBuilder()
- .setCustomId(`app_modal_${applicationRole.roleId}`)
- .setTitle(`Application for ${applicationRole.name}`);
-
- const questions =
- settings.questions || ["Why do you want this role?", "What is your experience?"];
-
- questions.forEach((question, index) => {
- const input = new TextInputBuilder()
- .setCustomId(`q${index}`)
- .setLabel(
- question.length > 45
- ? `${question.substring(0, 42)}...`
- : question,
- )
- .setStyle(TextInputStyle.Paragraph)
- .setRequired(true)
- .setMaxLength(1000);
-
- const row = new ActionRowBuilder().addComponents(input);
- modal.addComponents(row);
- });
-
- await interaction.showModal(modal);
-}
-
-async function handleStatus(interaction) {
- const appId = interaction.options.getString("id");
-
- if (appId) {
- const application = await getApplication(
- interaction.client,
- interaction.guild.id,
- appId,
- );
-
- if (!application || application.userId !== interaction.user.id) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Application not found or you do not have permission to view it.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- const embed = createEmbed({ title: `Application #${application.id} - ${application.roleName}`, description: `**Status:** ${application.status.charAt(0).toUpperCase() + application.status.slice(1)}` });
-
- return interaction.editReply({ embeds: [embed], flags: ["Ephemeral"] });
- } else {
- const applications = await getUserApplications(
- interaction.client,
- interaction.guild.id,
- interaction.user.id,
- );
-
- if (applications.length === 0) {
- return interaction.editReply({
- embeds: [
- errorEmbed("You have not submitted any applications yet."),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- const embed = createEmbed({ title: "Your Applications", description: "Here are your recent applications." });
-
- return interaction.editReply({ embeds: [embed], flags: ["Ephemeral"] });
- }
-}
-
diff --git a/src/commands/Config/config.js b/src/commands/Config/config.js
deleted file mode 100644
index d1c2f2aa21..0000000000
--- a/src/commands/Config/config.js
+++ /dev/null
@@ -1,222 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ChannelType } from 'discord.js';
-import { errorEmbed } from '../../utils/embeds.js';
-
-import birthdayToggle from './modules/config_birthday_toggle.js';
-import loggingStatus from './modules/config_logging_status.js';
-import loggingSetchannel from './modules/config_logging_setchannel.js';
-import loggingFilter from './modules/config_logging_filter.js';
-import reportsSetchannel from './modules/config_reports_setchannel.js';
-import premiumSetrole from './modules/config_premium_setrole.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("config")
- .setDescription("Configuration commands for the bot.")
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
- .setDMPermission(false)
- .addSubcommandGroup((group) =>
- group
- .setName("birthday")
- .setDescription("Manage birthday announcement settings.")
- .addSubcommand((subcommand) =>
- subcommand
- .setName("toggle")
- .setDescription(
- "Enable or disable birthday announcements by selecting a channel.",
- )
- .addChannelOption((option) =>
- option
- .setName("channel")
- .setDescription(
- "The text channel for birthday announcements (leave empty to disable).",
- )
- .setRequired(false)
- .addChannelTypes(ChannelType.GuildText),
- ),
- ),
- )
- .addSubcommandGroup((group) =>
- group
- .setName("logging")
- .setDescription("Manage logging configuration.")
- .addSubcommand((subcommand) =>
- subcommand
- .setName("status")
- .setDescription("Display current logging configuration."),
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("setchannel")
- .setDescription("Sets the channel where bot moderation and audit logs are sent.")
- .addChannelOption((option) =>
- option
- .setName("channel")
- .setDescription("The text channel to use for logging.")
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(false),
- )
- .addChannelOption((option) =>
- option
- .setName("ticket_lifecycle")
- .setDescription("The channel for ticket lifecycle events (open, close, delete, claim, etc.).")
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(false),
- )
- .addChannelOption((option) =>
- option
- .setName("ticket_transcript")
- .setDescription("The channel for ticket transcript logs.")
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(false),
- )
- .addBooleanOption((option) =>
- option
- .setName("disable")
- .setDescription("Set to True to disable logging completely.")
- .setRequired(false),
- ),
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("add")
- .setDescription("Adds a user or channel to the ignore list.")
- .addStringOption((option) =>
- option
- .setName("type")
- .setDescription("The type of entity to ignore.")
- .setRequired(true)
- .addChoices(
- { name: "User", value: "user" },
- { name: "Channel", value: "channel" },
- ),
- )
- .addStringOption((option) =>
- option
- .setName("id")
- .setDescription("The ID of the User or Channel to ignore.")
- .setRequired(true),
- ),
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("remove")
- .setDescription("Removes a user or channel from the ignore list.")
- .addStringOption((option) =>
- option
- .setName("type")
- .setDescription("The type of entity to stop ignoring.")
- .setRequired(true)
- .addChoices(
- { name: "User", value: "user" },
- { name: "Channel", value: "channel" },
- ),
- )
- .addStringOption((option) =>
- option
- .setName("id")
- .setDescription("The ID of the User or Channel to remove from the ignore list.")
- .setRequired(true),
- ),
- ),
- )
- .addSubcommandGroup((group) =>
- group
- .setName("reports")
- .setDescription("Manage report configuration.")
- .addSubcommand((subcommand) =>
- subcommand
- .setName("setchannel")
- .setDescription("Sets the channel where user reports will be sent.")
- .addChannelOption((option) =>
- option
- .setName("channel")
- .setDescription("The text channel to send reports to.")
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(true),
- ),
- ),
- )
- .addSubcommandGroup((group) =>
- group
- .setName("premium")
- .setDescription("Manage premium role configuration.")
- .addSubcommand((subcommand) =>
- subcommand
- .setName("setrole")
- .setDescription("Sets the Discord role granted when the Premium Role shop item is purchased.")
- .addRoleOption((option) =>
- option
- .setName("role")
- .setDescription("The role to be designated as the Premium Shop Role.")
- .setRequired(true),
- ),
- ),
- ),
-
- async execute(interaction, config, client) {
- if (!interaction.memberPermissions.has(PermissionFlagsBits.ManageGuild)) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need the `Manage Server` permission to use this command.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
-
- const subcommandGroup = interaction.options.getSubcommandGroup();
- const subcommand = interaction.options.getSubcommand();
-
- try {
- if (subcommandGroup === "birthday") {
- if (subcommand === "toggle") {
- return birthdayToggle.execute(interaction, config, client);
- }
- } else if (subcommandGroup === "logging") {
- if (subcommand === "status") {
- return loggingStatus.execute(interaction, config, client);
- } else if (subcommand === "setchannel") {
- return loggingSetchannel.execute(interaction, config, client);
- } else if (subcommand === "add" || subcommand === "remove") {
- return loggingFilter.execute(interaction, config, client);
- }
- } else if (subcommandGroup === "reports") {
- if (subcommand === "setchannel") {
- return reportsSetchannel.execute(interaction, config, client);
- }
- } else if (subcommandGroup === "premium") {
- if (subcommand === "setrole") {
- return premiumSetrole.execute(interaction, config, client);
- }
- }
-
- return interaction.editReply({
- embeds: [
- errorEmbed("Error", "Unknown subcommand or subcommand group."),
- ],
- flags: MessageFlags.Ephemeral,
- });
- } catch (error) {
- console.error("Config command error:", error);
-
- const errorMessage = {
- embeds: [
- errorEmbed(
- "Configuration Failed",
- "Could not save the configuration to the database.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- };
-
- if (interaction.deferred || interaction.replied) {
- return interaction.editReply(errorMessage);
- } else {
- return interaction.editReply(errorMessage);
- }
- }
- },
-};
-
diff --git a/src/commands/Config/modules/config_birthday_toggle.js b/src/commands/Config/modules/config_birthday_toggle.js
deleted file mode 100644
index 1bc162eec6..0000000000
--- a/src/commands/Config/modules/config_birthday_toggle.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { getGuildConfig, setGuildConfig } from '../../../services/guildConfig.js';
-
-export default {
- async execute(interaction, config, client) {
-try {
- const channel = interaction.options.getChannel("channel");
- const guildId = interaction.guildId;
-
- let guildConfig = await getGuildConfig(client, guildId);
-
- if (channel) {
- guildConfig.birthdayChannelId = channel.id;
- await setGuildConfig(client, guildId, guildConfig);
-
- return interaction.reply({
- embeds: [
- successEmbed(
- "🎂 Birthday Announcements Enabled",
- `Birthday announcements will now be posted in ${channel}.`,
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- } else {
- guildConfig.birthdayChannelId = null;
- await setGuildConfig(client, guildId, guildConfig);
-
- return interaction.editReply({
- embeds: [
- successEmbed(
- "🎂 Birthday Announcements Disabled",
- "Birthday announcements have been disabled. No channel selected.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
- } catch (error) {
- console.error("config_birthday_toggle error:", error);
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Configuration Error",
- "Could not save the birthday configuration.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
- }
-};
-
diff --git a/src/commands/Config/modules/config_logging_filter.js b/src/commands/Config/modules/config_logging_filter.js
deleted file mode 100644
index fc397646a2..0000000000
--- a/src/commands/Config/modules/config_logging_filter.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../../utils/embeds.js';
-import { logEvent } from '../../../utils/moderation.js';
-
-export default {
- async execute(interaction, config, client) {
-if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need Administrator permissions to manage log filters.",
- ),
- ],
- });
- }
- if (!client.db) {
- return interaction.editReply({
- embeds: [
- errorEmbed("Database Error", "Database not initialized."),
- ],
- });
- }
-
- const subcommand = interaction.options.getSubcommand();
- const type = interaction.options.getString("type");
- const entityId = interaction.options.getString("id");
- const guildId = interaction.guildId;
-
- const currentConfig = config;
-
- let targetArray;
- let entityName = "";
- let entityType = "";
-
- if (type === "user") {
- targetArray = currentConfig.logIgnore.users;
- entityType = "User";
- const member = await interaction.guild.members
- .fetch(entityId)
- .catch(() => null);
- entityName = member ? member.user.tag : `ID: ${entityId}`;
- } else if (type === "channel") {
- targetArray = currentConfig.logIgnore.channels;
- entityType = "Channel";
- const channel = interaction.guild.channels.cache.get(entityId);
- entityName = channel ? `#${channel.name}` : `ID: ${entityId}`;
- } else {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Invalid Type",
- "Please choose 'user' or 'channel'.",
- ),
- ],
- });
- }
-
- let successMessage = "";
-
- if (subcommand === "add") {
- if (targetArray.includes(entityId)) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Already Filtered",
- `${entityType} **${entityName}** is already on the ignore list.`,
- ),
- ],
- });
- }
- targetArray.push(entityId);
- successMessage = `${entityType} **${entityName}** has been added to the log ignore list. Events originating from them will not be logged.`;
- } else if (subcommand === "remove") {
- const index = targetArray.indexOf(entityId);
- if (index === -1) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Not Filtered",
- `${entityType} **${entityName}** was not found on the ignore list.`,
- ),
- ],
- });
- }
- targetArray.splice(index, 1);
- successMessage = `${entityType} **${entityName}** has been removed from the log ignore list. Events will now be logged.`;
- }
-
- try {
- const { getGuildConfigKey } = await import('../../../utils/database.js');
- const configKey = getGuildConfigKey(guildId);
- await client.db.set(configKey, currentConfig);
-
- const logEmbed = successEmbed(
- "Log Filter Updated",
- successMessage,
- ).addFields(
- {
- name: "Action",
- value:
- subcommand.charAt(0).toUpperCase() +
- subcommand.slice(1),
- inline: true,
- },
- { name: "Entity Type", value: entityType, inline: true },
- );
-
- await logEvent({
- client,
- guild: interaction.guild,
- event: {
- action: "Log Filter Updated",
- target: `Filter ${subcommand}`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- metadata: {
- entityType,
- loggingEnabled: currentConfig.enableLogging
- }
- }
- });
-
- await interaction.editReply({
- embeds: [successEmbed("Success!", successMessage)],
- });
- } catch (error) {
- console.error("Error saving log filter:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "Database Error",
- "Failed to save configuration change.",
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Config/modules/config_logging_setchannel.js b/src/commands/Config/modules/config_logging_setchannel.js
deleted file mode 100644
index d4d13ae359..0000000000
--- a/src/commands/Config/modules/config_logging_setchannel.js
+++ /dev/null
@@ -1,175 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../../utils/embeds.js';
-import { getGuildConfig, setGuildConfig } from '../../../services/guildConfig.js';
-import { logEvent } from '../../../utils/moderation.js';
-import { validateLogChannel } from '../../../utils/ticketLogging.js';
-
-export default {
- async execute(interaction, config, client) {
-if (!interaction.member.permissions.has(PermissionsBitField.Flags.Administrator)) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need Administrator permissions.",
- ),
- ],
- });
- }
-
- if (!client.db) {
- return interaction.editReply({
- embeds: [
- errorEmbed("Database Error", "Database not initialized."),
- ],
- });
- }
-
- const guildId = interaction.guildId;
-
- const currentConfig = await getGuildConfig(client, guildId);
-
- const logChannel = interaction.options.getChannel("channel");
- const disableLogging = interaction.options.getBoolean("disable");
- const ticketLifecycle = interaction.options.getChannel("ticket_lifecycle");
- const ticketTranscript = interaction.options.getChannel("ticket_transcript");
-
- try {
- if (ticketLifecycle) {
- const validation = validateLogChannel(ticketLifecycle, interaction.guild.members.me);
- if (!validation.valid) {
- return interaction.editReply({
- embeds: [errorEmbed("Invalid Channel", validation.error)],
- });
- }
-
- if (!currentConfig.ticketLogging) {
- currentConfig.ticketLogging = {};
- }
- currentConfig.ticketLogging.lifecycleChannelId = ticketLifecycle.id;
- await setGuildConfig(client, guildId, currentConfig);
-
- return interaction.editReply({
- embeds: [
- successEmbed(
- "🎫 Ticket Lifecycle Channel Set",
- `**Channel:** ${ticketLifecycle}\n**Logs:** Ticket open, close, delete, claim, unclaim, and priority events\n\n**Updated by:** ${interaction.user.tag}`
- ),
- ],
- });
- }
-
- if (ticketTranscript) {
- const validation = validateLogChannel(ticketTranscript, interaction.guild.members.me);
- if (!validation.valid) {
- return interaction.editReply({
- embeds: [errorEmbed("Invalid Channel", validation.error)],
- });
- }
-
- if (!currentConfig.ticketLogging) {
- currentConfig.ticketLogging = {};
- }
- currentConfig.ticketLogging.transcriptChannelId = ticketTranscript.id;
- await setGuildConfig(client, guildId, currentConfig);
-
- return interaction.editReply({
- embeds: [
- successEmbed(
- "📜 Ticket Transcript Channel Set",
- `**Channel:** ${ticketTranscript}\n**Logs:** Ticket transcript generation\n\n**Updated by:** ${interaction.user.tag}`
- ),
- ],
- });
- }
-
- if (disableLogging) {
- currentConfig.logChannelId = null;
- currentConfig.enableLogging = false;
- await setGuildConfig(client, guildId, currentConfig);
-
- return interaction.editReply({
- embeds: [
- successEmbed(
- "Logging Disabled 🚫",
- "Server logging has been disabled.",
- ),
- ],
- });
- }
-
- if (logChannel) {
- const permissionsInChannel = logChannel.permissionsFor(
- interaction.guild.members.me,
- );
- if (
- !permissionsInChannel.has(
- PermissionsBitField.Flags.SendMessages,
- ) ||
- !permissionsInChannel.has(PermissionsBitField.Flags.EmbedLinks)
- ) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Bot Permission Error",
- `I need Send Messages and Embed Links permissions in ${logChannel}.`,
- ),
- ],
- });
- }
-
- currentConfig.logChannelId = logChannel.id;
- currentConfig.enableLogging = true;
-
- await setGuildConfig(client, guildId, currentConfig);
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- "Log Channel Set ðŸ“",
- `Logs will be sent to ${logChannel}.`,
- ),
- ],
- });
-
- await logEvent({
- client,
- guild: interaction.guild,
- event: {
- action: "Log Channel Activated",
- target: logChannel.toString(),
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason: `Logging set by ${interaction.user}`,
- metadata: {
- channelId: logChannel.id,
- moderatorId: interaction.user.id,
- loggingEnabled: true
- }
- }
- });
- return;
- }
-
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "No Option Provided",
- "Please provide one of the following: channel, ticket_lifecycle, ticket_transcript, or disable."
- ),
- ],
- });
-
- } catch (error) {
- console.error("Error setting log channel:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "Configuration Error",
- "Could not save configuration.",
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Config/modules/config_logging_status.js b/src/commands/Config/modules/config_logging_status.js
deleted file mode 100644
index d8a5b6e706..0000000000
--- a/src/commands/Config/modules/config_logging_status.js
+++ /dev/null
@@ -1,184 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, EmbedBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../../utils/embeds.js';
-import { getGuildConfig } from '../../../services/guildConfig.js';
-import { getLevelingConfig, getWelcomeConfig, getApplicationSettings, getModlogSettings } from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
- try {
-if (!interaction.member.permissions.has(PermissionsBitField.Flags.ManageGuild)) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need Manage Server permissions.",
- ),
- ],
- });
- }
-
- await interaction.deferReply();
-
- const currentConfig = await getGuildConfig(client, interaction.guildId);
-
- const getStatus = (id, type) => {
- let status = "⌠Not Set";
- if (id) {
- const item =
- type === "role"
- ? interaction.guild.roles.cache.get(id)
- : interaction.guild.channels.cache.get(id);
-
- status = item ? item.toString() : `âš ï¸ ID: ${id} (Missing)`;
- }
- return status;
- };
-
- const logChannelStatus = getStatus(
- currentConfig.logChannelId,
- "channel",
- );
- const reportChannelStatus = getStatus(
- currentConfig.reportChannelId,
- "channel",
- );
- const premiumRoleStatus = getStatus(
- currentConfig.premiumRoleId,
- "role",
- );
-
- const levelingConfig = await getLevelingConfig(client, interaction.guildId);
- const levelingStatus = levelingConfig?.enabled ? "✅ **Enabled**" : "⌠**Disabled**";
-
- const welcomeConfig = await getWelcomeConfig(client, interaction.guildId);
- const welcomeStatus = welcomeConfig?.enabled ? "✅ **Enabled**" : "⌠**Disabled**";
- const goodbyeStatus = welcomeConfig?.goodbyeEnabled ? "✅ **Enabled**" : "⌠**Disabled**";
-
- const autoRoleStatus = getStatus(currentConfig.autoRole, "role");
-
- const birthdayStatus = currentConfig.birthdayChannelId ?
- "✅ **Enabled**" : "⌠**Disabled**";
-
- const moderationLoggingStatus = currentConfig.enableLogging && currentConfig.logChannelId
- ? "✅ **Enabled**" : "⌠**Disabled**";
-
- const applicationConfig = await getApplicationSettings(client, interaction.guildId);
- const applicationStatus = applicationConfig?.enabled ? "✅ **Enabled**" : "⌠**Disabled**";
-
- const maxTicketsPerUser = currentConfig.maxTicketsPerUser || 3;
- const dmOnClose = currentConfig.dmOnClose !== false;
-
- const ticketLogging = currentConfig.ticketLogging || {};
- const lifecycleChannelStatus = getStatus(ticketLogging.lifecycleChannelId, "channel");
- const transcriptChannelStatus = getStatus(ticketLogging.transcriptChannelId, "channel");
-
- let totalOpenTickets = 0;
- try {
- const { getFromDb } = await import('../../../utils/database.js');
- const ticketKeys = await getFromDb(`guild:${interaction.guildId}:ticket:*`, {});
- for (const key of Object.keys(ticketKeys)) {
- const ticketData = await getFromDb(key, null);
- if (ticketData && ticketData.status === 'open') totalOpenTickets++;
- }
- } catch (e) {
- console.error('Error counting tickets:', e);
- }
-
- const ticketLoggingStatus = ticketLogging.lifecycleChannelId || ticketLogging.transcriptChannelId
- ? "✅ **Enabled**" : "⌠**Disabled**";
-
- const ticketLimitsStatus = `🎫 **${maxTicketsPerUser}** per user\n📩 DM on Close: ${dmOnClose ? '✅' : 'âŒ'}\n📊 Open Tickets: ${totalOpenTickets}\n📠Ticket Logging: ${ticketLoggingStatus}`;
-
- const ignoredUsers = currentConfig.logIgnore?.users || [];
- const ignoredChannels = currentConfig.logIgnore?.channels || [];
-
- const formatIdList = (list) => {
- if (list.length === 0) return "None";
- if (list.length > 5)
- return `${list.length} IDs (see console for full list)`;
- return list.map((id) => `\`${id}\``).join("\n");
- };
-
- const statusEmbed = new EmbedBuilder()
- .setTitle("âš™ï¸ Server Configuration Status")
- .setDescription(
- `Current settings fetched for **${interaction.guild.name}**.`,
- )
- .setColor("#3498DB")
- .setTimestamp()
- .addFields(
- {
- name: "🎮 Leveling System",
- value: levelingStatus,
- inline: true,
- },
- {
- name: "🎂 Birthday System",
- value: birthdayStatus,
- inline: true,
- },
- {
- name: "👋 Welcome System",
- value: welcomeStatus,
- inline: true,
- },
- {
- name: "👋 Goodbye System",
- value: goodbyeStatus,
- inline: true,
- },
- {
- name: "🤖 Auto Role",
- value: autoRoleStatus,
- inline: true,
- },
- {
- name: "📋 Applications",
- value: applicationStatus,
- inline: true,
- },
- {
- name: "🔨 Enhanced Moderation Logging",
- value: moderationLoggingStatus,
- inline: true,
- },
- {
- name: "💎 Premium Role",
- value: premiumRoleStatus,
- inline: true,
- },
- {
- name: "🎫 Ticket Limits",
- value: ticketLimitsStatus,
- inline: true,
- },
- {
- name: "📊 Configuration Channels",
- value: "**Audit Logs:** " + logChannelStatus +
- "\n**Report Logs:** " + reportChannelStatus +
- "\n**Ticket Lifecycle:** " + lifecycleChannelStatus +
- "\n**Ticket Transcripts:** " + transcriptChannelStatus,
- inline: false,
- },
- {
- name: "⌠Log Filters",
- value: "**Users:** " + formatIdList(ignoredUsers) + "\n**Channels:** " + formatIdList(ignoredChannels),
- inline: false,
- },
- );
-
- await interaction.editReply({ embeds: [statusEmbed] });
- } catch (error) {
- console.error("config_logging_status error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "Configuration Error",
- "Failed to fetch or display the configuration status.",
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Config/modules/config_premium_setrole.js b/src/commands/Config/modules/config_premium_setrole.js
deleted file mode 100644
index 6ee3bc9c3d..0000000000
--- a/src/commands/Config/modules/config_premium_setrole.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../../utils/embeds.js';
-import { getGuildConfig, setGuildConfig } from '../../../services/guildConfig.js';
-
-export default {
- async execute(interaction, config, client) {
- if (!interaction.member.permissions.has(PermissionsBitField.Flags.ManageGuild)) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need Manage Server permissions to set the premium role.",
- ),
- ],
- });
- }
-
- const role = interaction.options.getRole("role");
- const guildId = interaction.guildId;
-
- try {
- const currentConfig = await getGuildConfig(client, guildId);
-
- currentConfig.premiumRoleId = role.id;
-
- await setGuildConfig(client, guildId, currentConfig);
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- "✅ Configuration Saved",
- `The **Premium Shop Role** has been successfully set to ${role.toString()}.`,
- ),
- ],
- });
- } catch (error) {
- console.error("SetPremiumRole command error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "System Error",
- "Could not save the guild configuration.",
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Config/modules/config_reports_setchannel.js b/src/commands/Config/modules/config_reports_setchannel.js
deleted file mode 100644
index 67f03253ee..0000000000
--- a/src/commands/Config/modules/config_reports_setchannel.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../../utils/embeds.js';
-import { getGuildConfig, setGuildConfig } from '../../../services/guildConfig.js';
-export default {
- async execute(interaction, config, client) {
- const channel = interaction.options.getChannel("channel");
- const guildId = interaction.guildId;
-
- try {
- let guildConfig = await getGuildConfig(client, guildId);
-
- guildConfig.reportChannelId = channel.id;
-
- await setGuildConfig(client, guildId, guildConfig);
-
- await interaction.reply({
- embeds: [
- successEmbed(
- "✅ Report Channel Set!",
- `All new reports will now be sent to ${channel}.`,
- ),
- ],
- });
- } catch (error) {
- console.error("Error setting report channel:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "Database Error",
- "Could not save the channel configuration. Check bot permissions.",
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Core/bug.js b/src/commands/Core/bug.js
deleted file mode 100644
index 78c17040d8..0000000000
--- a/src/commands/Core/bug.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, MessageFlags } from 'discord.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("bug")
- .setDescription("Report a bug or issue with the bot"),
-
- async execute(interaction) {
- const githubButton = new ButtonBuilder()
- .setLabel('🛠Report Bug on GitHub')
- .setStyle(ButtonStyle.Link)
- .setURL('https://github.com/codebymitch/TitanBot/issues');
-
- const row = new ActionRowBuilder().addComponents(githubButton);
-
- const bugReportEmbed = new EmbedBuilder()
- .setColor('#ff0000')
- .setTitle('🛠Bug Report')
- .setDescription('Found a bug? Please report it on our GitHub Issues page!\n\n' +
- '**When reporting a bug, please include:**\n' +
- '• 📠Detailed description of the issue\n' +
- '• 🔄 Steps to reproduce the problem\n' +
- '• 📸 Screenshots if applicable\n' +
- '• 💻 Your bot version and environment\n\n' +
- 'This helps us fix issues faster and more effectively!')
- .setFooter({
- text: 'TitanBot Bug Reporting System',
- iconURL: interaction.client.user.displayAvatarURL()
- })
- .setTimestamp();
-
- await interaction.reply({
- embeds: [bugReportEmbed],
- components: [row],
- flags: MessageFlags.Ephemeral
- });
- },
-};
-
diff --git a/src/commands/Core/commandlist.js b/src/commands/Core/commandlist.js
deleted file mode 100644
index 06a105fdd7..0000000000
--- a/src/commands/Core/commandlist.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import { SlashCommandBuilder, MessageFlags } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import path from 'path';
-import fs from 'fs';
-
-function getIcon(folder) {
- const icons = {
- Core: "ℹï¸",
- Moderation: "🛡ï¸",
- Economy: "💰",
- Utility: "🛠ï¸",
- Tickets: "🎫",
- Fun: "🎲",
- Leveling: "✨",
- Birthday: "ðŸŽ",
- Counter: "🔟",
- Giveaway: "🎉",
- Reaction_roles: "🔔",
- Search: "ðŸ”",
- Tools: "🔨",
- };
- return icons[folder] || "📂";
-}
-
-/**
- * Creates the dynamic Command List embed based on command files in the 'commands' directory.
- * @returns {EmbedBuilder}
- */
-function createCommandListEmbed() {
- const foldersPath = path.join(process.cwd(), "src", "commands");
- const commandFolders = fs.readdirSync(foldersPath);
-
- const embed = createEmbed({ title: "📜 TitanBot Command List", description: "Here is a complete list of all available commands, organized by category.", });
-
- let totalCommands = 0;
-
- for (const folder of commandFolders) {
- if (folder.startsWith(".")) continue;
-
- const commandsPath = path.join(foldersPath, folder);
-
- if (fs.lstatSync(commandsPath).isDirectory()) {
- const commandFiles = fs
- .readdirSync(commandsPath)
- .filter((file) => file.endsWith(".js"));
-
- if (commandFiles.length > 0) {
- const fileNames = commandFiles.map(
- (file) => `\`/${file.replace(".js", "")}\``,
- );
- totalCommands += commandFiles.length;
-
- embed.addFields({
- name: `${getIcon(folder)} ${folder}`,
- value: fileNames.join(", "),
- inline: false,
- });
- }
- }
- }
-
- embed.setFooter({ text: `TitanBot • ${totalCommands} Commands Loaded` });
- return embed;
-}
-export default {
- data: new SlashCommandBuilder()
- .setName("commandlist")
- .setDescription("Displays a full list of all available bot commands."),
-
- async execute(interaction) {
-try {
- const commandListEmbed = createCommandListEmbed();
-
- await interaction.reply({
- embeds: [commandListEmbed],
- ephemeral: false,
- });
- } catch (error) {
- console.error('CommandList command error:', error);
- if (!interaction.replied) {
- return interaction.reply({
- embeds: [createEmbed({ title: 'System Error', description: 'Could not build or display the command list.' })],
- flags: [MessageFlags.Ephemeral],
- });
- }
- }
- },
-};
-
diff --git a/src/commands/Core/help.js b/src/commands/Core/help.js
deleted file mode 100644
index 189030c14b..0000000000
--- a/src/commands/Core/help.js
+++ /dev/null
@@ -1,201 +0,0 @@
-import {
- SlashCommandBuilder,
- ActionRowBuilder,
- ButtonBuilder,
- ButtonStyle,
-} from "discord.js";
-import { createEmbed } from "../../utils/embeds.js";
-import {
- createSelectMenu,
-} from "../../utils/components.js";
-import fs from "fs/promises";
-import path from "path";
-import { fileURLToPath } from "url";
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-const CATEGORY_SELECT_ID = "help-category-select";
-const ALL_COMMANDS_ID = "help-all-commands";
-const BUG_REPORT_BUTTON_ID = "help-bug-report";
-const HELP_MENU_TIMEOUT_MS = 5 * 60 * 1000;
-
-const CATEGORY_ICONS = {
- Core: "ℹ️",
- Moderation: "🛡️",
- Economy: "💰",
- Fun: "🎮",
- Leveling: "📊",
- Utility: "🔧",
- Ticket: "🎫",
- Welcome: "👋",
- Giveaway: "🎉",
- Counter: "🔢",
- Tools: "🛠️",
- Search: "🔍",
- Reaction_Roles: "🎭",
- Community: "👥",
- Birthday: "🎂",
- Config: "⚙️",
-};
-
-/**
- * Creates the initial help menu embed and components
- * @returns {Object} Object containing embeds and components
- */
-async function createInitialHelpMenu() {
- const commandsPath = path.join(__dirname, "../../commands");
- const categoryDirs = (
- await fs.readdir(commandsPath, { withFileTypes: true })
- )
- .filter((dirent) => dirent.isDirectory())
- .map((dirent) => dirent.name)
- .sort();
-
- const options = [
- {
- label: "📋 All Commands",
- description: "View all available commands with pagination",
- value: ALL_COMMANDS_ID,
- },
- ...categoryDirs.map((category) => {
- const categoryName =
- category.charAt(0).toUpperCase() +
- category.slice(1).toLowerCase();
- const icon = CATEGORY_ICONS[categoryName] || "🔍";
- return {
- label: `${icon} ${categoryName}`,
- description: `View commands in the ${categoryName} category`,
- value: category,
- };
- }),
- ];
-
- const embed = createEmbed({
- title: "🤖 TitanBot Help Center",
- description: "Your all-in-one Discord companion for moderation, economy, fun, and server management.",
- color: 0x0099FF
- });
-
- embed.addFields(
- {
- name: "🛡️ **Moderation**",
- value: "Server moderation, user management, and enforcement tools",
- inline: true
- },
- {
- name: "💰 **Economy**",
- value: "Currency system, shops, and virtual economy",
- inline: true
- },
- {
- name: "🎮 **Fun**",
- value: "Games, entertainment, and interactive commands",
- inline: true
- },
- {
- name: "📊 **Leveling**",
- value: "User levels, XP system, and progression tracking",
- inline: true
- },
- {
- name: "🎫 **Tickets**",
- value: "Support ticket system for server management",
- inline: true
- },
- {
- name: "🎉 **Giveaways**",
- value: "Automated giveaway management and distribution",
- inline: true
- },
- {
- name: "👋 **Welcome**",
- value: "Member welcome messages and onboarding",
- inline: true
- },
- {
- name: "🎂 **Birthdays**",
- value: "Birthday tracking and celebration features",
- inline: true
- },
- {
- name: "🔧 **Utilities**",
- value: "Useful tools and server utilities",
- inline: true
- }
- );
-
- embed.setFooter({
- text: "Made with ❤️"
- });
- embed.setTimestamp();
-
- const bugReportButton = new ButtonBuilder()
- .setCustomId(BUG_REPORT_BUTTON_ID)
- .setLabel("Report Bug")
- .setStyle(ButtonStyle.Danger);
-
- const supportButton = new ButtonBuilder()
- .setLabel("Support Server")
- .setURL("https://discord.gg/QnWNz2dKCE")
- .setStyle(ButtonStyle.Link);
-
- const touchpointButton = new ButtonBuilder()
- .setLabel("Learn from Touchpoint")
- .setURL("https://www.youtube.com/@TouchDisc")
- .setStyle(ButtonStyle.Link);
-
- const selectRow = createSelectMenu(
- CATEGORY_SELECT_ID,
- "Select to view the commands",
- options,
- );
-
- const buttonRow = new ActionRowBuilder().addComponents([
- bugReportButton,
- supportButton,
- touchpointButton,
- ]);
-
- return {
- embeds: [embed],
- components: [buttonRow, selectRow],
- };
-}
-
-export default {
- data: new SlashCommandBuilder()
- .setName("help")
- .setDescription("Displays the help menu with all available commands"),
-
- async execute(interaction, guildConfig, client) {
- // Defer reply to avoid timeout (filesystem operations take time)
- const { MessageFlags } = await import('discord.js');
- await interaction.deferReply();
-
- const { embeds, components } = await createInitialHelpMenu();
-
- await interaction.editReply({
- embeds,
- components,
- });
-
- setTimeout(async () => {
- try {
- const closedEmbed = createEmbed({
- title: "Help menu closed",
- description: "Help menu has been closed, use /help again.",
- color: 0x808080,
- });
-
- await interaction.editReply({
- embeds: [closedEmbed],
- components: [],
- });
- } catch (error) {
- // Message might be deleted or no longer editable.
- }
- }, HELP_MENU_TIMEOUT_MS);
- },
-};
-
diff --git a/src/commands/Core/invite.js b/src/commands/Core/invite.js
deleted file mode 100644
index f052986999..0000000000
--- a/src/commands/Core/invite.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, MessageFlags } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("invite")
- .setDescription("Get the link to add this bot to your server"),
-
- async execute(interaction) {
-try {
- const clientId = interaction.client.application.id;
-
- const inviteUrl = `https://discord.com/oauth2/authorize?client_id=${clientId}&permissions=8&scope=bot%20applications.commands`;
-
- const embed = createEmbed({ title: "🔗 Invite This Bot", description: "Click the button below to add your customized bot to a server. This link uses your bot's unique ID and requests Administrator permissions.", }).setThumbnail(interaction.client.user.displayAvatarURL());
-
- const inviteButton = new ButtonBuilder()
- .setLabel("Invite Bot")
- .setStyle(ButtonStyle.Link)
- .setURL(inviteUrl);
-
- const actionRow = new ActionRowBuilder().addComponents(inviteButton);
-
- await interaction.reply({ embeds: [embed], components: [actionRow] });
- } catch (error) {
- console.error('Invite command error:', error);
- return interaction.editReply({
- embeds: [createEmbed({ title: 'System Error', description: 'Could not build the invite link.' })],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
-
-
diff --git a/src/commands/Core/ping.js b/src/commands/Core/ping.js
deleted file mode 100644
index d375b36cb8..0000000000
--- a/src/commands/Core/ping.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { SlashCommandBuilder, MessageFlags } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("ping")
- .setDescription("Checks the bot's latency and API speed"),
-
- async execute(interaction) {
-try {
-
- const reply = await interaction.reply({
- content: "Pinging...",
- });
-
- const latency = reply.createdTimestamp - interaction.createdTimestamp;
- const apiLatency = Math.round(interaction.client.ws.ping);
-
- const embed = createEmbed({ title: "📠Pong!", description: null }).addFields(
- { name: "Bot Latency", value: `${latency}ms`, inline: true },
- { name: "API Latency", value: `${apiLatency}ms`, inline: true },
- );
-
- await interaction.editReply({
- content: null,
- embeds: [embed],
- });
- } catch (error) {
- console.error('Ping command error:', error);
- return interaction.editReply({
- embeds: [createEmbed({ title: 'System Error', description: 'Could not determine latency at this time.' })],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
-
diff --git a/src/commands/Core/stats.js b/src/commands/Core/stats.js
deleted file mode 100644
index f682f046fc..0000000000
--- a/src/commands/Core/stats.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { SlashCommandBuilder, version, MessageFlags } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("stats")
- .setDescription("View bot statistics"),
-
- async execute(interaction) {
-try {
- const totalGuilds = interaction.client.guilds.cache.size;
- const totalMembers = interaction.client.guilds.cache.reduce(
- (acc, guild) => acc + guild.memberCount,
- 0,
- );
- const nodeVersion = process.version;
-
- const embed = createEmbed({ title: "📊 System Statistics", description: "Real-time performance metrics.", }).addFields(
- { name: "Servers", value: `${totalGuilds}`, inline: true },
- { name: "Users", value: `${totalMembers}`, inline: true },
- { name: "Node.js", value: `${nodeVersion}`, inline: true },
- { name: "Discord.js", value: `v${version}`, inline: true },
- {
- name: "Memory Usage",
- value: `${(process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2)} MB`,
- inline: true,
- },
- );
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error('Stats command error:', error);
- return interaction.editReply({
- embeds: [createEmbed({ title: 'System Error', description: 'Could not fetch system statistics.' })],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
-
diff --git a/src/commands/Core/support.js b/src/commands/Core/support.js
deleted file mode 100644
index 36e0997275..0000000000
--- a/src/commands/Core/support.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, MessageFlags } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-const SUPPORT_SERVER_URL = "https://discord.gg/QnWNz2dKCE";
-export default {
- data: new SlashCommandBuilder()
- .setName("support")
- .setDescription("Get link to the support server"),
-
- async execute(interaction) {
-try {
- const supportButton = new ButtonBuilder()
- .setLabel("Join Support Server")
- .setStyle(ButtonStyle.Link)
- .setURL(SUPPORT_SERVER_URL);
-
- const actionRow = new ActionRowBuilder().addComponents(supportButton);
-
- await interaction.reply({
- embeds: [
- createEmbed({ title: "🚑 Need Help?", description: "Join our official support server for assistance, report bugs, or suggest features. If you are customizing this bot, remember to change the link in the code!" }),
- ],
- components: [actionRow],
- flags: ["Ephemeral"],
- });
- } catch (error) {
- console.error('Support command error:', error);
- return interaction.editReply({
- embeds: [createEmbed({ title: 'System Error', description: 'Could not display support information.' })],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
-
-
diff --git a/src/commands/Core/uptime.js b/src/commands/Core/uptime.js
deleted file mode 100644
index 81820bd669..0000000000
--- a/src/commands/Core/uptime.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { SlashCommandBuilder, MessageFlags } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("uptime")
- .setDescription("Check how long the bot has been online"),
-
- async execute(interaction) {
-try {
- let totalSeconds = interaction.client.uptime / 1000;
- let days = Math.floor(totalSeconds / 86400);
- totalSeconds %= 86400;
- let hours = Math.floor(totalSeconds / 3600);
- totalSeconds %= 3600;
- let minutes = Math.floor(totalSeconds / 60);
- let seconds = Math.floor(totalSeconds % 60);
-
- const uptimeStr = `${days}d, ${hours}h, ${minutes}m, ${seconds}s`;
-
- await interaction.reply({
- embeds: [createEmbed({ title: "â±ï¸ System Uptime", description: `**${uptimeStr}**` })],
- });
- } catch (error) {
- console.error('Uptime command error:', error);
- return interaction.editReply({
- embeds: [createEmbed({ title: 'System Error', description: 'Could not compute uptime.' })],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
-
diff --git a/src/commands/Counter/counter.js b/src/commands/Counter/counter.js
deleted file mode 100644
index d266196af6..0000000000
--- a/src/commands/Counter/counter.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-
-import { handleCreate } from './modules/counter_create.js';
-import { handleList } from './modules/counter_list.js';
-import { handleUpdate } from './modules/counter_update.js';
-import { handleDelete } from './modules/counter_delete.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("counter")
- .setDescription("Manage server counters that track statistics in channel names")
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels)
- .addSubcommand(subcommand =>
- subcommand
- .setName("create")
- .setDescription("Create a new counter for a channel")
- .addStringOption(option =>
- option
- .setName("type")
- .setDescription("The type of counter to create")
- .setRequired(true)
- .addChoices(
- { name: "members and bots", value: "members" },
- { name: "members only", value: "members_only" },
- { name: "bots only", value: "bots" }
- )
- )
- .addStringOption(option =>
- option
- .setName("channel")
- .setDescription("The channel type for the counter")
- .setRequired(true)
- .addChoices(
- { name: "voice channel (recommended)", value: "voice" },
- { name: "text channel", value: "text" }
- )
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("list")
- .setDescription("List all counters for this server")
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("update")
- .setDescription("Update an existing counter")
- .addStringOption(option =>
- option
- .setName("counter-id")
- .setDescription("The ID of the counter to update")
- .setRequired(true)
- )
- .addStringOption(option =>
- option
- .setName("type")
- .setDescription("The new counter type")
- .setRequired(false)
- .addChoices(
- { name: "members and bots", value: "members" },
- { name: "members only", value: "members_only" },
- { name: "bots only", value: "bots" }
- )
- )
- .addChannelOption(option =>
- option
- .setName("channel")
- .setDescription("The new channel for the counter")
- .setRequired(false)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("delete")
- .setDescription("Delete an existing counter")
- .addStringOption(option =>
- option
- .setName("counter-id")
- .setDescription("The ID of the counter to delete")
- .setRequired(true)
- )
- ),
-
- async execute(interaction, guildConfig, client) {
- const subcommand = interaction.options.getSubcommand();
-
- try {
- switch (subcommand) {
- case "create":
- await handleCreate(interaction, client);
- break;
- case "list":
- await handleList(interaction, client);
- break;
- case "update":
- await handleUpdate(interaction, client);
- break;
- case "delete":
- await handleDelete(interaction, client);
- break;
- default:
- await interaction.reply({
- embeds: [errorEmbed("Unknown subcommand.")],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error(`Error in counter ${subcommand}:`, error);
-
- const errorEmbed = createEmbed({
- title: "⌠Error",
- description: "An error occurred while processing your request.",
- color: 0xFF0000
- });
-
- if (!interaction.replied && !interaction.deferred) {
- await interaction.reply({ embeds: [errorEmbed], flags: MessageFlags.Ephemeral }).catch(console.error);
- } else {
- await interaction.followUp({ embeds: [errorEmbed], flags: MessageFlags.Ephemeral }).catch(console.error);
- }
- }
- }
-};
-
-
diff --git a/src/commands/Counter/modules/counter_create.js b/src/commands/Counter/modules/counter_create.js
deleted file mode 100644
index e1f19748d0..0000000000
--- a/src/commands/Counter/modules/counter_create.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import { PermissionFlagsBits, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { getServerCounters, saveServerCounters, updateCounter } from '../../../services/counterService.js';
-
-/**
- * Handle counter creation subcommand
- * @param {CommandInteraction} interaction - The command interaction
- * @param {Client} client - Discord client
- */
-export async function handleCreate(interaction, client) {
- const guild = interaction.guild;
- const type = interaction.options.getString("type");
- const channelType = interaction.options.getString("channel");
-
- if (!interaction.member.permissions.has(PermissionFlagsBits.ManageChannels)) {
- await interaction.reply({
- embeds: [errorEmbed("You need **Manage Channels** permission to create counters.")],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- await interaction.deferReply();
-
- try {
- const counters = await getServerCounters(client, guild.id);
-
- let targetChannel;
- const channels = guild.channels.cache;
-
- if (channelType === "voice") {
- targetChannel = channels.find(c => c.type === ChannelType.GuildVoice);
- } else {
- targetChannel = channels.find(c => c.type === ChannelType.GuildText);
- }
-
- if (!targetChannel) {
- await interaction.editReply({
- embeds: [errorEmbed(`No ${channelType} channels found in this server.`)]
- });
- return;
- }
-
- const existingCounter = counters.find(c => c.channelId === targetChannel.id);
- if (existingCounter) {
- await interaction.editReply({
- embeds: [errorEmbed(`A counter already exists for channel **${targetChannel.name}**. Please delete it first or choose a different channel type.`)]
- });
- return;
- }
-
- const newCounter = {
- id: Date.now().toString(),
- type: type,
- channelId: targetChannel.id,
- guildId: guild.id,
- createdAt: new Date().toISOString()
- };
-
- counters.push(newCounter);
-
- const saved = await saveServerCounters(client, guild.id, counters);
- if (!saved) {
- await interaction.editReply({
- embeds: [errorEmbed("Failed to save counter data. Please try again.")]
- });
- return;
- }
-
- const updated = await updateCounter(client, guild, newCounter);
- if (!updated) {
- await interaction.editReply({
- embeds: [errorEmbed("Counter created but failed to update channel name. The counter will update on the next scheduled run.")]
- });
- return;
- }
-
- const typeDisplay = {
- members: "members and bots",
- bots: "bots only",
- members_only: "members only"
- };
-
- await interaction.editReply({
- embeds: [successEmbed(`✅ **Counter Created Successfully!**\n\n**Type:** ${typeDisplay[type]}\n**Channel Type:** ${channelType}\n**Channel:** ${targetChannel}\n**Channel Name:** ${targetChannel.name}\n**Counter ID:** \`${newCounter.id}\`\n\nThe counter will automatically update every 15 minutes.\n\nUse \`/counter list\` to view all counters.`)]
- });
-
- } catch (error) {
- console.error("Error creating counter:", error);
- await interaction.editReply({
- embeds: [errorEmbed("An error occurred while creating the counter. Please try again.")]
- });
- }
-}
-
diff --git a/src/commands/Counter/modules/counter_delete.js b/src/commands/Counter/modules/counter_delete.js
deleted file mode 100644
index 29b0e4d394..0000000000
--- a/src/commands/Counter/modules/counter_delete.js
+++ /dev/null
@@ -1,182 +0,0 @@
-import { PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { getServerCounters, saveServerCounters } from '../../../services/counterService.js';
-
-/**
- * Handle counter deletion subcommand
- * @param {CommandInteraction} interaction - The command interaction
- * @param {Client} client - Discord client
- */
-export async function handleDelete(interaction, client) {
- const guild = interaction.guild;
- const counterId = interaction.options.getString("counter-id");
-
- if (!interaction.member.permissions.has(PermissionFlagsBits.ManageChannels)) {
- await interaction.reply({
- embeds: [errorEmbed("You need **Manage Channels** permission to delete counters.")],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- await interaction.deferReply();
-
- try {
- const counters = await getServerCounters(client, guild.id);
-
- if (counters.length === 0) {
- await interaction.editReply({
- embeds: [errorEmbed("No counters found to delete.")]}
- );
- return;
- }
-
- const counterToDelete = counters.find(c => c.id === counterId);
- if (!counterToDelete) {
- await interaction.editReply({
- embeds: [errorEmbed(`Counter with ID \`${counterId}\` not found. Use \`/counter list\` to see all counters.`)]}
- );
- return;
- }
-
- const channel = guild.channels.cache.get(counterToDelete.channelId);
-
- const embed = createEmbed({
- title: "âš ï¸ Delete Counter & Channel",
- description: `Are you sure you want to delete this counter and its channel?\n\n**ID:** \`${counterToDelete.id}\`\n**Type:** ${getCounterTypeDisplay(counterToDelete.type)}\n**Channel:** ${channel || 'Deleted Channel'}\n\nâš ï¸ **The channel will be permanently deleted!**`,
- color: 0xFF0000
- });
-
- const row = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`confirm_delete_${counterToDelete.id}`)
- .setLabel("Confirm Delete")
- .setStyle(ButtonStyle.Danger),
- new ButtonBuilder()
- .setCustomId(`cancel_delete_${counterToDelete.id}`)
- .setLabel("Cancel")
- .setStyle(ButtonStyle.Secondary)
- );
-
- await interaction.editReply({ embeds: [embed], components: [row] });
-
- const filter = (i) => i.user.id === interaction.user.id && i.customId.includes(counterToDelete.id);
- const collector = interaction.channel.createMessageComponentCollector({
- filter,
- componentType: ComponentType.Button,
-time: 30000,
- max: 1
- });
-
- collector.on("collect", async (i) => {
- try {
- if (i.customId === `confirm_delete_${counterToDelete.id}`) {
- await i.update({ content: "Deleting counter...", components: [] });
- await performDeletion(interaction, client, guild, counterToDelete);
- } else if (i.customId === `cancel_delete_${counterToDelete.id}`) {
- await i.update({
- embeds: [createEmbed({
- title: "⌠Cancelled",
- description: "Counter deletion cancelled.",
- color: 0xFF0000
- })],
- components: []
- });
- }
- } catch (error) {
- console.error("Error in button interaction:", error);
- }
- });
-
- collector.on("end", async (collected) => {
- if (collected.size === 0) {
- await interaction.editReply({
- embeds: [createEmbed({
- title: "⌠Cancelled",
- description: "Counter deletion cancelled - no confirmation received.",
- color: 0xFF0000
- })],
- components: []
- });
- }
- });
-
- } catch (error) {
- console.error("Error in handleDelete:", error);
- await interaction.editReply({
- embeds: [errorEmbed("An error occurred while fetching counters. Please try again.")]}
- );
- }
-}
-
-/**
- * Perform the actual counter deletion
- * @param {CommandInteraction} interaction - The command interaction
- * @param {Client} client - Discord client
- * @param {Guild} guild - The guild
- * @param {Object} counter - The counter to delete
- */
-async function performDeletion(interaction, client, guild, counter) {
- try {
- const counters = await getServerCounters(client, guild.id);
-
- const updatedCounters = counters.filter(c => c.id !== counter.id);
-
- const saved = await saveServerCounters(client, guild.id, updatedCounters);
- if (!saved) {
- await interaction.followUp({
- embeds: [errorEmbed("Failed to delete counter. Please try again.")],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- const channel = guild.channels.cache.get(counter.channelId);
- let channelDeleted = false;
-
- if (channel) {
- try {
- await channel.delete(`Counter deleted - removing channel: ${counter.id}`);
- channelDeleted = true;
- } catch (error) {
- console.error("Error deleting channel:", error);
- }
- }
-
- let message = `✅ **Counter Deleted Successfully!**\n\n**ID:** \`${counter.id}\`\n**Type:** ${getCounterTypeDisplay(counter.type)}`;
-
- if (channelDeleted) {
- message += `\n**Channel:** ${channel.name} (deleted)`;
- } else if (channel) {
- message += `\n**Channel:** ${channel.name} (failed to delete)`;
- } else {
- message += `\n**Channel:** Already deleted`;
- }
-
- await interaction.followUp({
- embeds: [successEmbed(message)]
- });
-
- } catch (error) {
- console.error("Error deleting counter:", error);
- await interaction.followUp({
- embeds: [errorEmbed("An error occurred while deleting the counter. Please try again.")],
- flags: MessageFlags.Ephemeral
- });
- }
-}
-
-/**
- * Get display name for counter type
- * @param {string} type - Counter type
- * @returns {string} Display name
- */
-function getCounterTypeDisplay(type) {
- const types = {
- members: "👥 Members",
- bots: "🤖 Bots",
- members_only: "👤 Humans"
- };
- return types[type] || "â“ Unknown";
-}
-
diff --git a/src/commands/Counter/modules/counter_list.js b/src/commands/Counter/modules/counter_list.js
deleted file mode 100644
index a65a715c55..0000000000
--- a/src/commands/Counter/modules/counter_list.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import { PermissionFlagsBits } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { getServerCounters } from '../../../services/counterService.js';
-
-/**
- * Handle counter listing subcommand
- * @param {CommandInteraction} interaction - The command interaction
- * @param {Client} client - Discord client
- */
-export async function handleList(interaction, client) {
- const guild = interaction.guild;
-
- if (!interaction.member.permissions.has(PermissionFlagsBits.ManageChannels)) {
- await interaction.reply({
- embeds: [errorEmbed("You need **Manage Channels** permission to view counters.")],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- await interaction.deferReply();
-
- try {
- const counters = await getServerCounters(client, guild.id);
-
- if (counters.length === 0) {
- const embed = createEmbed({
- title: "📋 Server Counters",
- description: "No counters have been set up for this server yet.\n\nUse `/counter create` to set up your first counter!",
- color: 0xFFFF00
- });
-
- embed.addFields({
- name: "🔧 **Available Counter Types**",
- value: "👥 **Members** - Total server members\n🤖 **Bots** - Bot count only\n👤 **Humans** - Human members only",
- inline: false
- });
-
- embed.addFields({
- name: "📠**Usage Examples**",
- value: "`/counter create type:Members channel:#general`\n`/counter create type:Bots channel:#member-count`\n`/counter list`",
- inline: false
- });
-
- embed.setFooter({
- text: "Counter System • Automatic updates every 15 minutes"
- });
-
- await interaction.editReply({ embeds: [embed] });
- return;
- }
-
- const embed = createEmbed({
- title: `📋 Server Counters (${counters.length})`,
- description: "Here are all the active counters for this server.\n\nCounters automatically update every 15 minutes.",
- color: 0x0099FF
- });
-
- for (let i = 0; i < counters.length; i++) {
- const counter = counters[i];
- const channel = guild.channels.cache.get(counter.channelId);
-
- if (!channel) {
- embed.addFields({
- name: `⌠Counter #${i + 1} - Channel Missing`,
- value: `**ID:** \`${counter.id}\`\n**Type:** ${getCounterTypeDisplay(counter.type)}\n**Channel:** Deleted channel (ID: ${counter.channelId})\n**Status:** âš ï¸ Channel no longer exists\n**Created:** ${new Date(counter.createdAt).toLocaleDateString()}`,
- inline: false
- });
- continue;
- }
-
- const currentCount = getCurrentCount(guild, counter.type);
- const status = channel.name.includes(':') ? '✅ Active' : 'âš ï¸ Not Updated';
-
- embed.addFields({
- name: `${getCounterEmoji(counter.type)} Counter #${i + 1} - ${channel.name}`,
- value: `**ID:** \`${counter.id}\`\n**Type:** ${getCounterTypeDisplay(counter.type)}\n**Channel:** ${channel}\n**Current Count:** ${currentCount}\n**Status:** ${status}\n**Created:** ${new Date(counter.createdAt).toLocaleDateString()}`,
- inline: false
- });
- }
-
- embed.addFields({
- name: "📊 **Statistics**",
- value: `**Total Counters:** ${counters.length}\n**Active Counters:** ${counters.filter(c => {
- const channel = guild.channels.cache.get(c.channelId);
- return channel && channel.name.includes(':');
- }).length}\n**Next Update:** `,
- inline: false
- });
-
- embed.addFields({
- name: "🔧 **Management Commands**",
- value: "`/counter create` - Create new counter\n`/counter update` - Update existing counter\n`/counter delete` - Delete counter",
- inline: false
- });
-
- embed.setFooter({
- text: "Counter System • Automatic updates every 15 minutes"
- });
- embed.setTimestamp();
-
- await interaction.editReply({ embeds: [embed] });
-
- } catch (error) {
- console.error("Error displaying counters:", error);
- await interaction.editReply({
- embeds: [errorEmbed("An error occurred while fetching counters. Please try again.")]
- });
- }
-}
-
-/**
- * Get display name for counter type
- * @param {string} type - Counter type
- * @returns {string} Display name
- */
-function getCounterTypeDisplay(type) {
- const types = {
- members: "👥 Members",
- bots: "🤖 Bots",
- members_only: "👤 Humans"
- };
- return types[type] || "â“ Unknown";
-}
-
-/**
- * Get emoji for counter type
- * @param {string} type - Counter type
- * @returns {string} Emoji
- */
-function getCounterEmoji(type) {
- const emojis = {
- members: "👥",
- bots: "🤖",
- members_only: "👤"
- };
- return emojis[type] || "â“";
-}
-
-/**
- * Get current count for a counter type
- * @param {Guild} guild - The guild
- * @param {string} type - Counter type
- * @returns {number} Current count
- */
-function getCurrentCount(guild, type) {
- switch (type) {
- case "members":
- return guild.memberCount;
- case "bots":
- return guild.members.cache.filter((m) => m.user.bot).size;
- case "members_only":
- return guild.members.cache.filter((m) => !m.user.bot).size;
- default:
- return 0;
- }
-}
-
diff --git a/src/commands/Counter/modules/counter_update.js b/src/commands/Counter/modules/counter_update.js
deleted file mode 100644
index 4deb67c8f7..0000000000
--- a/src/commands/Counter/modules/counter_update.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import { PermissionFlagsBits, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../../utils/embeds.js';
-import { getServerCounters, saveServerCounters, updateCounter } from '../../../services/counterService.js';
-
-/**
- * Handle counter update subcommand
- * @param {CommandInteraction} interaction - The command interaction
- * @param {Client} client - Discord client
- */
-export async function handleUpdate(interaction, client) {
- const guild = interaction.guild;
- const counterId = interaction.options.getString("counter-id");
- const newType = interaction.options.getString("type");
- const newChannel = interaction.options.getChannel("channel");
-
- if (!interaction.member.permissions.has(PermissionFlagsBits.ManageChannels)) {
- await interaction.reply({
- embeds: [errorEmbed("You need **Manage Channels** permission to update counters.")],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- if (newChannel && newChannel.type !== ChannelType.GuildText && newChannel.type !== ChannelType.GuildVoice) {
- await interaction.reply({
- embeds: [errorEmbed("Counters can only be placed on text or voice channels.")],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- if (!newType && !newChannel) {
- await interaction.reply({
- embeds: [errorEmbed("You must provide at least one option to update (type or channel).")],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- await interaction.deferReply();
-
- try {
- const counters = await getServerCounters(client, guild.id);
-
- const counterIndex = counters.findIndex(c => c.id === counterId);
- if (counterIndex === -1) {
- await interaction.editReply({
- embeds: [errorEmbed(`Counter with ID \`${counterId}\` not found. Use \`/counter list\` to see all counters.`)]
- });
- return;
- }
-
- const counter = counters[counterIndex];
- const oldChannel = guild.channels.cache.get(counter.channelId);
-
- if (!oldChannel) {
- await interaction.editReply({
- embeds: [errorEmbed("The channel for this counter no longer exists. You cannot update a counter for a deleted channel.")]}
- );
- return;
- }
-
- if (newChannel && newChannel.id !== counter.channelId) {
- const existingCounter = counters.find(c => c.channelId === newChannel.id);
- if (existingCounter) {
- await interaction.editReply({
- embeds: [errorEmbed("The selected channel already has a counter. Please choose a different channel.")]}
- );
- return;
- }
- }
-
- const oldType = counter.type;
- const oldChannelId = counter.channelId;
-
- if (newType) counter.type = newType;
- if (newChannel) counter.channelId = newChannel.id;
- counter.updatedAt = new Date().toISOString();
-
- const saved = await saveServerCounters(client, guild.id, counters);
- if (!saved) {
- await interaction.editReply({
- embeds: [errorEmbed("Failed to save updated counter data. Please try again.")]}
- );
- return;
- }
-
- const updatedCounter = counters[counterIndex];
- const updated = await updateCounter(client, guild, updatedCounter);
- if (!updated) {
- await interaction.editReply({
- embeds: [errorEmbed("Counter updated but failed to update channel name. The counter will update on the next scheduled run.")]}
- );
- return;
- }
-
- const finalChannel = guild.channels.cache.get(updatedCounter.channelId);
- const typeEmoji = {
- members: "👥",
- bots: "🤖",
- members_only: "👤"
- };
-
- const typeDisplay = {
- members: "Members",
- bots: "Bots",
- members_only: "Humans"
- };
-
- const changes = [];
- if (newType && newType !== oldType) {
- changes.push(`**Type:** ${typeEmoji[oldType]} ${typeDisplay[oldType]} → ${typeEmoji[newType]} ${typeDisplay[newType]}`);
- }
- if (newChannel && newChannel.id !== oldChannelId) {
- changes.push(`**Channel:** ${oldChannel} → ${finalChannel}`);
- }
-
- await interaction.editReply({
- embeds: [successEmbed(`✅ **Counter Updated Successfully!**\n\n**Counter ID:** \`${counterId}\`\n\n${changes.join('\n')}\n\n**New Settings:**\n**Type:** ${typeEmoji[updatedCounter.type]} ${typeDisplay[updatedCounter.type]}\n**Channel:** ${finalChannel}\n**Channel Name:** ${finalChannel.name}\n\nThe counter will automatically update every 15 minutes.`)]
- });
-
- } catch (error) {
- console.error("Error updating counter:", error);
- await interaction.editReply({
- embeds: [errorEmbed("An error occurred while updating the counter. Please try again.")]}
- );
- }
-}
-
diff --git a/src/commands/Economy/balance.js b/src/commands/Economy/balance.js
deleted file mode 100644
index bf88f72c59..0000000000
--- a/src/commands/Economy/balance.js
+++ /dev/null
@@ -1,81 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, getMaxBankCapacity } from '../../utils/economy.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('balance')
- .setDescription("Check your or someone else's balance")
- .addUserOption(option =>
- option
- .setName('user')
- .setDescription('User to check balance for')
- .setRequired(false)
- ),
-
- async execute(interaction, config, client) {
- try {
- const targetUser = interaction.options.getUser("user") || interaction.user;
- const guildId = interaction.guildId;
-
- if (targetUser.bot) {
- return await interaction.reply({
- embeds: [errorEmbed(
- "⌠Invalid Target",
- "Bots don't have an economy balance."
- )],
- flags: MessageFlags.Ephemeral
- });
- }
-
- const userData = await getEconomyData(client, guildId, targetUser.id);
- const maxBank = getMaxBankCapacity(userData);
-
- const wallet = typeof userData.wallet === 'number' ? userData.wallet : 0;
- const bank = typeof userData.bank === 'number' ? userData.bank : 0;
-
- const embed = createEmbed({
- title: `💰 ${targetUser.username}'s Balance`,
- description: `Here is the current financial status for ${targetUser.username}.`,
- })
- .addFields(
- {
- name: "💵 Cash",
- value: `$${wallet.toLocaleString()}`,
- inline: true,
- },
- {
- name: "🦠Bank",
- value: `$${bank.toLocaleString()} / $${maxBank.toLocaleString()}`,
- inline: true,
- },
- {
- name: "💎 Total",
- value: `$${(wallet + bank).toLocaleString()}`,
- inline: true,
- }
- )
- .setFooter({
- text: `Requested by ${interaction.user.tag}`,
- iconURL: interaction.user.displayAvatarURL(),
- });
-
- await interaction.reply({ embeds: [embed] });
- } catch (error) {
- console.error('Balance command error:', error);
- try {
- await interaction.reply({
- embeds: [errorEmbed(
- "⌠Error",
- "Something went wrong while checking balance. Please try again."
- )],
- flags: MessageFlags.Ephemeral
- });
- } catch (replyError) {
- console.error('Failed to send error response:', replyError);
- }
- }
- },
-};
-
diff --git a/src/commands/Economy/beg.js b/src/commands/Economy/beg.js
deleted file mode 100644
index 548aab4771..0000000000
--- a/src/commands/Economy/beg.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData } from '../../utils/economy.js';
-import { botConfig } from '../../config/bot.js';
-import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { MessageTemplates } from '../../utils/messageTemplates.js';
-
-const COOLDOWN = 30 * 60 * 1000;
-const MIN_WIN = 50;
-const MAX_WIN = 200;
-const SUCCESS_CHANCE = 0.7;
-
-export default {
- data: new SlashCommandBuilder()
- .setName('beg')
- .setDescription('Beg for a small amount of money'),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- await interaction.deferReply();
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
-
- let userData = await getEconomyData(client, guildId, userId);
-
- if (!userData) {
- throw createError(
- "Failed to load economy data",
- ErrorTypes.DATABASE,
- "Failed to load your economy data. Please try again later.",
- { userId, guildId }
- );
- }
-
- const lastBeg = userData.lastBeg || 0;
- const remainingTime = lastBeg + COOLDOWN - Date.now();
-
- if (remainingTime > 0) {
- const minutes = Math.floor(remainingTime / 60000);
- const seconds = Math.floor((remainingTime % 60000) / 1000);
-
- let timeMessage =
- minutes > 0 ? `${minutes} minute(s)` : `${seconds} second(s)`;
-
- throw createError(
- "Beg cooldown active",
- ErrorTypes.RATE_LIMIT,
- `You are tired from begging! Try again in **${timeMessage}**.`,
- { remainingTime, minutes, seconds, cooldownType: 'beg' }
- );
- }
-
- const success = Math.random() < SUCCESS_CHANCE;
-
- let replyEmbed;
- let newCash = userData.wallet;
-
- if (success) {
- const amountWon =
- Math.floor(Math.random() * (MAX_WIN - MIN_WIN + 1)) + MIN_WIN;
-
- newCash += amountWon;
-
- const successMessages = [
- `A kind stranger drops **$${amountWon.toLocaleString()}** into your cup.`,
- `You spotted an unattended wallet! You grab **$${amountWon.toLocaleString()}** and run.`,
- `Someone took pity on you and gave you **$${amountWon.toLocaleString()}**!`,
- `You found **$${amountWon.toLocaleString()}** under a park bench.`,
- ];
-
- replyEmbed = MessageTemplates.SUCCESS.DATA_UPDATED(
- "begging",
- successMessages[
- Math.floor(Math.random() * successMessages.length)
- ]
- );
- } else {
- const failMessages = [
- "The police chased you off. You got nothing.",
- "Someone yelled, 'Get a job!' and walked past.",
- "A squirrel stole the single coin you had.",
- "You tried to beg, but you were too embarrassed and gave up.",
- ];
-
- replyEmbed = MessageTemplates.ERRORS.INSUFFICIENT_FUNDS(
- "nothing",
- "You failed to get any money from begging."
- );
- replyEmbed.data.description = failMessages[Math.floor(Math.random() * failMessages.length)];
- }
-
- userData.wallet = newCash;
-userData.lastBeg = Date.now();
-
- await setEconomyData(client, guildId, userId, userData);
-
- await interaction.editReply({ embeds: [replyEmbed] });
- }, { command: 'beg' });
- },
-};
-
diff --git a/src/commands/Economy/buy.js b/src/commands/Economy/buy.js
deleted file mode 100644
index 64127010a1..0000000000
--- a/src/commands/Economy/buy.js
+++ /dev/null
@@ -1,161 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { MessageFlags } from 'discord.js';
-import { getPromoRow } from '../../utils/components.js';
-import { shopItems } from '../../config/shop/items.js';
-import { getEconomyData, setEconomyData } from '../../utils/economy.js';
-import { getGuildConfig } from '../../services/guildConfig.js';
-import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { MessageTemplates } from '../../utils/messageTemplates.js';
-
-const SHOP_ITEMS = shopItems;
-
-export default {
- data: new SlashCommandBuilder()
- .setName('buy')
- .setDescription('Buy an item from the shop')
- .addStringOption(option =>
- option
- .setName('item_id')
- .setDescription('ID of the item to buy')
- .setRequired(true)
- )
- .addIntegerOption(option =>
- option
- .setName('quantity')
- .setDescription('Quantity to buy (default: 1)')
- .setRequired(false)
- .setMinValue(1)
- .setMaxValue(10)
- ),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- await interaction.deferReply();
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const itemId = interaction.options.getString("item_id").toLowerCase();
- const quantity = interaction.options.getInteger("quantity") || 1;
-
- const item = SHOP_ITEMS[itemId];
-
- if (!item) {
- throw createError(
- `Item ${itemId} not found`,
- ErrorTypes.VALIDATION,
- `The item ID \`${itemId}\` does not exist in the shop.`,
- { itemId }
- );
- }
-
- if (quantity < 1) {
- throw createError(
- "Invalid quantity",
- ErrorTypes.VALIDATION,
- "You must purchase a quantity of 1 or more.",
- { quantity }
- );
- }
-
- const totalCost = item.price * quantity;
-
- const guildConfig = await getGuildConfig(client, guildId);
- const PREMIUM_ROLE_ID = guildConfig.premiumRoleId;
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (userData.wallet < totalCost) {
- throw createError(
- "Insufficient funds",
- ErrorTypes.VALIDATION,
- `You need **$${totalCost.toLocaleString()}** to purchase ${quantity}x **${item.name}**, but you only have **$${userData.wallet.toLocaleString()}** in cash.`,
- { required: totalCost, current: userData.wallet, itemId, quantity }
- );
- }
-
- if (item.type === "role" && itemId === "premium_role") {
- if (!PREMIUM_ROLE_ID) {
- throw createError(
- "Premium role not configured",
- ErrorTypes.CONFIGURATION,
- "The **Premium Shop Role** has not been configured by a server administrator yet.",
- { itemId }
- );
- }
- if (interaction.member.roles.cache.has(PREMIUM_ROLE_ID)) {
- throw createError(
- "Role already owned",
- ErrorTypes.VALIDATION,
- `You already have the **${item.name}** role.`,
- { itemId, roleId: PREMIUM_ROLE_ID }
- );
- }
- if (quantity > 1) {
- throw createError(
- "Invalid quantity for role",
- ErrorTypes.VALIDATION,
- `You can only purchase the **${item.name}** role once.`,
- { itemId, quantity }
- );
- }
- }
-
- userData.wallet -= totalCost;
-
- let successDescription = `You successfully purchased ${quantity}x **${item.name}** for **$${totalCost.toLocaleString()}**!`;
-
- if (item.type === "role" && itemId === "premium_role") {
- const member = interaction.member;
-
- const role = interaction.guild.roles.cache.get(PREMIUM_ROLE_ID);
-
- if (!role) {
- throw createError(
- "Role not found",
- ErrorTypes.CONFIGURATION,
- "The configured premium role no longer exists in this guild.",
- { roleId: PREMIUM_ROLE_ID }
- );
- }
-
- try {
- await member.roles.add(
- role,
- `Purchased role: ${item.name}`,
- );
- successDescription += `\n\n**👑 The role ${role.toString()} has been granted to you!**`;
- } catch (roleError) {
- userData.wallet += totalCost;
- await setEconomyData(client, guildId, userId, userData);
- throw createError(
- "Role assignment failed",
- ErrorTypes.DISCORD_API,
- "Successfully deducted money, but failed to grant the role. Your cash has been refunded.",
- { roleId: PREMIUM_ROLE_ID, originalError: roleError.message }
- );
- }
- } else if (item.type === "upgrade") {
- userData.upgrades[itemId] = true;
- successDescription += `\n\n**✨ Your upgrade is now active!**`;
- } else if (item.type === "consumable") {
- userData.inventory[itemId] =
- (userData.inventory[itemId] || 0) + quantity;
- }
-
- await setEconomyData(client, guildId, userId, userData);
-
- const embed = successEmbed(
- "💰 Purchase Successful",
- successDescription,
- ).addFields({
- name: "New Balance",
- value: `$${userData.wallet.toLocaleString()}`,
- inline: true,
- });
-
- await interaction.editReply({ embeds: [embed], flags: [MessageFlags.Ephemeral] });
- }, { command: 'buy' });
- },
-};
-
diff --git a/src/commands/Economy/crime.js b/src/commands/Economy/crime.js
deleted file mode 100644
index cc30e1786d..0000000000
--- a/src/commands/Economy/crime.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData } from '../../utils/economy.js';
-import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { MessageTemplates } from '../../utils/messageTemplates.js';
-
-const CRIME_COOLDOWN = 60 * 60 * 1000;
-const MIN_CRIME_AMOUNT = 100;
-const MAX_CRIME_AMOUNT = 2000;
-const FAILURE_RATE = 0.4;
-const JAIL_TIME = 2 * 60 * 60 * 1000;
-
-const CRIME_TYPES = [
- { name: "Pickpocketing", min: 100, max: 500, risk: 0.3 },
- { name: "Burglary", min: 300, max: 1000, risk: 0.4 },
- { name: "Bank Heist", min: 1000, max: 5000, risk: 0.6 },
- { name: "Art Theft", min: 2000, max: 10000, risk: 0.7 },
- { name: "Cybercrime", min: 5000, max: 20000, risk: 0.8 },
-];
-
-export default {
- data: new SlashCommandBuilder()
- .setName('crime')
- .setDescription('Commit a crime to earn money (risky)')
- .addStringOption(option =>
- option
- .setName('type')
- .setDescription('Type of crime to commit')
- .setRequired(true)
- .addChoices(
- { name: 'Pickpocketing', value: 'pickpocketing' },
- { name: 'Burglary', value: 'burglary' },
- { name: 'Bank Heist', value: 'bank-heist' },
- { name: 'Art Theft', value: 'art-theft' },
- { name: 'Cybercrime', value: 'cybercrime' },
- )
- ),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- await interaction.deferReply();
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const now = Date.now();
-
- const userData = await getEconomyData(client, guildId, userId);
- const lastCrime = userData.cooldowns?.crime || 0;
- const isJailed = userData.jailedUntil && userData.jailedUntil > now;
-
- if (isJailed) {
- const timeLeft = Math.ceil((userData.jailedUntil - now) / (1000 * 60));
- throw createError(
- "User is in jail",
- ErrorTypes.RATE_LIMIT,
- `You're in jail for ${timeLeft} more minutes!`,
- { jailTimeRemaining: userData.jailedUntil - now }
- );
- }
-
- if (now < lastCrime + CRIME_COOLDOWN) {
- const timeLeft = Math.ceil((lastCrime + CRIME_COOLDOWN - now) / (1000 * 60));
- throw createError(
- "Crime cooldown active",
- ErrorTypes.RATE_LIMIT,
- `You need to wait ${timeLeft} more minutes before committing another crime.`,
- { remaining: lastCrime + CRIME_COOLDOWN - now, cooldownType: 'crime' }
- );
- }
-
- const crimeType = interaction.options.getString("type").toLowerCase();
- const crime = CRIME_TYPES.find(
- c => c.name.toLowerCase().replace(/\s+/g, '-') === crimeType
- );
-
- if (!crime) {
- throw createError(
- "Invalid crime type",
- ErrorTypes.VALIDATION,
- "Please select a valid crime type.",
- { crimeType }
- );
- }
-
- const isSuccess = Math.random() > crime.risk;
- const amountEarned = isSuccess
- ? Math.floor(Math.random() * (crime.max - crime.min + 1)) + crime.min
- : 0;
-
- userData.cooldowns = userData.cooldowns || {};
- userData.cooldowns.crime = now;
-
- if (isSuccess) {
- userData.wallet = (userData.wallet || 0) + amountEarned;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const embed = successEmbed(
- "Crime Successful!",
- `You successfully committed ${crime.name} and earned **${amountEarned}** coins!`
- );
-
- await interaction.editReply({ embeds: [embed] });
- } else {
- const fine = Math.floor(amountEarned * 0.2);
- userData.wallet = Math.max(0, (userData.wallet || 0) - fine);
- userData.jailedUntil = now + JAIL_TIME;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const embed = errorEmbed(
- "Crime Failed!",
- `You were caught while attempting ${crime.name} and have been sent to jail! ` +
- `You were fined ${fine} coins and will be in jail for 2 hours.`
- );
-
- await interaction.editReply({ embeds: [embed] });
- }
- }, { command: 'crime' });
- },
-};
-
diff --git a/src/commands/Economy/daily.js b/src/commands/Economy/daily.js
deleted file mode 100644
index 7b9847f8d2..0000000000
--- a/src/commands/Economy/daily.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData } from '../../utils/economy.js';
-import { getGuildConfig } from '../../services/guildConfig.js';
-import { formatDuration } from '../../utils/helpers.js';
-
-const DAILY_COOLDOWN = 24 * 60 * 60 * 1000;
-const DAILY_AMOUNT = 1000;
-const PREMIUM_BONUS_PERCENTAGE = 0.1;
-
-export default {
- data: new SlashCommandBuilder()
- .setName('daily')
- .setDescription('Claim your daily cash reward'),
-
- async execute(interaction, config, client) {
- try {
- await interaction.deferReply();
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const now = Date.now();
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (!userData) {
- return await interaction.editReply({
- embeds: [errorEmbed(
- "⌠Data Error",
- "Failed to load your economy data. Please try again later."
- )]
- });
- }
-
- const lastDaily = userData.lastDaily || 0;
-
- if (now < lastDaily + DAILY_COOLDOWN) {
- const timeRemaining = lastDaily + DAILY_COOLDOWN - now;
- return await interaction.editReply({
- embeds: [errorEmbed(
- "â±ï¸ Slow Down!",
- `You need to wait before claiming daily again. Try again in ${formatDuration(timeRemaining)}.`
- )]
- });
- }
-
- const guildConfig = await getGuildConfig(client, guildId);
- const PREMIUM_ROLE_ID = guildConfig.premiumRoleId;
-
- let earned = DAILY_AMOUNT;
- let bonusMessage = "";
- let hasPremiumRole = false;
-
- if (
- PREMIUM_ROLE_ID &&
- interaction.member &&
- interaction.member.roles.cache.has(PREMIUM_ROLE_ID)
- ) {
- const bonusAmount = Math.floor(
- DAILY_AMOUNT * PREMIUM_BONUS_PERCENTAGE,
- );
- earned += bonusAmount;
- bonusMessage = `\n✨ **Premium Bonus:** +$${bonusAmount.toLocaleString()}`;
- hasPremiumRole = true;
- }
-
- userData.wallet = (userData.wallet || 0) + earned;
-userData.lastDaily = now;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const embed = successEmbed(
- "✅ Daily Claimed!",
- `You have claimed your daily **$${earned.toLocaleString()}**!${bonusMessage}`
- )
- .addFields({
- name: "New Cash Balance",
- value: `$${userData.wallet.toLocaleString()}`,
- inline: true,
- })
- .setFooter({
- text: hasPremiumRole
- ? `Next claim in 24 hours. (Premium Active)`
- : `Next claim in 24 hours.`,
- });
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error('Daily command error:', error);
- try {
- await interaction.editReply({
- embeds: [errorEmbed(
- "⌠Error",
- "Something went wrong while claiming daily. Please try again."
- )]
- });
- } catch (replyError) {
- console.error('Failed to send error response:', replyError);
- }
- }
- },
-};
-
diff --git a/src/commands/Economy/deposit.js b/src/commands/Economy/deposit.js
deleted file mode 100644
index c917a2a4f1..0000000000
--- a/src/commands/Economy/deposit.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData, getMaxBankCapacity } from '../../utils/economy.js';
-import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { MessageTemplates } from '../../utils/messageTemplates.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('deposit')
- .setDescription('Deposit money from your wallet into your bank')
- .addStringOption(option =>
- option
- .setName('amount')
- .setDescription('Amount to deposit (number or "all")')
- .setRequired(true)
- ),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- await interaction.deferReply();
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const amountInput = interaction.options.getString("amount");
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (!userData) {
- throw createError(
- "Failed to load economy data",
- ErrorTypes.DATABASE,
- "Failed to load your economy data. Please try again later.",
- { userId, guildId }
- );
- }
-
- const maxBank = getMaxBankCapacity(userData);
- let depositAmount;
-
- if (amountInput.toLowerCase() === "all") {
- depositAmount = userData.wallet;
- } else {
- depositAmount = parseInt(amountInput);
-
- if (isNaN(depositAmount) || depositAmount <= 0) {
- throw createError(
- "Invalid deposit amount",
- ErrorTypes.VALIDATION,
- `Please enter a valid number or 'all'. You entered: \`${amountInput}\``,
- { amountInput, userId }
- );
- }
- }
-
- if (depositAmount === 0) {
- throw createError(
- "Zero deposit amount",
- ErrorTypes.VALIDATION,
- "You have no cash to deposit.",
- { userId, walletBalance: userData.wallet }
- );
- }
-
- if (depositAmount > userData.wallet) {
- depositAmount = userData.wallet;
- await interaction.followUp({
- embeds: [
- MessageTemplates.ERRORS.INVALID_INPUT(
- "deposit amount",
- `You tried to deposit more than you have. Depositing your remaining cash: **$${depositAmount.toLocaleString()}**`
- )
- ],
- flags: ["Ephemeral"],
- });
- }
-
- const availableSpace = maxBank - userData.bank;
-
- if (availableSpace <= 0) {
- throw createError(
- "Bank is full",
- ErrorTypes.VALIDATION,
- `Your bank is currently full (Max Capacity: $${maxBank.toLocaleString()}). Purchase a **Bank Upgrade** to increase your limit.`,
- { maxBank, currentBank: userData.bank, userId }
- );
- }
-
- if (depositAmount > availableSpace) {
- const originalDepositAmount = depositAmount;
- depositAmount = availableSpace;
-
- if (amountInput.toLowerCase() !== "all") {
- await interaction.followUp({
- embeds: [
- MessageTemplates.ERRORS.INVALID_INPUT(
- "deposit amount",
- `You only had space for **$${depositAmount.toLocaleString()}** in your bank account (Max: $${maxBank.toLocaleString()}). The rest remains in your cash.`
- )
- ],
- flags: ["Ephemeral"],
- });
- }
- }
-
- if (depositAmount === 0) {
- throw createError(
- "No space or cash for deposit",
- ErrorTypes.VALIDATION,
- "The amount you tried to deposit was either 0 or exceeded your bank capacity after checking your cash balance.",
- { depositAmount, availableSpace, walletBalance: userData.wallet }
- );
- }
-
- userData.wallet -= depositAmount;
- userData.bank += depositAmount;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const embed = MessageTemplates.SUCCESS.DATA_UPDATED(
- "deposit",
- `You successfully deposited **$${depositAmount.toLocaleString()}** into your bank.`
- )
- .addFields(
- {
- name: "💵 New Cash Balance",
- value: `$${userData.wallet.toLocaleString()}`,
- inline: true,
- },
- {
- name: "🦠New Bank Balance",
- value: `$${userData.bank.toLocaleString()} / $${maxBank.toLocaleString()}`,
- inline: true,
- },
- );
-
- await interaction.editReply({ embeds: [embed] });
- }, { command: 'deposit' });
- },
-};
-
-
diff --git a/src/commands/Economy/eleaderboard.js b/src/commands/Economy/eleaderboard.js
deleted file mode 100644
index 9ae071485f..0000000000
--- a/src/commands/Economy/eleaderboard.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyKey } from '../../utils/database.js';
-import { botConfig } from '../../config/bot.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("eleaderboard")
- .setDescription("View the server's top 10 richest users.")
- .addStringOption((option) =>
- option
- .setName("sort_by")
- .setDescription("The metric to sort the leaderboard by.")
- .addChoices(
- { name: "Net Worth (Cash + Bank)", value: "net_worth" },
- { name: "Cash", value: "cash" },
- { name: "Bank", value: "bank" },
- )
- .setRequired(false),
- )
- .setDMPermission(false),
-
-
- async execute(interaction, config, client) {
-const guildId = interaction.guildId;
- const sortBy = interaction.options.getString("sort_by") || "net_worth";
-
- try {
- const prefix = `guild:${guildId}:economy:`;
-
- let allKeys = await client.db.list(prefix);
-
- if (!Array.isArray(allKeys)) {
- allKeys = [];
- }
-
- if (allKeys.length === 0) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Leaderboard Empty",
- "No economy data found for this server.",
- ),
- ],
- });
- }
-
- let allUserData = [];
-
- for (const key of allKeys) {
- const userId = key.replace(prefix, "");
- const userData = await client.db.get(key);
-
- if (userData) {
- allUserData.push({
- userId: userId,
- cash: userData.wallet || 0,
- bank: userData.bank || 0,
- net_worth: (userData.wallet || 0) + (userData.bank || 0),
- });
- }
- }
-
- allUserData.sort((a, b) => {
- if (sortBy === "net_worth") return b.net_worth - a.net_worth;
- if (sortBy === "cash") return b.cash - a.cash;
- if (sortBy === "bank") return b.bank - a.bank;
-return b.net_worth - a.net_worth;
- });
-
- const topUsers = allUserData.slice(0, 10);
- const userRank =
- allUserData.findIndex((u) => u.userId === interaction.user.id) +
- 1;
- const rankEmoji = ["🥇", "🥈", "🥉"];
- const leaderboardEntries = [];
-
- for (let i = 0; i < topUsers.length; i++) {
- const user = topUsers[i];
- const member = await interaction.guild.members
- .fetch(user.userId)
- .catch(() => null);
- const username = member
- ? member.user.username
- : `Unknown User (${user.userId})`;
- const rank = i + 1;
- const emoji = rankEmoji[i] || `**#${rank}**`;
- const value =
- sortBy === "net_worth"
- ? user.net_worth
- : sortBy === "cash"
- ? user.cash
- : user.bank;
-
- leaderboardEntries.push(
- `${emoji} **${username}** - $${value.toLocaleString()}`,
- );
- }
-
- const fieldNameMap = {
- net_worth: "Net Worth (Cash + Bank)",
- cash: "Cash Balance",
- bank: "Bank Balance",
- };
-
- const embed = createEmbed(
- `👑 Economy Leaderboard (${fieldNameMap[sortBy]})`,
- leaderboardEntries.join("\n"),
- ).setFooter({
- text: `Your Rank: ${userRank > 0 ? userRank : "Not Ranked"} | Data sorted by ${fieldNameMap[sortBy]}`,
- });
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Leaderboard command error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "System Error",
- "Could not generate the leaderboard.",
- ),
- ],
- });
- }
- },
-};
-
-
diff --git a/src/commands/Economy/gamble.js b/src/commands/Economy/gamble.js
deleted file mode 100644
index 4e805818fd..0000000000
--- a/src/commands/Economy/gamble.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData } from '../../utils/economy.js';
-import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { MessageTemplates } from '../../utils/messageTemplates.js';
-
-const BASE_WIN_CHANCE = 0.4;
-const CLOVER_WIN_BONUS = 0.1;
-const PAYOUT_MULTIPLIER = 2.0;
-const GAMBLE_COOLDOWN = 5 * 60 * 1000;
-
-export default {
- data: new SlashCommandBuilder()
- .setName('gamble')
- .setDescription('Gamble your money for a chance to win more')
- .addIntegerOption(option =>
- option
- .setName('amount')
- .setDescription('Amount of cash to gamble')
- .setRequired(true)
- .setMinValue(1)
- ),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- await interaction.deferReply();
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const betAmount = interaction.options.getInteger("amount");
- const now = Date.now();
-
- const userData = await getEconomyData(client, guildId, userId);
- const lastGamble = userData.lastGamble || 0;
- let cloverCount = userData.inventory["lucky_clover"] || 0;
-
- if (now < lastGamble + GAMBLE_COOLDOWN) {
- const remaining = lastGamble + GAMBLE_COOLDOWN - now;
- const minutes = Math.floor(remaining / (1000 * 60));
- const seconds = Math.floor((remaining % (1000 * 60)) / 1000);
-
- throw createError(
- "Gamble cooldown active",
- ErrorTypes.RATE_LIMIT,
- `You need to cool down before gambling again. Wait **${minutes}m ${seconds}s**.`,
- { remaining, cooldownType: 'gamble' }
- );
- }
-
- if (userData.wallet < betAmount) {
- throw createError(
- "Insufficient cash for gamble",
- ErrorTypes.VALIDATION,
- `You only have $${userData.wallet.toLocaleString()} cash, but you are trying to bet $${betAmount.toLocaleString()}.`,
- { required: betAmount, current: userData.wallet }
- );
- }
-
- let winChance = BASE_WIN_CHANCE;
- let cloverMessage = "";
- let usedClover = false;
-
- if (cloverCount > 0) {
- winChance += CLOVER_WIN_BONUS;
- userData.inventory["lucky_clover"] -= 1;
- cloverMessage = `\n🀠**Lucky Clover Consumed:** Your win chance was boosted to **${Math.round(winChance * 100)}%**!`;
- usedClover = true;
- }
-
- const win = Math.random() < winChance;
- let cashChange = 0;
- let resultEmbed;
-
- if (win) {
- const amountWon = Math.floor(betAmount * PAYOUT_MULTIPLIER);
-cashChange = amountWon;
-
- resultEmbed = successEmbed(
- "🎉 You Won!",
- `You successfully gambled and turned your **$${betAmount.toLocaleString()}** bet into **$${amountWon.toLocaleString()}**!${cloverMessage}`,
- );
- } else {
-cashChange = -betAmount;
-
- resultEmbed = errorEmbed(
- "💔 You Lost...",
- `The dice rolled against you. You lost your **$${betAmount.toLocaleString()}** bet.`,
- );
- }
-
- userData.wallet = (userData.wallet || 0) + cashChange;
-userData.lastGamble = now;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const newCash = userData.wallet;
-
- resultEmbed.addFields({
- name: "💵 New Cash Balance",
- value: `$${newCash.toLocaleString()}`,
- inline: true,
- });
-
- if (usedClover) {
- resultEmbed.setFooter({
- text: `You have ${userData.inventory["lucky_clover"]} Lucky Clovers left.`,
- });
- } else {
- resultEmbed.setFooter({
- text: `Next gamble available in 5 minutes. Base win chance: ${Math.round(BASE_WIN_CHANCE * 100)}%.`,
- });
- }
-
- await interaction.editReply({ embeds: [resultEmbed] });
- }, { command: 'gamble' });
- },
-};
-
diff --git a/src/commands/Economy/inventory.js b/src/commands/Economy/inventory.js
deleted file mode 100644
index a408b22365..0000000000
--- a/src/commands/Economy/inventory.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { shopItems } from '../../config/shop/items.js';
-import { getEconomyData } from '../../utils/economy.js';
-
-const SHOP_ITEMS = shopItems;
-
-export default {
- data: new SlashCommandBuilder()
- .setName('inventory')
- .setDescription('View your economy inventory'),
-
- async execute(interaction, config, client) {
-const userId = interaction.user.id;
- const guildId = interaction.guildId;
-
- try {
- const userData = await getEconomyData(client, guildId, userId);
- const inventory = userData.inventory || {};
-
- let inventoryDescription = "Your inventory is currently empty.";
-
- if (Object.keys(inventory).length > 0) {
- inventoryDescription = Object.entries(inventory)
- .filter(
- ([itemId, quantity]) =>
- quantity > 0 && SHOP_ITEMS[itemId],
-)
- .map(
- ([itemId, quantity]) =>
- `**${SHOP_ITEMS[itemId].name}:** ${quantity}x`,
- )
- .join("\n");
- }
-
- const embed = createEmbed({ title: `📦 ${interaction.user.username}'s Inventory`, description: inventoryDescription, }).setThumbnail(interaction.user.displayAvatarURL());
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Inventory command error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "System Error",
- "Could not fetch inventory data.",
- ),
- ],
- });
- }
- },
-};
-
-
diff --git a/src/commands/Economy/mine.js b/src/commands/Economy/mine.js
deleted file mode 100644
index 184cd34b2f..0000000000
--- a/src/commands/Economy/mine.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData } from '../../utils/economy.js';
-import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { MessageTemplates } from '../../utils/messageTemplates.js';
-
-const MINE_COOLDOWN = 60 * 60 * 1000;
-const BASE_MIN_REWARD = 400;
-const BASE_MAX_REWARD = 1200;
-const DIAMOND_PICKAXE_MULTIPLIER = 1.5;
-
-const MINE_LOCATIONS = [
- "abandoned gold mine",
- "dark, damp cave",
- "backyard rock quarry",
- "volcanic obsidian vent",
- "deep-sea mineral trench",
-];
-
-export default {
- data: new SlashCommandBuilder()
- .setName('mine')
- .setDescription('Go mining to earn money'),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- await interaction.deferReply();
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const now = Date.now();
-
- const userData = await getEconomyData(client, guildId, userId);
- const lastMine = userData.lastMine || 0;
-const hasPickaxe = userData.inventory["diamond_pickaxe"] || 0;
-
- if (now < lastMine + MINE_COOLDOWN) {
- const remaining = lastMine + MINE_COOLDOWN - now;
- const hours = Math.floor(remaining / (1000 * 60 * 60));
- const minutes = Math.floor(
- (remaining % (1000 * 60 * 60)) / (1000 * 60),
- );
-
- throw createError(
- "Mining cooldown active",
- ErrorTypes.RATE_LIMIT,
- `Your pickaxe is cooling down. Wait for **${hours}h ${minutes}m** before mining again.`,
- { remaining, cooldownType: 'mine' }
- );
- }
-
- const baseEarned =
- Math.floor(
- Math.random() * (BASE_MAX_REWARD - BASE_MIN_REWARD + 1),
- ) + BASE_MIN_REWARD;
-
- let finalEarned = baseEarned;
- let multiplierMessage = "";
-
- if (hasPickaxe > 0) {
- finalEarned = Math.floor(
- baseEarned * DIAMOND_PICKAXE_MULTIPLIER,
- );
- multiplierMessage = `\n(💎 Diamond Pickaxe Bonus: **+50%**)`;
- }
-
- const location =
- MINE_LOCATIONS[
- Math.floor(Math.random() * MINE_LOCATIONS.length)
- ];
-
- userData.wallet += finalEarned;
-userData.lastMine = now;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const embed = successEmbed(
- "💰 Mining Expedition Successful!",
- `You explored a **${location}** and managed to find minerals worth **$${finalEarned.toLocaleString()}**!${multiplierMessage}`,
- )
- .addFields({
- name: "💵 New Cash Balance",
- value: `$${userData.wallet.toLocaleString()}`,
- inline: true,
- })
- .setFooter({ text: `Next mine available in 1 hour.` });
-
- await interaction.editReply({ embeds: [embed] });
- }, { command: 'mine' });
- },
-};
-
diff --git a/src/commands/Economy/pay.js b/src/commands/Economy/pay.js
deleted file mode 100644
index b67bd632d2..0000000000
--- a/src/commands/Economy/pay.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, addMoney, removeMoney, setEconomyData } from '../../utils/economy.js';
-import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { MessageTemplates } from '../../utils/messageTemplates.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('pay')
- .setDescription('Pay another user some of your cash')
- .addUserOption(option =>
- option
- .setName('user')
- .setDescription('User to pay')
- .setRequired(true)
- )
- .addIntegerOption(option =>
- option
- .setName('amount')
- .setDescription('Amount to pay')
- .setRequired(true)
- .setMinValue(1)
- ),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- await interaction.deferReply();
-
- const senderId = interaction.user.id;
- const receiver = interaction.options.getUser("user");
- const amount = interaction.options.getInteger("amount");
- const guildId = interaction.guildId;
-
- if (receiver.bot) {
- throw createError(
- "Cannot pay bot",
- ErrorTypes.VALIDATION,
- "You cannot pay a bot.",
- { receiverId: receiver.id, isBot: true }
- );
- }
-
- if (receiver.id === senderId) {
- throw createError(
- "Cannot pay self",
- ErrorTypes.VALIDATION,
- "You cannot pay yourself.",
- { senderId, receiverId: receiver.id }
- );
- }
-
- if (amount <= 0) {
- throw createError(
- "Invalid payment amount",
- ErrorTypes.VALIDATION,
- "Amount must be greater than zero.",
- { amount, senderId }
- );
- }
-
- const [senderData, receiverData] = await Promise.all([
- getEconomyData(client, guildId, senderId),
- getEconomyData(client, guildId, receiver.id)
- ]);
-
- if (!senderData) {
- throw createError(
- "Failed to load sender economy data",
- ErrorTypes.DATABASE,
- "Failed to load your economy data. Please try again later.",
- { userId: senderId, guildId }
- );
- }
-
- if (!receiverData) {
- throw createError(
- "Failed to load receiver economy data",
- ErrorTypes.DATABASE,
- "Failed to load the receiver's economy data. Please try again later.",
- { userId: receiver.id, guildId }
- );
- }
-
- if (senderData.wallet < amount) {
- throw createError(
- "Insufficient funds for payment",
- ErrorTypes.VALIDATION,
- `You only have $${senderData.wallet.toLocaleString()} in cash.`,
- { amount, walletBalance: senderData.wallet, senderId }
- );
- }
-
- senderData.wallet -= amount;
- receiverData.wallet += amount;
-
- await Promise.all([
- setEconomyData(client, guildId, senderId, senderData),
- setEconomyData(client, guildId, receiver.id, receiverData)
- ]);
-
- const embed = MessageTemplates.SUCCESS.DATA_UPDATED(
- "payment",
- `You successfully paid **${receiver.username}** the amount of **$${amount.toLocaleString()}**!`
- )
- .addFields(
- {
- name: "💳 Payment Amount",
- value: `$${amount.toLocaleString()}`,
- inline: true,
- },
- {
- name: "💵 Your New Balance",
- value: `$${senderData.wallet.toLocaleString()}`,
- inline: true,
- },
- )
- .setFooter({
- text: `Paid to ${receiver.tag}`,
- iconURL: receiver.displayAvatarURL(),
- });
-
- await interaction.editReply({ embeds: [embed] });
-
- try {
- const receiverEmbed = createEmbed({
- title: "💰 Incoming Payment!",
- description: `${interaction.user.username} paid you **$${amount.toLocaleString()}**.`
- }).addFields({
- name: "Your New Cash",
- value: `$${receiverData.wallet.toLocaleString()}`,
- inline: true,
- });
- await receiver.send({ embeds: [receiverEmbed] });
- } catch (e) {
- console.log(`Could not DM user ${receiver.id}:`, e.message);
- }
- }, { command: 'pay' });
- },
-};
-
-
diff --git a/src/commands/Economy/rob.js b/src/commands/Economy/rob.js
deleted file mode 100644
index 1a237b9ec7..0000000000
--- a/src/commands/Economy/rob.js
+++ /dev/null
@@ -1,156 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData } from '../../utils/economy.js';
-import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { MessageTemplates } from '../../utils/messageTemplates.js';
-
-const ROB_COOLDOWN = 4 * 60 * 60 * 1000;
-const BASE_ROB_SUCCESS_CHANCE = 0.25;
-const ROB_PERCENTAGE = 0.15;
-const FINE_PERCENTAGE = 0.1;
-
-export default {
- data: new SlashCommandBuilder()
- .setName('rob')
- .setDescription('Attempt to rob another user (very risky)')
- .addUserOption(option =>
- option
- .setName('user')
- .setDescription('User to rob')
- .setRequired(true)
- ),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- await interaction.deferReply();
-
- const robberId = interaction.user.id;
- const victimUser = interaction.options.getUser("user");
- const guildId = interaction.guildId;
- const now = Date.now();
-
- if (robberId === victimUser.id) {
- throw createError(
- "Cannot rob self",
- ErrorTypes.VALIDATION,
- "You cannot rob yourself.",
- { robberId, victimId: victimUser.id }
- );
- }
-
- if (victimUser.bot) {
- throw createError(
- "Cannot rob bot",
- ErrorTypes.VALIDATION,
- "You cannot rob a bot.",
- { victimId: victimUser.id, isBot: true }
- );
- }
-
- const robberData = await getEconomyData(client, guildId, robberId);
- const victimData = await getEconomyData(client, guildId, victimUser.id);
-
- if (!robberData || !victimData) {
- throw createError(
- "Failed to load economy data",
- ErrorTypes.DATABASE,
- "Failed to load economy data. Please try again later.",
- { robberId: !!robberData, victimId: !!victimData, guildId }
- );
- }
-
- const lastRob = robberData.lastRob || 0;
-
- if (now < lastRob + ROB_COOLDOWN) {
- const remaining = lastRob + ROB_COOLDOWN - now;
- const hours = Math.floor(remaining / (1000 * 60 * 60));
- const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
-
- throw createError(
- "Robbery cooldown active",
- ErrorTypes.RATE_LIMIT,
- `You need to lay low. Wait **${hours}h ${minutes}m** before attempting another robbery.`,
- { remaining, hours, minutes, cooldownType: 'rob' }
- );
- }
-
- if (victimData.wallet < 500) {
- throw createError(
- "Victim too poor",
- ErrorTypes.VALIDATION,
- `${victimUser.username} is too poor. They need at least $500 cash to be worth robbing.`,
- { victimWallet: victimData.wallet, required: 500 }
- );
- }
-
- const hasSafe = victimData.inventory["personal_safe"] || 0;
-
- if (hasSafe > 0) {
- robberData.lastRob = now;
- await setEconomyData(client, guildId, robberId, robberData);
-
- return await interaction.editReply({
- embeds: [
- MessageTemplates.ERRORS.CONFIGURATION_REQUIRED(
- "robbery protection",
- `${victimUser.username} was prepared! Your attempt failed because they own a **Personal Safe**. You got away clean but didn't gain anything.`
- )
- ],
- });
- }
-
- const isSuccessful = Math.random() < BASE_ROB_SUCCESS_CHANCE;
- let resultEmbed;
-
- if (isSuccessful) {
- const amountStolen = Math.floor(victimData.wallet * ROB_PERCENTAGE);
-
- robberData.wallet = (robberData.wallet || 0) + amountStolen;
- victimData.wallet = (victimData.wallet || 0) - amountStolen;
-
- resultEmbed = MessageTemplates.SUCCESS.DATA_UPDATED(
- "robbery",
- `You successfully stole **$${amountStolen.toLocaleString()}** from ${victimUser.username}!`
- );
- } else {
- const fineAmount = Math.floor((robberData.wallet || 0) * FINE_PERCENTAGE);
-
- if ((robberData.wallet || 0) < fineAmount) {
- robberData.wallet = 0;
- } else {
- robberData.wallet = (robberData.wallet || 0) - fineAmount;
- }
-
- resultEmbed = MessageTemplates.ERRORS.INSUFFICIENT_PERMISSIONS(
- "robbery failed",
- `You failed the robbery and were caught! You were fined **$${fineAmount.toLocaleString()}** of your own cash.`
- );
- }
-
- robberData.lastRob = now;
-
- await setEconomyData(client, guildId, robberId, robberData);
- await setEconomyData(client, guildId, victimUser.id, victimData);
-
- resultEmbed
- .addFields(
- {
- name: `Your New Cash (${interaction.user.username})`,
- value: `$${robberData.wallet.toLocaleString()}`,
- inline: true,
- },
- {
- name: `Victim's New Cash (${victimUser.username})`,
- value: `$${victimData.wallet.toLocaleString()}`,
- inline: true,
- },
- )
- .setFooter({ text: `Next robbery available in 4 hours.` });
-
- await interaction.editReply({ embeds: [resultEmbed] });
- }, { command: 'rob' });
- },
-};
-
-
diff --git a/src/commands/Economy/shop.js b/src/commands/Economy/shop.js
deleted file mode 100644
index ad60fb5bb7..0000000000
--- a/src/commands/Economy/shop.js
+++ /dev/null
@@ -1,208 +0,0 @@
-import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType, EmbedBuilder } from 'discord.js';
-import { shopItems } from '../../config/shop/items.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('shop')
- .setDescription('View the economy shop with pagination'),
-
- async execute(interaction, config, client) {
- try {
- const ITEMS_PER_PAGE = 2;
- const totalPages = Math.ceil(shopItems.length / ITEMS_PER_PAGE);
- let currentPage = 1;
-
- const createShopEmbed = (page) => {
- const startIndex = (page - 1) * ITEMS_PER_PAGE;
- const endIndex = startIndex + ITEMS_PER_PAGE;
- const pageItems = shopItems.slice(startIndex, endIndex);
-
- const embed = new EmbedBuilder()
- .setTitle("🛒 Store")
- .setColor(0x2b2d31)
- .setDescription(`Click a button below to instantly buy an item, or use the \`/item buy\` command.\nFor more details before purchasing, use the \`/item info\` command.`);
-
- pageItems.forEach(item => {
- embed.addFields({
- name: `${item.name}`,
- value: `ðŸ·ï¸ **Type:** ${item.type}\n${item.description}\n💚 **Price:** ${item.price}`,
- inline: false,
- });
- });
-
- embed.setFooter({ text: `Page ${page}/${totalPages}` });
- return embed;
- };
-
- const createShopComponents = (page) => {
- const startIndex = (page - 1) * ITEMS_PER_PAGE;
- const endIndex = startIndex + ITEMS_PER_PAGE;
- const pageItems = shopItems.slice(startIndex, endIndex);
-
- const components = [];
-
- pageItems.forEach((item, index) => {
- const isLastItem = index === pageItems.length - 1;
- const row = new ActionRowBuilder();
-
- row.addComponents(
- new ButtonBuilder()
- .setCustomId(`shop_buy:${item.id}`)
- .setLabel('Buy')
- .setStyle(ButtonStyle.Primary)
- );
-
- if (isLastItem && totalPages > 1) {
- row.addComponents(
- new ButtonBuilder()
- .setCustomId('shop_prev')
- .setLabel('â¬…ï¸ Previous')
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(page === 1),
- new ButtonBuilder()
- .setCustomId('shop_next')
- .setLabel('Next âž¡ï¸')
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(page === totalPages)
- );
- }
-
- components.push(row);
- });
-
- return components;
- };
-
- const message = await interaction.reply({
- embeds: [createShopEmbed(currentPage)],
- components: createShopComponents(currentPage),
- flags: 0
- });
-
- const collector = message.createMessageComponentCollector({
- componentType: ComponentType.Button,
- time: 300000
- });
-
- collector.on('collect', async (buttonInteraction) => {
- if (buttonInteraction.user.id !== interaction.user.id) {
- await buttonInteraction.reply({
- content: '⌠You cannot use these buttons. Run `/shop` to get your own shop view.',
- flags: 64
- });
- return;
- }
-
- const { customId } = buttonInteraction;
-
- if (customId === 'shop_prev' || customId === 'shop_next') {
- await buttonInteraction.deferUpdate();
- if (customId === 'shop_prev' && currentPage > 1) {
- currentPage--;
- } else if (customId === 'shop_next' && currentPage < totalPages) {
- currentPage++;
- }
- await buttonInteraction.editReply({
- embeds: [createShopEmbed(currentPage)],
- components: createShopComponents(currentPage)
- });
- return;
- }
-
- if (customId.startsWith('shop_buy:')) {
- await buttonInteraction.deferReply({ flags: 64 });
-
- const itemId = customId.split(':')[1];
- const item = shopItems.find(i => i.id === itemId);
-
- if (!item) {
- return await buttonInteraction.editReply({
- content: '⌠Item not found.'
- });
- }
-
- try {
- const { getEconomyData, setEconomyData } = await import('../../utils/economy.js');
- const { getGuildConfig } = await import('../../services/guildConfig.js');
- const { successEmbed, errorEmbed } = await import('../../utils/embeds.js');
-
- const userId = buttonInteraction.user.id;
- const guildId = buttonInteraction.guildId;
- const totalCost = item.price;
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (userData.wallet < totalCost) {
- return await buttonInteraction.editReply({
- embeds: [errorEmbed(
- 'Insufficient Funds',
- `You need **$${totalCost.toLocaleString()}** but only have **$${userData.wallet.toLocaleString()}**`
- )]
- });
- }
-
- if (item.type === 'role' && itemId === 'premium_role') {
- const guildConfig = await getGuildConfig(client, guildId);
- const roleId = guildConfig.premiumRoleId;
-
- if (!roleId) {
- return await buttonInteraction.editReply({
- embeds: [errorEmbed('Error', 'Premium role not configured')]
- });
- }
-
- if (buttonInteraction.member.roles.cache.has(roleId)) {
- return await buttonInteraction.editReply({
- embeds: [errorEmbed('Already Own', `You already have this role`)]
- });
- }
-
- await buttonInteraction.member.roles.add(roleId);
- }
-
- if (!userData.inventory) userData.inventory = {};
- userData.inventory[itemId] = (userData.inventory[itemId] || 0) + 1;
- userData.wallet -= totalCost;
-
- await setEconomyData(client, guildId, userId, userData);
-
- await buttonInteraction.editReply({
- embeds: [successEmbed(
- `✅ Purchased **${item.name}** for **$${totalCost.toLocaleString()}**\n\n**New Balance:** $${userData.wallet.toLocaleString()}`,
- '🛒 Purchase Complete'
- )]
- });
-
- console.log(`✅ User ${buttonInteraction.user.tag} purchased ${item.id} for $${totalCost}`);
- } catch (error) {
- console.error('Purchase error:', error);
- await buttonInteraction.editReply({
- embeds: [errorEmbed('Error', 'Failed to process purchase')]
- });
- }
- }
- });
-
- collector.on('end', async () => {
- try {
- const disabledComponents = createShopComponents(currentPage);
- disabledComponents.forEach(row => {
- row.components.forEach(button => button.setDisabled(true));
- });
-
- await message.edit({
- components: disabledComponents
- });
- } catch (error) {
- // Message may have been deleted
- }
- });
- } catch (error) {
- console.error('Shop command error:', error);
- await interaction.reply({
- content: '⌠An error occurred while loading the shop.',
- flags: 64
- });
- }
- },
-};
diff --git a/src/commands/Economy/slut.js b/src/commands/Economy/slut.js
deleted file mode 100644
index 3cfcf160d0..0000000000
--- a/src/commands/Economy/slut.js
+++ /dev/null
@@ -1,150 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData } from '../../utils/economy.js';
-
-const SLUT_COOLDOWN = 45 * 60 * 1000;
-const MIN_SLUT_AMOUNT = 100;
-const MAX_SLUT_AMOUNT = 800;
-const FAILURE_RATE = 0.25;
-
-const SLUT_ACTIVITIES = [
- { name: "Street Walking", min: 100, max: 400, risk: 0.2 },
- { name: "Escort Service", min: 200, max: 600, risk: 0.25 },
- { name: "Private Party", min: 300, max: 800, risk: 0.3 },
- { name: "VIP Client", min: 500, max: 1200, risk: 0.35 },
- { name: "Exclusive Service", min: 800, max: 2000, risk: 0.4 },
-];
-
-const SLUT_RESPONSES = [
- "You had an amazing night and earned 💰",
- "Your client was very generous! You made 💰",
- "Business is booming! You earned 💰",
- "That was profitable! You received 💰",
- "Your skills paid off! You got 💰",
-];
-
-const FAILURE_RESPONSES = [
- "Unfortunately, things didn't go well tonight. You earned nothing.",
- "Your client wasn't satisfied. No payment for you.",
- "It was a slow night. Better luck next time!",
- "Things went wrong and you had to leave empty-handed.",
- "Unfortunately, you got caught and had to run away!",
-];
-
-export default {
- data: new SlashCommandBuilder()
- .setName('slut')
- .setDescription('Do risky work to earn money')
- .addStringOption(option =>
- option
- .setName('activity')
- .setDescription('Type of activity to perform')
- .setRequired(false)
- .addChoices(
- { name: 'Street Walking', value: 'street_walking' },
- { name: 'Escort Service', value: 'escort_service' },
- { name: 'Private Party', value: 'private_party' },
- { name: 'VIP Client', value: 'vip_client' },
- { name: 'Exclusive Service', value: 'exclusive_service' },
- )
- ),
-
- async execute(interaction, config, client) {
-const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const now = Date.now();
-
- try {
- const userData = await getEconomyData(client, guildId, userId);
- const lastSlut = userData.lastSlut || 0;
-
- if (now - lastSlut < SLUT_COOLDOWN) {
- const remainingTime = Math.ceil((SLUT_COOLDOWN - (now - lastSlut)) / 60000);
- return interaction.reply({
- embeds: [
- warningEmbed(
- "â° Cooldown Active",
- `You need to wait ${remainingTime} minutes before you can work again!`
- )
- ]
- });
- }
-
- const activityType = interaction.options.getString("activity");
- const activity = activityType
- ? SLUT_ACTIVITIES.find(a => a.name.toLowerCase().replace(/\s+/g, '_') === activityType)
- : SLUT_ACTIVITIES[Math.floor(Math.random() * SLUT_ACTIVITIES.length)];
-
- if (!activity) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Invalid Activity",
- "Please select a valid activity from the choices."
- )
- ]
- });
- }
-
- const success = Math.random() > activity.risk;
- let earnings = 0;
-
- if (success) {
- earnings = Math.floor(Math.random() * (activity.max - activity.min + 1)) + activity.min;
-
- userData.wallet += earnings;
- userData.lastSlut = now;
- userData.totalSluts = (userData.totalSluts || 0) + 1;
- userData.totalSlutEarnings = (userData.totalSlutEarnings || 0) + earnings;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const response = SLUT_RESPONSES[Math.floor(Math.random() * SLUT_RESPONSES.length)];
- return interaction.editReply({
- embeds: [
- successEmbed(
- `💰 ${activity.name} - Success!`,
- `${response} **${earnings.toLocaleString()}** coins!\n\n` +
- `💳 **New Balance:** ${userData.wallet.toLocaleString()} coins\n` +
- `📊 **Total Sessions:** ${userData.totalSluts}\n` +
- `💵 **Total Earnings:** ${userData.totalSlutEarnings.toLocaleString()} coins`
- )
- ]
- });
- } else {
- userData.lastSlut = now;
- userData.totalSluts = (userData.totalSluts || 0) + 1;
- userData.failedSluts = (userData.failedSluts || 0) + 1;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const response = FAILURE_RESPONSES[Math.floor(Math.random() * FAILURE_RESPONSES.length)];
- return interaction.editReply({
- embeds: [
- errorEmbed(
- `⌠${activity.name} - Failed`,
- `${response}\n\n` +
- `💳 **Current Balance:** ${userData.wallet.toLocaleString()} coins\n` +
- `📊 **Total Sessions:** ${userData.totalSluts}\n` +
- `⌠**Failed Sessions:** ${userData.failedSluts}`
- )
- ]
- });
- }
-
- } catch (error) {
- console.error(`Error in slut command for user ${userId}:`, error);
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "System Error",
- "An error occurred while processing your request. Please try again later."
- )
- ]
- });
- }
- },
-};
-
-
diff --git a/src/commands/Economy/withdraw.js b/src/commands/Economy/withdraw.js
deleted file mode 100644
index 1e9c70b149..0000000000
--- a/src/commands/Economy/withdraw.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData, getMaxBankCapacity } from '../../utils/economy.js';
-import { withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { MessageTemplates } from '../../utils/messageTemplates.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('withdraw')
- .setDescription('Withdraw money from your bank to your wallet')
- .addIntegerOption(option =>
- option
- .setName('amount')
- .setDescription('Amount to withdraw')
- .setRequired(true)
- .setMinValue(1)
- ),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- await interaction.deferReply();
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const amountInput = interaction.options.getInteger("amount");
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (!userData) {
- throw createError(
- "Failed to load economy data",
- ErrorTypes.DATABASE,
- "Failed to load your economy data. Please try again later.",
- { userId, guildId }
- );
- }
-
- let withdrawAmount = amountInput;
-
- if (withdrawAmount <= 0) {
- throw createError(
- "Invalid withdrawal amount",
- ErrorTypes.VALIDATION,
- "You must withdraw a positive amount.",
- { amount: withdrawAmount, userId }
- );
- }
-
- if (withdrawAmount > userData.bank) {
- withdrawAmount = userData.bank;
- }
-
- if (withdrawAmount === 0) {
- throw createError(
- "Empty bank account",
- ErrorTypes.VALIDATION,
- "Your bank account is empty.",
- { userId, bankBalance: userData.bank }
- );
- }
-
- userData.wallet += withdrawAmount;
- userData.bank -= withdrawAmount;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const embed = MessageTemplates.SUCCESS.DATA_UPDATED(
- "withdrawal",
- `You successfully withdrew **$${withdrawAmount.toLocaleString()}** from your bank.`
- )
- .addFields(
- {
- name: "💵 New Cash Balance",
- value: `$${userData.wallet.toLocaleString()}`,
- inline: true,
- },
- {
- name: "🦠New Bank Balance",
- value: `$${userData.bank.toLocaleString()}`,
- inline: true,
- },
- );
-
- await interaction.editReply({ embeds: [embed] });
- }, { command: 'withdraw' });
- },
-};
-
diff --git a/src/commands/Economy/work.js b/src/commands/Economy/work.js
deleted file mode 100644
index 933294fa71..0000000000
--- a/src/commands/Economy/work.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getEconomyData, setEconomyData } from '../../utils/economy.js';
-
-const WORK_COOLDOWN = 30 * 60 * 1000;
-const MIN_WORK_AMOUNT = 50;
-const MAX_WORK_AMOUNT = 300;
-const WORK_JOBS = [
- "Software Developer",
- "Barista",
- "Janitor",
- "YouTuber",
- "Discord Bot Developer",
- "Cashier",
- "Pizza Delivery Driver",
- "Librarian",
- "Gardener",
- "Data Analyst",
-];
-
-export default {
- data: new SlashCommandBuilder()
- .setName('work')
- .setDescription('Work to earn some money'),
-
- async execute(interaction, config, client) {
- try {
- await interaction.deferReply();
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const now = Date.now();
-
- const userData = await getEconomyData(client, guildId, userId);
- const lastWork = userData.lastWork || 0;
- const inventory = userData.inventory || {};
- const extraWorkShifts = inventory["extra_work"] || 0;
-
- let cooldownActive = now < lastWork + WORK_COOLDOWN;
- let usedConsumable = false;
-
- if (cooldownActive) {
- if (extraWorkShifts > 0) {
- inventory["extra_work"] = (inventory["extra_work"] || 0) - 1;
- usedConsumable = true;
- } else {
- const remaining = lastWork + WORK_COOLDOWN - now;
- const hours = Math.floor(remaining / (1000 * 60 * 60));
- const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
-
- return await interaction.editReply({
- embeds: [errorEmbed(
- "â±ï¸ Slow Down!",
- `You're working too fast! Wait **${hours}h ${minutes}m** before working again.`
- )]
- });
- }
- }
-
- const earned = Math.floor(Math.random() * (MAX_WORK_AMOUNT - MIN_WORK_AMOUNT + 1)) + MIN_WORK_AMOUNT;
- const job = WORK_JOBS[Math.floor(Math.random() * WORK_JOBS.length)];
-
- userData.wallet = (userData.wallet || 0) + earned;
- userData.lastWork = now;
-
- await setEconomyData(client, guildId, userId, userData);
-
- const embed = successEmbed(
- "💼 Work Complete!",
- `You worked as a **${job}** and earned **$${earned.toLocaleString()}**!`
- )
- .addFields(
- {
- name: "💰 New Balance",
- value: `$${userData.wallet.toLocaleString()}`,
- inline: true,
- },
- {
- name: "â° Next Work",
- value: ``,
- inline: true,
- }
- )
- .setFooter({
- text: `Requested by ${interaction.user.tag}`,
- iconURL: interaction.user.displayAvatarURL(),
- });
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error('Work command error:', error);
- try {
- await interaction.editReply({
- embeds: [errorEmbed(
- "⌠Error",
- "Something went wrong while processing your work request. Please try again."
- )]
- });
- } catch (replyError) {
- console.error('Failed to send error response:', replyError);
- }
- }
- },
-};
-
diff --git a/src/commands/Fun/fact.js b/src/commands/Fun/fact.js
deleted file mode 100644
index 0148990c32..0000000000
--- a/src/commands/Fun/fact.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-const facts = [
- "A day on Venus is longer than a year on Venus.",
- "The shortest war in history was between Britain and Zanzibar on August 27, 1896. It lasted 38 to 45 minutes.",
- "The word 'Strengths' is the longest word in the English language with only one vowel.",
- "Octopuses have three hearts and blue blood.",
- "There are more trees on Earth than stars in the Milky Way galaxy.",
- "The total weight of all the ants on Earth is thought to be about the same as the total weight of all humans.",
-];
-
-export default {
- data: new SlashCommandBuilder()
- .setName("fact")
- .setDescription("Shares a random, interesting fact."),
-
- async execute(interaction) {
-try {
- const randomFact = facts[Math.floor(Math.random() * facts.length)];
-
- const embed = successEmbed("🧠Did You Know?", `💡 **${randomFact}**`);
-
- await interaction.reply({ embeds: [embed] });
- } catch (error) {
- console.error("Fact command error:", error);
- await interaction.editReply({ embeds: [errorEmbed("System Error", "Could not fetch a fact right now.")] });
- }
- },
-};
-
diff --git a/src/commands/Fun/fight.js b/src/commands/Fun/fight.js
deleted file mode 100644
index e06fb0e171..0000000000
--- a/src/commands/Fun/fight.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
-export default {
- data: new SlashCommandBuilder()
- .setName("fight")
- .setDescription("Starts a simulated 1v1 text-based battle.")
- .addUserOption((option) =>
- option
- .setName("opponent")
- .setDescription("The user to fight.")
- .setRequired(true),
- ),
-
- async execute(interaction) {
-try {
- const challenger = interaction.user;
- const opponent = interaction.options.getUser("opponent");
-
- if (challenger.id === opponent.id) {
- return interaction.reply({
- content: `âš”ï¸ **${challenger.username}** can't fight themselves! That's a draw before it even starts.`,
- });
- }
-
- const winner = rand(0, 1) === 0 ? challenger : opponent;
- const loser = winner.id === challenger.id ? opponent : challenger;
-const rounds = rand(3, 7);
-const damage = rand(10, 50);
-
- const log = [];
- log.push(
- `💥 **${challenger.username}** challenges **${opponent.username}** to a duel! (Best of ${rounds} rounds)`,
- );
-
- for (let i = 1; i <= rounds; i++) {
- const attacker = rand(0, 1) === 0 ? challenger : opponent;
- const target = attacker.id === challenger.id ? opponent : challenger;
- const action = [
- "throws a wild punch",
- "lands a critical hit",
- "uses a weak spell",
- "parries and counterattacks",
- ][rand(0, 3)];
- log.push(
- `\n**Round ${i}:** ${attacker.username} ${action} on ${target.username} for ${rand(1, damage)} damage!`,
- );
- }
-
- const outcomeText = log.join("\n");
-
- const embed = successEmbed(
- `${outcomeText}\n\n👑 **${winner.username}** has defeated ${loser.username} and claims the victory!`,
- `🆠Duel Complete!`
- );
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Fight command error:", error);
- await interaction.editReply({ embeds: [errorEmbed("System Error", "Could not run the fight command.")] });
- }
- },
-};
-
diff --git a/src/commands/Fun/filp.js b/src/commands/Fun/filp.js
deleted file mode 100644
index 99d6c4d3c2..0000000000
--- a/src/commands/Fun/filp.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("flip")
- .setDescription("Flips a coin (Heads or Tails)."),
-
- async execute(interaction) {
-try {
- const result = Math.random() < 0.5 ? "Heads" : "Tails";
- const emoji = result === "Heads" ? "🪙" : "🔮";
-
- const embed = successEmbed(
- "Heads or Tails?",
- `The coin landed on... **${result}** ${emoji}!`,
- );
-
- await interaction.reply({ embeds: [embed] });
- } catch (error) {
- console.error("Flip command error:", error);
- await interaction.editReply({ embeds: [errorEmbed("System Error", "Could not flip a coin right now.")] });
- }
- },
-};
-
diff --git a/src/commands/Fun/mock.js b/src/commands/Fun/mock.js
deleted file mode 100644
index d3a7bfbe29..0000000000
--- a/src/commands/Fun/mock.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("mock")
- .setDescription("cOnVeRtS yOuR tExT tO sPoNgEbOb CaSe.")
- .addStringOption((option) =>
- option
- .setName("text")
- .setDescription("The text to mock.")
- .setRequired(true),
- ),
-
- async execute(interaction) {
-try {
- const originalText = interaction.options.getString("text");
- let mockedText = "";
-
- for (let i = 0; i < originalText.length; i++) {
- const char = originalText[i];
- if (i % 2 === 0) {
- mockedText += char.toLowerCase();
- } else {
- mockedText += char.toUpperCase();
- }
- }
-
- const embed = successEmbed("sPoNgEbOb cAsE", `"${mockedText}"`);
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Mock command error:", error);
- await interaction.editReply({ embeds: [errorEmbed("System Error", "Could not mock text right now.")] });
- }
- },
-};
-
diff --git a/src/commands/Fun/reverse.js b/src/commands/Fun/reverse.js
deleted file mode 100644
index b8e5f05f7b..0000000000
--- a/src/commands/Fun/reverse.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("reverse")
- .setDescription("Writes your text backwards.")
- .addStringOption((option) =>
- option
- .setName("text")
- .setDescription("The text to reverse.")
- .setRequired(true),
- ),
-
- async execute(interaction) {
-try {
- const originalText = interaction.options.getString("text");
-
- const reversedText = originalText.split("").reverse().join("");
-
- const embed = successEmbed(
- "Backwards Text",
- `Original: **${originalText}**\nReversed: **${reversedText}**`,
- );
-
- await interaction.reply({ embeds: [embed] });
- } catch (error) {
- console.error("Reverse command error:", error);
- await interaction.editReply({ embeds: [errorEmbed("System Error", "Could not reverse text right now.")] });
- }
- },
-};
-
diff --git a/src/commands/Fun/roll.js b/src/commands/Fun/roll.js
deleted file mode 100644
index 86e681c4b2..0000000000
--- a/src/commands/Fun/roll.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("roll")
- .setDescription("Rolls dice using standard notation (e.g., 2d20, 1d6 + 5).")
- .addStringOption((option) =>
- option
- .setName("notation")
- .setDescription("The dice notation (e.g., 2d6, 1d20 + 4)")
- .setRequired(true),
- ),
-
- async execute(interaction) {
-try {
- const notation = interaction.options
- .getString("notation")
- .toLowerCase()
-.replace(/\s/g, "");
-
- const match = notation.match(/^(\d*)d(\d+)([\+\-]\d+)?$/);
-
- if (!match) {
- return interaction.reply({
- embeds: [
- errorEmbed("Invalid notation. Use format like `1d20` or `3d6+5`."),
- ],
- });
- }
-
- const numDice = parseInt(match[1] || "1", 10);
- const numSides = parseInt(match[2], 10);
- const modifier = parseInt(match[3] || "0", 10);
-
- if (numDice > 20 || numSides > 1000 || numSides < 1) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Please keep the number of dice under 20 and sides under 1000.",
- ),
- ],
- });
- }
-
- let rolls = [];
- let totalRoll = 0;
-
- for (let i = 0; i < numDice; i++) {
- const roll = Math.floor(Math.random() * numSides) + 1;
- rolls.push(roll);
- totalRoll += roll;
- }
-
- const finalTotal = totalRoll + modifier;
-
- const resultsDetail =
- numDice > 1 ? `**Rolls:** ${rolls.join(" + ")}\n` : "";
- const modifierText = modifier !== 0 ? ` + (${modifier})` : "";
-
- const embed = successEmbed(
- `🎲 Rolling ${numDice}d${numSides}${modifier !== 0 ? match[3] : ""}`,
- `${resultsDetail}**Total Roll:** ${totalRoll}${modifierText} = **${finalTotal}**`,
- );
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Roll command error:", error);
- await interaction.editReply({ embeds: [errorEmbed("System Error", "Could not roll dice right now.")] });
- }
- },
-};
-
diff --git a/src/commands/Fun/ship.js b/src/commands/Fun/ship.js
deleted file mode 100644
index a9cd9ee9c8..0000000000
--- a/src/commands/Fun/ship.js
+++ /dev/null
@@ -1,74 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-function stringToHash(str) {
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- const char = str.charCodeAt(i);
- hash = (hash << 5) - hash + char;
-hash |= 0;
- }
- return Math.abs(hash);
-}
-
-export default {
- data: new SlashCommandBuilder()
- .setName("ship")
- .setDescription("Calculate the compatibility score between two people.")
- .addStringOption((option) =>
- option
- .setName("name1")
- .setDescription("The first name or user.")
- .setRequired(true),
- )
- .addStringOption((option) =>
- option
- .setName("name2")
- .setDescription("The second name or user.")
- .setRequired(true),
- ),
-
- async execute(interaction) {
-try {
- const name1 = interaction.options.getString("name1").trim();
- const name2 = interaction.options.getString("name2").trim();
-
- const sortedNames = [name1, name2].sort();
-
- const combination = sortedNames.join("-").toLowerCase();
-
-const score = stringToHash(combination) % 101;
-
- let description;
- if (score === 100) {
- description = "Soulmates! It's destiny, they belong together!";
- } else if (score >= 80) {
- description = "A perfect match! Get the wedding bells ready!";
- } else if (score >= 60) {
- description = "Solid chemistry. Definitely worth exploring!";
- } else if (score >= 40) {
- description = "Just friends status. Maybe with time?";
- } else if (score >= 20) {
- description = "It's a struggle. They might need space.";
- } else {
- description = "Zero compatibility. Run for the hills!";
- }
-
- const progressBar =
- "â–ˆ".repeat(Math.floor(score / 10)) +
- "â–‘".repeat(10 - Math.floor(score / 10));
-
- const embed = successEmbed(
- `💖 Ship Score: ${name1} vs ${name2}`,
- `Compatibility: **${score}%**\n\n\`${progressBar}\`\n\n*${description}*`,
- );
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Ship command error:", error);
- await interaction.editReply({ embeds: [errorEmbed("System Error", "Could not calculate compatibility right now.")] });
- }
- },
-};
-
diff --git a/src/commands/Fun/wanted.js b/src/commands/Fun/wanted.js
deleted file mode 100644
index 8a273d4e12..0000000000
--- a/src/commands/Fun/wanted.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("wanted")
- .setDescription("Create a WANTED poster for a user.")
- .addUserOption((option) =>
- option
- .setName("user")
- .setDescription("The user who is wanted.")
- .setRequired(true),
- )
- .addStringOption((option) =>
- option
- .setName("crime")
- .setDescription("The crime they committed.")
- .setRequired(false)
- .setMaxLength(100),
- ),
- async execute(interaction) {
-try {
- const targetUser = interaction.options.getUser("user");
- const crime =
- interaction.options.getString("crime") || "Too adorable for this server.";
-
- const bountyAmount = Math.floor(
- Math.random() * (100000000 - 1000000) + 1000000,
- );
- const bounty = `$${bountyAmount.toLocaleString()} USD`;
-
- const embed = {
-color: 0x964b00,
- title: `💥 ððˆð† ððŽð”ðð“ð˜: WANTED! 💥`,
- description: `**CRIMINAL:** ${targetUser.tag}\n**CRIME:** ${crime}`,
- fields: [
- {
- name: "DEAD OR ALIVE",
- value: `**BOUNTY:** ${bounty}`,
- inline: false,
- },
- ],
- image: {
- url: targetUser.displayAvatarURL({ size: 1024, format: "png" }),
- },
- footer: {
- text: `Last seen in ${interaction.guild.name}`,
- },
- };
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Wanted command error:", error);
- await interaction.editReply({ embeds: [createEmbed({ title: "System Error", description: "Could not create wanted poster." })] });
- }
- },
-};
-
diff --git a/src/commands/Giveaway/gcreate.js b/src/commands/Giveaway/gcreate.js
deleted file mode 100644
index 09c3f686b2..0000000000
--- a/src/commands/Giveaway/gcreate.js
+++ /dev/null
@@ -1,152 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { giveawayEmbed, giveawayButtons, saveGiveaway } from '../../utils/giveaways.js';
-const parseDuration = (durationString) => {
- const regex = /(\d+)([hmds])/i;
- const match = durationString.match(regex);
-
- if (!match) return null;
-
- const amount = parseInt(match[1], 10);
- const unit = match[2].toLowerCase();
- let ms = 0;
-
- switch (unit) {
- case "s":
- ms = amount * 1000;
- break;
- case "m":
- ms = amount * 60 * 1000;
- break;
- case "h":
- ms = amount * 60 * 60 * 1000;
- break;
- case "d":
- ms = amount * 24 * 60 * 60 * 1000;
- break;
- default:
- return null;
- }
-
- const maxDuration = 30 * 24 * 60 * 60 * 1000;
- if (ms > maxDuration) {
- return maxDuration;
- }
- const minDuration = 10 * 1000;
- if (ms < minDuration) {
- return minDuration;
- }
-
- return ms;
-};
-export default {
- data: new SlashCommandBuilder()
- .setName("gcreate")
- .setDescription("Starts a new giveaway in a specified channel.")
- .addStringOption((option) =>
- option
- .setName("duration")
- .setDescription(
- "How long the giveaway should last (e.g., 1h, 30m, 5d).",
- )
- .setRequired(true),
- )
- .addIntegerOption((option) =>
- option
- .setName("winners")
- .setDescription("The number of winners to pick.")
- .setMinValue(1)
- .setMaxValue(10)
- .setRequired(true),
- )
- .addStringOption((option) =>
- option
- .setName("prize")
- .setDescription("The prize being given away.")
- .setRequired(true),
- )
- .addChannelOption((option) =>
- option
- .setName("channel")
- .setDescription("The channel to send the giveaway to (defaults to current channel).")
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(false),
- )
-.setDefaultMemberPermissions(0x0000000000000008n),
-
- async execute(interaction) {
- try {
- if (!interaction.inGuild()) {
- throw new Error("This command can only be used in a server.");
- }
-
- if (
- !interaction.member.permissions.has(
- PermissionsBitField.Flags.ManageGuild,
- )
- ) {
- throw new Error("You need the 'Manage Server' permission to start a giveaway.");
- }
-
- const durationString = interaction.options.getString("duration");
- const winnerCount = interaction.options.getInteger("winners");
- const prize = interaction.options.getString("prize");
- const targetChannel = interaction.options.getChannel("channel") || interaction.channel;
-
- const durationMs = parseDuration(durationString);
-
- if (durationMs === null) {
- throw new Error("Invalid duration format. Use: 1h, 30m, 5d, 10s (min: 10s, max: 30d)");
- }
-
- const endTime = Date.now() + durationMs;
-
- const initialGiveawayData = {
- messageId: "placeholder",
- channelId: targetChannel.id,
- guildId: interaction.guildId,
- prize: prize,
- hostId: interaction.user.id,
- endTime: endTime,
- endsAt: endTime,
- winnerCount: winnerCount,
- participants: [],
- isEnded: false,
- };
-
- const embed = giveawayEmbed(initialGiveawayData, "active");
- const row = giveawayButtons(false);
- const giveawayMessage = await targetChannel.send({
- content: "🎉 **NEW GIVEAWAY** 🎉",
- embeds: [embed],
- components: [row],
- });
-
- initialGiveawayData.messageId = giveawayMessage.id;
- await saveGiveaway(
- interaction.client,
- interaction.guildId,
- initialGiveawayData,
- );
-
- await interaction.reply({
- embeds: [
- successEmbed(
- "Giveaway Started!",
- `A new giveaway for **${prize}** has been started in ${targetChannel} and will end in ${durationString}.`,
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- } catch (error) {
- console.error('Gcreate command error:', error);
- const replyMethod = interaction.replied || interaction.deferred ? 'editReply' : 'reply';
- return interaction[replyMethod]({
- embeds: [errorEmbed('System Error', 'Could not start giveaway at this time.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
diff --git a/src/commands/Giveaway/gdelete.js b/src/commands/Giveaway/gdelete.js
deleted file mode 100644
index eb36d5f0b3..0000000000
--- a/src/commands/Giveaway/gdelete.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getGuildGiveaways, deleteGiveaway } from '../../utils/giveaways.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("gdelete")
- .setDescription(
- "Deletes a giveaway message and removes it from the database (no winner picked).",
- )
- .addStringOption((option) =>
- option
- .setName("messageid")
- .setDescription("The message ID of the giveaway to delete.")
- .setRequired(true),
- )
-.setDefaultMemberPermissions(0x0000000000000008n),
-
- async execute(interaction) {
- try {
- if (!interaction.inGuild()) {
- throw new Error("This command can only be used in a server.");
- }
-
- if (!interaction.member.permissions.has(PermissionsBitField.Flags.ManageGuild)) {
- throw new Error("You need the 'Manage Server' permission to delete a giveaway.");
- }
-
- const messageId = interaction.options.getString("messageid");
- const giveaways = await getGuildGiveaways(interaction.client, interaction.guildId);
- const giveaway = giveaways.find(g => g.messageId === messageId);
-
- if (!giveaway) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Giveaway Not Found",
- "No giveaway was found with that message ID.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
-
- let deletedMessage = false;
- let channelName = "Unknown Channel";
-
- const channel = await interaction.client.channels
- .fetch(giveaway.channelId)
- .catch(() => null);
- if (channel && channel.isTextBased()) {
- channelName = channel.name;
- const message = await channel.messages
- .fetch(messageId)
- .catch(() => null);
- if (message) {
- await message.delete();
- deletedMessage = true;
- }
- }
-
- await deleteGiveaway(
- interaction.client,
- interaction.guildId,
- messageId,
- );
-
- const statusMsg = deletedMessage
- ? `and the message was deleted from #${channelName}`
- : `but the message was already deleted or the channel was inaccessible.`;
-
- return interaction.reply({
- embeds: [
- successEmbed(
- "Giveaway Deleted",
- `Successfully deleted the giveaway for **${giveaway.prize}** ${statusMsg}. No winner was picked.`,
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- } catch (error) {
- console.error("Error deleting giveaway:", error);
- const replyMethod = interaction.replied || interaction.deferred ? 'editReply' : 'reply';
- return interaction[replyMethod]({
- embeds: [
- errorEmbed(
- "Deletion Failed",
- "An error occurred while trying to delete the giveaway. Check bot permissions.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
diff --git a/src/commands/Giveaway/gend.js b/src/commands/Giveaway/gend.js
deleted file mode 100644
index 134a0a78b2..0000000000
--- a/src/commands/Giveaway/gend.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { giveawayEmbed, giveawayButtons, getGuildGiveaways, saveGiveaway, pickWinners, deleteGiveaway } from '../../utils/giveaways.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("gend")
- .setDescription(
- "Ends an active giveaway immediately and picks the winner(s).",
- )
- .addStringOption((option) =>
- option
- .setName("messageid")
- .setDescription("The message ID of the giveaway to end.")
- .setRequired(true),
- )
-.setDefaultMemberPermissions(0x0000000000000008n),
-
- async execute(interaction) {
- try {
- if (!interaction.inGuild()) {
- throw new Error("This command can only be used in a server.");
- }
-
- if (!interaction.member.permissions.has(PermissionsBitField.Flags.ManageGuild)) {
- throw new Error("You need the 'Manage Server' permission to end a giveaway.");
- }
-
- const messageId = interaction.options.getString("messageid");
- const giveaways = await getGuildGiveaways(interaction.client, interaction.guildId);
- const giveaway = giveaways.find(g => g.messageId === messageId);
-
- if (!giveaway) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Giveaway Not Found",
- "No giveaway was found with that message ID in the database.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
-
- if (giveaway.isEnded) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Giveaway Already Ended",
- "This giveaway has already finished.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
-
- const winners = pickWinners(giveaway.participants || [], giveaway.winnerCount);
-const winnerIds = winners.map((w) => w);
-
- const channel = await interaction.client.channels.fetch(
- giveaway.channelId,
- );
- if (!channel || !channel.isTextBased()) {
- await deleteGiveaway(
- interaction.client,
- interaction.guildId,
- messageId,
- );
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Channel Error",
- "Could not find the channel where the giveaway was hosted. The giveaway has been removed from the database.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
-
- const message = await channel.messages
- .fetch(messageId)
- .catch(() => null);
- if (!message) {
- await deleteGiveaway(
- interaction.client,
- interaction.guildId,
- messageId,
- );
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Message Error",
- "Could not find the giveaway message. The giveaway has been removed from the database.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
-
- giveaway.isEnded = true;
-giveaway.winnerIds = winnerIds;
- await saveGiveaway(
- interaction.client,
- interaction.guildId,
- giveaway,
- );
-
- const newEmbed = giveawayEmbed(giveaway, "ended", winnerIds);
- const newRow = giveawayButtons(true);
-
- await message.edit({
- content: "🎉 **GIVEAWAY ENDED** 🎉",
- embeds: [newEmbed],
- components: [newRow],
- });
-
- if (winnerIds.length > 0) {
- const winnerMentions = winnerIds
- .map((id) => `<@${id}>`)
- .join(", ");
- await channel.send({
- content: `CONGRATULATIONS ${winnerMentions}! You won the **${giveaway.prize}** giveaway! Please contact the host <@${giveaway.hostId}> to claim your prize.`,
- });
- } else {
- await channel.send({
- content: `The giveaway for **${giveaway.prize}** has ended with no valid entries.`,
- });
- }
-
- return interaction.reply({
- embeds: [
- successEmbed(
- "Giveaway Ended",
- `Successfully ended the giveaway for **${giveaway.prize}** in ${channel}.`,
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- } catch (error) {
- console.error("Error ending giveaway:", error);
- const replyMethod = interaction.replied || interaction.deferred ? 'editReply' : 'reply';
- return interaction[replyMethod]({
- embeds: [
- errorEmbed(
- "Giveaway Failed",
- "An error occurred while trying to end the giveaway and update the message.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
diff --git a/src/commands/Giveaway/greroll.js b/src/commands/Giveaway/greroll.js
deleted file mode 100644
index c70422ef06..0000000000
--- a/src/commands/Giveaway/greroll.js
+++ /dev/null
@@ -1,185 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { giveawayEmbed, giveawayButtons, getGuildGiveaways, saveGiveaway, pickWinners, deleteGiveaway } from '../../utils/giveaways.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("greroll")
- .setDescription("Rerolls the winner(s) for an ended giveaway.")
- .addStringOption((option) =>
- option
- .setName("messageid")
- .setDescription("The message ID of the ended giveaway.")
- .setRequired(true),
- )
-.setDefaultMemberPermissions(0x0000000000000008n),
-
- async execute(interaction) {
- if (!interaction.inGuild()) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Command Failed",
- "This command can only be used in a server.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- if (
- !interaction.member.permissions.has(
- PermissionsBitField.Flags.ManageGuild,
- )
- ) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need the 'Manage Server' permission to reroll a giveaway.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- const messageId = interaction.options.getString("messageid");
- const giveaways = await getGuildGiveaways(
- interaction.client,
- interaction.guildId,
- );
- let giveaway = giveaways[messageId];
-
- if (!giveaway) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Giveaway Not Found",
- "No giveaway was found with that message ID in the database.",
- ),
- ],
- });
- }
-
- if (!giveaway.isEnded) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Giveaway Not Ended",
- "This giveaway is still active. Please use `/gend` to end it first.",
- ),
- ],
- });
- }
-
- const participants = giveaway.participants || [];
-
- if (participants.length < giveaway.winnerCount) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Reroll Failed",
- "Not enough entries to pick the required number of winners.",
- ),
- ],
- });
- }
-
- try {
- const newWinners = pickWinners(
- participants,
- giveaway.winnerCount,
- );
- const newWinnerIds = newWinners.map((w) => w);
-
- const channel = await interaction.client.channels.fetch(
- giveaway.channelId,
- );
- if (!channel || !channel.isTextBased()) {
- giveaway.winnerIds = newWinnerIds;
- await saveGiveaway(
- interaction.client,
- interaction.guildId,
- giveaway,
- );
- return interaction.editReply({
- embeds: [
- successEmbed(
- "Reroll Complete",
- "The new winners have been selected and saved to the database. Could not find channel to announce.",
- ),
- ],
- });
- }
-
- const message = await channel.messages
- .fetch(messageId)
- .catch(() => null);
- if (!message) {
- giveaway.winnerIds = newWinnerIds;
- await saveGiveaway(
- interaction.client,
- interaction.guildId,
- giveaway,
- );
- const winnerMentions = newWinnerIds
- .map((id) => `<@${id}>`)
- .join(", ");
- await channel.send({
- content: `🎉 **GIVEAWAY REROLL** 🎉 New winners for **${giveaway.prize}**: ${winnerMentions}!`,
- });
- return interaction.editReply({
- embeds: [
- successEmbed(
- "Reroll Complete",
- `The new winners have been announced in ${channel}. (Original message not found).`,
- ),
- ],
- });
- }
-
-giveaway.winnerIds = newWinnerIds;
- await saveGiveaway(
- interaction.client,
- interaction.guildId,
- giveaway,
- );
-
- const newEmbed = giveawayEmbed(giveaway, "reroll", newWinnerIds);
-const newRow = giveawayButtons(true);
-
- await message.edit({
- content: "🔄 **GIVEAWAY REROLLED** 🔄",
- embeds: [newEmbed],
- components: [newRow],
- });
-
- const winnerMentions = newWinnerIds
- .map((id) => `<@${id}>`)
- .join(", ");
- await channel.send({
- content: `🔄 **REROLL WINNERS** 🔄 CONGRATULATIONS ${winnerMentions}! You are the new winner(s) for the **${giveaway.prize}** giveaway! Please contact the host <@${giveaway.hostId}> to claim your prize.`,
- });
-
- return interaction.editReply({
- embeds: [
- successEmbed(
- "Reroll Successful",
- `Successfully rerolled the giveaway for **${giveaway.prize}** in ${channel}.`,
- ),
- ],
- });
- } catch (error) {
- console.error("Error rerolling giveaway:", error);
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Reroll Failed",
- "An unexpected error occurred while trying to reroll the giveaway.",
- ),
- ],
- });
- }
- },
-};
-
diff --git a/src/commands/JoinToCreate/jointocreate.js b/src/commands/JoinToCreate/jointocreate.js
deleted file mode 100644
index ed55c0f51c..0000000000
--- a/src/commands/JoinToCreate/jointocreate.js
+++ /dev/null
@@ -1,491 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType, StringSelectMenuBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js';
-import { errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { logger } from '../../utils/logger.js';
-import { addJoinToCreateTrigger, getJoinToCreateConfig, updateJoinToCreateConfig } from '../../utils/database.js';
-
-
-export default {
- data: new SlashCommandBuilder()
- .setName("jointocreate")
- .setDescription("Manage Join to Create voice channels system.")
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
- .setDMPermission(false)
- .addSubcommand((subcommand) =>
- subcommand
- .setName("setup")
- .setDescription("Set up a new Join to Create voice channel.")
- .addChannelOption((option) =>
- option
- .setName("category")
- .setDescription("Category to create the channel in.")
- .addChannelTypes(ChannelType.GuildCategory)
- )
- .addStringOption((option) =>
- option
- .setName("channel_name")
- .setDescription("Select a template for naming temporary voice channels.")
- .addChoices(
- { name: "{username}'s Room (Default)", value: "{username}'s Room" },
- { name: "{username}'s Channel", value: "{username}'s Channel" },
- { name: "{username}'s Lounge", value: "{username}'s Lounge" },
- { name: "{username}'s Space", value: "{username}'s Space" },
- { name: "{displayName}'s Room", value: "{displayName}'s Room" },
- { name: "{username}'s VC", value: "{username}'s VC" },
- { name: "🎵 {username}'s Music Room", value: "🎵 {username}'s Music Room" },
- { name: "🎮 {username}'s Gaming Room", value: "🎮 {username}'s Gaming Room" },
- { name: "💬 {username}'s Chat Room", value: "💬 {username}'s Chat Room" },
- { name: "{username}'s Private Room", value: "{username}'s Private Room" }
- )
- )
- .addIntegerOption((option) =>
- option
- .setName("user_limit")
- .setDescription("Maximum number of users in temporary channels. (0 = unlimited)")
- )
- .addIntegerOption((option) =>
- option
- .setName("bitrate")
- .setDescription("Bitrate for temporary channels in kbps (8-96).")
- )
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("config")
- .setDescription("Configure an existing Join to Create system.")
- .addChannelOption((option) =>
- option
- .setName("trigger_channel")
- .setDescription("The Join to Create trigger channel to configure.")
- .setRequired(true)
- .addChannelTypes(ChannelType.GuildVoice)
- )
- ),
- category: "utility",
-
- async execute(interaction, config, client) {
- try {
- const subcommand = interaction.options.getSubcommand();
-
- let responseEmbed;
-
- if (subcommand === "setup") {
- const category = interaction.options.getChannel('category');
- const nameTemplate = interaction.options.getString('channel_name') || "{username}'s Room";
- const userLimit = interaction.options.getInteger('user_limit') || 0;
- const bitrate = interaction.options.getInteger('bitrate') || 64;
- const guildId = interaction.guild.id;
-
- await interaction.deferReply({ flags: MessageFlags.Ephemeral });
-
- const existingChannels = await interaction.guild.channels.fetch();
- const existingJoinToCreate = existingChannels.filter(c =>
- c.type === ChannelType.GuildVoice &&
- c.name === 'Join to Create'
- );
-
- if (existingJoinToCreate.size > 0) {
- const triggerChannel = existingJoinToCreate.first();
-
- await addJoinToCreateTrigger(client, guildId, triggerChannel.id, {
- nameTemplate: nameTemplate,
- userLimit: userLimit,
- bitrate: bitrate * 1000,
- categoryId: category?.id
- });
-
- responseEmbed = successEmbed(
- `Updated existing Join to Create channel: ${triggerChannel}\n\n` +
- `**New Settings:**\n` +
- `• Template: \`${nameTemplate}\`\n` +
- `• User Limit: ${userLimit === 0 ? 'None' : userLimit}\n` +
- `• Bitrate: ${bitrate} kbps`,
- '✅ Settings Updated'
- );
- } else {
- const triggerChannel = await interaction.guild.channels.create({
- name: 'Join to Create',
- type: ChannelType.GuildVoice,
- parent: category?.id,
-userLimit: 0,
-bitrate: 64000,
- permissionOverwrites: [
- {
- id: interaction.guild.id,
- allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.Connect],
- },
- ],
- });
-
- await addJoinToCreateTrigger(client, guildId, triggerChannel.id, {
- nameTemplate: nameTemplate,
- userLimit: userLimit,
- bitrate: bitrate * 1000,
- categoryId: category?.id
- });
-
- responseEmbed = successEmbed(
- `Created Join to Create channel: ${triggerChannel}\n\n` +
- `**Settings:**\n` +
- `• Template: \`${nameTemplate}\`\n` +
- `• User Limit: ${userLimit === 0 ? 'None' : userLimit}\n` +
- `• Bitrate: ${bitrate} kbps`,
- '✅ Setup Complete'
- );
- }
- }
-
- else if (subcommand === "config") {
- const triggerChannel = interaction.options.getChannel('trigger_channel');
-
- await interaction.deferReply({ flags: MessageFlags.Ephemeral });
-
- const currentConfig = await getJoinToCreateConfig(client, interaction.guild.id);
-
- if (!currentConfig.triggerChannels.includes(triggerChannel.id)) {
- throw new Error(`${triggerChannel} is not configured as a Join to Create trigger channel.`);
- }
-
- const channelConfig = currentConfig.channelOptions?.[triggerChannel.id] || {};
-
- const nameSelect = new StringSelectMenuBuilder()
- .setCustomId(`jtc_config_name_${triggerChannel.id}`)
- .setPlaceholder('📠Select a name template')
- .addOptions(
- { label: "{username}'s Room (Default)", value: "{username}'s Room", description: "Classic room naming with username", emoji: "ðŸ " },
- { label: "{username}'s Channel", value: "{username}'s Channel", description: "Simple channel naming", emoji: "📢" },
- { label: "{username}'s Lounge", value: "{username}'s Lounge", description: "Casual lounge atmosphere", emoji: "🛋ï¸" },
- { label: "{username}'s Space", value: "{username}'s Space", description: "Personal space for users", emoji: "🌌" },
- { label: "{displayName}'s Room", value: "{displayName}'s Room", description: "Uses server nickname instead of username", emoji: "ðŸ·ï¸" },
- { label: "{username}'s VC", value: "{username}'s VC", description: "Voice Channel abbreviation", emoji: "🎤" },
- { label: "🎵 {username}'s Music Room", value: "🎵 {username}'s Music Room", description: "Perfect for music sessions", emoji: "🎵" },
- { label: "🎮 {username}'s Gaming Room", value: "🎮 {username}'s Gaming Room", description: "Gaming focused channel", emoji: "🎮" },
- { label: "💬 {username}'s Chat Room", value: "💬 {username}'s Chat Room", description: "Great for conversations", emoji: "💬" },
- { label: "{username}'s Private Room", value: "{username}'s Private Room", description: "Private space for users", emoji: "🔒" }
- );
-
- const limitButton = new ButtonBuilder()
- .setCustomId(`jtc_config_limit_${triggerChannel.id}`)
- .setLabel('👥 Change User Limit')
- .setStyle(ButtonStyle.Secondary);
-
- const bitrateButton = new ButtonBuilder()
- .setCustomId(`jtc_config_bitrate_${triggerChannel.id}`)
- .setLabel('🎵 Change Bitrate')
- .setStyle(ButtonStyle.Secondary);
-
- const deleteButton = new ButtonBuilder()
- .setCustomId(`jtc_config_delete_${triggerChannel.id}`)
- .setLabel('ðŸ—‘ï¸ Remove Channel')
- .setStyle(ButtonStyle.Danger);
-
- const row1 = new ActionRowBuilder().addComponents(nameSelect);
- const row2 = new ActionRowBuilder().addComponents(limitButton, bitrateButton, deleteButton);
-
- responseEmbed = {
- title: 'âš™ï¸ Join to Create Configuration',
- description: `Current settings for ${triggerChannel}`,
- color: 0x0099ff,
- fields: [
- {
- name: '📠Channel Name Template',
- value: `\`${channelConfig.nameTemplate || currentConfig.channelNameTemplate || "{username}'s Room"}\``,
- inline: false
- },
- {
- name: '👥 User Limit',
- value: `${(channelConfig.userLimit || currentConfig.userLimit) === 0 ? 'No limit' : (channelConfig.userLimit || currentConfig.userLimit) + ' users'}`,
- inline: true
- },
- {
- name: '🎵 Bitrate',
- value: `${(channelConfig.bitrate || currentConfig.bitrate || 64000) / 1000} kbps`,
- inline: true
- }
- ],
- footer: { text: 'Use the dropdown and buttons below to modify settings' },
- timestamp: new Date()
- };
-
- const message = await interaction.editReply({
- embeds: [responseEmbed],
- components: [row1, row2]
- });
-
- const collector = message.createMessageComponentCollector({
-time: 300000
- });
-
- collector.on('collect', async (componentInteraction) => {
- if (!componentInteraction.member.permissions.has(PermissionFlagsBits.ManageGuild)) {
- await componentInteraction.reply({
- content: '⌠You need **Manage Server** permission to use these controls.',
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- const customId = componentInteraction.customId;
-
- try {
- if (customId.includes('jtc_config_name_')) {
- await handleNameTemplateSelect(componentInteraction, triggerChannel, currentConfig);
- } else if (customId.includes('jtc_config_limit_')) {
- await handleUserLimitModal(componentInteraction, triggerChannel, currentConfig);
- } else if (customId.includes('jtc_config_bitrate_')) {
- await handleBitrateModal(componentInteraction, triggerChannel, currentConfig);
- } else if (customId.includes('jtc_config_delete_')) {
- await handleChannelDeletion(componentInteraction, triggerChannel, currentConfig, collector);
- }
- } catch (error) {
- logger.error('Error handling config component interaction:', error);
- await componentInteraction.reply({
- content: '⌠An error occurred while processing your request.',
- flags: MessageFlags.Ephemeral
- });
- }
- });
-
- collector.on('end', () => {
- const disabledRow1 = new ActionRowBuilder().addComponents(
- nameSelect.setDisabled(true)
- );
- const disabledRow2 = new ActionRowBuilder().addComponents(
- limitButton.setDisabled(true),
- bitrateButton.setDisabled(true),
- deleteButton.setDisabled(true)
- );
-
- message.edit({
- components: [disabledRow1, disabledRow2],
- embeds: [{
- ...responseEmbed,
- footer: { text: 'Configuration session expired. Run the command again to make changes.' }
- }]
- }).catch(() => {});
- });
- }
-
- return await interaction.editReply({ embeds: [responseEmbed] });
-
- } catch (error) {
- logger.error('Error in jointocreate command:', error);
-
- try {
- const errorEmbedObj = errorEmbed("Error", error.message || "An error occurred while executing this command.");
-
- if (interaction.deferred) {
- return await interaction.editReply({ embeds: [errorEmbedObj] });
- } else {
- return await interaction.reply({ embeds: [errorEmbedObj], flags: MessageFlags.Ephemeral });
- }
- } catch (replyError) {
- logger.error('Failed to send error message:', replyError);
- }
- }
- },
-};
-
-async function handleNameTemplateSelect(interaction, triggerChannel, currentConfig) {
- const selectedTemplate = interaction.values[0];
-
- const channelOptions = currentConfig.channelOptions || {};
- channelOptions[triggerChannel.id] = {
- ...channelOptions[triggerChannel.id],
- nameTemplate: selectedTemplate
- };
-
- await updateJoinToCreateConfig(interaction.client, interaction.guild.id, {
- channelOptions: channelOptions
- });
-
- await interaction.reply({
- content: `✅ **Name template updated to:** \`${selectedTemplate}\``,
- flags: MessageFlags.Ephemeral
- });
-}
-
-async function handleUserLimitModal(interaction, triggerChannel, currentConfig) {
- const modal = new ModalBuilder()
- .setCustomId(`jtc_limit_modal_${triggerChannel.id}`)
- .setTitle('Configure User Limit')
- .addComponents(
- new ActionRowBuilder().addComponents(
- new TextInputBuilder()
- .setCustomId('user_limit')
- .setLabel('Enter user limit (0-99, 0 = no limit)')
- .setPlaceholder('Enter a number between 0 and 99')
- .setStyle(TextInputStyle.Short)
- .setRequired(true)
- .setMinLength(1)
- .setMaxLength(2)
- .setValue((currentConfig.channelOptions?.[triggerChannel.id]?.userLimit || currentConfig.userLimit || 0).toString())
- )
- );
-
- await interaction.showModal(modal);
-
- const filter = (i) => i.customId === `jtc_limit_modal_${triggerChannel.id}` && i.user.id === interaction.user.id;
- try {
- const modalSubmission = await interaction.awaitModalSubmit({ filter, time: 60000 });
-
- const userInput = modalSubmission.fields.getTextInputValue('user_limit').trim();
- const userLimit = parseInt(userInput);
-
- if (isNaN(userLimit) || userLimit < 0 || userLimit > 99) {
- await modalSubmission.reply({
- content: '⌠**Invalid input!** Please enter a number between 0 and 99.\n• **0** = No user limit\n• **1-99** = Maximum number of users allowed',
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- const channelOptions = currentConfig.channelOptions || {};
- channelOptions[triggerChannel.id] = {
- ...channelOptions[triggerChannel.id],
- userLimit: userLimit
- };
-
- await updateJoinToCreateConfig(modalSubmission.client, modalSubmission.guild.id, {
- channelOptions: channelOptions
- });
-
- await modalSubmission.reply({
- content: `✅ **User limit updated to:** ${userLimit === 0 ? 'No limit' : userLimit + ' users'}`,
- flags: MessageFlags.Ephemeral
- });
-
- } catch (error) {
- if (error.code === 'INTERACTION_COLLECTOR_ERROR') {
-return;
- }
- logger.error('Error handling user limit modal:', error);
- }
-}
-
-async function handleBitrateModal(interaction, triggerChannel, currentConfig) {
- const modal = new ModalBuilder()
- .setCustomId(`jtc_bitrate_modal_${triggerChannel.id}`)
- .setTitle('Configure Bitrate')
- .addComponents(
- new ActionRowBuilder().addComponents(
- new TextInputBuilder()
- .setCustomId('bitrate')
- .setLabel('Enter bitrate in kbps (8-384)')
- .setPlaceholder('Enter a number between 8 and 384')
- .setStyle(TextInputStyle.Short)
- .setRequired(true)
- .setMinLength(1)
- .setMaxLength(3)
- .setValue(((currentConfig.channelOptions?.[triggerChannel.id]?.bitrate || currentConfig.bitrate || 64000) / 1000).toString())
- )
- );
-
- await interaction.showModal(modal);
-
- const filter = (i) => i.customId === `jtc_bitrate_modal_${triggerChannel.id}` && i.user.id === interaction.user.id;
- try {
- const modalSubmission = await interaction.awaitModalSubmit({ filter, time: 60000 });
-
- const userInput = modalSubmission.fields.getTextInputValue('bitrate').trim();
- const bitrate = parseInt(userInput);
-
- if (isNaN(bitrate) || bitrate < 8 || bitrate > 384) {
- await modalSubmission.reply({
- content: '⌠**Invalid input!** Please enter a number between 8 and 384.\n• **8-64 kbps** = Minimum quality\n• **96-128 kbps** = Standard quality\n• **256-384 kbps** = High quality (requires boost level)',
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- const channelOptions = currentConfig.channelOptions || {};
- channelOptions[triggerChannel.id] = {
- ...channelOptions[triggerChannel.id],
- bitrate: bitrate * 1000
- };
-
- await updateJoinToCreateConfig(modalSubmission.client, modalSubmission.guild.id, {
- channelOptions: channelOptions
- });
-
- await modalSubmission.reply({
- content: `✅ **Bitrate updated to:** ${bitrate} kbps`,
- flags: MessageFlags.Ephemeral
- });
-
- } catch (error) {
- if (error.code === 'INTERACTION_COLLECTOR_ERROR') {
-return;
- }
- logger.error('Error handling bitrate modal:', error);
- }
-}
-
-async function handleChannelDeletion(interaction, triggerChannel, currentConfig, collector) {
- const confirmRow = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`jtc_delete_confirm`)
- .setLabel('ðŸ—‘ï¸ Yes, Delete Channel')
- .setStyle(ButtonStyle.Danger),
- new ButtonBuilder()
- .setCustomId(`jtc_delete_cancel`)
- .setLabel('⌠Cancel')
- .setStyle(ButtonStyle.Secondary)
- );
-
- await interaction.reply({
- content: `âš ï¸ **Are you sure you want to delete ${triggerChannel}?**\n\nThis will remove the Join to Create functionality for this channel and cannot be undone.`,
- components: [confirmRow],
- flags: MessageFlags.Ephemeral
- });
-
- const message = await interaction.fetchReply();
- const deleteCollector = message.createMessageComponentCollector({
- componentType: ComponentType.Button,
- time: 30000
- });
-
- deleteCollector.on('collect', async (deleteInteraction) => {
- if (deleteInteraction.customId === 'jtc_delete_confirm') {
- try {
- const updatedTriggerChannels = currentConfig.triggerChannels.filter(id => id !== triggerChannel.id);
- const updatedChannelOptions = { ...currentConfig.channelOptions };
- delete updatedChannelOptions[triggerChannel.id];
-
- await updateJoinToCreateConfig(deleteInteraction.client, deleteInteraction.guild.id, {
- triggerChannels: updatedTriggerChannels,
- channelOptions: updatedChannelOptions
- });
-
- await triggerChannel.delete('Join to Create channel removed by administrator');
-
- await deleteInteraction.update({
- content: `✅ **${triggerChannel.name} has been deleted.**`,
- components: []
- });
-
- collector.stop();
- } catch (error) {
- if (error.code === 'INTERACTION_COLLECTOR_ERROR') {
-return;
- }
- logger.error('Error handling channel deletion confirmation:', error);
- }
- } else {
- await deleteInteraction.update({
- content: '⌠**Channel deletion cancelled.**',
- components: []
- });
- }
- });
-
- deleteCollector.on('end', () => {
- interaction.editReply({ components: [] }).catch(() => {});
- });
-
- deleteCollector.on('error', (error) => {
- if (error.code !== 'INTERACTION_COLLECTOR_ERROR') {
- logger.error('Unexpected error in delete collector:', error);
- }
- });
-}
-
diff --git a/src/commands/JoinToCreate/modules/config_setup.js b/src/commands/JoinToCreate/modules/config_setup.js
deleted file mode 100644
index d3e52ed482..0000000000
--- a/src/commands/JoinToCreate/modules/config_setup.js
+++ /dev/null
@@ -1,490 +0,0 @@
-import {
- ActionRowBuilder,
- StringSelectMenuBuilder,
- StringSelectMenuOptionBuilder,
- ChannelType,
- MessageFlags,
- ComponentType,
- EmbedBuilder,
- ButtonBuilder,
- ButtonStyle
-} from 'discord.js';
-import { successEmbed, errorEmbed } from '../../../utils/embeds.js';
-import {
- getJoinToCreateConfig,
- updateJoinToCreateConfig,
- removeJoinToCreateTrigger,
- addJoinToCreateTrigger
-} from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
- try {
- const triggerChannel = interaction.options.getChannel('trigger_channel');
- const guildId = interaction.guild.id;
-
- const currentConfig = await getJoinToCreateConfig(client, guildId);
-
- if (!currentConfig.triggerChannels.includes(triggerChannel.id)) {
- throw new Error(`${triggerChannel} is not configured as a Join to Create trigger channel.`);
- }
-
- const embed = new EmbedBuilder()
- .setTitle('âš™ï¸ Join to Create Configuration')
- .setDescription(`Configure settings for ${triggerChannel}`)
- .setColor('#0099ff')
- .addFields(
- {
- name: '📠Current Channel Name Template',
- value: `\`${currentConfig.channelOptions?.[triggerChannel.id]?.nameTemplate || currentConfig.channelNameTemplate}\``,
- inline: false
- },
- {
- name: '👥 Current User Limit',
- value: `${currentConfig.channelOptions?.[triggerChannel.id]?.userLimit || currentConfig.userLimit === 0 ? 'No limit' : currentConfig.userLimit + ' users'}`,
- inline: true
- },
- {
- name: '🎵 Current Bitrate',
- value: `${(currentConfig.channelOptions?.[triggerChannel.id]?.bitrate || currentConfig.bitrate) / 1000} kbps`,
- inline: true
- }
- )
- .setFooter({ text: 'Select an option to configure below' })
- .setTimestamp();
-
- const selectMenu = new StringSelectMenuBuilder()
- .setCustomId(`jointocreate_config_${triggerChannel.id}`)
- .setPlaceholder('Select a configuration option')
- .addOptions(
- new StringSelectMenuOptionBuilder()
- .setLabel('Change Channel Name Template')
- .setDescription('Modify the template for temporary channel names')
- .setValue('name_template'),
- new StringSelectMenuOptionBuilder()
- .setLabel('Change User Limit')
- .setDescription('Set maximum users per temporary channel')
- .setValue('user_limit'),
- new StringSelectMenuOptionBuilder()
- .setLabel('Change Bitrate')
- .setDescription('Adjust audio quality for temporary channels')
- .setValue('bitrate'),
- new StringSelectMenuOptionBuilder()
- .setLabel('Remove This Trigger Channel')
- .setDescription('Remove this channel from the Join to Create system')
- .setValue('remove_trigger'),
- new StringSelectMenuOptionBuilder()
- .setLabel('View Current Settings')
- .setDescription('Show all current configuration details')
- .setValue('view_settings')
- );
-
- const row = new ActionRowBuilder().addComponents(selectMenu);
-
- await interaction.editReply({
- embeds: [embed],
- components: [row],
- }).catch(error => {
- console.error('Failed to edit reply in config_setup:', error);
- });
-
- const collector = interaction.channel.createMessageComponentCollector({
- componentType: ComponentType.StringSelect,
- filter: (i) => i.user.id === interaction.user.id && i.customId === `jointocreate_config_${triggerChannel.id}`,
-time: 60000
- });
-
- collector.on('collect', async (selectInteraction) => {
- await selectInteraction.deferUpdate();
-
- const selectedOption = selectInteraction.values[0];
-
- try {
- switch (selectedOption) {
- case 'name_template':
- await handleNameTemplateChange(selectInteraction, triggerChannel, currentConfig, client);
- break;
- case 'user_limit':
- await handleUserLimitChange(selectInteraction, triggerChannel, currentConfig, client);
- break;
- case 'bitrate':
- await handleBitrateChange(selectInteraction, triggerChannel, currentConfig, client);
- break;
- case 'remove_trigger':
- await handleRemoveTrigger(selectInteraction, triggerChannel, currentConfig, client);
- break;
- case 'view_settings':
- await handleViewSettings(selectInteraction, triggerChannel, currentConfig, client);
- break;
- }
- } catch (error) {
- console.error('Configuration menu error:', error);
- await selectInteraction.followUp({
- embeds: [errorEmbed('Configuration Error', 'An error occurred while processing your selection.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- });
-
- collector.on('end', async (collected, reason) => {
- if (reason === 'time') {
- const disabledRow = new ActionRowBuilder().addComponents(
- selectMenu.setDisabled(true)
- );
-
- await interaction.editReply({
- components: [disabledRow],
- }).catch(() => {});
- }
- });
- } catch (error) {
- console.error('Error in config_setup:', error);
- throw new Error(`Failed to configure Join to Create: ${error.message}`);
- }
- }
-};
-
-async function handleNameTemplateChange(interaction, triggerChannel, currentConfig, client) {
- const embed = new EmbedBuilder()
- .setTitle('📠Channel Name Template Configuration')
- .setDescription('Please enter the new channel name template.')
- .addFields(
- {
- name: 'Available Variables',
- value: '• `{username}` - User\'s username\n• `{display_name}` - User\'s display name\n• `{user_tag}` - User\'s tag (User#1234)\n• `{guild_name}` - Server name',
- inline: false
- },
- {
- name: 'Current Template',
- value: `\`${currentConfig.channelOptions?.[triggerChannel.id]?.nameTemplate || currentConfig.channelNameTemplate}\``,
- inline: false
- }
- )
- .setColor('#0099ff')
- .setFooter({ text: 'Type your new template in the chat below' });
-
- await interaction.followUp({ embeds: [embed], flags: MessageFlags.Ephemeral });
-
- const collector = interaction.channel.createMessageCollector({
- filter: (m) => m.author.id === interaction.user.id,
-time: 30000,
- max: 1
- });
-
- collector.on('collect', async (message) => {
- try {
- const newTemplate = message.content.trim();
-
- if (!newTemplate || newTemplate.length > 100) {
- await interaction.followUp({
- embeds: [errorEmbed('Invalid Template', 'Template must be between 1 and 100 characters.')],
- flags: MessageFlags.Ephemeral,
- });
- return;
- }
-
- const channelOptions = currentConfig.channelOptions || {};
- channelOptions[triggerChannel.id] = {
- ...channelOptions[triggerChannel.id],
- nameTemplate: newTemplate
- };
-
- await updateJoinToCreateConfig(client, interaction.guild.id, {
- channelOptions: channelOptions
- });
-
- await interaction.followUp({
- embeds: [successEmbed('✅ Template Updated', `Channel name template changed to \`${newTemplate}\``)],
- flags: MessageFlags.Ephemeral,
- });
-
- await message.delete().catch(() => {});
- } catch (error) {
- console.error('Template update error:', error);
- await interaction.followUp({
- embeds: [errorEmbed('Update Failed', 'Could not update the channel name template.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- });
-
- collector.on('end', (collected, reason) => {
- if (reason === 'time') {
- interaction.followUp({
- embeds: [errorEmbed('Timeout', 'No response received. Template update cancelled.')],
- flags: MessageFlags.Ephemeral,
- }).catch(() => {});
- }
- });
-}
-
-async function handleUserLimitChange(interaction, triggerChannel, currentConfig, client) {
- const embed = new EmbedBuilder()
- .setTitle('👥 User Limit Configuration')
- .setDescription('Please enter the new user limit (0-99, where 0 = no limit).')
- .addFields(
- {
- name: 'Current Limit',
- value: `${currentConfig.channelOptions?.[triggerChannel.id]?.userLimit || currentConfig.userLimit === 0 ? 'No limit' : currentConfig.userLimit + ' users'}`,
- inline: false
- }
- )
- .setColor('#0099ff')
- .setFooter({ text: 'Type the new limit in the chat below' });
-
- await interaction.followUp({ embeds: [embed], flags: MessageFlags.Ephemeral });
-
- const collector = interaction.channel.createMessageCollector({
- filter: (m) => m.author.id === interaction.user.id && /^\d+$/.test(m.content.trim()),
- time: 30000,
- max: 1
- });
-
- collector.on('collect', async (message) => {
- try {
- const newLimit = parseInt(message.content.trim());
-
- if (newLimit < 0 || newLimit > 99) {
- await interaction.followUp({
- embeds: [errorEmbed('Invalid Limit', 'User limit must be between 0 and 99.')],
- flags: MessageFlags.Ephemeral,
- });
- return;
- }
-
- const channelOptions = currentConfig.channelOptions || {};
- channelOptions[triggerChannel.id] = {
- ...channelOptions[triggerChannel.id],
- userLimit: newLimit
- };
-
- await updateJoinToCreateConfig(client, interaction.guild.id, {
- channelOptions: channelOptions
- });
-
- await interaction.followUp({
- embeds: [successEmbed('✅ Limit Updated', `User limit changed to ${newLimit === 0 ? 'No limit' : newLimit + ' users'}`)],
- flags: MessageFlags.Ephemeral,
- });
-
- await message.delete().catch(() => {});
- } catch (error) {
- console.error('User limit update error:', error);
- await interaction.followUp({
- embeds: [errorEmbed('Update Failed', 'Could not update the user limit.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- });
-
- collector.on('end', (collected, reason) => {
- if (reason === 'time') {
- interaction.followUp({
- embeds: [errorEmbed('Timeout', 'No valid response received. Update cancelled.')],
- flags: MessageFlags.Ephemeral,
- }).catch(() => {});
- }
- });
-}
-
-async function handleBitrateChange(interaction, triggerChannel, currentConfig, client) {
- const embed = new EmbedBuilder()
- .setTitle('🎵 Bitrate Configuration')
- .setDescription('Please enter the new bitrate in kbps (8-384).')
- .addFields(
- {
- name: 'Current Bitrate',
- value: `${(currentConfig.channelOptions?.[triggerChannel.id]?.bitrate || currentConfig.bitrate) / 1000} kbps`,
- inline: false
- },
- {
- name: 'Common Values',
- value: '• 64 kbps - Normal quality\n• 96 kbps - Good quality\n• 128 kbps - High quality\n• 256 kbps - Very high quality',
- inline: false
- }
- )
- .setColor('#0099ff')
- .setFooter({ text: 'Type the new bitrate in the chat below' });
-
- await interaction.followUp({ embeds: [embed], flags: MessageFlags.Ephemeral });
-
- const collector = interaction.channel.createMessageCollector({
- filter: (m) => m.author.id === interaction.user.id && /^\d+$/.test(m.content.trim()),
- time: 30000,
- max: 1
- });
-
- collector.on('collect', async (message) => {
- try {
- const newBitrate = parseInt(message.content.trim());
-
- if (newBitrate < 8 || newBitrate > 384) {
- await interaction.followUp({
- embeds: [errorEmbed('Invalid Bitrate', 'Bitrate must be between 8 and 384 kbps.')],
- flags: MessageFlags.Ephemeral,
- });
- return;
- }
-
- const channelOptions = currentConfig.channelOptions || {};
- channelOptions[triggerChannel.id] = {
- ...channelOptions[triggerChannel.id],
- bitrate: newBitrate * 1000
- };
-
- await updateJoinToCreateConfig(client, interaction.guild.id, {
- channelOptions: channelOptions
- });
-
- await interaction.followUp({
- embeds: [successEmbed('✅ Bitrate Updated', `Bitrate changed to ${newBitrate} kbps`)],
- flags: MessageFlags.Ephemeral,
- });
-
- await message.delete().catch(() => {});
- } catch (error) {
- console.error('Bitrate update error:', error);
- await interaction.followUp({
- embeds: [errorEmbed('Update Failed', 'Could not update the bitrate.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- });
-
- collector.on('end', (collected, reason) => {
- if (reason === 'time') {
- interaction.followUp({
- embeds: [errorEmbed('Timeout', 'No valid response received. Update cancelled.')],
- flags: MessageFlags.Ephemeral,
- }).catch(() => {});
- }
- });
-}
-
-async function handleRemoveTrigger(interaction, triggerChannel, currentConfig, client) {
- const embed = new EmbedBuilder()
- .setTitle('âš ï¸ Remove Trigger Channel')
- .setDescription(`Are you sure you want to remove ${triggerChannel} from the Join to Create system?`)
- .setColor('#ff6600')
- .setFooter({ text: 'This action cannot be undone' });
-
- const row = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`confirm_remove_${triggerChannel.id}`)
- .setLabel('Remove Channel')
- .setStyle(ButtonStyle.Danger),
- new ButtonBuilder()
- .setCustomId(`cancel_remove_${triggerChannel.id}`)
- .setLabel('Cancel')
- .setStyle(ButtonStyle.Secondary)
- );
-
- await interaction.followUp({
- embeds: [embed],
- components: [row],
- flags: MessageFlags.Ephemeral
- });
-
- const collector = interaction.channel.createMessageComponentCollector({
- componentType: ComponentType.Button,
- filter: (i) => i.user.id === interaction.user.id &&
- (i.customId === `confirm_remove_${triggerChannel.id}` || i.customId === `cancel_remove_${triggerChannel.id}`),
- time: 30000,
- max: 1
- });
-
- collector.on('collect', async (buttonInteraction) => {
- await buttonInteraction.deferUpdate();
-
- if (buttonInteraction.customId === `confirm_remove_${triggerChannel.id}`) {
- try {
- const success = await removeJoinToCreateTrigger(client, interaction.guild.id, triggerChannel.id);
-
- if (success) {
- await buttonInteraction.followUp({
- embeds: [successEmbed('✅ Channel Removed', `${triggerChannel} has been removed from the Join to Create system.`)],
- flags: MessageFlags.Ephemeral,
- });
- } else {
- await buttonInteraction.followUp({
- embeds: [errorEmbed('Removal Failed', 'Could not remove the trigger channel.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- } catch (error) {
- console.error('Remove trigger error:', error);
- await buttonInteraction.followUp({
- embeds: [errorEmbed('Removal Failed', 'An error occurred while removing the trigger channel.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- } else {
- await buttonInteraction.followUp({
- embeds: [successEmbed('✅ Cancelled', 'Channel removal has been cancelled.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- });
-
- collector.on('end', (collected, reason) => {
- if (reason === 'time') {
- interaction.followUp({
- embeds: [errorEmbed('Timeout', 'No response received. Removal cancelled.')],
- flags: MessageFlags.Ephemeral,
- }).catch(() => {});
- }
- });
-}
-
-async function handleViewSettings(interaction, triggerChannel, currentConfig, client) {
- const channelConfig = currentConfig.channelOptions?.[triggerChannel.id] || {};
-
- const embed = new EmbedBuilder()
- .setTitle('📋 Current Settings')
- .setDescription(`Configuration for ${triggerChannel}`)
- .setColor('#0099ff')
- .addFields(
- {
- name: '🎯 Trigger Channel',
- value: `${triggerChannel} (${triggerChannel.id})`,
- inline: false
- },
- {
- name: '📠Channel Name Template',
- value: `\`${channelConfig.nameTemplate || currentConfig.channelNameTemplate}\``,
- inline: false
- },
- {
- name: '👥 User Limit',
- value: `${channelConfig.userLimit || currentConfig.userLimit === 0 ? 'No limit' : (channelConfig.userLimit || currentConfig.userLimit) + ' users'}`,
- inline: true
- },
- {
- name: '🎵 Bitrate',
- value: `${(channelConfig.bitrate || currentConfig.bitrate) / 1000} kbps`,
- inline: true
- },
- {
- name: '📠Category',
- value: currentConfig.categoryId ? `<#${currentConfig.categoryId}>` : 'Not set',
- inline: true
- },
- {
- name: '📊 System Status',
- value: currentConfig.enabled ? '✅ Enabled' : '⌠Disabled',
- inline: true
- },
- {
- name: '🔢 Active Temporary Channels',
- value: Object.keys(currentConfig.temporaryChannels || {}).length.toString(),
- inline: true
- }
- )
- .setTimestamp();
-
- await interaction.followUp({
- embeds: [embed],
- flags: MessageFlags.Ephemeral
- });
-}
-
diff --git a/src/commands/JoinToCreate/modules/setup.js b/src/commands/JoinToCreate/modules/setup.js
deleted file mode 100644
index df3b439b46..0000000000
--- a/src/commands/JoinToCreate/modules/setup.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import { ChannelType, MessageFlags, PermissionFlagsBits } from 'discord.js';
-import { successEmbed, errorEmbed } from '../../../utils/embeds.js';
-import { addJoinToCreateTrigger, getJoinToCreateConfig } from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
- const category = interaction.options.getChannel('category');
- const nameTemplate = interaction.options.getString('channel_name') || "{username}'s Room";
- const userLimit = interaction.options.getInteger('user_limit') || 0;
- const bitrate = interaction.options.getInteger('bitrate') || 64;
- const guildId = interaction.guild.id;
-
- try {
- const triggerChannel = await interaction.guild.channels.create({
- name: 'Join to Create',
- type: ChannelType.GuildVoice,
- parent: category?.id,
- userLimit: userLimit,
- bitrate: bitrate * 1000,
- permissionOverwrites: [
- {
- id: interaction.guild.id,
- allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.Connect],
- },
- ],
- });
-
- await addJoinToCreateTrigger(client, guildId, triggerChannel.id, {
- nameTemplate: nameTemplate,
- userLimit: userLimit,
- bitrate: bitrate * 1000,
- categoryId: category?.id
- });
-
- const embed = successEmbed(
- `Created trigger channel: ${triggerChannel}\n\n` +
- `**Settings:**\n` +
- `• Temporary Channel Name Template: \`${nameTemplate}\`\n` +
- `• User Limit: ${userLimit === 0 ? 'No limit' : userLimit + ' users'}\n` +
- `• Bitrate: ${bitrate} kbps\n` +
- `${category ? `• Category: ${category.name}` : '• Category: None (root level)'}\n\n` +
- `When users join this channel, a temporary voice channel will be created for them.`,
- '✅ Join to Create Setup Complete'
- );
-
- try {
- if (interaction.deferred) {
- await interaction.editReply({ embeds: [embed] });
- } else {
- await interaction.reply({ embeds: [embed], flags: MessageFlags.Ephemeral });
- }
- } catch (responseError) {
- console.error('Error responding to interaction:', responseError);
-
- try {
- if (!interaction.replied) {
- await interaction.reply({ embeds: [embed], flags: MessageFlags.Ephemeral });
- }
- } catch (e) {
- console.error('All response attempts failed:', e);
- }
- }
- } catch (error) {
- console.error('Error in JoinToCreate setup:', error);
- throw new Error(`Failed to set up Join to Create: ${error.message}`);
- }
- }
-};
-
diff --git a/src/commands/Leveling/leaderboard.js b/src/commands/Leveling/leaderboard.js
deleted file mode 100644
index 3d03a82a59..0000000000
--- a/src/commands/Leveling/leaderboard.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getLeaderboard, getLevelingConfig, getXpForLevel } from '../../utils/database.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("leaderboard")
- .setDescription("Shows the server's level leaderboard")
- .setDMPermission(false),
- category: "Leveling",
-
- async execute(interaction, config, client) {
- const leaderboard = await getLeaderboard(client, interaction.guildId, 10);
- const levelingConfig = await getLevelingConfig(client, interaction.guildId);
-
- if (!levelingConfig?.enabled) {
- throw new Error("The leveling system is currently disabled on this server.");
- }
-
- if (leaderboard.length === 0) {
- throw new Error("No level data found yet. Start chatting to gain XP!");
- }
-
- const embed = new EmbedBuilder()
- .setTitle("🆠Level Leaderboard")
- .setColor("#3498db")
- .setDescription("Top 10 most active members in this server:")
- .setTimestamp();
-
- const leaderboardText = await Promise.all(
- leaderboard.map(async (user, index) => {
- try {
- const member = await interaction.guild.members.fetch(user.userId).catch(() => null);
- const username = member?.user?.tag || `Unknown User (${user.userId})`;
- const xpForNextLevel = getXpForLevel(user.level + 1);
-
- let rankPrefix = `${index + 1}.`;
- if (index === 0) rankPrefix = "🥇";
- else if (index === 1) rankPrefix = "🥈";
- else if (index === 2) rankPrefix = "🥉";
- else rankPrefix = `**${index + 1}.**`;
-
- return `${rankPrefix} ${username} - Level ${user.level} (${user.xp}/${xpForNextLevel} XP)`;
- } catch (error) {
- return `**${index + 1}.** Error loading user ${user.userId}`;
- }
- }),
- );
-
- embed.addFields({
- name: "Rankings",
- value: leaderboardText.join("\n"),
- });
-
- await interaction.editReply({ embeds: [embed] });
- },
-};
-
diff --git a/src/commands/Leveling/leveladd.js b/src/commands/Leveling/leveladd.js
deleted file mode 100644
index 0311108997..0000000000
--- a/src/commands/Leveling/leveladd.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getUserLevelData, saveUserLevelData, getXpForLevel } from '../../utils/database.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("leveladd")
- .setDescription("Add levels to a user")
- .addUserOption((option) =>
- option
- .setName("user")
- .setDescription("The user to add levels to")
- .setRequired(true),
- )
- .addIntegerOption((option) =>
- option
- .setName("levels")
- .setDescription("Number of levels to add")
- .setRequired(true)
- .setMinValue(1),
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
- .setDMPermission(false),
- category: "Leveling",
-
- async execute(interaction, config, client) {
- try {
- const targetUser = interaction.options.getUser("user");
- const levelsToAdd = interaction.options.getInteger("levels");
-
- const userData = await getUserLevelData(
- client,
- interaction.guildId,
- targetUser.id,
- );
-
- const newLevel = userData.level + levelsToAdd;
- const newXp = 0;
- const newTotalXp =
- userData.totalXp +
- (getXpForLevel(newLevel) - getXpForLevel(userData.level));
-
- userData.level = newLevel;
- userData.xp = newXp;
- userData.totalXp = newTotalXp;
-
- await saveUserLevelData(
- client,
- interaction.guildId,
- targetUser.id,
- userData,
- );
-
- await interaction.editReply({
- embeds: [
- successEmbed("Levels Added", `Successfully added ${levelsToAdd} levels to ${targetUser.tag}.`),
- ],
- },
- errorEmbed("Level Add Failed", "Could not add levels to that user.")
- );
- } catch (error) {
- console.error('LevelAdd command error:', error);
- return interaction.reply({
- embeds: [errorEmbed('System Error', 'Could not add levels at this time.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
diff --git a/src/commands/Leveling/levelconfig.js b/src/commands/Leveling/levelconfig.js
deleted file mode 100644
index be8fae86b8..0000000000
--- a/src/commands/Leveling/levelconfig.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getLevelingConfig, saveLevelingConfig } from '../../utils/database.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("levelconfig")
- .setDescription("Configure the leveling system")
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
- .addSubcommand(subcommand =>
- subcommand
- .setName("toggle")
- .setDescription("Enable or disable the leveling system")
- .addBooleanOption(option =>
- option
- .setName("enabled")
- .setDescription("Whether to enable or disable the leveling system")
- .setRequired(true)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("channel")
- .setDescription("Set the level up notification channel")
- .addChannelOption(option =>
- option
- .setName("channel")
- .setDescription("The channel to send level up notifications to")
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(true)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("xp")
- .setDescription("Set the XP range per message")
- .addIntegerOption(option =>
- option
- .setName("min")
- .setDescription("Minimum XP per message")
- .setRequired(true)
- .setMinValue(1)
- )
- .addIntegerOption(option =>
- option
- .setName("max")
- .setDescription("Maximum XP per message")
- .setRequired(true)
- .setMinValue(1)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("message")
- .setDescription("Set the level up message")
- .addStringOption(option =>
- option
- .setName("text")
- .setDescription("Use {user} for the username and {level} for the level")
- .setRequired(true)
- )
- )
- .setDMPermission(false),
- category: "Leveling",
-
- async execute(interaction, config, client) {
-try {
- const subcommand = interaction.options.getSubcommand();
- const levelingConfig = await getLevelingConfig(client, interaction.guildId);
-
- switch (subcommand) {
- case "toggle": {
- const enabled = interaction.options.getBoolean("enabled");
- levelingConfig.enabled = enabled;
-
- await saveLevelingConfig(client, interaction.guildId, levelingConfig);
-
- await interaction.reply({
- embeds: [createEmbed({ title: "Leveling System Updated", description: `The leveling system has been ${enabled ? "enabled" : "disabled"}.` })]
- });
- break;
- }
-
- case "channel": {
- const channel = interaction.options.getChannel("channel");
- levelingConfig.levelUpChannel = channel.id;
-
- await saveLevelingConfig(client, interaction.guildId, levelingConfig);
-
- await interaction.editReply({
- embeds: [createEmbed({ title: "Level Up Channel Set", description: `Level up notifications will now be sent in ${channel}.` })]
- });
- break;
- }
-
- case "xp": {
- const min = interaction.options.getInteger("min");
- const max = interaction.options.getInteger("max");
-
- if (min > max) {
- return interaction.editReply({
- embeds: [createEmbed({ title: "Invalid Range", description: "The minimum XP cannot be greater than the maximum XP." })]
- });
- }
-
- levelingConfig.xpRange = { min, max };
- await saveLevelingConfig(client, interaction.guildId, levelingConfig);
-
- await interaction.editReply({
- embeds: [createEmbed({ title: "XP Range Updated", description: `XP per message is now between ${min} and ${max}.` })]
- });
- break;
- }
-
- case "message": {
- const text = interaction.options.getString("text");
- levelingConfig.levelUpMessage = text;
-
- await saveLevelingConfig(client, interaction.guildId, levelingConfig);
-
- await interaction.editReply({
- embeds: [createEmbed({ title: "Level Up Message Updated", description: `The level up message has been updated.` })]
- });
- break;
- }
- }
-
- } catch (error) {
- console.error("LevelConfig command error:", error);
- await interaction.editReply({
- embeds: [createEmbed({ title: "Error", description: "An error occurred while updating the leveling configuration." })]
- });
- }
- }
-};
-
diff --git a/src/commands/Leveling/levelremove.js b/src/commands/Leveling/levelremove.js
deleted file mode 100644
index 3e3552f597..0000000000
--- a/src/commands/Leveling/levelremove.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getUserLevelData, saveUserLevelData, getXpForLevel } from '../../utils/database.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("levelremove")
- .setDescription("Remove levels from a user")
- .addUserOption(option =>
- option
- .setName("user")
- .setDescription("The user to remove levels from")
- .setRequired(true)
- )
- .addIntegerOption(option =>
- option
- .setName("levels")
- .setDescription("Number of levels to remove")
- .setRequired(true)
- .setMinValue(1)
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
- .setDMPermission(false),
- category: "Leveling",
-
- async execute(interaction, config, client) {
-try {
- const targetUser = interaction.options.getUser("user");
- const levelsToRemove = interaction.options.getInteger("levels");
- const userData = await getUserLevelData(client, interaction.guildId, targetUser.id);
-
- const newLevel = Math.max(0, userData.level - levelsToRemove);
-
- if (userData.level === 0) {
- return interaction.reply({
- embeds: [createEmbed({ title: "Level Remove", description: `${targetUser.tag} is already at level 0.` })]
- });
- }
-
-const newXp = 0;
- const newTotalXp = getXpForLevel(newLevel) + newXp;
-
- userData.level = newLevel;
- userData.xp = newXp;
- userData.totalXp = newTotalXp;
-
- await saveUserLevelData(client, interaction.guildId, targetUser.id, userData);
-
- await interaction.editReply({
- embeds: [createEmbed({ title: "Levels Removed", description: `Successfully removed ${levelsToRemove} levels from ${targetUser.tag}. They are now level ${newLevel}.` })]
- });
-
- } catch (error) {
- console.error("LevelRemove command error:", error);
- await interaction.editReply({
- embeds: [createEmbed({ title: "Error", description: "An error occurred while removing levels from the user." })]
- });
- }
- }
-};
-
diff --git a/src/commands/Leveling/levelset.js b/src/commands/Leveling/levelset.js
deleted file mode 100644
index 7aa1975ca4..0000000000
--- a/src/commands/Leveling/levelset.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getUserLevelData, saveUserLevelData, getXpForLevel } from '../../utils/database.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("levelset")
- .setDescription("Set a user's level")
- .addUserOption(option =>
- option
- .setName("user")
- .setDescription("The user to set the level for")
- .setRequired(true)
- )
- .addIntegerOption(option =>
- option
- .setName("level")
- .setDescription("The level to set")
- .setRequired(true)
- .setMinValue(0)
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
- .setDMPermission(false),
- category: "Leveling",
-
- async execute(interaction, config, client) {
-try {
- const targetUser = interaction.options.getUser("user");
- const newLevel = interaction.options.getInteger("level");
- const userData = await getUserLevelData(client, interaction.guildId, targetUser.id);
-
-const newXp = 0;
- const newTotalXp = getXpForLevel(newLevel) + newXp;
-
- userData.level = newLevel;
- userData.xp = newXp;
- userData.totalXp = newTotalXp;
-
- await saveUserLevelData(client, interaction.guildId, targetUser.id, userData);
-
- await interaction.reply({
- embeds: [createEmbed({ title: "Level Set", description: `Successfully set ${targetUser.tag}'s level to ${newLevel}.` })]
- });
-
- } catch (error) {
- console.error("LevelSet command error:", error);
- await interaction.editReply({
- embeds: [createEmbed({ title: "Error", description: "An error occurred while setting the user's level." })]
- });
- }
- }
-};
-
diff --git a/src/commands/Leveling/rank.js b/src/commands/Leveling/rank.js
deleted file mode 100644
index 8a8cdeccb7..0000000000
--- a/src/commands/Leveling/rank.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import { SlashCommandBuilder, EmbedBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getUserLevelData, getLevelingConfig, getXpForLevel } from '../../utils/database.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("rank")
- .setDescription("Check your or another user's rank and level")
- .addUserOption((option) =>
- option
- .setName("user")
- .setDescription("The user to check the rank of")
- .setRequired(false),
- )
- .setDMPermission(false),
- category: "Leveling",
-
- async execute(interaction, config, client) {
- const targetUser = interaction.options.getUser("user") || interaction.user;
- const member = await interaction.guild.members
- .fetch(targetUser.id)
- .catch(() => null);
-
- if (!member) {
- throw new Error("Could not find the specified user in this server.");
- }
-
- const [userData, levelingConfig] = await Promise.all([
- getUserLevelData(client, interaction.guildId, targetUser.id),
- getLevelingConfig(client, interaction.guildId),
- ]);
-
- if (!levelingConfig?.enabled) {
- throw new Error("The leveling system is currently disabled on this server.");
- }
-
- const safeUserData = {
- level: userData?.level ?? 0,
- xp: userData?.xp ?? 0,
- totalXp: userData?.totalXp ?? 0,
- };
-
- const xpNeeded = getXpForLevel(safeUserData.level + 1);
- const progress = xpNeeded > 0 ? Math.floor((safeUserData.xp / xpNeeded) * 100) : 0;
- const progressBar = createProgressBar(progress, 20);
-
- const embed = new EmbedBuilder()
- .setTitle(`${member.displayName}'s Rank`)
- .setThumbnail(member.displayAvatarURL({ dynamic: true }))
- .addFields(
- {
- name: "Level",
- value: safeUserData.level.toString(),
- inline: true,
- },
- {
- name: "XP",
- value: `${safeUserData.xp}/${xpNeeded}`,
- inline: true,
- },
- {
- name: "Total XP",
- value: safeUserData.totalXp.toString(),
- inline: true,
- },
- {
- name: `Progress to Level ${safeUserData.level + 1}`,
- value: `${progressBar} ${progress}%`,
- },
- )
- .setColor("#3498db")
- .setTimestamp();
-
- await interaction.editReply({ embeds: [embed]
- },
- errorEmbed("Rank Error", "Could not fetch rank information. Try again later.")
- );
- },
-};
-
-function createProgressBar(percentage, length = 10) {
- const filled = Math.round((percentage / 100) * length);
- return "â–ˆ".repeat(filled) + "â–‘".repeat(length - filled);
-}
-
diff --git a/src/commands/Moderation/ban.js b/src/commands/Moderation/ban.js
deleted file mode 100644
index 445af33e1b..0000000000
--- a/src/commands/Moderation/ban.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logModerationAction } from '../../utils/moderation.js';
-import { logger } from '../../utils/logger.js';
-import { InteractionHelper } from '../../utils/interactionHelper.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("ban")
- .setDescription("Ban a user from the server")
- .addUserOption((option) =>
- option
- .setName("target")
- .setDescription("The user to ban")
- .setRequired(true),
- )
- .addStringOption((option) =>
- option.setName("reason").setDescription("Reason for the ban"),
- )
-.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers),
- category: "moderation",
-
- async execute(interaction, config, client) {
- try {
- if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers)) {
- throw new Error("You do not have permission to ban members.");
- }
-
- const user = interaction.options.getUser("target");
- const reason = interaction.options.getString("reason") || "No reason provided";
-
- if (user.id === interaction.user.id) {
- throw new Error("You cannot ban yourself.");
- }
- if (user.id === client.user.id) {
- throw new Error("You cannot ban the bot.");
- }
-
- const targetMember = await interaction.guild.members
- .fetch(user.id)
- .catch(() => null);
-
- if (targetMember && interaction.member.roles.highest.position <= targetMember.roles.highest.position) {
- throw new Error("You cannot ban a user with an equal or higher role than you.");
- }
- if (targetMember && interaction.guild.members.me.roles.highest.position <= targetMember.roles.highest.position) {
- throw new Error("I cannot ban a user with an equal or higher role than me.");
- }
-
- await interaction.guild.members.ban(user, { reason });
-
- await logModerationAction({
- client,
- guild: interaction.guild,
- event: {
- action: "Member Banned",
- target: `${user.tag} (${user.id})`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason,
- metadata: {
- userId: user.id,
- moderatorId: interaction.user.id,
- permanent: true
- }
- }
- });
-
- await InteractionHelper.universalReply(interaction, {
- embeds: [
- successEmbed(
- `🚫 **Banned** ${user.tag}`,
- `**Reason:** ${reason}`,
- ),
- ],
- });
- } catch (error) {
- logger.error('Ban command error:', error);
- const errorMessage = {
- embeds: [errorEmbed("Ban Failed", error.message || "Could not ban that user. They might have a higher role or insufficient permissions.")]
- };
-
- await InteractionHelper.universalReply(interaction, errorMessage);
- }
- },
-};
-
diff --git a/src/commands/Moderation/cases.js b/src/commands/Moderation/cases.js
deleted file mode 100644
index 5375a0d6f5..0000000000
--- a/src/commands/Moderation/cases.js
+++ /dev/null
@@ -1,164 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getModerationCases } from '../../utils/moderation.js';
-import { logger } from '../../utils/logger.js';
-export default {
- data: new SlashCommandBuilder()
- .setName('cases')
- .setDescription('View moderation cases and audit logs')
- .setDefaultMemberPermissions(PermissionFlagsBits.ViewAuditLog)
- .setDMPermission(false)
- .addStringOption(option =>
- option.setName('filter')
- .setDescription('Filter cases by type or user')
- .addChoices(
- { name: 'All Cases', value: 'all' },
- { name: 'Bans', value: 'Member Banned' },
- { name: 'Kicks', value: 'Member Kicked' },
- { name: 'Timeouts', value: 'Member Timed Out' },
- { name: 'Warnings', value: 'User Warned' }
- )
- )
- .addUserOption(option =>
- option.setName('user')
- .setDescription('Filter cases by specific user')
- )
- .addIntegerOption(option =>
- option.setName('limit')
- .setDescription('Number of cases to show (default: 10)')
- .setMinValue(1)
- .setMaxValue(50)
- ),
-
- async execute(interaction, config, client) {
- try {
- const filterType = interaction.options.getString('filter') || 'all';
- const targetUser = interaction.options.getUser('user');
- const limit = interaction.options.getInteger('limit') || 10;
-
- const filters = {
- limit,
- action: filterType === 'all' ? undefined : filterType,
- userId: targetUser?.id
- };
-
- const cases = await getModerationCases(interaction.guild.id, filters);
-
- if (cases.length === 0) {
- throw new Error(targetUser
- ? `No moderation cases found for ${targetUser.tag}`
- : `No ${filterType === 'all' ? '' : filterType} cases found in this server.`
- );
- }
-
- const CASES_PER_PAGE = 5;
- const totalPages = Math.ceil(cases.length / CASES_PER_PAGE);
- let currentPage = 1;
-
- const createCasesEmbed = (page) => {
- const startIndex = (page - 1) * CASES_PER_PAGE;
- const endIndex = startIndex + CASES_PER_PAGE;
- const pageCases = cases.slice(startIndex, endIndex);
-
- const embed = createEmbed({
- title: '📋 Moderation Cases',
- description: `Showing moderation cases for **${interaction.guild.name}**\n\n**Page ${page} of ${totalPages}**`
- });
-
- pageCases.forEach(case_ => {
- const date = new Date(case_.createdAt).toLocaleDateString();
- const time = new Date(case_.createdAt).toLocaleTimeString();
-
- embed.addFields({
- name: `Case #${case_.caseId} - ${case_.action}`,
- value: `**Target:** ${case_.target}\n**Moderator:** ${case_.executor}\n**Date:** ${date} at ${time}\n**Reason:** ${case_.reason || 'No reason provided'}`,
- inline: false
- });
- });
-
- embed.setFooter({
- text: `Total cases: ${cases.length} | Filter: ${filterType}${targetUser ? ` | User: ${targetUser.tag}` : ''}`
- });
-
- return embed;
- };
-
- const createNavigationRow = (page) => {
- const row = new ActionRowBuilder();
-
- const prevButton = new ButtonBuilder()
- .setCustomId('prev_page')
- .setLabel('â¬…ï¸ Previous')
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(page === 1);
-
- const pageInfoButton = new ButtonBuilder()
- .setCustomId('page_info')
- .setLabel(`Page ${page}/${totalPages}`)
- .setStyle(ButtonStyle.Primary)
- .setDisabled(true);
-
- const nextButton = new ButtonBuilder()
- .setCustomId('next_page')
- .setLabel('Next âž¡ï¸')
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(page === totalPages);
-
- row.addComponents(prevButton, pageInfoButton, nextButton);
- return row;
- };
-
- const message = await interaction.editReply({
- embeds: [createCasesEmbed(currentPage)],
- components: [createNavigationRow(currentPage)]
- });
-
- const collector = message.createMessageComponentCollector({
- componentType: ComponentType.Button,
-time: 120000
- });
-
- collector.on('collect', async (buttonInteraction) => {
- await buttonInteraction.deferUpdate();
-
- if (buttonInteraction.user.id !== interaction.user.id) {
- await buttonInteraction.followUp({
- content: 'You cannot use these buttons. Run `/cases` to get your own case view.',
- flags: ["Ephemeral"]
- });
- return;
- }
-
- const { customId } = buttonInteraction;
-
- if (customId === 'prev_page' && currentPage > 1) {
- currentPage--;
- } else if (customId === 'next_page' && currentPage < totalPages) {
- currentPage++;
- }
-
- await buttonInteraction.editReply({
- embeds: [createCasesEmbed(currentPage)],
- components: [createNavigationRow(currentPage)]
- });
- });
-
- collector.on('end', async () => {
- const disabledRow = createNavigationRow(currentPage);
- disabledRow.components.forEach(button => button.setDisabled(true));
-
- try {
- await message.edit({
- components: [disabledRow]
- });
- } catch (error) {
- }
- });
-
- } catch (error) {
- logger.error('Error in cases command:', error);
- throw error;
- }
- }
-};
-
diff --git a/src/commands/Moderation/dm.js b/src/commands/Moderation/dm.js
deleted file mode 100644
index a145b5d48d..0000000000
--- a/src/commands/Moderation/dm.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logEvent } from '../../utils/moderation.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("dm")
- .setDescription("Send a direct message to a user (Staff only)")
- .addUserOption(option =>
- option
- .setName("user")
- .setDescription("The user to send a DM to")
- .setRequired(true)
- )
- .addStringOption(option =>
- option
- .setName("message")
- .setDescription("The message to send")
- .setRequired(true)
- )
- .addBooleanOption(option =>
- option
- .setName("anonymous")
- .setDescription("Send the message anonymously (default: false)")
- .setRequired(false)
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers)
- .setDMPermission(false),
- category: "Moderation",
-
- async execute(interaction, config, client) {
- const targetUser = interaction.options.getUser("user");
- const message = interaction.options.getString("message");
- const anonymous = interaction.options.getBoolean("anonymous") || false;
-
- try {
- const dmChannel = await targetUser.createDM();
-
- await dmChannel.send({
- embeds: [
- successEmbed(
- anonymous ? "Message from the Staff Team" : `Message from ${interaction.user.tag}`,
- message
- ).setFooter({
- text: `You cannot reply to this message.`
- })
- ]
- });
-
- await logEvent({
- client: interaction.client,
- guild: interaction.guild,
- event: {
- action: "DM Sent",
- target: `${targetUser.tag} (${targetUser.id})`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason: `Anonymous: ${anonymous ? 'Yes' : 'No'}\nMessage: ${message}`,
- metadata: {
- userId: targetUser.id,
- moderatorId: interaction.user.id,
- anonymous,
- messageLength: message.length
- }
- }
- });
-
- return await interaction.editReply({
- embeds: [
- successEmbed(
- "DM Sent",
- `Successfully sent a message to ${targetUser.tag}`
- ),
- ],
- });
- } catch (error) {
- console.error("Error in dm command:", error);
-
-if (error.code === 50007) {
- return await interaction.editReply({
- embeds: [
- errorEmbed("Error", `Could not send a DM to ${targetUser.tag}. They may have DMs disabled.`),
- ],
- });
- }
-
- return await interaction.editReply({
- embeds: [
- errorEmbed("Error", `Failed to send DM: ${error.message}`),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Moderation/kick.js b/src/commands/Moderation/kick.js
deleted file mode 100644
index b2b8c75b90..0000000000
--- a/src/commands/Moderation/kick.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logModerationAction } from '../../utils/moderation.js';
-import { logger } from '../../utils/logger.js';
-
-export default {
- data: 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 the kick"),
- )
-.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers),
- category: "moderation",
-
- async execute(interaction, config, client) {
- if (!interaction.member.permissions.has(PermissionFlagsBits.KickMembers))
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You do not have permission to kick members.",
- ),
- ],
- });
-
- const targetUser = interaction.options.getUser("target");
- const member = interaction.options.getMember("target");
- const reason =
- interaction.options.getString("reason") || "No reason provided";
-
- if (targetUser.id === interaction.user.id) {
- return await interaction.editReply({
- embeds: [errorEmbed("You cannot kick yourself.")],
- });
- }
- if (targetUser.id === client.user.id) {
- return await interaction.editReply({
- embeds: [errorEmbed("You cannot kick the bot.")],
- });
- }
-
- if (!member) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Target Not Found",
- "The target user is not currently in this server, and therefore cannot be kicked.",
- ),
- ],
- });
- }
-
- try {
- if (
- interaction.member.roles.highest.position <=
- member.roles.highest.position
- ) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Cannot Kick",
- "You cannot kick a user with an equal or higher role than you.",
- ),
- ],
- });
- }
-
- if (!member.kickable) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Bot Hierarchy Error",
- "I cannot kick this user. Please check my role position relative to the target user.",
- ),
- ],
- });
- }
-
- await member.kick(reason);
-
- const caseId = await logModerationAction({
- client,
- guild: interaction.guild,
- event: {
- action: "Member Kicked",
- target: `${targetUser.tag} (${targetUser.id})`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason,
- metadata: {
- userId: targetUser.id,
- moderatorId: interaction.user.id
- }
- }
- });
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- `👢 **Kicked** ${targetUser.tag}\n**Reason:** ${reason}`,
- ),
- ],
- });
- } catch (error) {
- logger.error("Kick Error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "An unexpected error occurred while trying to kick the user.",
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Moderation/lock.js b/src/commands/Moderation/lock.js
deleted file mode 100644
index f795cee799..0000000000
--- a/src/commands/Moderation/lock.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logEvent } from '../../utils/moderation.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("lock")
- .setDescription(
- "Locks the current channel (prevents @everyone from sending messages).",
- )
-.setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels),
- category: "moderation",
-
- async execute(interaction, config, client) {
-
- if (!interaction.member.permissions.has(PermissionFlagsBits.ManageChannels))
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need the `Manage Channels` permission to lock channels.",
- ),
- ],
- });
-
- const channel = interaction.channel;
- const everyoneRole = interaction.guild.roles.everyone;
-
- try {
- const currentPermissions = channel.permissionsFor(everyoneRole);
- if (currentPermissions.has(PermissionFlagsBits.SendMessages) === false) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Channel Already Locked",
- `${channel} is already locked.`,
- ),
- ],
- });
- }
-
- await channel.permissionOverwrites.edit(
- everyoneRole,
- { SendMessages: false },
-{ type: 0, reason: `Channel locked by ${interaction.user.tag}` },
- );
-
- const lockEmbed = createEmbed(
- "🔒 Channel Locked (Action Log)",
- `${channel} has been locked down by ${interaction.user}.`,
- )
-.setColor("#CC00CC")
- .addFields(
- { name: "Channel", value: channel.toString(), inline: true },
- {
- name: "Moderator",
- value: `${interaction.user.tag} (${interaction.user.id})`,
- inline: true,
- },
- );
-
- await logEvent({
- client,
- guild: interaction.guild,
- event: {
- action: "Channel Locked",
- target: channel.toString(),
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- metadata: {
- channelId: channel.id,
- category: channel.parent?.name || 'None',
- moderatorId: interaction.user.id
- }
- }
- });
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- `🔒 **Channel Locked**`,
- `${channel} is now locked down. No one can speak here now.`,
- ),
- ],
- });
- } catch (error) {
- console.error("Lock Error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "An unexpected error occurred while trying to lock the channel. Check my permissions (I need 'Manage Channels').",
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Moderation/massban.js b/src/commands/Moderation/massban.js
deleted file mode 100644
index 5a571d72e1..0000000000
--- a/src/commands/Moderation/massban.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { logModerationAction } from '../../utils/moderation.js';
-import { logger } from '../../utils/logger.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("massban")
- .setDescription("Ban multiple users from the server at once")
- .addStringOption(option =>
- option
- .setName("users")
- .setDescription("User IDs or mentions to ban (separated by spaces or commas)")
- .setRequired(true)
- )
- .addStringOption(option =>
- option.setName("reason")
- .setDescription("Reason for the mass ban")
- .setRequired(false)
- )
- .addIntegerOption(option =>
- option
- .setName("delete_days")
- .setDescription("Number of days of messages to delete (0-7)")
- .setMinValue(0)
- .setMaxValue(7)
- .setRequired(false)
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers),
- category: "moderation",
-
- async execute(interaction, config, client) {
- if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers)) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You do not have permission to ban members."
- ),
- ],
- });
- }
-
- const usersInput = interaction.options.getString("users");
- const reason = interaction.options.getString("reason") || "Mass ban - No reason provided";
- const deleteDays = interaction.options.getInteger("delete_days") || 0;
-
- try {
- const userIds = usersInput
-.replace(/<@!?(\d+)>/g, '$1')
-.split(/[\s,]+/)
-.filter(id => id && /^\d+$/.test(id))
-.slice(0, 20);
-
- if (userIds.length === 0) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Invalid Users",
- "Please provide valid user IDs or mentions. Maximum 20 users at once."
- ),
- ],
- });
- }
-
- if (userIds.includes(interaction.user.id)) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Cannot Ban Self",
- "You cannot include yourself in a mass ban."
- ),
- ],
- });
- }
-
- if (userIds.includes(client.user.id)) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Cannot Ban Bot",
- "You cannot include the bot in a mass ban."
- ),
- ],
- });
- }
-
- const results = {
- successful: [],
- failed: [],
- skipped: []
- };
-
- for (const userId of userIds) {
- try {
- const user = await client.users.fetch(userId).catch(() => null);
-
- if (!user) {
- results.failed.push({ userId, reason: "User not found" });
- continue;
- }
-
- const member = await interaction.guild.members.fetch(userId).catch(() => null);
-
- if (member) {
- if (member.roles.highest.position >= interaction.member.roles.highest.position &&
- interaction.guild.ownerId !== interaction.user.id) {
- results.skipped.push({
- user: user.tag,
- userId,
- reason: "Cannot ban user with equal or higher role"
- });
- continue;
- }
- }
-
- await interaction.guild.members.ban(userId, {
- reason: reason,
- deleteMessageDays: deleteDays
- });
-
- results.successful.push({
- user: user.tag,
- userId
- });
-
- await logModerationAction({
- client,
- guild: interaction.guild,
- event: {
- action: "Member Banned",
- target: `${user.tag} (${user.id})`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason: `${reason} (Mass Ban)`,
- metadata: {
- userId: user.id,
- moderatorId: interaction.user.id,
- massBan: true,
- permanent: true
- }
- }
- });
-
- } catch (error) {
- logger.error(`Failed to ban user ${userId}:`, error);
- results.failed.push({
- userId,
- reason: error.message || "Unknown error"
- });
- }
- }
-
- let description = `**Mass Ban Results:**\n\n`;
-
- if (results.successful.length > 0) {
- description += `✅ **Successfully Banned (${results.successful.length}):**\n`;
- results.successful.forEach(result => {
- description += `• ${result.user} (${result.userId})\n`;
- });
- description += '\n';
- }
-
- if (results.skipped.length > 0) {
- description += `âš ï¸ **Skipped (${results.skipped.length}):**\n`;
- results.skipped.forEach(result => {
- description += `• ${result.user} - ${result.reason}\n`;
- });
- description += '\n';
- }
-
- if (results.failed.length > 0) {
- description += `⌠**Failed (${results.failed.length}):**\n`;
- results.failed.forEach(result => {
- description += `• ${result.userId} - ${result.reason}\n`;
- });
- }
-
- const embed = results.successful.length > 0 ? successEmbed : warningEmbed;
-
- return await interaction.editReply({
- embeds: [
- embed(
- `🔨 Mass Ban Completed`,
- description
- )
- ]
- });
-
- } catch (error) {
- logger.error("Error in massban command:", error);
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "System Error",
- "An error occurred while processing the mass ban. Please try again later."
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Moderation/masskick.js b/src/commands/Moderation/masskick.js
deleted file mode 100644
index b4d2607dd3..0000000000
--- a/src/commands/Moderation/masskick.js
+++ /dev/null
@@ -1,185 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { logModerationAction } from '../../utils/moderation.js';
-import { logger } from '../../utils/logger.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("masskick")
- .setDescription("Kick multiple users from the server at once")
- .addStringOption(option =>
- option
- .setName("users")
- .setDescription("User IDs or mentions to kick (separated by spaces or commas)")
- .setRequired(true)
- )
- .addStringOption(option =>
- option.setName("reason")
- .setDescription("Reason for the mass kick")
- .setRequired(false)
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers),
- category: "moderation",
-
- async execute(interaction, config, client) {
- if (!interaction.member.permissions.has(PermissionFlagsBits.KickMembers)) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You do not have permission to kick members."
- ),
- ],
- });
- }
-
- const usersInput = interaction.options.getString("users");
- const reason = interaction.options.getString("reason") || "Mass kick - No reason provided";
-
- try {
- const userIds = usersInput
-.replace(/<@!?(\d+)>/g, '$1')
-.split(/[\s,]+/)
-.filter(id => id && /^\d+$/.test(id))
-.slice(0, 20);
-
- if (userIds.length === 0) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Invalid Users",
- "Please provide valid user IDs or mentions. Maximum 20 users at once."
- ),
- ],
- });
- }
-
- if (userIds.includes(interaction.user.id)) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Cannot Kick Self",
- "You cannot include yourself in a mass kick."
- ),
- ],
- });
- }
-
- if (userIds.includes(client.user.id)) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Cannot Kick Bot",
- "You cannot include the bot in a mass kick."
- ),
- ],
- });
- }
-
- const results = {
- successful: [],
- failed: [],
- skipped: []
- };
-
- for (const userId of userIds) {
- try {
- const member = await interaction.guild.members.fetch(userId).catch(() => null);
-
- if (!member) {
- results.failed.push({ userId, reason: "User not in server" });
- continue;
- }
-
- if (member.roles.highest.position >= interaction.member.roles.highest.position &&
- interaction.guild.ownerId !== interaction.user.id) {
- results.skipped.push({
- user: member.user.tag,
- userId,
- reason: "Cannot kick user with equal or higher role"
- });
- continue;
- }
-
- await member.kick(reason);
-
- results.successful.push({
- user: member.user.tag,
- userId
- });
-
- await logModerationAction({
- client,
- guild: interaction.guild,
- event: {
- action: "Member Kicked",
- target: `${member.user.tag} (${member.user.id})`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason: `${reason} (Mass Kick)`,
- metadata: {
- userId: member.user.id,
- moderatorId: interaction.user.id,
- massKick: true
- }
- }
- });
-
- } catch (error) {
- logger.error(`Failed to kick user ${userId}:`, error);
- results.failed.push({
- userId,
- reason: error.message || "Unknown error"
- });
- }
- }
-
- let description = `**Mass Kick Results:**\n\n`;
-
- if (results.successful.length > 0) {
- description += `✅ **Successfully Kicked (${results.successful.length}):**\n`;
- results.successful.forEach(result => {
- description += `• ${result.user} (${result.userId})\n`;
- });
- description += '\n';
- }
-
- if (results.skipped.length > 0) {
- description += `âš ï¸ **Skipped (${results.skipped.length}):**\n`;
- results.skipped.forEach(result => {
- description += `• ${result.user} - ${result.reason}\n`;
- });
- description += '\n';
- }
-
- if (results.failed.length > 0) {
- description += `⌠**Failed (${results.failed.length}):**\n`;
- results.failed.forEach(result => {
- description += `• ${result.userId} - ${result.reason}\n`;
- });
- }
-
- const embed = results.successful.length > 0 ? successEmbed : warningEmbed;
-
- return await interaction.editReply({
- embeds: [
- embed(
- `👢 Mass Kick Completed`,
- description
- )
- ]
- });
-
- } catch (error) {
- logger.error("Error in masskick command:", error);
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "System Error",
- "An error occurred while processing the mass kick. Please try again later."
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Moderation/purge.js b/src/commands/Moderation/purge.js
deleted file mode 100644
index 834ed9a6a3..0000000000
--- a/src/commands/Moderation/purge.js
+++ /dev/null
@@ -1,102 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logEvent } from '../../utils/moderation.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("purge")
- .setDescription("Delete a specific amount of messages")
- .addIntegerOption((option) =>
- option
- .setName("amount")
- .setDescription("Number of messages (1-100)")
- .setRequired(true),
- )
-.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
- category: "moderation",
-
- async execute(interaction, config, client) {
-
- if (!interaction.member.permissions.has(PermissionFlagsBits.ManageMessages))
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need the `Manage Messages` permission to purge messages.",
- ),
- ],
- });
-
- const amount = interaction.options.getInteger("amount");
- const channel = interaction.channel;
-
- if (amount < 1 || amount > 100)
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Invalid Amount",
- "Please specify a number between 1 and 100.",
- ),
- ],
- });
-
- try {
- const fetched = await channel.messages.fetch({ limit: amount });
- const deleted = await channel.bulkDelete(fetched, true);
- const deletedCount = deleted.size;
-
- const purgeEmbed = createEmbed(
- "ðŸ—‘ï¸ Messages Purged (Action Log)",
- `${deletedCount} messages were deleted by ${interaction.user}.`,
- )
-.setColor("#E67E22")
- .addFields(
- { name: "Channel", value: channel.toString(), inline: true },
- {
- name: "Moderator",
- value: `${interaction.user.tag} (${interaction.user.id})`,
- inline: true,
- },
- { name: "Count", value: `${deletedCount} messages`, inline: false },
- );
-
- await logEvent({
- client,
- guild: interaction.guild,
- event: {
- action: "Messages Purged",
- target: `${channel} (${deletedCount} messages)`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason: `Deleted ${deletedCount} messages`,
- metadata: {
- channelId: channel.id,
- messageCount: deletedCount,
- requestedAmount: amount,
- moderatorId: interaction.user.id
- }
- }
- });
-
- await interaction.editReply({
- embeds: [
- successEmbed(`ðŸ—‘ï¸ Deleted ${deletedCount} messages in ${channel}.`),
- ],
-ephemeral: false,
- });
-
- setTimeout(() => interaction.deleteReply().catch(() => {}), 3000);
- } catch (error) {
- console.error("Purge Error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "An unexpected error occurred during message deletion. Note: Messages older than 14 days cannot be bulk deleted.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
- }
-};
-
diff --git a/src/commands/Moderation/timeout.js b/src/commands/Moderation/timeout.js
deleted file mode 100644
index fe55af4322..0000000000
--- a/src/commands/Moderation/timeout.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logModerationAction } from '../../utils/moderation.js';
-import { logger } from '../../utils/logger.js';
-
-
-const durationChoices = [
- { name: "5 minutes", value: 5 },
- { name: "10 minutes", value: 10 },
- { name: "30 minutes", value: 30 },
- { name: "1 hour", value: 60 },
- { name: "6 hours", value: 360 },
- { name: "1 day", value: 1440 },
- { name: "1 week", value: 10080 },
-];
-export default {
- data: new SlashCommandBuilder()
- .setName("timeout")
- .setDescription("Timeout a user for a specific duration.")
- .addUserOption((option) =>
- option
- .setName("target")
- .setDescription("User to timeout")
- .setRequired(true),
- )
- .addIntegerOption(
- (option) =>
- option
- .setName("duration")
- .setDescription("Duration of the timeout")
- .setRequired(true)
-.addChoices(...durationChoices),
- )
- .addStringOption((option) =>
- option.setName("reason").setDescription("Reason for the timeout"),
- )
-.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
- category: "moderation",
-
- async execute(interaction, config, client) {
- if (
- !interaction.member.permissions.has(
- PermissionFlagsBits.ModerateMembers,
- )
- )
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need the `Moderate Members` permission to set a timeout.",
- ),
- ],
- });
-
- const targetUser = interaction.options.getUser("target");
- const member = interaction.options.getMember("target");
- const durationMinutes = interaction.options.getInteger("duration");
- const reason =
- interaction.options.getString("reason") || "No reason provided";
-
- if (targetUser.id === interaction.user.id) {
- return await interaction.editReply({
- embeds: [errorEmbed("You cannot timeout yourself.")],
- });
- }
- if (targetUser.id === client.user.id) {
- return await interaction.editReply({
- embeds: [errorEmbed("You cannot timeout the bot.")],
- });
- }
- if (!member) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Target Not Found",
- "The target user is not currently in this server.",
- ),
- ],
- });
- }
-
- if (!member.moderatable)
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Cannot Timeout",
- "I cannot timeout this user. They might have a higher role than me or you.",
- ),
- ],
- });
-
- try {
- const durationMs = durationMinutes * 60 * 1000;
-
- await member.timeout(durationMs, reason);
-
- const durationDisplay =
- durationChoices.find((c) => c.value === durationMinutes)
- ?.name || `${durationMinutes} minutes`;
-
- const caseId = await logModerationAction({
- client,
- guild: interaction.guild,
- event: {
- action: "Member Timed Out",
- target: `${targetUser.tag} (${targetUser.id})`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason: `${reason}\nDuration: ${durationDisplay}`,
- duration: durationDisplay,
- metadata: {
- userId: targetUser.id,
- moderatorId: interaction.user.id,
- durationMinutes,
- timeoutEnds: new Date(Date.now() + durationMs).toISOString()
- }
- }
- });
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- `â³ **Timed out** ${targetUser.tag} for ${durationDisplay}.`,
- `**Reason:** ${reason}`,
- ),
- ],
- });
- } catch (error) {
- logger.error("Timeout Error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "An unexpected error occurred during the timeout action. Please check my role permissions.",
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Moderation/unban.js b/src/commands/Moderation/unban.js
deleted file mode 100644
index 8872c10b3f..0000000000
--- a/src/commands/Moderation/unban.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { logModerationAction } from '../../utils/moderation.js';
-import { logger } from '../../utils/logger.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("unban")
- .setDescription("Unban a user from the server")
- .addUserOption(option =>
- option
- .setName("target")
- .setDescription("The user to unban (can be ID or mention)")
- .setRequired(true)
- )
- .addStringOption(option =>
- option.setName("reason")
- .setDescription("Reason for the unban")
- .setRequired(false)
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers),
- category: "moderation",
-
- async execute(interaction, config, client) {
- try {
- if (!interaction.member.permissions.has(PermissionFlagsBits.BanMembers)) {
- throw new Error("You do not have permission to unban members.");
- }
-
- const targetUser = interaction.options.getUser("target");
- const reason = interaction.options.getString("reason") || "No reason provided";
-
- const banList = await interaction.guild.bans.fetch();
- const banInfo = banList.get(targetUser.id);
-
- if (!banInfo) {
- throw new Error(`${targetUser.tag} is not currently banned from this server.`);
- }
-
- await interaction.guild.members.unban(targetUser.id, reason);
-
- await logModerationAction({
- client,
- guild: interaction.guild,
- event: {
- action: "Member Unbanned",
- target: `${targetUser.tag} (${targetUser.id})`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason,
- metadata: {
- userId: targetUser.id,
- moderatorId: interaction.user.id
- }
- }
- });
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- "✅ User Unbanned",
- `Successfully unbanned **${targetUser.tag}** from the server.\n\n**Reason:** ${reason}\n**Moderator:** ${interaction.user.tag}`
- )
- ]
- });
- } catch (error) {
- logger.error('Unban command error:', error);
- throw error;
- }
- }
-};
-
diff --git a/src/commands/Moderation/unlock.js b/src/commands/Moderation/unlock.js
deleted file mode 100644
index 5b782f6799..0000000000
--- a/src/commands/Moderation/unlock.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logEvent } from '../../utils/moderation.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("unlock")
- .setDescription(
- "Unlocks the current channel (allows @everyone to send messages again).",
- )
-.setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels),
- category: "moderation",
-
- async execute(interaction, config, client) {
-
- if (
- !interaction.member.permissions.has(
- PermissionFlagsBits.ManageChannels,
- )
- )
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need the `Manage Channels` permission to unlock channels.",
- ),
- ],
- });
-
- const channel = interaction.channel;
- const everyoneRole = interaction.guild.roles.everyone;
-
- try {
- const currentPermissions = channel.permissionsFor(everyoneRole);
- if (
- currentPermissions.has(PermissionFlagsBits.SendMessages) ===
- true ||
- currentPermissions.has(PermissionFlagsBits.SendMessages) ===
- null
- ) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Channel Already Unlocked",
- `${channel} is not explicitly locked (everyone can already send messages).`,
- ),
- ],
- });
- }
-
- await channel.permissionOverwrites.edit(
- everyoneRole,
- { SendMessages: true },
- {
- type: 0,
- reason: `Channel unlocked by ${interaction.user.tag}`,
-},
- );
-
- const unlockEmbed = createEmbed(
- "🔓 Channel Unlocked (Action Log)",
- `${channel} has been unlocked by ${interaction.user}.`,
- )
-.setColor("#2ECC71")
- .addFields(
- {
- name: "Channel",
- value: channel.toString(),
- inline: true,
- },
- {
- name: "Moderator",
- value: `${interaction.user.tag} (${interaction.user.id})`,
- inline: true,
- },
- );
-
- await logEvent({
- client,
- guild: interaction.guild,
- event: {
- action: "Channel Unlocked",
- target: channel.toString(),
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- metadata: {
- channelId: channel.id,
- category: channel.parent?.name || 'None'
- }
- }
- });
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- `🔓 **Channel Unlocked**`,
- `${channel} is now unlocked. You may speak now.`,
- ),
- ],
- });
- } catch (error) {
- console.error("Unlock Error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "An unexpected error occurred while trying to unlock the channel. Check my permissions (I need 'Manage Channels').",
- ),
- ],
- });
- }
- }
-};
-
diff --git a/src/commands/Moderation/untimeout.js b/src/commands/Moderation/untimeout.js
deleted file mode 100644
index d34454db93..0000000000
--- a/src/commands/Moderation/untimeout.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logEvent } from '../../utils/moderation.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("untimeout")
- .setDescription("Remove timeout from a user")
- .addUserOption((option) =>
- option
- .setName("target")
- .setDescription("User to untimeout")
- .setRequired(true),
- )
-.setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
- category: "moderation",
-
- async execute(interaction, config, client) {
- try {
- if (!interaction.member.permissions.has(PermissionFlagsBits.ModerateMembers)) {
- throw new Error("You need the `Moderate Members` permission to remove a timeout.");
- }
-
- const targetUser = interaction.options.getUser("target");
- const member = interaction.options.getMember("target");
-
- if (!member) {
- throw new Error("The target user is not currently in this server.");
- }
-
- if (!member.moderatable) {
- throw new Error("I cannot modify this user. They might have a higher role than me or you.");
- }
-
- if (!member.isCommunicationDisabled()) {
- throw new Error(`${targetUser.tag} is not currently timed out.`);
- }
-
- await member.timeout(null, "Timeout removed by moderator");
-
- await logEvent({
- client,
- guild: interaction.guild,
- event: {
- action: "Member Untimeouted",
- target: `${targetUser.tag} (${targetUser.id})`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- metadata: {
- userId: targetUser.id,
- previousTimeout: member.communicationDisabledUntilTimestamp
- }
- }
- });
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- `🔓 **Removed timeout** from ${targetUser.tag}`,
- ),
- ],
- });
- } catch (error) {
- logger.error('Untimeout command error:', error);
- throw error;
- }
- }
-};
-
diff --git a/src/commands/Moderation/usernotes.js b/src/commands/Moderation/usernotes.js
deleted file mode 100644
index d3cdfd3a25..0000000000
--- a/src/commands/Moderation/usernotes.js
+++ /dev/null
@@ -1,324 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { logger } from '../../utils/logger.js';
-import { getFromDb, setInDb, deleteFromDb } from '../../utils/database.js';
-/**
- * Get user notes key for database
- * @param {string} guildId - Guild ID
- * @param {string} userId - User ID
- * @returns {string} Database key
- */
-function getUserNotesKey(guildId, userId) {
- return `moderation_user_notes_${guildId}_${userId}`;
-}
-
-/**
- * Get all user notes for a guild
- * @param {string} guildId - Guild ID
- * @returns {string} Database key for guild notes list
- */
-function getGuildNotesListKey(guildId) {
- return `moderation_user_notes_list_${guildId}`;
-}
-
-export default {
- data: new SlashCommandBuilder()
- .setName("usernotes")
- .setDescription("Manage user notes for moderation purposes")
- .addSubcommand(subcommand =>
- subcommand
- .setName("add")
- .setDescription("Add a note to a user")
- .addUserOption(option =>
- option
- .setName("target")
- .setDescription("The user to add a note for")
- .setRequired(true)
- )
- .addStringOption(option =>
- option
- .setName("note")
- .setDescription("The note to add")
- .setRequired(true)
- )
- .addStringOption(option =>
- option
- .setName("type")
- .setDescription("Type of note")
- .addChoices(
- { name: "Warning", value: "warning" },
- { name: "Positive", value: "positive" },
- { name: "Neutral", value: "neutral" },
- { name: "Alert", value: "alert" }
- )
- .setRequired(false)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("view")
- .setDescription("View notes for a user")
- .addUserOption(option =>
- option
- .setName("target")
- .setDescription("The user to view notes for")
- .setRequired(true)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("remove")
- .setDescription("Remove a specific note from a user")
- .addUserOption(option =>
- option
- .setName("target")
- .setDescription("The user to remove a note from")
- .setRequired(true)
- )
- .addIntegerOption(option =>
- option
- .setName("index")
- .setDescription("The index of the note to remove")
- .setRequired(true)
- .setMinValue(1)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("clear")
- .setDescription("Clear all notes for a user")
- .addUserOption(option =>
- option
- .setName("target")
- .setDescription("The user to clear notes for")
- .setRequired(true)
- )
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages),
- category: "moderation",
-
- async execute(interaction, config, client) {
- if (!interaction.member.permissions.has(PermissionFlagsBits.ManageMessages)) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You do not have permission to manage user notes."
- ),
- ],
- });
- }
-
- const subcommand = interaction.options.getSubcommand();
- const targetUser = interaction.options.getUser("target");
- const guildId = interaction.guild.id;
-
- if (subcommand !== "view" && subcommand !== "remove" && subcommand !== "clear" && subcommand !== "add") {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Invalid Subcommand",
- "Please select a valid subcommand."
- ),
- ],
- });
- }
-
- let notes = [];
- if (targetUser) {
- const notesKey = getUserNotesKey(guildId, targetUser.id);
- notes = await getFromDb(notesKey, []);
- }
-
- try {
- switch (subcommand) {
- case "add":
- return await handleAddNote(interaction, targetUser, notes, guildId);
- case "view":
- return await handleViewNotes(interaction, targetUser, notes);
- case "remove":
- return await handleRemoveNote(interaction, targetUser, notes, guildId);
- case "clear":
- return await handleClearNotes(interaction, targetUser, notes, guildId);
- default:
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Invalid Subcommand",
- "Please select a valid subcommand."
- ),
- ],
- });
- }
- } catch (error) {
- logger.error(`Error in usernotes command (${subcommand}):`, error);
- return interaction.reply({
- embeds: [
- errorEmbed(
- "System Error",
- "An error occurred while processing your request. Please try again later."
- ),
- ],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-async function handleAddNote(interaction, targetUser, notes, guildId) {
- const note = interaction.options.getString("note");
- const type = interaction.options.getString("type") || "neutral";
-
- if (note.length > 1000) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Note Too Long",
- "Notes must be 1000 characters or less."
- ),
- ],
- });
- }
-
- const noteData = {
- id: Date.now(),
- content: note,
- type: type,
- author: interaction.user.tag,
- authorId: interaction.user.id,
- timestamp: new Date().toISOString()
- };
-
- notes.push(noteData);
-
- const notesKey = getUserNotesKey(guildId, targetUser.id);
- await setInDb(notesKey, notes);
-
- const typeInfo = getNoteTypeInfo(type);
-
- return interaction.reply({
- embeds: [
- successEmbed(
- `${typeInfo.emoji} Note Added`,
- `Added a **${type}** note for **${targetUser.tag}**:\n\n` +
- `> ${note}\n\n` +
- `**Moderator:** ${interaction.user.tag}\n` +
- `**Total Notes:** ${notes.length}`
- )
- ]
- });
-}
-
-async function handleViewNotes(interaction, targetUser, notes) {
- if (notes.length === 0) {
- return interaction.reply({
- embeds: [
- infoEmbed(
- "📠No Notes",
- `There are no notes for **${targetUser.tag}**.`
- ),
- ],
- });
- }
-
- const sortedNotes = [...notes].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
-
- let description = `**Notes for ${targetUser.tag} (${targetUser.id}):**\n\n`;
-
- sortedNotes.forEach((note, index) => {
- const typeInfo = getNoteTypeInfo(note.type);
- const date = new Date(note.timestamp).toLocaleDateString();
- description += `${typeInfo.emoji} **Note #${index + 1}** (${note.type}) - ${date}\n`;
- description += `> ${note.content}\n`;
- description += `*Added by ${note.author}*\n\n`;
- });
-
- if (description.length > 4000) {
- description = description.substring(0, 3900) + "\n... *(truncated)*";
- }
-
- return interaction.reply({
- embeds: [
- infoEmbed(
- `📠User Notes (${notes.length})`,
- description
- )
- ]
- });
-}
-
-async function handleRemoveNote(interaction, targetUser, notes, guildId) {
-const index = interaction.options.getInteger("index") - 1;
-
- if (index < 0 || index >= notes.length) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Invalid Index",
- `Please provide a valid note index (1-${notes.length}).`
- ),
- ],
- });
- }
-
- const removedNote = notes[index];
- notes.splice(index, 1);
-
- const notesKey = getUserNotesKey(guildId, targetUser.id);
- await setInDb(notesKey, notes);
-
- const typeInfo = getNoteTypeInfo(removedNote.type);
-
- return interaction.reply({
- embeds: [
- successEmbed(
- `${typeInfo.emoji} Note Removed`,
- `Removed note #${index + 1} from **${targetUser.tag}**:\n\n` +
- `> ${removedNote.content}\n\n` +
- `**Remaining Notes:** ${notes.length}`
- )
- ]
- });
-}
-
-async function handleClearNotes(interaction, targetUser, notes, guildId) {
- const noteCount = notes.length;
-
- if (noteCount === 0) {
- return interaction.reply({
- embeds: [
- infoEmbed(
- "No Notes to Clear",
- `There are no notes for **${targetUser.tag}** to clear.`
- ),
- ],
- });
- }
-
- notes.length = 0;
-
- const notesKey = getUserNotesKey(guildId, targetUser.id);
- await setInDb(notesKey, notes);
-
- return interaction.reply({
- embeds: [
- successEmbed(
- "ðŸ—‘ï¸ Notes Cleared",
- `Cleared **${noteCount}** notes from **${targetUser.tag}**.`
- )
- ]
- });
-}
-
-function getNoteTypeInfo(type) {
- const types = {
- warning: { emoji: "âš ï¸", color: "#FF6B6B" },
- positive: { emoji: "✅", color: "#51CF66" },
- neutral: { emoji: "ðŸ“", color: "#74C0FC" },
- alert: { emoji: "🚨", color: "#FFD43B" }
- };
-
- return types[type] || types.neutral;
-}
-
-
diff --git a/src/commands/Moderation/warn.js b/src/commands/Moderation/warn.js
deleted file mode 100644
index 45fe2660b7..0000000000
--- a/src/commands/Moderation/warn.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logModerationAction } from '../../utils/moderation.js';
-import { logger } from '../../utils/logger.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("warn")
- .setDescription("Warn a user")
- .addUserOption((o) =>
- o
- .setName("target")
- .setRequired(true)
- .setDescription("User to warn"),
- )
- .addStringOption((o) =>
- o
- .setName("reason")
- .setRequired(true)
- .setDescription("Reason for the warning"),
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
- category: "moderation",
-
- async execute(interaction, config, client) {
- try {
- if (!interaction.member.permissions.has(PermissionFlagsBits.ModerateMembers)) {
- throw new Error("You need the `Moderate Members` permission to issue warnings.");
- }
-
- const target = interaction.options.getUser("target");
- const member = interaction.options.getMember("target");
- const reason = interaction.options.getString("reason");
- const moderator = interaction.user;
- const guildId = interaction.guildId;
-
- if (!member) {
- throw new Error("The target user is not currently in this server.");
- }
-
- const warningsKey = `warnings-${guildId}-${target.id}`;
- const userWarns = await client.db.get(warningsKey);
- const warningsArray = Array.isArray(userWarns) ? userWarns : [];
-
- const newWarning = {
- reason: reason,
- moderatorId: moderator.id,
- date: Date.now(),
- };
- warningsArray.push(newWarning);
- await client.db.set(warningsKey, warningsArray);
-
- const totalWarns = warningsArray.length;
-
- await logModerationAction({
- client,
- guild: interaction.guild,
- event: {
- action: "User Warned",
- target: `${target.tag} (${target.id})`,
- executor: `${moderator.tag} (${moderator.id})`,
- reason,
- metadata: {
- userId: target.id,
- moderatorId: moderator.id,
- totalWarns,
- warningNumber: totalWarns
- }
- }
- });
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- `âš ï¸ **Warned** ${target.tag}`,
- `**Reason:** ${reason}\n**Total Warns:** ${totalWarns}`,
- ),
- ],
- });
- } catch (error) {
- logger.error('Warn command error:', error);
- throw error;
- }
- }
-};
-
diff --git a/src/commands/Moderation/warnings.js b/src/commands/Moderation/warnings.js
deleted file mode 100644
index b5cb354bf0..0000000000
--- a/src/commands/Moderation/warnings.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { logEvent } from '../../utils/moderation.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("warnings")
- .setDescription("View all warnings for a user")
- .addUserOption((o) =>
- o
- .setName("target")
- .setRequired(true)
- .setDescription("User to check warnings for"),
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers),
- category: "moderation",
-
- async execute(interaction, config, client) {
- try {
- const target = interaction.options.getUser("target");
- const guildId = interaction.guildId;
-
- const warningsKey = `warnings-${guildId}-${target.id}`;
- const userWarns = await client.db.get(warningsKey);
- const warningsArray = Array.isArray(userWarns) ? userWarns : [];
- const validWarnings = warningsArray.filter((w) => w && typeof w === 'object' && w.reason && w.moderatorId && w.date);
- const totalWarns = validWarnings.length;
-
- if (totalWarns === 0) {
- await interaction.editReply({
- embeds: [
- createEmbed({
- title: `Warnings: ${target.tag}`,
- description: "✅ This user has no recorded warnings."
- }).setColor("#2ECC71"),
- ],
- });
- return;
- }
-
- const embed = createEmbed({
- title: `Warnings: ${target.tag}`,
- description: `Total Warnings: **${totalWarns}**`
- }).setColor("#F39C12");
-
- const warningFields = validWarnings
- .map((w, i) => {
- const discordTimestamp = Math.floor(w.date / 1000);
- return {
- name: `[#${i + 1}] Reason: ${w.reason.substring(0, 100)}`,
- value: `**Moderator:** <@${w.moderatorId}>\n**Date:** ()`,
- inline: false,
- };
- })
- .slice(0, 25);
-
- embed.addFields(warningFields);
-
- await logEvent({
- client,
- guild: interaction.guild,
- event: {
- action: "Warnings Viewed",
- target: `${target.tag} (${target.id})`,
- executor: `${interaction.user.tag} (${interaction.user.id})`,
- reason: `Viewed ${totalWarns} warnings`,
- metadata: {
- userId: target.id,
- moderatorId: interaction.user.id,
- totalWarnings: totalWarns,
- validWarnings: validWarnings.length
- }
- }
- });
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- logger.error('Warnings command error:', error);
- throw error;
- }
- }
-};
-
diff --git a/src/commands/Reaction_roles/rdelete.js b/src/commands/Reaction_roles/rdelete.js
deleted file mode 100644
index e103fa49de..0000000000
--- a/src/commands/Reaction_roles/rdelete.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-export default {
- data: new SlashCommandBuilder()
- .setName('rdelete')
- .setDescription('Delete a reaction role message')
- .addStringOption(option =>
- option.setName('message_id')
- .setDescription('The ID of the reaction role message to delete')
- .setRequired(true)
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
-
- async execute(interaction) {
- try {
-const messageId = interaction.options.getString('message_id');
- const key = `reaction_roles:${interaction.guildId}:${messageId}`;
-
- const reactionRoleData = await interaction.client.db.get(key);
-
- let actualData;
- if (reactionRoleData && reactionRoleData.ok && reactionRoleData.value) {
- actualData = reactionRoleData.value;
- } else if (reactionRoleData && reactionRoleData.value) {
- actualData = reactionRoleData.value;
- } else {
- actualData = reactionRoleData;
- }
-
- if (!actualData) {
- return interaction.editReply({
- embeds: [errorEmbed('Error', 'No reaction role message found with that ID in this server.')]
- });
- }
-
- try {
- let channel;
- try {
- channel = await interaction.guild.channels.fetch(actualData.channelId);
- } catch (channelError) {
- channel = interaction.guild.channels.cache.get(actualData.channelId);
-
- if (!channel) {
- channel = interaction.client.channels.cache.get(actualData.channelId);
- }
- }
-
- if (channel) {
- const message = await channel.messages.fetch(messageId).catch(() => null);
- if (message) {
- await message.delete().catch(() => null);
- }
- }
- } catch (error) {
- console.error('Error deleting message:', error);
- }
-
- try {
- await interaction.client.db.delete(key);
- } catch (dbError) {
- console.error(`[ReactionRole] Error removing from database:`, dbError);
- return interaction.editReply({
- embeds: [errorEmbed('Database Error', 'Failed to remove reaction role data from database.')]
- });
- }
-
- await interaction.editReply({
- embeds: [successEmbed('Success', 'Reaction role message has been deleted from the database.')]
- });
-
- } catch (error) {
- console.error('Error deleting reaction role:', error);
- interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while deleting the reaction role message.')]
- });
- }
- }
-};
-
diff --git a/src/commands/Reaction_roles/rlist.js b/src/commands/Reaction_roles/rlist.js
deleted file mode 100644
index 3778b9a2e1..0000000000
--- a/src/commands/Reaction_roles/rlist.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, EmbedBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getAllReactionRoleMessages } from '../../services/reactionRoleService.js';
-export default {
- data: new SlashCommandBuilder()
- .setName('rlist')
- .setDescription('List all reaction role messages in this server')
- .setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
-
- async execute(interaction) {
- try {
- const guildReactionRoles = await getAllReactionRoleMessages(interaction.client, interaction.guildId);
-
- if (guildReactionRoles.length === 0) {
- return interaction.reply({
- embeds: [errorEmbed('No Reaction Roles', 'There are no reaction role messages in this server.')]
- });
- }
-
- const embed = new EmbedBuilder()
- .setTitle('Reaction Role Messages')
- .setColor(0x3498DB)
- .setDescription('List of all active reaction role messages:');
-
- for (const rr of guildReactionRoles) {
- const channel = await interaction.guild.channels.fetch(rr.channelId).catch(() => null);
- const message = channel ? await channel.messages.fetch(rr.messageId).catch(() => null) : null;
-
- embed.addFields({
- name: `Message ID: ${rr.messageId}`,
- value: `Channel: ${channel || 'Unknown'}\n` +
- `Message: ${message ? `[Jump to Message](${message.url})` : 'Message not found'}\n` +
- `Roles: ${rr.roles ? Object.keys(rr.roles).length : rr.roles?.length || 0} roles configured`
- });
- }
-
- await interaction.editReply({ embeds: [embed] });
-
- } catch (error) {
- console.error('Error listing reaction roles:', error);
- interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while listing reaction roles.')]
- });
- }
- }
-};
-
diff --git a/src/commands/Reaction_roles/rsetup.js b/src/commands/Reaction_roles/rsetup.js
deleted file mode 100644
index 242a3f53f1..0000000000
--- a/src/commands/Reaction_roles/rsetup.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, ActionRowBuilder, StringSelectMenuBuilder, EmbedBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-export default {
- data: new SlashCommandBuilder()
- .setName('rsetup')
- .setDescription('Set up a reaction role message')
- .addChannelOption(option =>
- option.setName('channel')
- .setDescription('The channel to send the reaction role message to')
- .setRequired(true)
- )
- .addStringOption(option =>
- option.setName('title')
- .setDescription('Title for the reaction role message')
- .setRequired(true)
- )
- .addStringOption(option =>
- option.setName('description')
- .setDescription('Description for the reaction role message')
- .setRequired(true)
- )
- .addRoleOption(option =>
- option.setName('role1')
- .setDescription('First role to add')
- .setRequired(true)
- )
- .addRoleOption(option =>
- option.setName('role2')
- .setDescription('Second role to add')
- .setRequired(false)
- )
- .addRoleOption(option =>
- option.setName('role3')
- .setDescription('Third role to add')
- .setRequired(false)
- )
- .addRoleOption(option =>
- option.setName('role4')
- .setDescription('Fourth role to add')
- .setRequired(false)
- )
- .addRoleOption(option =>
- option.setName('role5')
- .setDescription('Fifth role to add')
- .setRequired(false)
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
-
- async execute(interaction) {
- try {
-const channel = interaction.options.getChannel('channel');
- const title = interaction.options.getString('title');
- const description = interaction.options.getString('description');
-
- const roles = [];
- for (let i = 1; i <= 5; i++) {
- const role = interaction.options.getRole(`role${i}`);
- if (role) {
- if (role.position >= interaction.guild.members.me.roles.highest.position) {
- return interaction.reply({
- embeds: [errorEmbed('Error', `I don't have permission to manage the role ${role.name}. Please move my role higher in the role hierarchy.`)]
- });
- }
- roles.push(role);
- }
- }
-
- if (roles.length < 1) {
- return interaction.editReply({
- embeds: [errorEmbed('Error', 'You must provide at least one role.')]
- });
- }
-
- const row = new ActionRowBuilder().addComponents(
- new StringSelectMenuBuilder()
- .setCustomId('reaction_roles')
- .setPlaceholder('Select your roles')
- .setMinValues(0)
- .setMaxValues(roles.length)
- .addOptions(
- roles.map(role => ({
- label: role.name,
- description: `Add/remove the ${role.name} role`,
- value: role.id,
-emoji: 'ðŸŽ'
- }))
- )
- );
-
- const message = await channel.send({
- embeds: [{
- title,
- description,
- color: 0x3498DB,
- fields: [
- {
- name: 'Available Roles',
- value: roles.map(role => `• ${role}`).join('\n')
- }
- ]
- }],
- components: [row]
- });
-
- const reactionRoleData = {
- guildId: interaction.guildId,
- channelId: channel.id,
- messageId: message.id,
- roles: roles.map(role => role.id)
- };
-
- const key = `reaction_roles:${interaction.guildId}:${message.id}`;
- console.log(`[ReactionRole] Saving reaction role data for message ${message.id}:`, reactionRoleData);
- try {
- await interaction.client.db.set(key, reactionRoleData);
- console.log(`[ReactionRole] Successfully saved reaction role data for message ${message.id}`);
-
- const savedData = await interaction.client.db.get(key);
- console.log(`[ReactionRole] Verified saved data for message ${message.id}:`, savedData);
-
- if (!savedData) {
- throw new Error('Failed to verify saved reaction role data');
- }
- } catch (error) {
- console.error(`[ReactionRole] Error saving reaction role data:`, error);
-throw error;
- }
-
- await interaction.editReply({
- embeds: [successEmbed('Success', `Reaction role message created in ${channel}!`)]
- });
-
- } catch (error) {
- console.error('Error setting up reaction roles:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while setting up reaction roles.')]
- });
- }
- }
-};
-
diff --git a/src/commands/Search/define.js b/src/commands/Search/define.js
deleted file mode 100644
index 47c37b18fc..0000000000
--- a/src/commands/Search/define.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import axios from 'axios';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('define')
- .setDescription('Look up a word definition')
- .addStringOption(option =>
- option.setName('word')
- .setDescription('The word to look up')
- .setRequired(true)),
- async execute(interaction) {
-try {
- const word = interaction.options.getString('word');
-
- if (word.length < 2) {
- return await interaction.reply({
- embeds: [errorEmbed('Error', 'Please enter a word with at least 2 characters.')],
- flags: ["Ephemeral"]
- });
- }
-
- const response = await axios.get(
- `https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(word)}`,
- { timeout: 5000 }
- );
-
- if (!response.data || response.data.length === 0) {
- return await interaction.editReply({
- embeds: [errorEmbed('Not Found', `No definitions found for "${word}".`)]
- });
- }
-
- const data = response.data[0];
- const embed = successEmbed(
- data.word,
- data.phonetic ? `*${data.phonetic}*` : ''
- );
-
- data.meanings.slice(0, 5).forEach(meaning => {
- const definitions = meaning.definitions
- .slice(0, 3)
- .map((def, idx) => {
- let text = `${idx + 1}. ${def.definition}`;
- if (def.example) {
- text += `\n *Example: ${def.example}*`;
- }
- return text;
- })
- .join('\n\n');
-
- if (definitions) {
- embed.addFields({
- name: `**${meaning.partOfSpeech || 'Definition'}**`,
- value: definitions,
- inline: false
- });
- }
- });
-
- embed.setFooter({ text: 'Powered by Free Dictionary API' });
-
- await interaction.editReply({ embeds: [embed] });
-
- } catch (error) {
- console.error('Dictionary lookup error:', error);
-
- let errorMessage = 'Failed to look up the word. ';
- if (error.response) {
- if (error.response.status === 404) {
- errorMessage = `No definitions found for "${interaction.options.getString('word')}".`;
- } else {
- errorMessage += `API Error: ${error.response.status} ${error.response.statusText}`;
- }
- } else if (error.code === 'ECONNABORTED') {
- errorMessage += 'The request timed out. Please try again later.';
- }
-
- await interaction.editReply({
- embeds: [errorEmbed('Error', errorMessage)],
- flags: ["Ephemeral"]
- });
- }
- },
-};
-
diff --git a/src/commands/Search/google.js b/src/commands/Search/google.js
deleted file mode 100644
index 5c31f5927f..0000000000
--- a/src/commands/Search/google.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('google')
- .setDescription('Search Google')
- .addStringOption(option =>
- option.setName('query')
- .setDescription('What would you like to search for?')
- .setRequired(true)),
- async execute(interaction) {
-try {
- const query = interaction.options.getString('query');
- const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}`;
-
- const embed = successEmbed(
- 'Google Search',
- `[Search for "${query}"](${searchUrl})`
- )
- .setFooter({ text: 'Google Search Results' });
-
- await interaction.reply({ embeds: [embed] });
- } catch (error) {
- console.error('Error in google command:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'Failed to process your search. Please try again later.')],
- flags: ["Ephemeral"]
- });
- }
- },
-};
-
diff --git a/src/commands/Search/movie.js b/src/commands/Search/movie.js
deleted file mode 100644
index b78b2eb3d4..0000000000
--- a/src/commands/Search/movie.js
+++ /dev/null
@@ -1,244 +0,0 @@
-import axios from 'axios';
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-const TMDB_API_KEY = process.env.TMDB_API_KEY || '4e44d9029b1270a757cddc766a1bcb63';
- "4e44d9029b1270a757cddc766a1bcb63";
-const IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500";
-const MAX_RESULTS = 5;
-
-export default {
- data: new SlashCommandBuilder()
- .setName("movie")
- .setDescription("Search for a movie or TV show")
- .addStringOption((option) =>
- option
- .setName("title")
- .setDescription("The title of the movie or TV show")
- .setRequired(true)
- .setMaxLength(100),
- )
- .addStringOption((option) =>
- option
- .setName("type")
- .setDescription("Type of content to search for")
- .addChoices(
- { name: "Movie", value: "movie" },
- { name: "TV Show", value: "tv" },
- )
- .setRequired(false),
- ),
- async execute(interaction) {
-try {
- const guildConfig = await getGuildConfig(
- interaction.client,
- interaction.guild?.id,
- );
- if (guildConfig?.disabledCommands?.includes("movie")) {
- return await interaction.reply({
- embeds: [
- errorEmbed(
- "Command Disabled",
- "The movie/TV show search command is disabled in this server.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- if (!TMDB_API_KEY) {
- console.error("TMDB API key is not configured");
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Configuration Error",
- "Movie/TV show search is not properly configured.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
-
- const title = interaction.options.getString("title");
- const type = interaction.options.getString("type") || "movie";
-
-
- const searchResponse = await axios.get(
- `https://api.themoviedb.org/3/search/${type}`,
- {
- params: {
- api_key: TMDB_API_KEY,
- query: title,
- include_adult: guildConfig?.allowNsfwContent
- ? undefined
- : false,
- language: guildConfig?.language || "en-US",
- page: 1,
- region: guildConfig?.region || "US",
- },
-timeout: 8000,
- },
- );
-
- if (!searchResponse.data?.results?.length) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Not Found",
- `No ${type === "movie" ? "movies" : "TV shows"} found for "${title}".`,
- ),
- ],
- });
- }
-
- const result = searchResponse.data.results[0];
- const mediaType = type === "movie" ? "Movie" : "TV Show";
- const mediaTitle = result.title || result.name || "Unknown Title";
- const releaseDate = result.release_date || result.first_air_date;
- const year = releaseDate
- ? new Date(releaseDate).getFullYear()
- : "N/A";
-
- const detailsResponse = await axios.get(
- `https://api.themoviedb.org/3/${type}/${result.id}`,
- {
- params: {
- api_key: TMDB_API_KEY,
- language: guildConfig?.language || "en-US",
- append_to_response:
- "credits,release_dates,content_ratings",
- },
- timeout: 8000,
- },
- );
-
- const details = detailsResponse.data;
- const runtime = details.runtime
- ? `${Math.floor(details.runtime / 60)}h ${details.runtime % 60}m`
- : details.episode_run_time?.[0]
- ? `${details.episode_run_time[0]}m per episode`
- : "N/A";
-
- let contentRating = "N/A";
- if (type === "movie") {
- const usCert = details.release_dates?.results?.find(
- (r) => r.iso_3166_1 === "US",
- );
- if (usCert?.release_dates?.[0]?.certification) {
- contentRating = usCert.release_dates[0].certification;
- }
- } else {
- const usCert = details.content_ratings?.results?.find(
- (r) => r.iso_3166_1 === "US",
- );
- if (usCert?.rating) {
- contentRating = usCert.rating;
- }
- }
-
- const genres =
- details.genres?.map((g) => g.name).join(", ") || "N/A";
-
- const cast =
- details.credits?.cast
- ?.slice(0, 3)
- .map((p) => p.name)
- .join(", ") || "N/A";
-
- const embed = successEmbed(
- details.overview || "No overview available.",
- `${mediaTitle} (${year})`
- )
- .setURL(`https://www.themoviedb.org/${type}/${result.id}`)
- .setThumbnail(
- result.poster_path
- ? `${IMAGE_BASE_URL}${result.poster_path}`
- : null,
- )
- .addFields(
- { name: "Type", value: mediaType, inline: true },
- {
- name: "Rating",
- value: result.vote_average
- ? `â ${result.vote_average.toFixed(1)}/10 (${result.vote_count.toLocaleString()} votes)`
- : "N/A",
- inline: true,
- },
- {
- name: "Content Rating",
- value: contentRating,
- inline: true,
- },
- { name: "Runtime", value: runtime, inline: true },
- {
- name: "Release Date",
- value: releaseDate
- ? new Date(releaseDate).toLocaleDateString()
- : "N/A",
- inline: true,
- },
- { name: "Genres", value: genres, inline: true },
- { name: "Cast", value: cast, inline: false },
- )
- .setFooter({
- text: "Powered by The Movie Database",
- iconURL:
- "https://www.themoviedb.org/assets/2/v4/logos/v2/blue_square_1-5bdc75aaebeb75dc7ae79426ddd9be3b2be1e342510f8202baf6bffa71d7f5c4.svg",
- });
-
- if (result.backdrop_path) {
- embed.setImage(
- `https://image.tmdb.org/t/p/w1280${result.backdrop_path}`,
- );
- }
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Movie/TV show search error:", error);
-
- let errorMessage = "Failed to fetch movie/TV show information. ";
- if (error.code === "ECONNABORTED") {
- errorMessage =
- "The request to TMDB timed out. Please try again later.";
- } else if (error.response?.status === 401) {
- errorMessage =
- "Invalid TMDB API key. Please contact the bot administrator.";
- } else if (error.response?.status === 404) {
- errorMessage =
- "The requested movie/TV show could not be found.";
- } else if (error.response?.status === 429) {
- errorMessage =
- "Too many requests to TMDB. Please try again in a few minutes.";
- } else if (error.response?.data?.status_message) {
- errorMessage += `API Error: ${error.response.data.status_message}`;
- }
-
- await interaction
- .editReply({
- embeds: [errorEmbed("Error", errorMessage)],
- flags: ["Ephemeral"],
- })
- .catch(() => {
- interaction
- .followUp({
- embeds: [errorEmbed("Error", errorMessage)],
- flags: ["Ephemeral"],
- })
- .catch(console.error);
- });
- }
- },
-};
-
-async function getGuildConfig(client, guildId) {
- if (!guildId) return {};
- try {
- const config = await client.db?.get(`guild_${guildId}_config`);
- return config || {};
- } catch (error) {
- console.error("Error fetching guild config:", error);
- return {};
- }
-}
-
diff --git a/src/commands/Search/urban.js b/src/commands/Search/urban.js
deleted file mode 100644
index 3c58c8ec79..0000000000
--- a/src/commands/Search/urban.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import axios from 'axios';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('urban')
- .setDescription('Search Urban Dictionary for definitions')
- .addStringOption(option =>
- option.setName('term')
- .setDescription('The term to look up on Urban Dictionary')
- .setRequired(true)),
-
- async execute(interaction) {
-try {
- const term = interaction.options.getString('term');
-
- if (term.length < 2) {
- return await interaction.reply({
- embeds: [errorEmbed('Error', 'Please enter a term with at least 2 characters.')],
- flags: ["Ephemeral"]
- });
- }
-
- const guildConfig = await getGuildConfig(interaction.client, interaction.guild.id);
- if (guildConfig?.disabledCommands?.includes('urban')) {
- return await interaction.editReply({
- embeds: [errorEmbed('Command Disabled', 'The Urban Dictionary command is disabled in this server.')],
- flags: ["Ephemeral"]
- });
- }
-
- const response = await axios.get(
- `https://api.urbandictionary.com/v0/define?term=${encodeURIComponent(term)}`,
- { timeout: 5000 }
- );
-
- if (!response.data?.list?.length) {
- return await interaction.editReply({
- embeds: [errorEmbed('Not Found', `No definitions found for "${term}" on Urban Dictionary.`)]
- });
- }
-
- const definition = response.data.list[0];
- const cleanDefinition = definition.definition.replace(/\[|\]/g, '');
- const cleanExample = definition.example.replace(/\[|\]/g, '');
-
- const formattedDefinition = cleanDefinition
-.replace(/\n\s*\n/g, '\n\n')
- .slice(0, 2000);
-
- const formattedExample = cleanExample
- ? `*"${cleanExample.replace(/\n/g, ' ').slice(0, 500)}..."*`
- : '*No example provided*';
-
- const embed = successEmbed(
- definition.word,
- formattedDefinition
- )
- .setURL(definition.permalink)
- .addFields(
- {
- name: 'Example',
- value: formattedExample,
- inline: false
- },
- {
- name: 'Stats',
- value: `👠${definition.thumbs_up.toLocaleString()} • 👎 ${definition.thumbs_down.toLocaleString()}`,
- inline: true
- },
- {
- name: 'Author',
- value: definition.author || 'Anonymous',
- inline: true
- }
- )
- .setFooter({
- text: 'Urban Dictionary',
- iconURL: 'https://i.imgur.com/8aQrX3a.png'
- });
-
- await interaction.editReply({ embeds: [embed] });
-
- } catch (error) {
- console.error('Urban Dictionary error:', error);
-
- let errorMessage = 'Failed to fetch from Urban Dictionary. ';
- if (error.code === 'ECONNABORTED') {
- errorMessage = 'The request to Urban Dictionary timed out. Please try again later.';
- } else if (error.response?.status === 429) {
- errorMessage = 'Too many requests to Urban Dictionary. Please try again in a few minutes.';
- }
-
- await interaction.editReply({
- embeds: [errorEmbed('Error', errorMessage)],
- flags: ["Ephemeral"]
- });
- }
- },
-};
-
-async function getGuildConfig(client, guildId) {
- try {
- const config = await client.db.get(`guild_${guildId}_config`);
- return config || {};
- } catch (error) {
- console.error('Error fetching guild config:', error);
- return {};
- }
-}
-
diff --git a/src/commands/Ticket/claim.js b/src/commands/Ticket/claim.js
deleted file mode 100644
index d0f6cc2683..0000000000
--- a/src/commands/Ticket/claim.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { claimTicket } from '../../services/ticket.js';
-import { logEvent } from '../../utils/moderation.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("claim")
- .setDescription("Claims an open ticket, assigning it to you.")
- .setDMPermission(false)
- .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers),
-
- async execute(interaction, guildConfig, client) {
-const channel = interaction.channel;
-
- try {
- const result = await claimTicket(channel, interaction.user);
-
- if (!result.success) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Not a Ticket Channel",
- result.error || "This command can only be used in a valid ticket channel.",
- ),
- ],
- });
- }
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- "Ticket Claimed!",
- "You have successfully claimed this ticket.",
- ),
- ],
- });
-
- const logEmbed = createEmbed({
- title: "✅ Ticket Claimed (Audit Log)",
- description: `${interaction.user} claimed ticket ${channel}.`,
- color: "#00FF00",
- fields: [
- {
- name: "Claimed By",
- value: interaction.user.tag,
- inline: true,
- },
- {
- name: "Channel",
- value: channel.toString(),
- inline: true,
- },
- ]
- });
-
- await logEvent({
- client,
- guildId: interaction.guildId,
- event: {
- action: "Ticket Claimed",
- target: channel.toString(),
- executor: interaction.user.toString()
- }
- });
-
- } catch (error) {
- console.error(`Error claiming ticket ${channel.id}:`, error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "Claim Failed",
- "Could not claim the ticket due to an internal error.",
- ),
- ],
- });
- }
- },
-};
-
diff --git a/src/commands/Ticket/close.js b/src/commands/Ticket/close.js
deleted file mode 100644
index 4aeb3a80db..0000000000
--- a/src/commands/Ticket/close.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { closeTicket } from '../../services/ticket.js';
-import { logEvent } from '../../utils/moderation.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("close")
- .setDescription("Closes the current ticket.")
- .setDMPermission(false)
- .addStringOption((option) =>
- option
- .setName("reason")
- .setDescription("The reason for closing the ticket.")
- .setRequired(false),
- ),
-
- async execute(interaction, guildConfig, client) {
-const channel = interaction.channel;
- const reason =
- interaction.options?.getString("reason") ||
- "Closed via command without a specific reason.";
-
- try {
- const result = await closeTicket(channel, interaction.user, reason);
-
- if (!result.success) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Not a Ticket Channel",
- result.error || "This command can only be used in a valid ticket channel.",
- ),
- ],
- });
- }
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- "Ticket Closed!",
- "This ticket has been closed successfully.",
- ),
- ],
- });
-
- const logEmbed = createEmbed({
- title: "🔒 Ticket Closed (Audit Log)",
- description: `${channel} was closed by ${interaction.user}.`,
- color: "#FF0000",
- fields: [
- {
- name: "Closed By",
- value: interaction.user.tag,
- inline: true,
- },
- { name: "Reason", value: reason, inline: false },
- {
- name: "Channel",
- value: channel.toString(),
- inline: true,
- },
- ]
- });
-
- await logEvent({
- client,
- guildId: interaction.guildId,
- event: {
- action: "Ticket Closed",
- target: channel.toString(),
- executor: interaction.user.toString(),
- reason: reason
- }
- });
-
- } catch (error) {
- console.error(`Error closing ticket ${channel.id}:`, error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "Close Failed",
- "Could not close the ticket due to an internal error.",
- ),
- ],
- });
- }
- },
-};
-
diff --git a/src/commands/Ticket/modules/ticket_limits_check.js b/src/commands/Ticket/modules/ticket_limits_check.js
deleted file mode 100644
index 5d7d7670a1..0000000000
--- a/src/commands/Ticket/modules/ticket_limits_check.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../../utils/embeds.js';
-import { getGuildConfig } from '../../../services/guildConfig.js';
-
-export default {
- async execute(interaction, config, client) {
- const user = interaction.options.getUser('user');
- const guildId = interaction.guild.id;
-
- try {
- const guildConfig = await getGuildConfig(client, guildId);
- const maxTickets = guildConfig.maxTicketsPerUser || 3;
-
- const ticketChannels = interaction.guild.channels.cache.filter(
- channel => channel.name.startsWith('ticket-') &&
- channel.topic &&
- channel.topic.includes(user.id)
- );
-
- const openTicketCount = ticketChannels.size;
-
- const embed = infoEmbed(
- `🎫 Ticket Limit Check: ${user.tag}`,
- `**Open Tickets:** ${openTicketCount}/${maxTickets}\n` +
- `**Remaining:** ${Math.max(0, maxTickets - openTicketCount)}\n\n` +
- (openTicketCount >= maxTickets
- ? 'âš ï¸ This user has reached their ticket limit.'
- : '✅ This user can create more tickets.')
- );
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error('Error checking ticket limits:', error);
- throw new Error('Failed to check ticket limits. Please try again.');
- }
- }
-};
-
diff --git a/src/commands/Ticket/modules/ticket_limits_set.js b/src/commands/Ticket/modules/ticket_limits_set.js
deleted file mode 100644
index 1ec9471c9c..0000000000
--- a/src/commands/Ticket/modules/ticket_limits_set.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../../utils/embeds.js';
-import { getGuildConfig } from '../../../services/guildConfig.js';
-import { getGuildConfigKey } from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
- const maxTickets = interaction.options.getInteger('max_tickets');
- const guildId = interaction.guild.id;
-
- try {
- const guildConfig = await getGuildConfig(client, guildId);
-
- guildConfig.maxTicketsPerUser = maxTickets;
-
- const configKey = getGuildConfigKey(guildId);
- await client.db.set(configKey, guildConfig);
-
- const embed = successEmbed(
- '✅ Ticket Limit Updated',
- `Maximum tickets per user has been set to **${maxTickets}**.\n\n` +
- `Users will now be limited to ${maxTickets} open ticket${maxTickets !== 1 ? 's' : ''} at a time.`
- );
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error('Error setting ticket limits:', error);
- throw new Error('Failed to update ticket limits. Please try again.');
- }
- }
-};
-
diff --git a/src/commands/Ticket/modules/ticket_limits_toggle_dm.js b/src/commands/Ticket/modules/ticket_limits_toggle_dm.js
deleted file mode 100644
index 23ec4f6631..0000000000
--- a/src/commands/Ticket/modules/ticket_limits_toggle_dm.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../../utils/embeds.js';
-import { getGuildConfig } from '../../../services/guildConfig.js';
-import { getGuildConfigKey } from '../../../utils/database.js';
-
-export default {
- async execute(interaction, config, client) {
- const guildId = interaction.guild.id;
-
- try {
- const guildConfig = await getGuildConfig(client, guildId);
-
-const currentSetting = guildConfig.dmOnClose !== false;
- guildConfig.dmOnClose = !currentSetting;
-
- const configKey = getGuildConfigKey(guildId);
- await client.db.set(configKey, guildConfig);
-
- const embed = successEmbed(
- '✅ DM Notification Setting Updated',
- `DM notifications when tickets are closed: **${guildConfig.dmOnClose ? 'Enabled' : 'Disabled'}**\n\n` +
- (guildConfig.dmOnClose
- ? '📬 Users will receive a DM when their ticket is closed.'
- : '📠Users will NOT receive a DM when their ticket is closed.')
- );
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error('Error toggling DM setting:', error);
- throw new Error('Failed to toggle DM setting. Please try again.');
- }
- }
-};
-
diff --git a/src/commands/Ticket/priority.js b/src/commands/Ticket/priority.js
deleted file mode 100644
index f5e247b38f..0000000000
--- a/src/commands/Ticket/priority.js
+++ /dev/null
@@ -1,101 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { updateTicketPriority } from '../../services/ticket.js';
-import { logEvent } from '../../utils/moderation.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("priority")
- .setDescription("Sets the priority level for the current support ticket.")
- .addStringOption((option) =>
- option
- .setName("level")
- .setDescription("The priority level for the ticket.")
- .setRequired(true)
- .addChoices(
- { name: "🔴 Urgent", value: "urgent" },
- { name: "🟠High", value: "high" },
- { name: "🟡 Medium", value: "medium" },
- { name: "🟢 Low", value: "low" },
- { name: "⚪ None", value: "none" },
- ),
- )
- .setDefaultMemberPermissions(PermissionFlagsBits.KickMembers)
- .setDMPermission(false),
- category: "Ticket",
-
- async execute(interaction, guildConfig, client) {
-const priorityLevel = interaction.options.getString("level");
-
- try {
- const result = await updateTicketPriority(interaction.channel, priorityLevel, interaction.user);
-
- if (!result.success) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Not a Ticket Channel",
- result.error || "This command can only be used in a valid ticket channel.",
- ),
- ],
- });
- }
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- "Priority Updated",
- `Ticket priority set to **${priorityLevel.toUpperCase()}**.`,
- ),
- ],
- });
-
- const logEmbed = createEmbed({
- title: "📊 Priority Updated (Audit Log)",
- description: `${interaction.user} updated ticket priority to **${priorityLevel.toUpperCase()}**.`,
- color: "#F39C12",
- fields: [
- {
- name: "Updated By",
- value: interaction.user.tag,
- inline: true,
- },
- {
- name: "Channel",
- value: interaction.channel.toString(),
- inline: true,
- },
- {
- name: "Priority",
- value: priorityLevel.toUpperCase(),
- inline: true,
- },
- ]
- });
-
- await logEvent({
- client,
- guildId: interaction.guildId,
- event: {
- action: "Priority Updated",
- target: interaction.channel.toString(),
- executor: interaction.user.toString(),
- reason: `Priority set to ${priorityLevel.toUpperCase()}`
- }
- });
-
- } catch (error) {
- console.error(`Error setting priority for ticket ${interaction.channel.id}:`, error);
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Action Failed",
- "Could not update ticket priority. Please check the bot's console for details.",
- ),
- ],
- });
- }
- },
-};
-
diff --git a/src/commands/Ticket/ticket.js b/src/commands/Ticket/ticket.js
deleted file mode 100644
index 99e70083bb..0000000000
--- a/src/commands/Ticket/ticket.js
+++ /dev/null
@@ -1,317 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getGuildConfig } from '../../services/guildConfig.js';
-import { logEvent } from '../../utils/moderation.js';
-
-import ticketLimitsSet from './modules/ticket_limits_set.js';
-import ticketLimitsCheck from './modules/ticket_limits_check.js';
-import ticketLimitsToggleDM from './modules/ticket_limits_toggle_dm.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("ticket")
- .setDescription("Manages the server's ticket system.")
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageChannels)
- .addSubcommand((subcommand) =>
- subcommand
- .setName("setup")
- .setDescription(
- "Sets up the ticket creation panel in a specified channel.",
- )
- .addChannelOption((option) =>
- option
-.setName("panel_channel")
- .setDescription(
- "The channel where the ticket panel will be sent.",
- )
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(true),
- )
-
- .addStringOption((option) =>
- option
- .setName("panel_message")
- .setDescription(
- "The main message/description for the ticket panel.",
- )
- .setRequired(true),
- )
- .addStringOption((option) =>
- option
- .setName("button_label")
- .setDescription(
- "The label for the ticket creation button (default: Create Ticket)",
- )
- .setRequired(false),
- )
- .addChannelOption((option) =>
- option
- .setName("category")
- .setDescription(
- "The category where new tickets will be created (optional).",
- )
- .addChannelTypes(ChannelType.GuildCategory)
- .setRequired(false),
- )
- .addChannelOption((option) =>
- option
- .setName("closed_category")
- .setDescription(
- "The category where closed tickets will be moved (optional).",
- )
- .addChannelTypes(ChannelType.GuildCategory)
- .setRequired(false),
- )
- .addRoleOption((option) =>
- option
- .setName("staff_role")
- .setDescription(
- "The role that can access tickets (optional).",
- )
- .setRequired(false),
- )
- .addIntegerOption((option) =>
- option
- .setName("max_tickets_per_user")
- .setDescription("Maximum number of tickets a user can create (default: 3)")
- .setMinValue(1)
- .setMaxValue(10)
- .setRequired(false),
- )
- .addBooleanOption((option) =>
- option
- .setName("dm_on_close")
- .setDescription("Send DM to user when their ticket is closed (default: true)")
- .setRequired(false),
- ),
- )
- .addSubcommandGroup((group) =>
- group
- .setName("limits")
- .setDescription("Manage ticket limits and settings")
- .addSubcommand((subcommand) =>
- subcommand
- .setName("set")
- .setDescription("Set the maximum number of tickets per user")
- .addIntegerOption((option) =>
- option
- .setName("max_tickets")
- .setDescription("Maximum number of tickets a user can create (1-10)")
- .setMinValue(1)
- .setMaxValue(10)
- .setRequired(true)
- )
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("check")
- .setDescription("Check a user's current ticket count")
- .addUserOption((option) =>
- option
- .setName("user")
- .setDescription("The user to check")
- .setRequired(true)
- )
- )
- .addSubcommand((subcommand) =>
- subcommand
- .setName("toggle_dm")
- .setDescription("Toggle DM notifications when tickets are closed")
- )
- ),
- category: "ticket",
-
- async execute(interaction, config, client) {
-
- if (
- !interaction.member.permissions.has(
- PermissionFlagsBits.ManageChannels,
- )
- )
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Permission Denied",
- "You need the `Manage Channels` permission for this action.",
- ),
- ],
- });
-
- const subcommand = interaction.options.getSubcommand();
- const subcommandGroup = interaction.options.getSubcommandGroup();
-
- if (subcommandGroup === "limits") {
- switch (subcommand) {
- case "set":
- return ticketLimitsSet.execute(interaction, config, client);
- case "check":
- return ticketLimitsCheck.execute(interaction, config, client);
- case "toggle_dm":
- return ticketLimitsToggleDM.execute(interaction, config, client);
- default:
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Invalid Subcommand",
- "Please select a valid limits subcommand."
- ),
- ],
- });
- }
- }
-
- if (subcommand === "setup") {
- const panelChannel =
- interaction.options.getChannel("panel_channel");
- const categoryChannel = interaction.options.getChannel("category");
- const closedCategoryChannel = interaction.options.getChannel("closed_category");
- const staffRole = interaction.options.getRole("staff_role");
-const panelMessage = interaction.options.getString("panel_message") || "Click the button below to create a support ticket.";
- const buttonLabel =
- interaction.options.getString("button_label") ||
-"Create Ticket";
- const maxTicketsPerUser = interaction.options.getInteger("max_tickets_per_user") || 3;
-const dmOnClose = interaction.options.getBoolean("dm_on_close") !== false;
-
- const setupEmbed = createEmbed({
- title: "🎫 Support Tickets",
-description: panelMessage,
- color: "#3498DB"
- });
-
- const ticketButton = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId("create_ticket")
-.setLabel(buttonLabel)
- .setStyle(ButtonStyle.Primary)
- .setEmoji("📩"),
- );
-
- try {
- await panelChannel.send({
- embeds: [setupEmbed],
- components: [ticketButton],
- });
-
- if (client.db && interaction.guildId) {
- const currentConfig = await getGuildConfig(
- client,
- interaction.guildId,
- );
- currentConfig.ticketCategoryId = categoryChannel ? categoryChannel.id : null;
- currentConfig.ticketClosedCategoryId = closedCategoryChannel ? closedCategoryChannel.id : null;
- currentConfig.ticketStaffRoleId = staffRole ? staffRole.id : null;
- currentConfig.ticketPanelChannelId = panelChannel.id;
- currentConfig.maxTicketsPerUser = maxTicketsPerUser;
- currentConfig.dmOnClose = dmOnClose;
-
- const { getGuildConfigKey } = await import('../../utils/database.js');
- const configKey = getGuildConfigKey(interaction.guildId);
- await client.db.set(configKey, currentConfig);
- console.log(
- `[DB] Saved ticketCategoryId, ticketClosedCategoryId, ticketStaffRoleId, ticketPanelChannelId, maxTicketsPerUser, and dmOnClose for guild ${interaction.guildId}`,
- );
- }
-
- let successMessage = `The ticket creation panel has been sent to ${panelChannel}. `;
-
- if (categoryChannel) {
- successMessage += `New tickets will be created in the **${categoryChannel.name}** category. `;
- } else {
- successMessage += 'New tickets will be created in a new "Tickets" category. ';
- }
-
- if (closedCategoryChannel) {
- successMessage += `Closed tickets will be moved to **${closedCategoryChannel.name}**. `;
- }
-
- if (staffRole) {
- successMessage += `**${staffRole.name}** role will have access to tickets. `;
- }
-
- successMessage += `\n\n**Max Tickets Per User:** ${maxTicketsPerUser}\n**DM on Close:** ${dmOnClose ? 'Enabled' : 'Disabled'}`;
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- "Ticket Panel Set Up",
- successMessage,
- ),
- ],
- });
-
- const logEmbed = createEmbed({
- title: "🔧 Ticket System Setup (Configuration Log)",
- description: `The ticket panel was set up in ${panelChannel} by ${interaction.user}.`,
- color: "#F39C12"
- })
- .addFields(
- {
- name: "Panel Channel",
- value: panelChannel.toString(),
- inline: true,
- },
- {
- name: "Ticket Category",
- value: categoryChannel
- ? categoryChannel.toString()
- : "None specified.",
- inline: true,
- },
- {
- name: "Closed Category",
- value: closedCategoryChannel
- ? closedCategoryChannel.toString()
- : "None specified.",
- inline: true,
- },
- {
- name: "Staff Role",
- value: staffRole
- ? staffRole.toString()
- : "None specified.",
- inline: true,
- },
- {
- name: "Max Tickets Per User",
- value: maxTicketsPerUser.toString(),
- inline: true,
- },
- {
- name: "DM on Close",
- value: dmOnClose ? 'Enabled' : 'Disabled',
- inline: true,
- },
- {
- name: "Moderator",
- value: `${interaction.user.tag} (${interaction.user.id})`,
- inline: false,
- },
- );
-
- logEvent({
- client,
- guildId: interaction.guildId,
- event: {
- action: "Ticket System Setup",
- target: panelChannel.toString(),
- executor: interaction.user.toString(),
- reason: "Ticket panel configuration"
- }
- });
- } catch (error) {
- console.error("Ticket Setup Error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "Setup Failed",
- "Could not send the ticket panel or save configuration. Check the bot's permissions (especially the ability to send messages in the target channel) and database connection.",
- ),
- ],
- });
- }
- }
- }
-};
-
diff --git a/src/commands/Tools/baseconvert.js b/src/commands/Tools/baseconvert.js
deleted file mode 100644
index 75b9293c7a..0000000000
--- a/src/commands/Tools/baseconvert.js
+++ /dev/null
@@ -1,161 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-const BASE_ALPHABETS = {
- 'BIN': { base: 2, prefix: '0b', name: 'Binary', alphabet: '01' },
- 'OCT': { base: 8, prefix: '0o', name: 'Octal', alphabet: '0-7' },
- 'DEC': { base: 10, prefix: '', name: 'Decimal', alphabet: '0-9' },
- 'HEX': { base: 16, prefix: '0x', name: 'Hexadecimal', alphabet: '0-9A-F' },
- 'B64': { base: 64, prefix: 'b64:', name: 'Base64', alphabet: 'A-Za-z0-9+/=' },
- 'B36': { base: 36, prefix: '', name: 'Base36', alphabet: '0-9A-Z' },
- 'B58': { base: 58, prefix: '', name: 'Base58', alphabet: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' },
- 'B62': { base: 62, prefix: '', name: 'Base62', alphabet: '0-9A-Za-z' },
-};
-
-const BASE_NAMES = Object.entries(BASE_ALPHABETS).map(([key, { name }]) => ({ name: `${key} (${name})`, value: key }));
-
-export default {
- data: new SlashCommandBuilder()
- .setName('baseconvert')
- .setDescription('Convert numbers between different bases')
- .addStringOption(option =>
- option.setName('number')
- .setDescription('The number to convert')
- .setRequired(true))
- .addStringOption(option =>
- option.setName('from')
- .setDescription('Source base/format')
- .setRequired(true)
- .addChoices(...BASE_NAMES))
- .addStringOption(option =>
- option.setName('to')
- .setDescription('Target base/format (default: all)')
- .setRequired(false)
- .addChoices(...BASE_NAMES)),
-
- async execute(interaction) {
-try {
- const numberStr = interaction.options.getString('number').trim();
- const fromBase = interaction.options.getString('from');
- const toBase = interaction.options.getString('to');
-
- const { base: fromBaseValue, prefix: fromPrefix, name: fromName } = BASE_ALPHABETS[fromBase];
-
- const cleanNumber = fromPrefix && numberStr.startsWith(fromPrefix)
- ? numberStr.slice(fromPrefix.length)
- : numberStr;
-
- if (!cleanNumber) {
- return interaction.reply({
- embeds: [errorEmbed('Error', 'Please provide a valid number to convert.')],
- flags: ["Ephemeral"]
- });
- }
-
- const alphabet = BASE_ALPHABETS[fromBase].alphabet;
- const regex = new RegExp(`^[${alphabet}]+$`, 'i');
-
- if (!regex.test(cleanNumber)) {
- return interaction.editReply({
- embeds: [errorEmbed(
- 'Invalid Input',
- `The input is not a valid ${fromName} number. ` +
- `Valid characters for ${fromBase} (${fromName}): ${alphabet}`
- )],
- flags: ["Ephemeral"]
- });
- }
-
- let decimalValue;
- try {
- if (fromBase === 'B64') {
- decimalValue = BigInt(Buffer.from(cleanNumber, 'base64').reduce((a, b) => a * 256n + BigInt(b), 0n));
- } else {
- decimalValue = BigInt(cleanNumber, fromBaseValue);
- }
- } catch (error) {
- return interaction.editReply({
- embeds: [errorEmbed('Conversion Error', 'Failed to parse the input number. It may be too large or invalid.')],
- flags: ["Ephemeral"]
- });
- }
-
- if (toBase) {
- const { base: toBaseValue, prefix: toPrefix, name: toName } = BASE_ALPHABETS[toBase];
- let result;
-
- try {
- if (toBase === 'B64') {
- const bytes = [];
- let n = decimalValue;
- while (n > 0n) {
- bytes.unshift(Number(n & 0xffn));
- n >>= 8n;
- }
- result = Buffer.from(bytes).toString('base64');
- } else {
- result = decimalValue.toString(toBaseValue).toUpperCase();
- }
-
- const embed = successEmbed(
- '🔄 Base Conversion Result',
- `**From ${fromName} (${fromBase}):** \`${fromPrefix}${cleanNumber}\`\n` +
- `**To ${toName} (${toBase}):** \`${toPrefix}${result}\`\n` +
- `**Decimal:** \`${decimalValue.toLocaleString()}\``
- );
-
- await interaction.editReply({ embeds: [embed] });
-
- } catch (error) {
- console.error('Base conversion error:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Conversion Error', `Failed to convert to ${toName}. The number might be too large.`)]
- });
- }
-
- } else {
- let description = `**Input (${fromName}):** \`${fromPrefix}${cleanNumber}\`\n`;
- description += `**Decimal:** \`${decimalValue.toLocaleString()}\`\n\n`;
-
- for (const [baseKey, { base, prefix, name }] of Object.entries(BASE_ALPHABETS)) {
- if (baseKey === fromBase) continue;
-
- try {
- let value;
- if (baseKey === 'B64') {
- const bytes = [];
- let n = decimalValue;
- while (n > 0n) {
- bytes.unshift(Number(n & 0xffn));
- n >>= 8n;
- }
- value = Buffer.from(bytes).toString('base64');
- } else {
- value = decimalValue.toString(base).toUpperCase();
- }
-
- description += `**${name} (${baseKey}):** \`${prefix}${value}\`\n`;
- } catch (error) {
- description += `**${name} (${baseKey}):** *Too large to convert*\n`;
- }
- }
-
- const embed = successEmbed(
- '🔄 Base Conversion Results',
- description
- );
-
- await interaction.editReply({ embeds: [embed] });
- }
-
- } catch (error) {
- console.error('Baseconvert command error:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while converting the number. Please try again.')],
- flags: ["Ephemeral"]
- });
- }
- },
-};
-
diff --git a/src/commands/Tools/calculate.js b/src/commands/Tools/calculate.js
deleted file mode 100644
index cea050899b..0000000000
--- a/src/commands/Tools/calculate.js
+++ /dev/null
@@ -1,426 +0,0 @@
-import { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-function evaluate(expression) {
- let expr = expression.replace(/\s/g, '').toLowerCase();
-
- const math = {
- sin: Math.sin,
- cos: Math.cos,
- tan: Math.tan,
- sqrt: Math.sqrt,
- abs: Math.abs,
- log: Math.log,
- log10: Math.log10,
- exp: Math.exp,
- pi: Math.PI,
- e: Math.E
- };
-
- expr = expr.replace(/sin|cos|tan|sqrt|abs|log|log10|exp|pi|e/g, (match) => `math.${match}`);
-
- expr = expr.replace(/(\d+)\s*deg/g, (match, num) => `(${num} * Math.PI / 180)`);
-
- expr = expr.replace(/\^/g, '**');
-
- try {
- const func = new Function('math', `return ${expr}`);
- return func(math);
- } catch (error) {
- throw new Error(`Invalid expression: ${error.message}`);
- }
-}
-
-const calculationHistory = new Map();
-const MAX_HISTORY = 5;
-
-export default {
- data: new SlashCommandBuilder()
- .setName("calculate")
- .setDescription("Evaluate a mathematical expression")
- .addStringOption((option) =>
- option
- .setName("expression")
- .setDescription(
- "The mathematical expression to evaluate (e.g., 2+2*3, sin(45 deg), 16^0.5)",
- )
- .setRequired(true),
- ),
-
- async execute(interaction) {
-try {
-
- const expression = interaction.options.getString("expression");
-
- if (
- !/^[0-9+\-*/.()^%! ,<>=&|~?:\[\]{}a-z√π∞°]+$/i.test(expression)
- ) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- "Invalid Expression",
- "The expression contains invalid characters. " +
- "Only basic math operations, numbers, and common functions are allowed.",
- ),
- ],
- });
- }
-
- const dangerousPatterns = [
- /\b(?:import|require|process|fs|child_process|exec|eval|Function|setTimeout|setInterval|new\s+Function)\s*\(/i,
- /`/g, // Template literals
-/\$\{.*\}/,
- /\b(?:localStorage|document|window|fetch|XMLHttpRequest)\b/,
- /\b(?:while|for)\s*\([^)]*\)\s*\{/,
- /\b(?:function\*|yield|await|async)\b/,
- ];
-
- for (const pattern of dangerousPatterns) {
- if (pattern.test(expression)) {
- return interaction.editReply({
- embeds: [
- errorEmbed(
- "Security Alert",
- "The expression contains potentially dangerous code and cannot be evaluated.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
- }
-
- let result;
- try {
- result = evaluate(expression);
-
- let formattedResult;
- if (typeof result === "number") {
- formattedResult = result.toLocaleString("en-US", {
- maximumFractionDigits: 10,
- });
-
- if (
- Math.abs(result) > 0 &&
- (Math.abs(result) >= 1e10 || Math.abs(result) < 1e-3)
- ) {
- formattedResult = result.toExponential(6);
- }
- } else if (typeof result === "boolean") {
- formattedResult = result ? "true" : "false";
- } else if (result === null || result === undefined) {
- formattedResult = "No result";
- } else if (
- Array.isArray(result) ||
- typeof result === "object"
- ) {
- formattedResult =
- "```json\n" + JSON.stringify(result, null, 2) + "\n```";
- } else {
- formattedResult = String(result);
- }
-
- const userId = interaction.user.id;
- if (!calculationHistory.has(userId)) {
- calculationHistory.set(userId, []);
- }
-
- const history = calculationHistory.get(userId);
- history.unshift({
- expression,
- result: formattedResult,
- timestamp: Date.now(),
- });
-
- if (history.length > MAX_HISTORY) {
- history.pop();
- }
-
- const row = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`calc_${interaction.id}_add`)
- .setLabel("+")
- .setStyle(ButtonStyle.Primary),
- new ButtonBuilder()
- .setCustomId(`calc_${interaction.id}_subtract`)
- .setLabel("-")
- .setStyle(ButtonStyle.Primary),
- new ButtonBuilder()
- .setCustomId(`calc_${interaction.id}_multiply`)
- .setLabel("×")
- .setStyle(ButtonStyle.Primary),
- new ButtonBuilder()
- .setCustomId(`calc_${interaction.id}_divide`)
- .setLabel("÷")
- .setStyle(ButtonStyle.Primary),
- new ButtonBuilder()
- .setCustomId(`calc_${interaction.id}_history`)
- .setLabel("History")
- .setStyle(ButtonStyle.Secondary),
- );
-
- const embed = successEmbed(
- "🧮 Calculation Result",
- `**Expression:** \`${expression.replace(/`/g, "\`")}\`\n` +
- `**Result:** \`${formattedResult}\`\n\n` +
- `*Use the buttons below to perform operations with the result.*`,
- );
-
- await interaction.editReply({
- embeds: [embed],
- components: [row],
- });
-
- const filter = (i) =>
- i.customId.startsWith(`calc_${interaction.id}`) &&
- i.user.id === interaction.user.id;
-const BUTTON_TIMEOUT = 300000;
- const collector =
- interaction.channel.createMessageComponentCollector({
- filter,
- time: BUTTON_TIMEOUT,
- });
-
- collector.on("collect", async (i) => {
- try {
- const operation = i.customId.split("_")[2];
-
- if (operation === "history") {
- if (!i.deferred && !i.replied) {
- await i.deferUpdate().catch(console.error);
- }
-
- const userHistory =
- calculationHistory.get(userId) || [];
-
- if (userHistory.length === 0) {
- await i.followUp({
- content: "No calculation history found.",
- flags: ["Ephemeral"],
- });
- return;
- }
-
- const historyText = userHistory
- .map(
- (item, index) =>
- `${index + 1}. **${item.expression}** = \`${item.result}\`\n` +
- ` `,
- )
- .join("\n\n");
-
- await i.followUp({
- content: `📜 **Your Calculation History**\n\n${historyText}`,
- flags: ["Ephemeral"],
- });
- return;
- }
-
- let operator = "";
-
- switch (operation) {
- case "add":
- operator = "+";
- break;
- case "subtract":
- operator = "-";
- break;
- case "multiply":
- operator = "*";
- break;
- case "divide":
- operator = "/";
- break;
- }
-
- try {
- await i.showModal({
- customId: `calc_modal_${i.user.id}_${operation}`,
- title: `Enter a number to ${operation}`,
- components: [
- {
- type: 1,
- components: [
- {
- type: 4,
- customId: "operand",
- label: `Number to ${operator} with ${formattedResult}`,
- placeholder:
- "Enter a number...",
- style: 1,
- required: true,
- maxLength: 50,
- },
- ],
- },
- ],
- });
- } catch (modalError) {
- console.error("Failed to show modal:", modalError);
- if (!i.replied) {
- await i
- .reply({
- content:
- "Failed to open calculator. Please try again.",
- flags: ["Ephemeral"],
- })
- .catch(console.error);
- }
- return;
- }
-
- try {
- const modalResponse = await i.awaitModalSubmit({
- filter: (m) =>
- m.customId ===
- `calc_modal_${i.user.id}_${operation}`,
-time: 300000,
- });
-
- await modalResponse.deferUpdate();
-
- const operand =
- modalResponse.fields.getTextInputValue(
- "operand",
- );
- const newExpression = `(${expression}) ${operator} (${operand})`;
-
- let newResult;
- try {
- newResult = evaluate(newExpression);
-
- let formattedNewResult;
- if (typeof newResult === "number") {
- formattedNewResult = newResult.toLocaleString("en-US", {
- maximumFractionDigits: 10,
- });
-
- if (
- Math.abs(newResult) > 0 &&
- (Math.abs(newResult) >= 1e10 || Math.abs(newResult) < 1e-3)
- ) {
- formattedNewResult = newResult.toExponential(6);
- }
- } else {
- formattedNewResult = String(newResult);
- }
-
- const updatedEmbed = successEmbed(
- "🧮 Calculation Result",
- `**Expression:** \`${newExpression.replace(/`/g, "\`")}\`\n` +
- `**Result:** \`${formattedNewResult}\`\n\n` +
- `*Use the buttons below to perform operations with the result.*`,
- );
-
- await modalResponse.editReply({
- embeds: [updatedEmbed],
- });
-
- } catch (calcError) {
- await modalResponse.followUp({
- embeds: [errorEmbed("Calculation Error", "Failed to evaluate the new expression.")],
- flags: ["Ephemeral"],
- });
- }
- } catch (error) {
- console.error("Modal error:", error);
- if (!i.deferred && !i.replied) {
- await i
- .followUp({
- content:
- "An error occurred while processing your input.",
- flags: ["Ephemeral"],
- })
- .catch(console.error);
- }
- }
- } catch (error) {
- console.error("Button interaction error:", error);
- if (!i.deferred && !i.replied) {
- await i
- .followUp({
- content:
- "An error occurred while processing your request.",
- flags: ["Ephemeral"],
- })
- .catch(console.error);
- }
- }
- });
-
- collector.on("end", (collected, reason) => {
- if (reason === "timeout") {
- const disabledRow =
- new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(
- `calc_${interaction.id}_expired`,
- )
- .setLabel("Calculator Expired")
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(true),
- );
-
- interaction
- .editReply({
- components: [disabledRow],
- content:
- "â±ï¸ This calculator has expired. Use the command again to perform more calculations.",
- })
- .catch(console.error);
- } else {
- const disabledRow = ActionRowBuilder.from(
- row,
- ).setComponents(
- row.components.map((component) =>
- ButtonBuilder.from(component).setDisabled(true),
- ),
- );
-
- interaction
- .editReply({ components: [disabledRow] })
- .catch(console.error);
- }
- });
- } catch (error) {
- console.error("Calculation error:", error);
-
- let errorMessage = "Failed to evaluate the expression. ";
-
- if (error.message.includes("Unexpected type")) {
- errorMessage +=
- "The expression contains an unsupported operation or function.";
- } else if (error.message.includes("Undefined symbol")) {
- errorMessage +=
- "The expression contains an undefined variable or function.";
- } else if (error.message.includes("Brackets not balanced")) {
- errorMessage += "The expression has unbalanced brackets.";
- } else if (
- error.message.includes("Unexpected operator") ||
- error.message.includes("Unexpected character")
- ) {
- errorMessage +=
- "The expression contains an invalid operator or character.";
- } else {
- errorMessage += "Please check the syntax and try again.";
- }
-
- await interaction.editReply({
- embeds: [errorEmbed("Calculation Error", errorMessage)],
- });
- }
- } catch (error) {
- console.error("Calculate command error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "Error",
- "An error occurred while processing your calculation.",
- ),
- ],
- flags: ["Ephemeral"],
- });
- }
- },
-};
-
diff --git a/src/commands/Tools/countdown.js b/src/commands/Tools/countdown.js
deleted file mode 100644
index 9eb9d98ddc..0000000000
--- a/src/commands/Tools/countdown.js
+++ /dev/null
@@ -1,375 +0,0 @@
-import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-const activeCountdowns = new Map();
-
-const createControlButtons = (countdownId, isPaused = false) => {
- return new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`countdown_pause_${countdownId}`)
- .setLabel(isPaused ? "â–¶ï¸ Resume" : "â¸ï¸ Pause")
- .setStyle(ButtonStyle.Secondary),
- new ButtonBuilder()
- .setCustomId(`countdown_cancel_${countdownId}`)
- .setLabel("⌠Cancel")
- .setStyle(ButtonStyle.Danger),
- );
-};
-export default {
- data: new SlashCommandBuilder()
- .setName("countdown")
- .setDescription("Start a countdown timer")
- .addIntegerOption((option) =>
- option
- .setName("minutes")
- .setDescription("Number of minutes to count down (0-1440)")
- .setMinValue(0)
- .setMaxValue(1440)
- .setRequired(false),
- )
- .addIntegerOption((option) =>
- option
- .setName("seconds")
- .setDescription("Number of seconds to count down (0-59)")
- .setMinValue(0)
- .setMaxValue(59)
- .setRequired(false),
- )
- .addStringOption((option) =>
- option
- .setName("title")
- .setDescription("Optional title for the countdown")
- .setRequired(false),
- ),
-
- async execute(interaction) {
- try {
- const minutes = interaction.options.getInteger("minutes") || 0;
- const seconds = interaction.options.getInteger("seconds") || 0;
- const title = interaction.options.getString("title") || "Countdown Timer";
-
- const totalSeconds = minutes * 60 + seconds;
-
- if (totalSeconds <= 0) {
- throw new Error("Please specify a duration of at least 1 second.");
- }
-
- if (totalSeconds > 86400) {
- throw new Error("Countdown cannot be longer than 24 hours.");
- }
-
- const endTime = Date.now() + totalSeconds * 1000;
- const countdownId = `${interaction.channelId}-${Date.now()}`;
-
- const row = createControlButtons(countdownId);
-
- const initialEmbed = successEmbed(
- `â±ï¸ ${title}`,
- `Time remaining: **${formatTime(totalSeconds)}**`,
- );
-
- const message = await interaction.channel.send({
- embeds: [initialEmbed],
- components: [row],
- });
-
- const countdownData = {
- message,
- endTime,
- remainingTime: totalSeconds * 1000,
- isPaused: false,
- title,
- lastUpdate: Date.now(),
- interval: null,
- };
-
- startCountdown(countdownId, countdownData);
-
- activeCountdowns.set(countdownId, countdownData);
-
- const filter = (i) => {
- return i.customId.startsWith('countdown_') &&
- i.customId.endsWith(countdownId) &&
- i.user.id === interaction.user.id;
- };
-
- const collector = message.createMessageComponentCollector({
- filter,
- time: totalSeconds * 1000 + 60000
- });
-
- collector.on('collect', async (i) => {
- const [action, id] = i.customId.split('_').slice(1);
-
- if (id !== countdownId) return;
-
- const countdownData = activeCountdowns.get(countdownId);
- if (!countdownData) {
- await i.reply({
- content: "This countdown has expired or was cancelled.",
- flags: ["Ephemeral"],
- });
- return;
- }
-
- if (!i.member.permissions.has("MANAGE_MESSAGES")) {
- await i.reply({
- content: 'You need the "Manage Messages" permission to control countdowns.',
- flags: ["Ephemeral"],
- });
- return;
- }
-
- switch (action) {
- case "pause":
- if (countdownData.isPaused) {
- countdownData.isPaused = false;
- countdownData.endTime = Date.now() + countdownData.remainingTime;
- startCountdown(countdownId, countdownData);
-
- const currentEmbed = countdownData.message.embeds[0];
- await countdownData.message.edit({
- embeds: [currentEmbed],
- components: [createControlButtons(countdownId, false)],
- });
-
- await i.reply({
- content: "â–¶ï¸ Countdown resumed!",
- flags: ["Ephemeral"],
- });
- } else {
- clearInterval(countdownData.interval);
- countdownData.isPaused = true;
- countdownData.remainingTime = countdownData.endTime - Date.now();
-
- const currentEmbed = countdownData.message.embeds[0];
- await countdownData.message.edit({
- embeds: [currentEmbed],
- components: [createControlButtons(countdownId, true)],
- });
-
- await i.reply({
- content: "â¸ï¸ Countdown paused!",
- flags: ["Ephemeral"],
- });
- }
- break;
-
- case "cancel":
- clearInterval(countdownData.interval);
-
- const embed = successEmbed(
- `â±ï¸ ${countdownData.title} (Cancelled)`,
- "The countdown was cancelled.",
- );
-
- await countdownData.message.edit({
- embeds: [embed],
- components: [],
- });
-
- cleanupCountdown(countdownId);
-
- await i.reply({
- content: "⌠Countdown cancelled!",
- flags: ["Ephemeral"],
- });
- break;
- }
- });
-
- collector.on("end", () => {
- cleanupCountdown(countdownId);
- });
-
- await interaction.reply({
- content: "✅ Countdown started!",
- flags: MessageFlags.Ephemeral,
- });
- } catch (error) {
- console.error("Countdown command error:", error);
- const replyMethod = interaction.replied || interaction.deferred ? 'editReply' : 'reply';
- await interaction[replyMethod]({
- embeds: [
- errorEmbed(
- "Error",
- "Failed to start the countdown. Please try again.",
- ),
- ],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
-export async function handleCountdownInteraction(interaction) {
- if (!interaction.isButton()) return false;
-
- const [action, countdownId] = interaction.customId.split("_").slice(1);
- const countdownData = activeCountdowns.get(countdownId);
-
- if (!countdownData) {
- await interaction.editReply({
- content: "This countdown has expired or was cancelled.",
- flags: ["Ephemeral"],
- });
- return true;
- }
-
- if (!interaction.member.permissions.has("MANAGE_MESSAGES")) {
- await interaction.editReply({
- content:
- 'You need the "Manage Messages" permission to control countdowns.',
- flags: ["Ephemeral"],
- });
- return true;
- }
-
- switch (action) {
- case "pause":
- if (countdownData.isPaused) {
- countdownData.isPaused = false;
- countdownData.endTime =
- Date.now() + countdownData.remainingTime;
- startCountdown(countdownId, countdownData);
-
- const currentEmbed = countdownData.message.embeds[0];
- await countdownData.message.edit({
- embeds: [currentEmbed],
- components: [createControlButtons(countdownId, false)],
- });
-
- await interaction.editReply({
- content: "â–¶ï¸ Countdown resumed!",
- flags: ["Ephemeral"],
- });
- } else {
- clearInterval(countdownData.interval);
- countdownData.isPaused = true;
- countdownData.remainingTime =
- countdownData.endTime - Date.now();
-
- const currentEmbed = countdownData.message.embeds[0];
- await countdownData.message.edit({
- embeds: [currentEmbed],
- components: [createControlButtons(countdownId, true)],
- });
-
- await interaction.editReply({
- content: "â¸ï¸ Countdown paused!",
- flags: ["Ephemeral"],
- });
- }
- break;
-
- case "cancel":
- clearInterval(countdownData.interval);
-
- const embed = successEmbed(
- `â±ï¸ ${countdownData.title} (Cancelled)`,
- "The countdown was cancelled.",
- );
-
- await countdownData.message.edit({
- embeds: [embed],
- components: [],
- });
-
- cleanupCountdown(countdownId);
-
- await interaction.editReply({
- content: "⌠Countdown cancelled!",
- flags: ["Ephemeral"],
- });
- break;
- }
-
- return true;
-}
-
-function startCountdown(countdownId, countdownData) {
- if (countdownData.interval) {
- clearInterval(countdownData.interval);
- countdownData.interval = null;
- }
-
- console.log("Starting countdown with data:", {
- endTime: new Date(countdownData.endTime).toISOString(),
- remaining: countdownData.endTime - Date.now(),
- });
-
- countdownData.interval = setInterval(async () => {
- try {
- if (countdownData.isPaused) return;
-
- const now = Date.now();
- const remaining = Math.max(0, countdownData.endTime - now);
- countdownData.remainingTime = remaining;
-
- if (now - countdownData.lastUpdate >= 1000) {
- countdownData.lastUpdate = now;
-
- const embed = successEmbed(
- `â±ï¸ ${countdownData.title}`,
- `Time remaining: **${formatTime(Math.ceil(remaining / 1000))}**`,
- );
-
- try {
- await countdownData.message.edit({
- embeds: [embed],
- components: [
- createControlButtons(
- countdownId,
- countdownData.isPaused,
- ),
- ],
- });
- } catch (error) {
- console.error("Error updating countdown message:", error);
- }
- }
-
- if (remaining <= 0) {
- clearInterval(countdownData.interval);
-
- const finishedEmbed = successEmbed(
- `â±ï¸ ${countdownData.title} (Finished!)`,
- "â° Time's up!",
- );
-
- await countdownData.message.edit({
- embeds: [finishedEmbed],
- components: [],
- });
-
- cleanupCountdown(countdownId);
- }
- } catch (error) {
- console.error("Countdown update error:", error);
- cleanupCountdown(countdownId);
- }
- }, 100);
-}
-
-function cleanupCountdown(countdownId) {
- const countdownData = activeCountdowns.get(countdownId);
- if (countdownData) {
- clearInterval(countdownData.interval);
- activeCountdowns.delete(countdownId);
- }
-}
-
-function formatTime(seconds) {
- const h = Math.floor(seconds / 3600);
- const m = Math.floor((seconds % 3600) / 60);
- const s = seconds % 60;
-
- return [
- h > 0 ? h.toString().padStart(2, "0") : null,
- m.toString().padStart(2, "0"),
- s.toString().padStart(2, "0"),
- ]
- .filter(Boolean)
- .join(":");
-}
-
diff --git a/src/commands/Tools/generatepassword.js b/src/commands/Tools/generatepassword.js
deleted file mode 100644
index 0cb0b2c2c4..0000000000
--- a/src/commands/Tools/generatepassword.js
+++ /dev/null
@@ -1,142 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-export default {
- data: new SlashCommandBuilder()
- .setName('generatepassword')
- .setDescription('Generate a strong, random password')
- .addIntegerOption(option =>
- option.setName('length')
- .setDescription('Password length (default: 16, max: 50)')
- .setMinValue(8)
- .setMaxValue(50)
- .setRequired(false))
- .addBooleanOption(option =>
- option.setName('uppercase')
- .setDescription('Include uppercase letters (A-Z)')
- .setRequired(false))
- .addBooleanOption(option =>
- option.setName('numbers')
- .setDescription('Include numbers (0-9)')
- .setRequired(false))
- .addBooleanOption(option =>
- option.setName('symbols')
- .setDescription('Include symbols (!@#$%^&*)')
- .setRequired(false)),
-
- async execute(interaction) {
- try {
- const length = interaction.options.getInteger('length') || 16;
- const includeUppercase = interaction.options.getBoolean('uppercase') ?? true;
- const includeNumbers = interaction.options.getBoolean('numbers') ?? true;
- const includeSymbols = interaction.options.getBoolean('symbols') ?? true;
-
- if (length < 8 || length > 50) {
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Password length must be between 8 and 50 characters.')],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- const lowercase = 'abcdefghijklmnopqrstuvwxyz';
- const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
- const numbers = '0123456789';
- const symbols = '!@#$%^&*()_+-=[]{}|;:,.<>?';
-
- let chars = lowercase;
- if (includeUppercase) chars += uppercase;
- if (includeNumbers) chars += numbers;
- if (includeSymbols) chars += symbols;
-
- let password = '';
- const randomValues = new Uint32Array(length);
- crypto.getRandomValues(randomValues);
-
- for (let i = 0; i < length; i++) {
- const randomIndex = randomValues[i] % chars.length;
- password += chars[randomIndex];
- }
-
- if (includeUppercase && !/[A-Z]/.test(password)) {
- const randomIndex = Math.floor(Math.random() * length);
- const randomUpper = uppercase[Math.floor(Math.random() * uppercase.length)];
- password = password.substring(0, randomIndex) + randomUpper + password.substring(randomIndex + 1);
- }
-
- if (includeNumbers && !/[0-9]/.test(password)) {
- const randomIndex = Math.floor(Math.random() * length);
- const randomNumber = numbers[Math.floor(Math.random() * numbers.length)];
- password = password.substring(0, randomIndex) + randomNumber + password.substring(randomIndex + 1);
- }
-
- if (includeSymbols && !/[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]/.test(password)) {
- const randomIndex = Math.floor(Math.random() * length);
- const randomSymbol = symbols[Math.floor(Math.random() * symbols.length)];
- password = password.substring(0, randomIndex) + randomSymbol + password.substring(randomIndex + 1);
- }
-
- let strength = 'Weak';
- let strengthEmoji = '🔴';
-let strengthColor = 0xff0000;
-
- const hasLower = /[a-z]/.test(password);
- const hasUpper = /[A-Z]/.test(password);
- const hasNumber = /[0-9]/.test(password);
- const hasSymbol = /[^a-zA-Z0-9]/.test(password);
-
- const uniqueChars = new Set(password).size;
- const uniqueRatio = uniqueChars / password.length;
-
- let score = 0;
- score += password.length * 4;
- score += (password.length - (password.match(/[a-z]/g) || []).length) * 2;
- score += (password.length - (password.match(/[A-Z]/g) || []).length) * 2;
- score += (password.match(/[0-9]/g) || []).length * 4;
- score += (password.match(/[^a-zA-Z0-9]/g) || []).length * 6;
-
- if (uniqueRatio < 0.5) score *= 0.7;
- if (hasLower && hasUpper) score *= 1.2;
- if (hasNumber) score *= 1.2;
- if (hasSymbol) score *= 1.3;
-
- if (score > 80) {
- strength = 'Very Strong';
- strengthEmoji = '🟢';
-strengthColor = 0x00ff00;
- } else if (score > 60) {
- strength = 'Strong';
- strengthEmoji = '🟢';
-strengthColor = 0x00aa00;
- } else if (score > 40) {
- strength = 'Good';
- strengthEmoji = '🟡';
-strengthColor = 0xffff00;
- } else if (score > 20) {
- strength = 'Weak';
- strengthEmoji = '🟠';
-strengthColor = 0xffa500;
- }
-
- const embed = successEmbed(
- '🔑 Generated Password',
- `**Password:** ||\`${password}\`||\n` +
- `**Length:** ${password.length} characters\n` +
- `**Strength:** ${strengthEmoji} ${strength}\n` +
- `**Contains:** ${hasLower ? 'Lowercase' : ''}${hasUpper ? ', Uppercase' : ''}${hasNumber ? ', Numbers' : ''}${hasSymbol ? ', Symbols' : ''}`
- ).setColor(strengthColor);
-
- await interaction.reply({
- embeds: [embed],
- flags: MessageFlags.Ephemeral
- });
- } catch (error) {
- console.error('GeneratePassword command error:', error);
- return interaction.reply({
- embeds: [errorEmbed('System Error', 'Could not generate a password at this time.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
diff --git a/src/commands/Tools/hexcolor.js b/src/commands/Tools/hexcolor.js
deleted file mode 100644
index 50143195f7..0000000000
--- a/src/commands/Tools/hexcolor.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('hexcolor')
- .setDescription('Generate a random hex color with preview')
- .addStringOption(option =>
- option.setName('color')
- .setDescription('Specific hex color (e.g., #FF5733 or FF5733)')
- .setRequired(false)),
-
- async execute(interaction) {
-try {
- let hexColor = interaction.options.getString('color');
- let isRandom = false;
-
- if (!hexColor) {
- isRandom = true;
- hexColor = '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
- } else {
- hexColor = hexColor.replace('#', '');
- if (!/^[0-9A-Fa-f]{3,6}$/.test(hexColor)) {
- return interaction.editReply({
- embeds: [errorEmbed('Error', 'Please provide a valid hex color code (e.g., #FF5733 or FF5733)')],
- flags: ["Ephemeral"]
- });
- }
-
- if (hexColor.length === 3) {
- hexColor = hexColor.split('').map(c => c + c).join('');
- }
-
- hexColor = '#' + hexColor.toUpperCase();
- }
-
- const r = parseInt(hexColor.slice(1, 3), 16);
- const g = parseInt(hexColor.slice(3, 5), 16);
- const b = parseInt(hexColor.slice(5, 7), 16);
-
- const brightness = (r * 299 + g * 587 + b * 114) / 1000;
- const textColor = brightness > 128 ? '#000000' : '#FFFFFF';
-
- const colorPreviewUrl = `https://dummyimage.com/200x100/${hexColor.replace('#', '')}/${textColor.replace('#', '')}?text=${encodeURIComponent(hexColor)}`;
-
- const colorName = getColorName(hexColor);
-
- const embed = successEmbed(
- '🎨 Color Information',
- `**Hex:** \`${hexColor}\`\n` +
- `**RGB:** \`rgb(${r}, ${g}, ${b})\`\n` +
- `**HSL:** \`${rgbToHsl(r, g, b)}\`\n` +
- `**Name:** ${colorName || 'Custom Color'}`
- )
- .setColor(hexColor)
- .setImage(colorPreviewUrl);
-
- if (isRandom) {
- embed.setFooter({ text: '✨ Randomly generated color' });
- }
-
- await interaction.editReply({ embeds: [embed] });
-
- } catch (error) {
- console.error('Hexcolor command error:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'Failed to process the color. Please try again.')],
- flags: ["Ephemeral"]
- });
- }
- },
-};
-
-function rgbToHsl(r, g, b) {
- r /= 255, g /= 255, b /= 255;
- const max = Math.max(r, g, b), min = Math.min(r, g, b);
- let h, s, l = (max + min) / 2;
-
- if (max === min) {
-h = s = 0;
- } else {
- const d = max - min;
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
- switch (max) {
- case r: h = (g - b) / d + (g < b ? 6 : 0); break;
- case g: h = (b - r) / d + 2; break;
- case b: h = (r - g) / d + 4; break;
- }
- h /= 6;
- }
-
- return `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
-}
-
-function getColorName(hex) {
- const colors = {
- '#FF0000': 'Red',
- '#00FF00': 'Green',
- '#0000FF': 'Blue',
- '#FFFF00': 'Yellow',
- '#FF00FF': 'Magenta',
- '#00FFFF': 'Cyan',
- '#000000': 'Black',
- '#FFFFFF': 'White',
- '#808080': 'Gray',
- '#FFA500': 'Orange',
- '#800080': 'Purple',
- '#A52A2A': 'Brown',
- '#FFC0CB': 'Pink',
- '#008000': 'Dark Green',
- '#000080': 'Navy',
- '#FFD700': 'Gold',
- '#C0C0C0': 'Silver',
- '#FF6347': 'Tomato',
- '#40E0D0': 'Turquoise',
- '#E6E6FA': 'Lavender'
- };
-
- if (colors[hex.toUpperCase()]) {
- return colors[hex.toUpperCase()];
- }
-
- const hexValue = parseInt(hex.replace('#', ''), 16);
- let closestColor = '';
- let minDistance = Infinity;
-
- for (const [colorHex, name] of Object.entries(colors)) {
- const colorValue = parseInt(colorHex.replace('#', ''), 16);
- const distance = Math.abs(hexValue - colorValue);
-
- if (distance < minDistance) {
- minDistance = distance;
- closestColor = name;
- }
- }
-
- return minDistance < 1000000 ? `Close to ${closestColor}` : null;
-}
-
diff --git a/src/commands/Tools/poll.js b/src/commands/Tools/poll.js
deleted file mode 100644
index efa71d0fd0..0000000000
--- a/src/commands/Tools/poll.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-const EMOJIS = ['1ï¸âƒ£', '2ï¸âƒ£', '3ï¸âƒ£', '4ï¸âƒ£', '5ï¸âƒ£', '6ï¸âƒ£', '7ï¸âƒ£', '8ï¸âƒ£', '9ï¸âƒ£', '🔟'];
-const MAX_OPTIONS = 10;
-export default {
- data: new SlashCommandBuilder()
- .setName('poll')
- .setDescription('Create a simple poll with up to 10 options')
- .addStringOption(option =>
- option.setName('question')
- .setDescription('The poll question')
- .setRequired(true))
- .addStringOption(option =>
- option.setName('option1')
- .setDescription('First option')
- .setRequired(true))
- .addStringOption(option =>
- option.setName('option2')
- .setDescription('Second option')
- .setRequired(true))
- .addStringOption(option =>
- option.setName('option3')
- .setDescription('Third option (optional)')
- .setRequired(false))
- .addStringOption(option =>
- option.setName('option4')
- .setDescription('Fourth option (optional)')
- .setRequired(false))
- .addStringOption(option =>
- option.setName('option5')
- .setDescription('Fifth option (optional)')
- .setRequired(false))
- .addStringOption(option =>
- option.setName('option6')
- .setDescription('Sixth option (optional)')
- .setRequired(false))
- .addStringOption(option =>
- option.setName('option7')
- .setDescription('Seventh option (optional)')
- .setRequired(false))
- .addStringOption(option =>
- option.setName('option8')
- .setDescription('Eighth option (optional)')
- .setRequired(false))
- .addStringOption(option =>
- option.setName('option9')
- .setDescription('Ninth option (optional)')
- .setRequired(false))
- .addStringOption(option =>
- option.setName('option10')
- .setDescription('Tenth option (optional)')
- .setRequired(false))
- .addBooleanOption(option =>
- option.setName('anonymous')
- .setDescription('Make the poll anonymous (default: false)')
- .setRequired(false)),
-
- async execute(interaction) {
- try {
- const question = interaction.options.getString('question');
- const isAnonymous = interaction.options.getBoolean('anonymous') || false;
-
- const options = [];
- for (let i = 1; i <= MAX_OPTIONS; i++) {
- const option = interaction.options.getString(`option${i}`);
- if (option) options.push(option);
- }
-
- if (options.length < 2) {
- throw new Error("You must provide at least 2 options for the poll.");
- }
-
- let description = `**${question}**\n\n`;
- options.forEach((option, index) => {
- description += `${EMOJIS[index]} ${option}\n`;
- });
-
- if (isAnonymous) {
- description += '\n*This is an anonymous poll. Votes are not tracked to users.*';
- } else {
- description += '\n*React with the emoji to vote!*';
- }
-
- const embed = successEmbed(
- `📊 ${isAnonymous ? 'Anonymous ' : ''}Poll`,
- description
- );
-
- const message = await interaction.channel.send({ embeds: [embed] });
-
- for (let i = 0; i < options.length; i++) {
- await message.react(EMOJIS[i]);
- await new Promise(resolve => setTimeout(resolve, 500));
- }
-
- await interaction.reply({
- content: '✅ Poll created successfully!',
- flags: MessageFlags.Ephemeral
- });
- } catch (error) {
- console.error('Poll command error:', error);
- return interaction.reply({
- embeds: [errorEmbed('System Error', 'Could not create poll at this time.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
diff --git a/src/commands/Tools/randomuser.js b/src/commands/Tools/randomuser.js
deleted file mode 100644
index b72e30f559..0000000000
--- a/src/commands/Tools/randomuser.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('randomuser')
- .setDescription('Select a random user from the server')
- .addRoleOption(option =>
- option.setName('role')
- .setDescription('Limit selection to users with this role')
- .setRequired(false))
- .addBooleanOption(option =>
- option.setName('bots')
- .setDescription('Include bots in the selection (default: false)')
- .setRequired(false))
- .addBooleanOption(option =>
- option.setName('online')
- .setDescription('Only select from online users (default: false)')
- .setRequired(false))
- .addBooleanOption(option =>
- option.setName('mention')
- .setDescription('Mention the selected user (default: false)')
- .setRequired(false)),
-
- async execute(interaction) {
-try {
- if (!interaction.guild) {
- return interaction.reply({
- embeds: [errorEmbed('Error', 'This command can only be used in a server.')],
- flags: ["Ephemeral"]
- });
- }
-
- const role = interaction.options.getRole('role');
- const includeBots = interaction.options.getBoolean('bots') || false;
- const onlineOnly = interaction.options.getBoolean('online') || false;
- const shouldMention = interaction.options.getBoolean('mention') || false;
-
- let members = interaction.guild.members.cache.filter(member => {
- if (member.user.bot && !includeBots) return false;
-
- if (onlineOnly && member.presence?.status === 'offline') return false;
-
- if (role && !member.roles.cache.has(role.id)) return false;
-
- return true;
- });
-
- let memberArray = Array.from(members.values());
-
- if (!includeBots) {
- memberArray = memberArray.filter(member => !member.user.bot);
- }
-
- if (memberArray.length === 0) {
- let errorMessage = 'No users found matching the criteria.';
- if (role) errorMessage += ` No users have the ${role.name} role.`;
- if (onlineOnly) errorMessage += ' No online users found.';
-
- return interaction.editReply({
- embeds: [errorEmbed('No Users Found', errorMessage)],
- flags: ["Ephemeral"]
- });
- }
-
- const randomIndex = Math.floor(Math.random() * memberArray.length);
- const selectedMember = memberArray[randomIndex];
-
- const user = selectedMember.user;
- const joinDate = selectedMember.joinedAt;
- const roles = selectedMember.roles.cache
-.filter(role => role.id !== interaction.guild.id)
- .sort((a, b) => b.position - a.position)
- .map(role => role.toString())
-.slice(0, 10);
-
- const embed = successEmbed(
- '🎲 Random User Selected',
- shouldMention ? `${selectedMember}` : `**${user.username}**`
- )
- .setThumbnail(user.displayAvatarURL({ dynamic: true, size: 256 }))
- .addFields(
- { name: '👤 Username', value: user.username, inline: true },
- { name: '🤖 Bot', value: user.bot ? 'Yes' : 'No', inline: true },
- { name: `🎠Roles (${roles.length})`, value: roles.length > 0 ? roles.slice(0, 5).join(' ') + (roles.length > 5 ? ` +${roles.length - 5} more` : '') : 'No roles', inline: false }
- )
- .setColor(selectedMember.displayHexColor || '#3498db');
-
- const row = new ActionRowBuilder()
- .addComponents(
- new ButtonBuilder()
- .setCustomId(`randomuser_${interaction.user.id}_again`)
- .setLabel('🎲 Pick Another User')
- .setStyle(ButtonStyle.Primary)
- );
-
- const response = await interaction.editReply({
- content: shouldMention ? `${selectedMember}, you've been chosen!` : null,
- embeds: [embed],
- components: [row],
- allowedMentions: { users: shouldMention ? [user.id] : [] }
- });
-
- const filter = (i) => i.customId === `randomuser_${interaction.user.id}_again` && i.user.id === interaction.user.id;
-const collector = response.createMessageComponentCollector({ filter, time: 300000 });
-
- collector.on('collect', async (i) => {
- try {
- let newMembers = interaction.guild.members.cache.filter(member => {
- if (member.user.bot && !includeBots) return false;
-
- if (onlineOnly && member.presence?.status === 'offline') return false;
-
- if (role && !member.roles.cache.has(role.id)) return false;
-
- return true;
- });
-
- let newMemberArray = Array.from(newMembers.values());
-
- if (!includeBots) {
- newMemberArray = newMemberArray.filter(member => !member.user.bot);
- }
-
- if (newMemberArray.length === 0) {
- await i.update({
- embeds: [errorEmbed('No Users Found', 'No users found matching the criteria.')],
- components: [row]
- });
- return;
- }
-
- const newRandomIndex = Math.floor(Math.random() * newMemberArray.length);
- const newSelectedMember = newMemberArray[newRandomIndex];
- const newUser = newSelectedMember.user;
-
- const newRoles = newSelectedMember.roles.cache
- .filter(r => r.id !== interaction.guild.id)
- .sort((a, b) => b.position - a.position)
- .map(r => r.toString())
- .slice(0, 10);
-
- const newEmbed = successEmbed(
- '🎲 Random User Selected',
- shouldMention ? `${newSelectedMember}` : `**${newUser.username}**`
- )
- .setThumbnail(newUser.displayAvatarURL({ dynamic: true, size: 256 }))
- .addFields(
- { name: '👤 Username', value: newUser.username, inline: true },
- { name: '🤖 Bot', value: newUser.bot ? 'Yes' : 'No', inline: true },
- { name: `🎠Roles (${newRoles.length})`, value: newRoles.length > 0 ? newRoles.slice(0, 5).join(' ') + (newRoles.length > 5 ? ` +${newRoles.length - 5} more` : '') : 'No roles', inline: false }
- )
- .setColor(newSelectedMember.displayHexColor || '#3498db');
-
- await i.update({
- content: shouldMention ? `${newSelectedMember}, you've been chosen!` : null,
- embeds: [newEmbed],
- components: [row],
- allowedMentions: { users: shouldMention ? [newUser.id] : [] }
- });
-
- } catch (error) {
- console.error('Button interaction error:', error);
- await i.reply({
- content: 'An error occurred while selecting another user.',
- flags: ["Ephemeral"]
- });
- }
- });
-
- collector.on('end', () => {
- const disabledRow = ActionRowBuilder.from(row).setComponents(
- ButtonBuilder.from(row.components[0]).setDisabled(true)
- );
-
- interaction.editReply({ components: [disabledRow] }).catch(console.error);
- });
-
- } catch (error) {
- console.error('RandomUser command error:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'Failed to select a random user. Please try again.')],
- flags: ["Ephemeral"]
- });
- }
- },
-};
-
diff --git a/src/commands/Tools/shorten.js b/src/commands/Tools/shorten.js
deleted file mode 100644
index b5eb861bbb..0000000000
--- a/src/commands/Tools/shorten.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("shorten")
- .setDescription("Shorten a URL using is.gd")
- .addStringOption(option =>
- option
- .setName("url")
- .setDescription("The URL to shorten")
- .setRequired(true)
- )
- .addStringOption(option =>
- option
- .setName("custom")
- .setDescription("Custom URL ending (optional)")
- .setRequired(false)
- )
- .setDMPermission(false),
- category: "Tools",
-
- async execute(interaction, config, client) {
- const url = interaction.options.getString("url");
- const custom = interaction.options.getString("custom");
-
- try {
- new URL(url);
- } catch (e) {
- throw new Error("Invalid URL format. Include http:// or https://");
- }
-
- if (custom && !/^[a-zA-Z0-9_-]+$/.test(custom)) {
- throw new Error("Custom URL can only contain letters, numbers, underscores, and hyphens.");
- }
-
- let apiUrl = `https://is.gd/create.php?format=simple&url=${encodeURIComponent(url)}`;
- if (custom) {
- apiUrl += `&shorturl=${encodeURIComponent(custom)}`;
- }
-
- const response = await fetch(apiUrl);
- const shortUrl = await response.text();
-
- try {
- new URL(shortUrl);
- } catch (e) {
- if (shortUrl.includes("already exists")) {
- throw new Error("That custom URL is already taken. Try a different one.");
- } else if (shortUrl.includes("invalid")) {
- throw new Error("Invalid URL. Include http:// or https://");
- }
- throw new Error(`URL shortening failed: ${shortUrl}`);
- }
-
- await interaction.editReply({
- embeds: [
- successEmbed("URL Shortened", `Here's your shortened URL: ${shortUrl}`),
- ],
- },
- errorEmbed("URL Shortening Failed", "Couldn't shorten that URL. Check the format and try again.")
- );
- },
-};
-
diff --git a/src/commands/Tools/time.js b/src/commands/Tools/time.js
deleted file mode 100644
index ca4eefa2be..0000000000
--- a/src/commands/Tools/time.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { SlashCommandBuilder, MessageFlags } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-export default {
- data: new SlashCommandBuilder()
- .setName('time')
- .setDescription('Get the current time in different timezones')
- .addStringOption(option =>
- option.setName('timezone')
- .setDescription('The timezone to display (e.g., UTC, America/New_York)')
- .setRequired(false)),
-
- async execute(interaction) {
- try {
- const timezone = interaction.options.getString('timezone') || 'UTC';
-
- let timeString;
- try {
- timeString = new Date().toLocaleString('en-US', {
- timeZone: timezone,
- weekday: 'long',
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
- timeZoneName: 'short'
- });
- } catch (error) {
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Invalid timezone. Please use a valid timezone identifier (e.g., UTC, America/New_York, Europe/London)')],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- const now = new Date();
- const unixTimestamp = Math.floor(now.getTime() / 1000);
-
- const embed = successEmbed(
- '🕒 Current Time',
- `**${timezone}:** ${timeString}\n` +
- `**Unix Timestamp:** \`${unixTimestamp}\`\n` +
- `**ISO String:** \`${now.toISOString()}\``
- );
-
- await interaction.reply({ embeds: [embed], flags: MessageFlags.Ephemeral });
- } catch (error) {
- console.error('Time command error:', error);
- const replyMethod = interaction.replied || interaction.deferred ? 'editReply' : 'reply';
- await interaction[replyMethod]({
- embeds: [errorEmbed('Error', 'Failed to get the current time.')],
- flags: MessageFlags.Ephemeral
- });
- }
- },
-};
-
-
diff --git a/src/commands/Tools/unixtime.js b/src/commands/Tools/unixtime.js
deleted file mode 100644
index f12b88081d..0000000000
--- a/src/commands/Tools/unixtime.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-export default {
- data: new SlashCommandBuilder()
- .setName('unixtime')
- .setDescription('Get the current Unix timestamp'),
-
- async execute(interaction) {
- try {
- const now = new Date();
- const unixTimestamp = Math.floor(now.getTime() / 1000);
-
- const embed = successEmbed(
- 'â±ï¸ Current Unix Timestamp',
- `**Seconds since Unix Epoch:** \`${unixTimestamp}\`\n` +
- `**Milliseconds since Unix Epoch:** \`${now.getTime()}\`\n\n` +
- `**Human-readable (UTC):** ${now.toUTCString()}\n` +
- `**ISO String:** ${now.toISOString()}`
- );
-
- await interaction.editReply({
- embeds: [embed],
- flags: ["Ephemeral"]
- },
- errorEmbed('Failed to get Unix timestamp. Please try again later.')
- );
- } catch (error) {
- console.error('Unixtime command error:', error);
- return interaction.reply({
- embeds: [errorEmbed('System Error', 'Could not get Unix timestamp at this time.')],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
diff --git a/src/commands/Utility/avatar.js b/src/commands/Utility/avatar.js
deleted file mode 100644
index 023971b3c1..0000000000
--- a/src/commands/Utility/avatar.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("avatar")
- .setDescription("Display a user's avatar image")
- .addUserOption((option) =>
- option
- .setName("target")
- .setDescription(
- "The user whose avatar you want to see (defaults to you)",
- ),
- ),
-
- async execute(interaction) {
-try {
- const user = interaction.options.getUser("target") || interaction.user;
-
- const embed = createEmbed({ title: `${user.username}'s Avatar`, description: `[Download Link](${user.displayAvatarURL({ size: 2048, dynamic: true })})` })
- .setImage(user.displayAvatarURL({ size: 2048, dynamic: true }));
-
- await interaction.reply({ embeds: [embed] });
- } catch (error) {
- console.error('Avatar command error:', error);
- return interaction.editReply({
- embeds: [createEmbed({ title: 'System Error', description: 'Could not display avatar at this time.' })],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
diff --git a/src/commands/Utility/firstmsg.js b/src/commands/Utility/firstmsg.js
deleted file mode 100644
index 2469f0b1f0..0000000000
--- a/src/commands/Utility/firstmsg.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-import { InteractionHelper } from '../../utils/interactionHelper.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("firstmsg")
- .setDescription("Get a link to the first message in this channel")
- .setDMPermission(false)
- .setDefaultMemberPermissions(PermissionFlagsBits.SendMessages),
- category: "Utility",
-
- async execute(interaction, config, client) {
- const commandBody = async () => {
- try {
- const messages = await interaction.channel.messages.fetch({
- limit: 1,
- after: '1',
- cache: false
- });
-
- const firstMessage = messages.first();
-
- if (!firstMessage) {
- return await interaction.editReply({
- embeds: [successEmbed("First Message", "No messages found in this channel!")],
- });
- }
-
- const messageLink = `https://discord.com/channels/${interaction.guildId}/${interaction.channelId}/${firstMessage.id}`;
-
- await interaction.editReply({
- embeds: [
- successEmbed(
- "First Message in #" + interaction.channel.name,
- `[Jump to first message](${messageLink})`
- ),
- ],
- });
-
- } catch (error) {
- console.error("Error in firstmsg command:", error);
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "Failed to fetch the first message.")],
- });
- }
- };
-
- if (interaction.deferred || interaction.replied) {
- try {
- await commandBody();
- } catch (error) {
- console.error('FirstMsg execution error when interaction already acknowledged:', error);
- await interaction.editReply({
- embeds: [errorEmbed('System Error', 'Could not complete the command.')],
- });
- }
- return;
- }
-
- await InteractionHelper.safeExecute(
- interaction,
- commandBody,
- { title: 'Command Error', description: 'Failed to execute command. Please try again later.' }
- );
- },
-};
-
diff --git a/src/commands/Utility/report.js b/src/commands/Utility/report.js
deleted file mode 100644
index 9c53876cb2..0000000000
--- a/src/commands/Utility/report.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getGuildConfig } from '../../services/guildConfig.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("report")
- .setDescription("Report a user or an issue to the server staff.")
- .addUserOption((option) =>
- option
- .setName("user")
- .setDescription("The user you want to report.")
- .setRequired(true),
- )
- .addStringOption((option) =>
- option
- .setName("reason")
- .setDescription("The reason for the report (be detailed).")
- .setRequired(true)
- .setMaxLength(500),
- )
- .setDMPermission(false),
- category: "Utility",
-
- /**
- * @param {import('discord.js').ChatInputCommandInteraction} interaction
- * @param {object} config
- * @param {import('discord.js').Client} client
- */
- async execute(interaction, config, client) {
-
- const targetUser = interaction.options.getUser("user");
- const reason = interaction.options.getString("reason");
- const guildId = interaction.guildId;
-
- try {
- const guildConfig = await getGuildConfig(client, guildId);
- const reportChannelId = guildConfig.reportChannelId;
-
- if (!reportChannelId) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Setup Required",
- "The report channel has not been set up. Please ask a moderator to use `/setreportchannel` first.",
- ),
- ],
- });
- }
-
- const reportChannel =
- interaction.guild.channels.cache.get(reportChannelId);
-
- if (!reportChannel) {
- return await interaction.editReply({
- embeds: [
- errorEmbed(
- "Channel Missing",
- "The configured report channel is missing or inaccessible. Please ask a moderator to reset it.",
- ),
- ],
- });
- }
-
- const reportEmbed = createEmbed({ title: `🚨 NEW USER REPORT: ${targetUser.tag}`, description: `**Reported By:** ${interaction.user.tag} (\`${interaction.user.id}\`)\n**Reported User:** ${targetUser.tag} (\`${targetUser.id}\`)` })
-.setColor(0xff0000)
- .setThumbnail(targetUser.displayAvatarURL())
- .addFields(
- { name: "Reason", value: reason },
- {
- name: "Reported In Channel",
- value: interaction.channel.toString(),
- inline: true,
- },
- {
- name: "Time",
- value: new Date().toUTCString(),
- inline: true,
- },
- );
-
- await reportChannel.send({
- content: `<@&${interaction.guild.ownerId}> New Report!`,
- embeds: [reportEmbed],
- });
-
- await interaction.editReply({
- embeds: [
- createEmbed({ title: "✅ Report Submitted", description: `Your report against **${targetUser.tag}** has been successfully filed and sent to the moderation team. Thank you!`, }),
- ],
- });
- } catch (error) {
- console.error("Report command error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "System Error",
- "An unexpected error occurred while submitting the report.",
- ),
- ],
- });
- }
- },
-};
-
diff --git a/src/commands/Utility/serverinfo.js b/src/commands/Utility/serverinfo.js
deleted file mode 100644
index d67a1e31b8..0000000000
--- a/src/commands/Utility/serverinfo.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("serverinfo")
- .setDescription("Get detailed information about the server"),
-
- async execute(interaction) {
-try {
- const guild = interaction.guild;
- const owner = await guild.fetchOwner();
-
- const createdTimestamp = Math.floor(guild.createdAt.getTime() / 1000);
-
- const embed = createEmbed({ title: `🰠Server Info: ${guild.name}`, description: `Server ID: ${guild.id}` })
- .setThumbnail(guild.iconURL({ size: 256 }))
- .addFields(
- { name: "Owner", value: owner.user.tag, inline: true },
- { name: "Members", value: `${guild.memberCount}`, inline: true },
- {
- name: "Channels",
- value: `${guild.channels.cache.size}`,
- inline: true,
- },
- { name: "Roles", value: `${guild.roles.cache.size}`, inline: true },
- {
- name: "Boosts",
- value: `Level ${guild.premiumTier} (${guild.premiumSubscriptionCount})`,
- inline: true,
- },
- {
- name: "Creation Date",
- value: ``,
- inline: true,
- },
- );
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error('ServerInfo command error:', error);
- return interaction.editReply({
- embeds: [createEmbed({ title: 'System Error', description: 'Could not retrieve server information.' })],
- flags: MessageFlags.Ephemeral,
- });
- }
- },
-};
-
diff --git a/src/commands/Utility/todo.js b/src/commands/Utility/todo.js
deleted file mode 100644
index 982af8c157..0000000000
--- a/src/commands/Utility/todo.js
+++ /dev/null
@@ -1,457 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getFromDb, setInDb } from '../../utils/database.js';
-function generateShareId() {
- return Math.random().toString(36).substring(2, 9);
-}
-
-export default {
- data: new SlashCommandBuilder()
- .setName("todo")
- .setDescription("Manage your personal to-do list")
- .addSubcommand(subcommand =>
- subcommand
- .setName("add")
- .setDescription("Add a task to your to-do list")
- .addStringOption(option =>
- option
- .setName("task")
- .setDescription("The task to add")
- .setRequired(true)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("list")
- .setDescription("View your to-do list")
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("complete")
- .setDescription("Mark a task as complete")
- .addIntegerOption(option =>
- option
- .setName("number")
- .setDescription("The number of the task to complete")
- .setRequired(true)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("remove")
- .setDescription("Remove a task from your to-do list")
- .addIntegerOption(option =>
- option
- .setName("number")
- .setDescription("The number of the task to remove")
- .setRequired(true)
- )
- )
- .addSubcommandGroup(group =>
- group
- .setName("share")
- .setDescription("Manage shared to-do lists")
- .addSubcommand(subcommand =>
- subcommand
- .setName("create")
- .setDescription("Create a new shared to-do list")
- .addStringOption(option =>
- option
- .setName("name")
- .setDescription("Name for the shared list")
- .setRequired(true)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("add")
- .setDescription("Add a member to a shared list")
- .addStringOption(option =>
- option
- .setName("list_id")
- .setDescription("ID of the shared list")
- .setRequired(true)
- )
- .addUserOption(option =>
- option
- .setName("user")
- .setDescription("User to add to the list")
- .setRequired(true)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("view")
- .setDescription("View a shared to-do list")
- .addStringOption(option =>
- option
- .setName("list_id")
- .setDescription("ID of the shared list")
- .setRequired(true)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("addtask")
- .setDescription("Add a task to a shared to-do list")
- .addStringOption(option =>
- option
- .setName("list_id")
- .setDescription("ID of the shared list")
- .setRequired(true)
- )
- .addStringOption(option =>
- option
- .setName("task")
- .setDescription("The task to add")
- .setRequired(true)
- )
- )
- )
- .setDMPermission(false)
- .setDefaultMemberPermissions(PermissionFlagsBits.SendMessages),
- category: "Utility",
-
- async execute(interaction, config, client) {
- const userId = interaction.user.id;
- const subcommand = interaction.options.getSubcommand();
- const shareSubcommand = interaction.options.getSubcommandGroup() === 'share' ? interaction.options.getSubcommand() : null;
-
- async function getOrCreateSharedList(listId, creatorId = null, listName = null) {
- const listKey = `shared_todo_${listId}`;
- let listData = await getFromDb(listKey, null);
-
- if (!listData || (listData.ok === false && listData.error)) {
- if (creatorId) {
- listData = {
- id: listId,
- name: listName,
- creatorId,
- members: [creatorId],
- tasks: [],
- nextId: 1,
- createdAt: new Date().toISOString()
- };
- await setInDb(listKey, listData);
- } else {
- return null;
- }
- }
-
- if (listData) {
- if (!Array.isArray(listData.tasks)) listData.tasks = [];
- if (!listData.nextId) listData.nextId = 1;
- if (!Array.isArray(listData.members)) listData.members = [];
- }
-
- return listData;
- }
-
- try {
- if (shareSubcommand) {
- switch (shareSubcommand) {
- case 'create': {
- const listName = interaction.options.getString('name');
- const listId = generateShareId();
-
- await getOrCreateSharedList(listId, userId, listName);
-
- const userSharedLists = await getFromDb(`user_shared_lists_${userId}`, []);
- const sharedListsArray = Array.isArray(userSharedLists) ? userSharedLists : [];
- if (!sharedListsArray.includes(listId)) {
- sharedListsArray.push(listId);
- await setInDb(`user_shared_lists_${userId}`, sharedListsArray);
- }
-
- return await interaction.editReply({
- embeds: [
- successEmbed(
- "Shared List Created",
- `Created shared list "${listName}" with ID: \`${listId}\`\n` +
- `Use \`/todo share add list_id:${listId} user:@username\` to add members.`
- )
- ]
- });
- }
-
- case 'add': {
- const listId = interaction.options.getString('list_id');
- const memberToAdd = interaction.options.getUser('user');
-
- const listData = await getOrCreateSharedList(listId);
- if (!listData) {
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "Shared list not found.")]
- });
- }
-
- if (listData.creatorId !== userId) {
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "Only the list creator can add members.")]
- });
- }
-
- if (!listData.members.includes(memberToAdd.id)) {
- listData.members.push(memberToAdd.id);
- await setInDb(`shared_todo_${listId}`, listData);
-
- const memberLists = await getFromDb(`user_shared_lists_${memberToAdd.id}`, []);
- const memberListsArray = Array.isArray(memberLists) ? memberLists : [];
- if (!memberListsArray.includes(listId)) {
- memberListsArray.push(listId);
- await setInDb(`user_shared_lists_${memberToAdd.id}`, memberListsArray);
- }
-
- return await interaction.editReply({
- embeds: [
- successEmbed("Member Added",
- `Added ${memberToAdd.username} to the shared list "${listData.name}"`
- )
- ]
- });
- } else {
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "User is already a member of this list.")]
- });
- }
- }
-
- case 'view': {
- const listId = interaction.options.getString('list_id');
- const listData = await getOrCreateSharedList(listId);
-
- if (!listData) {
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "Shared list not found.")]
- });
- }
-
- if (!listData.members.includes(userId)) {
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "You don't have access to this list.")]
- });
- }
-
- if (listData.tasks.length === 0) {
- const memberList = listData.members.map(memberId => {
- const member = interaction.guild.members.cache.get(memberId);
- return member ? member.user.username : `<@${memberId}>`;
- }).join(', ');
-
- const owner = interaction.guild.members.cache.get(listData.creatorId);
- const ownerName = owner ? owner.user.username : `<@${listData.creatorId}>`;
-
- return await interaction.editReply({
- embeds: [
- successEmbed(
- `📋 **${listData.name}**\n\n` +
- `👑 **Owner:** ${ownerName}\n` +
- `👥 **Members:** ${memberList}\n\n` +
- `*This list is currently empty. Use the "Add Task" button to add tasks!*`,
- `Shared List (ID: \`${listId}\`)`
- )
- ],
- components: [
- new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`shared_todo_add_${listId}`)
- .setLabel('Add Task')
- .setStyle(ButtonStyle.Primary),
- new ButtonBuilder()
- .setCustomId(`shared_todo_complete_${listId}`)
- .setLabel('Complete Task')
- .setStyle(ButtonStyle.Success)
- )
- ]
- });
- }
-
- const taskList = listData.tasks
- .map(task =>
- `${task.completed ? '✅' : 'ðŸ“'} #${task.id} ${task.text} ` +
- `\`[${new Date(task.createdAt).toLocaleDateString()}]` +
- (task.completed ? ` • Completed by ${task.completedBy}` : '') + '`'
- )
- .join('\n');
-
- const memberList = listData.members.map(memberId => {
- const member = interaction.guild.members.cache.get(memberId);
- return member ? member.user.username : `<@${memberId}>`;
- }).join(', ');
-
- const owner = interaction.guild.members.cache.get(listData.creatorId);
- const ownerName = owner ? owner.user.username : `<@${listData.creatorId}>`;
-
- const fullListDisplay = `📋 **${listData.name}**\n\n` +
- `👑 **Owner:** ${ownerName}\n` +
- `👥 **Members:** ${memberList}\n\n` +
- `**Tasks:**\n${taskList}`;
-
- return await interaction.editReply({
- embeds: [
- successEmbed(fullListDisplay, `Shared List (ID: \`${listId}\`)`)
- ],
- components: [
- new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`shared_todo_add_${listId}`)
- .setLabel('Add Task')
- .setStyle(ButtonStyle.Primary),
- new ButtonBuilder()
- .setCustomId(`shared_todo_complete_${listId}`)
- .setLabel('Complete Task')
- .setStyle(ButtonStyle.Success)
- )
- ]
- });
- }
-
- case 'addtask': {
- const listId = interaction.options.getString('list_id');
- const taskText = interaction.options.getString('task');
-
- const listData = await getOrCreateSharedList(listId);
-
- if (!listData) {
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "Shared list not found.")]
- });
- }
-
- if (!listData.members.includes(userId)) {
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "You don't have access to this list.")]
- });
- }
-
- const newTask = {
- id: listData.nextId++,
- text: taskText,
- completed: false,
- createdAt: new Date().toISOString(),
- createdBy: userId
- };
-
- listData.tasks.push(newTask);
- await setInDb(`shared_todo_${listId}`, listData);
-
- return await interaction.editReply({
- embeds: [
- successEmbed("Task Added", `Added "${taskText}" to the shared list "${listData.name}"`)
- ]
- });
- }
- }
- return;
- }
-
- const dbKey = `todo_${userId}`;
-
- const userData = await getFromDb(dbKey, {
- tasks: [],
- nextId: 1
- });
-
- if (!userData.tasks) userData.tasks = [];
- if (!userData.nextId) userData.nextId = 1;
-
- switch (subcommand) {
- case 'add': {
- const taskText = interaction.options.getString('task');
-
- const newTask = {
- id: userData.nextId++,
- text: taskText,
- completed: false,
- createdAt: new Date().toISOString()
- };
-
- userData.tasks.push(newTask);
- await setInDb(dbKey, userData);
-
- return await interaction.editReply({
- embeds: [
- successEmbed(
- "Task Added",
- `Added "${taskText}" to your to-do list.`
- ),
- ],
- });
- }
-
- case 'list': {
- if (userData.tasks.length === 0) {
- return await interaction.editReply({
- embeds: [successEmbed("Your to-do list is empty!", "Your To-Do List")],
- });
- }
-
- const taskList = userData.tasks
- .map(task =>
- `${task.completed ? '✅' : 'ðŸ“'} #${task.id} ${task.text} ` +
- `\`[${new Date(task.createdAt).toLocaleDateString()}\``
- )
- .join('\n');
-
- return await interaction.editReply({
- embeds: [
- successEmbed(taskList, "Your To-Do List")
- ],
- });
- }
-
- case 'complete': {
- const taskNumber = interaction.options.getInteger('number');
- const task = userData.tasks.find(t => t.id === taskNumber);
-
- if (!task) {
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "Task not found.")],
- });
- }
-
- task.completed = true;
- await setInDb(`todo_${userId}`, userData);
-
- return await interaction.editReply({
- embeds: [
- successEmbed("Task Completed", `Marked "${task.text}" as complete!`)
- ],
- });
- }
-
- case 'remove': {
- const taskNumber = interaction.options.getInteger('number');
- const taskIndex = userData.tasks.findIndex(t => t.id === taskNumber);
-
- if (taskIndex === -1) {
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "Task not found.")],
- });
- }
-
- const [removedTask] = userData.tasks.splice(taskIndex, 1);
- await setInDb(`todo_${userId}`, userData);
-
- return await interaction.editReply({
- embeds: [
- successEmbed("Task Removed", `Removed "${removedTask.text}" from your to-do list.`)
- ],
- });
- }
-
- default:
- return await interaction.editReply({
- embeds: [errorEmbed("Error", "Invalid subcommand.")],
- });
- }
- } catch (error) {
- console.error("Error in todo command:", error);
- await interaction.editReply({
- embeds: [errorEmbed("Error", "An error occurred while processing your request.")],
- });
- }
- },
-};
-
diff --git a/src/commands/Utility/userinfo.js b/src/commands/Utility/userinfo.js
deleted file mode 100644
index f3c78d7670..0000000000
--- a/src/commands/Utility/userinfo.js
+++ /dev/null
@@ -1,69 +0,0 @@
-import { SlashCommandBuilder, MessageFlags } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-export default {
- data: new SlashCommandBuilder()
- .setName("userinfo")
- .setDescription("Get detailed information about a user")
- .addUserOption((option) =>
- option
- .setName("target")
- .setDescription("The user to inspect (defaults to you)"),
- ),
-
- async execute(interaction) {
- try {
- const user = interaction.options.getUser("target") || interaction.user;
- const member = interaction.guild.members.cache.get(user.id);
-
- const createdTimestamp = Math.floor(user.createdAt.getTime() / 1000);
- const joinedTimestamp = member?.joinedAt ? Math.floor(member.joinedAt.getTime() / 1000) : null;
-
- const embed = createEmbed({ title: `👤 User Info: ${user.username}` })
- .setThumbnail(user.displayAvatarURL({ size: 256 }))
- .addFields(
- { name: "ID", value: user.id, inline: true },
- { name: "Bot", value: user.bot ? "Yes" : "No", inline: true },
- {
- name: "Roles",
- value:
- member && member.roles.cache.size > 1
- ? member.roles.cache
- .map((r) => r.name)
- .slice(0, 5)
- .join(", ")
- : "None",
- inline: true,
- },
- {
- name: "Account Created",
- value: ``,
- inline: false,
- },
- {
- name: "Joined Server",
- value: joinedTimestamp ? `` : "Not in server",
- inline: false,
- },
- {
- name: "Highest Role",
- value: member?.roles?.highest?.name || "None",
- inline: true,
- },
- );
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error('UserInfo command error:', error);
- const errEmbed = createEmbed({ title: 'System Error', description: 'Could not retrieve user information.' });
- if (interaction.deferred || interaction.replied) {
- await interaction.editReply({ embeds: [errEmbed] });
- } else {
- await interaction.reply({ embeds: [errEmbed], flags: MessageFlags.Ephemeral });
- }
- return;
- }
- },
-};
-
-
diff --git a/src/commands/Utility/weather.js b/src/commands/Utility/weather.js
deleted file mode 100644
index 9a5b3128a8..0000000000
--- a/src/commands/Utility/weather.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-const GEOCODING_URL = "https://geocoding-api.open-meteo.com/v1/search";
-const WEATHER_URL = "https://api.open-meteo.com/v1/forecast";
-
-export default {
- data: new SlashCommandBuilder()
- .setName("weather")
- .setDescription("Get real-time weather information for a location")
- .addStringOption((option) =>
- option
- .setName("city")
- .setDescription("The city name, e.g., 'London' or 'Tokyo'")
- .setRequired(true),
- ),
-
- async execute(interaction) {
-try {
- const city = interaction.options.getString("city");
-
- const geoResponse = await fetch(
- `${GEOCODING_URL}?name=${encodeURIComponent(city)}`,
- );
- const geoData = await geoResponse.json();
-
- if (!geoData.results || geoData.results.length === 0) {
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "City Not Found",
- `Could not find a location for **${city}**. Please check the spelling.`,
- ),
- ],
- });
- return;
- }
-
- const { latitude, longitude, name, country } = geoData.results[0];
- const cityDisplay = name;
-
- const weatherResponse = await fetch(
- `${WEATHER_URL}?latitude=${latitude}&longitude=${longitude}¤t_weather=true`,
- );
- const weatherData = await weatherResponse.json();
-
- if (weatherData.error) {
- console.error("Open-Meteo API Error:", weatherData.reason);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "API Error",
- "A weather service error occurred.",
- ),
- ],
- });
- return;
- }
-
- const current = weatherData.current || weatherData.current_weather || {};
- const temperature = current.temperature != null ? Math.round(current.temperature) : "N/A";
- const humidity = current.relativehumidity ?? current.relative_humidity_2m ?? "N/A";
- const windSpeed = current.windspeed != null ? Math.round(current.windspeed) : "N/A";
- const weatherCode = current.weathercode ?? current.weather_code ?? null;
-
- const condition = getWeatherDescription(weatherCode);
-
- const embed = createEmbed({ title: `🌎 Weather in ${cityDisplay}, ${country}`, description: condition.description })
- .addFields(
- {
- name: "ðŸŒ¡ï¸ Temperature",
- value: `${temperature}°C`,
- inline: true,
- },
- {
- name: "💧 Humidity",
- value: `${humidity}%`,
- inline: true,
- },
- {
- name: "💨 Wind Speed",
- value: `${windSpeed} km/h`,
- inline: true,
- },
- )
- .setFooter({
- text: `Latitude: ${latitude.toFixed(2)} | Longitude: ${longitude.toFixed(2)}`,
- });
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- console.error("Weather command general error:", error);
- await interaction.editReply({
- embeds: [
- errorEmbed(
- "System Error",
- "An unexpected error occurred while processing the weather request.",
- ),
- ],
- });
- }
- },
-};
-
-/**
- * Converts the WMO Weather Code (WMO_CODE) to a readable description and emoji.
- * @param {number} code
- */
-function getWeatherDescription(code) {
- if (code >= 0 && code <= 3) {
- return { description: "Clear sky / Partly cloudy ☀ï¸", emoji: "☀ï¸" };
- } else if (code >= 45 && code <= 48) {
- return { description: "Fog and Rime fog 🌫ï¸", emoji: "🌫ï¸" };
- } else if (code >= 51 && code <= 67) {
- return { description: "Drizzle or Rain 🌧ï¸", emoji: "🌧ï¸" };
- } else if (code >= 71 && code <= 75) {
- return { description: "Snow fall â„ï¸", emoji: "â„ï¸" };
- } else if (code >= 80 && code <= 86) {
- return { description: "Showers (Rain/Snow) 🌨ï¸", emoji: "🌨ï¸" };
- } else if (code >= 95 && code <= 99) {
- return { description: "Thunderstorm ⛈ï¸", emoji: "⛈ï¸" };
- }
- return { description: "Unknown conditions.", emoji: "" };
-}
-
diff --git a/src/commands/Utility/wipedata.js b/src/commands/Utility/wipedata.js
deleted file mode 100644
index 30357062d4..0000000000
--- a/src/commands/Utility/wipedata.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { SlashCommandBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, warningEmbed } from '../../utils/embeds.js';
-import { getConfirmationButtons } from '../../utils/components.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('wipedata')
- .setDescription('Delete all your personal data from the bot (irreversible)'),
-
- async execute(interaction, guildConfig, client) {
- try {
- const warningMessage =
- `âš ï¸ **THIS ACTION IS IRREVERSIBLE!** âš ï¸\n\n` +
- `This will permanently delete **ALL** your data from this server including:\n` +
- `• 💰 Economy balance (wallet & bank)\n` +
- `• 📊 Levels and XP\n` +
- `• 🎒 Inventory items\n` +
- `• ðŸ›ï¸ Shop purchases\n` +
- `• 🎂 Birthday information\n` +
- `• 🔢 Counter data\n` +
- `• 📋 All other personal data\n\n` +
- `**This cannot be undone. Are you absolutely sure?**`;
-
- const embed = warningEmbed(warningMessage, 'ðŸ—‘ï¸ Wipe All Data');
-
- const confirmButtons = getConfirmationButtons('wipedata');
-
- await interaction.reply({
- embeds: [embed],
- components: [confirmButtons],
- flags: MessageFlags.Ephemeral
- });
- } catch (error) {
- console.error('Wipedata command error:', error);
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Could not process wipedata command.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
diff --git a/src/commands/Verification/modules/autoVerify.js b/src/commands/Verification/modules/autoVerify.js
deleted file mode 100644
index ee69f52edf..0000000000
--- a/src/commands/Verification/modules/autoVerify.js
+++ /dev/null
@@ -1,208 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed } from '../../../utils/embeds.js';
-import { getGuildConfig, setGuildConfig } from '../../../services/guildConfig.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("autoverify")
- .setDescription("Configure automatic verification settings")
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
- .addSubcommand(subcommand =>
- subcommand
- .setName("enable")
- .setDescription("Enable automatic verification")
- .addStringOption(option =>
- option
- .setName("criteria")
- .setDescription("Criteria for automatic verification")
- .addChoices(
- { name: "Account Age (older than 7 days)", value: "account_age" },
- { name: "Server Members (less than 1000 members)", value: "server_size" },
- { name: "No Criteria (verify everyone)", value: "none" }
- )
- .setRequired(true)
- )
- .addIntegerOption(option =>
- option
- .setName("account_age_days")
- .setDescription("Minimum account age in days (for account age criteria)")
- .setMinValue(1)
- .setMaxValue(365)
- .setRequired(false)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("disable")
- .setDescription("Disable automatic verification")
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("status")
- .setDescription("Check automatic verification status")
- ),
-
- async execute(interaction, config, client) {
- const subcommand = interaction.options.getSubcommand();
- const guild = interaction.guild;
-
- try {
- switch (subcommand) {
- case "enable":
- return await handleEnable(interaction, guild, client);
- case "disable":
- return await handleDisable(interaction, guild, client);
- case "status":
- return await handleStatus(interaction, guild, client);
- default:
- return await interaction.reply({
- embeds: [errorEmbed("Invalid Subcommand", "Please select a valid subcommand.")],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error("AutoVerify command error:", error);
- return await interaction.reply({
- embeds: [errorEmbed("System Error", "An error occurred while processing your request.")],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-async function handleEnable(interaction, guild, client) {
- const criteria = interaction.options.getString("criteria");
- const accountAgeDays = interaction.options.getInteger("account_age_days") || 7;
-
- await interaction.deferReply();
-
- try {
- const guildConfig = await getGuildConfig(client, guild.id);
-
- if (!guildConfig.verification) {
- guildConfig.verification = {};
- }
-
- guildConfig.verification.autoVerify = {
- enabled: true,
- criteria: criteria,
- accountAgeDays: criteria === "account_age" ? accountAgeDays : null
- };
-
- await setGuildConfig(client, guild.id, guildConfig);
-
- let criteriaDescription = "";
- switch (criteria) {
- case "account_age":
- criteriaDescription = `Accounts older than ${accountAgeDays} days`;
- break;
- case "server_size":
- criteriaDescription = "All users (server has less than 1000 members)";
- break;
- case "none":
- criteriaDescription = "All users immediately";
- break;
- }
-
- await interaction.editReply({
- embeds: [successEmbed(
- "Auto-Verification Enabled",
- `Automatic verification has been enabled!\n\n**Criteria:** ${criteriaDescription}\n\nUsers who meet these criteria will be automatically verified when they join the server.`
- )]
- });
-
- } catch (error) {
- console.error("Auto-verification enable error:", error);
- await interaction.editReply({
- embeds: [errorEmbed("Failed to Enable", "Could not enable auto-verification. Please try again.")],
- });
- }
-}
-
-async function handleDisable(interaction, guild, client) {
- await interaction.deferReply();
-
- try {
- const guildConfig = await getGuildConfig(client, guild.id);
-
- if (!guildConfig.verification?.autoVerify?.enabled) {
- return await interaction.editReply({
- embeds: [infoEmbed("Already Disabled", "Auto-verification is already disabled.")],
- });
- }
-
- guildConfig.verification.autoVerify.enabled = false;
- await setGuildConfig(client, guild.id, guildConfig);
-
- await interaction.editReply({
- embeds: [successEmbed(
- "Auto-Verification Disabled",
- "Automatic verification has been disabled. Users will now need to verify manually."
- )]
- });
-
- } catch (error) {
- console.error("Auto-verification disable error:", error);
- await interaction.editReply({
- embeds: [errorEmbed("Failed to Disable", "Could not disable auto-verification. Please try again.")],
- });
- }
-}
-
-async function handleStatus(interaction, guild, client) {
- const guildConfig = await getGuildConfig(client, guild.id);
-
- if (!guildConfig.verification?.autoVerify?.enabled) {
- return await interaction.reply({
- embeds: [infoEmbed(
- "Auto-Verification Status",
- "🔴 **Status:** Disabled\n\nAuto-verification is currently disabled. Users must verify manually.\n\nUse `/autoverify enable` to enable it."
- )],
- flags: MessageFlags.Ephemeral
- });
- }
-
- const autoVerify = guildConfig.verification.autoVerify;
- let criteriaDescription = "";
-
- switch (autoVerify.criteria) {
- case "account_age":
- criteriaDescription = `Accounts older than ${autoVerify.accountAgeDays} days`;
- break;
- case "server_size":
- criteriaDescription = "All users (server has less than 1000 members)";
- break;
- case "none":
- criteriaDescription = "All users immediately";
- break;
- }
-
- const statusEmbed = createEmbed({
- title: "🤖 Auto-Verification Status",
- description: "Current auto-verification configuration:",
- color: "#00FF00"
- })
- .addFields(
- {
- name: "📊 Status",
- value: "✅ Enabled",
- inline: true
- },
- {
- name: "🎯 Criteria",
- value: criteriaDescription,
- inline: true
- },
- {
- name: "📅 Account Age Requirement",
- value: autoVerify.accountAgeDays ? `${autoVerify.accountAgeDays} days` : "N/A",
- inline: true
- }
- );
-
- await interaction.reply({
- embeds: [statusEmbed],
- flags: MessageFlags.Ephemeral
- });
-}
-
diff --git a/src/commands/Verification/verification.js b/src/commands/Verification/verification.js
deleted file mode 100644
index fb41cb8f77..0000000000
--- a/src/commands/Verification/verification.js
+++ /dev/null
@@ -1,364 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, MessageFlags } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed } from '../../utils/embeds.js';
-import { getGuildConfig, setGuildConfig } from '../../services/guildConfig.js';
-import { handleInteractionError, withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
-import { ContextualMessages, MessageTemplates } from '../../utils/messageTemplates.js';
-import { logger } from '../../utils/logger.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName("verification")
- .setDescription("Manage the server verification system")
- .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
- .addSubcommand(subcommand =>
- subcommand
- .setName("setup")
- .setDescription("Set up the verification system")
- .addChannelOption(option =>
- option
- .setName("verification_channel")
- .setDescription("Channel where verification messages will be sent")
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(true)
- )
- .addRoleOption(option =>
- option
- .setName("verified_role")
- .setDescription("Role to give to verified users")
- .setRequired(true)
- )
- .addStringOption(option =>
- option
- .setName("message")
- .setDescription("Custom verification message")
- .setRequired(false)
- )
- .addStringOption(option =>
- option
- .setName("button_text")
- .setDescription("Text for the verification button")
- .setRequired(false)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("verify")
- .setDescription("Verify yourself (for users to use)")
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("remove")
- .setDescription("Remove verification from a user")
- .addUserOption(option =>
- option
- .setName("user")
- .setDescription("User to remove verification from")
- .setRequired(true)
- )
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("disable")
- .setDescription("Disable the verification system")
- )
- .addSubcommand(subcommand =>
- subcommand
- .setName("status")
- .setDescription("Check verification system status")
- ),
-
- async execute(interaction, config, client) {
- return withErrorHandling(async () => {
- const subcommand = interaction.options.getSubcommand();
- const guild = interaction.guild;
-
- switch (subcommand) {
- case "setup":
- return await handleSetup(interaction, guild, client);
- case "verify":
- return await handleVerify(interaction, guild, client);
- case "remove":
- return await handleRemove(interaction, guild, client);
- case "disable":
- return await handleDisable(interaction, guild, client);
- case "status":
- return await handleStatus(interaction, guild, client);
- default:
- throw createError(
- `Unknown subcommand: ${subcommand}`,
- ErrorTypes.VALIDATION,
- "Please select a valid subcommand.",
- { subcommand }
- );
- }
- }, { command: 'verification', subcommand: interaction.options.getSubcommand() });
- }
-};
-
-async function handleSetup(interaction, guild, client) {
- const verificationChannel = interaction.options.getChannel("verification_channel");
- const verifiedRole = interaction.options.getRole("verified_role");
- const message = interaction.options.getString("message") || "Click the button below to verify yourself and gain access to the server!";
- const buttonText = interaction.options.getString("button_text") || "Verify";
-
- const requiredPermissions = ["SendMessages", "EmbedLinks"];
- const missingChannelPerms = requiredPermissions.filter(perm =>
- !verificationChannel.permissionsFor(guild.members.me).has(perm)
- );
-
- if (missingChannelPerms.length > 0) {
- throw createError(
- `Missing channel permissions: ${missingChannelPerms.join(', ')}`,
- ErrorTypes.PERMISSION,
- `I need the following permissions in the verification channel: ${missingChannelPerms.join(', ')}`,
- { missingPermissions: missingChannelPerms, channel: verificationChannel.id }
- );
- }
-
- if (!guild.members.me.permissions.has("ManageRoles")) {
- throw createError(
- "Missing ManageRoles permission",
- ErrorTypes.PERMISSION,
- "I need the 'Manage Roles' permission to give verified roles.",
- { missingPermission: "ManageRoles" }
- );
- }
-
- const botRole = guild.members.me.roles.highest;
- if (verifiedRole.position >= botRole.position) {
- throw createError(
- "Role hierarchy error",
- ErrorTypes.PERMISSION,
- "The verified role must be below my highest role in the server role hierarchy.",
- { rolePosition: verifiedRole.position, botRolePosition: botRole.position }
- );
- }
-
- await interaction.deferReply();
-
- const verifyEmbed = createEmbed({
- title: "✅ Server Verification",
- description: message,
- color: "#00FF00"
- });
-
- const verifyButton = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId("verify_user")
- .setLabel(buttonText)
- .setStyle(ButtonStyle.Success)
- .setEmoji("✅")
- );
-
- const verifyMessage = await verificationChannel.send({
- embeds: [verifyEmbed],
- components: [verifyButton]
- });
-
- const guildConfig = await getGuildConfig(client, guild.id);
- guildConfig.verification = {
- enabled: true,
- channelId: verificationChannel.id,
- messageId: verifyMessage.id,
- roleId: verifiedRole.id,
- message: message,
- buttonText: buttonText
- };
-
- await setGuildConfig(client, guild.id, guildConfig);
-
- await interaction.editReply({
- embeds: [ContextualMessages.configUpdated(
- "Verification System",
- [
- `Channel: ${verificationChannel}`,
- `Verified Role: ${verifiedRole}`,
- `Button Text: ${buttonText}`
- ]
- )]
- });
-}
-
-async function handleVerify(interaction, guild, client) {
- const guildConfig = await getGuildConfig(client, guild.id);
-
- if (!guildConfig.verification?.enabled) {
- throw createError(
- "Verification system is disabled",
- ErrorTypes.CONFIGURATION,
- "The verification system is not enabled on this server.",
- { feature: 'verification', status: 'disabled' }
- );
- }
-
- const verifiedRole = guild.roles.cache.get(guildConfig.verification.roleId);
- if (!verifiedRole) {
- throw createError(
- "Verified role not found",
- ErrorTypes.CONFIGURATION,
- "Verified role not found. Please contact an administrator.",
- { roleId: guildConfig.verification.roleId }
- );
- }
-
- const member = interaction.member;
-
- if (member.roles.cache.has(verifiedRole.id)) {
- return await interaction.reply({
- embeds: [MessageTemplates.INFO.ALREADY_CONFIGURED("verified")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- await member.roles.add(verifiedRole.id, "User verified themselves");
-
- await interaction.reply({
- embeds: [MessageTemplates.SUCCESS.ACTION_COMPLETE(
- `You have been verified and given the **${verifiedRole.name}** role! Welcome to the server! 🎉`
- )],
- flags: MessageFlags.Ephemeral
- });
-}
-
-async function handleRemove(interaction, guild, client) {
- const targetUser = interaction.options.getUser("user");
- const targetMember = guild.members.cache.get(targetUser.id);
-
- if (!targetMember) {
- throw createError(
- `User ${targetUser.tag} not found in guild`,
- ErrorTypes.USER_INPUT,
- `The specified user is not in this server.`,
- { userId: targetUser.id, userTag: targetUser.tag }
- );
- }
-
- const guildConfig = await getGuildConfig(client, guild.id);
-
- if (!guildConfig.verification?.enabled) {
- throw createError(
- "Verification system is disabled",
- ErrorTypes.CONFIGURATION,
- "The verification system is not enabled on this server.",
- { feature: 'verification', status: 'disabled' }
- );
- }
-
- const verifiedRole = guild.roles.cache.get(guildConfig.verification.roleId);
- if (!verifiedRole) {
- throw createError(
- "Verified role not found",
- ErrorTypes.CONFIGURATION,
- "Verified role not found. Please contact an administrator.",
- { roleId: guildConfig.verification.roleId }
- );
- }
-
- if (!targetMember.roles.cache.has(verifiedRole.id)) {
- return await interaction.reply({
- embeds: [MessageTemplates.INFO.NO_DATA("verification role on this user")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- await targetMember.roles.remove(verifiedRole.id, `Verification removed by ${interaction.user.tag}`);
-
- await interaction.reply({
- embeds: [MessageTemplates.SUCCESS.OPERATION_COMPLETE(
- `Successfully removed verification from ${targetUser.tag}.`
- )]
- });
-}
-
-async function handleDisable(interaction, guild, client) {
- const guildConfig = await getGuildConfig(client, guild.id);
-
- if (!guildConfig.verification?.enabled) {
- return await interaction.reply({
- embeds: [MessageTemplates.INFO.ALREADY_CONFIGURED("disabled")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- await interaction.deferReply();
-
- if (guildConfig.verification.channelId && guildConfig.verification.messageId) {
- const channel = guild.channels.cache.get(guildConfig.verification.channelId);
- if (channel) {
- try {
- const message = await channel.messages.fetch(guildConfig.verification.messageId);
- if (message) {
- await message.delete();
- }
- } catch (error) {
- logger.info("Could not delete verification message (may have been deleted already):", error.message);
- }
- }
- }
-
- guildConfig.verification.enabled = false;
- await setGuildConfig(client, guild.id, guildConfig);
-
- await interaction.editReply({
- embeds: [MessageTemplates.SUCCESS.OPERATION_COMPLETE(
- "The verification system has been disabled and the verification message has been removed."
- )]
- });
-}
-
-async function handleStatus(interaction, guild, client) {
- const guildConfig = await getGuildConfig(client, guild.id);
-
- if (!guildConfig.verification?.enabled) {
- return await interaction.reply({
- embeds: [infoEmbed(
- "Verification Status",
- "🔴 **Status:** Disabled\n\nThe verification system is not currently enabled on this server.\n\nUse `/verification setup` to enable it."
- )],
- flags: MessageFlags.Ephemeral
- });
- }
-
- const verificationChannel = guild.channels.cache.get(guildConfig.verification.channelId);
- const verifiedRole = guild.roles.cache.get(guildConfig.verification.roleId);
-
- const statusEmbed = createEmbed({
- title: "✅ Verification System Status",
- description: "Current verification system configuration:",
- color: "#00FF00"
- })
- .addFields(
- {
- name: "📢 Verification Channel",
- value: verificationChannel ? verificationChannel.toString() : "Not found",
- inline: true
- },
- {
- name: "ðŸ·ï¸ Verified Role",
- value: verifiedRole ? verifiedRole.toString() : "Not found",
- inline: true
- },
- {
- name: "🔘 Button Text",
- value: guildConfig.verification.buttonText || "Verify",
- inline: true
- },
- {
- name: "📠Custom Message",
- value: guildConfig.verification.message ? "✅ Configured" : "⌠Not set",
- inline: true
- },
- {
- name: "👥 Verified Users",
- value: verifiedRole ? `${verifiedRole.members.size} users` : "Unknown",
- inline: true
- }
- );
-
- await interaction.reply({
- embeds: [statusEmbed],
- flags: MessageFlags.Ephemeral
- });
-}
-
-
diff --git a/src/commands/Voice/activity.js b/src/commands/Voice/activity.js
deleted file mode 100644
index 6fb53bdf55..0000000000
--- a/src/commands/Voice/activity.js
+++ /dev/null
@@ -1,162 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-
-const ACTIVITIES = {
- 'youtube': '880218394199220334',
- 'poker': '755827207812677713',
- 'chess': '832012774040141894',
- 'checkers': '832013003968348200',
- 'letter-league': '879863686565621790',
- 'spellcast': '852509694341283871',
- 'sketch': '902271654783242291',
- 'blazing8s': '832025144389533716',
- 'puttparty': '945737671223947305',
- 'landio': '903769130790969345',
- 'bobble': '947957217959759964',
- 'knowwhat': '976052223358406656'
-};
-
-const ACTIVITY_NAMES = {
- 'youtube': 'YouTube Together',
- 'poker': 'Poker Night',
- 'chess': 'Chess in the Park',
- 'checkers': 'Checkers in the Park',
- 'letter-league': 'Letter League',
- 'spellcast': 'SpellCast',
- 'sketch': 'Sketch Heads',
- 'blazing8s': 'Blazing 8s',
- 'puttparty': 'Putt Party',
- 'landio': 'Land-io',
- 'bobble': 'Bobble League',
- 'knowwhat': 'Know What I Mean'
-};
-
-export default {
- data: new SlashCommandBuilder()
- .setName('activity')
- .setDescription('Start a Discord Activity in your voice channel')
- .setDMPermission(false)
- .setDefaultMemberPermissions(PermissionFlagsBits.Connect)
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('youtube')
- .setDescription('Watch YouTube videos together in a voice channel')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('poker')
- .setDescription('Play Poker Night with friends')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('chess')
- .setDescription('Play Chess in the Park')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('checkers')
- .setDescription('Play Checkers in the Park')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('letter-league')
- .setDescription('Play the word-based game Letter League')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('spellcast')
- .setDescription('Play the magical word game SpellCast')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('sketch')
- .setDescription('Play Sketch Heads (Pictionary style)')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('blazing8s')
- .setDescription('Play the card game Blazing 8s')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('puttparty')
- .setDescription('Play Putt Party (Mini-golf)')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('landio')
- .setDescription('Play the territory game Land-io')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('bobble')
- .setDescription('Play Bobble League')
- )
-
- .addSubcommand(subcommand =>
- subcommand
- .setName('knowwhat')
- .setDescription('Play Know What I Mean')
- ),
-
- category: "Fun",
-
- async execute(interaction, config, client) {
- const { member, options } = interaction;
- const activity = options.getSubcommand();
- const activityId = ACTIVITIES[activity];
- const activityName = ACTIVITY_NAMES[activity] || activity;
-
- if (!member.voice.channel) {
- return await interaction.editReply({
- embeds: [errorEmbed('You need to be in a voice channel to start an activity!')]
- });
- }
-
- const permissions = member.voice.channel.permissionsFor(interaction.guild.members.me);
- if (!permissions.has('CreateInstantInvite')) {
- return await interaction.editReply({
- embeds: [errorEmbed('I need the `Create Invite` permission to start an activity!')]
- });
- }
-
- try {
- const invite = await interaction.client.rest.post(
- `/channels/${member.voice.channel.id}/invites`,
- {
- body: {
-max_age: 86400,
-target_type: 2,
- target_application_id: activityId,
- },
- }
- );
-
- await interaction.editReply({
- embeds: [successEmbed(
- `Click the link below to start **${activityName}** in ${member.voice.channel.name}!\n` +
- `[Join ${activityName} Activity](https://discord.gg/${invite.code})`
- )]
- });
-
- } catch (error) {
- console.error('Error creating activity invite:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Failed to create the activity. Please try again later.')]
- });
- }
- },
-};
-
diff --git a/src/commands/Welcome/autorole.js b/src/commands/Welcome/autorole.js
deleted file mode 100644
index 6be538ced4..0000000000
--- a/src/commands/Welcome/autorole.js
+++ /dev/null
@@ -1,166 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, EmbedBuilder } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getWelcomeConfig, updateWelcomeConfig } from '../../utils/database.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('autorole')
- .setDescription('Manage roles that are automatically assigned to new members')
- .setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
- .addSubcommand(subcommand =>
- subcommand
- .setName('add')
- .setDescription('Add a role to be automatically assigned to new members')
- .addRoleOption(option =>
- option.setName('role')
- .setDescription('The role to add')
- .setRequired(true)))
- .addSubcommand(subcommand =>
- subcommand
- .setName('remove')
- .setDescription('Remove a role from auto-assignment')
- .addRoleOption(option =>
- option.setName('role')
- .setDescription('The role to remove')
- .setRequired(true)))
- .addSubcommand(subcommand =>
- subcommand
- .setName('list')
- .setDescription('List all auto-assigned roles')),
-
- async execute(interaction) {
-const { options, guild, client } = interaction;
- const subcommand = options.getSubcommand();
-
- if (subcommand === 'add') {
- const role = options.getRole('role');
-
- if (role.position >= guild.members.me.roles.highest.position) {
- return interaction.reply({
- content: "⌠I can't assign roles that are higher than my highest role.",
- flags: ["Ephemeral"]
- });
- }
-
- try {
- const config = await getWelcomeConfig(client, guild.id);
- const autoRoles = new Set(config.roleIds || []);
-
- if (autoRoles.has(role.id)) {
- return interaction.editReply({
- content: `⌠The role ${role} is already set to be auto-assigned.`,
- flags: ["Ephemeral"]
- });
- }
-
- autoRoles.add(role.id);
-
- await updateWelcomeConfig(client, guild.id, {
- roleIds: Array.from(autoRoles)
- });
-
- await interaction.editReply({
- content: `✅ Added ${role} to auto-assigned roles.`,
- flags: ["Ephemeral"]
- });
- } catch (error) {
- await interaction.editReply({
- content: '⌠An error occurred while adding the role.',
- flags: ["Ephemeral"]
- });
- }
- }
-
- else if (subcommand === 'remove') {
- const role = options.getRole('role');
-
- try {
- const config = await getWelcomeConfig(client, guild.id);
- const autoRoles = new Set(config.roleIds || []);
-
- if (!autoRoles.has(role.id)) {
- return interaction.editReply({
- content: `⌠The role ${role} is not set to be auto-assigned.`,
- flags: ["Ephemeral"]
- });
- }
-
- autoRoles.delete(role.id);
-
- await updateWelcomeConfig(client, guild.id, {
- roleIds: Array.from(autoRoles)
- });
-
- await interaction.editReply({
- content: `✅ Removed ${role} from auto-assigned roles.`,
- flags: ["Ephemeral"]
- });
- } catch (error) {
- await interaction.editReply({
- content: '⌠An error occurred while removing the role.',
- flags: ["Ephemeral"]
- });
- }
- }
-
- else if (subcommand === 'list') {
- try {
- const config = await getWelcomeConfig(client, guild.id);
- const autoRoles = Array.isArray(config.roleIds) ? config.roleIds : [];
-
- if (autoRoles.length === 0) {
- return interaction.editReply({
- content: 'â„¹ï¸ No roles are set to be auto-assigned.',
- flags: ["Ephemeral"]
- });
- }
-
- const roles = await guild.roles.fetch();
- const validRoles = [];
- const invalidRoleIds = [];
-
- for (const roleId of autoRoles) {
- const role = roles.get(roleId);
- if (role) {
- validRoles.push(role);
- } else {
- invalidRoleIds.push(roleId);
- }
- }
-
- if (invalidRoleIds.length > 0) {
- const updatedRoles = autoRoles.filter(id => !invalidRoleIds.includes(id));
- await updateWelcomeConfig(client, guild.id, {
- roleIds: updatedRoles
- });
- }
-
- if (validRoles.length === 0) {
- return interaction.editReply({
- content: 'â„¹ï¸ No valid auto-assigned roles found. Any invalid roles have been removed.',
- flags: ["Ephemeral"]
- });
- }
-
- const embed = new EmbedBuilder()
-.setColor(0x0099ff)
- .setTitle('Auto-Assigned Roles')
- .setDescription(validRoles.map(r => `• ${r}`).join('\n'))
- .setFooter({ text: `Total: ${validRoles.length} role(s)` });
-
- await interaction.editReply({
- embeds: [embed],
- flags: ["Ephemeral"]
- });
-
- } catch (error) {
- await interaction.editReply({
- content: '⌠An error occurred while listing auto-assigned roles.',
- flags: ["Ephemeral"]
- });
- }
- }
- },
-};
-
diff --git a/src/commands/Welcome/goodbye.js b/src/commands/Welcome/goodbye.js
deleted file mode 100644
index ab8c0559bb..0000000000
--- a/src/commands/Welcome/goodbye.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, PermissionsBitField, ChannelType, EmbedBuilder } from 'discord.js';
-import { createEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getWelcomeConfig, updateWelcomeConfig } from '../../utils/database.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('goodbye')
- .setDescription('Configure the goodbye message system')
- .setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
- .addSubcommand(subcommand =>
- subcommand
- .setName('setup')
- .setDescription('Set up the goodbye message')
- .addChannelOption(option =>
- option.setName('channel')
- .setDescription('The channel to send goodbye messages to')
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(true))
- .addStringOption(option =>
- option.setName('message')
- .setDescription('Goodbye message. Variables: {user}, {username}, {server}, {memberCount}')
- .setRequired(true))
- .addStringOption(option =>
- option.setName('image')
- .setDescription('URL of the image to include in the goodbye message')
- .setRequired(false)))
- .addSubcommand(subcommand =>
- subcommand
- .setName('toggle')
- .setDescription('Enable or disable goodbye messages')),
-
- async execute(interaction) {
-const { options, guild, client } = interaction;
- const subcommand = options.getSubcommand();
-
- if (subcommand === 'setup') {
- const channel = options.getChannel('channel');
- const message = options.getString('message');
- const image = options.getString('image');
-
- try {
- await updateWelcomeConfig(client, guild.id, {
- goodbyeEnabled: true,
- goodbyeChannelId: channel.id,
- leaveMessage: message,
- leaveEmbed: {
- title: "Goodbye {user.tag}",
- description: message,
-color: 0xff0000,
- ...(image && { image: { url: image } })
- }
- });
-
- const previewMessage = message
- .replace(/{user}/g, interaction.user.tag)
- .replace(/{username}/g, interaction.user.username)
- .replace(/{server}/g, guild.name)
- .replace(/{memberCount}/g, guild.memberCount.toLocaleString());
-
- const embed = new EmbedBuilder()
-.setColor(0x00ff00)
- .setTitle('✅ Goodbye System Configured')
- .setDescription(`Goodbye messages will now be sent to ${channel}`)
- .addFields(
- { name: 'Message Preview', value: previewMessage },
- { name: 'Status', value: '✅ Enabled' }
- )
- .setFooter({ text: 'Tip: Use /goodbye toggle to enable/disable goodbye messages' });
-
- if (image) {
- embed.setImage(image);
- }
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- await interaction.editReply({
- content: '⌠An error occurred while setting up the goodbye system.',
- flags: ["Ephemeral"]
- });
- }
- }
-
- else if (subcommand === 'toggle') {
- try {
- const currentConfig = await getWelcomeConfig(client, guild.id);
- const newStatus = !currentConfig.goodbyeEnabled;
-
- await updateWelcomeConfig(client, guild.id, {
- goodbyeEnabled: newStatus
- });
-
- await interaction.editReply({
- content: `✅ Goodbye messages have been ${newStatus ? 'enabled' : 'disabled'}.`,
- flags: ["Ephemeral"]
- });
- } catch (error) {
- await interaction.editReply({
- content: '⌠An error occurred while toggling goodbye messages.',
- flags: ["Ephemeral"]
- });
- }
- }
- },
-};
-
diff --git a/src/commands/Welcome/welcome.js b/src/commands/Welcome/welcome.js
deleted file mode 100644
index a9576cf3ed..0000000000
--- a/src/commands/Welcome/welcome.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, EmbedBuilder } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../../utils/embeds.js';
-import { getPromoRow } from '../../utils/components.js';
-import { getWelcomeConfig, updateWelcomeConfig } from '../../utils/database.js';
-
-export default {
- data: new SlashCommandBuilder()
- .setName('welcome')
- .setDescription('Configure the welcome system')
- .setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
- .addSubcommand(subcommand =>
- subcommand
- .setName('setup')
- .setDescription('Set up the welcome message')
- .addChannelOption(option =>
- option.setName('channel')
- .setDescription('The channel to send welcome messages to')
- .addChannelTypes(ChannelType.GuildText)
- .setRequired(true))
- .addStringOption(option =>
- option.setName('message')
- .setDescription('Welcome message. Variables: {user}, {username}, {server}, {memberCount}')
- .setRequired(true))
- .addStringOption(option =>
- option.setName('image')
- .setDescription('URL of the image to include in the welcome message')
- .setRequired(false))
- .addBooleanOption(option =>
- option.setName('ping')
- .setDescription('Whether to ping the user in the welcome message')
- .setRequired(false)))
- .addSubcommand(subcommand =>
- subcommand
- .setName('toggle')
- .setDescription('Enable or disable welcome messages')),
-
- async execute(interaction) {
-const { options, guild, client } = interaction;
- const subcommand = options.getSubcommand();
-
- if (subcommand === 'setup') {
- const channel = options.getChannel('channel');
- const message = options.getString('message');
- const image = options.getString('image');
- const ping = options.getBoolean('ping') ?? false;
-
- try {
- const newConfig = await updateWelcomeConfig(client, guild.id, {
- enabled: true,
- channelId: channel.id,
- welcomeMessage: message,
- welcomeImage: image || undefined,
- welcomePing: ping
- });
-
- const previewMessage = message
- .replace(/{user}/g, interaction.user.toString())
- .replace(/{username}/g, interaction.user.username)
- .replace(/{server}/g, guild.name)
- .replace(/{memberCount}/g, guild.memberCount.toLocaleString());
-
- const embed = new EmbedBuilder()
-.setColor(0x00ff00)
- .setTitle('✅ Welcome System Configured')
- .setDescription(`Welcome messages will now be sent to ${channel}`)
- .addFields(
- { name: 'Message Preview', value: previewMessage },
- { name: 'Ping User', value: ping ? '✅ Yes' : '⌠No' },
- { name: 'Status', value: '✅ Enabled' }
- )
- .setFooter({ text: 'Tip: Use /welcome toggle to enable/disable welcome messages' });
-
- if (image) {
- embed.setImage(image);
- }
-
- await interaction.editReply({ embeds: [embed] });
- } catch (error) {
- await interaction.editReply({
- content: '⌠An error occurred while setting up the welcome system.',
- flags: ["Ephemeral"]
- });
- }
- }
-
- else if (subcommand === 'toggle') {
- try {
- const currentConfig = await getWelcomeConfig(client, guild.id);
- const newStatus = !currentConfig.enabled;
-
- await updateWelcomeConfig(client, guild.id, {
- enabled: newStatus
- });
-
- await interaction.editReply({
- content: `✅ Welcome messages have been ${newStatus ? 'enabled' : 'disabled'}.`,
- flags: ["Ephemeral"]
- });
- } catch (error) {
- await interaction.editReply({
- content: '⌠An error occurred while toggling welcome messages.',
- flags: ["Ephemeral"]
- });
- }
- }
- },
-};
-
diff --git a/src/config/bot.js b/src/config/bot.js
deleted file mode 100644
index 190c5a2af4..0000000000
--- a/src/config/bot.js
+++ /dev/null
@@ -1,331 +0,0 @@
-/**
- * Bot Configuration
- * Centralized configuration for Titan Bot
- */
-
-import { ActivityType } from "discord.js";
-import { logger } from '../utils/logger.js';
-
-// Default bot configuration
-export const botConfig = {
- // Bot presence and status
- presence: {
- status: "online",
- activities: [
- {
- name: "/help | Titan Bot",
- type: ActivityType.Playing,
- },
- ],
- },
-
- // Command settings
- commands: {
- prefix: "!",
- owners: process.env.OWNER_IDS?.split(",") || [],
- defaultCooldown: 3, // seconds
- deleteCommands: false,
- testGuildId: process.env.TEST_GUILD_ID,
- },
-
- // Legacy prefix for backward compatibility
- prefix: "!",
-
- // Application system settings
- applications: {
- defaultQuestions: [
- { question: "What is your name?", required: true },
- { question: "How old are you?", required: true },
- { question: "Why do you want to join?", required: true },
- ],
- statusColors: {
- pending: "#FFA500",
- approved: "#00FF00",
- denied: "#FF0000",
- },
- applicationCooldown: 24, // hours
- deleteDeniedAfter: 7, // days
- deleteApprovedAfter: 30, // days
- managerRoles: [], // Will be populated from environment or database
- },
-
- // Embed theming and color scheme
- embeds: {
- colors: {
- // Primary brand colors
- primary: "#5865F2", // Discord blurple
- secondary: "#2F3136", // Dark theme background
-
- // Status colors
- success: "#57F287", // Green
- error: "#ED4245", // Red
- warning: "#FEE75C", // Yellow/Orange
- info: "#3498DB", // Blue
-
- // Grayscale
- light: "#FFFFFF",
- dark: "#202225",
- gray: "#99AAB5",
-
- // Discord-specific colors
- blurple: "#5865F2",
- green: "#57F287",
- yellow: "#FEE75C",
- fuchsia: "#EB459E",
- red: "#ED4245",
- black: "#000000",
-
- // System-specific colors
- giveaway: {
- active: "#57F287",
- ended: "#ED4245",
- },
- ticket: {
- open: "#57F287",
- claimed: "#FAA61A",
- closed: "#ED4245",
- pending: "#99AAB5",
- },
- economy: "#F1C40F",
- birthday: "#E91E63",
- moderation: "#9B59B6",
-
- // Priority levels
- priority: {
- none: "#95A5A6",
- low: "#3498db",
- medium: "#2ecc71",
- high: "#f1c40f",
- urgent: "#e74c3c",
- },
- },
- footer: {
- text: "Titan Bot",
- icon: null,
- },
- thumbnail: null,
- author: {
- name: null,
- icon: null,
- url: null,
- },
- },
-
- // Economy settings
- economy: {
- currency: {
- name: "coins",
- namePlural: "coins",
- symbol: "$",
- },
- startingBalance: 0,
- baseBankCapacity: 100000,
- dailyAmount: 100,
- workMin: 10,
- workMax: 100,
- begMin: 5,
- begMax: 50,
- robSuccessRate: 0.4,
- robFailJailTime: 3600000, // 1 hour in ms
- },
-
- // Shop settings
- shop: {
- // Will be imported from shop/index.js
- },
-
- // Ticket system settings
- tickets: {
- defaultCategory: null,
- supportRoles: [],
- priorities: {
- none: {
- emoji: "⚪",
- color: "#95A5A6",
- label: "None",
- },
- low: {
- emoji: "🟢",
- color: "#2ECC71",
- label: "Low",
- },
- medium: {
- emoji: "🟡",
- color: "#F1C40F",
- label: "Medium",
- },
- high: {
- emoji: "🔴",
- color: "#E74C3C",
- label: "High",
- },
- urgent: {
- emoji: "🚨",
- color: "#E91E63",
- label: "Urgent",
- },
- },
- defaultPriority: "none",
- archiveCategory: null,
- logChannel: null,
- },
-
- // Giveaway system settings
- giveaways: {
- defaultDuration: 86400000, // 24 hours
- minimumWinners: 1,
- maximumWinners: 10,
- minimumDuration: 300000, // 5 minutes
- maximumDuration: 2592000000, // 30 days
- allowedRoles: [],
- bypassRoles: [],
- },
-
- // Birthday system settings
- birthday: {
- defaultRole: null,
- announcementChannel: null,
- timezone: "UTC",
- },
-
- // Welcome system settings
- welcome: {
- defaultWelcomeMessage:
- "Welcome {user} to {server}! We now have {memberCount} members!",
- defaultGoodbyeMessage:
- "{user} has left the server. We now have {memberCount} members.",
- defaultWelcomeChannel: null,
- defaultGoodbyeChannel: null,
- },
-
- // Counter system settings
- counters: {
- defaults: {
- name: "{name} Counter",
- description: "Server {name} counter",
- type: "voice",
- channelName: "{name}-{count}",
- },
- permissions: {
- deny: ["VIEW_CHANNEL"],
- allow: ["VIEW_CHANNEL", "CONNECT", "SPEAK"],
- },
- messages: {
- created: "✅ Created counter **{name}**",
- deleted: "ðŸ—‘ï¸ Deleted counter **{name}**",
- updated: "🔄 Updated counter **{name}**",
- },
- types: {
- members: {
- name: "👥 Members",
- description: "Total members in the server",
- getCount: (guild) => guild.memberCount.toString(),
- },
- bots: {
- name: "🤖 Bots",
- description: "Total bot accounts in the server",
- getCount: (guild) =>
- guild.members.cache.filter((m) => m.user.bot).size.toString(),
- },
- members_only: {
- name: "👤 Humans",
- description: "Total human members (non-bots)",
- getCount: (guild) =>
- guild.members.cache.filter((m) => !m.user.bot).size.toString(),
- },
- },
- },
-
- // System messages
- messages: {
- noPermission: "You do not have permission to use this command.",
- cooldownActive: "Please wait {time} before using this command again.",
- errorOccurred: "An error occurred while executing this command.",
- missingPermissions:
- "I am missing required permissions to perform this action.",
- commandDisabled: "This command has been disabled.",
- maintenanceMode: "The bot is currently in maintenance mode.",
- },
-
- // Feature toggles
- features: {
- music: false,
- economy: true,
- tickets: true,
- giveaways: true,
- birthday: true,
- moderation: true,
- logging: true,
- welcome: true,
- },
-};
-
-/**
- * Validates the bot configuration
- * @param {Object} config - The configuration to validate
- * @returns {string[]} Array of error messages, empty if valid
- */
-export function validateConfig(config) {
- const errors = [];
-
- // Debug: Log environment variables (without sensitive data)
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Environment variables check:');
- logger.debug('DISCORD_TOKEN exists:', !!process.env.DISCORD_TOKEN);
- logger.debug('TOKEN exists:', !!process.env.TOKEN);
- logger.debug('CLIENT_ID exists:', !!process.env.CLIENT_ID);
- logger.debug('GUILD_ID exists:', !!process.env.GUILD_ID);
- logger.debug('REDIS_URL exists:', !!process.env.REDIS_URL);
- logger.debug('NODE_ENV:', process.env.NODE_ENV);
- }
-
- if (!process.env.DISCORD_TOKEN && !process.env.TOKEN) {
- errors.push("Bot token is required (DISCORD_TOKEN or TOKEN environment variable)");
- }
-
- if (!process.env.CLIENT_ID) {
- errors.push("Client ID is required (CLIENT_ID environment variable)");
- }
-
- // Add more validations as needed
-
- return errors;
-}
-
-// Validate the configuration when imported
-const configErrors = validateConfig(botConfig);
-if (configErrors.length > 0) {
- console.error("Bot configuration errors:", configErrors.join("\n"));
- if (process.env.NODE_ENV === "production") {
- process.exit(1);
- }
-}
-
-// Export as BotConfig for backward compatibility
-export const BotConfig = botConfig;
-
-// Theme helper functions
-export function getColor(path, fallback = "#99AAB5") {
- // If a numeric color or a hex string is provided directly, return it as-is
- if (typeof path === "number") return path;
- if (typeof path === "string" && path.startsWith("#")) {
- // Convert hex string to integer expected by Discord.js Embed#setColor
- return parseInt(path.replace("#", ""), 16);
- }
- return path
- .split(".")
- .reduce(
- (obj, key) => (obj && obj[key] !== undefined ? obj[key] : fallback),
- botConfig.embeds.colors,
- );
-}
-
-export function getRandomColor() {
- const colors = Object.values(botConfig.embeds.colors).flatMap((color) =>
- typeof color === "string" ? color : Object.values(color),
- );
- return colors[Math.floor(Math.random() * colors.length)];
-}
-
-export default botConfig;
-
diff --git a/src/config/index.js b/src/config/index.js
deleted file mode 100644
index eb89338e76..0000000000
--- a/src/config/index.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { fileURLToPath } from "url";
-import path from "path";
-import botConfig, { validateConfig } from "./bot.js";
-import { shopConfig as shop } from "./shop/index.js";
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-/**
- * Main configuration object for the application
- * Combines environment variables, bot config, and other settings
- */
-const config = {
- paths: {
- root: path.join(__dirname, "../.."),
- commands: path.join(__dirname, "../src/commands"),
- events: path.join(__dirname, "../events"),
- config: __dirname,
- utils: path.join(__dirname, "../utils"),
- services: path.join(__dirname, "../services"),
- models: path.join(__dirname, "../models"),
- },
-
- bot: {
- ...botConfig,
- token: process.env.DISCORD_TOKEN || process.env.TOKEN,
- clientId: process.env.CLIENT_ID,
- guildId: process.env.GUILD_ID,
-
- shop: {
- ...botConfig.shop,
- ...shop,
- },
- },
-
- database: {
- url: process.env.DATABASE_URL || "replit",
- options: {
- useNewUrlParser: true,
- useUnifiedTopology: true,
- connectTimeoutMS: 10000,
- retryWrites: true,
- w: "majority",
- },
-
- collections: {
- users: "users",
- guilds: "guilds",
- economy: "economy",
- shop: "shop",
- tickets: "tickets",
- moderation: "moderation",
- leveling: "leveling",
- giveaways: "giveaways",
- },
- },
-
- logging: {
- level: process.env.LOG_LEVEL || "info",
- file: {
- enabled: process.env.LOG_TO_FILE === "true",
- path: path.join(__dirname, "../../logs"),
- maxSize: "20m",
- maxFiles: "14d",
- zippedArchive: true,
- },
- console: {
- enabled: true,
- colorize: true,
- timestamp: true,
- },
- sentry: {
- enabled: process.env.SENTRY_DSN ? true : false,
- dsn: process.env.SENTRY_DSN,
- environment: process.env.NODE_ENV || "development",
- },
- },
-
- api: {
- port: process.env.PORT || 3000,
- cors: {
- origin: process.env.CORS_ORIGIN?.split(",") || "*",
- methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
- allowedHeaders: ["Content-Type", "Authorization"],
- },
- rateLimit: {
-windowMs: 15 * 60 * 1000,
-max: 100,
- },
- },
-
- shop,
-
- features: {
- economy: true,
- leveling: true,
- moderation: true,
- tickets: true,
- giveaways: true,
- music: false,
- welcome: true,
- logging: true,
- },
-
- env: process.env.NODE_ENV || "development",
- isProduction: process.env.NODE_ENV === "production",
- isDevelopment: process.env.NODE_ENV !== "production",
-};
-
-Object.freeze(config);
-
-export default config;
-
diff --git a/src/config/postgres.js b/src/config/postgres.js
deleted file mode 100644
index ff4246b141..0000000000
--- a/src/config/postgres.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/**
- * PostgreSQL Configuration for Titan Bot
- * Centralizes PostgreSQL connection settings and options
- */
-
-export const pgConfig = {
- url: process.env.POSTGRES_URL || 'postgresql://localhost:5432/titanbot',
-
- options: {
- host: process.env.POSTGRES_HOST || 'localhost',
-
- port: parseInt(process.env.POSTGRES_PORT) || 5432,
-
- database: process.env.POSTGRES_DB || 'titanbot',
-
- user: process.env.POSTGRES_USER || 'postgres',
-
- password: (process.env.POSTGRES_PASSWORD || '').toString(),
-
- ssl: process.env.POSTGRES_SSL === 'true' ? {
- rejectUnauthorized: false
- } : false,
-
- connectionTimeoutMillis: 10000,
-
- idleTimeoutMillis: 30000,
-
- max: 20,
-
- min: 2,
-
- idleTimeoutMillis: 30000,
-
- acquireTimeoutMillis: 10000,
-
- retries: 3,
-
- backoffBase: 100,
- backoffMultiplier: 2,
- },
-
- tables: {
- guilds: 'guilds',
- users: 'users',
- guild_users: 'guild_users',
- birthdays: 'birthdays',
- giveaways: 'giveaways',
- tickets: 'ticket_data',
- afk_status: 'afk_status',
- welcome_configs: 'welcome_configs',
- leveling_configs: 'leveling_configs',
- user_levels: 'user_levels',
- economy: 'economy',
- invite_tracking: 'invite_tracking',
- application_roles: 'application_roles',
- temp_data: 'temp_data',
- cache_data: 'cache_data',
- },
-
- defaultTTL: {
- userSession: 86400,
-
- temp: 3600,
-
- cache: 1800,
-
- guildConfig: null,
-
- economy: null,
-
- leveling: null,
-
- giveaway: null,
-
- ticket: 604800,
-
- afk: 86400,
-
- welcome: null,
-
- birthday: null,
- },
-
- features: {
- pooling: true,
-
- ssl: process.env.POSTGRES_SSL === 'true',
-
- metrics: true,
-
- debug: process.env.NODE_ENV === 'development',
-
- autoCreateTables: true,
-
- autoMigrate: false,
- },
-
- healthCheck: {
- enabled: true,
-
- interval: 30000,
-
- maxFailures: 3,
-
- query: 'SELECT 1',
- },
-
- migration: {
- enabled: false,
-
- table: 'migrations',
-
- directory: 'database/migrations',
-
- rollbackOnFailure: false,
- }
-};
-
-export default pgConfig;
-
diff --git a/src/config/shop/index.js b/src/config/shop/index.js
deleted file mode 100644
index ca596dd6e8..0000000000
--- a/src/config/shop/index.js
+++ /dev/null
@@ -1,195 +0,0 @@
-/**
- * Shop Configuration Module
- * Centralized configuration for the shop system
- */
-
-import { shopItems, getItemById, getItemsByType, getItemPrice, validatePurchase } from './items.js';
-import { botConfig } from '../bot.js';
-
-const { currency } = botConfig.economy;
-
-export const shopConfig = {
- name: 'TitanBot Shop',
- currency: currency.name,
- currencyName: currency.name,
- currencyNamePlural: currency.namePlural || `${currency.name}s`,
- currencySymbol: currency.symbol || '💵',
-
- categories: [
- {
- id: 'consumables',
- name: 'Consumables',
- description: 'One-time use items that provide temporary benefits',
- icon: 'ðŸ¯',
- itemTypes: ['consumable']
- },
- {
- id: 'upgrades',
- name: 'Upgrades',
- description: 'Permanent upgrades that enhance your abilities',
- icon: 'âš¡',
- itemTypes: ['upgrade']
- },
- {
- id: 'tools',
- name: 'Tools',
- description: 'Equipment that helps you gather resources more efficiently',
- icon: 'â›ï¸',
- itemTypes: ['tool']
- },
- {
- id: 'roles',
- name: 'Roles',
- description: 'Special roles with unique perks',
- icon: 'ðŸŽ',
- itemTypes: ['role']
- }
- ],
-
- transaction: {
-cooldown: 1000,
-maxQuantity: 10,
-confirmTimeout: 30000,
-
- refundPolicy: {
- enabled: true,
-window: 300000,
-fee: 0.1
- }
- },
-
- ui: {
- itemsPerPage: 5,
- showOutOfStock: true,
- showOwnedItems: true,
- showAffordability: true,
-
- colors: {
-primary: '#5865F2',
-success: '#43B581',
-error: '#F04747',
-warning: '#FAA61A',
-info: '#00B0F4',
-
- rarity: {
-common: '#99AAB5',
-uncommon: '#2ECC71',
-rare: '#3498DB',
-epic: '#9B59B6',
-legendary: '#F1C40F',
-mythic: '#E74C3C'
- }
- },
-
- emojis: {
- currency: '🪙',
- quantity: '✖ï¸',
- price: '💵',
- owned: '✅',
- outOfStock: 'âŒ',
-
- types: {
- consumable: 'ðŸ¯',
- upgrade: 'âš¡',
- tool: 'â›ï¸',
- role: 'ðŸŽ'
- }
- }
- },
-
- events: {
- restock: {
- enabled: true,
-interval: 86400000,
-announcementChannel: null,
- message: '🛒 **Shop Restocked!** New items are now available!'
- },
-
- sales: {
- enabled: true,
- schedule: [
- {
-day: 0,
-discount: 0.2,
- message: '🔥 **Weekend Sale!** 20% off all items!'
- },
- ]
- }
- }
-};
-
-export {
- shopItems,
- getItemById,
- getItemsByType,
- getItemPrice,
- validatePurchase
-};
-
-/**
- * Get the current price of an item, considering any active sales or discounts
- * @param {string} itemId - The ID of the item
- * @param {Object} [options] - Additional options
- * @param {number} [options.quantity=1] - The quantity being purchased
- * @param {Object} [options.userData] - The user's data for personalized pricing
- * @returns {number} The final price after applying discounts
- */
-export function getCurrentPrice(itemId, { quantity = 1, userData = null } = {}) {
- const basePrice = getItemPrice(itemId) * quantity;
-
- let discount = 0;
-
- const now = new Date();
- if (shopConfig.events.sales.enabled) {
- const today = now.getDay();
- const sale = shopConfig.events.sales.schedule.find(s => s.day === today);
- if (sale) {
- discount += sale.discount;
- }
- }
-
- if (userData) {
- if (userData.roles?.includes('premium')) {
- discount += 0.1;
- }
-
- if (quantity >= 10) {
-discount += 0.1;
- }
- }
-
- discount = Math.max(0, Math.min(1, discount));
-
- return Math.floor(basePrice * (1 - discount));
-}
-
-/**
- * Get the shop category for a specific item
- * @param {string} itemType - The type of the item
- * @returns {Object} The category object
- */
-export function getCategoryForItem(itemType) {
- return shopConfig.categories.find(cat =>
- cat.itemTypes.includes(itemType)
- ) || {
- id: 'other',
- name: 'Other',
- description: 'Miscellaneous items',
- icon: '📦'
- };
-}
-
-/**
- * Get all items in a specific category
- * @param {string} categoryId - The ID of the category
- * @returns {Array} Array of items in the category
- */
-export function getItemsInCategory(categoryId) {
- const category = shopConfig.categories.find(cat => cat.id === categoryId);
- if (!category) return [];
-
- return shopItems.filter(item =>
- category.itemTypes.includes(item.type)
- );
-}
-
diff --git a/src/config/shop/items.js b/src/config/shop/items.js
deleted file mode 100644
index c33c786203..0000000000
--- a/src/config/shop/items.js
+++ /dev/null
@@ -1,212 +0,0 @@
-/**
- * Shop items configuration
- * Defines all items available in the shop with their properties
- */
-
-export const shopItems = [
- {
- id: 'extra_work',
- name: 'Extra Work Shift',
- price: 5000,
- description: 'Allows 1 extra use of the `/work` command.',
- type: 'consumable',
- maxQuantity: 5,
-cooldown: 86400000,
- effect: {
- type: 'command_boost',
- command: 'work',
- uses: 1
- }
- },
- {
- id: 'bank_upgrade_1',
- name: 'Bank Upgrade I',
- price: 15000,
- description: 'Increases bank capacity and allows more funds to be deposited.',
- type: 'upgrade',
- maxLevel: 5,
- effect: {
- type: 'bank_capacity',
- multiplier: 1.5
- }
- },
- {
- id: 'diamond_pickaxe',
- name: 'Diamond Pickaxe',
- price: 50000,
- description: 'Increases yield from `/mine`',
- type: 'tool',
- durability: 100,
- effect: {
- type: 'mining_yield',
- multiplier: 2.0
- }
- },
- {
- id: 'premium_role',
- name: 'Premium Server Role',
- price: 15000,
- description: 'A special role granting a fancy color and a 10% daily bonus.',
- type: 'role',
-roleId: null,
- effect: {
- type: 'daily_bonus',
- multiplier: 1.1
- }
- },
- {
- id: 'lucky_clover',
- name: 'Lucky Clover',
- price: 10000,
- description: 'Increases the chance of winning a higher payout on `/gamble` once.',
- type: 'consumable',
- effect: {
- type: 'gamble_boost',
- multiplier: 1.5,
- uses: 1
- }
- },
- {
- id: 'fishing_rod',
- name: '🎣 Fishing Rod',
- price: 5000,
- description: 'Used for fishing commands',
- type: 'tool',
- durability: 100,
- effect: {
- type: 'fishing_yield',
- multiplier: 1.0
- }
- },
- {
- id: 'pickaxe',
- name: 'â›ï¸ Pickaxe',
- price: 7500,
- description: 'Used for mining commands',
- type: 'tool',
- durability: 100,
- effect: {
- type: 'mining_yield',
- multiplier: 1.2
- }
- },
- {
- id: 'laptop',
- name: '💻 Laptop',
- price: 15000,
- description: 'Increases work earnings',
- type: 'tool',
- durability: 200,
- effect: {
- type: 'work_yield',
- multiplier: 1.5
- }
- },
- {
- id: 'lucky_charm',
- name: '🀠Lucky Charm',
- price: 10000,
- description: 'Increases luck for gambling',
- type: 'consumable',
- effect: {
- type: 'gamble_boost',
- multiplier: 1.3,
- uses: 3
- }
- },
- {
- id: 'bank_note',
- name: '📜 Bank Note',
- price: 25000,
- description: 'Increases bank capacity by 10,000',
- type: 'upgrade',
- maxLevel: 10,
- effect: {
- type: 'bank_capacity',
- increase: 10000
- }
- }
-];
-
-/**
- * Get a shop item by its ID
- * @param {string} itemId - The ID of the item to find
- * @returns {Object|undefined} The item object or undefined if not found
- */
-export function getItemById(itemId) {
- return shopItems.find(item => item.id === itemId);
-}
-
-/**
- * Get all items of a specific type
- * @param {string} type - The type of items to retrieve
- * @returns {Array} Array of items matching the type
- */
-export function getItemsByType(type) {
- return shopItems.filter(item => item.type === type);
-}
-
-/**
- * Get the price of an item by its ID
- * @param {string} itemId - The ID of the item
- * @returns {number} The price of the item, or 0 if not found
- */
-export function getItemPrice(itemId) {
- const item = getItemById(itemId);
- return item ? item.price : 0;
-}
-
-/**
- * Validate if an item can be purchased
- * @param {string} itemId - The ID of the item to validate
- * @param {Object} userData - The user's data including inventory and stats
- * @returns {{valid: boolean, reason: string}} Validation result
- */
-export function validatePurchase(itemId, userData) {
- const item = getItemById(itemId);
- if (!item) {
- return { valid: false, reason: 'Item not found' };
- }
-
- if (item.type === 'consumable' && item.maxQuantity) {
- const userItem = userData.inventory?.find(i => i.id === itemId);
- if (userItem && userItem.quantity >= item.maxQuantity) {
- return {
- valid: false,
- reason: `You can only have a maximum of ${item.maxQuantity} ${item.name}s`
- };
- }
- }
-
- if (item.type === 'upgrade' && item.maxLevel) {
- const userUpgrade = userData.upgrades?.find(u => u.id === itemId);
- if (userUpgrade && userUpgrade.level >= item.maxLevel) {
- return {
- valid: false,
- reason: `You've already reached the maximum level for ${item.name}`
- };
- }
- }
-
- if (item.type === 'tool') {
- const userTool = userData.inventory?.find(i => i.id === itemId);
- if (userTool && userTool.durability > 0) {
- return {
- valid: false,
- reason: `You already have a ${item.name} in good condition`
- };
- }
- }
-
- if (item.type === 'role' && item.roleId) {
- if (userData.roles?.includes(item.roleId)) {
- return {
- valid: false,
- reason: `You already have the ${item.name} role`
- };
- }
- }
-
- return { valid: true };
-}
-
diff --git a/src/events/channelDelete.js b/src/events/channelDelete.js
deleted file mode 100644
index 1f0ccfc236..0000000000
--- a/src/events/channelDelete.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import {
- getJoinToCreateConfig,
- removeJoinToCreateTrigger,
- unregisterTemporaryChannel
-} from '../utils/database.js';
-import { logger } from '../utils/logger.js';
-
-export default {
- name: 'channelDelete',
- async execute(channel, client) {
-if (channel.type !== 2 && channel.type !== 4) {
- return;
- }
-
- const guildId = channel.guild.id;
-
- try {
- const config = await getJoinToCreateConfig(client, guildId);
-
- if (!config.enabled) {
- return;
- }
-
- if (config.triggerChannels.includes(channel.id)) {
- logger.info(`Join to Create trigger channel ${channel.name} (${channel.id}) was deleted, removing from configuration`);
-
- const success = await removeJoinToCreateTrigger(client, guildId, channel.id);
- if (success) {
- logger.info(`Successfully removed trigger channel ${channel.id} from Join to Create configuration`);
- } else {
- logger.warn(`Failed to remove trigger channel ${channel.id} from Join to Create configuration`);
- }
- }
-
- if (config.temporaryChannels[channel.id]) {
- logger.info(`Join to Create temporary channel ${channel.name} (${channel.id}) was deleted, cleaning up database`);
-
- const success = await unregisterTemporaryChannel(client, guildId, channel.id);
- if (success) {
- logger.info(`Successfully cleaned up temporary channel ${channel.id} from database`);
- } else {
- logger.warn(`Failed to cleanup temporary channel ${channel.id} from database`);
- }
- }
-
- if (config.categoryId === channel.id) {
- logger.warn(`Category ${channel.name} (${channel.id}) used for Join to Create temporary channels was deleted. Join to Create will be disabled.`);
-
- config.categoryId = null;
- config.enabled = false;
-
- try {
- await client.db.set(`guild:${guildId}:jointocreate`, config);
- logger.info(`Disabled Join to Create for guild ${guildId} due to category deletion`);
- } catch (error) {
- logger.error(`Failed to disable Join to Create for guild ${guildId}:`, error);
- }
- }
-
- } catch (error) {
- logger.error(`Error in channelDelete event for guild ${guildId}:`, error);
- }
- }
-};
-
diff --git a/src/events/guildMemberAdd.js b/src/events/guildMemberAdd.js
deleted file mode 100644
index 19c71a659f..0000000000
--- a/src/events/guildMemberAdd.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import { Events, EmbedBuilder } from 'discord.js';
-import { getGuildConfig } from '../services/guildConfig.js';
-import { getWelcomeConfig } from '../utils/database.js';
-import { successEmbed, errorEmbed } from '../utils/embeds.js';
-
-export default {
- name: Events.GuildMemberAdd,
- once: false,
-
- async execute(member) {
- try {
- const { guild, user } = member;
-
- const config = await getGuildConfig(member.client, guild.id);
-
- const welcomeConfig = await getWelcomeConfig(member.client, guild.id);
-
- if (welcomeConfig?.enabled && welcomeConfig.channelId) {
- const channel = guild.channels.cache.get(welcomeConfig.channelId);
- if (channel) {
- let welcomeMessage = welcomeConfig.welcomeMessage || 'Welcome {user} to {server}!';
- welcomeMessage = welcomeMessage
- .replace(/{user}/g, user.toString())
- .replace(/{username}/g, user.username)
- .replace(/{server}/g, guild.name)
- .replace(/{membercount}/g, guild.memberCount);
-
- let messageContent = '';
- if (welcomeConfig.welcomePing) {
- messageContent = user.toString();
- }
-
- const embed = new EmbedBuilder()
-.setColor(0x00ff00)
- .setTitle('🎉 Welcome!')
-.setDescription(welcomeMessage)
- .setThumbnail(user.displayAvatarURL())
- .addFields(
- { name: 'User', value: `${user.tag} (${user.id})`, inline: true },
- { name: 'Member Count', value: guild.memberCount.toString(), inline: true }
- )
- .setTimestamp()
- .setFooter({ text: `Welcome to ${guild.name}!` });
-
- if (welcomeConfig.welcomeImage) {
- embed.setImage(welcomeConfig.welcomeImage);
- }
-
- await channel.send({
- content: messageContent || null,
- embeds: [embed]
- });
- }
- }
-
- if (welcomeConfig?.roleIds && welcomeConfig.roleIds.length > 0) {
- const delay = welcomeConfig.autoRoleDelay || 0;
-
- if (delay > 0) {
- setTimeout(async () => {
- for (const roleId of welcomeConfig.roleIds) {
- const role = guild.roles.cache.get(roleId);
- if (role) {
- await member.roles.add(role).catch(() => {});
- }
- }
- }, delay * 1000);
- } else {
- for (const roleId of welcomeConfig.roleIds) {
- const role = guild.roles.cache.get(roleId);
- if (role) {
- await member.roles.add(role).catch(() => {});
- }
- }
- }
- }
-
- if (config?.verification?.enabled) {
- await handleVerification(member, guild, config.verification, member.client);
- }
-
- } catch (error) {
- console.error('Error in guildMemberAdd event:', error);
- }
- }
-};
-
-async function handleVerification(member, guild, verificationConfig, client) {
- try {
- if (!verificationConfig.autoVerify?.enabled) {
- return;
- }
-
- let shouldVerify = false;
- const autoVerify = verificationConfig.autoVerify;
-
- switch (autoVerify.criteria) {
- case "account_age":
- const accountAge = Date.now() - member.user.createdTimestamp;
- const requiredAge = autoVerify.accountAgeDays * 24 * 60 * 60 * 1000;
- shouldVerify = accountAge >= requiredAge;
- break;
-
- case "server_size":
- shouldVerify = guild.memberCount < 1000;
- break;
-
- case "none":
- shouldVerify = true;
- break;
- }
-
- if (shouldVerify) {
- const verifiedRole = guild.roles.cache.get(verificationConfig.roleId);
-
- if (verifiedRole && guild.members.me.permissions.has("ManageRoles")) {
- const botRole = guild.members.me.roles.highest;
- if (verifiedRole.position < botRole.position) {
- await member.roles.add(verifiedRole.id, "Auto-verified on join");
- console.log(`✅ Auto-verified ${member.user.tag} (${member.id}) in ${guild.name}`);
-
- try {
- await member.send({
- embeds: [{
- title: "🎉 Welcome to the Server!",
- description: `You have been automatically verified in **${guild.name}**! You now have access to all server channels and features.`,
- color: 0x00FF00
- }]
- });
- } catch (error) {
- console.log(`Could not send auto-verification DM to ${member.user.tag}: ${error.message}`);
- }
- } else {
- console.warn(`Cannot auto-verify ${member.user.tag}: Verified role is higher than bot's highest role`);
- }
- } else {
- console.warn(`Cannot auto-verify ${member.user.tag}: Missing verified role or ManageRoles permission`);
- }
- }
-
- } catch (error) {
- console.error(`Error in auto-verification for ${member.user.tag}:`, error);
- }
-}
-
diff --git a/src/events/guildMemberRemove.js b/src/events/guildMemberRemove.js
deleted file mode 100644
index 67ffcf072e..0000000000
--- a/src/events/guildMemberRemove.js
+++ /dev/null
@@ -1,72 +0,0 @@
-import { Events, EmbedBuilder } from 'discord.js';
-import { getGuildConfig } from '../services/guildConfig.js';
-import { getWelcomeConfig } from '../utils/database.js';
-
-export default {
- name: Events.GuildMemberRemove,
- once: false,
-
- async execute(member) {
- try {
- const { guild, user } = member;
-
- const config = await getGuildConfig(member.client, guild.id);
-
- const welcomeConfig = await getWelcomeConfig(member.client, guild.id);
-
- if (welcomeConfig?.goodbyeEnabled && welcomeConfig?.goodbyeChannelId) {
- const channel = guild.channels.cache.get(welcomeConfig.goodbyeChannelId);
- if (channel) {
- let goodbyeMessage = welcomeConfig.leaveMessage || '{user.tag} has left the server.';
- goodbyeMessage = goodbyeMessage
- .replace(/{user}/g, user.toString())
- .replace(/{user\.tag}/g, user.tag)
- .replace(/{username}/g, user.username)
- .replace(/{server}/g, guild.name)
- .replace(/{membercount}/g, guild.memberCount);
-
- const embed = new EmbedBuilder()
- .setTitle(welcomeConfig.leaveEmbed?.title || '👋 Goodbye')
-.setDescription(goodbyeMessage)
- .setColor(welcomeConfig.leaveEmbed?.color || 0xff0000)
- .setThumbnail(user.displayAvatarURL())
- .addFields(
- { name: 'User', value: `${user.tag} (${user.id})`, inline: true },
- { name: 'Member Count', value: guild.memberCount.toString(), inline: true }
- )
- .setTimestamp()
- .setFooter({ text: `Goodbye from ${guild.name}!` });
-
- if (welcomeConfig.leaveEmbed?.image) {
- embed.setImage(welcomeConfig.leaveEmbed.image.url);
- }
-
- await channel.send({ embeds: [embed] });
- }
- }
-
- if (config?.logChannelId) {
- const logChannel = guild.channels.cache.get(config.logChannelId);
- if (logChannel) {
- const embed = new EmbedBuilder()
- .setTitle('Member Left')
- .setColor('#FF0000')
- .setThumbnail(user.displayAvatarURL())
- .addFields(
- { name: 'User', value: `${user.tag} (${user.id})`, inline: true },
- { name: 'Joined Server', value: ``, inline: true },
- { name: 'Member Count', value: guild.memberCount.toString(), inline: true }
- )
- .setTimestamp()
- .setFooter({ text: 'Member Left' });
-
- await logChannel.send({ embeds: [embed] });
- }
- }
-
- } catch (error) {
- console.error('Error in guildMemberRemove event:', error);
- }
- }
-};
-
diff --git a/src/events/interactionCreate.js b/src/events/interactionCreate.js
deleted file mode 100644
index 37df237045..0000000000
--- a/src/events/interactionCreate.js
+++ /dev/null
@@ -1,204 +0,0 @@
-import { Events, MessageFlags } from 'discord.js';
-import { logger } from '../utils/logger.js';
-import { getGuildConfig } from '../services/guildConfig.js';
-import { errorEmbed } from '../utils/embeds.js';
-import { handleApplicationModal } from '../commands/Community/apply.js';
-import { handleApplicationButton, handleApplicationReviewModal } from '../commands/Community/app-admin.js';
-import { handleInteractionError, createError, ErrorTypes } from '../utils/errorHandler.js';
-import { MessageTemplates } from '../utils/messageTemplates.js';
-
-export default {
- name: Events.InteractionCreate,
- async execute(interaction, client) {
- try {
- if (interaction.isChatInputCommand()) {
- try {
- logger.info(`Command executed: /${interaction.commandName} by ${interaction.user.tag}`);
- const command = client.commands.get(interaction.commandName);
-
- if (!command) {
- throw createError(
- `No command matching ${interaction.commandName} was found.`,
- ErrorTypes.CONFIGURATION,
- 'Sorry, that command does not exist.',
- { commandName: interaction.commandName }
- );
- }
-
-
- let guildConfig = null;
- if (interaction.guild) {
- guildConfig = await getGuildConfig(client, interaction.guild.id);
- if (guildConfig?.disabledCommands?.[interaction.commandName]) {
- throw createError(
- `Command ${interaction.commandName} is disabled in this guild`,
- ErrorTypes.CONFIGURATION,
- 'This command has been disabled for this server.',
- { commandName: interaction.commandName, guildId: interaction.guild.id }
- );
- }
- }
-
- await command.execute(interaction, guildConfig, client);
- } catch (error) {
- await handleInteractionError(interaction, error, {
- type: 'command',
- commandName: interaction.commandName
- });
- }
- }
- else if (interaction.isButton()) {
- if (interaction.customId.startsWith('app_approve_') || interaction.customId.startsWith('app_deny_')) {
- try {
- await handleApplicationButton(interaction);
- } catch (error) {
- await handleInteractionError(interaction, error, {
- type: 'button',
- customId: interaction.customId,
- handler: 'application'
- });
- }
- return;
- }
-
- if (interaction.customId.startsWith('shared_todo_')) {
- const parts = interaction.customId.split('_');
-const buttonType = parts.slice(0, 3).join('_');
- const listId = parts[3];
- const button = client.buttons.get(buttonType);
-
- if (button) {
- try {
- await button.execute(interaction, client, [listId]);
- } catch (error) {
- await handleInteractionError(interaction, error, {
- type: 'button',
- customId: interaction.customId,
- handler: 'todo'
- });
- }
- } else {
- throw createError(
- `No button handler found for ${buttonType}`,
- ErrorTypes.CONFIGURATION,
- 'This button is not available.',
- { buttonType }
- );
- }
- return;
- }
-
- const [customId, ...args] = interaction.customId.split(':');
- const button = client.buttons.get(customId);
-
- if (!button) {
- throw createError(
- `No button handler found for ${customId}`,
- ErrorTypes.CONFIGURATION,
- 'This button is not available.',
- { customId }
- );
- }
-
- try {
- await button.execute(interaction, client, args);
- } catch (error) {
- await handleInteractionError(interaction, error, {
- type: 'button',
- customId: interaction.customId,
- handler: 'general'
- });
- }
- }
- else if (interaction.isStringSelectMenu()) {
- const [customId, ...args] = interaction.customId.split(':');
- const selectMenu = client.selectMenus.get(customId);
-
- if (!selectMenu) {
- throw createError(
- `No select menu handler found for ${customId}`,
- ErrorTypes.CONFIGURATION,
- 'This select menu is not available.',
- { customId }
- );
- }
-
- try {
- await selectMenu.execute(interaction, client, args);
- } catch (error) {
- await handleInteractionError(interaction, error, {
- type: 'select_menu',
- customId: interaction.customId
- });
- }
- }
- else if (interaction.isModalSubmit()) {
- if (interaction.customId.startsWith('app_modal_')) {
- try {
- await handleApplicationModal(interaction);
- } catch (error) {
- await handleInteractionError(interaction, error, {
- type: 'modal',
- customId: interaction.customId,
- handler: 'application'
- });
- }
- return;
- }
-
- if (interaction.customId.startsWith('app_review_')) {
- try {
- await handleApplicationReviewModal(interaction);
- } catch (error) {
- await handleInteractionError(interaction, error, {
- type: 'modal',
- customId: interaction.customId,
- handler: 'application_review'
- });
- }
- return;
- }
-
- const [customId, ...args] = interaction.customId.split(':');
- const modal = client.modals.get(customId);
-
- if (!modal) {
- throw createError(
- `No modal handler found for ${customId}`,
- ErrorTypes.CONFIGURATION,
- 'This form is not available.',
- { customId }
- );
- }
-
- try {
- await modal.execute(interaction, client, args);
- } catch (error) {
- await handleInteractionError(interaction, error, {
- type: 'modal',
- customId: interaction.customId,
- handler: 'general'
- });
- }
- }
- } catch (error) {
- logger.error('Unhandled error in interactionCreate:', error);
-
- try {
- const errorMessage = {
- embeds: [MessageTemplates.ERRORS.DATABASE_ERROR('processing your interaction')],
- flags: ['Ephemeral']
- };
-
- if (interaction.deferred || interaction.replied) {
- await interaction.editReply(errorMessage);
- } else {
- await interaction.reply(errorMessage);
- }
- } catch (replyError) {
- logger.error('Failed to send fallback error response:', replyError);
- }
- }
- }
-};
-
diff --git a/src/events/messageCreate.js b/src/events/messageCreate.js
deleted file mode 100644
index e499b1cd5c..0000000000
--- a/src/events/messageCreate.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import { Events } from 'discord.js';
-import { logger } from '../utils/logger.js';
-import { getLevelingConfig, getUserLevelData, saveUserLevelData } from '../utils/database.js';
-import { addXp } from '../services/xpSystem.js';
-
-export default {
- name: Events.MessageCreate,
- async execute(message, client) {
- try {
- if (message.author.bot || !message.guild) return;
-
- await handleLeveling(message, client);
-
- } catch (error) {
- logger.error('Error in messageCreate event:', error);
- }
- },
-};
-
-async function handleLeveling(message, client) {
- const levelingConfig = await getLevelingConfig(client, message.guild.id);
-
- if (!levelingConfig?.enabled) return;
-
- if (levelingConfig.ignoredChannels?.includes(message.channel.id)) return;
-
- if (levelingConfig.ignoredRoles?.length > 0) {
- const member = await message.guild.members.fetch(message.author.id).catch(() => null);
- if (member && member.roles.cache.some(role => levelingConfig.ignoredRoles.includes(role.id))) return;
- }
-
- if (levelingConfig.blacklistedUsers?.includes(message.author.id)) return;
-
- const userData = await getUserLevelData(client, message.guild.id, message.author.id);
-
- const cooldownTime = levelingConfig.xpCooldown || 60;
- const now = Date.now();
- const timeSinceLastMessage = now - (userData.lastMessage || 0);
-
- if (timeSinceLastMessage < cooldownTime * 1000) return;
-
- const minXP = levelingConfig.xpPerMessage?.min || 15;
- const maxXP = levelingConfig.xpPerMessage?.max || 25;
- const xpToGive = Math.floor(Math.random() * (maxXP - minXP + 1)) + minXP;
-
- let finalXP = xpToGive;
- if (levelingConfig.xpMultiplier && levelingConfig.xpMultiplier > 1) {
- finalXP = Math.floor(finalXP * levelingConfig.xpMultiplier);
- }
-
- const result = await addXp(client, message.guild, message.member, finalXP);
-
- if (result.success && result.leveledUp) {
- logger.info(`${message.author.tag} leveled up to ${result.level} in ${message.guild.name}`);
- }
-}
-
diff --git a/src/events/ready.js b/src/events/ready.js
deleted file mode 100644
index f212d8fefd..0000000000
--- a/src/events/ready.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Events } from "discord.js";
-import { logger } from "../utils/logger.js";
-import config from "../config/index.js";
-
-export default {
- name: Events.ClientReady,
- once: true,
-
- async execute(client) {
- try {
- client.user.setPresence(config.bot.presence);
-
- logger.info(`Ready! Logged in as ${client.user.tag}`);
- logger.info(`Serving ${client.guilds.cache.size} guild(s)`);
- logger.info(`Loaded ${client.commands.size} commands`);
- } catch (error) {
- logger.error("Error in ready event:", error);
- }
- },
-};
-
diff --git a/src/events/voiceStateUpdate.js b/src/events/voiceStateUpdate.js
deleted file mode 100644
index 6a7370097a..0000000000
--- a/src/events/voiceStateUpdate.js
+++ /dev/null
@@ -1,240 +0,0 @@
-import {
- getJoinToCreateConfig,
- registerTemporaryChannel,
- unregisterTemporaryChannel,
- getTemporaryChannelInfo,
- formatChannelName
-} from '../utils/database.js';
-import { logger } from '../utils/logger.js';
-
-const channelCreationCooldown = new Map();
-
-export default {
- name: 'voiceStateUpdate',
- async execute(oldState, newState, client) {
- if (newState.member.user.bot) return;
-
- const guildId = newState.guild.id;
- const userId = newState.member.id;
- const cooldownKey = `${guildId}-${userId}`;
-
- try {
- const config = await getJoinToCreateConfig(client, guildId);
-
- if (!config.enabled || config.triggerChannels.length === 0) {
- return;
- }
-
- if (!oldState.channel && newState.channel) {
- await handleVoiceJoin(client, newState, config);
- }
-
- if (oldState.channel && !newState.channel) {
- await handleVoiceLeave(client, oldState, config);
- }
-
- if (oldState.channel && newState.channel && oldState.channel.id !== newState.channel.id) {
- await handleVoiceMove(client, oldState, newState, config);
- }
-
- } catch (error) {
- logger.error(`Error in voiceStateUpdate for guild ${guildId}:`, error);
- }
-
- async function handleVoiceJoin(client, state, config) {
- const { channel, member } = state;
-
- if (!config.triggerChannels.includes(channel.id)) {
- return;
- }
-
- const now = Date.now();
- if (channelCreationCooldown.has(cooldownKey)) {
- const lastCreation = channelCreationCooldown.get(cooldownKey);
-if (now - lastCreation < 2000) {
- logger.warn(`User ${member.id} is on cooldown for channel creation`);
- return;
- }
- }
-
- const existingTempChannel = Object.keys(config.temporaryChannels || {}).find(
- tempChannelId => {
- const tempInfo = config.temporaryChannels[tempChannelId];
- return tempInfo && tempInfo.ownerId === member.id;
- }
- );
-
- if (existingTempChannel) {
- const tempChannel = state.guild.channels.cache.get(existingTempChannel);
- if (tempChannel) {
- try {
- await member.voice.setChannel(tempChannel);
- return;
- } catch (error) {
- logger.warn(`Failed to move user ${member.id} to existing channel ${existingTempChannel}:`, error);
- }
- }
- }
-
- if (member.voice.channel?.id !== channel.id) {
- return;
- }
-
- channelCreationCooldown.set(cooldownKey, now);
-
- await createTemporaryChannel(client, state, config);
- }
-
- async function handleVoiceLeave(client, state, config) {
- const { channel, member } = state;
-
- const tempChannelInfo = await getTemporaryChannelInfo(client, state.guild.id, channel.id);
-
- if (!tempChannelInfo) {
- return;
- }
-
- if (channel.members.size === 0) {
- await deleteTemporaryChannel(client, channel, state.guild.id);
- } else if (tempChannelInfo.ownerId === member.id) {
- const nextMember = channel.members.first();
- if (nextMember) {
- await transferChannelOwnership(client, channel, state.guild.id, nextMember.id);
- }
- }
- }
-
- async function handleVoiceMove(client, oldState, newState, config) {
- if (oldState.channel) {
- const tempChannelInfo = await getTemporaryChannelInfo(client, oldState.guild.id, oldState.channel.id);
-
- if (tempChannelInfo) {
- if (oldState.channel.members.size === 0) {
- await deleteTemporaryChannel(client, oldState.channel, oldState.guild.id);
- } else if (tempChannelInfo.ownerId === oldState.member.id) {
- const nextMember = oldState.channel.members.first();
- if (nextMember) {
- await transferChannelOwnership(client, oldState.channel, oldState.guild.id, nextMember.id);
- }
- }
- }
- }
-
- if (config.triggerChannels.includes(newState.channel.id) &&
- !config.triggerChannels.includes(oldState.channel?.id)) {
- await handleVoiceJoin(client, newState, config);
- }
- }
-
- async function createTemporaryChannel(client, state, config) {
- const { channel: triggerChannel, member, guild } = state;
-
- try {
- const channelOptions = config.channelOptions?.[triggerChannel.id] || {};
- const nameTemplate = channelOptions.nameTemplate || config.channelNameTemplate || "{username}'s Room";
-
- let userLimit = channelOptions.userLimit ?? config.userLimit ?? 0;
- const bitrate = channelOptions.bitrate ?? config.bitrate ?? 64000;
-
- userLimit = Math.max(0, Math.min(99, userLimit || 0));
-
- logger.info(`Creating temporary channel for user ${member.id} with user limit: ${userLimit}`);
-
- const channelName = formatChannelName(nameTemplate, {
- username: member.user.username,
- userTag: member.user.tag,
- displayName: member.displayName,
- guildName: guild.name,
- channelName: triggerChannel.name
- });
-
- const tempChannel = await guild.channels.create({
- name: channelName,
-type: 2,
- parent: triggerChannel.parentId,
-userLimit: userLimit === 0 ? undefined : userLimit,
- bitrate: bitrate,
- permissionOverwrites: [
- {
- id: member.id,
- allow: ['Connect', 'Speak', 'PrioritySpeaker', 'MoveMembers']
- },
- {
- id: guild.id,
- allow: ['Connect', 'Speak']
- }
- ]
- });
-
- await registerTemporaryChannel(client, guild.id, tempChannel.id, member.id, triggerChannel.id);
-
- await member.voice.setChannel(tempChannel);
-
- logger.info(`Created temporary voice channel ${tempChannel.name} (${tempChannel.id}) for user ${member.user.tag} in guild ${guild.name} with user limit ${userLimit}`);
-
- setTimeout(() => {
- channelCreationCooldown.delete(cooldownKey);
- }, 3000);
-
- } catch (error) {
- logger.error(`Failed to create temporary channel for user ${member.user.tag} in guild ${guild.name}:`, error);
-
- channelCreationCooldown.delete(cooldownKey);
-
- try {
- await member.send({
- content: `⌠Failed to create your temporary voice channel. Please contact a server administrator.`
-}).catch(() => {});
- } catch (dmError) {
- }
- }
- }
-
- async function deleteTemporaryChannel(client, channel, guildId) {
- try {
- await unregisterTemporaryChannel(client, guildId, channel.id);
-
- await channel.delete('Temporary voice channel - empty');
-
- logger.info(`Deleted temporary voice channel ${channel.name} (${channel.id}) in guild ${channel.guild.name}`);
-
- } catch (error) {
- logger.error(`Failed to delete temporary channel ${channel.id}:`, error);
- }
- }
-
- async function transferChannelOwnership(client, channel, guildId, newOwnerId) {
- try {
- const config = await getJoinToCreateConfig(client, guildId);
- const tempChannelInfo = config.temporaryChannels[channel.id];
-
- if (!tempChannelInfo) return;
-
- config.temporaryChannels[channel.id].ownerId = newOwnerId;
- await client.db.set(`guild:${guildId}:jointocreate`, config);
-
- const newOwner = await channel.guild.members.fetch(newOwnerId);
- if (newOwner) {
- const channelOptions = config.channelOptions?.[tempChannelInfo.triggerChannelId] || {};
- const nameTemplate = channelOptions.nameTemplate || config.channelNameTemplate;
-
- const newChannelName = formatChannelName(nameTemplate, {
- username: newOwner.user.username,
- userTag: newOwner.user.tag,
- displayName: newOwner.displayName,
- guildName: channel.guild.name,
- channelName: channel.guild.channels.cache.get(tempChannelInfo.triggerChannelId)?.name || 'Voice Channel'
- });
-
- await channel.setName(newChannelName);
- }
-
- logger.info(`Transferred ownership of temporary channel ${channel.id} to user ${newOwnerId}`);
-
- } catch (error) {
- logger.error(`Failed to transfer ownership of channel ${channel.id}:`, error);
- }
- }
- }
-};
-
diff --git a/src/handlers/commandLoader.js b/src/handlers/commandLoader.js
deleted file mode 100644
index 5b1a061c38..0000000000
--- a/src/handlers/commandLoader.js
+++ /dev/null
@@ -1,328 +0,0 @@
-import fs from 'fs/promises';
-import path from 'path';
-import { fileURLToPath } from 'url';
-import { Collection } from 'discord.js';
-import { logger } from '../utils/logger.js';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-/**
- * Extracts subcommand information from a SlashCommandBuilder
- * @param {SlashCommandBuilder} commandData - The command data object
- * @returns {string[]} - Array of subcommand names
- */
-function getSubcommandInfo(commandData) {
- const subcommands = [];
-
- if (commandData.options) {
- for (const option of commandData.options) {
-if (option.type === 1) {
- subcommands.push(option.name);
-} else if (option.type === 2) {
- if (option.options) {
- for (const subOption of option.options) {
-if (subOption.type === 1) {
- subcommands.push(`${option.name}/${subOption.name}`);
- }
- }
- }
- }
- }
- }
-
- return subcommands;
-}
-
-/**
- * Recursively loads all command files from the specified directory
- * @param {string} directory - Directory to load commands from
- * @param {string[]} fileList - List of file paths (used for recursion)
- * @returns {Promise} - Array of file paths
- */
-async function getAllFiles(directory, fileList = []) {
- const files = await fs.readdir(directory, { withFileTypes: true });
-
- for (const file of files) {
- const filePath = path.join(directory, file.name);
-
- if (file.isDirectory()) {
- if (file.name === 'modules') {
- continue;
- }
- await getAllFiles(filePath, fileList);
- } else if (file.name.endsWith('.js')) {
- fileList.push(filePath);
- }
- }
-
- return fileList;
-}
-
-/**
- * Loads all commands from the commands directory
- * @param {import('discord.js').Client} client - Discord.js client
- * @returns {Promise>} - Collection of commands
- */
-export async function loadCommands(client) {
- client.commands = new Collection();
- const commandsPath = path.join(__dirname, '../commands');
- const commandFiles = await getAllFiles(commandsPath);
-
- logger.info(`Found ${commandFiles.length} command files to load`);
-
- const uniqueCommandNames = new Set();
-
- for (const filePath of commandFiles) {
- try {
- const normalizedPath = filePath.replace(/\\/g, '/');
-
- const commandName = path.basename(filePath, '.js');
- const commandDir = path.dirname(filePath);
- const category = path.basename(commandDir);
-
- const commandModule = await import(`file://${filePath}`);
- const command = commandModule.default || commandModule;
-
- if (!command.data || !command.execute) {
- logger.warn(`Command at ${filePath} is missing required "data" or "execute" property.`);
- continue;
- }
-
- command.category = category;
- command.filePath = normalizedPath;
-
- const primaryCommandName = command.data.name;
-
- if (!uniqueCommandNames.has(primaryCommandName)) {
- uniqueCommandNames.add(primaryCommandName);
-
- client.commands.set(primaryCommandName, command);
- }
-
- const subcommands = getSubcommandInfo(command.data.toJSON());
-
- logger.info(`Loaded command: ${primaryCommandName} from ${normalizedPath} (category: ${category})`);
-
- if (subcommands.length > 0) {
- logger.info(` - Subcommands: ${subcommands.join(', ')}`);
- }
-
- } catch (error) {
- console.error(`Error loading command from ${filePath}:`, error);
- logger.error(`Error loading command from ${filePath}:`, error);
- }
- }
-
- const commandsWithSubcommands = Array.from(client.commands.values()).filter(cmd => {
- const subcommands = getSubcommandInfo(cmd.data.toJSON());
- return subcommands.length > 0;
- });
-
- const totalSubcommands = commandsWithSubcommands.reduce((total, cmd) => {
- return total + getSubcommandInfo(cmd.data.toJSON()).length;
- }, 0);
-
- const uniqueCommands = new Set();
- for (const [name, command] of client.commands.entries()) {
- if (command.data && command.data.name) {
- uniqueCommands.add(command.data.name);
- }
- }
-
- logger.info(`Loaded ${uniqueCommands.size} commands`);
- return client.commands;
-}
-
-/**
- * Registers all slash commands with Discord
- * @param {import('discord.js').Client} client - Discord.js client
- * @param {string} [guildId] - Optional guild ID to register commands for a specific guild
- * @returns {Promise}
- */
-export async function registerCommands(client, guildId) {
- try {
- const commands = [];
- let totalSubcommands = 0;
-const registeredNames = new Set();
-
- for (const command of client.commands.values()) {
- if (command.data && typeof command.data.toJSON === 'function') {
- const commandName = command.data.name;
-
- logger.debug(`Processing command for registration: ${commandName}`);
-
- if (!registeredNames.has(commandName)) {
- registeredNames.add(commandName);
- const commandJson = command.data.toJSON();
- commands.push(commandJson);
-
- const subcommands = getSubcommandInfo(commandJson);
- totalSubcommands += subcommands.length;
-
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`Registering command: ${commandName}`);
- }
- } else {
- logger.debug(`Skipping duplicate command: ${commandName}`);
- }
- } else {
- logger.warn(`Command missing data or toJSON method: ${command}`);
- }
- }
-
- const totalCommandsWithSubs = commands.length + totalSubcommands;
-
- if (guildId) {
-
- logger.info(`Preparing to register ${totalCommandsWithSubs} commands for guild ${guildId}`);
-
- logger.info('Validating commands before registration...');
-
- let validationErrors = [];
- commands.forEach((cmd, index) => {
- if (cmd.name && cmd.name.length > 32) {
- validationErrors.push(`Command ${cmd.name} has name longer than 32 chars: "${cmd.name}" (${cmd.name.length} chars)`);
- }
- if (cmd.description && cmd.description.length > 110) {
- validationErrors.push(`Command ${cmd.name} has description longer than 110 chars: "${cmd.description}" (${cmd.description.length} chars)`);
- }
-
- if (cmd.options) {
- cmd.options.forEach((option, optIndex) => {
- if (option.name && option.name.length > 32) {
- validationErrors.push(`Command ${cmd.name} option ${option.name} has name longer than 32 chars: "${option.name}" (${option.name.length} chars)`);
- }
- if (option.description && option.description.length > 110) {
- validationErrors.push(`Command ${cmd.name} option ${option.name} has description longer than 110 chars: "${option.description}" (${option.description.length} chars)`);
- }
-
- if (option.choices) {
- option.choices.forEach((choice, choiceIndex) => {
- if (choice.name && choice.name.length > 110) {
- validationErrors.push(`Command ${cmd.name} option ${option.name} choice ${choice.name} has name longer than 110 chars: "${choice.name}" (${choice.name.length} chars)`);
- }
- if (choice.value && choice.value.length > 100) {
- validationErrors.push(`Command ${cmd.name} option ${option.name} choice ${choice.name} has value longer than 100 chars: "${choice.value}" (${choice.value.length} chars)`);
- }
- });
- }
-
- if (option.options) {
- option.options.forEach((subOption, subOptIndex) => {
- if (subOption.name && subOption.name.length > 32) {
- validationErrors.push(`Command ${cmd.name} subcommand ${option.name} option ${subOption.name} has name longer than 32 chars: "${subOption.name}" (${subOption.name.length} chars)`);
- }
- if (subOption.description && subOption.description.length > 110) {
- validationErrors.push(`Command ${cmd.name} subcommand ${option.name} option ${subOption.name} has description longer than 110 chars: "${subOption.description}" (${subOption.description.length} chars)`);
- }
-
- if (subOption.choices) {
- subOption.choices.forEach((choice, choiceIndex) => {
- if (choice.name && choice.name.length > 110) {
- validationErrors.push(`Command ${cmd.name} subcommand ${option.name} option ${subOption.name} choice ${choice.name} has name longer than 110 chars: "${choice.name}" (${choice.name.length} chars)`);
- }
- if (choice.value && choice.value.length > 100) {
- validationErrors.push(`Command ${cmd.name} subcommand ${option.name} option ${subOption.name} choice ${choice.name} has value longer than 100 chars: "${choice.value}" (${choice.value.length} chars)`);
- }
- });
- }
- });
- }
- });
- }
- });
-
- if (validationErrors.length > 0) {
- logger.error('Command validation failed. Errors:');
- validationErrors.forEach(error => logger.error(` - ${error}`));
- throw new Error(`Command validation failed with ${validationErrors.length} errors`);
- }
-
- logger.info('Command validation passed');
-
- const guild = await client.guilds.fetch(guildId);
-
- const existingCommands = await guild.commands.fetch();
- logger.info(`Found ${existingCommands.size} existing guild commands`);
-
- const MAX_COMMANDS = 100;
- let commandsToRegister = commands;
-
- if (commands.length > MAX_COMMANDS) {
- logger.warn(`Command count (${commands.length}) exceeds Discord limit (${MAX_COMMANDS}), truncating...`);
- commandsToRegister = commands.slice(0, MAX_COMMANDS);
- logger.info(`Truncated to ${commandsToRegister.length} commands for registration`);
- }
-
- if (process.env.NODE_ENV !== 'production') {
- logger.info(`Registering ${totalCommandsWithSubs} commands for guild ${guild.name} (${guild.id})`);
- }
-
- try {
- logger.info(`Registering ${commandsToRegister.length} new commands...`);
-
- await guild.commands.set(commandsToRegister);
-
- logger.info(`Successfully registered ${commandsToRegister.length} guild commands`);
-
- const registeredCommands = await guild.commands.fetch();
- if (registeredCommands.size !== commandsToRegister.length) {
- logger.warn(`Warning: Expected ${commandsToRegister.length} commands, but Discord reports ${registeredCommands.size} registered`);
- } else {
- logger.info(`Verification passed: ${registeredCommands.size} commands successfully registered`);
- }
-
- } catch (error) {
- logger.error('Failed to register commands:', error);
-
- if (existingCommands.size > 0) {
- logger.info('Attempting to restore previous commands due to registration failure...');
- try {
- await guild.commands.set(existingCommands.map(cmd => cmd));
- logger.info('Successfully restored previous commands');
- } catch (restoreError) {
- logger.error('Failed to restore previous commands:', restoreError);
- }
- }
-
- throw error;
- }
- } else {
- logger.info('Skipping global command registration - bot is guild-only');
- }
- } catch (error) {
- logger.error('Error registering commands:', error);
- throw error;
- }
-}
-
-/**
- * Reloads a specific command
- * @param {import('discord.js').Client} client - Discord.js client
- * @param {string} commandName - Name of the command to reload
- * @returns {Promise<{success: boolean, message: string}>} - Result of the reload operation
- */
-export async function reloadCommand(client, commandName) {
- const command = client.commands.get(commandName);
-
- if (!command) {
- return { success: false, message: `Command "${commandName}" not found` };
- }
-
- try {
- const commandPath = path.resolve(command.filePath);
- delete require.cache[require.resolve(commandPath)];
-
- const newCommand = (await import(`file://${commandPath}`)).default;
-
- client.commands.set(commandName, newCommand);
-
- logger.info(`Reloaded command: ${commandName}`);
- return { success: true, message: `Successfully reloaded command "${commandName}"` };
- } catch (error) {
- logger.error(`Error reloading command "${commandName}":`, error);
- return { success: false, message: `Error reloading command: ${error.message}` };
- }
-}
-
diff --git a/src/handlers/commands.js b/src/handlers/commands.js
deleted file mode 100644
index edd57787cc..0000000000
--- a/src/handlers/commands.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { readdir, stat } from 'fs/promises';
-import { join, dirname } from 'path';
-import { fileURLToPath } from 'url';
-import { logger } from '../utils/logger.js';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-async function getFiles(dir) {
- const dirents = await readdir(dir, { withFileTypes: true });
- const files = await Promise.all(dirents.map(async (dirent) => {
- const res = join(dir, dirent.name);
- return dirent.isDirectory() ? getFiles(res) : res;
- }));
- return files.flat();
-}
-
-export default async (client) => {
- try {
- const commandsPath = join(__dirname, '../commands');
- const commandFiles = (await getFiles(commandsPath))
- .filter(file => file.endsWith('.js') && !file.includes('_') && !file.includes('\\_') && !file.includes('/_'));
-
- let loadedCount = 0;
-
- for (const file of commandFiles) {
- try {
- const relativePath = file.replace(commandsPath, '').replace(/^[\\/]/, '').replace(/\\/g, '/');
- const commandModule = await import(`../commands/${relativePath}`);
- const command = commandModule.default;
-
- if (!command.data || !command.execute) {
- logger.warn(`Command at ${file} is missing required "data" or "execute" property.`);
- continue;
- }
-
- client.commands.set(command.data.name, command);
- loadedCount++;
- logger.debug(`Loaded command: ${command.data.name}`);
- } catch (error) {
- logger.error(`Error loading command ${file}:`, error);
- }
- }
-
- logger.info(`Successfully loaded ${loadedCount} commands`);
- } catch (error) {
- logger.error('Error loading commands:', error);
- }
-};
diff --git a/src/handlers/events.js b/src/handlers/events.js
deleted file mode 100644
index 6576a61260..0000000000
--- a/src/handlers/events.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { readdir } from 'fs/promises';
-import { join } from 'path';
-import { fileURLToPath } from 'url';
-import { dirname } from 'path';
-import { logger } from '../utils/logger.js';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-export default async function loadEvents(client) {
- const eventsPath = join(__dirname, '../events');
- const eventFiles = await readdir(eventsPath).then(files => files.filter(file => file.endsWith('.js')));
-
- for (const file of eventFiles) {
- const filePath = join(eventsPath, file);
- try {
- const { default: event } = await import(`file://${filePath}`);
-
- if (event.once) {
- client.once(event.name, (...args) => event.execute(...args, client));
- } else {
- client.on(event.name, (...args) => event.execute(...args, client));
- }
- } catch (error) {
- logger.error(`Error loading event ${file}:`, error);
- }
- }
-}
-
diff --git a/src/handlers/giveawayButtonLoader.js b/src/handlers/giveawayButtonLoader.js
deleted file mode 100644
index dca400858a..0000000000
--- a/src/handlers/giveawayButtonLoader.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { giveawayJoinHandler, giveawayEndHandler, giveawayRerollHandler } from './giveawayButtons.js';
-import { logger } from '../utils/logger.js';
-
-/**
- * Load giveaway button handlers
- * @param {Client} client - Discord client
- */
-export function loadGiveawayButtons(client) {
- client.buttons.set(giveawayJoinHandler.customId, giveawayJoinHandler);
- client.buttons.set(giveawayEndHandler.customId, giveawayEndHandler);
- client.buttons.set(giveawayRerollHandler.customId, giveawayRerollHandler);
-
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Giveaway button handlers loaded');
- }
-}
-
diff --git a/src/handlers/giveawayButtons.js b/src/handlers/giveawayButtons.js
deleted file mode 100644
index 536ea26bc4..0000000000
--- a/src/handlers/giveawayButtons.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import { giveawayEmbed, giveawayButtons, getGuildGiveaways, saveGiveaway, pickWinners, isGiveawayEnded } from '../utils/giveaways.js';
-import { createEmbed, errorEmbed, successEmbed } from '../utils/embeds.js';
-import { InteractionResponseType } from 'discord.js';
-
-export const giveawayJoinHandler = {
- customId: 'giveaway_join',
- async execute(interaction, client) {
- try {
- const guildGiveaways = await getGuildGiveaways(client, interaction.guildId);
- const giveaway = guildGiveaways[interaction.message.id];
-
- if (!giveaway) {
- return interaction.reply({
- embeds: [errorEmbed('Error', 'This giveaway is no longer active.')],
-flags: 64
- });
- }
-
- const endedByTime = isGiveawayEnded(giveaway);
- const endedByFlag = giveaway.ended || giveaway.isEnded;
- if (endedByFlag && !endedByTime) {
- giveaway.ended = false;
- giveaway.isEnded = false;
- await saveGiveaway(client, interaction.guildId, giveaway);
- }
-
- if (endedByTime || endedByFlag) {
- return interaction.reply({
- embeds: [errorEmbed('Error', 'This giveaway has already ended.')],
-flags: 64
- });
- }
-
- const participants = giveaway.participants || [];
- const userId = interaction.user.id;
-
- if (participants.includes(userId)) {
- return interaction.reply({
- embeds: [errorEmbed('Already Entered', 'You have already entered this giveaway!')],
-flags: 64
- });
- }
-
- participants.push(userId);
- giveaway.participants = participants;
-
- await saveGiveaway(client, interaction.guildId, giveaway);
-
- const updatedEmbed = giveawayEmbed(giveaway, 'active');
- const updatedRow = giveawayButtons(false);
-
- await interaction.message.edit({
- embeds: [updatedEmbed],
- components: [updatedRow]
- });
-
- await interaction.reply({
- embeds: [successEmbed('Success!', 'You have entered the giveaway! 🎉')],
-flags: 64
- });
-
- } catch (error) {
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Failed to join the giveaway. Please try again.')],
-flags: 64
- });
- }
- }
-};
-
-export const giveawayEndHandler = {
- customId: 'giveaway_end',
- async execute(interaction, client) {
- try {
- const guildGiveaways = await getGuildGiveaways(client, interaction.guildId);
- const giveaway = guildGiveaways[interaction.message.id];
-
- if (!giveaway) {
- return interaction.reply({
- embeds: [errorEmbed('Error', 'This giveaway is no longer active.')],
-flags: 64
- });
- }
-
- if (giveaway.ended || giveaway.isEnded || isGiveawayEnded(giveaway)) {
- return interaction.reply({
- embeds: [errorEmbed('Error', 'This giveaway has already ended.')],
-flags: 64
- });
- }
-
- const participants = giveaway.participants || [];
- const winners = pickWinners(participants, giveaway.winnerCount);
-
- giveaway.ended = true;
- giveaway.isEnded = true;
- giveaway.winners = winners;
- giveaway.endedAt = new Date().toISOString();
-
- await saveGiveaway(client, interaction.guildId, giveaway);
-
- const updatedEmbed = giveawayEmbed(giveaway, 'ended', winners);
- const updatedRow = giveawayButtons(true);
-
- await interaction.message.edit({
- content: '🎉 **GIVEAWAY ENDED** 🎉',
- embeds: [updatedEmbed],
- components: [updatedRow]
- });
-
- await interaction.reply({
- embeds: [successEmbed('Giveaway Ended', `The giveaway has been ended and ${winners.length} winner(s) have been selected!`)],
-flags: 64
- });
-
- } catch (error) {
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Failed to end the giveaway. Please try again.')],
-flags: 64
- });
- }
- }
-};
-
-export const giveawayRerollHandler = {
- customId: 'giveaway_reroll',
- async execute(interaction, client) {
- try {
- const guildGiveaways = await getGuildGiveaways(client, interaction.guildId);
- const giveaway = guildGiveaways[interaction.message.id];
-
- if (!giveaway) {
- return interaction.reply({
- embeds: [errorEmbed('Error', 'This giveaway is no longer active.')],
-flags: 64
- });
- }
-
- if (!giveaway.ended && !giveaway.isEnded) {
- return interaction.reply({
- embeds: [errorEmbed('Error', 'This giveaway has not ended yet.')],
-flags: 64
- });
- }
-
- const participants = giveaway.participants || [];
- const newWinners = pickWinners(participants, giveaway.winnerCount);
-
- giveaway.winners = newWinners;
- giveaway.rerolledAt = new Date().toISOString();
-
- await saveGiveaway(client, interaction.guildId, giveaway);
-
- const updatedEmbed = giveawayEmbed(giveaway, 'reroll', newWinners);
- const updatedRow = giveawayButtons(true);
-
- await interaction.message.edit({
- content: '🔄 **GIVEAWAY REROLLED** 🔄',
- embeds: [updatedEmbed],
- components: [updatedRow]
- });
-
- await interaction.reply({
- embeds: [successEmbed('Giveaway Rerolled', `New winner(s) have been selected!`)],
-flags: 64
- });
-
- } catch (error) {
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Failed to reroll the giveaway. Please try again.')],
-flags: 64
- });
- }
- }
-};
-
diff --git a/src/handlers/helpButtonLoader.js b/src/handlers/helpButtonLoader.js
deleted file mode 100644
index a0bb8f0bff..0000000000
--- a/src/handlers/helpButtonLoader.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { helpBackButton, helpBugReportButton, helpPaginationButton } from './helpButtons.js';
-import { logger } from '../utils/logger.js';
-
-/**
- * Load help button handlers
- * @param {Client} client - Discord client
- */
-export default function loadHelpButtons(client) {
- client.buttons.set(helpBackButton.name, helpBackButton);
- client.buttons.set(helpBugReportButton.name, helpBugReportButton);
- client.buttons.set('help-page_first', helpPaginationButton);
- client.buttons.set('help-page_prev', helpPaginationButton);
- client.buttons.set('help-page_next', helpPaginationButton);
- client.buttons.set('help-page_last', helpPaginationButton);
-
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Help button handlers loaded');
- }
-}
-
diff --git a/src/handlers/helpButtons.js b/src/handlers/helpButtons.js
deleted file mode 100644
index 672d35c6b4..0000000000
--- a/src/handlers/helpButtons.js
+++ /dev/null
@@ -1,259 +0,0 @@
-import { createEmbed } from '../utils/embeds.js';
-import { createButton, createSelectMenu, getPaginationRow } from '../utils/components.js';
-import { createAllCommandsMenu } from './helpSelectMenus.js';
-import fs from 'fs/promises';
-import path from 'path';
-import { fileURLToPath } from 'url';
-import { Collection, ActionRowBuilder, ButtonBuilder, ButtonStyle, MessageFlags } from 'discord.js';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-const COMMAND_LIST_ID = "help-command-list";
-const BACK_BUTTON_ID = "help-back-to-main";
-const CATEGORY_SELECT_ID = "help-category-select";
-const ALL_COMMANDS_ID = "help-all-commands";
-const PAGINATION_PREFIX = "help-page";
-const BUG_REPORT_BUTTON_ID = "help-bug-report";
-
-const CATEGORY_ICONS = {
- Core: "ℹ️",
- Moderation: "🛡️",
- Economy: "💰",
- Fun: "🎮",
- Leveling: "📊",
- Utility: "🔧",
- Ticket: "🎫",
- Welcome: "👋",
- Giveaway: "🎉",
- Counter: "🔢",
- Tools: "🛠️",
- Search: "🔍",
- Reaction_Roles: "🎭",
- Community: "👥",
- Birthday: "🎂",
- Config: "⚙️",
-};
-
-async function createCategorySelectMenu() {
- const commandsPath = path.join(__dirname, "../commands");
- const categoryDirs = (
- await fs.readdir(commandsPath, { withFileTypes: true })
- )
- .filter((dirent) => dirent.isDirectory())
- .map((dirent) => dirent.name)
- .sort();
-
- const options = [
- {
- label: "📋 All Commands",
- description: "View all available commands with pagination",
- value: ALL_COMMANDS_ID,
- },
- ...categoryDirs.map((category) => {
- const categoryName =
- category.charAt(0).toUpperCase() +
- category.slice(1).toLowerCase();
- const icon = CATEGORY_ICONS[categoryName] || "🔍";
- return {
- label: `${icon} ${categoryName}`,
- description: `View commands in the ${categoryName} category`,
- value: category,
- };
- }),
- ];
-
- const embed = createEmbed({
- title: "🤖 TitanBot Help Center",
- description: "Your all-in-one Discord companion for moderation, economy, fun, and server management.\n\nSelect a category below to explore our powerful commands:",
- color: 0x0099FF
- });
-
- embed.addFields(
- {
- name: "🛡️ **Moderation**",
- value: "Server moderation, user management, and enforcement tools",
- inline: true
- },
- {
- name: "💰 **Economy**",
- value: "Currency system, shops, and virtual economy",
- inline: true
- },
- {
- name: "🎮 **Fun**",
- value: "Games, entertainment, and interactive commands",
- inline: true
- },
- {
- name: "📊 **Leveling**",
- value: "User levels, XP system, and progression tracking",
- inline: true
- },
- {
- name: "🎫 **Tickets**",
- value: "Support ticket system for server management",
- inline: true
- },
- {
- name: "🎉 **Giveaways**",
- value: "Automated giveaway management and distribution",
- inline: true
- },
- {
- name: "👋 **Welcome**",
- value: "Member welcome messages and onboarding",
- inline: true
- },
- {
- name: "🎂 **Birthdays**",
- value: "Birthday tracking and celebration features",
- inline: true
- },
- {
- name: "🔧 **Utilities**",
- value: "Useful tools and server utilities",
- inline: true
- }
- );
-
- embed.setFooter({
- text: "Made with ❤️"
- });
- embed.setTimestamp();
-
- const bugReportButton = new ButtonBuilder()
- .setCustomId(BUG_REPORT_BUTTON_ID)
- .setLabel("Report Bug")
- .setStyle(ButtonStyle.Danger);
-
- const supportButton = new ButtonBuilder()
- .setLabel("Support Server")
- .setURL("https://discord.gg/QnWNz2dKCE")
- .setStyle(ButtonStyle.Link);
-
- const touchpointButton = new ButtonBuilder()
- .setLabel("Learn from Touchpoint")
- .setURL("https://www.youtube.com/@TouchDisc")
- .setStyle(ButtonStyle.Link);
-
- const selectRow = createSelectMenu(
- CATEGORY_SELECT_ID,
- "Select to view the commands",
- options,
- );
-
- const buttonRow = new ActionRowBuilder().addComponents([
- bugReportButton,
- supportButton,
- touchpointButton,
- ]);
-
- return {
- embeds: [embed],
- components: [buttonRow, selectRow],
- };
-}
-
-export const helpBackButton = {
- name: BACK_BUTTON_ID,
- async execute(interaction, client) {
- const { embeds, components } = await createCategorySelectMenu();
- await interaction.update({
- embeds,
- components,
- });
- },
-};
-
-export const helpBugReportButton = {
- name: BUG_REPORT_BUTTON_ID,
- async execute(interaction, client) {
- const githubButton = new ButtonBuilder()
- .setLabel('🐛 Report Bug on GitHub')
- .setStyle(ButtonStyle.Link)
- .setURL('https://github.com/codebymitch/TitanBot/issues');
-
- const bugRow = new ActionRowBuilder().addComponents(githubButton);
-
- const bugReportEmbed = createEmbed({
- title: '🐛 Bug Report',
- description: 'Found a bug? Please report it on our GitHub Issues page!\n\n' +
- '**When reporting a bug, please include:**\n' +
- '• 📝 Detailed description of the issue\n' +
- '• 📋 Steps to reproduce the problem\n' +
- '• 📸 Screenshots if applicable\n' +
- '• 💻 Your bot version and environment\n\n' +
- 'This helps us fix issues faster and more effectively!'
- });
- bugReportEmbed.setColor(0xff0000);
- bugReportEmbed.setFooter({
- text: 'TitanBot Bug Reporting System',
- iconURL: client.user.displayAvatarURL()
- });
- bugReportEmbed.setTimestamp();
-
- await interaction.reply({
- embeds: [bugReportEmbed],
- components: [bugRow],
- flags: MessageFlags.Ephemeral
- });
- },
-};
-
-export const helpReportCommand = {
- name: COMMAND_LIST_ID,
- categoryName: null,
- async execute(interaction, client) {
- // This is handled by help select menu
- }
-};
-
-function getPaginationInfo(components) {
- for (const row of components || []) {
- for (const component of row.components || []) {
- if (component.customId === `${PAGINATION_PREFIX}_page`) {
- const label = component.label || '';
- const match = label.match(/Page\s+(\d+)\s+of\s+(\d+)/i);
- if (match) {
- return {
- currentPage: Number(match[1]),
- totalPages: Number(match[2]),
- };
- }
- }
- }
- }
-
- return { currentPage: 1, totalPages: 1 };
-}
-
-export const helpPaginationButton = {
- name: `${PAGINATION_PREFIX}_next`,
- async execute(interaction, client) {
- const { currentPage, totalPages } = getPaginationInfo(interaction.message?.components);
-
- let nextPage = currentPage;
- switch (interaction.customId) {
- case `${PAGINATION_PREFIX}_first`:
- nextPage = 1;
- break;
- case `${PAGINATION_PREFIX}_prev`:
- nextPage = Math.max(1, currentPage - 1);
- break;
- case `${PAGINATION_PREFIX}_next`:
- nextPage = Math.min(totalPages, currentPage + 1);
- break;
- case `${PAGINATION_PREFIX}_last`:
- nextPage = totalPages;
- break;
- default:
- nextPage = currentPage;
- break;
- }
-
- const { embeds, components } = await createAllCommandsMenu(nextPage, client);
- await interaction.update({ embeds, components });
- },
-};
-
diff --git a/src/handlers/helpSelectMenuLoader.js b/src/handlers/helpSelectMenuLoader.js
deleted file mode 100644
index adf8b97f9c..0000000000
--- a/src/handlers/helpSelectMenuLoader.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { helpCategorySelectMenu } from './helpSelectMenus.js';
-import { logger } from '../utils/logger.js';
-
-/**
- * Load help select menu handlers
- * @param {Client} client - Discord client
- */
-export default function loadHelpSelectMenus(client) {
- client.selectMenus.set(helpCategorySelectMenu.name, helpCategorySelectMenu);
-
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Help select menu handlers loaded');
- }
-}
-
diff --git a/src/handlers/helpSelectMenus.js b/src/handlers/helpSelectMenus.js
deleted file mode 100644
index faa0bb2298..0000000000
--- a/src/handlers/helpSelectMenus.js
+++ /dev/null
@@ -1,307 +0,0 @@
-import { createEmbed } from '../utils/embeds.js';
-import { createButton, getPaginationRow } from '../utils/components.js';
-import fs from 'fs/promises';
-import path from 'path';
-import { fileURLToPath } from 'url';
-import { Collection, ActionRowBuilder } from 'discord.js';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-const BACK_BUTTON_ID = "help-back-to-main";
-const ALL_COMMANDS_ID = "help-all-commands";
-const PAGINATION_PREFIX = "help-page";
-const CATEGORY_SELECT_ID = "help-category-select";
-
-const CATEGORY_ICONS = {
- Core: "ℹ️",
- Moderation: "🛡️",
- Economy: "💰",
- Fun: "🎮",
- Leveling: "📊",
- Utility: "🔧",
- Ticket: "🎫",
- Welcome: "👋",
- Giveaway: "🎉",
- Counter: "🔢",
- Tools: "🛠️",
- Search: "🔍",
- Reaction_Roles: "🎭",
- Community: "👥",
- Birthday: "🎂",
- Config: "⚙️",
-};
-
-async function createCategoryCommandsMenu(category, client) {
- const categoryName =
- category.charAt(0).toUpperCase() + category.slice(1).toLowerCase();
- const icon = CATEGORY_ICONS[categoryName] || "🔍";
-
- const categoryCommands = [];
-
- try {
- const categoryPath = path.join(__dirname, "../commands", category);
- const commandFiles = (await fs.readdir(categoryPath))
- .filter((file) => file.endsWith(".js"))
- .sort();
-
- for (const file of commandFiles) {
- const filePath = path.join(categoryPath, file);
- const commandModule = await import(`file://${filePath}`);
- const command = commandModule.default;
-
- if (command && command.data && command.data.name) {
- if (
- command.data.name === "help" ||
- command.data.name === "commandlist"
- )
- continue;
-
- categoryCommands.push({
- name: command.data.name,
- description: command.data.description || "No description",
- options: command.data.options || [],
- });
- }
- }
- } catch (error) {
- console.error(
- `Error reading commands from category ${category}:`,
- error,
- );
- }
-
- categoryCommands.sort((a, b) => a.name.localeCompare(b.name));
-
- let registeredCommands = new Collection();
- try {
- if (client && client.application) {
- const commands = await client.application.commands.fetch();
- for (const cmd of commands.values()) {
- registeredCommands.set(cmd.name, cmd);
- }
- }
- } catch (error) {
- console.error("Error fetching registered commands:", error);
- }
-
- const embed = createEmbed({
- title: `${icon} ${categoryName} Commands`,
- description: categoryCommands.length > 0
- ? `Click any command mention below to use it:`
- : `No commands found in the **${categoryName}** category.`
- });
-
- if (categoryCommands.length > 0) {
- const commandMentions = categoryCommands
- .map((cmd) => {
- const registeredCmd = registeredCommands.get(cmd.name);
- if (registeredCmd && registeredCmd.id) {
- return `${cmd.name}:${registeredCmd.id}> · ${cmd.description}`;
- }
- return `\`/${cmd.name}\` · ${cmd.description}`;
- })
- .join("\n");
-
- const maxLength = 1000;
- if (commandMentions.length <= maxLength) {
- embed.addFields({
- name: "Commands",
- value: commandMentions,
- inline: false,
- });
- } else {
- const chunks = [];
- let currentChunk = "";
- const lines = commandMentions.split("\n");
-
- for (const line of lines) {
- if ((currentChunk + "\n" + line).length > maxLength) {
- if (currentChunk) chunks.push(currentChunk);
- currentChunk = line;
- } else {
- currentChunk += (currentChunk ? "\n" : "") + line;
- }
- }
- if (currentChunk) chunks.push(currentChunk);
-
- chunks.forEach((chunk, index) => {
- embed.addFields({
- name: `Commands (Part ${index + 1})`,
- value: chunk,
- inline: false,
- });
- });
- }
- }
-
- const backButton = createButton(
- BACK_BUTTON_ID,
- "Back",
- "primary",
- "⬅️",
- false,
- );
-
- const buttonRow = new ActionRowBuilder().addComponents(backButton);
-
- return {
- embeds: [embed],
- components: [buttonRow],
- };
-}
-
-export async function createAllCommandsMenu(page = 1, client) {
- const commandsPerPage = 45;
- const allCommands = [];
-
- const commandsPath = path.join(__dirname, "../commands");
- const categoryDirs = (
- await fs.readdir(commandsPath, { withFileTypes: true })
- )
- .filter((dirent) => dirent.isDirectory())
- .map((dirent) => dirent.name)
- .sort();
-
- for (const category of categoryDirs) {
- try {
- const categoryPath = path.join(
- __dirname,
- "../commands",
- category,
- );
- const commandFiles = (await fs.readdir(categoryPath))
- .filter((file) => file.endsWith(".js"))
- .sort();
-
- for (const file of commandFiles) {
- const filePath = path.join(categoryPath, file);
- const commandModule = await import(`file://${filePath}`);
- const command = commandModule.default;
-
- if (command && command.data && command.data.name) {
- if (
- command.data.name === "help" ||
- command.data.name === "commandlist"
- )
- continue;
-
- allCommands.push({
- name: command.data.name,
- description:
- command.data.description || "No description",
- category:
- category.charAt(0).toUpperCase() +
- category.slice(1).toLowerCase(),
- });
- }
- }
- } catch (error) {
- console.error(
- `Error reading commands from category ${category}:`,
- error,
- );
- }
- }
-
- allCommands.sort((a, b) => a.name.localeCompare(b.name));
-
- let registeredCommands = new Collection();
- try {
- if (client && client.application) {
- const commands = await client.application.commands.fetch();
- for (const cmd of commands.values()) {
- registeredCommands.set(cmd.name, cmd);
- }
- }
- } catch (error) {
- console.error("Error fetching registered commands:", error);
- }
-
- const totalPages = Math.ceil(allCommands.length / commandsPerPage);
- const startIndex = (page - 1) * commandsPerPage;
- const endIndex = startIndex + commandsPerPage;
- const pageCommands = allCommands.slice(startIndex, endIndex);
-
- const embed = createEmbed({
- title: "📋 All Commands",
- description: `(${allCommands.length}+ total commands)`
- });
-
- if (pageCommands.length > 0) {
- const commandMentions = pageCommands.map((cmd) => {
- const registeredCmd = registeredCommands.get(cmd.name);
- if (registeredCmd && registeredCmd.id) {
- return `${cmd.name}:${registeredCmd.id}> · ${cmd.category}`;
- }
- return `\`/${cmd.name}\` · ${cmd.category}`;
- });
-
- const columnCount = pageCommands.length > 20 ? 3 : (pageCommands.length > 10 ? 2 : 1);
- const chunkSize = Math.ceil(commandMentions.length / columnCount);
-
- for (let i = 0; i < columnCount; i++) {
- const chunk = commandMentions
- .slice(i * chunkSize, (i + 1) * chunkSize)
- .join("\n");
-
- if (!chunk) continue;
-
- embed.addFields({
- name: i === 0 ? `Commands (Page ${page})` : "Commands (cont.)",
- value: chunk,
- inline: columnCount > 1,
- });
- }
- }
-
- const components = [];
-
- if (totalPages > 1) {
- const paginationRow = getPaginationRow(
- PAGINATION_PREFIX,
- page,
- totalPages,
- );
- components.push(paginationRow);
- }
-
- const backButton = createButton(
- BACK_BUTTON_ID,
- "Back",
- "primary",
- "⬅️",
- false,
- );
-
- const buttonRow = new ActionRowBuilder().addComponents(backButton);
- components.push(buttonRow);
-
- return {
- embeds: [embed],
- components,
- currentPage: page,
- totalPages,
- };
-}
-
-export const helpCategorySelectMenu = {
- name: CATEGORY_SELECT_ID,
- async execute(interaction, client) {
- const selectedCategory = interaction.values[0];
-
- if (selectedCategory === ALL_COMMANDS_ID) {
- const { embeds, components } = await createAllCommandsMenu(1, client);
- await interaction.update({
- embeds,
- components,
- });
- } else {
- const { embeds, components } = await createCategoryCommandsMenu(selectedCategory, client);
- await interaction.update({
- embeds,
- components,
- });
- }
- },
-};
diff --git a/src/handlers/interactions.js b/src/handlers/interactions.js
deleted file mode 100644
index e72b1baf78..0000000000
--- a/src/handlers/interactions.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import { readdir } from 'fs/promises';
-import { join } from 'path';
-import { fileURLToPath } from 'url';
-import { dirname } from 'path';
-import { logger } from '../utils/logger.js';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-const interactionTypes = ['buttons', 'selectMenus', 'modals'];
-
-export default async (client) => {
- try {
- const interactionsPath = join(__dirname, '../interactions');
-
- for (const type of interactionTypes) {
- const typePath = join(interactionsPath, type);
-
- try {
- const interactionFiles = (await readdir(typePath)).filter(file => file.endsWith('.js'));
-
- for (const file of interactionFiles) {
- try {
- const interaction = (await import(`../interactions/${type}/${file}`)).default;
-
- if (!interaction.name || !interaction.execute) {
- logger.warn(`Interaction ${file} in ${type} is missing required properties.`);
- continue;
- }
-
- client[type].set(interaction.name, interaction);
- logger.info(`Loaded ${type.slice(0, -1)}: ${interaction.name}`);
- } catch (error) {
- logger.error(`Error loading interaction ${file} in ${type}:`, error);
- }
- }
-
- logger.info(`Loaded ${interactionFiles.length} ${type}`);
- } catch (error) {
- if (error.code !== 'ENOENT') {
- logger.error(`Error loading ${type}:`, error);
- } else {
- logger.warn(`No ${type} directory found, skipping...`);
- }
- }
- }
- } catch (error) {
- logger.error('Error loading interactions:', error);
- }
-};
-
diff --git a/src/handlers/reactionRoles.js b/src/handlers/reactionRoles.js
deleted file mode 100644
index 4cd28aae49..0000000000
--- a/src/handlers/reactionRoles.js
+++ /dev/null
@@ -1,181 +0,0 @@
-import { Events, EmbedBuilder, MessageFlags } from 'discord.js';
-import { getReactionRoleMessage, addReactionRole, removeReactionRole } from '../services/reactionRoleService.js';
-import { errorEmbed } from '../utils/embeds.js';
-
-/**
- * Handle reaction add events for reaction roles
- * @param {import('discord.js').Client} client - The Discord client
- * @param {import('discord.js').MessageReaction} reaction - The reaction object
- * @param {import('discord.js').User} user - The user who reacted
- * @returns {Promise}
- */
-async function handleReactionAdd(client, reaction, user) {
- try {
- if (user.bot || !reaction.message.guild) return;
-
- const { message } = reaction;
- const { guild } = message;
- const emoji = reaction.emoji.id || reaction.emoji.name;
-
- const reactionRoleMessage = await getReactionRoleMessage(
- client,
- guild.id,
- message.id
- );
-
- if (!reactionRoleMessage) return;
-
- const roleId = reactionRoleMessage.roles[emoji];
- if (!roleId) return;
-
- const member = await guild.members.fetch(user.id);
- const role = guild.roles.cache.get(roleId);
-
- if (!role) {
- await removeReactionRole(client, guild.id, message.id, emoji);
- return;
- }
-
- await member.roles.add(role);
-
- } catch (error) {
- console.error('Error in handleReactionAdd:', error);
- }
-}
-
-/**
- * Handle reaction remove events for reaction roles
- * @param {import('discord.js').Client} client - The Discord client
- * @param {import('discord.js').MessageReaction} reaction - The reaction object
- * @param {import('discord.js').User} user - The user who removed their reaction
- * @returns {Promise}
- */
-async function handleReactionRemove(client, reaction, user) {
- try {
- if (user.bot || !reaction.message.guild) return;
-
- const { message } = reaction;
- const { guild } = message;
- const emoji = reaction.emoji.id || reaction.emoji.name;
-
- const reactionRoleMessage = await getReactionRoleMessage(
- client,
- guild.id,
- message.id
- );
-
- if (!reactionRoleMessage) return;
-
- const roleId = reactionRoleMessage.roles[emoji];
- if (!roleId) return;
-
- const member = await guild.members.fetch(user.id);
- const role = guild.roles.cache.get(roleId);
-
- if (!role) {
- await removeReactionRole(client, guild.id, message.id, emoji);
- return;
- }
-
- await member.roles.remove(role);
-
- } catch (error) {
- console.error('Error in handleReactionRemove:', error);
- }
-}
-
-/**
- * Handle reaction role interactions
- * @param {import('discord.js').Interaction} interaction - The interaction to handle
- * @returns {Promise} Whether the interaction was handled
- */
-export async function handleReactionRoles(interaction) {
- try {
- if (!interaction.isCommand()) return false;
-
- const { commandName, options, guild, member } = interaction;
-
- if (commandName === 'reactionrole') {
- const subcommand = options.getSubcommand();
-
- if (subcommand === 'create') {
- if (!member.permissions.has('MANAGE_ROLES')) {
- await interaction.reply({
- embeds: [errorEmbed('You need the `Manage Roles` permission to use this command.')],
- flags: MessageFlags.Ephemeral
- });
- return true;
- }
-
- const messageId = options.getString('message_id');
- const emoji = options.getString('emoji');
- const role = options.getRole('role');
-
- let emojiId = emoji;
- const emojiMatch = emoji.match(//);
- if (emojiMatch) {
- emojiId = emojiMatch[1];
- }
-
- await addReactionRole(
- interaction.client,
- guild.id,
- messageId,
- emojiId,
- role.id
- );
-
- try {
- const channel = interaction.channel;
- const message = await channel.messages.fetch(messageId);
- await message.react(emoji);
- } catch (error) {
- console.error('Error adding reaction to message:', error);
- }
-
- await interaction.reply({
- embeds: [
- new EmbedBuilder()
- .setDescription(`✅ Added reaction role for ${emoji} to <@&${role.id}>`)
- .setColor('#00ff00')
- ],
- flags: MessageFlags.Ephemeral
- });
-
- return true;
- }
- }
-
- return false;
- } catch (error) {
- console.error('Error in handleReactionRoles:', error);
- if (interaction.replied || interaction.deferred) {
- await interaction.followUp({
- embeds: [errorEmbed('An error occurred while processing your request.')],
- flags: MessageFlags.Ephemeral
- });
- } else {
- await interaction.reply({
- embeds: [errorEmbed('An error occurred while processing your request.')],
- flags: MessageFlags.Ephemeral
- });
- }
- return true;
- }
-}
-
-/**
- * Set up reaction role event listeners
- * @param {import('discord.js').Client} client - The Discord client
- */
-export function setupReactionRoleListeners(client) {
- client.on(Events.MessageReactionAdd, async (reaction, user) => {
- await handleReactionAdd(client, reaction, user);
- });
-
- client.on(Events.MessageReactionRemove, async (reaction, user) => {
- await handleReactionRemove(client, reaction, user);
- });
-}
-
-
diff --git a/src/handlers/shopButtonLoader.js b/src/handlers/shopButtonLoader.js
deleted file mode 100644
index 7effca6dd1..0000000000
--- a/src/handlers/shopButtonLoader.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { shopPurchaseHandler } from './shopButtons.js';
-import { logger } from '../utils/logger.js';
-
-/**
- * Load shop button handlers
- * @param {Client} client - Discord client
- */
-export default async function loadShopButtons(client) {
- try {
- client.buttons.set('shop_buy', shopPurchaseHandler);
-
- logger.info('Shop button handlers loaded');
- } catch (error) {
- logger.error('Error loading shop button handlers:', error);
- }
-}
-
diff --git a/src/handlers/shopButtons.js b/src/handlers/shopButtons.js
deleted file mode 100644
index a14497b17d..0000000000
--- a/src/handlers/shopButtons.js
+++ /dev/null
@@ -1,128 +0,0 @@
-import { shopItems } from '../config/shop/items.js';
-import { errorEmbed, successEmbed } from '../utils/embeds.js';
-import { getEconomyData, setEconomyData } from '../utils/economy.js';
-import { getGuildConfig } from '../services/guildConfig.js';
-import { InteractionHelper } from '../utils/interactionHelper.js';
-import { MessageFlags } from 'discord.js';
-import { withErrorHandling, createError, ErrorTypes } from '../utils/errorHandler.js';
-
-/**
- * Handle shop purchase button clicks
- * Button ID format: shop_buy:item_id
- */
-const shopPurchaseHandler = {
- name: 'shop_buy',
- async execute(interaction, client, args) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const itemId = args[0];
-
- if (!itemId) {
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'Invalid item ID.')],
- });
- return;
- }
-
- // Find the item in the shop
- const item = shopItems.find(i => i.id === itemId);
-
- if (!item) {
- await interaction.editReply({
- embeds: [errorEmbed('Invalid Item', `The item \`${itemId}\` does not exist in the shop.`)],
- });
- return;
- }
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
- const quantity = 1; // Default quantity for button purchase
-
- const totalCost = item.price * quantity;
-
- // Get user economy data
- const userData = await getEconomyData(client, guildId, userId);
-
- // Check if user has enough money
- if (userData.wallet < totalCost) {
- await interaction.editReply({
- embeds: [errorEmbed(
- 'Insufficient Funds',
- `You need **$${totalCost.toLocaleString()}** to purchase **${item.name}**, but you only have **$${userData.wallet.toLocaleString()}** in cash.\n\nUse the **\`/work\`** command to earn more money.`
- )],
- });
- return;
- }
-
- // Handle role purchases
- if (item.type === 'role' && itemId === 'premium_role') {
- const guildConfig = await getGuildConfig(client, guildId);
- const PREMIUM_ROLE_ID = guildConfig.premiumRoleId;
-
- if (!PREMIUM_ROLE_ID) {
- await interaction.editReply({
- embeds: [errorEmbed('Not Configured', 'The **Premium Shop Role** has not been configured by a server administrator.')],
- });
- return;
- }
-
- if (interaction.member.roles.cache.has(PREMIUM_ROLE_ID)) {
- await interaction.editReply({
- embeds: [errorEmbed('Already Own', `You already have the **${item.name}** role.`)],
- });
- return;
- }
-
- // Add role to user
- try {
- await interaction.member.roles.add(PREMIUM_ROLE_ID, `Purchased from shop`);
- } catch (error) {
- console.error('Error adding premium role:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'Failed to add the role. Please contact an administrator.')],
- });
- return;
- }
- }
-
- // Handle inventory items
- if (!userData.inventory) {
- userData.inventory = {};
- }
-
- if (!userData.inventory[itemId]) {
- userData.inventory[itemId] = 0;
- }
-
- userData.inventory[itemId] += quantity;
- userData.wallet -= totalCost;
-
- // Save updated data
- await setEconomyData(client, guildId, userId, userData);
-
- // Success message
- await interaction.editReply({
- embeds: [successEmbed(
- `✅ **Purchase Successful!**\n\n` +
- `You bought **${quantity}x ${item.name}** for **$${totalCost.toLocaleString()}**\n\n` +
- `**New Balance:** $${userData.wallet.toLocaleString()}`,
- '🛒 Shop Purchase'
- )],
- });
-
- console.log(`✅ User ${interaction.user.tag} (${userId}) purchased ${quantity}x ${item.id} for $${totalCost} in guild ${guildId}`);
-
- } catch (error) {
- console.error('Shop purchase button handler error:', error);
-
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while processing your purchase. Please try again later.')],
- });
- }
- }
-};
-
-export { shopPurchaseHandler };
-
diff --git a/src/handlers/ticketButtonLoader.js b/src/handlers/ticketButtonLoader.js
deleted file mode 100644
index a8544e44ec..0000000000
--- a/src/handlers/ticketButtonLoader.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import createTicketHandler, {
- createTicketModalHandler,
- closeTicketHandler,
- claimTicketHandler,
- priorityTicketHandler,
- transcriptTicketHandler,
- unclaimTicketHandler,
- reopenTicketHandler,
- deleteTicketHandler
-} from './ticketButtons.js';
-import { logger } from '../utils/logger.js';
-
-export default async function loadTicketButtons(client) {
- try {
- client.buttons.set('create_ticket', createTicketHandler);
- client.buttons.set('ticket_close', closeTicketHandler);
- client.buttons.set('ticket_claim', claimTicketHandler);
- client.buttons.set('ticket_priority', priorityTicketHandler);
- client.buttons.set('ticket_transcript', transcriptTicketHandler);
- client.buttons.set('ticket_unclaim', unclaimTicketHandler);
- client.buttons.set('ticket_reopen', reopenTicketHandler);
- client.buttons.set('ticket_delete', deleteTicketHandler);
-
- client.modals.set('create_ticket_modal', createTicketModalHandler);
-
- logger.info('Ticket button handlers loaded');
- } catch (error) {
- logger.error('Error loading ticket button handlers:', error);
- }
-}
-
diff --git a/src/handlers/ticketButtons.js b/src/handlers/ticketButtons.js
deleted file mode 100644
index 81aa285946..0000000000
--- a/src/handlers/ticketButtons.js
+++ /dev/null
@@ -1,527 +0,0 @@
-import { ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, AttachmentBuilder, MessageFlags } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../utils/embeds.js';
-import { createTicket, closeTicket, claimTicket, updateTicketPriority } from '../services/ticket.js';
-import { getGuildConfig } from '../services/guildConfig.js';
-import { logEvent } from '../utils/moderation.js';
-import { logTicketEvent } from '../utils/ticketLogging.js';
-import { logger } from '../utils/logger.js';
-import { InteractionHelper } from '../utils/interactionHelper.js';
-
-const createTicketHandler = {
- name: 'create_ticket',
- async execute(interaction, client) {
- try {
- const config = await getGuildConfig(client, interaction.guildId);
- const categoryId = config.ticketCategoryId || null;
- const maxTicketsPerUser = config.maxTicketsPerUser || 3;
-
- const { getUserTicketCount } = await import('../services/ticket.js');
- const currentTicketCount = await getUserTicketCount(interaction.guildId, interaction.user.id);
- const remainingTickets = maxTicketsPerUser - currentTicketCount;
-
- if (currentTicketCount >= maxTicketsPerUser) {
- return interaction.reply({
- embeds: [
- errorEmbed(
- '🎫 Ticket Limit Reached',
- `You have reached the maximum number of open tickets (${maxTicketsPerUser}).\n\nPlease close your existing tickets before creating a new one.\n\n**Current Tickets:** ${currentTicketCount}/${maxTicketsPerUser}`
- )
- ],
- flags: MessageFlags.Ephemeral
- });
- }
-
- const modal = new ModalBuilder()
- .setCustomId('create_ticket_modal')
- .setTitle('Create a Ticket');
-
- const reasonInput = new TextInputBuilder()
- .setCustomId('reason')
- .setLabel('Why are you creating this ticket?')
- .setStyle(TextInputStyle.Paragraph)
- .setPlaceholder('Describe your issue...')
- .setRequired(true)
- .setMaxLength(1000);
-
- const actionRow = new ActionRowBuilder().addComponents(reasonInput);
- modal.addComponents(actionRow);
-
- await interaction.showModal(modal);
- } catch (error) {
- console.error('Error creating ticket modal:', error);
- if (!interaction.replied && !interaction.deferred) {
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Could not open ticket creation form.')],
- flags: MessageFlags.Ephemeral
- });
- } else {
- await interaction.followUp({
- embeds: [errorEmbed('Error', 'Could not open ticket creation form.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
- }
-};
-
-const createTicketModalHandler = {
- name: 'create_ticket_modal',
- async execute(interaction, client) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const reason = interaction.fields.getTextInputValue('reason');
- const config = await getGuildConfig(client, interaction.guildId);
- const categoryId = config.ticketCategoryId || null;
-
- const result = await createTicket(
- interaction.guild,
- interaction.member,
- categoryId,
- reason
- );
-
- if (result.success) {
- await interaction.editReply({
- embeds: [successEmbed(
- 'Ticket Created',
- `Your ticket has been created in ${result.channel}!`
- )]
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed('Error', result.error || 'Failed to create ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error('Error creating ticket:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while creating your ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-const closeTicketHandler = {
- name: 'ticket_close',
- async execute(interaction, client) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const result = await closeTicket(interaction.channel, interaction.user);
-
- if (result.success) {
- await interaction.editReply({
- embeds: [successEmbed('Ticket Closed', 'This ticket has been closed.')],
- flags: MessageFlags.Ephemeral
- });
-
- await logEvent({
- client,
- guildId: interaction.guildId,
- event: {
- action: 'Ticket Closed',
- target: interaction.channel.toString(),
- executor: interaction.user.toString(),
- reason: 'Closed via ticket button'
- }
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed('Error', result.error || 'Failed to close ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error('Error closing ticket:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while closing the ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-const claimTicketHandler = {
- name: 'ticket_claim',
- async execute(interaction, client) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const result = await claimTicket(interaction.channel, interaction.user);
-
- if (result.success) {
- await interaction.editReply({
- embeds: [successEmbed('Ticket Claimed', 'You have successfully claimed this ticket!')],
- flags: MessageFlags.Ephemeral
- });
-
- await logEvent({
- client,
- guildId: interaction.guildId,
- event: {
- action: 'Ticket Claimed',
- target: interaction.channel.toString(),
- executor: interaction.user.toString()
- }
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed('Error', result.error || 'Failed to claim ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error('Error claiming ticket:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while claiming the ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-const priorityTicketHandler = {
- name: 'ticket_priority',
- async execute(interaction, client, args) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const priority = args[0];
- const result = await updateTicketPriority(interaction.channel, priority, interaction.user);
-
- if (result.success) {
- await interaction.editReply({
- embeds: [successEmbed('Priority Updated', `Ticket priority set to ${priority}.`)],
- flags: MessageFlags.Ephemeral
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed('Error', result.error || 'Failed to update priority.')],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error('Error updating ticket priority:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while updating the priority.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-const transcriptTicketHandler = {
- name: 'ticket_transcript',
- async execute(interaction, client) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const messages = await interaction.channel.messages.fetch({ limit: 100 });
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Total messages fetched:', messages?.size || 0);
- }
-
- if (!messages || messages.size === 0) {
- await interaction.editReply({
- embeds: [errorEmbed('No Messages', 'No messages found in this ticket channel.')],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- const messagesArray = Array.from(messages.values());
- const userMessages = messagesArray.filter(m => {
- const hasAuthor = m.author && typeof m.author === 'object';
- const hasTag = hasAuthor && m.author.tag;
-const isUserMessage = m.type === 0;
-
- if (!hasAuthor) {
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Filtering message without author:', m.id, m.type);
- }
- } else if (!hasTag) {
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Message author exists but no tag:', m.id, m.author);
- }
- }
-
- return hasAuthor && hasTag && isUserMessage;
- });
-
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Filtered user messages:', userMessages?.length || 0);
- }
- const sortedMessages = userMessages.sort((a, b) => a.createdTimestamp - b.createdTimestamp);
-
- if (!sortedMessages || sortedMessages.length === 0) {
- await interaction.editReply({
- embeds: [errorEmbed('No User Messages', 'No user messages found to include in the transcript.')],
- flags: MessageFlags.Ephemeral
- });
- return;
- }
-
- let htmlTranscript = `
-
-
- Ticket Transcript - ${interaction.channel.name}
-
-
-
-
-`;
-
- for (const message of sortedMessages) {
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Processing message:', message.id, 'Author exists:', !!message.author, 'Attachments exist:', !!message.attachments);
- }
-
- const timestamp = ``;
- const author = message.author?.tag || message.author?.username || 'Unknown User';
- const content = message.content || '*No content (embed/attachment only)*';
-
- htmlTranscript += `
-
-
[${timestamp}]
-
${author}
-
${content.replace(/\n/g, ' ')}
`;
-
- if (message.attachments && message.attachments.size > 0) {
- htmlTranscript += `
-
- 📎 Attachments: ${message.attachments.map(a => `
${a.name} `).join(', ')}
-
`;
- }
-
- htmlTranscript += `
-
`;
- }
-
- htmlTranscript += `
-
-`;
-
- const transcriptEmbed = createEmbed({
- title: `📜 Ticket Transcript - ${interaction.channel.name}`,
- description: `**Channel:** ${interaction.channel.name}\n**Created:** \n**Generated:** 📅 \n**Messages:** ${sortedMessages.length}\n\n📎 The complete HTML transcript has been attached as a file.`,
- color: 0x3498db,
- footer: { text: `Ticket ID: ${interaction.channel.id}` }
- });
-
- const { Buffer } = await import('buffer');
- const buffer = Buffer.from(htmlTranscript, 'utf-8');
-
- try {
- await interaction.user.send({
- content: `📜 **Ticket Transcript** for \`${interaction.channel.name}\``,
- embeds: [transcriptEmbed],
- files: [{
- attachment: buffer,
- name: `ticket-transcript-${interaction.channel.name}.html`
- }]
- });
-
- await interaction.editReply({
- embeds: [{
- title: '✅ Transcript Sent',
- description: 'The ticket transcript has been sent to your DMs as both an embed and an HTML file.',
-color: 4689679
- }],
- flags: MessageFlags.Ephemeral
- });
-
- await logTicketEvent({
- client: interaction.client,
- guildId: interaction.guildId,
- event: {
- type: 'transcript',
- ticketId: interaction.channel.id,
- ticketNumber: interaction.channel.name.replace(/[^0-9]/g, ''),
- userId: interaction.user.id,
- executorId: interaction.user.id,
- metadata: {
- messageCount: sortedMessages.length,
- sentToDM: true,
- transcriptSize: htmlTranscript.length
- },
- attachments: [
- new AttachmentBuilder(buffer, { name: `ticket-transcript-${interaction.channel.name}.html` })
- ]
- }
- });
- } catch (dmError) {
- console.error('Could not DM user:', dmError);
- await interaction.editReply({
- embeds: [errorEmbed('DM Failed', 'I couldn\'t send you the transcript. Please enable DMs from server members.')],
- flags: MessageFlags.Ephemeral
- });
- }
-
- } catch (error) {
- console.error('Error creating transcript:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'Failed to create ticket transcript.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-const unclaimTicketHandler = {
- name: 'ticket_unclaim',
- async execute(interaction, client) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const { unclaimTicket } = await import('../services/ticket.js');
- const result = await unclaimTicket(interaction.channel, interaction.member);
-
- if (result.success) {
- await interaction.editReply({
- embeds: [successEmbed('Ticket Unclaimed', 'You have successfully unclaimed this ticket!')],
- flags: MessageFlags.Ephemeral
- });
-
- await logEvent({
- client,
- guildId: interaction.guildId,
- event: {
- action: 'Ticket Unclaimed',
- target: interaction.channel.toString(),
- executor: interaction.user.toString()
- }
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed('Error', result.error || 'Failed to unclaim ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error('Error unclaiming ticket:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while unclaiming the ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-const reopenTicketHandler = {
- name: 'ticket_reopen',
- async execute(interaction, client) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const { reopenTicket } = await import('../services/ticket.js');
- const result = await reopenTicket(interaction.channel, interaction.member);
-
- if (result.success) {
- await interaction.editReply({
- embeds: [successEmbed('Ticket Reopened', 'You have successfully reopened this ticket!')],
- flags: MessageFlags.Ephemeral
- });
-
- await logEvent({
- client,
- guildId: interaction.guildId,
- event: {
- action: 'Ticket Reopened',
- target: interaction.channel.toString(),
- executor: interaction.user.toString()
- }
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed('Error', result.error || 'Failed to reopen ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error('Error reopening ticket:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while reopening the ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-const deleteTicketHandler = {
- name: 'ticket_delete',
- async execute(interaction, client) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const { deleteTicket } = await import('../services/ticket.js');
- const result = await deleteTicket(interaction.channel, interaction.member);
-
- if (result.success) {
- await interaction.editReply({
- embeds: [successEmbed('Ticket Deleted', 'This ticket will be permanently deleted in 3 seconds.')],
- flags: MessageFlags.Ephemeral
- });
-
- await logEvent({
- client,
- guildId: interaction.guildId,
- event: {
- action: 'Ticket Deleted',
- target: interaction.channel.toString(),
- executor: interaction.user.toString()
- }
- });
- } else {
- await interaction.editReply({
- embeds: [errorEmbed('Error', result.error || 'Failed to delete ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- } catch (error) {
- console.error('Error deleting ticket:', error);
- await interaction.editReply({
- embeds: [errorEmbed('Error', 'An error occurred while deleting the ticket.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-export default createTicketHandler;
-export {
- createTicketModalHandler,
- closeTicketHandler,
- claimTicketHandler,
- priorityTicketHandler,
- transcriptTicketHandler,
- unclaimTicketHandler,
- reopenTicketHandler,
- deleteTicketHandler
-};
-
-
diff --git a/src/handlers/todoButtonLoader.js b/src/handlers/todoButtonLoader.js
deleted file mode 100644
index 0be6f17165..0000000000
--- a/src/handlers/todoButtonLoader.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import todoAddHandler, { sharedTodoCompleteHandler, sharedTodoAddModalHandler, sharedTodoCompleteModalHandler } from './todoButtons.js';
-import { logger } from '../utils/logger.js';
-
-export default async function loadTodoButtons(client) {
- try {
- client.buttons.set('shared_todo_add', todoAddHandler);
-
- client.buttons.set('shared_todo_complete', sharedTodoCompleteHandler);
-
- client.modals.set('shared_todo_add_modal', sharedTodoAddModalHandler);
- client.modals.set('shared_todo_complete_modal', sharedTodoCompleteModalHandler);
-
- logger.info('Todo button handlers loaded');
- } catch (error) {
- logger.error('Error loading todo button handlers:', error);
- }
-}
-
diff --git a/src/handlers/todoButtons.js b/src/handlers/todoButtons.js
deleted file mode 100644
index f9ef3589a3..0000000000
--- a/src/handlers/todoButtons.js
+++ /dev/null
@@ -1,165 +0,0 @@
-import { ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, MessageFlags } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed } from '../utils/embeds.js';
-import { getFromDb, setInDb } from '../utils/database.js';
-
-const sharedTodoAddHandler = {
- name: 'shared_todo_add',
- async execute(interaction, client, args) {
- const listId = args[0];
-
- const modal = new ModalBuilder()
- .setCustomId(`shared_todo_add_modal:${listId}`)
- .setTitle('Add Task to Shared List');
-
- const taskInput = new TextInputBuilder()
- .setCustomId('task_text')
- .setLabel('Enter the task description')
- .setStyle(TextInputStyle.Short)
- .setRequired(true)
- .setMaxLength(200);
-
- const actionRow = new ActionRowBuilder().addComponents(taskInput);
- modal.addComponents(actionRow);
-
- await interaction.showModal(modal);
- }
-};
-
-const sharedTodoCompleteHandler = {
- name: 'shared_todo_complete',
- async execute(interaction, client, args) {
- const listId = args[0];
-
- const modal = new ModalBuilder()
- .setCustomId(`shared_todo_complete_modal:${listId}`)
- .setTitle('Complete Task in Shared List');
-
- const taskIdInput = new TextInputBuilder()
- .setCustomId('task_id')
- .setLabel('Enter the task ID to complete')
- .setStyle(TextInputStyle.Short)
- .setRequired(true)
- .setPlaceholder('e.g., 1, 2, 3');
-
- const actionRow = new ActionRowBuilder().addComponents(taskIdInput);
- modal.addComponents(actionRow);
-
- await interaction.showModal(modal);
- }
-};
-
-const sharedTodoAddModalHandler = {
- name: 'shared_todo_add_modal',
- async execute(interaction, client, args) {
- const listId = args[0];
- const taskText = interaction.fields.getTextInputValue('task_text');
- const userId = interaction.user.id;
-
- try {
- const listKey = `shared_todo_${listId}`;
- let listData = await getFromDb(listKey, null);
-
- if (!listData) {
- return interaction.reply({
- embeds: [errorEmbed("Error", "Shared list not found.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- if (!listData.members || !listData.members.includes(userId)) {
- return interaction.reply({
- embeds: [errorEmbed("Error", "You don't have access to this list.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- if (!listData.tasks) listData.tasks = [];
- if (!listData.nextId) listData.nextId = 1;
-
- const newTask = {
- id: listData.nextId++,
- text: taskText,
- completed: false,
- createdAt: new Date().toISOString(),
- createdBy: userId
- };
-
- listData.tasks.push(newTask);
- await setInDb(listKey, listData);
-
- return interaction.reply({
- embeds: [successEmbed("Task Added", `Added "${taskText}" to the shared list.`)],
- flags: MessageFlags.Ephemeral
- });
-
- } catch (error) {
- console.error("Error in shared todo add modal:", error);
- return interaction.reply({
- embeds: [errorEmbed("Error", "An error occurred while adding the task.")],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-const sharedTodoCompleteModalHandler = {
- name: 'shared_todo_complete_modal',
- async execute(interaction, client, args) {
- const listId = args[0];
- const taskId = parseInt(interaction.fields.getTextInputValue('task_id'));
- const userId = interaction.user.id;
-
- try {
- const listKey = `shared_todo_${listId}`;
- let listData = await getFromDb(listKey, null);
-
- if (!listData) {
- return interaction.reply({
- embeds: [errorEmbed("Error", "Shared list not found.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- if (!listData.members || !listData.members.includes(userId)) {
- return interaction.reply({
- embeds: [errorEmbed("Error", "You don't have access to this list.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- if (!listData.tasks) listData.tasks = [];
-
- const task = listData.tasks.find(t => t.id === taskId);
-
- if (!task) {
- return interaction.reply({
- embeds: [errorEmbed("Error", "Task not found.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- task.completed = true;
- task.completedBy = userId;
- task.completedAt = new Date().toISOString();
-
- await setInDb(listKey, listData);
-
- return interaction.reply({
- embeds: [successEmbed("Task Completed", `Marked "${task.text}" as complete!`)],
- flags: MessageFlags.Ephemeral
- });
-
- } catch (error) {
- console.error("Error in shared todo complete modal:", error);
- return interaction.reply({
- embeds: [errorEmbed("Error", "An error occurred while completing the task.")],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-};
-
-export default sharedTodoAddHandler;
-export { sharedTodoCompleteHandler, sharedTodoAddModalHandler, sharedTodoCompleteModalHandler };
-
-
diff --git a/src/handlers/verificationButtonLoader.js b/src/handlers/verificationButtonLoader.js
deleted file mode 100644
index 2a140eb3d3..0000000000
--- a/src/handlers/verificationButtonLoader.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import verificationButtonHandler from '../interactions/verificationButtonHandler.js';
-
-/**
- * Load verification button handlers
- * @param {Client} client - Discord client
- */
-export async function loadVerificationButtons(client) {
- client.buttons.set(verificationButtonHandler.customId, verificationButtonHandler);
- console.log('✅ Loaded verification button handler');
-}
-
-export default { loadVerificationButtons };
-
diff --git a/src/handlers/wipedataButtonLoader.js b/src/handlers/wipedataButtonLoader.js
deleted file mode 100644
index 6121d79fde..0000000000
--- a/src/handlers/wipedataButtonLoader.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { wipedataConfirmHandler, wipedataCancelHandler } from './wipedataButtons.js';
-import { logger } from '../utils/logger.js';
-
-/**
- * Load wipedata button handlers
- * @param {Client} client - Discord client
- */
-export default async function loadWipedataButtons(client) {
- try {
- client.buttons.set('wipedata_yes', wipedataConfirmHandler);
- client.buttons.set('wipedata_no', wipedataCancelHandler);
-
- logger.info('Wipedata button handlers loaded');
- } catch (error) {
- logger.error('Error loading wipedata button handlers:', error);
- }
-}
-
diff --git a/src/handlers/wipedataButtons.js b/src/handlers/wipedataButtons.js
deleted file mode 100644
index fc09c99c76..0000000000
--- a/src/handlers/wipedataButtons.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import { createEmbed, errorEmbed, successEmbed } from '../utils/embeds.js';
-import { InteractionHelper } from '../utils/interactionHelper.js';
-import { MessageFlags } from 'discord.js';
-
-/**
- * Handle wipedata confirmation button
- * Deletes all user data from the database
- */
-const wipedataConfirmHandler = {
- name: 'wipedata_yes',
- async execute(interaction, client) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const userId = interaction.user.id;
- const guildId = interaction.guildId;
-
- // List of all possible data keys for a user that should be wiped
- const dataKeyPatterns = [
- `economy:${guildId}:${userId}`,
- `level:${guildId}:${userId}`,
- `xp:${guildId}:${userId}`,
- `inventory:${guildId}:${userId}`,
- `bank:${guildId}:${userId}`,
- `wallet:${guildId}:${userId}`,
- `cooldowns:${guildId}:${userId}`,
- `shop:${guildId}:${userId}`,
- `shop_data:${guildId}:${userId}`,
- `counter:${guildId}:${userId}`,
- `birthday:${guildId}:${userId}`,
- `balance:${guildId}:${userId}`,
- `user:${guildId}:${userId}`,
- `leveling:${guildId}:${userId}`,
- `crimexp:${guildId}:${userId}`,
- `robxp:${guildId}:${userId}`,
- `crime_cooldown:${guildId}:${userId}`,
- `rob_cooldown:${guildId}:${userId}`,
- `lastDaily:${guildId}:${userId}`,
- `lastWork:${guildId}:${userId}`,
- `lastCrime:${guildId}:${userId}`,
- `lastRob:${guildId}:${userId}`,
- ];
-
- let deletedCount = 0;
- const deleteErrors = [];
-
- // Delete each data key
- for (const key of dataKeyPatterns) {
- try {
- const exists = await client.db.exists(key);
- if (exists) {
- await client.db.delete(key);
- deletedCount++;
- }
- } catch (error) {
- console.error(`Error deleting key ${key}:`, error);
- deleteErrors.push(key);
- }
- }
-
- // Check for any additional user-related keys by prefix search if available
- try {
- if (client.db.list && typeof client.db.list === 'function') {
- const userPrefix = `${guildId}:${userId}`;
- const allKeys = await client.db.list(userPrefix);
-
- if (Array.isArray(allKeys)) {
- for (const key of allKeys) {
- if (!dataKeyPatterns.includes(key)) {
- try {
- await client.db.delete(key);
- deletedCount++;
- } catch (error) {
- console.error(`Error deleting additional key ${key}:`, error);
- }
- }
- }
- }
- }
- } catch (error) {
- console.warn('Could not perform prefix search on database:', error);
- }
-
- const successMessage =
- `✅ **Your data has been successfully wiped!**\n\n` +
- `**Records Deleted:** ${deletedCount}\n\n` +
- `Your account has been reset to default values. You can now start fresh!\n\n` +
- `*All your economy balance, levels, items, and personal data have been removed.*`;
-
- await interaction.editReply({
- embeds: [successEmbed(successMessage, 'ðŸ—‘ï¸ Data Wipe Complete')],
- components: []
- });
-
- console.log(`✅ User ${interaction.user.tag} (${userId}) wiped their data in guild ${guildId} - Deleted ${deletedCount} records`);
-
- } catch (error) {
- console.error('Wipedata confirm button handler error:', error);
-
- await interaction.editReply({
- embeds: [errorEmbed('Data Wipe Failed', 'An error occurred while wiping your data. Please try again later or contact support.')],
- components: []
- });
- }
- }
-};
-
-/**
- * Handle wipedata cancellation button
- * Simply closes the confirmation prompt
- */
-const wipedataCancelHandler = {
- name: 'wipedata_no',
- async execute(interaction, client) {
- try {
- await interaction.update({
- embeds: [
- createEmbed({
- title: '⌠Data Wipe Cancelled',
- description: 'Your data has been preserved. Your account remains unchanged.',
- color: 'info'
- })
- ],
- components: []
- });
-
- console.log(`â„¹ï¸ User ${interaction.user.tag} (${interaction.user.id}) cancelled data wipe in guild ${interaction.guildId}`);
- } catch (error) {
- console.error('Wipedata cancel button handler error:', error);
-
- if (!interaction.replied && !interaction.deferred) {
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Could not cancel data wipe.')],
- flags: MessageFlags.Ephemeral
- });
- }
- }
- }
-};
-
-export { wipedataConfirmHandler, wipedataCancelHandler };
-
-
diff --git a/src/interactions/selectMenus/reaction_roles.js b/src/interactions/selectMenus/reaction_roles.js
deleted file mode 100644
index 1e62788fb4..0000000000
--- a/src/interactions/selectMenus/reaction_roles.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import { EmbedBuilder, MessageFlags } from 'discord.js';
-import { InteractionHelper } from '../../utils/interactionHelper.js';
-
-/**
- * Handle reaction role select menu interactions
- * @param {import('discord.js').StringSelectMenuInteraction} interaction - The select menu interaction
- * @param {import('discord.js').Client} client - The Discord client
- * @returns {Promise}
- */
-export async function execute(interaction, client) {
- try {
- const deferSuccess = await InteractionHelper.safeDefer(interaction, { flags: MessageFlags.Ephemeral });
- if (!deferSuccess) return;
-
- const key = `reaction_roles:${interaction.guildId}:${interaction.message.id}`;
- const reactionRoleData = await client.db.get(key);
-
- let actualData;
- if (reactionRoleData && reactionRoleData.ok && reactionRoleData.value) {
- actualData = reactionRoleData.value;
- } else if (reactionRoleData && reactionRoleData.value) {
- actualData = reactionRoleData.value;
- } else {
- actualData = reactionRoleData;
- }
-
- if (!actualData) {
- return interaction.editReply({
- embeds: [
- new EmbedBuilder()
- .setDescription('⌠This reaction role message is no longer active.')
- .setColor('#ED4245')
- ]
- });
- }
-
- const member = interaction.member;
- const selectedRoleIds = interaction.values;
-
- if (!interaction.guild.members.me.permissions.has('ManageRoles')) {
- return interaction.editReply({
- embeds: [
- new EmbedBuilder()
- .setDescription('⌠I do not have permission to manage roles in this server.')
- .setColor('#ED4245')
- ]
- });
- }
-
- const botRolePosition = interaction.guild.members.me.roles.highest.position;
-
- let availableRoleIds;
- if (Array.isArray(actualData.roles)) {
- availableRoleIds = actualData.roles;
- } else if (typeof actualData.roles === 'object') {
- availableRoleIds = Object.values(actualData.roles);
- } else {
- availableRoleIds = [];
- }
-
- const addedRoles = [];
- const removedRoles = [];
-
- for (const roleId of selectedRoleIds) {
- if (!availableRoleIds.includes(roleId)) continue;
-
- const role = interaction.guild.roles.cache.get(roleId);
- if (!role) continue;
-
- if (role.position >= botRolePosition) continue;
-
- if (!member.roles.cache.has(roleId)) {
- await member.roles.add(role);
- addedRoles.push(role.name);
- }
- }
-
- for (const roleId of availableRoleIds) {
-if (selectedRoleIds.includes(roleId)) continue;
-
- const role = interaction.guild.roles.cache.get(roleId);
- if (!role) continue;
-
- if (role.position >= botRolePosition) continue;
-
- if (member.roles.cache.has(roleId)) {
- await member.roles.remove(role);
- removedRoles.push(role.name);
- }
- }
-
- let description = '🎠**Roles updated successfully!**\n\n';
-
- if (addedRoles.length > 0) {
- description += `✅ **Added:** ${addedRoles.map(name => `**${name}**`).join(', ')}\n`;
- }
-
- if (removedRoles.length > 0) {
- description += `⌠**Removed:** ${removedRoles.map(name => `**${name}**`).join(', ')}\n`;
- }
-
- if (addedRoles.length === 0 && removedRoles.length === 0) {
- description += 'No changes were made to your roles.';
- }
-
- const responseEmbed = new EmbedBuilder()
- .setDescription(description)
- .setColor('#57F287')
- .setTimestamp();
-
- await interaction.editReply({ embeds: [responseEmbed] });
-
- } catch (error) {
- console.error('Error handling reaction role select menu:', error);
-
- const errorEmbed = new EmbedBuilder()
- .setDescription('⌠An error occurred while updating your roles. Please try again.')
- .setColor('#ED4245');
-
- if (interaction.replied || interaction.deferred) {
- await interaction.followUp({ embeds: [errorEmbed], flags: MessageFlags.Ephemeral });
- } else {
- await interaction.reply({ embeds: [errorEmbed], flags: MessageFlags.Ephemeral });
- }
- }
-}
-
-export default {
- name: 'reaction_roles',
- execute
-};
-
diff --git a/src/interactions/verificationButtonHandler.js b/src/interactions/verificationButtonHandler.js
deleted file mode 100644
index e282af097f..0000000000
--- a/src/interactions/verificationButtonHandler.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import { ButtonInteraction, PermissionFlagsBits, MessageFlags } from 'discord.js';
-import { createEmbed, errorEmbed, successEmbed, infoEmbed } from '../utils/embeds.js';
-import { getGuildConfig } from '../services/guildConfig.js';
-
-/**
- * Handle verification button clicks
- * @param {ButtonInteraction} interaction - The button interaction
- * @param {Client} client - Discord client
- */
-export async function handleVerificationButton(interaction, client) {
- const guild = interaction.guild;
- const member = interaction.member;
-
- try {
- const guildConfig = await getGuildConfig(client, guild.id);
-
- if (!guildConfig.verification?.enabled) {
- return await interaction.reply({
- embeds: [errorEmbed("Verification Disabled", "The verification system is not enabled on this server.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- const verifiedRole = guild.roles.cache.get(guildConfig.verification.roleId);
- if (!verifiedRole) {
- return await interaction.reply({
- embeds: [errorEmbed("Configuration Error", "Verified role not found. Please contact an administrator.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- if (member.roles.cache.has(verifiedRole.id)) {
- return await interaction.reply({
- embeds: [infoEmbed("Already Verified", "You are already verified and have access to the server.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- if (!guild.members.me.permissions.has("ManageRoles")) {
- return await interaction.reply({
- embeds: [errorEmbed("Bot Permission Error", "I don't have permission to manage roles. Please contact an administrator.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- const botRole = guild.members.me.roles.highest;
- if (verifiedRole.position >= botRole.position) {
- return await interaction.reply({
- embeds: [errorEmbed("Role Hierarchy Error", "The verified role is higher than or equal to my highest role. Please contact an administrator.")],
- flags: MessageFlags.Ephemeral
- });
- }
-
- await member.roles.add(verifiedRole.id, "User verified themselves");
-
- await interaction.reply({
- embeds: [successEmbed(
- "✅ Verification Successful!",
- `You have been verified and given the **${verifiedRole.name}** role! Welcome to the server! 🎉\n\nYou now have access to all server channels and features.`
- )],
- flags: MessageFlags.Ephemeral
- });
-
- console.log(`✅ ${member.user.tag} (${member.id}) verified themselves in ${guild.name}`);
-
- } catch (error) {
- console.error("Verification button handler error:", error);
-
- if (interaction.replied || interaction.deferred) {
- await interaction.followUp({
- embeds: [errorEmbed("Verification Failed", "Failed to assign verified role. Please contact an administrator.")],
- flags: MessageFlags.Ephemeral
- });
- } else {
- await interaction.reply({
- embeds: [errorEmbed("Verification Failed", "Failed to assign verified role. Please contact an administrator.")],
- flags: MessageFlags.Ephemeral
- });
- }
- }
-}
-
-export default {
- customId: "verify_user",
- execute: handleVerificationButton
-};
-
diff --git a/src/services/birthdayService.js b/src/services/birthdayService.js
deleted file mode 100644
index ddc54f5ee6..0000000000
--- a/src/services/birthdayService.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import { getGuildConfig } from './guildConfig.js';
-import { getGuildBirthdays } from '../utils/database.js';
-import { logger } from '../utils/logger.js';
-
-/**
- * Check for birthdays across all guilds and send birthday messages
- * @param {Object} client - Discord client
- */
-export async function checkBirthdays(client) {
- const today = new Date();
- const currentMonth = today.getUTCMonth() + 1;
- const currentDay = today.getUTCDate();
-
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`🎂 Running daily birthday check for UTC: ${currentMonth}/${currentDay}.`);
- }
-
- for (const [guildId, guild] of client.guilds.cache) {
- try {
- const config = await getGuildConfig(client, guildId);
- const { birthdayChannelId, birthdayRoleId } = config;
-
- if (!birthdayChannelId || !birthdayRoleId) {
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`Skipping birthday check for ${guild.name}: Missing channel or role config.`);
- }
- continue;
- }
-
- const channel = await guild.channels.fetch(birthdayChannelId).catch(() => null);
- if (!channel) continue;
-
- const trackingKey = `bday-role-tracking-${guildId}`;
- const trackingData = (await client.db.get(trackingKey)) || {};
- const updatedTrackingData = { ...trackingData };
-
- for (const userId of Object.keys(trackingData)) {
- try {
- const member = await guild.members.fetch(userId).catch(() => null);
- if (member && member.roles.cache.has(birthdayRoleId)) {
- await member.roles.remove(birthdayRoleId, "Birthday role expired");
- }
- delete updatedTrackingData[userId];
- } catch (error) {
- console.error(`Error removing birthday role from ${userId}:`, error);
- }
- }
-
- if (Object.keys(updatedTrackingData).length !== Object.keys(trackingData).length) {
- await client.db.set(trackingKey, updatedTrackingData);
- }
-
- const birthdaysKey = `birthdays:${guildId}`;
- const birthdays = (await client.db.get(birthdaysKey)) || {};
- const birthdayMembers = [];
- for (const [userId, userData] of Object.entries(birthdays)) {
- if (userData.month === currentMonth && userData.day === currentDay) {
- const member = await guild.members.fetch(userId).catch(() => null);
- if (member) {
- birthdayMembers.push(member);
- try {
- await member.roles.add(birthdayRoleId, "Happy Birthday! 🎉");
- updatedTrackingData[userId] = true;
- } catch (error) {
- console.error(`Error adding birthday role to ${member.user.tag}:`, error);
- }
- }
- }
- }
-
- if (birthdayMembers.length > 0) {
- await client.db.set(trackingKey, updatedTrackingData);
- const mentionList = birthdayMembers.map(m => m.toString()).join(', ');
-
- await channel.send({
- embeds: [{
- title: '🎉 Happy Birthday! 🎂',
- description: `A very happy birthday to ${mentionList}! Wishing you an amazing day! 🎈`,
- color: 0xff69b4,
- footer: { text: 'Birthday Bot' },
- timestamp: new Date()
- }]
- });
- }
- } catch (error) {
- logger.error(`Error processing birthdays for guild ${guildId}:`, error);
- }
- }
-}
-
diff --git a/src/services/counterService.js b/src/services/counterService.js
deleted file mode 100644
index 53a3e8e328..0000000000
--- a/src/services/counterService.js
+++ /dev/null
@@ -1,154 +0,0 @@
-import { logger } from '../utils/logger.js';
-
-/**
- * Update a specific counter
- * @param {Client} client - Discord client
- * @param {Guild} guild - The guild
- * @param {Object} counter - The counter to update
- * @returns {Promise} Whether the update was successful
- */
-export async function updateCounter(client, guild, counter) {
- try {
- if (!counter || !counter.type || !counter.channelId) {
- logger.warn('Skipping invalid counter in updateCounter:', counter);
- return false;
- }
-
- const { type, channelId } = counter;
- const channel = guild.channels.cache.get(channelId);
- if (!channel) {
- logger.error('Channel not found for counter:', channelId);
- return false;
- }
-
- let count;
- switch (type) {
- case "members":
- count = guild.memberCount;
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`Member count for guild ${guild.id}: ${count}`);
- }
- break;
- case "bots":
- count = guild.members.cache.filter((m) => m.user.bot).size;
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`Bot count for guild ${guild.id}: ${count}`);
- }
- break;
- case "members_only":
- count = guild.members.cache.filter((m) => !m.user.bot).size;
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`Human count for guild ${guild.id}: ${count}`);
- }
- break;
- default:
- logger.error('Unknown counter type:', type);
- return false;
- }
-
- const baseName = channel.name.replace(/\s*[:]\s*\d+$/, '').trim();
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`Base name: "${baseName}", Current name: "${channel.name}"`);
- }
-
- const newName = `${baseName}: ${count}`;
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`New name would be: "${newName}"`);
- }
-
- if (channel.name !== newName) {
- try {
- await channel.setName(newName);
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`Updated channel name to: "${newName}"`);
- }
- } catch (error) {
- logger.error(`Failed to update channel name for ${channel.id}:`, error);
- return false;
- }
- } else {
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Channel name already correct, no update needed');
- }
- }
- return true;
- } catch (error) {
- logger.error("Error updating counter:", error);
- return false;
- }
-}
-
-/**
- * Get all server counters
- * @param {Client} client - Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} The counters array
- */
-export async function getServerCounters(client, guildId) {
- try {
- if (!client || !client.db) {
- logger.warn('Database not available for getServerCounters');
- return [];
- }
-
- const data = await client.db.get(`counters:${guildId}`);
-
- let counters = [];
-
- if (data && typeof data === 'object' && data.ok && Array.isArray(data.value)) {
- counters = data.value;
- } else if (Array.isArray(data)) {
- counters = data;
- } else if (data && typeof data === 'object' && !data.ok) {
- counters = Object.values(data);
- } else {
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('No counter data found, returning empty array');
- }
- return [];
- }
-
-
- const validCounters = counters.filter(counter =>
- counter &&
- typeof counter === 'object' &&
- counter.type &&
- counter.channelId &&
- counter.id
- );
-
- return validCounters;
- } catch (error) {
- logger.error("Error getting server counters:", error);
- return [];
- }
-}
-
-/**
- * Save server counters
- * @param {Client} client - Discord client
- * @param {string} guildId - The guild ID
- * @param {Array} counters - The counters array to save
- * @returns {Promise} Whether the save was successful
- */
-export async function saveServerCounters(client, guildId, counters) {
- try {
- if (!client || !client.db) {
- logger.warn('Database not available for saveServerCounters');
- return false;
- }
-
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`Saving ${counters.length} counters for guild ${guildId}:`, counters);
- }
- await client.db.set(`counters:${guildId}`, counters);
- if (process.env.NODE_ENV !== 'production') {
- logger.debug('Counters saved successfully');
- }
- return true;
- } catch (error) {
- logger.error("Error saving server counters:", error);
- return false;
- }
-}
-
diff --git a/src/services/database.js b/src/services/database.js
deleted file mode 100644
index e3c477e2a9..0000000000
--- a/src/services/database.js
+++ /dev/null
@@ -1,287 +0,0 @@
-import 'dotenv/config';
-import { pgDb } from '../utils/postgresDatabase.js';
-import { logger } from '../utils/logger.js';
-
-let db = null;
-let useFallback = false;
-let connectionType = 'none';
-
-async function initializeServicesDatabase() {
- try {
- logger.info('Services: Attempting to connect to PostgreSQL...');
- const pgConnected = await pgDb.connect();
- if (pgConnected) {
- db = pgDb;
- connectionType = 'postgresql';
- logger.info('✅ Services: PostgreSQL Database initialized');
- return;
- }
- } catch (error) {
- logger.warn('Services: PostgreSQL connection failed, using mock database:', error.message);
- }
-
- db = {
- get: async (key, defaultValue = null) => defaultValue,
- set: async (key, value, ttl = null) => true,
- delete: async (key) => true,
- list: async (prefix) => [],
- exists: async (key) => false,
- increment: async (key, amount = 1) => amount,
- decrement: async (key, amount = 1) => -amount
- };
- useFallback = true;
- connectionType = 'memory';
- logger.info('Services: Using mock database (fallback)');
-}
-
-initializeServicesDatabase();
-
-
-export function getGuildConfigKey(guildId) {
- return `guild:${guildId}:config`;
-}
-
-export function getGuildBirthdaysKey(guildId) {
- return `guild:${guildId}:birthdays`;
-}
-
-export function getEconomyKey(guildId, userId) {
- return `guild:${guildId}:economy:${userId}`;
-}
-
-export function getAFKKey(guildId, userId) {
- return `guild:${guildId}:afk:${userId}`;
-}
-
-export function giveawayKey(guildId) {
- return `guild:${guildId}:giveaways`;
-}
-
-export const getGiveawaysKey = giveawayKey;
-
-export function getTicketKey(guildId, channelId) {
- return `guild:${guildId}:ticket:${channelId}`;
-}
-
-export function getWelcomeConfigKey(guildId) {
- return `guild:${guildId}:welcome`;
-}
-
-export function getLevelingKey(guildId) {
- return `guild:${guildId}:leveling`;
-}
-
-export function getUserLevelKey(guildId, userId) {
- return `guild:${guildId}:leveling:${userId}`;
-}
-
-export function getInviteTrackingKey(guildId) {
- return `guild:${guildId}:invites`;
-}
-
-export function getMemberInvitesKey(guildId, userId) {
- return `guild:${guildId}:invites:${userId}`;
-}
-
-export function getInviteUsesKey(guildId, inviteCode) {
- return `guild:${guildId}:invite_uses:${inviteCode}`;
-}
-
-export function getFakeAccountKey(guildId, userId) {
- return `guild:${guildId}:fake_account:${userId}`;
-}
-
-
-export async function initializeDatabase() {
- await initializeServicesDatabase();
- logger.info('Services database initialized');
- return db;
-}
-
-function extractData(replitResponse) {
- if (replitResponse && typeof replitResponse === 'object') {
- let current = replitResponse;
- while (current && typeof current === 'object' && 'ok' in current && 'value' in current) {
- current = current.value;
- }
-
- if (current && typeof current === 'object' && 'tasks' in current) {
- return current;
- }
-
- return current;
- }
- return replitResponse;
-}
-
-export async function getFromDb(key, defaultValue = null) {
- try {
- if (!db) {
- await initializeServicesDatabase();
- }
- const value = await db.get(key);
-
- const extractedData = extractData(value);
-
- const result = extractedData !== null ? extractedData : defaultValue;
- return result;
- } catch (error) {
- logger.error(`Error getting value for key ${key}:`, error);
- return defaultValue;
- }
-}
-
-export async function setInDb(key, value, ttl = null) {
- try {
- if (!db) {
- await initializeServicesDatabase();
- }
- await db.set(key, value, ttl);
-
- const verifyValue = await db.get(key);
- const extractedVerifyValue = extractData(verifyValue);
-
- return true;
- } catch (error) {
- logger.error(`Error setting value for key ${key}:`, error);
- return false;
- }
-}
-
-export async function deleteFromDb(key) {
- try {
- if (!db) {
- await initializeServicesDatabase();
- }
- await db.delete(key);
- return true;
- } catch (error) {
- logger.error(`Error deleting key ${key}:`, error);
- return false;
- }
-}
-
-
-export async function setGuildConfig(client, guildId, newData) {
- try {
- if (!db) {
- await initializeServicesDatabase();
- }
- const key = getGuildConfigKey(guildId);
- await db.set(key, newData);
- return true;
- } catch (error) {
- logger.error('Error setting guild config:', error);
- throw error;
- }
-}
-
-
-export async function getTicketData(guildId, channelId) {
- if (!db) {
- await initializeServicesDatabase();
- }
- const key = getTicketKey(guildId, channelId);
- return await db.get(key);
-}
-
-export async function saveTicketData(guildId, channelId, data) {
- if (!db) {
- await initializeServicesDatabase();
- }
- const key = getTicketKey(guildId, channelId);
- await db.set(key, data);
-}
-
-export async function deleteTicketData(guildId, channelId) {
- if (!db) {
- await initializeServicesDatabase();
- }
- const key = getTicketKey(guildId, channelId);
- await db.delete(key);
-}
-
-
-export async function getGuildGiveaways(client, guildId) {
- if (!db) {
- await initializeServicesDatabase();
- }
- const key = giveawayKey(guildId);
- return await db.get(key) || [];
-}
-
-export async function saveGiveaway(client, guildId, giveawayData) {
- if (!db) {
- await initializeServicesDatabase();
- }
- const key = giveawayKey(guildId);
- const giveaways = await getGuildGiveaways(client, guildId);
- const existingIndex = giveaways.findIndex(g => g.messageId === giveawayData.messageId);
-
- if (existingIndex >= 0) {
- giveaways[existingIndex] = giveawayData;
- } else {
- giveaways.push(giveawayData);
- }
-
- await db.set(key, giveaways);
- return giveawayData;
-}
-
-export async function deleteGiveaway(client, guildId, messageId) {
- if (!db) {
- await initializeServicesDatabase();
- }
- const key = giveawayKey(guildId);
- const giveaways = await getGuildGiveaways(client, guildId);
- const updatedGiveaways = giveaways.filter(g => g.messageId !== messageId);
-
- if (updatedGiveaways.length !== giveaways.length) {
- await db.set(key, updatedGiveaways);
- return true;
- }
-
- return false;
-}
-
-
-export async function getWelcomeConfig(client, guildId) {
- if (!db) {
- await initializeServicesDatabase();
- }
- const key = getWelcomeConfigKey(guildId);
- const config = await db.get(key);
-
- if (!config) {
- const defaultConfig = {
- enabled: false,
- channel: null,
- message: 'Welcome {user} to {server}!',
- dmMessage: null,
- role: null
- };
-
- await db.set(key, defaultConfig);
- return defaultConfig;
- }
-
- return config;
-}
-
-export async function saveWelcomeConfig(client, guildId, config) {
- if (!db) {
- await initializeServicesDatabase();
- }
- const key = getWelcomeConfigKey(guildId);
- await db.set(key, config);
- return config;
-}
-
-export default {
- db,
- pgDb,
- initializeServicesDatabase,
- getConnectionType: () => connectionType,
- useFallback: () => useFallback
-};
-
diff --git a/src/services/economy.js b/src/services/economy.js
deleted file mode 100644
index 8708317efa..0000000000
--- a/src/services/economy.js
+++ /dev/null
@@ -1,305 +0,0 @@
-import { BotConfig } from "../config/bot.js";
-import { getEconomyKey } from './database.js';
-import { getFromDb, setInDb } from '../utils/database.js';
-import { logger } from '../utils/logger.js';
-import { normalizeEconomyData } from '../utils/schemas.js';
-
-const BASE_BANK_CAPACITY = BotConfig.economy.baseBankCapacity;
-const BANK_CAPACITY_PER_LEVEL = BotConfig.economy.bankCapacityPerLevel || 5000;
-
-const DEFAULT_ECONOMY_DATA = {
- wallet: 0,
- bank: 0,
- bankLevel: 0,
- dailyStreak: 0,
- lastDaily: 0,
- lastWork: 0,
- lastCrime: 0,
- lastRob: 0,
- inventory: {},
- cooldowns: {}
-};
-
-/**
- * Helper function to calculate the user's maximum bank capacity
- * @param {Object} userData - The user's economy data
- * @returns {number} The maximum bank capacity for the user
- */
-export function getMaxBankCapacity(userData) {
- if (!userData) return BASE_BANK_CAPACITY;
-
- const bankLevel = userData.bankLevel || 0;
- return BASE_BANK_CAPACITY + (bankLevel * BANK_CAPACITY_PER_LEVEL);
-}
-
-/**
- * Get economy data for a user
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @returns {Promise} The user's economy data
- */
-export async function getEconomyData(client, guildId, userId) {
- try {
- const key = getEconomyKey(guildId, userId);
-
- const data = await getFromDb(key, DEFAULT_ECONOMY_DATA);
-
- return normalizeEconomyData(data, DEFAULT_ECONOMY_DATA);
- } catch (error) {
- logger.error(`Error getting economy data for user ${userId} in guild ${guildId}:`, error);
-
- if (error.message && (error.message.includes('ECONNREFUSED') || error.message.includes('connection'))) {
- if (client._economyFallback) {
- const key = getEconomyKey(guildId, userId);
- const cached = client._economyFallback.get(key);
- return normalizeEconomyData(cached, DEFAULT_ECONOMY_DATA);
- }
- }
-
- return normalizeEconomyData({}, DEFAULT_ECONOMY_DATA);
- }
-}
-
-/**
- * Save economy data for a user
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @param {Object} newData - The new economy data to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function setEconomyData(client, guildId, userId, newData) {
- let mergedData = null;
- try {
- const key = getEconomyKey(guildId, userId);
-
- const existingData = await getEconomyData(client, guildId, userId);
- mergedData = normalizeEconomyData(
- { ...existingData, ...newData },
- DEFAULT_ECONOMY_DATA
- );
-
- await setInDb(key, mergedData);
- return true;
- } catch (error) {
- logger.error('Error saving economy data:', error);
-
- if (error.message && (error.message.includes('ECONNREFUSED') || error.message.includes('connection'))) {
- logger.warn(`PostgreSQL unavailable, using memory fallback for guild ${guildId}`);
-
- if (!client._economyFallback) {
- client._economyFallback = new Map();
- }
- client._economyFallback.set(key, mergedData);
- return true;
- }
-
- return false;
- }
-}
-
-/**
- * Add money to a user's wallet or bank
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @param {number} amount - The amount to add
- * @param {string} [type='wallet'] - Where to add the money ('wallet' or 'bank')
- * @returns {Promise<{success: boolean, newBalance: number, maxBank?: number}>} Result of the operation
- */
-export async function addMoney(client, guildId, userId, amount, type = 'wallet') {
- try {
- if (amount <= 0) {
- return { success: false, error: 'Amount must be positive' };
- }
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (type === 'bank') {
- const maxBank = getMaxBankCapacity(userData);
- if ((userData.bank || 0) + amount > maxBank) {
- return {
- success: false,
- error: 'Bank capacity exceeded',
- current: userData.bank || 0,
- max: maxBank
- };
- }
- userData.bank = (userData.bank || 0) + amount;
- } else {
- userData.wallet = (userData.wallet || 0) + amount;
- }
-
- await setEconomyData(client, guildId, userId, userData);
-
- return {
- success: true,
- newBalance: type === 'bank' ? userData.bank : userData.wallet,
- ...(type === 'bank' ? { maxBank: getMaxBankCapacity(userData) } : {})
- };
- } catch (error) {
- logger.error(`Error adding money to ${type} for user ${userId} in guild ${guildId}:`, error);
- return { success: false, error: 'An error occurred while processing your request' };
- }
-}
-
-/**
- * Remove money from a user's wallet or bank
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @param {number} amount - The amount to remove
- * @param {string} [type='wallet'] - Where to remove the money from ('wallet' or 'bank')
- * @returns {Promise<{success: boolean, newBalance: number}>} Result of the operation
- */
-export async function removeMoney(client, guildId, userId, amount, type = 'wallet') {
- try {
- if (amount <= 0) {
- return { success: false, error: 'Amount must be positive' };
- }
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (type === 'bank') {
- if ((userData.bank || 0) < amount) {
- return {
- success: false,
- error: 'Insufficient funds in bank',
- current: userData.bank || 0,
- required: amount
- };
- }
- userData.bank = (userData.bank || 0) - amount;
- } else {
- if ((userData.wallet || 0) < amount) {
- return {
- success: false,
- error: 'Insufficient funds in wallet',
- current: userData.wallet || 0,
- required: amount
- };
- }
- userData.wallet = (userData.wallet || 0) - amount;
- }
-
- await setEconomyData(client, guildId, userId, userData);
-
- return {
- success: true,
- newBalance: type === 'bank' ? userData.bank : userData.wallet
- };
- } catch (error) {
- logger.error(`Error removing money from ${type} for user ${userId} in guild ${guildId}:`, error);
- return { success: false, error: 'An error occurred while processing your request' };
- }
-}
-
-/**
- * Transfer money between wallet and bank
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @param {number} amount - The amount to transfer
- * @param {string} direction - 'deposit' or 'withdraw'
- * @returns {Promise<{success: boolean, wallet: number, bank: number, maxBank?: number}>} Result of the operation
- */
-export async function transferMoney(client, guildId, userId, amount, direction) {
- try {
- if (amount <= 0) {
- return { success: false, error: 'Amount must be positive' };
- }
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (direction === 'deposit') {
- if (userData.wallet < amount) {
- return {
- success: false,
- error: 'Insufficient funds in wallet',
- current: userData.wallet,
- required: amount
- };
- }
-
- const maxBank = getMaxBankCapacity(userData);
- if (userData.bank + amount > maxBank) {
- return {
- success: false,
- error: 'Bank capacity exceeded',
- current: userData.bank,
- max: maxBank,
- required: amount
- };
- }
-
- userData.wallet -= amount;
- userData.bank += amount;
- userData.lastDeposit = Date.now();
-
- } else if (direction === 'withdraw') {
- if (userData.bank < amount) {
- return {
- success: false,
- error: 'Insufficient funds in bank',
- current: userData.bank,
- required: amount
- };
- }
-
- userData.bank -= amount;
- userData.wallet += amount;
- userData.lastWithdraw = Date.now();
-
- } else {
- return { success: false, error: 'Invalid transfer direction' };
- }
-
- await setEconomyData(client, guildId, userId, userData);
-
- return {
- success: true,
- wallet: userData.wallet,
- bank: userData.bank,
- ...(direction === 'deposit' ? { maxBank: getMaxBankCapacity(userData) } : {})
- };
-
- } catch (error) {
- logger.error(`Error transferring money (${direction}) for user ${userId} in guild ${guildId}:`, error);
- return { success: false, error: 'An error occurred while processing your request' };
- }
-}
-
-/**
- * Check if a user is on cooldown for a specific action
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @param {string} action - The action to check cooldown for
- * @param {number} cooldownTime - The cooldown time in milliseconds
- * @returns {Promise<{onCooldown: boolean, timeLeft?: number}>} Cooldown status
- */
-export async function checkCooldown(client, guildId, userId, action, cooldownTime) {
- try {
- const userData = await getEconomyData(client, guildId, userId);
- const now = Date.now();
- const lastAction = userData.lastAction || {};
- const lastTime = lastAction[action] || 0;
- const timeLeft = (lastTime + cooldownTime) - now;
-
- if (timeLeft > 0) {
- return { onCooldown: true, timeLeft };
- }
-
- lastAction[action] = now;
- userData.lastAction = lastAction;
- await setEconomyData(client, guildId, userId, userData);
-
- return { onCooldown: false };
-
- } catch (error) {
- logger.error(`Error checking cooldown for ${action} for user ${userId} in guild ${guildId}:`, error);
- return { onCooldown: true, timeLeft: cooldownTime };
- }
-}
-
diff --git a/src/services/giveawayService.js b/src/services/giveawayService.js
deleted file mode 100644
index 05104fc14e..0000000000
--- a/src/services/giveawayService.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import { pickWinners } from '../utils/giveaways.js';
-import { getEndedGiveaways, markGiveawayEnded } from '../utils/database.js';
-import { logger } from '../utils/logger.js';
-
-/**
- * Check for ended giveaways across all guilds and process them
- * Uses SQL queries to find only giveaways that have ended (optimized with index)
- * @param {Object} client - The Discord client
- */
-export async function checkGiveaways(client) {
- try {
- if (!client.db) {
- logger.warn('Database not available for giveaway check');
- return;
- }
-
- // Get all giveaways that have ended (uses SQL index on ends_at)
- const endedGiveaways = await getEndedGiveaways(client);
-
- if (endedGiveaways.length === 0) {
- return;
- }
-
- logger.info(`Processing ${endedGiveaways.length} ended giveaways`);
-
- for (const giveawayRecord of endedGiveaways) {
- try {
- const { id: giveawayId, guild_id: guildId, message_id: messageId, data: giveawayData } = giveawayRecord;
- const giveaway = typeof giveawayData === 'string' ? JSON.parse(giveawayData) : giveawayData;
-
- const guild = client.guilds.cache.get(guildId);
- if (!guild) {
- logger.debug(`Guild ${guildId} not found, skipping giveaway ${messageId}`);
- continue;
- }
-
- const channel = await guild.channels.fetch(giveaway.channelId).catch(() => null);
- if (!channel) {
- logger.debug(`Channel ${giveaway.channelId} not found for giveaway ${messageId}`);
- continue;
- }
-
- const message = await channel.messages.fetch(messageId).catch(() => null);
- if (!message) {
- logger.debug(`Message ${messageId} not found for giveaway in channel ${giveaway.channelId}`);
- continue;
- }
-
- const participants = giveaway.participants || [];
- const winners = pickWinners(participants, giveaway.winnerCount || 1);
-
- const winnerMentions = winners.length > 0
- ? winners.map(id => `<@${id}>`).join(', ')
- : 'No valid entries!';
-
- const embed = message.embeds[0]?.data || {};
- const endedEmbed = {
- ...embed,
- title: '🎉 Giveaway Ended!',
- fields: [
- { name: 'Winners', value: winnerMentions, inline: true },
- { name: 'Entries', value: participants.length.toString(), inline: true },
- { name: 'Prize', value: giveaway.prize || 'Mystery Prize!', inline: true }
- ],
- color: 0xff0000,
- footer: { text: 'Giveaway ended' },
- timestamp: new Date().toISOString()
- };
-
- await message.edit({
- embeds: [endedEmbed],
- components: []
- });
-
- // Make the giveaway data ended
- giveaway.ended = true;
-
- // Update in database with SQL
- const markedSuccess = await markGiveawayEnded(client, giveawayId, giveaway);
- if (!markedSuccess) {
- logger.warn(`Failed to mark giveaway ${messageId} as ended in database`);
- }
-
- if (winners.length > 0) {
- const winnerAnnouncement = `🎉 Congratulations ${winnerMentions}! You won the **${giveaway.prize || 'giveaway'}**!`;
- await channel.send({ content: winnerAnnouncement });
- }
-
- logger.info(`Ended giveaway ${messageId} in guild ${guildId}`);
- } catch (error) {
- logger.error(`Error processing giveaway:`, error);
- }
- }
- } catch (error) {
- logger.error('Error checking giveaways:', error);
- }
-}
-
diff --git a/src/services/guildConfig.js b/src/services/guildConfig.js
deleted file mode 100644
index 332facbc9f..0000000000
--- a/src/services/guildConfig.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import { getGuildConfig as getGuildConfigDb, setGuildConfig as setGuildConfigDb } from '../utils/database.js';
-import { BotConfig } from '../config/bot.js';
-import { normalizeGuildConfig } from '../utils/schemas.js';
-
-const GUILD_CONFIG_DEFAULTS = {
- prefix: BotConfig.prefix,
- modRole: null,
- adminRole: null,
- logChannelId: null,
- welcomeChannel: null,
- welcomeMessage: 'Welcome {user} to {server}!',
- autoRole: null,
- dmOnClose: true
-};
-
-/**
- * Get guild configuration with defaults
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} The guild configuration
- */
-export async function getGuildConfig(client, guildId) {
- const config = await getGuildConfigDb(client, guildId);
-
- return normalizeGuildConfig(config, GUILD_CONFIG_DEFAULTS);
-}
-
-/**
- * Set guild configuration
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Object} config - The configuration to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function setGuildConfig(client, guildId, config) {
- const normalized = normalizeGuildConfig(config, GUILD_CONFIG_DEFAULTS);
- return await setGuildConfigDb(client, guildId, normalized);
-}
-
-/**
- * Update guild configuration
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Object} updates - The updates to apply
- * @returns {Promise} Whether the operation was successful
- */
-export async function updateGuildConfig(client, guildId, updates) {
- const currentConfig = await getGuildConfigDb(client, guildId);
- const newConfig = { ...currentConfig, ...updates };
- const normalized = normalizeGuildConfig(newConfig, GUILD_CONFIG_DEFAULTS);
- return await setGuildConfigDb(client, guildId, normalized);
-}
-
-/**
- * Get a specific configuration value
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} key - The configuration key
- * @param {*} defaultValue - Default value if key doesn't exist
- * @returns {Promise<*>} The configuration value
- */
-export async function getConfigValue(client, guildId, key, defaultValue = null) {
- const config = await getGuildConfig(client, guildId);
- return config[key] !== undefined ? config[key] : defaultValue;
-}
-
-/**
- * Set a specific configuration value
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} key - The configuration key
- * @param {*} value - The value to set
- * @returns {Promise} Whether the operation was successful
- */
-export async function setConfigValue(client, guildId, key, value) {
- return await updateGuildConfig(client, guildId, { [key]: value });
-}
-
diff --git a/src/services/leveling.js b/src/services/leveling.js
deleted file mode 100644
index b1a822e76c..0000000000
--- a/src/services/leveling.js
+++ /dev/null
@@ -1,151 +0,0 @@
-import { EmbedBuilder } from 'discord.js';
-import { logger } from '../utils/logger.js';
-import { getGuildConfig } from './guildConfig.js';
-import { addXp } from './xpSystem.js';
-
-// Database is managed centrally in app.js via client.db
-// All functions in this file receive 'client' as parameter and use client.db
-const BASE_XP = 100;
-const XP_MULTIPLIER = 1.5;
-
-export function getXpForLevel(level) {
- return 5 * Math.pow(level, 2) + 50 * level + 50;
-}
-
-export function getLevelFromXp(xp) {
- let level = 0;
- let xpNeeded = 0;
-
- while (xp >= getXpForLevel(level)) {
- xpNeeded = getXpForLevel(level);
- xp -= xpNeeded;
- level++;
- }
-
- return {
- level,
- currentXp: xp,
- xpNeeded: getXpForLevel(level)
- };
-}
-
-export async function getLeaderboard(client, guildId, limit = 10) {
- try {
- const guild = client.guilds.cache.get(guildId);
- if (!guild) return [];
-
- const members = await guild.members.fetch();
- const leaderboard = [];
-
- for (const [userId, member] of members) {
- if (member.user.bot) continue;
-
- const data = await getUserLevelData(client, guildId, userId);
- if (data && data.totalXp > 0) {
- leaderboard.push({
- userId,
- username: member.user.username,
- discriminator: member.user.discriminator,
- ...data
- });
- }
- }
-
- leaderboard.sort((a, b) => b.totalXp - a.totalXp);
-
- leaderboard.forEach((entry, index) => {
- entry.rank = index + 1;
- });
-
- return leaderboard.slice(0, limit);
-
- } catch (error) {
- logger.error('Error getting leaderboard:', error);
- return [];
- }
-}
-
-export function createLeaderboardEmbed(leaderboard, guild) {
- const embed = new EmbedBuilder()
- .setTitle(`🆠${guild.name} Leaderboard`)
- .setColor('#2ecc71')
- .setTimestamp();
-
- if (leaderboard.length === 0) {
- embed.setDescription('No users on the leaderboard yet!');
- return embed;
- }
-
- const top3 = leaderboard.slice(0, 3);
- const rest = leaderboard.slice(3);
-
- const top3Text = top3.map((user, index) => {
- const medal = ['🥇', '🥈', '🥉'][index];
- return `${medal} **#${user.rank}** ${user.username} - Level ${user.level} (${user.totalXp} XP)`;
- }).join('\n');
-
- const restText = rest.map(user => {
- return `**#${user.rank}** ${user.username} - Level ${user.level} (${user.totalXp} XP)`;
- }).join('\n');
-
- embed.setDescription(
- `**Top Members**\n${top3Text}${restText ? '\n\n' + restText : ''}`
- );
-
- return embed;
-}
-
-
-export async function getLevelingConfig(client, guildId) {
- const config = await getGuildConfig(client, guildId);
- return config.leveling || {
- enabled: true,
- xpPerMessage: 15,
- xpCooldown: 60,
- levelUpMessage: '{user} has leveled up to level {level}!',
- ignoredChannels: [],
- roleRewards: {}
- };
-}
-
-export async function getUserLevelData(client, guildId, userId) {
- const key = `${guildId}:leveling:user:${userId}`;
- const data = await client.db.get(key);
-
- if (!data) {
- return {
- xp: 0,
- level: 0,
- totalXp: 0,
- lastMessage: 0,
- rank: 0
- };
- }
-
- return {
- xp: data.xp || 0,
- level: data.level || 0,
- totalXp: data.totalXp || 0,
- lastMessage: data.lastMessage || 0,
- rank: data.rank || 0
- };
-}
-
-export async function saveUserLevelData(client, guildId, userId, data) {
- const key = `${guildId}:leveling:user:${userId}`;
- await client.db.set(key, data);
-}
-
-export async function saveLevelingConfig(client, guildId, config) {
- try {
- const guildConfig = await getGuildConfig(client, guildId);
- guildConfig.leveling = config;
- const { getGuildConfigKey } = await import('../utils/database.js');
- const configKey = getGuildConfigKey(guildId);
- return await client.db.set(configKey, guildConfig);
- } catch (error) {
- logger.error('Error saving leveling config:', error);
- throw error;
- }
-}
-
diff --git a/src/services/reactionRoleService.js b/src/services/reactionRoleService.js
deleted file mode 100644
index f0510bffa9..0000000000
--- a/src/services/reactionRoleService.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * Get the reaction role message from the database
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} messageId - The message ID
- * @returns {Promise} The reaction role message or null if not found
- */
-export async function getReactionRoleMessage(client, guildId, messageId) {
- try {
- const key = `reaction_roles:${guildId}:${messageId}`;
- const data = await client.db.get(key);
- return data || null;
- } catch (error) {
- console.error(`Error getting reaction role message ${messageId} in guild ${guildId}:`, error);
- return null;
- }
-}
-
-/**
- * Add a reaction role to a message
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} messageId - The message ID
- * @param {string} emoji - The emoji ID or name
- * @param {string} roleId - The role ID
- * @returns {Promise} Whether the operation was successful
- */
-export async function addReactionRole(client, guildId, messageId, emoji, roleId) {
- try {
- const key = `reaction_roles:${guildId}:${messageId}`;
- const data = await getReactionRoleMessage(client, guildId, messageId) || {
- messageId,
- guildId,
- channelId: '',
- roles: {}
- };
-
- data.roles[emoji] = roleId;
-
- await client.db.set(key, data);
- return true;
- } catch (error) {
- console.error(`Error adding reaction role in guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Remove a reaction role from a message
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} messageId - The message ID
- * @param {string} emoji - The emoji ID or name
- * @returns {Promise} Whether the operation was successful
- */
-export async function removeReactionRole(client, guildId, messageId, emoji) {
- try {
- const key = `reaction_roles:${guildId}:${messageId}`;
- const data = await getReactionRoleMessage(client, guildId, messageId);
-
- if (!data || !data.roles[emoji]) {
- return false;
- }
-
- delete data.roles[emoji];
-
- if (Object.keys(data.roles).length === 0) {
- await client.db.delete(key);
- } else {
- await client.db.set(key, data);
- }
-
- return true;
- } catch (error) {
- console.error(`Error removing reaction role in guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Get all reaction role messages for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} Array of reaction role messages
- */
-export async function getAllReactionRoleMessages(client, guildId) {
- try {
- const prefix = `reaction_roles:${guildId}:`;
-
- let keys;
- try {
- keys = await client.db.list(prefix);
-
- if (keys && typeof keys === 'object') {
- if (Array.isArray(keys)) {
- } else if (keys.value && Array.isArray(keys.value)) {
- keys = keys.value;
- } else {
- const allKeys = await client.db.list();
-
- if (Array.isArray(allKeys)) {
- keys = allKeys.filter(key => key.startsWith(prefix));
- } else if (allKeys.value && Array.isArray(allKeys.value)) {
- keys = allKeys.value.filter(key => key.startsWith(prefix));
- } else {
- return [];
- }
- }
- } else {
- return [];
- }
- } catch (listError) {
- console.error(`[ReactionRole] Error listing keys:`, listError);
- return [];
- }
-
- if (!keys || keys.length === 0) {
- return [];
- }
-
- const messages = [];
-
- for (const key of keys) {
- try {
- const data = await client.db.get(key);
-
- if (data) {
- let actualData;
- if (data && data.ok && data.value) {
- actualData = data.value;
- } else if (data && data.value) {
- actualData = data.value;
- } else {
- actualData = data;
- }
-
- if (actualData) {
- messages.push(actualData);
- }
- }
- } catch (dataError) {
- console.error(`[ReactionRole] Error getting data for key ${key}:`, dataError);
- }
- }
-
- return messages;
- } catch (error) {
- console.error(`Error getting all reaction role messages for guild ${guildId}:`, error);
- return [];
- }
-}
-
-/**
- * Set the channel ID for a reaction role message
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} messageId - The message ID
- * @param {string} channelId - The channel ID
- * @returns {Promise} Whether the operation was successful
- */
-export async function setReactionRoleChannel(client, guildId, messageId, channelId) {
- try {
- const key = `reaction_roles:${guildId}:${messageId}`;
- const data = await getReactionRoleMessage(client, guildId, messageId) || {
- messageId,
- guildId,
- channelId: '',
- roles: {}
- };
-
- data.channelId = channelId;
- await client.db.set(key, data);
- return true;
- } catch (error) {
- console.error(`Error setting channel for reaction role message ${messageId}:`, error);
- return false;
- }
-}
-
diff --git a/src/services/shopService.js b/src/services/shopService.js
deleted file mode 100644
index c34a1a35d0..0000000000
--- a/src/services/shopService.js
+++ /dev/null
@@ -1,179 +0,0 @@
-import { EmbedBuilder } from 'discord.js';
-import { shopConfig, getItemById, validatePurchase, getCurrentPrice, getItemsInCategory } from '../config/shop/index.js';
-import { logger } from '../utils/logger.js';
-import { getUserBalance, updateUserBalance } from './economyService.js';
-
-/**
- * Shop service for handling shop-related operations
- */
-class ShopService {
- constructor() {
- this.logger = logger.child({ module: 'ShopService' });
- }
-
- /**
- * Purchase an item from the shop
- * @param {string} userId - The ID of the user making the purchase
- * @param {string} itemId - The ID of the item to purchase
- * @param {number} [quantity=1] - The quantity to purchase
- * @param {Object} [options] - Additional options
- * @returns {Promise<{success: boolean, message: string, data?: any}>} The result of the purchase
- */
- async purchaseItem(userId, itemId, quantity = 1, options = {}) {
- try {
- const { guildId } = options;
-
- const item = getItemById(itemId);
- if (!item) {
- return { success: false, message: 'Item not found in the shop.' };
- }
-
- const userBalance = await getUserBalance(userId, guildId);
-
- const totalCost = getCurrentPrice(itemId, { quantity, userData: userBalance });
-
- if (userBalance.balance < totalCost) {
- const currency = this.getCurrencyInfo();
- return {
- success: false,
- message: `You don't have enough ${currency.namePlural} to purchase this item.`
- };
- }
-
- const validation = validatePurchase(itemId, userBalance);
- if (!validation.valid) {
- return { success: false, message: validation.reason };
- }
-
- await updateUserBalance(userId, -totalCost, 'shop_purchase', {
- itemId,
- itemName: item.name,
- quantity,
- unitPrice: totalCost / quantity
- });
-
- await this.addToUserInventory(userId, itemId, quantity, guildId);
-
- this.logger.info(`User ${userId} purchased ${quantity}x ${item.name} for ${totalCost} ${this.getCurrencyName()}`);
-
- return {
- success: true,
- message: `Successfully purchased ${quantity}x ${item.name} for ${totalCost} ${this.getCurrencyName()}`,
- data: {
- item,
- quantity,
- totalCost,
- remainingBalance: userBalance.balance - totalCost
- }
- };
- } catch (error) {
- this.logger.error(`Error purchasing item: ${error.message}`, { error, userId, itemId, quantity });
- return {
- success: false,
- message: 'An error occurred while processing your purchase. Please try again later.'
- };
- }
- }
-
- /**
- * Get the user's inventory
- * @param {string} userId - The ID of the user
- * @param {string} [guildId] - The ID of the guild (for server-specific items)
- * @returns {Promise} The user's inventory
- */
- async getUserInventory(userId, guildId) {
- try {
-return [];
- } catch (error) {
- this.logger.error(`Error getting user inventory: ${error.message}`, { error, userId, guildId });
- return [];
- }
- }
-
- /**
- * Add an item to the user's inventory
- * @private
- */
- async addToUserInventory(userId, itemId, quantity = 1, guildId = null) {
- try {
- } catch (error) {
- this.logger.error(`Error adding item to inventory: ${error.message}`, { error, userId, itemId, quantity, guildId });
- throw error;
- }
- }
-
- /**
- * Get the currency name
- * @returns {string} The name of the currency
- */
- getCurrencyName() {
-return 'coins';
- }
-
- /**
- * Create an embed for the shop
- * @param {Object} options - Options for the shop embed
- * @param {string} [options.category] - The category to display
- * @param {number} [options.page=1] - The page number
- * @returns {EmbedBuilder} The shop embed
- */
- createShopEmbed(options = {}) {
- const { category, page = 1 } = options;
-
- const embed = new EmbedBuilder()
- .setTitle('🛒 TitanBot Shop')
- .setColor('#5865F2')
- .setDescription('Browse and purchase items from the shop. Use the buttons to navigate.')
- .setFooter({ text: `Page ${page}` });
-
-
- return embed;
- }
-
- /**
- * Get the shop categories
- * @returns {Array} The available shop categories
- */
- getCategories() {
- const categories = [
- {
- id: 'all',
- name: 'All Items',
- emoji: 'ðŸ›ï¸',
- description: 'Browse all available items',
- icon: 'ðŸ›ï¸'
- },
- ...shopConfig.categories
- ];
-
- return categories;
- }
-
- /**
- * Get the currency information
- * @returns {Object} Currency information
- */
- getCurrencyInfo() {
- return {
- name: shopConfig.currencyName,
- namePlural: shopConfig.currencyNamePlural,
- symbol: shopConfig.currencySymbol
- };
- }
-
- /**
- * Get items for a specific category
- * @param {string} categoryId - The category ID
- * @returns {Array} Array of items in the category
- */
- getItemsForCategory(categoryId) {
- if (categoryId === 'all') {
- return shopItems;
- }
- return getItemsInCategory(categoryId);
- }
-}
-
-const shopService = new ShopService();
-export default shopService;
-
diff --git a/src/services/ticket.js b/src/services/ticket.js
deleted file mode 100644
index 68e63904cf..0000000000
--- a/src/services/ticket.js
+++ /dev/null
@@ -1,824 +0,0 @@
-import {
- ChannelType,
- ActionRowBuilder,
- ButtonBuilder,
- ButtonStyle,
- EmbedBuilder,
- PermissionFlagsBits
-} from 'discord.js';
-import { getGuildConfig } from './guildConfig.js';
-import { getTicketData, saveTicketData, deleteTicketData, getFromDb, setInDb } from './database.js';
-import { logger } from '../utils/logger.js';
-import { createEmbed, errorEmbed } from '../utils/embeds.js';
-import { logTicketEvent } from '../utils/ticketLogging.js';
-
-const PRIORITY_MAP = {
- none: { name: '⚪ NONE', color: '#95a5a6', emoji: '⚪', label: 'None' },
- low: { name: '🔵 LOW', color: '#3498db', emoji: '🔵', label: 'Low' },
- medium: { name: '🟢 MEDIUM', color: '#2ecc71', emoji: '🟢', label: 'Medium' },
- high: { name: '🟡 HIGH', color: '#f1c40f', emoji: '🟡', label: 'High' },
- urgent: { name: '🔴 URGENT', color: '#e74c3c', emoji: '🔴', label: 'Urgent' }
-};
-
-/**
- * Count the number of open tickets for a user in a guild
- */
-export async function getUserTicketCount(guildId, userId) {
- try {
- const ticketKeys = await getFromDb(`guild:${guildId}:ticket:*`, {});
- const allKeys = Object.keys(ticketKeys);
-
- let userTicketCount = 0;
-
- for (const key of allKeys) {
- try {
- const ticketData = await getFromDb(key, null);
- if (ticketData && ticketData.userId === userId && ticketData.status === 'open') {
- userTicketCount++;
- }
- } catch (error) {
- continue;
- }
- }
-
- return userTicketCount;
- } catch (error) {
- logger.error('Error counting user tickets:', error);
- return 0;
- }
-}
-
-export async function createTicket(guild, member, categoryId, reason = 'No reason provided', priority = 'none') {
- try {
- const config = await getGuildConfig({}, guild.id);
- const ticketConfig = config.tickets || {};
-
- const maxTicketsPerUser = config.maxTicketsPerUser || 3;
- const currentTicketCount = await getUserTicketCount(guild.id, member.id);
-
- if (currentTicketCount >= maxTicketsPerUser) {
- return {
- success: false,
- error: `You have reached the maximum number of open tickets (${maxTicketsPerUser}). Please close your existing tickets before creating a new one.`
- };
- }
-
- let category = categoryId ?
- guild.channels.cache.get(categoryId) :
- guild.channels.cache.find(c =>
- c.type === ChannelType.GuildCategory &&
- c.name.toLowerCase().includes('tickets')
- );
-
- if (!category && !categoryId) {
- category = await guild.channels.create({
- name: 'Tickets',
- type: ChannelType.GuildCategory,
- permissionOverwrites: [
- {
- id: guild.id,
- deny: [PermissionFlagsBits.ViewChannel],
- },
- ],
- });
- }
-
- const ticketNumber = await getNextTicketNumber(guild.id);
-
- let channelName = `ticket-${ticketNumber}`;
-
- if (priority !== 'none') {
- const priorityInfo = PRIORITY_MAP[priority];
- if (priorityInfo) {
- channelName = `${priorityInfo.emoji} ${channelName}`;
- }
- }
-
- const channel = await guild.channels.create({
- name: channelName,
- type: ChannelType.GuildText,
- parent: category?.id,
- permissionOverwrites: [
- {
- id: guild.id,
- deny: [PermissionFlagsBits.ViewChannel],
- },
- {
- id: member.id,
- allow: [
- PermissionFlagsBits.ViewChannel,
- PermissionFlagsBits.SendMessages,
- PermissionFlagsBits.AttachFiles,
- PermissionFlagsBits.ReadMessageHistory,
- ],
- },
- ...(ticketConfig.supportRoles?.map(roleId => ({
- id: roleId,
- allow: [
- PermissionFlagsBits.ViewChannel,
- PermissionFlagsBits.SendMessages,
- PermissionFlagsBits.AttachFiles,
- PermissionFlagsBits.ReadMessageHistory,
- ],
- })) || []),
- ],
- });
-
- const ticketData = {
- id: channel.id,
- userId: member.id,
- guildId: guild.id,
- createdAt: new Date().toISOString(),
- status: 'open',
- claimedBy: null,
- priority: priority || 'none',
- reason,
- };
-
- await saveTicketData(guild.id, channel.id, ticketData);
-
- const priorityInfo = PRIORITY_MAP[priority] || PRIORITY_MAP.none;
-
- const embed = createEmbed({
- title: `Ticket #${ticketNumber}`,
- description: `${member.toString()}, thanks for creating a ticket!\n\n**Reason:** ${reason}\n**Priority:** ${priorityInfo.emoji} ${priorityInfo.label}`,
- color: priorityInfo.color,
- fields: [
- { name: 'Status', value: '🟢 Open', inline: true },
- { name: 'Claimed By', value: 'Not claimed', inline: true },
- { name: 'Created', value: ``, inline: true },
- ],
- });
-
- const row = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId('ticket_close')
- .setLabel('Close Ticket')
- .setStyle(ButtonStyle.Danger)
- .setEmoji('🔒'),
- new ButtonBuilder()
- .setCustomId('ticket_claim')
- .setLabel('Claim')
- .setStyle(ButtonStyle.Primary)
- .setEmoji('🙋'),
- new ButtonBuilder()
- .setCustomId('ticket_transcript')
- .setLabel('Transcript')
- .setStyle(ButtonStyle.Secondary)
- .setEmoji('📜')
- );
-
- if (ticketConfig.enablePriority) {
- row.addComponents(
- new ButtonBuilder()
- .setCustomId('ticket_priority:low')
- .setLabel('Low')
- .setStyle(ButtonStyle.Secondary)
- .setEmoji('🔵'),
- new ButtonBuilder()
- .setCustomId('ticket_priority:high')
- .setLabel('High')
- .setStyle(ButtonStyle.Danger)
- .setEmoji('🔴')
- );
- }
-
- const messageContent = `${member.toString()}${ticketConfig.supportRoles?.length ? ' ' + ticketConfig.supportRoles.map(r => `<@&${r}>`).join(' ') : ''}`;
-
- await channel.send({
- content: messageContent,
- embeds: [embed],
- components: [row]
- });
-
- await logTicketEvent({
- client: guild.client,
- guildId: guild.id,
- event: {
- type: 'open',
- ticketId: channel.id,
- ticketNumber: ticketNumber,
- userId: member.id,
- executorId: member.id,
- reason: reason,
- priority: priority || 'none',
- metadata: {
- channelId: channel.id,
- categoryName: category?.name || 'Default'
- }
- }
- });
-
- return { success: true, channel, ticketData };
-
- } catch (error) {
- logger.error('Error creating ticket:', error);
- return {
- success: false,
- error: error.message || 'Failed to create ticket'
- };
- }
-}
-
-export async function closeTicket(channel, closer, reason = 'No reason provided') {
- try {
- const ticketData = await getTicketData(channel.guild.id, channel.id);
- if (!ticketData) {
- return { success: false, error: 'This is not a ticket channel' };
- }
-
- const config = await getGuildConfig(channel.client, channel.guild.id);
-const dmOnClose = config.dmOnClose !== false;
-
- ticketData.status = 'closed';
- ticketData.closedBy = closer.id;
- ticketData.closedAt = new Date().toISOString();
- ticketData.closeReason = reason;
-
- await saveTicketData(channel.guild.id, channel.id, ticketData);
-
- if (dmOnClose) {
- try {
- const ticketCreator = await channel.client.users.fetch(ticketData.userId).catch(() => null);
- if (ticketCreator) {
- const dmEmbed = createEmbed({
- title: '🎫 Your Ticket Has Been Closed',
- description: `Your ticket **${channel.name}** has been closed.\n\n**Reason:** ${reason}\n**Closed by:** ${closer.tag}\n**Closed at:** \n\nThank you for using our support system! If you have any further questions, feel free to create a new ticket.`,
- color: '#e74c3c',
- footer: { text: `Ticket ID: ${ticketData.id}` }
- });
-
- await ticketCreator.send({ embeds: [dmEmbed] });
- }
- } catch (dmError) {
- console.warn(`Could not send DM to ticket creator ${ticketData.userId}:`, dmError.message);
- }
- }
-
- try {
- const user = await channel.guild.members.fetch(ticketData.userId).catch(() => null);
- const targetUser = user?.user || await channel.client.users.fetch(ticketData.userId).catch(() => null);
-
- if (targetUser) {
- const overwrite = channel.permissionOverwrites.cache.get(ticketData.userId);
- if (overwrite) {
- await overwrite.edit({
- ViewChannel: false,
- SendMessages: false,
- });
- } else {
- await channel.permissionOverwrites.create(targetUser, {
- ViewChannel: false,
- SendMessages: false,
- });
- }
- }
- } catch (permError) {
- console.warn('Could not update user permissions for closed ticket:', permError.message);
- }
-
- const messages = await channel.messages.fetch();
- const ticketMessage = messages.find(m =>
- m.embeds.length > 0 &&
- m.embeds[0].title?.startsWith('Ticket #')
- );
-
- if (ticketMessage) {
- const embed = ticketMessage.embeds[0];
- const statusField = embed.fields?.find(f => f.name === 'Status');
-
- if (statusField) {
- statusField.value = '🔴 Closed';
- }
-
- const updatedEmbed = createEmbed({
- title: embed.title || 'Ticket',
- description: embed.description || 'Ticket discussion',
- color: '#e74c3c',
- fields: embed.fields || [],
- footer: embed.footer
- });
-
- await ticketMessage.edit({
- embeds: [updatedEmbed],
-components: []
- });
- }
-
- try {
- const user = await channel.guild.members.fetch(ticketData.userId).catch(() => null);
- if (user) {
- await channel.permissionOverwrites.delete(user, 'Ticket closed');
- }
- } catch (error) {
- logger.warn(`Could not remove user ${ticketData.userId} from ticket channel:`, error.message);
- }
-
- const closeEmbed = createEmbed({
- title: 'Ticket Closed',
- description: `This ticket has been closed by ${closer}.\n**Reason:** ${reason}${dmOnClose ? '\n\n📩 A DM has been sent to the ticket creator.' : ''}`,
- color: '#e74c3c',
- footer: { text: `Ticket ID: ${ticketData.id}` }
- });
-
- const controlRow = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId('ticket_reopen')
- .setLabel('Reopen Ticket')
- .setStyle(ButtonStyle.Success)
- .setEmoji('🔓'),
- new ButtonBuilder()
- .setCustomId('ticket_delete')
- .setLabel('Delete Ticket')
- .setStyle(ButtonStyle.Danger)
- .setEmoji('🗑ï¸')
- );
-
- await channel.send({ embeds: [closeEmbed], components: [controlRow] });
-
- await logTicketEvent({
- client: channel.client,
- guildId: channel.guild.id,
- event: {
- type: 'close',
- ticketId: channel.id,
- ticketNumber: ticketData.id,
- userId: ticketData.userId,
- executorId: closer.id,
- reason: reason,
- metadata: {
- dmSent: dmOnClose,
- closedAt: ticketData.closedAt
- }
- }
- });
-
- return { success: true, ticketData };
-
- } catch (error) {
- logger.error('Error closing ticket:', error);
- return {
- success: false,
- error: error.message || 'Failed to close ticket'
- };
- }
-}
-
-export async function claimTicket(channel, claimer) {
- try {
- const ticketData = await getTicketData(channel.guild.id, channel.id);
- if (!ticketData) {
- return { success: false, error: 'This is not a ticket channel' };
- }
-
- if (ticketData.claimedBy) {
- return {
- success: false,
- error: `This ticket is already claimed by <@${ticketData.claimedBy}>`
- };
- }
-
- ticketData.claimedBy = claimer.id;
- ticketData.claimedAt = new Date().toISOString();
-
- await saveTicketData(channel.guild.id, channel.id, ticketData);
-
- const messages = await channel.messages.fetch();
- const ticketMessage = messages.find(m =>
- m.embeds.length > 0 &&
- m.embeds[0].title?.startsWith('Ticket #')
- );
-
- if (ticketMessage) {
- const embed = ticketMessage.embeds[0];
- const claimedField = embed.fields?.find(f => f.name === 'Claimed By');
-
- if (claimedField) {
- claimedField.value = claimer.toString();
- }
-
- const row = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId('ticket_close')
- .setLabel('Close Ticket')
- .setStyle(ButtonStyle.Danger)
- .setEmoji('🔒'),
- new ButtonBuilder()
- .setCustomId('ticket_claim')
- .setLabel('Claimed')
- .setStyle(ButtonStyle.Secondary)
- .setEmoji('🙋')
- .setDisabled(true),
- new ButtonBuilder()
- .setCustomId('ticket_transcript')
- .setLabel('Transcript')
- .setStyle(ButtonStyle.Secondary)
- .setEmoji('📜')
- );
-
- await ticketMessage.edit({
- embeds: [embed],
- components: [row]
- });
- }
-
- const claimEmbed = createEmbed({
- title: 'Ticket Claimed',
- description: `🎉 ${claimer} has claimed this ticket!`,
- color: '#2ecc71'
- });
-
- const unclaimRow = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId('ticket_unclaim')
- .setLabel('Unclaim')
- .setStyle(ButtonStyle.Secondary)
- .setEmoji('🔓')
- );
-
- await channel.send({ embeds: [claimEmbed], components: [unclaimRow] });
-
- await logTicketEvent({
- client: channel.client,
- guildId: channel.guild.id,
- event: {
- type: 'claim',
- ticketId: channel.id,
- ticketNumber: ticketData.id,
- userId: ticketData.userId,
- executorId: claimer.id,
- metadata: {
- claimedAt: ticketData.claimedAt
- }
- }
- });
-
- return { success: true, ticketData };
-
- } catch (error) {
- logger.error('Error claiming ticket:', error);
- return {
- success: false,
- error: error.message || 'Failed to claim ticket'
- };
- }
-}
-
-export async function reopenTicket(channel, reopener) {
- try {
- const ticketData = await getTicketData(channel.guild.id, channel.id);
- if (!ticketData) {
- return { success: false, error: 'This is not a ticket channel' };
- }
-
- if (ticketData.status !== 'closed') {
- return {
- success: false,
- error: 'This ticket is not currently closed'
- };
- }
-
- ticketData.status = 'open';
- ticketData.closedBy = null;
- ticketData.closedAt = null;
- ticketData.closeReason = null;
-
- await saveTicketData(channel.guild.id, channel.id, ticketData);
-
- try {
- const user = await channel.guild.members.fetch(ticketData.userId).catch(() => null);
- if (user) {
- await channel.permissionOverwrites.create(user, {
- ViewChannel: true,
- SendMessages: true,
- ReadMessageHistory: true,
- AttachFiles: true
- });
- }
- } catch (error) {
- logger.warn(`Could not restore access for user ${ticketData.userId}:`, error.message);
- }
-
- const messages = await channel.messages.fetch();
- const ticketMessage = messages.find(m =>
- m.embeds.length > 0 &&
- m.embeds[0].title?.startsWith('Ticket #')
- );
-
- if (ticketMessage) {
- const embed = ticketMessage.embeds[0];
- const statusField = embed.fields?.find(f => f.name === 'Status');
-
- if (statusField) {
- statusField.value = '🟢 Open';
- }
-
- const row = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId('ticket_close')
- .setLabel('Close Ticket')
- .setStyle(ButtonStyle.Danger)
- .setEmoji('🔒'),
- new ButtonBuilder()
- .setCustomId('ticket_claim')
- .setLabel(ticketData.claimedBy ? 'Claimed' : 'Claim')
- .setStyle(ticketData.claimedBy ? ButtonStyle.Secondary : ButtonStyle.Primary)
- .setEmoji(ticketData.claimedBy ? '🙋' : '🔑')
- .setDisabled(!!ticketData.claimedBy),
- new ButtonBuilder()
- .setCustomId('ticket_transcript')
- .setLabel('Transcript')
- .setStyle(ButtonStyle.Secondary)
- .setEmoji('📜')
- );
-
- await ticketMessage.edit({
- embeds: [embed],
- components: [row]
- });
- }
-
- const reopenEmbed = createEmbed({
- title: 'Ticket Reopened',
- description: `🔓 ${reopener} has reopened this ticket!`,
- color: '#2ecc71'
- });
-
- await channel.send({ embeds: [reopenEmbed] });
-
- return { success: true, ticketData };
-
- } catch (error) {
- logger.error('Error reopening ticket:', error);
- return {
- success: false,
- error: error.message || 'Failed to reopen ticket'
- };
- }
-}
-
-export async function deleteTicket(channel, deleter) {
- try {
- const ticketData = await getTicketData(channel.guild.id, channel.id);
- if (!ticketData) {
- return { success: false, error: 'This is not a ticket channel' };
- }
-
- const deleteEmbed = createEmbed({
- title: 'Ticket Deleted',
- description: `ðŸ—‘ï¸ This ticket will be permanently deleted in 3 seconds.`,
- color: '#e74c3c',
- footer: { text: `Ticket ID: ${ticketData.id}` }
- });
-
- await channel.send({ embeds: [deleteEmbed] });
-
- await logTicketEvent({
- client: channel.client,
- guildId: channel.guild.id,
- event: {
- type: 'delete',
- ticketId: channel.id,
- ticketNumber: ticketData.id,
- userId: ticketData.userId,
- executorId: deleter.id,
- metadata: {
- deletedAt: new Date().toISOString()
- }
- }
- });
-
- setTimeout(async () => {
- try {
- await channel.delete('Ticket deleted permanently');
- logger.info(`Deleted ticket channel ${channel.name} (${channel.id})`);
- } catch (deleteError) {
- logger.error(`Failed to delete ticket channel ${channel.id}:`, deleteError);
- }
- }, 3000);
-
- return { success: true, ticketData };
-
- } catch (error) {
- logger.error('Error deleting ticket:', error);
- return {
- success: false,
- error: error.message || 'Failed to delete ticket'
- };
- }
-}
-
-export async function unclaimTicket(channel, unclaimer) {
- try {
- const ticketData = await getTicketData(channel.guild.id, channel.id);
- if (!ticketData) {
- return { success: false, error: 'This is not a ticket channel' };
- }
-
- if (!ticketData.claimedBy) {
- return {
- success: false,
- error: 'This ticket is not currently claimed'
- };
- }
-
- if (ticketData.claimedBy !== unclaimer.id && !unclaimer.permissions.has(PermissionFlagsBits.ManageChannels)) {
- return {
- success: false,
- error: 'You can only unclaim your own tickets or need Manage Channels permission.'
- };
- }
-
- const previousClaimer = ticketData.claimedBy;
- ticketData.claimedBy = null;
- ticketData.claimedAt = null;
-
- await saveTicketData(channel.guild.id, channel.id, ticketData);
-
- const messages = await channel.messages.fetch();
- const ticketMessage = messages.find(m =>
- m.embeds.length > 0 &&
- m.embeds[0].title?.startsWith('Ticket #')
- );
-
- if (ticketMessage) {
- const embed = ticketMessage.embeds[0];
- const claimedField = embed.fields?.find(f => f.name === 'Claimed By');
-
- if (claimedField) {
- claimedField.value = 'Not claimed';
- }
-
- const row = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId('ticket_close')
- .setLabel('Close Ticket')
- .setStyle(ButtonStyle.Danger)
- .setEmoji('🔒'),
- new ButtonBuilder()
- .setCustomId('ticket_claim')
- .setLabel('Claim')
- .setStyle(ButtonStyle.Primary)
- .setEmoji('🙋'),
- new ButtonBuilder()
- .setCustomId('ticket_transcript')
- .setLabel('Transcript')
- .setStyle(ButtonStyle.Secondary)
- .setEmoji('📜')
- );
-
- await ticketMessage.edit({
- embeds: [embed],
- components: [row]
- });
- }
-
- const recentMessages = await channel.messages.fetch({ limit: 50 });
- const claimMessage = recentMessages.find(m =>
- m.embeds.length > 0 &&
- m.embeds[0].title === 'Ticket Claimed' &&
- m.components.length > 0 &&
- m.components[0].components.some(c => c.customId === 'ticket_unclaim')
- );
-
- if (claimMessage) {
- const unclaimEmbed = createEmbed({
- title: 'Ticket Unclaimed',
- description: `🔓 ${unclaimer} has unclaimed this ticket!`,
- color: '#f39c12'
- });
-
- await claimMessage.edit({
- embeds: [unclaimEmbed],
-components: []
- });
- } else {
- const unclaimEmbed = createEmbed({
- title: 'Ticket Unclaimed',
- description: `🔓 ${unclaimer} has unclaimed this ticket!`,
- color: '#f39c12'
- });
-
- await channel.send({ embeds: [unclaimEmbed] });
- }
-
- await logTicketEvent({
- client: channel.client,
- guildId: channel.guild.id,
- event: {
- type: 'unclaim',
- ticketId: channel.id,
- ticketNumber: ticketData.id,
- userId: ticketData.userId,
- executorId: unclaimer.id,
- metadata: {
- previousClaimer: previousClaimer
- }
- }
- });
-
- return { success: true, ticketData };
-
- } catch (error) {
- logger.error('Error unclaiming ticket:', error);
- return {
- success: false,
- error: error.message || 'Failed to unclaim ticket'
- };
- }
-}
-
-async function getNextTicketNumber(guildId) {
- const randomTicket = Math.floor(Math.random() * 900) + 100;
- return randomTicket.toString();
-}
-
-export async function updateTicketPriority(channel, priority, updater) {
- try {
- const ticketData = await getTicketData(channel.guild.id, channel.id);
- if (!ticketData) {
- return { success: false, error: 'This is not a ticket channel' };
- }
-
- const priorityInfo = PRIORITY_MAP[priority];
- if (!priorityInfo) {
- return { success: false, error: 'Invalid priority level' };
- }
-
- ticketData.priority = priority;
- ticketData.priorityUpdatedBy = updater.id;
- ticketData.priorityUpdatedAt = new Date().toISOString();
-
- await saveTicketData(channel.guild.id, channel.id, ticketData);
-
- if (priority !== 'none') {
- const priorityEmoji = priorityInfo.emoji;
- const currentName = channel.name;
-
- const cleanName = currentName.replace(/[🔵🟢🟡🔴⚪]/g, '').trim();
-
- const newName = `${priorityEmoji} ${cleanName}`;
-
- try {
- await channel.setName(newName);
- } catch (nameError) {
- logger.warn(`Could not update channel name for priority: ${nameError.message}`);
- }
- }
-
- const messages = await channel.messages.fetch();
- const ticketMessage = messages.find(m =>
- m.embeds.length > 0 &&
- m.embeds[0].title?.startsWith('Ticket #')
- );
-
- if (ticketMessage) {
- const embed = ticketMessage.embeds[0];
-
- const updatedEmbed = createEmbed({
- title: embed.title || 'Ticket',
- description: embed.description?.split('\n**Priority:**')[0] + `\n**Priority:** ${priorityInfo.emoji} ${priorityInfo.label}`,
- color: priorityInfo.color,
- fields: embed.fields || [],
- footer: embed.footer
- });
-
- await ticketMessage.edit({ embeds: [updatedEmbed] });
- }
-
- const updateEmbed = createEmbed({
- title: 'Priority Updated',
- description: `📊 Ticket priority updated to **${priorityInfo.emoji} ${priorityInfo.label}** by ${updater}`,
- color: priorityInfo.color
- });
-
- await channel.send({ embeds: [updateEmbed] });
-
- await logTicketEvent({
- client: channel.client,
- guildId: channel.guild.id,
- event: {
- type: 'priority',
- ticketId: channel.id,
- ticketNumber: ticketData.id,
- userId: ticketData.userId,
- executorId: updater.id,
- priority: priority,
- metadata: {
- previousPriority: ticketData.priority,
- updatedAt: ticketData.priorityUpdatedAt
- }
- }
- });
-
- return { success: true, ticketData };
-
- } catch (error) {
- logger.error('Error updating ticket priority:', error);
- return {
- success: false,
- error: error.message || 'Failed to update ticket priority'
- };
- }
-}
-
diff --git a/src/services/xpSystem.js b/src/services/xpSystem.js
deleted file mode 100644
index 990d54bbbe..0000000000
--- a/src/services/xpSystem.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import { logger } from '../utils/logger.js';
-import { getLevelingConfig } from '../utils/database.js';
-import { getUserLevelData, saveUserLevelData } from './leveling.js';
-import { getXpForLevel } from '../utils/database.js';
-
-export async function addXp(client, guild, member, xpToAdd) {
- try {
- const config = await getLevelingConfig(client, guild.id);
-
- if (!config.enabled) {
- return { success: false, reason: 'Leveling is disabled in this server' };
- }
-
- const levelData = await getUserLevelData(client, guild.id, member.id);
-
- levelData.xp += xpToAdd;
- levelData.totalXp += xpToAdd;
- levelData.lastMessage = Date.now();
-
- const xpNeededForNextLevel = getXpForLevel(levelData.level + 1);
- let didLevelUp = false;
-
- if (levelData.xp >= xpNeededForNextLevel) {
- levelData.level += 1;
- levelData.xp = levelData.xp - xpNeededForNextLevel;
- didLevelUp = true;
-
- if (config.roleRewards && config.roleRewards[levelData.level]) {
- const roleId = config.roleRewards[levelData.level];
- const role = guild.roles.cache.get(roleId);
-
- if (role) {
- try {
- await member.roles.add(role);
- logger.info(`Added role ${role.name} to ${member.user.tag} for reaching level ${levelData.level}`);
- } catch (error) {
- logger.error(`Failed to add role ${roleId} to ${member.id}:`, error);
- }
- }
- }
-
- const levelUpChannel = config.levelUpChannel ?
- guild.channels.cache.get(config.levelUpChannel) :
- guild.systemChannel;
-
- if (levelUpChannel && levelUpChannel.isTextBased() && config.announceLevelUp) {
- const message = config.levelUpMessage
- .replace(/{user}/g, member.toString())
- .replace(/{level}/g, levelData.level)
- .replace(/{xp}/g, levelData.xp)
- .replace(/{xpNeeded}/g, getXpForLevel(levelData.level + 1));
-
- try {
- await levelUpChannel.send(message);
- } catch (error) {
- logger.error('Failed to send level up message:', error);
- }
- }
- }
-
- await saveUserLevelData(client, guild.id, member.id, levelData);
-
- return {
- success: true,
- level: levelData.level,
- xp: levelData.xp,
- totalXp: levelData.totalXp,
- xpNeeded: getXpForLevel(levelData.level + 1),
- leveledUp: didLevelUp
- };
-
- } catch (error) {
- logger.error('Error adding XP:', error);
- return { success: false, error: error.message };
- }
-}
-
diff --git a/src/utils/components.js b/src/utils/components.js
deleted file mode 100644
index fc85170e22..0000000000
--- a/src/utils/components.js
+++ /dev/null
@@ -1,123 +0,0 @@
-import { ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder } from 'discord.js';
-import { getColor } from '../config/bot.js';
-
-export function getPromoRow() {
- return new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setLabel('Invite Me')
- .setURL('https://discord.com/oauth2/authorize?client_id=YOUR_BOT_ID&scope=bot&permissions=8')
- .setStyle(ButtonStyle.Link),
- new ButtonBuilder()
- .setLabel('Support Server')
- .setURL('https://discord.gg/YOUR_SERVER_INVITE')
- .setStyle(ButtonStyle.Link),
- new ButtonBuilder()
- .setLabel('GitHub')
- .setURL('https://github.com/yourusername/your-bot')
- .setStyle(ButtonStyle.Link)
- );
-}
-
-export function getConfirmationButtons(customIdPrefix = 'confirm') {
- return new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`${customIdPrefix}_yes`)
- .setLabel('Confirm')
- .setStyle(ButtonStyle.Success)
- .setEmoji('✅'),
- new ButtonBuilder()
- .setCustomId(`${customIdPrefix}_no`)
- .setLabel('Cancel')
- .setStyle(ButtonStyle.Danger)
- .setEmoji('âŒ')
- );
-}
-
-export function getPaginationRow(customIdPrefix = 'page', currentPage = 1, totalPages = 1) {
- return new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId(`${customIdPrefix}_first`)
- .setLabel('⏮️')
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(currentPage === 1),
- new ButtonBuilder()
- .setCustomId(`${customIdPrefix}_prev`)
- .setLabel('◀️')
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(currentPage === 1),
- new ButtonBuilder()
- .setCustomId(`${customIdPrefix}_page`)
- .setLabel(`Page ${currentPage} of ${totalPages}`)
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(true),
- new ButtonBuilder()
- .setCustomId(`${customIdPrefix}_next`)
- .setLabel('▶️')
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(currentPage >= totalPages),
- new ButtonBuilder()
- .setCustomId(`${customIdPrefix}_last`)
- .setLabel('⏭️')
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(currentPage >= totalPages)
- );
-}
-
-export function createSelectMenu(customId, placeholder, options = [], min = 1, max = 1) {
- return new ActionRowBuilder().addComponents(
- new StringSelectMenuBuilder()
- .setCustomId(customId)
- .setPlaceholder(placeholder)
- .setMinValues(min)
- .setMaxValues(max)
- .addOptions(options)
- );
-}
-
-export function createButton(customId, label, style = 'primary', emoji = null, disabled = false) {
- const button = new ButtonBuilder()
- .setCustomId(customId)
- .setLabel(label)
- .setStyle(ButtonStyle[style.charAt(0).toUpperCase() + style.slice(1).toLowerCase()] || ButtonStyle.Primary)
- .setDisabled(disabled);
-
- if (emoji) {
- button.setEmoji(emoji);
- }
-
- return button;
-}
-
-export function createLinkButton(label, url, emoji = null) {
- const button = new ButtonBuilder()
- .setLabel(label)
- .setURL(url)
- .setStyle(ButtonStyle.Link);
-
- if (emoji) {
- button.setEmoji(emoji);
- }
-
- return button;
-}
-
-export function createButtonRow(buttons) {
- const row = new ActionRowBuilder();
-
- for (const button of buttons) {
- if (button.url) {
- row.addComponents(createLinkButton(button.label, button.url, button.emoji));
- } else {
- row.addComponents(createButton(
- button.customId,
- button.label,
- button.style || 'primary',
- button.emoji,
- button.disabled || false
- ));
- }
- }
-
- return row;
-}
-
diff --git a/src/utils/database.js b/src/utils/database.js
deleted file mode 100644
index f9534f0fbb..0000000000
--- a/src/utils/database.js
+++ /dev/null
@@ -1,1927 +0,0 @@
-import { pgDb } from './postgresDatabase.js';
-import { MemoryStorage } from './memoryStorage.js';
-import { logger } from './logger.js';
-import { BotConfig } from '../config/bot.js';
-import { normalizeGuildConfig } from './schemas.js';
-
-class DatabaseWrapper {
- constructor() {
- this.initialized = false;
- this.db = null;
- this.useFallback = false;
- this.connectionType = 'none';
- this.degradedModeWarningShown = false;
- }
-
- async initialize() {
- if (this.initialized) {
- return;
- }
-
- try {
- logger.info('Attempting to connect to PostgreSQL...');
- const pgConnected = await pgDb.connect();
- if (pgConnected) {
- this.db = pgDb;
- this.connectionType = 'postgresql';
- logger.info('✅ PostgreSQL Database initialized - using persistent database');
- this.initialized = true;
- return;
- }
- } catch (error) {
- logger.warn('PostgreSQL connection failed:', error.message);
- }
-
- // Fallback to memory storage
- this.db = new MemoryStorage();
- this.useFallback = true;
- this.connectionType = 'memory';
- logger.warn('âš ï¸ DATABASE DEGRADED MODE ENABLED - Using in-memory storage (data will be lost on restart)');
- logger.warn('âš ï¸ Please check PostgreSQL connection and restart the bot when fixed');
- this.initialized = true;
- this.degradedModeWarningShown = true;
- }
-
- async set(key, value, ttl = null) {
- if (this.useFallback) {
- logger.debug(`[DEGRADED] Writing to memory: ${key}`);
- }
- return this.db.set(key, value, ttl);
- }
-
- async get(key, defaultValue = null) {
- return this.db.get(key, defaultValue);
- }
-
- async delete(key) {
- if (this.useFallback) {
- logger.debug(`[DEGRADED] Deleting from memory: ${key}`);
- }
- return this.db.delete(key);
- }
-
- async list(prefix) {
- return this.db.list(prefix);
- }
-
- async exists(key) {
- if (this.db.exists) {
- return this.db.exists(key);
- }
- const value = await this.db.get(key);
- return value !== null;
- }
-
- async increment(key, amount = 1) {
- if (this.useFallback) {
- logger.debug(`[DEGRADED] Incrementing in memory: ${key}`);
- }
- if (this.db.increment) {
- return this.db.increment(key, amount);
- }
- const current = await this.db.get(key, 0);
- const newValue = current + amount;
- await this.db.set(key, newValue);
- return newValue;
- }
-
- async decrement(key, amount = 1) {
- if (this.useFallback) {
- logger.debug(`[DEGRADED] Decrementing in memory: ${key}`);
- }
- if (this.db.decrement) {
- return this.db.decrement(key, amount);
- }
- const current = await this.db.get(key, 0);
- const newValue = current - amount;
- await this.db.set(key, newValue);
- return newValue;
- }
-
- /**
- * Check if database is in degraded mode (memory-only fallback)
- * @returns {boolean} True if using in-memory storage fallback
- */
- isDegraded() {
- return this.useFallback;
- }
-
- /**
- * Check if database is fully available (PostgreSQL)
- * @returns {boolean} True if connected to PostgreSQL
- */
- isAvailable() {
- return this.db && !this.useFallback;
- }
-
- /**
- * Get current connection status
- * @returns {Object} Status information
- */
- getStatus() {
- return {
- initialized: this.initialized,
- connectionType: this.connectionType,
- isDegraded: this.useFallback,
- isAvailable: this.isAvailable()
- };
- }
-
- getConnectionType() {
- return this.connectionType;
- }
-}
-
-export const db = new DatabaseWrapper();
-
-export async function initializeDatabase() {
- try {
- logger.info("Initializing Database (PostgreSQL > Memory fallback)...");
- await db.initialize();
- logger.info("✅ Database initialized");
- return { db };
- } catch (error) {
- logger.error("⌠Database Initialization Error:", error);
- return { db };
- }
-}
-
-export async function getFromDb(key, defaultValue = null) {
- try {
- const value = await db.get(key);
- return value === null ? defaultValue : value;
- } catch (error) {
- logger.error(`Error getting value for key ${key}:`, error);
- return defaultValue;
- }
-}
-
-export async function setInDb(key, value, ttl = null) {
- try {
- await db.set(key, value, ttl);
- return true;
- } catch (error) {
- logger.error(`Error setting value for key ${key}:`, error);
- return false;
- }
-}
-
-export async function deleteFromDb(key) {
- try {
- await db.delete(key);
- return true;
- } catch (error) {
- logger.error(`Error deleting key ${key}:`, error);
- return false;
- }
-}
-
-/**
- * Extract actual data from database response (for backward compatibility)
- * @param {any} data - Data to unwrap
- * @returns {any} Unwrapped data
- */
-export function unwrapReplitData(data) {
- if (
- typeof data === "object" &&
- data !== null &&
- data.ok !== undefined &&
- data.value !== undefined
- ) {
- return unwrapReplitData(data.value);
- }
- return data;
-}
-
-export const getGuildConfigKey = (guildId) => `guild:${guildId}:config`;
-export const getGuildBirthdaysKey = (guildId) => `guild:${guildId}:birthdays`;
-
-/**
- * Get or initialize guild configuration
- * @param {Object} client - Discord client with database
- * @param {string} guildId - Guild ID
- * @returns {Promise} Guild configuration
- */
-export async function getGuildConfig(client, guildId) {
- try {
- if (!client.db || typeof client.db.get !== "function") {
- return {};
- }
-
- const configKey = getGuildConfigKey(guildId);
- const rawConfig = await client.db.get(configKey, {});
- const cleanedConfig = unwrapReplitData(rawConfig);
-
- const defaults = {
- logIgnore: { users: [], channels: [] },
- enabledCommands: {},
- reportChannelId: null,
- birthdayChannelId: null,
- premiumRoleId: null
- };
-
- return normalizeGuildConfig(cleanedConfig, defaults);
- } catch (error) {
- console.error(`Error fetching config for guild ${guildId}:`, error);
- return {};
- }
-}
-
-/**
- * Save guild configuration
- * @param {Object} client - Discord client with database
- * @param {string} guildId - Guild ID
- * @param {Object} config - Configuration to save
- * @returns {Promise} Success status
- */
-export async function setGuildConfig(client, guildId, config) {
- try {
- if (!client.db || typeof client.db.set !== "function") {
- console.error("Database client is not available for setGuildConfig.");
- return false;
- }
-
- const key = getGuildConfigKey(guildId);
- await client.db.set(key, config);
- return true;
- } catch (error) {
- console.error(`Error saving config for guild ${guildId}:`, error);
- return false;
- }
-}
-
-export { DatabaseWrapper, pgDb };
-
-export const getMessage = (key, replacements = {}) => {
- let message = BotConfig.messages[key] || key;
- for (const [k, v] of Object.entries(replacements)) {
- message = message.replace(new RegExp(`\\{${k}\\}`, "g"), v);
- }
- return message;
-};
-
-export const getColor = (path, fallback = "#000000") => {
- const parts = path.split(".");
- let current = BotConfig.embeds.colors;
-
- for (const part of parts) {
- if (current[part] === undefined) {
- console.warn(`Color path '${path}' not found in config, using fallback`);
- return fallback;
- }
- current = current[part];
- }
-
- return typeof current === "string" ? current : fallback;
-};
-
-/**
- * Get all birthdays for a guild
- * @param {Object} client - Discord client with database
- * @param {string} guildId - Guild ID
- * @returns {Promise} Object mapping user IDs to birthday data
- */
-export async function getGuildBirthdays(client, guildId) {
- const key = getGuildBirthdaysKey(guildId);
- try {
- if (!client.db || typeof client.db.get !== "function") {
- console.error("Database client is not available for getGuildBirthdays.");
- return {};
- }
-
- const rawData = await client.db.get(key, {});
- return unwrapReplitData(rawData) || {};
- } catch (error) {
- console.error(`Error retrieving birthdays for guild ${guildId}:`, error);
- return {};
- }
-}
-
-/**
- * Set a user's birthday
- * @param {Object} client - Discord client with database
- * @param {string} guildId - Guild ID
- * @param {string} userId - User ID
- * @param {number} month - Month (1-12)
- * @param {number} day - Day (1-31)
- * @returns {Promise} Success status
- */
-export async function setBirthday(client, guildId, userId, month, day) {
- try {
- if (!client.db || typeof client.db.set !== "function") {
- console.error("Database client is not available for setBirthday.");
- return false;
- }
-
- const key = getGuildBirthdaysKey(guildId);
- const birthdays = await getGuildBirthdays(client, guildId);
- birthdays[userId] = { month, day };
- await client.db.set(key, birthdays);
- return true;
- } catch (error) {
- console.error(`Error setting birthday for user ${userId} in guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Delete a user's birthday
- * @param {Object} client - Discord client with database
- * @param {string} guildId - Guild ID
- * @param {string} userId - User ID
- * @returns {Promise} Success status
- */
-export async function deleteBirthday(client, guildId, userId) {
- try {
- if (!client.db || typeof client.db.set !== "function") {
- console.error("Database client is not available for deleteBirthday.");
- return false;
- }
-
- const key = getGuildBirthdaysKey(guildId);
- const birthdays = await getGuildBirthdays(client, guildId);
- if (birthdays[userId]) {
- delete birthdays[userId];
- await client.db.set(key, birthdays);
- }
- return true;
- } catch (error) {
- console.error(`Error deleting birthday for user ${userId} in guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Get the month name from a month number (1-12)
- * @param {number} monthNum - Month number (1-12)
- * @returns {string} Month name
- */
-export function getMonthName(monthNum) {
- const months = [
- 'January', 'February', 'March', 'April', 'May', 'June',
- 'July', 'August', 'September', 'October', 'November', 'December'
- ];
- const index = Math.max(0, Math.min(monthNum - 1, 11));
- return monthNum >= 1 && monthNum <= 12 ? months[index] : 'Invalid Month';
-}
-
-
-/**
- * Get all giveaways for a guild
- * @param {Object} client - Discord client with database
- * @param {string} guildId - Guild ID
- * @returns {Promise} Object mapping message IDs to giveaway data
- */
-export async function getGuildGiveaways(client, guildId) {
- const key = giveawayKey(guildId);
- try {
- if (!client.db || typeof client.db.get !== "function") {
- console.error("Database client is not available for getGuildGiveaways.");
- return {};
- }
-
- const giveaways = await client.db.get(key, {});
- return unwrapReplitData(giveaways) || {};
- } catch (error) {
- console.error(`Error getting giveaways for guild ${guildId}:`, error);
- return {};
- }
-}
-
-/**
- * Save a giveaway
- * @param {Object} client - Discord client with database
- * @param {string} guildId - Guild ID
- * @param {Object} giveawayData - The giveaway data to save
- * @returns {Promise} Success status
- */
-export async function saveGiveaway(client, guildId, giveawayData) {
- try {
- if (!client.db || typeof client.db.set !== "function") {
- console.error("Database client is not available for saveGiveaway.");
- return false;
- }
-
- const key = giveawayKey(guildId);
- const giveaways = await getGuildGiveaways(client, guildId);
-
- giveaways[giveawayData.messageId] = giveawayData;
-
- await client.db.set(key, giveaways);
- return true;
- } catch (error) {
- console.error('Error saving giveaway:', error);
- return false;
- }
-}
-
-/**
- * Delete a giveaway
- * @param {Object} client - Discord client with database
- * @param {string} guildId - Guild ID
- * @param {string} messageId - The message ID of the giveaway to delete
- * @returns {Promise} Success status
- */
-export async function deleteGiveaway(client, guildId, messageId) {
- try {
- const key = giveawayKey(guildId);
- const giveaways = await getGuildGiveaways(client, guildId);
-
- if (giveaways[messageId]) {
- delete giveaways[messageId];
- await client.db.set(key, giveaways);
- return true;
- }
- return false;
- } catch (error) {
- console.error('Error deleting giveaway:', error);
- return false;
- }
-}
-
-/**
- * Get all giveaways that have ended (SQL-optimized for PostgreSQL)
- * Uses the giveaways table index on ends_at for efficient querying
- * @param {Object} client - Discord client with database
- * @returns {Promise} Array of ended giveaway records
- */
-export async function getEndedGiveaways(client) {
- try {
- if (!client.db || !client.db.isAvailable()) {
- logger.warn('Database not available for getEndedGiveaways, using fallback');
- return [];
- }
-
- const { pgDb } = await import('./postgresDatabase.js');
- const { pgConfig } = await import('../config/postgres.js');
-
- if (!pgDb.isAvailable()) {
- return [];
- }
-
- const result = await pgDb.pool.query(
- `SELECT id, guild_id, message_id, data, ends_at
- FROM ${pgConfig.tables.giveaways}
- WHERE ends_at <= NOW()
- AND (data->>'ended')::boolean = false
- ORDER BY ends_at ASC`
- );
-
- return result.rows || [];
- } catch (error) {
- logger.error('Error getting ended giveaways:', error);
- return [];
- }
-}
-
-/**
- * Mark a giveaway as ended in the database
- * @param {Object} client - Discord client with database
- * @param {number} giveawayId - The giveaway ID from the database
- * @param {Object} endedData - The updated giveaway data to save
- * @returns {Promise} Success status
- */
-export async function markGiveawayEnded(client, giveawayId, endedData) {
- try {
- if (!client.db || !client.db.isAvailable()) {
- logger.warn('Database not available for markGiveawayEnded');
- return false;
- }
-
- const { pgDb } = await import('./postgresDatabase.js');
- const { pgConfig } = await import('../config/postgres.js');
-
- if (!pgDb.isAvailable()) {
- return false;
- }
-
- await pgDb.pool.query(
- `UPDATE ${pgConfig.tables.giveaways}
- SET data = $1, updated_at = NOW()
- WHERE id = $2`,
- [endedData, giveawayId]
- );
-
- return true;
- } catch (error) {
- logger.error('Error marking giveaway as ended:', error);
- return false;
- }
-}
-
-/**
- * Generate a consistent key for giveaways in the database
- * @param {string} guildId - The guild ID
- * @returns {string} The formatted key
- */
-export function giveawayKey(guildId) {
- return `guild:${guildId}:giveaways`;
-}
-
-/**
- * Get the economy data key for a user in a guild
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @returns {string} The economy key
- */
-export function getEconomyKey(guildId, userId) {
- return `guild:${guildId}:economy:${userId}`;
-}
-
-/**
- * Get the AFK status key for a user in a guild
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @returns {string} The AFK key
- */
-export function getAFKKey(guildId, userId) {
- return `guild:${guildId}:afk:${userId}`;
-}
-
-/**
- * Get the welcome system configuration key for a guild
- * @param {string} guildId - The guild ID
- * @returns {string} The welcome config key
- */
-export function getWelcomeConfigKey(guildId) {
- return `guild:${guildId}:welcome`;
-}
-
-/**
- * Get welcome system configuration for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @returns {Promise} The welcome system configuration
- */
-export async function getWelcomeConfig(client, guildId) {
- if (!client.db) {
- console.warn('Database not available for getWelcomeConfig');
- return {
- enabled: false,
- channelId: null,
- welcomeMessage: "Welcome {user.mention} to {server.name}!",
- welcomeEmbed: {
- title: "Welcome to {server.name}!",
- description: "We're glad to have you here, {user.mention}!",
- color: getColor('primary'),
- thumbnail: true,
- footer: "Enjoy your stay!"
- },
- goodbyeEnabled: false,
- goodbyeChannelId: null,
- goodbyeMessage: "Goodbye {user.mention}! We'll miss you.",
- goodbyeEmbed: {
- title: "Goodbye!",
- description: "{user.mention} has left the server.",
- color: getColor('error'),
- footer: "Come back soon!"
- },
- autoRole: null,
- delay: 1000
- };
- }
-
- const key = getWelcomeConfigKey(guildId);
- try {
- const config = await client.db.get(key, {});
- const unwrapped = unwrapReplitData(config);
-
- return {
- enabled: unwrapped.enabled || false,
- channelId: unwrapped.channelId || null,
- welcomeMessage: unwrapped.welcomeMessage || "Welcome {user.mention} to {server.name}!",
- welcomeEmbed: unwrapped.welcomeEmbed || {
- title: "Welcome to {server.name}!",
- description: "We're glad to have you here, {user.mention}!",
- color: getColor('primary'),
- thumbnail: true,
- footer: `You are member #{memberCount}`
- },
- goodbyeEnabled: unwrapped.goodbyeEnabled || false,
- goodbyeChannelId: unwrapped.goodbyeChannelId || null,
- leaveMessage: unwrapped.leaveMessage || "{user.tag} has left the server.",
- leaveEmbed: unwrapped.leaveEmbed || {
- title: "Goodbye {user.tag}",
- description: "We're sorry to see you go!",
- color: getColor('error'),
- thumbnail: true,
- footer: "We now have {memberCount} members"
- },
- dmMessage: unwrapped.dmMessage || "",
- roleIds: unwrapped.roleIds || [],
- autoRoleDelay: unwrapped.autoRoleDelay || 0,
- joinLogs: unwrapped.joinLogs || {
- enabled: false,
- channelId: null
- },
- leaveLogs: unwrapped.leaveLogs || {
- enabled: false,
- channelId: null
- },
- ...unwrapped
- };
- } catch (error) {
- console.error(`Error getting welcome config for guild ${guildId}:`, error);
- return {
- enabled: false,
- channelId: null,
- welcomeMessage: "Welcome {user.mention} to {server.name}!",
- welcomeEmbed: {
- title: "Welcome to {server.name}!",
- description: "We're glad to have you here, {user.mention}!",
- color: getColor('primary'),
- thumbnail: true,
- footer: `You are member #{memberCount}`
- },
- goodbyeEnabled: false,
- goodbyeChannelId: null,
- leaveMessage: "{user.tag} has left the server.",
- leaveEmbed: {
- title: "Goodbye {user.tag}",
- description: "We're sorry to see you go!",
- color: getColor('error'),
- thumbnail: true,
- footer: "We now have {memberCount} members"
- },
- dmMessage: "",
- roleIds: [],
- autoRoleDelay: 0,
- joinLogs: { enabled: false, channelId: null },
- leaveLogs: { enabled: false, channelId: null }
- };
- }
-}
-
-/**
- * Save welcome system configuration for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @param {Object} config - The configuration to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function saveWelcomeConfig(client, guildId, config) {
- const key = getWelcomeConfigKey(guildId);
- try {
- const existingConfig = await getWelcomeConfig(client, guildId);
- const mergedConfig = { ...existingConfig, ...config };
-
- await client.db.set(key, mergedConfig);
- return true;
- } catch (error) {
- console.error(`Error saving welcome config for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Update specific fields in the welcome config
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @param {Object} updates - The fields to update
- * @returns {Promise} The updated config
- */
-export async function updateWelcomeConfig(client, guildId, updates) {
- try {
- const currentConfig = await getWelcomeConfig(client, guildId);
- const updatedConfig = { ...currentConfig, ...updates };
-
- await saveWelcomeConfig(client, guildId, updatedConfig);
- return updatedConfig;
- } catch (error) {
- console.error(`Error updating welcome config for guild ${guildId}:`, error);
- throw error;
- }
-}
-
-
-/**
- * Gets the leveling data key for a guild
- * @param {string} guildId - The ID of the guild
- * @returns {string} The leveling data key
- */
-export function getLevelingKey(guildId) {
- return `guild:${guildId}:leveling:config`;
-}
-
-/**
- * Gets the user level data key
- * @param {string} guildId - The ID of the guild
- * @param {string} userId - The ID of the user
- * @returns {string} The user level data key
- */
-export function getUserLevelKey(guildId, userId) {
- return `guild:${guildId}:leveling:users:${userId}`;
-}
-
-/**
- * Gets the leveling configuration for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @returns {Promise} The leveling configuration
- */
-export async function getLevelingConfig(client, guildId) {
- const key = getLevelingKey(guildId);
- try {
- const config = await getFromDb(key, {
- enabled: false,
- xpPerMessage: 10,
- xpPerMinute: 60,
- cooldownEnabled: true,
- messageLengthMultiplier: true,
- levelUpMessages: true,
- levelUpChannel: null,
- roles: {},
- milestones: {}
- });
-
- return config;
- } catch (error) {
- logger.error('Error getting leveling config:', error);
- return {
- enabled: false,
- xpPerMessage: 10,
- xpPerMinute: 60,
- cooldownEnabled: true,
- messageLengthMultiplier: true,
- levelUpMessages: true,
- levelUpChannel: null,
- roles: {},
- milestones: {}
- };
- }
-}
-
-/**
- * Saves the leveling configuration for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @param {Object} config - The configuration to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function saveLevelingConfig(client, guildId, config) {
- const key = getLevelingKey(guildId);
- try {
- await setInDb(key, config);
-
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`💾 Saved leveling config to database (guild: ${guildId})`);
- }
- return true;
- } catch (error) {
- logger.error(`Error saving leveling config for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Gets a user's level data
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @param {string} userId - The ID of the user
- * @returns {Promise} The user's level data
- */
-export async function getUserLevelData(client, guildId, userId) {
- const key = getUserLevelKey(guildId, userId);
- try {
- const data = await getFromDb(key, null);
- if (!data) {
- return {
- xp: 0,
- level: 0,
- totalXp: 0,
- lastMessage: 0,
- rank: 0,
- xpToNextLevel: getXpForLevel(1)
- };
- }
-
- const levelData = {
- xp: data.xp || 0,
- level: data.level || 0,
- totalXp: data.totalXp || 0,
- lastMessage: data.lastMessage || 0,
- rank: data.rank || 0,
- xpToNextLevel: getXpForLevel((data.level || 0) + 1)
- };
-
- return levelData;
- } catch (error) {
- console.error(`Error getting level data for user ${userId} in guild ${guildId}:`, error);
- return {
- xp: 0,
- level: 0,
- totalXp: 0,
- lastMessage: 0,
- rank: 0,
- xpToNextLevel: getXpForLevel(1)
- };
- }
-}
-
-/**
- * Saves a user's level data
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @param {string} userId - The ID of the user
- * @param {Object} data - The level data to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function saveUserLevelData(client, guildId, userId, data) {
- const key = getUserLevelKey(guildId, userId);
- try {
- const levelData = {
- ...data,
- xp: data.xp || 0,
- level: data.level || 0,
- totalXp: data.totalXp || 0,
- lastMessage: data.lastMessage || 0,
- rank: data.rank || 0,
- updatedAt: Date.now()
- };
-
- await setInDb(key, levelData);
- return true;
- } catch (error) {
- console.error(`Error saving level data for user ${userId} in guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Calculates the XP needed for a specific level
- * @param {number} level - The level to calculate XP for
- * @returns {number} The XP needed for the level
- */
-export function getXpForLevel(level) {
- return 5 * Math.pow(level, 2) + 50 * level + 50;
-}
-
-/**
- * Gets the leaderboard for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @param {number} limit - The maximum number of entries to return (default: 10)
- * @returns {Promise} Sorted array of user level data
- */
-export async function getLeaderboard(client, guildId, limit = 10) {
- try {
- if (!client.db || typeof client.db.list !== "function") {
- console.error("Database client is not available for getLeaderboard.");
- return [];
- }
-
- const prefix = `guild:${guildId}:leveling:users:`;
- let keys = await client.db.list(prefix);
-
- if (!Array.isArray(keys)) {
- if (typeof keys === 'object' && keys !== null) {
- keys = Object.keys(keys).filter(key => key.startsWith(prefix));
- } else {
- return [];
- }
- }
-
- if (keys.length === 0) {
- return [];
- }
-
- const userDataPromises = keys.map(async (key) => {
- try {
- const userId = key.replace(prefix, '');
- const data = await client.db.get(key);
- if (!data) return null;
-
- const unwrapped = unwrapReplitData(data);
- return {
- userId,
- xp: unwrapped.xp || 0,
- level: unwrapped.level || 0,
- totalXp: unwrapped.totalXp || 0,
-rank: 0
- };
- } catch (error) {
- console.error(`Error processing leaderboard key ${key}:`, error);
- return null;
- }
- });
-
- let userData = (await Promise.all(userDataPromises)).filter(Boolean);
-
- userData.sort((a, b) => (b.totalXp || 0) - (a.totalXp || 0));
-
- userData = userData.map((user, index) => ({
- ...user,
- rank: index + 1
- }));
-
- return userData.slice(0, limit);
- } catch (error) {
- console.error(`Error getting leaderboard for guild ${guildId}:`, error);
- return [];
- }
-}
-
-
-/**
- * Get the application roles key for a guild
- * @param {string} guildId - The guild ID
- * @returns {string} The application roles key
- */
-export function getApplicationRolesKey(guildId) {
- return `guild:${guildId}:applications:roles`;
-}
-
-/**
- * Get application roles for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} Array of application roles
- */
-export async function getApplicationRoles(client, guildId) {
- try {
- if (!client.db || typeof client.db.get !== "function") {
- console.error("Database client is not available for getApplicationRoles.");
- return [];
- }
-
- const key = getApplicationRolesKey(guildId);
- const roles = await client.db.get(key, []);
- const unwrappedRoles = unwrapReplitData(roles);
- return Array.isArray(unwrappedRoles) ? unwrappedRoles : [];
- } catch (error) {
- console.error(`Error getting application roles for guild ${guildId}:`, error);
- return [];
- }
-}
-
-/**
- * Save application roles for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Array} roles - Array of application roles
- * @returns {Promise} Whether the operation was successful
- */
-export async function saveApplicationRoles(client, guildId, roles) {
- try {
- if (!client.db || typeof client.db.set !== "function") {
- console.error("Database client is not available for saveApplicationRoles.");
- return false;
- }
-
- const key = getApplicationRolesKey(guildId);
- await client.db.set(key, roles);
- return true;
- } catch (error) {
- console.error(`Error saving application roles for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Get the application settings key for a guild
- * @param {string} guildId - The guild ID
- * @returns {string} The application settings key
- */
-export function getApplicationSettingsKey(guildId) {
- return `guild:${guildId}:applications:settings`;
-}
-
-/**
- * Get the user applications key
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @returns {string} The user applications key
- */
-export function getUserApplicationsKey(guildId, userId) {
- return `guild:${guildId}:applications:users:${userId}`;
-}
-
-/**
- * Get the application key
- * @param {string} guildId - The guild ID
- * @param {string} applicationId - The application ID
- * @returns {string} The application key
- */
-export function getApplicationKey(guildId, applicationId) {
- return `guild:${guildId}:applications:${applicationId}`;
-}
-
-/**
- * Get application settings for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} The application settings
- */
-export async function getApplicationSettings(client, guildId) {
- if (!client.db) {
- console.warn('Database not available for getApplicationSettings');
- return {
- enabled: false,
- applicationChannelId: null,
- logChannelId: null,
- questions: [
- "Why do you want to join our staff team?",
- "What experience do you have that would make you a good fit?",
- "How much time can you dedicate to this role?"
- ]
- };
- }
-
- const key = getApplicationSettingsKey(guildId);
- try {
- const settings = await client.db.get(key, {});
- const unwrapped = unwrapReplitData(settings);
-
- const defaultSettings = {
- enabled: false,
- applicationChannelId: null,
- logChannelId: null,
- questions: [
- "Why do you want to join our staff team?",
- "What experience do you have that would make you a good fit?",
- "How much time can you dedicate to this role?"
- ],
- roles: {
- admin: null,
- reviewer: null,
- accepted: null,
- denied: null
- },
- requiredRoles: [],
- deniedRoles: [],
-minAccountAge: 0,
- maxApplications: 1,
-cooldown: 7,
- allowMultipleApplications: false,
- requireVerification: false,
- customWelcomeMessage: ""
- };
-
- return { ...defaultSettings, ...unwrapped };
- } catch (error) {
- console.error(`Error getting application settings for guild ${guildId}:`, error);
- return {
- enabled: false,
- applicationChannelId: null,
- logChannelId: null,
- questions: [
- "Why do you want to join our staff team?",
- "What experience do you have that would make you a good fit?",
- "How much time can you dedicate to this role?"
- ],
- roles: {
- admin: null,
- reviewer: null,
- accepted: null,
- denied: null
- },
- requiredRoles: [],
- deniedRoles: [],
- minAccountAge: 0,
- maxApplications: 1,
- cooldown: 7,
- allowMultipleApplications: false,
- requireVerification: false,
- customWelcomeMessage: ""
- };
- }
-}
-
-/**
- * Save application settings for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Object} settings - The settings to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function saveApplicationSettings(client, guildId, settings) {
- const key = getApplicationSettingsKey(guildId);
- try {
- const existingSettings = await getApplicationSettings(client, guildId);
- const mergedSettings = { ...existingSettings, ...settings };
-
- await client.db.set(key, mergedSettings);
- return true;
- } catch (error) {
- console.error(`Error saving application settings for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Create a new application
- * @param {Object} client - The Discord client
- * @param {Object} application - The application data
- * @returns {Promise} The created application
- */
-export async function createApplication(client, application) {
- const { guildId, userId } = application;
- const applicationId = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
- const key = getApplicationKey(guildId, applicationId);
-
- const newApplication = {
- ...application,
- id: applicationId,
-status: 'pending',
- createdAt: Date.now(),
- updatedAt: Date.now(),
- reviewedBy: null,
- reviewedAt: null,
- notes: []
- };
-
- try {
- if (!client.db || typeof client.db.set !== "function") {
- console.error("Database client is not available for createApplication.");
- throw new Error("Database not available");
- }
-
- await client.db.set(key, newApplication);
-
- const userKey = getUserApplicationsKey(guildId, userId);
- const userApplications = await client.db.get(userKey, []);
- const unwrappedApplications = unwrapReplitData(userApplications);
-
- const applicationsArray = Array.isArray(unwrappedApplications) ? unwrappedApplications : [];
- applicationsArray.push(applicationId);
-
- await client.db.set(userKey, applicationsArray);
- if (process.env.NODE_ENV !== 'production') {
- logger.debug(`Successfully created application ${applicationId} for user ${userId}`);
- }
-
- return newApplication;
- } catch (error) {
- console.error(`Error creating application for user ${userId} in guild ${guildId}:`, error);
- throw error;
- }
-}
-
-/**
- * Get an application by ID
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} applicationId - The application ID
- * @returns {Promise} The application or null if not found
- */
-export async function getApplication(client, guildId, applicationId) {
- const key = getApplicationKey(guildId, applicationId);
- try {
- const application = await client.db.get(key, null);
- return unwrapReplitData(application);
- } catch (error) {
- console.error(`Error getting application ${applicationId} in guild ${guildId}:`, error);
- return null;
- }
-}
-
-/**
- * Update an application
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} applicationId - The application ID
- * @param {Object} updates - The updates to apply
- * @returns {Promise} The updated application
- */
-export async function updateApplication(client, guildId, applicationId, updates) {
- const key = getApplicationKey(guildId, applicationId);
- try {
- const existingApplication = await getApplication(client, guildId, applicationId);
- if (!existingApplication) {
- throw new Error(`Application ${applicationId} not found`);
- }
-
- const updatedApplication = {
- ...existingApplication,
- ...updates,
- updatedAt: Date.now()
- };
-
- await client.db.set(key, updatedApplication);
- return updatedApplication;
- } catch (error) {
- console.error(`Error updating application ${applicationId} in guild ${guildId}:`, error);
- throw error;
- }
-}
-
-/**
- * Get all applications for a user in a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @returns {Promise} The user's applications
- */
-export async function getUserApplications(client, guildId, userId) {
- const userKey = getUserApplicationsKey(guildId, userId);
- try {
- if (!client.db || typeof client.db.get !== "function") {
- console.error("Database client is not available for getUserApplications.");
- return [];
- }
-
- const applicationIds = await client.db.get(userKey, []);
- const unwrappedIds = unwrapReplitData(applicationIds);
-
- const idsArray = Array.isArray(unwrappedIds) ? unwrappedIds : [];
-
- const applicationPromises = idsArray.map(id =>
- getApplication(client, guildId, id)
- );
-
- const applications = await Promise.all(applicationPromises);
- return applications.filter(Boolean);
- } catch (error) {
- console.error(`Error getting applications for user ${userId} in guild ${guildId}:`, error);
- return [];
- }
-}
-
-/**
- * Get all applications in a guild with optional filters
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Object} [filters] - Optional filters
- * @param {string} [filters.status] - Filter by status
- * @param {string} [filters.userId] - Filter by user ID
- * @param {number} [filters.limit=50] - Maximum number of applications to return
- * @param {number} [filters.offset=0] - Number of applications to skip
- * @returns {Promise} The filtered applications
- */
-export async function getApplications(client, guildId, filters = {}) {
- const {
- status,
- userId,
- limit = 50,
- offset = 0
- } = filters;
-
- try {
- if (!client.db || typeof client.db.list !== "function") {
- console.error("Database client is not available for getApplications.");
- return [];
- }
-
- const prefix = `guild:${guildId}:applications:`;
- let keys = await client.db.list(prefix);
-
- if (!Array.isArray(keys)) {
- if (typeof keys === 'object' && keys !== null) {
- const keyArray = Object.keys(keys).filter(key => key.startsWith(prefix));
- keys = keyArray;
- } else {
- return [];
- }
- }
-
- const applicationKeys = keys.filter(key => !key.endsWith('settings') && key.includes('applications:'));
-
- const applicationPromises = applicationKeys.map(key => client.db.get(key));
- let applications = (await Promise.all(applicationPromises))
- .map(unwrapReplitData)
- .filter(Boolean);
-
- if (status) {
- applications = applications.filter(app => app.status === status);
- }
-
- if (userId) {
- applications = applications.filter(app => app.userId === userId);
- }
-
- applications.sort((a, b) => b.createdAt - a.createdAt);
-
- return applications.slice(offset, offset + limit);
- } catch (error) {
- console.error(`Error getting applications for guild ${guildId}:`, error);
- return [];
- }
-}
-
-
-/**
- * Get the modlog settings key for a guild
- * @param {string} guildId - The guild ID
- * @returns {string} The modlog settings key
- */
-export function getModlogSettingsKey(guildId) {
- return `guild:${guildId}:modlog:settings`;
-}
-
-/**
- * Get the modlog entry key
- * @param {string} guildId - The guild ID
- * @param {string} caseId - The case ID
- * @returns {string} The modlog entry key
- */
-export function getModlogEntryKey(guildId, caseId) {
- return `guild:${guildId}:modlog:cases:${caseId}`;
-}
-
-/**
- * Get the modlog user cases key
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @returns {string} The user's modlog cases key
- */
-export function getUserModlogKey(guildId, userId) {
- return `guild:${guildId}:modlog:users:${userId}`;
-}
-
-/**
- * Get modlog settings for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} The modlog settings
- */
-export async function getModlogSettings(client, guildId) {
- const key = getModlogSettingsKey(guildId);
- try {
- const settings = await client.db.get(key, {});
- const unwrapped = unwrapReplitData(settings);
-
- const defaultSettings = {
- enabled: false,
- channelId: null,
- ignoredChannels: [],
- ignoredUsers: [],
- ignoredActions: [],
- logBans: true,
- logKicks: true,
- logMutes: true,
- logWarns: true,
- logMessageDeletes: false,
- logMessageEdits: false,
- logChannelUpdates: true,
- logMemberUpdates: true,
- logRoleUpdates: true,
- logVoiceUpdates: true,
- logEmojiUpdates: true,
- logStickerUpdates: true,
- logInviteCreates: true,
- logInviteDeletes: true,
- logWebhookUpdates: true,
- logIntegrationUpdates: true,
- logBotActions: true,
- logPunishments: true,
- logJoins: true,
- logLeaves: true,
- logNicknameChanges: true,
- logRoleChanges: true,
- logTimeoutAdds: true,
- logTimeoutRemovals: true,
- logThreadCreates: true,
- logThreadDeletes: true,
- logThreadUpdates: true,
- logScheduledEvents: true,
- logAutomod: true,
- logStages: true,
- logGuildUpdates: true,
- logEmojiRoleUpdates: true,
- logStickerRoleUpdates: true,
- logStickerUpdates: true,
- logIntegrationRoleUpdates: true,
- logWebhookRoleUpdates: true,
- logAutoModRuleUpdates: true,
- logAutoModExecutions: true,
- logScheduledEventUpdates: true,
- logScheduledEventUsers: true,
- logScheduledEventCreates: true,
- logScheduledEventDeletes: true,
- logScheduledEventUserAdds: true,
- logScheduledEventUserRemoves: true,
- logThreadMembers: true,
- logThreadMembersUpdates: true,
- logGuildScheduledEvents: true,
- logGuildScheduledEventUsers: true,
- logGuildScheduledEventCreates: true,
- logGuildScheduledEventDeletes: true,
- logGuildScheduledEventUserAdds: true,
- logGuildScheduledEventUserRemoves: true,
- logGuildScheduledEventUpdates: true,
- logGuildScheduledEventUserUpdates: true,
- logGuildScheduledEventUserAdd: true,
- logGuildScheduledEventUserRemove: true,
- logGuildScheduledEventUserUpdate: true,
- logGuildScheduledEventCreate: true,
- logGuildScheduledEventDelete: true,
- logGuildScheduledEventUpdate: true,
- logGuildScheduledEventUserAdds: true,
- logGuildScheduledEventUserRemoves: true,
- logGuildScheduledEventUserUpdates: true,
- logGuildScheduledEventUsersAdd: true,
- logGuildScheduledEventUsersRemove: true,
- logGuildScheduledEventUsersUpdate: true,
- logGuildScheduledEventUserAdd: true,
- logGuildScheduledEventUserRemove: true,
- logGuildScheduledEventUserUpdate: true,
- logGuildScheduledEventUsersAdd: true,
- logGuildScheduledEventUsersRemove: true,
- logGuildScheduledEventUsersUpdate: true,
- logGuildScheduledEventUserAdd: true,
- logGuildScheduledEventUserRemove: true,
- logGuildScheduledEventUserUpdate: true,
- logGuildScheduledEventUsersAdd: true,
- logGuildScheduledEventUsersRemove: true,
- logGuildScheduledEventUsersUpdate: true,
- logGuildScheduledEventUserAdd: true,
- logGuildScheduledEventUserRemove: true,
- logGuildScheduledEventUserUpdate: true,
- logGuildScheduledEventUsersAdd: true,
- logGuildScheduledEventUsersRemove: true,
- logGuildScheduledEventUsersUpdate: true
- };
-
- return { ...defaultSettings, ...unwrapped };
- } catch (error) {
- console.error(`Error getting modlog settings for guild ${guildId}:`, error);
- return {
- enabled: false,
- channelId: null,
- ignoredChannels: [],
- ignoredUsers: [],
- ignoredActions: [],
- logBans: true,
- logKicks: true,
- logMutes: true,
- logWarns: true,
- logMessageDeletes: false,
- logMessageEdits: false,
- logChannelUpdates: true,
- logMemberUpdates: true,
- logRoleUpdates: true,
- logVoiceUpdates: true,
- logEmojiUpdates: true,
- logStickerUpdates: true,
- logInviteCreates: true,
- logInviteDeletes: true,
- logWebhookUpdates: true,
- logIntegrationUpdates: true,
- logBotActions: true,
- logPunishments: true,
- logJoins: true,
- logLeaves: true,
- logNicknameChanges: true,
- logRoleChanges: true,
- logTimeoutAdds: true,
- logTimeoutRemovals: true
- };
- }
-}
-
-/**
- * Save modlog settings for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Object} settings - The settings to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function saveModlogSettings(client, guildId, settings) {
- const key = getModlogSettingsKey(guildId);
- try {
- const existingSettings = await getModlogSettings(client, guildId);
- const mergedSettings = { ...existingSettings, ...settings };
-
- await client.db.set(key, mergedSettings);
- return true;
- } catch (error) {
- console.error(`Error saving modlog settings for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Create a new modlog entry
- * @param {Object} client - The Discord client
- * @param {Object} entry - The modlog entry data
- * @returns {Promise} The created modlog entry
- */
-export async function createModlogEntry(client, entry) {
- const { guildId, userId } = entry;
- const caseId = generateCaseId();
- const key = getModlogEntryKey(guildId, caseId);
-
- const newEntry = {
- ...entry,
- caseId,
- createdAt: Date.now(),
- updatedAt: Date.now()
- };
-
- try {
- await client.db.set(key, newEntry);
-
- const userKey = getUserModlogKey(guildId, userId);
- const userEntries = await client.db.get(userKey, []);
-
- if (!userEntries.includes(caseId)) {
- userEntries.push(caseId);
- await client.db.set(userKey, userEntries);
- }
-
- return newEntry;
- } catch (error) {
- console.error(`Error creating modlog entry for user ${userId} in guild ${guildId}:`, error);
- throw error;
- }
-}
-
-/**
- * Get a modlog entry by case ID
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} caseId - The case ID
- * @returns {Promise} The modlog entry or null if not found
- */
-export async function getModlogEntry(client, guildId, caseId) {
- const key = getModlogEntryKey(guildId, caseId);
- try {
- const entry = await client.db.get(key, null);
- return unwrapReplitData(entry);
- } catch (error) {
- console.error(`Error getting modlog entry ${caseId} in guild ${guildId}:`, error);
- return null;
- }
-}
-
-/**
- * Update a modlog entry
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} caseId - The case ID
- * @param {Object} updates - The updates to apply
- * @returns {Promise} The updated modlog entry
- */
-export async function updateModlogEntry(client, guildId, caseId, updates) {
- const key = getModlogEntryKey(guildId, caseId);
- try {
- const existingEntry = await getModlogEntry(client, guildId, caseId);
- if (!existingEntry) {
- throw new Error(`Modlog entry ${caseId} not found`);
- }
-
- const updatedEntry = {
- ...existingEntry,
- ...updates,
- updatedAt: Date.now()
- };
-
- await client.db.set(key, updatedEntry);
- return updatedEntry;
- } catch (error) {
- console.error(`Error updating modlog entry ${caseId} in guild ${guildId}:`, error);
- throw error;
- }
-}
-
-/**
- * Get all modlog entries for a user in a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @returns {Promise} The user's modlog entries
- */
-export async function getUserModlogEntries(client, guildId, userId) {
- const userKey = getUserModlogKey(guildId, userId);
- try {
- const caseIds = await client.db.get(userKey, []);
- const entryPromises = caseIds.map(caseId =>
- getModlogEntry(client, guildId, caseId)
- );
-
- const entries = await Promise.all(entryPromises);
- return entries.filter(Boolean);
- } catch (error) {
- console.error(`Error getting modlog entries for user ${userId} in guild ${guildId}:`, error);
- return [];
- }
-}
-
-/**
- * Get all modlog entries in a guild with optional filters
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Object} [filters] - Optional filters
- * @param {string} [filters.userId] - Filter by user ID
- * @param {string} [filters.moderatorId] - Filter by moderator ID
- * @param {string} [filters.action] - Filter by action type
- * @param {number} [filters.limit=50] - Maximum number of entries to return
- * @param {number} [filters.offset=0] - Number of entries to skip
- * @returns {Promise} The filtered modlog entries
- */
-export async function getModlogEntries(client, guildId, filters = {}) {
- const {
- userId,
- moderatorId,
- action,
- limit = 50,
- offset = 0
- } = filters;
-
- try {
- const prefix = `guild:${guildId}:modlog:cases:`;
- const keys = await client.db.list(prefix);
-
- const entryPromises = keys.map(key => client.db.get(key));
- let entries = (await Promise.all(entryPromises))
- .map(unwrapReplitData)
- .filter(Boolean);
-
- if (userId) {
- entries = entries.filter(entry => entry.userId === userId);
- }
-
- if (moderatorId) {
- entries = entries.filter(entry => entry.moderatorId === moderatorId);
- }
-
- if (action) {
- entries = entries.filter(entry => entry.action === action);
- }
-
- entries.sort((a, b) => b.createdAt - a.createdAt);
-
- return entries.slice(offset, offset + limit);
- } catch (error) {
- console.error(`Error getting modlog entries for guild ${guildId}:`, error);
- return [];
- }
-}
-
-
-/**
- * Get the Join to Create configuration key for a guild
- * @param {string} guildId - The guild ID
- * @returns {string} The Join to Create config key
- */
-export function getJoinToCreateConfigKey(guildId) {
- return `guild:${guildId}:jointocreate`;
-}
-
-/**
- * Get the Join to Create temporary channels key for a guild
- * @param {string} guildId - The guild ID
- * @returns {string} The temporary channels key
- */
-export function getJoinToCreateChannelsKey(guildId) {
- return `guild:${guildId}:jointocreate:channels`;
-}
-
-/**
- * Get Join to Create configuration for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} The Join to Create configuration
- */
-export async function getJoinToCreateConfig(client, guildId) {
- if (!client.db) {
- console.warn('Database not available for getJoinToCreateConfig');
- return {
- enabled: false,
- triggerChannels: [],
- categoryId: null,
- channelNameTemplate: "{username}'s Room",
- userLimit: 0,
- bitrate: 64000,
- temporaryChannels: {}
- };
- }
-
- const key = getJoinToCreateConfigKey(guildId);
- try {
- const config = await client.db.get(key, {});
- const unwrapped = unwrapReplitData(config);
-
- return {
- enabled: unwrapped.enabled || false,
- triggerChannels: unwrapped.triggerChannels || [],
- categoryId: unwrapped.categoryId || null,
- channelNameTemplate: unwrapped.channelNameTemplate || "{username}'s Room",
- userLimit: unwrapped.userLimit || 0,
- bitrate: unwrapped.bitrate || 64000,
- temporaryChannels: unwrapped.temporaryChannels || {},
- ...unwrapped
- };
- } catch (error) {
- console.error(`Error getting Join to Create config for guild ${guildId}:`, error);
- return {
- enabled: false,
- triggerChannels: [],
- categoryId: null,
- channelNameTemplate: "{username}'s Room",
- userLimit: 0,
- bitrate: 64000,
- temporaryChannels: {}
- };
- }
-}
-
-/**
- * Save Join to Create configuration for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Object} config - The configuration to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function saveJoinToCreateConfig(client, guildId, config) {
- const key = getJoinToCreateConfigKey(guildId);
- try {
- const existingConfig = await getJoinToCreateConfig(client, guildId);
- const mergedConfig = { ...existingConfig, ...config };
-
- await client.db.set(key, mergedConfig);
- return true;
- } catch (error) {
- console.error(`Error saving Join to Create config for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Update specific fields in the Join to Create config
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Object} updates - The fields to update
- * @returns {Promise} The updated config
- */
-export async function updateJoinToCreateConfig(client, guildId, updates) {
- try {
- const currentConfig = await getJoinToCreateConfig(client, guildId);
- const updatedConfig = { ...currentConfig, ...updates };
-
- await saveJoinToCreateConfig(client, guildId, updatedConfig);
- return updatedConfig;
- } catch (error) {
- console.error(`Error updating Join to Create config for guild ${guildId}:`, error);
- throw error;
- }
-}
-
-/**
- * Add a trigger channel to the Join to Create system
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} channelId - The trigger channel ID
- * @param {Object} options - Channel-specific options
- * @returns {Promise} Whether the operation was successful
- */
-export async function addJoinToCreateTrigger(client, guildId, channelId, options = {}) {
- try {
- const config = await getJoinToCreateConfig(client, guildId);
-
- if (config.triggerChannels.includes(channelId)) {
- return false;
- }
-
- config.triggerChannels.push(channelId);
- config.enabled = config.triggerChannels.length > 0;
-
- if (Object.keys(options).length > 0) {
- if (!config.channelOptions) {
- config.channelOptions = {};
- }
- config.channelOptions[channelId] = {
- nameTemplate: options.nameTemplate || config.channelNameTemplate,
- userLimit: options.userLimit || config.userLimit,
- bitrate: options.bitrate || config.bitrate
- };
- }
-
- return await saveJoinToCreateConfig(client, guildId, config);
- } catch (error) {
- console.error(`Error adding Join to Create trigger for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Remove a trigger channel from the Join to Create system
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} channelId - The trigger channel ID
- * @returns {Promise} Whether the operation was successful
- */
-export async function removeJoinToCreateTrigger(client, guildId, channelId) {
- try {
- const config = await getJoinToCreateConfig(client, guildId);
-
- const index = config.triggerChannels.indexOf(channelId);
- if (index === -1) {
- return false;
- }
-
- config.triggerChannels.splice(index, 1);
- config.enabled = config.triggerChannels.length > 0;
-
- if (config.channelOptions && config.channelOptions[channelId]) {
- delete config.channelOptions[channelId];
- }
-
- return await saveJoinToCreateConfig(client, guildId, config);
- } catch (error) {
- console.error(`Error removing Join to Create trigger for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Register a temporary channel in the system
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} channelId - The temporary channel ID
- * @param {string} ownerId - The owner user ID
- * @param {string} triggerChannelId - The trigger channel that created this
- * @returns {Promise} Whether the operation was successful
- */
-export async function registerTemporaryChannel(client, guildId, channelId, ownerId, triggerChannelId) {
- try {
- const config = await getJoinToCreateConfig(client, guildId);
-
- config.temporaryChannels[channelId] = {
- ownerId,
- triggerChannelId,
- createdAt: Date.now()
- };
-
- return await saveJoinToCreateConfig(client, guildId, config);
- } catch (error) {
- console.error(`Error registering temporary channel for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Unregister a temporary channel from the system
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} channelId - The temporary channel ID
- * @returns {Promise} Whether the operation was successful
- */
-export async function unregisterTemporaryChannel(client, guildId, channelId) {
- try {
- const config = await getJoinToCreateConfig(client, guildId);
-
- if (config.temporaryChannels[channelId]) {
- delete config.temporaryChannels[channelId];
- return await saveJoinToCreateConfig(client, guildId, config);
- }
-
- return false;
- } catch (error) {
- console.error(`Error unregistering temporary channel for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Get a temporary channel's information
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} channelId - The temporary channel ID
- * @returns {Promise} The temporary channel info or null if not found
- */
-export async function getTemporaryChannelInfo(client, guildId, channelId) {
- try {
- const config = await getJoinToCreateConfig(client, guildId);
- return config.temporaryChannels[channelId] || null;
- } catch (error) {
- console.error(`Error getting temporary channel info for guild ${guildId}:`, error);
- return null;
- }
-}
-
-/**
- * Format channel name with template variables
- * @param {string} template - The name template
- * @param {Object} variables - The variables to replace
- * @returns {string} The formatted channel name
- */
-export function formatChannelName(template, variables) {
- let formatted = template;
-
- const replacements = {
- '{username}': variables.username || 'User',
- '{user_tag}': variables.userTag || 'User#0000',
- '{display_name}': variables.displayName || 'User',
- '{guild_name}': variables.guildName || 'Server',
- '{channel_name}': variables.channelName || 'Voice Channel'
- };
-
- for (const [placeholder, value] of Object.entries(replacements)) {
- formatted = formatted.replace(new RegExp(placeholder.replace(/[{}]/g, '\\$&'), 'g'), value);
- }
-
- formatted = formatted.replace(/[^\w\s-]/g, '').trim();
-formatted = formatted.substring(0, 100);
-
- return formatted || 'Voice Channel';
-}
-
-/**
- * Generate a unique case ID
- * @returns {string} A unique case ID
- */
-function generateCaseId() {
- return `${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 4)}`;
-}
-
diff --git a/src/utils/dateUtils.js b/src/utils/dateUtils.js
deleted file mode 100644
index 9c15ae61bf..0000000000
--- a/src/utils/dateUtils.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Get the name of a month from its number (1-12)
- * @param {number} monthNum - The month number (1-12)
- * @returns {string} The month name or "Invalid Month" if invalid
- */
-export function getMonthName(monthNum) {
- const months = [
- "Jan",
- "Feb",
- "Mar",
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- ];
- return months[monthNum - 1] || "Invalid Month";
-}
-
diff --git a/src/utils/economy.js b/src/utils/economy.js
deleted file mode 100644
index 8faf3622d6..0000000000
--- a/src/utils/economy.js
+++ /dev/null
@@ -1,452 +0,0 @@
-import { getColor } from './database.js';
-import { BotConfig } from '../config/bot.js';
-import { normalizeEconomyData } from './schemas.js';
-
-const ECONOMY_CONFIG = BotConfig.economy || {};
-const BASE_BANK_CAPACITY = ECONOMY_CONFIG.baseBankCapacity || 10000;
-const BANK_CAPACITY_PER_LEVEL = ECONOMY_CONFIG.bankCapacityPerLevel || 5000;
-const DAILY_AMOUNT = ECONOMY_CONFIG.dailyAmount || 100;
-const WORK_MIN = ECONOMY_CONFIG.workMin || 10;
-const WORK_MAX = ECONOMY_CONFIG.workMax || 100;
-const COOLDOWNS = ECONOMY_CONFIG.cooldowns || {
-daily: 24 * 60 * 60 * 1000,
-work: 60 * 60 * 1000,
-crime: 2 * 60 * 60 * 1000,
-rob: 4 * 60 * 60 * 1000,
-};
-
-const DEFAULT_ECONOMY_DATA = {
- wallet: 0,
- bank: 0,
- bankLevel: 0,
- xp: 0,
- level: 1,
- lastDaily: 0,
- lastWork: 0,
- lastCrime: 0,
- lastRob: 0,
- inventory: {},
- cooldowns: {}
-};
-
-/**
- * Get the economy key for a user
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @returns {string} The economy key
- */
-export function getEconomyKey(guildId, userId) {
- return `economy:${guildId}:${userId}`;
-}
-
-/**
- * Calculate the maximum bank capacity for a user
- * @param {Object} userData - The user's economy data
- * @returns {number} The maximum bank capacity
- */
-export function getMaxBankCapacity(userData) {
- if (!userData) return BASE_BANK_CAPACITY;
-
- const bankLevel = userData.bankLevel || 0;
- return BASE_BANK_CAPACITY + (bankLevel * BANK_CAPACITY_PER_LEVEL);
-}
-
-/**
- * Format an amount of currency
- * @param {number} amount - The amount to format
- * @returns {string} The formatted currency string
- */
-export function formatCurrency(amount) {
- return `${amount.toLocaleString()} ${ECONOMY_CONFIG.currency || 'coins'}`;
-}
-
-/**
- * Get economy data for a user
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @returns {Promise} The user's economy data
- */
-export async function getEconomyData(client, guildId, userId) {
- try {
- if (!client.db || typeof client.db.get !== 'function') {
- throw new Error('Database not available');
- }
-
- const key = getEconomyKey(guildId, userId);
- const data = await client.db.get(key, {});
-
- return normalizeEconomyData(data, DEFAULT_ECONOMY_DATA);
- } catch (error) {
- console.error(`Error getting economy data for user ${userId}:`, error);
- return normalizeEconomyData({}, DEFAULT_ECONOMY_DATA);
- }
-}
-
-/**
- * Save economy data for a user
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @param {Object} data - The data to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function setEconomyData(client, guildId, userId, data) {
- try {
- if (!client.db || typeof client.db.set !== 'function') {
- throw new Error('Database not available');
- }
-
- const key = getEconomyKey(guildId, userId);
- const normalized = normalizeEconomyData(data, DEFAULT_ECONOMY_DATA);
- await client.db.set(key, normalized);
- return true;
- } catch (error) {
- console.error(`Error saving economy data for user ${userId}:`, error);
- return false;
- }
-}
-
-/**
- * Update a user's balance
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @param {Object} options - Update options
- * @param {number} [options.wallet] - Amount to add to wallet (can be negative)
- * @param {number} [options.bank] - Amount to add to bank (can be negative)
- * @param {number} [options.xp] - Amount of XP to add
- * @returns {Promise} The updated economy data
- */
-export async function updateBalance(client, guildId, userId, options = {}) {
- const data = await getEconomyData(client, guildId, userId);
-
- if (options.wallet !== undefined) {
- data.wallet = Math.max(0, (data.wallet || 0) + options.wallet);
- }
-
- if (options.bank !== undefined) {
- const maxBank = getMaxBankCapacity(data);
- data.bank = Math.min(Math.max(0, (data.bank || 0) + options.bank), maxBank);
- }
-
- if (options.xp !== undefined) {
- data.xp = Math.max(0, (data.xp || 0) + options.xp);
-
- const xpNeeded = Math.floor(5 * Math.pow(data.level || 1, 2) + 50 * (data.level || 1) + 100);
- if (data.xp >= xpNeeded) {
- data.xp -= xpNeeded;
- data.level = (data.level || 1) + 1;
- data.leveledUp = true;
- }
- }
-
- await setEconomyData(client, guildId, userId, data);
- return data;
-}
-
-/**
- * Check if a user has a cooldown
- * @param {Object} userData - The user's economy data
- * @param {string} action - The action to check
- * @returns {Object} Cooldown information
- */
-export function checkCooldown(userData, action) {
- const cooldownTime = COOLDOWNS[action] || 0;
- const lastUsed = userData[`last${action.charAt(0).toUpperCase() + action.slice(1)}`] || 0;
- const now = Date.now();
- const remaining = Math.max(0, (lastUsed + cooldownTime) - now);
-
- return {
- onCooldown: remaining > 0,
- remaining,
- formatted: formatCooldown(remaining)
- };
-}
-
-/**
- * Format a cooldown time
- * @param {number} ms - Time in milliseconds
- * @returns {string} Formatted time string
- */
-function formatCooldown(ms) {
- if (ms < 1000) return 'now';
-
- const seconds = Math.floor(ms / 1000);
- const minutes = Math.floor(seconds / 60);
- const hours = Math.floor(minutes / 60);
- const days = Math.floor(hours / 24);
-
- if (days > 0) return `${days}d ${hours % 24}h`;
- if (hours > 0) return `${hours}h ${minutes % 60}m`;
- if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
- return `${seconds}s`;
-}
-
-/**
- * Get a random work reward
- * @returns {Object} Work reward information
- */
-export function getWorkReward() {
- const amount = Math.floor(Math.random() * (WORK_MAX - WORK_MIN + 1)) + WORK_MIN;
- const jobs = [
- 'worked at a fast food restaurant',
- 'worked as a programmer',
- 'worked as a construction worker',
- 'worked as a doctor',
- 'worked as a streamer',
- 'worked as a YouTuber',
- 'worked as a teacher',
- 'worked as a cashier',
- 'worked as a delivery driver',
- 'worked as a freelancer'
- ];
-
- const job = jobs[Math.floor(Math.random() * jobs.length)];
-
- return {
- amount,
- job,
- message: `You ${job} and earned ${formatCurrency(amount)}!`
- };
-}
-
-/**
- * Get a random crime outcome
- * @returns {Object} Crime outcome information
- */
-export function getCrimeOutcome() {
- const outcomes = [
- {
- success: true,
- amount: Math.floor(Math.random() * 200) + 50,
- message: 'You successfully robbed a bank and got away with {amount}!'
- },
- {
- success: true,
- amount: Math.floor(Math.random() * 100) + 20,
- message: 'You pickpocketed someone and stole {amount}!'
- },
- {
- success: true,
- amount: Math.floor(Math.random() * 150) + 30,
- message: 'You hacked into a bank account and transferred {amount} to yourself!'
- },
- {
- success: false,
- fine: Math.floor(Math.random() * 100) + 50,
- message: 'You got caught and had to pay a fine of {fine}!'
- },
- {
- success: false,
- fine: Math.floor(Math.random() * 150) + 50,
- message: 'The police caught you! You paid {fine} to get out of jail.'
- },
- {
- success: false,
- fine: 0,
- message: 'Your attempt failed, but you managed to escape!'
- }
- ];
-
- return outcomes[Math.floor(Math.random() * outcomes.length)];
-}
-
-/**
- * Get a random rob outcome
- * @param {number} targetBalance - The target's wallet balance
- * @returns {Object} Rob outcome information
- */
-export function getRobOutcome(targetBalance) {
- if (targetBalance <= 0) {
- return {
- success: false,
- amount: 0,
- message: 'The target has no money to steal!'
- };
- }
-
-const success = Math.random() > 0.4;
-
- if (success) {
- const amount = Math.min(
-Math.floor(Math.random() * (targetBalance * 0.3)) + 1,
- targetBalance
- );
-
- return {
- success: true,
- amount,
- message: `You successfully robbed them and got away with {amount}!`
- };
- } else {
- const fine = Math.floor(Math.random() * 200) + 100;
-
- return {
- success: false,
- amount: 0,
- fine,
- message: `You got caught! You had to pay a fine of {fine}.`
- };
- }
-}
-
-/**
- * Format a shop item
- * @param {Object} item - The item to format
- * @param {number} index - The item index
- * @returns {string} Formatted item string
- */
-export function formatShopItem(item, index) {
- return `**${index + 1}.** ${item.emoji} **${item.name}** - ${formatCurrency(item.price)}\n${item.description}\n`;
-}
-
-/**
- * Get the shop inventory
- * @returns {Array} The shop items
- */
-/**
- * Add money to a user's wallet or bank
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @param {number} amount - The amount to add
- * @param {string} [type='wallet'] - Where to add the money ('wallet' or 'bank')
- * @returns {Promise<{success: boolean, newBalance: number, maxBank?: number}>} Result of the operation
- */
-export async function addMoney(client, guildId, userId, amount, type = 'wallet') {
- try {
- if (amount <= 0) {
- return { success: false, error: 'Amount must be positive' };
- }
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (type === 'bank') {
- const maxBank = getMaxBankCapacity(userData);
- if ((userData.bank || 0) + amount > maxBank) {
- return {
- success: false,
- error: 'Bank capacity exceeded',
- current: userData.bank || 0,
- max: maxBank
- };
- }
- userData.bank = (userData.bank || 0) + amount;
- } else {
- userData.wallet = (userData.wallet || 0) + amount;
- }
-
- await setEconomyData(client, guildId, userId, userData);
-
- return {
- success: true,
- newBalance: type === 'bank' ? userData.bank : userData.wallet,
- ...(type === 'bank' ? { maxBank: getMaxBankCapacity(userData) } : {})
- };
- } catch (error) {
- console.error(`Error adding money to ${type} for user ${userId} in guild ${guildId}:`, error);
- return { success: false, error: 'An error occurred while processing your request' };
- }
-}
-
-/**
- * Remove money from a user's wallet or bank
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} userId - The user ID
- * @param {number} amount - The amount to remove
- * @param {string} [type='wallet'] - Where to remove the money from ('wallet' or 'bank')
- * @returns {Promise<{success: boolean, newBalance: number}>} Result of the operation
- */
-export async function removeMoney(client, guildId, userId, amount, type = 'wallet') {
- try {
- if (amount <= 0) {
- return { success: false, error: 'Amount must be positive' };
- }
-
- const userData = await getEconomyData(client, guildId, userId);
-
- if (type === 'bank') {
- if ((userData.bank || 0) < amount) {
- return {
- success: false,
- error: 'Insufficient funds in bank',
- current: userData.bank || 0,
- required: amount
- };
- }
- userData.bank = (userData.bank || 0) - amount;
- } else {
- if ((userData.wallet || 0) < amount) {
- return {
- success: false,
- error: 'Insufficient funds in wallet',
- current: userData.wallet || 0,
- required: amount
- };
- }
- userData.wallet = (userData.wallet || 0) - amount;
- }
-
- await setEconomyData(client, guildId, userId, userData);
-
- return {
- success: true,
- newBalance: type === 'bank' ? userData.bank : userData.wallet
- };
- } catch (error) {
- console.error(`Error removing money from ${type} for user ${userId} in guild ${guildId}:`, error);
- return { success: false, error: 'An error occurred while processing your request' };
- }
-}
-
-export function getShopInventory() {
- return [
- {
- id: 'fishing_rod',
- name: 'Fishing Rod',
- emoji: '🎣',
- price: 500,
- description: 'Catch fish to sell for profit!',
- type: 'tool'
- },
- {
- id: 'hunting_rifle',
- name: 'Hunting Rifle',
- emoji: '🔫',
- price: 1000,
- description: 'Hunt animals for meat and fur!',
- type: 'tool'
- },
- {
- id: 'laptop',
- name: 'Laptop',
- emoji: '💻',
- price: 2000,
- description: 'Work as a programmer for higher pay!',
- type: 'tool',
- workMultiplier: 1.5
- },
- {
- id: 'bank_loan',
- name: 'Bank Loan',
- emoji: 'ðŸ¦',
- price: 5000,
- description: 'Increases your bank capacity by 50,000!',
- type: 'upgrade',
- effect: 'bank_capacity',
- value: 50000
- },
- {
- id: 'lottery_ticket',
- name: 'Lottery Ticket',
- emoji: '🎫',
- price: 100,
- description: 'A chance to win big!',
- type: 'consumable',
- use: 'gamble'
- }
- ];
-}
-
diff --git a/src/utils/embeds.js b/src/utils/embeds.js
deleted file mode 100644
index 0216dab90f..0000000000
--- a/src/utils/embeds.js
+++ /dev/null
@@ -1,168 +0,0 @@
-import { EmbedBuilder } from 'discord.js';
-import { getColor } from '../config/bot.js';
-
-export function createEmbed({
- title = '',
- description = '',
- color = 'primary',
- fields = [],
- author = null,
- footer = null,
- thumbnail = null,
- image = null,
- timestamp = true,
- url = null
-} = {}) {
- const embed = new EmbedBuilder();
- if (title) embed.setTitle(title);
- if (description) embed.setDescription(description);
- embed.setColor(getColor(color));
-
- if (fields && fields.length > 0) {
- embed.addFields(fields);
- }
-
- if (author) {
- if (typeof author === 'string') {
- embed.setAuthor({ name: author });
- } else {
- embed.setAuthor(author);
- }
- }
-
- if (footer) {
- if (typeof footer === 'string') {
- embed.setFooter({ text: footer });
- } else {
- embed.setFooter(footer);
- }
- }
-
- if (thumbnail) {
- if (typeof thumbnail === 'string') {
- embed.setThumbnail(thumbnail);
- } else {
- embed.setThumbnail(thumbnail.url);
- }
- }
-
- if (image) {
- if (typeof image === 'string') {
- embed.setImage(image);
- } else {
- embed.setImage(image.url);
- }
- }
-
- if (timestamp) {
- embed.setTimestamp();
- }
-
- if (url) {
- embed.setURL(url);
- }
-
- return embed;
-}
-
-export function errorEmbed(message, error = null, options = {}) {
- const { showDetails = process.env.NODE_ENV !== 'production' } = options;
- let description = message;
-
- if (error && showDetails) {
- const detailText = typeof error === 'string' ? error : (error.message || String(error));
- description = `${message}\n${formatCodeBlock(detailText)}`;
- }
-
- return createEmbed({
- title: '⌠Error',
- description,
- color: 'error',
- timestamp: true
- });
-}
-
-export function successEmbed(message, title = '✅ Success') {
- return createEmbed({
- title,
- description: message,
- color: 'success',
- timestamp: true
- });
-}
-
-export function infoEmbed(message, title = 'â„¹ï¸ Information') {
- return createEmbed({
- title,
- description: message,
- color: 'info',
- timestamp: true
- });
-}
-
-export function warningEmbed(message, title = 'âš ï¸ Warning') {
- return createEmbed({
- title,
- description: message,
- color: 'warning',
- timestamp: true
- });
-}
-
-export function formatUser(user) {
- return `${user} (${user.tag} | ${user.id})`;
-}
-
-export function formatDate(date) {
- return ``;
-}
-
-export function formatRelativeTime(date) {
- return ``;
-}
-
-export function formatCodeBlock(content, language = '') {
- return `\`\`\`${language}\n${content}\n\`\`\``;
-}
-
-export function formatInlineCode(content) {
- return `\`${content}\``;
-}
-
-export function formatBold(content) {
- return `**${content}**`;
-}
-
-export function formatItalic(content) {
- return `*${content}*`;
-}
-
-export function formatUnderline(content) {
- return `__${content}__`;
-}
-
-export function formatStrikethrough(content) {
- return `~~${content}~~`;
-}
-
-export function formatSpoiler(content) {
- return `||${content}||`;
-}
-
-export function formatQuote(content) {
- return `> ${content}`;
-}
-
-export function formatList(items, ordered = false) {
- return items
- .map((item, index) => (ordered ? `${index + 1}.` : '•') + ` ${item}`)
- .join('\n');
-}
-
-export function formatProgressBar(current, max, size = 10) {
- const progress = Math.min(Math.max(0, current / max), 1);
- const filled = Math.round(size * progress);
- const empty = size - filled;
- return `[${'â–ˆ'.repeat(filled)}${'â–‘'.repeat(empty)}] ${Math.round(progress * 100)}%`;
-}
-
diff --git a/src/utils/errorHandler.js b/src/utils/errorHandler.js
deleted file mode 100644
index 32c26754b2..0000000000
--- a/src/utils/errorHandler.js
+++ /dev/null
@@ -1,268 +0,0 @@
-import { logger } from './logger.js';
-import { createEmbed } from './embeds.js';
-import { MessageFlags } from 'discord.js';
-
-/**
- * Error types for categorization
- */
-export const ErrorTypes = {
- VALIDATION: 'validation',
- PERMISSION: 'permission',
- CONFIGURATION: 'configuration',
- DATABASE: 'database',
- NETWORK: 'network',
- DISCORD_API: 'discord_api',
- USER_INPUT: 'user_input',
- RATE_LIMIT: 'rate_limit',
- UNKNOWN: 'unknown'
-};
-
-/**
- * Custom error class for TitanBot
- */
-export class TitanBotError extends Error {
- constructor(message, type = ErrorTypes.UNKNOWN, userMessage = null, context = {}) {
- super(message);
- this.name = 'TitanBotError';
- this.type = type;
- this.userMessage = userMessage;
- this.context = context;
- this.timestamp = new Date().toISOString();
- }
-}
-
-/**
- * Error categorization utility
- */
-export function categorizeError(error) {
- if (error instanceof TitanBotError) {
- return error.type;
- }
-
- const message = error.message?.toLowerCase() || '';
- const code = error.code;
-
- if (code >= 10000 && code < 20000) {
- return ErrorTypes.DISCORD_API;
- }
-
- if (message.includes('rate limit') || code === 50001) {
- return ErrorTypes.RATE_LIMIT;
- }
-
- if (message.includes('permission') || message.includes('missing') || code === 50013) {
- return ErrorTypes.PERMISSION;
- }
-
- if (message.includes('database') || message.includes('connection') || message.includes('timeout')) {
- return ErrorTypes.DATABASE;
- }
-
- if (message.includes('network') || message.includes('fetch') || message.includes('enotconn')) {
- return ErrorTypes.NETWORK;
- }
-
- if (message.includes('config') || message.includes('not found') || message.includes('invalid')) {
- return ErrorTypes.CONFIGURATION;
- }
-
- if (message.includes('validation') || message.includes('invalid') || message.includes('required')) {
- return ErrorTypes.VALIDATION;
- }
-
- return ErrorTypes.UNKNOWN;
-}
-
-/**
- * User-friendly error messages
- */
-const UserMessages = {
- [ErrorTypes.VALIDATION]: {
- default: "Please check your input and try again.",
- missing_required: "You're missing some required information. Please check the command options.",
- invalid_format: "The format you provided is incorrect. Please try again."
- },
- [ErrorTypes.PERMISSION]: {
- default: "I don't have permission to do that. Please check my server permissions.",
- user_permission: "You don't have permission to use this command.",
- bot_permission: "I need additional permissions to perform this action."
- },
- [ErrorTypes.CONFIGURATION]: {
- default: "Something is not configured correctly. Please contact an administrator.",
- missing_config: "This feature hasn't been set up yet. Please contact an administrator.",
- invalid_config: "The configuration is invalid. Please contact an administrator."
- },
- [ErrorTypes.DATABASE]: {
- default: "I'm having trouble with my database. Please try again in a moment.",
- connection_failed: "I'm having trouble connecting to my database. Please try again later.",
- timeout: "The operation took too long. Please try again."
- },
- [ErrorTypes.NETWORK]: {
- default: "I'm having network issues. Please try again in a moment.",
- timeout: "The request timed out. Please try again.",
- unreachable: "I can't reach the service right now. Please try again later."
- },
- [ErrorTypes.DISCORD_API]: {
- default: "I'm having trouble with Discord. Please try again in a moment.",
- rate_limit: "You're doing that too much. Please wait a moment and try again.",
- forbidden: "I'm not allowed to do that. Please check my permissions."
- },
- [ErrorTypes.USER_INPUT]: {
- default: "There was an issue with your request. Please try again.",
- invalid_user: "I couldn't find that user. Please check the user mention or ID.",
- invalid_channel: "I couldn't find that channel. Please check the channel mention or ID."
- },
- [ErrorTypes.RATE_LIMIT]: {
- default: "You're doing that too much. Please wait a moment and try again.",
- command_cooldown: "This command is on cooldown. Please wait before using it again.",
- global_rate_limit: "You're being rate limited by Discord. Please wait a moment."
- },
- [ErrorTypes.UNKNOWN]: {
- default: "Something went wrong. Please try again in a moment.",
- unexpected: "An unexpected error occurred. Please try again later."
- }
-};
-
-/**
- * Get user-friendly error message
- */
-export function getUserMessage(error, context = {}) {
- const type = categorizeError(error);
- const messages = UserMessages[type] || UserMessages[ErrorTypes.UNKNOWN];
-
- if (error.userMessage) {
- return error.userMessage;
- }
-
- if (context.subtype && messages[context.subtype]) {
- return messages[context.subtype];
- }
-
- return messages.default;
-}
-
-/**
- * Centralized error handler for interactions
- */
-export async function handleInteractionError(interaction, error, context = {}) {
- const errorType = categorizeError(error);
- const userMessage = getUserMessage(error, context);
-
- logger.error(`Interaction Error [${errorType.toUpperCase()}]`, {
- error: error.message,
- stack: error.stack,
- type: errorType,
- interaction: {
- type: interaction.type,
- commandName: interaction.commandName,
- customId: interaction.customId,
- userId: interaction.user.id,
- guildId: interaction.guildId,
- channelId: interaction.channelId
- },
- context
- });
-
- const embed = createEmbed({
- title: getErrorTitle(errorType),
- description: userMessage,
- color: 'error',
- timestamp: true
- });
-
- if (errorType === ErrorTypes.RATE_LIMIT) {
- embed.addFields({
- name: "💡 Tip",
- value: "Rate limits help prevent spam. Wait a moment before trying again."
- });
- } else if (errorType === ErrorTypes.PERMISSION) {
- embed.addFields({
- name: "🔧 Need Help?",
- value: "Contact a server administrator if you believe this is an error."
- });
- } else if (errorType === ErrorTypes.CONFIGURATION) {
- embed.addFields({
- name: "📋 Configuration",
- value: "This feature needs to be configured by a server administrator."
- });
- }
-
- try {
- const errorMessage = {
- embeds: [embed]
- };
-
- if (!interaction.deferred && !interaction.replied) {
- errorMessage.flags = MessageFlags.Ephemeral;
- }
-
- if (interaction.deferred || interaction.replied) {
- await interaction.editReply(errorMessage);
- } else {
- await interaction.reply(errorMessage);
- }
- } catch (replyError) {
- logger.error('Failed to send error response:', replyError);
- }
-}
-
-/**
- * Get error title based on type
- */
-function getErrorTitle(errorType) {
- const titles = {
- [ErrorTypes.VALIDATION]: "⌠Invalid Input",
- [ErrorTypes.PERMISSION]: "🚫 Permission Denied",
- [ErrorTypes.CONFIGURATION]: "âš™ï¸ Configuration Error",
- [ErrorTypes.DATABASE]: "ðŸ—„ï¸ Database Error",
- [ErrorTypes.NETWORK]: "🌠Network Error",
- [ErrorTypes.DISCORD_API]: "🔌 API Error",
- [ErrorTypes.USER_INPUT]: "💬 Input Error",
- [ErrorTypes.RATE_LIMIT]: "â±ï¸ Slow Down!",
- [ErrorTypes.UNKNOWN]: "â“ Unexpected Error"
- };
-
- return titles[errorType] || titles[ErrorTypes.UNKNOWN];
-}
-
-/**
- * Error handler wrapper for command execution
- */
-export function withErrorHandling(fn, context = {}) {
- return async (...args) => {
- try {
- return await fn(...args);
- } catch (error) {
- const interaction = args.find(arg =>
- arg && typeof arg === 'object' &&
- (arg.isCommand || arg.isButton || arg.isModalSubmit || arg.isStringSelectMenu || arg.isChatInputCommand)
- );
-
- if (interaction) {
- await handleInteractionError(interaction, error, context);
- } else {
- logger.error('Error in non-interaction context:', error);
- }
-
- return null;
- }
- };
-}
-
-/**
- * Create a TitanBotError with context
- */
-export function createError(message, type = ErrorTypes.UNKNOWN, userMessage = null, context = {}) {
- return new TitanBotError(message, type, userMessage, context);
-}
-
-export default {
- ErrorTypes,
- TitanBotError,
- categorizeError,
- getUserMessage,
- handleInteractionError,
- withErrorHandling,
- createError
-};
-
diff --git a/src/utils/giveaways.js b/src/utils/giveaways.js
deleted file mode 100644
index 35a74d4800..0000000000
--- a/src/utils/giveaways.js
+++ /dev/null
@@ -1,178 +0,0 @@
-import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
-import botConfig from '../config/bot.js';
-import { getGuildGiveaways as getGuildGiveawaysDb, saveGiveaway as saveGiveawayDb, deleteGiveaway as deleteGiveawayDb, unwrapReplitData } from './database.js';
-
-/**
- * Generate a consistent key for giveaways in the database
- * @param {string} guildId - The guild ID
- * @returns {string} The formatted key
- */
-export function giveawayKey(guildId) {
- return `guild:${guildId}:giveaways`;
-}
-
-/**
- * Get all giveaways for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} Object mapping message IDs to giveaway data
- */
-export async function getGuildGiveaways(client, guildId) {
- try {
- const key = giveawayKey(guildId);
- const giveaways = await client.db.get(key, {});
- const unwrappedGiveaways = unwrapReplitData(giveaways);
- return unwrappedGiveaways || {};
- } catch (error) {
- console.error(`Error getting giveaways for guild ${guildId}:`, error);
- return {};
- }
-}
-
-/**
- * Save a giveaway
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {Object} giveawayData - The giveaway data to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function saveGiveaway(client, guildId, giveawayData) {
- try {
- const key = giveawayKey(guildId);
- const giveaways = await getGuildGiveaways(client, guildId);
- giveaways[giveawayData.messageId] = giveawayData;
- await client.db.set(key, giveaways);
- return true;
- } catch (error) {
- console.error(`Error saving giveaway in guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Delete a giveaway
- * @param {Object} client - The Discord client
- * @param {string} guildId - The guild ID
- * @param {string} messageId - The message ID of the giveaway to delete
- * @returns {Promise} Whether the operation was successful
- */
-export async function deleteGiveaway(client, guildId, messageId) {
- try {
- const key = giveawayKey(guildId);
- const giveaways = await getGuildGiveaways(client, guildId);
-
- if (!giveaways[messageId]) {
- return false;
- }
-
- delete giveaways[messageId];
- await client.db.set(key, giveaways);
- return true;
- } catch (error) {
- console.error(`Error deleting giveaway ${messageId} in guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Create an embed for a giveaway
- * @param {Object} giveaway - The giveaway data
- * @param {string} status - The status of the giveaway ('active' or 'ended')
- * @param {Array} [winners=[]] - Array of winner user IDs
- * @returns {EmbedBuilder} The formatted embed
- */
-export function createGiveawayEmbed(giveaway, status, winners = []) {
- const isEnded = status === 'ended';
- const participants = giveaway.participants || [];
-
- const embed = new EmbedBuilder()
- .setTitle(`🎉 ${giveaway.prize}`)
- .setDescription(giveaway.description || 'Enter this amazing giveaway!')
- .setColor(isEnded ? botConfig.embeds.colors.error : botConfig.embeds.colors.success)
- .addFields(
- { name: 'Hosted by', value: `<@${giveaway.hostId}>`, inline: true },
- { name: 'Winners', value: `${giveaway.winnerCount}`, inline: true },
- { name: 'Entries', value: `${participants.length}`, inline: true }
- );
-
- if (isEnded) {
- embed.addFields(
- { name: 'Winners', value: winners.length > 0 ? winners.map(id => `<@${id}>`).join(', ') : 'No winners' }
- );
- } else {
- const endTime = giveaway.endsAt || giveaway.endTime;
- embed.addFields(
- { name: 'Ends at', value: `` }
- );
- }
-
- return embed;
-}
-
-/**
- * Check if a giveaway has ended
- * @param {Object} giveaway - The giveaway data
- * @returns {boolean} Whether the giveaway has ended
- */
-export function isGiveawayEnded(giveaway) {
- const endTime = giveaway.endsAt || giveaway.endTime;
- return Date.now() > endTime;
-}
-
-/**
- * Pick random winners from the entrants
- * @param {Array} entrants - Array of user IDs
- * @param {number} count - Number of winners to pick
- * @returns {Array} Array of winner user IDs
- */
-export function pickWinners(entrants, count) {
- if (!entrants || entrants.length === 0) return [];
-
- const shuffled = [...entrants].sort(() => 0.5 - Math.random());
- return shuffled.slice(0, Math.min(count, shuffled.length));
-}
-
-/**
- * Create an embed for a giveaway (alias for createGiveawayEmbed)
- * @param {Object} giveaway - The giveaway data
- * @param {string} status - The status of the giveaway ('active', 'ended', 'reroll')
- * @param {Array} [winners=[]] - Array of winner user IDs
- * @returns {EmbedBuilder} The formatted embed
- */
-export function giveawayEmbed(giveaway, status, winners = []) {
- return createGiveawayEmbed(giveaway, status, winners);
-}
-
-/**
- * Create action row with giveaway buttons
- * @param {boolean} ended - Whether the giveaway has ended
- * @returns {ActionRowBuilder} The action row with buttons
- */
-export function giveawayButtons(ended) {
- if (ended) {
- return new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId('giveaway_reroll')
- .setLabel('🎲 Reroll')
- .setStyle(ButtonStyle.Secondary)
- .setDisabled(false),
- new ButtonBuilder()
- .setCustomId('giveaway_end')
- .setLabel('✅ Ended')
- .setStyle(ButtonStyle.Success)
- .setDisabled(true)
- );
- } else {
- return new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setCustomId('giveaway_join')
- .setLabel('🎉 Join')
- .setStyle(ButtonStyle.Primary),
- new ButtonBuilder()
- .setCustomId('giveaway_end')
- .setLabel('🛑 End')
- .setStyle(ButtonStyle.Danger)
- );
- }
-}
-
diff --git a/src/utils/helpers.js b/src/utils/helpers.js
deleted file mode 100644
index 1a9d1bdf68..0000000000
--- a/src/utils/helpers.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import { BotConfig } from "../config/bot.js";
-
-/**
- * Generates priority map from BotConfig for ticket priorities.
- * Maps priority level strings to their display names and colors.
- * Uses the centralized configuration from BotConfig.
- */
-export function getPriorityMap() {
- const priorities = BotConfig.tickets?.priorities || {};
- const map = {};
-
- for (const [key, config] of Object.entries(priorities)) {
- map[key] = {
- name: `${config.emoji} ${config.label.toUpperCase()}`,
- color: config.color,
- emoji: config.emoji,
- label: config.label,
- };
- }
-
- return map;
-}
-
-/**
- * Get a color from the configuration with a fallback
- * @param {string} path - Dot notation path to the color (e.g., 'primary', 'ticket.open')
- * @param {string} fallback - Fallback color if the path doesn't exist
- * @returns {string} The color code
- */
-export function getColor(path, fallback = "#000000") {
- const parts = path.split(".");
- let current = BotConfig.embeds.colors;
-
- for (const part of parts) {
- if (current[part] === undefined) {
- console.warn(
- `Color path '${path}' not found in config, using fallback`,
- );
- return fallback;
- }
- current = current[part];
- }
-
- return typeof current === "string" ? current : fallback;
-}
-
-/**
- * Get a message from the configuration with optional replacements
- * @param {string} key - The message key (e.g., 'noPermission')
- * @param {Object} [replacements] - Key-value pairs for string replacements
- * @returns {string} The formatted message
- */
-export function getMessage(key, replacements = {}) {
- let message = BotConfig.messages[key] || key;
-
- for (const [k, v] of Object.entries(replacements)) {
- message = message.replace(new RegExp(`\\{${k}\\}`, "g"), v);
- }
-
- return message;
-}
-
-/**
- * Format a duration in milliseconds into a human-readable string
- * @param {number} ms - Duration in milliseconds
- * @returns {string} Formatted duration string (e.g., "2d 5h 30m")
- */
-export function formatDuration(ms) {
- if (ms < 0) return "0s";
-
- const seconds = Math.floor(ms / 1000) % 60;
- const minutes = Math.floor(ms / (1000 * 60)) % 60;
- const hours = Math.floor(ms / (1000 * 60 * 60)) % 24;
- const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-
- const parts = [];
- if (days > 0) parts.push(`${days}d`);
- if (hours > 0) parts.push(`${hours}h`);
- if (minutes > 0) parts.push(`${minutes}m`);
- if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
-
- return parts.join(" ");
-}
-
-export const PRIORITY_MAP = getPriorityMap();
-
diff --git a/src/utils/interactionHelper.js b/src/utils/interactionHelper.js
deleted file mode 100644
index 7f6d763d88..0000000000
--- a/src/utils/interactionHelper.js
+++ /dev/null
@@ -1,239 +0,0 @@
-import { logger } from './logger.js';
-import { MessageFlags } from 'discord.js';
-import { handleInteractionError } from './errorHandler.js';
-
-/**
- * Helper class for handling Discord interactions with proper error handling
- */
-export class InteractionHelper {
- /**
- * Check if interaction is still valid (not expired)
- * @param {Interaction} interaction - Discord interaction
- * @returns {boolean} - Whether interaction is still valid
- */
- static isInteractionValid(interaction) {
- if (!interaction || !interaction.id) return false;
-
- if (interaction.createdTimestamp && (Date.now() - interaction.createdTimestamp) > 14 * 60 * 1000) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Ensure interaction is in a state that can be replied to
- * @param {Interaction} interaction - Discord interaction
- * @param {Object} deferOptions - Options for deferReply if needed
- * @returns {Promise} - Whether interaction is ready for replies
- */
- static async ensureReady(interaction, deferOptions = { flags: MessageFlags.Ephemeral }) {
- if (!this.isInteractionValid(interaction)) {
- return false;
- }
-
- if (interaction.replied || interaction.deferred) {
- return true;
- }
-
- return await this.safeDefer(interaction, deferOptions);
- }
-
- /**
- * Safely defer a reply with error handling
- * @param {Interaction} interaction - Discord interaction
- * @param {Object} options - Options forwarded to deferReply (optional)
- * @returns {Promise} - Whether defer was successful
- */
- static async safeDefer(interaction, options = {}) {
- try {
- if (interaction.deferred || interaction.replied) {
- return true;
- }
-
- if (!this.isInteractionValid(interaction)) {
- logger.warn(`Interaction ${interaction.id} has expired before defer, ignoring`);
- return false;
- }
-
- await interaction.deferReply(options);
- return true;
- } catch (error) {
-if (error.code === 10062) {
- logger.warn(`Interaction ${interaction.id} expired during defer:`, error.message);
- return false;
- }
- if (error.name === 'InteractionAlreadyReplied' || error.code === 40060) {
- logger.warn(`Interaction ${interaction.id} already acknowledged during defer:`, error.message);
- return true;
- }
- logger.error('Failed to defer reply:', error);
- return false;
- }
- }
-
- /**
- * Safely edit a reply with error handling
- * @param {Interaction} interaction - Discord interaction
- * @param {Object} options - Reply options
- * @returns {Promise} - Whether edit was successful
- */
- static async safeEditReply(interaction, options) {
- try {
- if (!this.isInteractionValid(interaction)) {
- logger.warn(`Interaction ${interaction.id} has expired before edit, ignoring`);
- return false;
- }
-
- if (!interaction.replied && !interaction.deferred) {
- logger.warn(`Interaction ${interaction.id} not deferred, attempting reply instead of edit`);
- return await this.safeReply(interaction, options);
- }
-
- await interaction.editReply(options);
- return true;
- } catch (error) {
-if (error.code === 10062) {
- logger.warn(`Interaction ${interaction.id} expired during edit:`, error.message);
- return false;
- }
-if (error.code === 40060) {
- logger.warn(`Interaction ${interaction.id} already acknowledged during edit:`, error.message);
- return false;
- }
- if (error.name === 'InteractionNotReplied' || error.message.includes('not been sent or deferred')) {
- logger.warn(`Interaction ${interaction.id} not replied, attempting reply instead of edit:`, error.message);
- return await this.safeReply(interaction, options);
- }
- logger.error('Failed to edit reply:', error);
- return false;
- }
- }
-
- /**
- * Safely reply to an interaction with error handling
- * @param {Interaction} interaction - Discord interaction
- * @param {Object} options - Reply options
- * @returns {Promise} - Whether reply was successful
- */
- static async safeReply(interaction, options) {
- try {
- if (!this.isInteractionValid(interaction)) {
- logger.warn(`Interaction ${interaction.id} has expired before reply, ignoring`);
- return false;
- }
-
- await interaction.reply(options);
- return true;
- } catch (error) {
-if (error.code === 10062) {
- logger.warn(`Interaction ${interaction.id} expired during reply:`, error.message);
- return false;
- }
-if (error.code === 40060) {
- logger.warn(`Interaction ${interaction.id} already acknowledged during reply:`, error.message);
- return false;
- }
- logger.error('Failed to reply:', error);
- return false;
- }
- }
-
- /**
- * Safely handle command execution with comprehensive error handling
- * @param {Interaction} interaction - Discord interaction
- * @param {Function} commandFunction - The command function to execute
- * @param {Object|string} errorEmbed - Error embed to use for failures
- * @param {Object} options - Additional options (autoDefer, deferOptions)
- * @returns {Promise}
- */
- static async safeExecute(interaction, commandFunction, errorEmbed, options = {}) {
- const { autoDefer = true, deferOptions = { flags: MessageFlags.Ephemeral } } = options;
-
- if (!this.isInteractionValid(interaction)) {
- logger.warn(`Interaction ${interaction.id} has expired, ignoring`);
- return;
- }
-
- if (autoDefer && !interaction.replied && !interaction.deferred) {
- const deferStartTime = Date.now();
- const deferSuccess = await this.safeDefer(interaction, deferOptions);
-
-if (Date.now() - deferStartTime > 3000) {
- logger.warn(`Interaction ${interaction.id} defer took too long (${Date.now() - deferStartTime}ms), command may expire`);
- }
-
- if (!deferSuccess) {
- logger.warn(`Interaction ${interaction.id} defer failed, skipping command execution`);
- return;
- }
- }
-
- try {
- await commandFunction();
- } catch (error) {
- logger.error('Error executing command:', error);
-
- if (!errorEmbed) {
- await handleInteractionError(interaction, error, { source: 'interactionHelper.safeExecute' });
- return;
- }
-
- let errorResponse;
- if (typeof errorEmbed === 'string') {
- const { errorEmbed: createErrorEmbed } = await import('./embeds.js');
- errorResponse = { embeds: [createErrorEmbed(errorEmbed, error)] };
- } else if (errorEmbed && typeof errorEmbed === 'object') {
- errorResponse = { embeds: [errorEmbed] };
- } else {
- const { errorEmbed: createErrorEmbed } = await import('./embeds.js');
- errorResponse = { embeds: [createErrorEmbed('Command execution failed.', error)] };
- }
-
- const editSuccess = await this.safeEditReply(interaction, errorResponse);
- if (!editSuccess) {
- logger.warn(`Failed to send error response for interaction ${interaction.id}, interaction may have expired`);
- }
- }
- }
-
- /**
- * Universal reply method that handles all interaction states
- * @param {Interaction} interaction - Discord interaction
- * @param {Object} options - Reply options
- * @returns {Promise} - Whether reply was successful
- */
- static async universalReply(interaction, options) {
- const isReady = await this.ensureReady(interaction, options.flags ? { flags: options.flags } : {});
- if (!isReady) {
- return false;
- }
-
- if (interaction.deferred) {
- return await this.safeEditReply(interaction, options);
- } else {
- return await this.safeReply(interaction, options);
- }
- }
-}
-
-/**
- * Decorator for wrapping command execute methods with error handling
- * @param {Function} target - The command method
- * @param {string} propertyName - The method name
- * @param {PropertyDescriptor} descriptor - The method descriptor
- */
-export function withErrorHandling(target, propertyName, descriptor) {
- const originalMethod = descriptor.value;
-
- descriptor.value = async function(interaction, config, client) {
- await InteractionHelper.safeExecute(
- interaction,
- () => originalMethod.call(this, interaction, config, client),
- { title: 'Command Error', description: 'Failed to execute command. Please try again later.' }
- );
- };
-
- return descriptor;
-}
-
diff --git a/src/utils/logger.js b/src/utils/logger.js
deleted file mode 100644
index d021471881..0000000000
--- a/src/utils/logger.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import winston from 'winston';
-import 'winston-daily-rotate-file';
-import path from 'path';
-import { fileURLToPath } from 'url';
-
-const { createLogger, format, transports } = winston;
-const { combine, timestamp, printf, colorize, errors, json } = format;
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-const logFormat = printf(({ level, message, timestamp, stack }) => {
- const logMessage = `[${timestamp}] [${level}]: ${stack || message}`;
- return logMessage;
-});
-
-const logger = createLogger({
- level: process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
- format: combine(
- timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
- errors({ stack: true }),
- format.json()
- ),
- defaultMeta: { service: 'titan-bot' },
- transports: [
- new transports.DailyRotateFile({
- filename: path.join(__dirname, '../../logs/error-%DATE%.log'),
- level: 'error',
- maxSize: '20m',
- maxFiles: '14d',
- zippedArchive: true,
- }),
- new transports.DailyRotateFile({
- filename: path.join(__dirname, '../../logs/combined-%DATE%.log'),
- maxSize: '20m',
- maxFiles: '7d',
- zippedArchive: true,
- }),
- ],
- exceptionHandlers: [
- new transports.DailyRotateFile({
- filename: path.join(__dirname, '../../logs/exceptions-%DATE%.log'),
- maxSize: '20m',
- maxFiles: '14d',
- zippedArchive: true,
- }),
- ],
- rejectionHandlers: [
- new transports.DailyRotateFile({
- filename: path.join(__dirname, '../../logs/rejections-%DATE%.log'),
- maxSize: '20m',
- maxFiles: '14d',
- zippedArchive: true,
- }),
- ],
-});
-
-if (process.env.NODE_ENV !== 'production') {
- logger.add(new transports.Console({
- format: combine(
- colorize(),
- timestamp({ format: 'HH:mm:ss' }),
- errors({ stack: true }),
- logFormat
- ),
- level: 'debug',
- }));
-} else {
- logger.add(new transports.Console({
- format: combine(
- colorize(),
- timestamp({ format: 'HH:mm:ss' }),
- errors({ stack: true }),
- logFormat
- ),
- level: 'warn',
- }));
-}
-
-logger.stream = {
- write: (message) => {
- logger.info(message.trim());
- },
-};
-
-export { logger };
-
-export default logger;
-
diff --git a/src/utils/memoryStorage.js b/src/utils/memoryStorage.js
deleted file mode 100644
index 1966d808aa..0000000000
--- a/src/utils/memoryStorage.js
+++ /dev/null
@@ -1,99 +0,0 @@
-import { logger } from './logger.js';
-
-/**
- * In-memory storage fallback for when database is unavailable
- * Used when PostgreSQL connection fails
- */
-class MemoryStorage {
- constructor() {
- this.data = new Map();
- this.expirationTimes = new Map();
- }
-
- async get(key, defaultValue = null) {
- const value = this.data.get(key);
-
- if (this.expirationTimes.has(key)) {
- const expirationTime = this.expirationTimes.get(key);
- if (Date.now() > expirationTime) {
- this.data.delete(key);
- this.expirationTimes.delete(key);
- return defaultValue;
- }
- }
-
- return value !== undefined ? value : defaultValue;
- }
-
- async set(key, value, ttl = null) {
- this.data.set(key, value);
-
- if (ttl && ttl > 0) {
- this.expirationTimes.set(key, Date.now() + (ttl * 1000));
- }
-
- return true;
- }
-
- async delete(key) {
- this.data.delete(key);
- this.expirationTimes.delete(key);
- return true;
- }
-
- async list(prefix) {
- const keys = [];
- for (const [key] of this.data.keys()) {
- if (key.startsWith(prefix)) {
- if (this.expirationTimes.has(key)) {
- const expirationTime = this.expirationTimes.get(key);
- if (Date.now() > expirationTime) {
- this.data.delete(key);
- this.expirationTimes.delete(key);
- continue;
- }
- }
- keys.push(key);
- }
- }
- return keys;
- }
-
- async exists(key) {
- const value = this.data.get(key);
-
- if (this.expirationTimes.has(key)) {
- const expirationTime = this.expirationTimes.get(key);
- if (Date.now() > expirationTime) {
- this.data.delete(key);
- this.expirationTimes.delete(key);
- return false;
- }
- }
-
- return value !== undefined;
- }
-
- async increment(key, amount = 1) {
- const current = await this.get(key, 0);
- const newValue = current + amount;
- await this.set(key, newValue);
- return newValue;
- }
-
- async decrement(key, amount = 1) {
- const current = await this.get(key, 0);
- const newValue = current - amount;
- await this.set(key, newValue);
- return newValue;
- }
-
- async clear() {
- this.data.clear();
- this.expirationTimes.clear();
- return true;
- }
-}
-
-export { MemoryStorage };
-
diff --git a/src/utils/messageTemplates.js b/src/utils/messageTemplates.js
deleted file mode 100644
index ce00f6e63d..0000000000
--- a/src/utils/messageTemplates.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import { EmbedBuilder } from 'discord.js';
-
-/**
- * Message templates for consistent bot responses
- */
-export const MessageTemplates = {
- SUCCESS: {
- DATA_UPDATED: (action, description) => new EmbedBuilder()
- .setColor('#00ff00')
- .setTitle(`✅ ${action.charAt(0).toUpperCase() + action.slice(1)} Successful`)
- .setDescription(description)
- .setTimestamp(),
-
- COMMAND_EXECUTED: (command) => new EmbedBuilder()
- .setColor('#00ff00')
- .setTitle('✅ Command Executed')
- .setDescription(`Successfully executed \`${command}\``)
- .setTimestamp()
- },
-
- ERRORS: {
- DATABASE_ERROR: (operation) => new EmbedBuilder()
- .setColor('#ff0000')
- .setTitle('ðŸ—„ï¸ Database Error')
- .setDescription(`I'm having trouble with my database while ${operation}. Please try again later.`)
- .setTimestamp(),
-
- INSUFFICIENT_FUNDS: (currency, description) => new EmbedBuilder()
- .setColor('#ff9900')
- .setTitle('💰 Insufficient Funds')
- .setDescription(description || `You don't have enough ${currency} for this operation.`)
- .setTimestamp(),
-
- PERMISSION_DENIED: (permission) => new EmbedBuilder()
- .setColor('#ff0000')
- .setTitle('🚫 Permission Denied')
- .setDescription(`You need the \`${permission}\` permission to use this command.`)
- .setTimestamp(),
-
- INVALID_INPUT: (field) => new EmbedBuilder()
- .setColor('#ff9900')
- .setTitle('⌠Invalid Input')
- .setDescription(`The ${field || 'input'} you provided is invalid. Please check and try again.`)
- .setTimestamp()
- },
-
- INFO: {
- LOADING: (description) => new EmbedBuilder()
- .setColor('#ffff00')
- .setTitle('â³ Loading...')
- .setDescription(description || 'Please wait while I process your request.')
- .setTimestamp(),
-
- PROCESSING: (description) => new EmbedBuilder()
- .setColor('#0099ff')
- .setTitle('âš™ï¸ Processing')
- .setDescription(description || 'Processing your request...')
- .setTimestamp()
- }
-};
-
-export const ContextualMessages = {
- configUpdated: (title, configLines) => new EmbedBuilder()
- .setColor('#00ff00')
- .setTitle(`✅ ${title} Updated`)
- .setDescription(configLines.join('\n'))
- .setTimestamp()
-};
-
-export default MessageTemplates;
-
diff --git a/src/utils/moderation.js b/src/utils/moderation.js
deleted file mode 100644
index 1695b89f4f..0000000000
--- a/src/utils/moderation.js
+++ /dev/null
@@ -1,244 +0,0 @@
-import { EmbedBuilder } from 'discord.js';
-import { getGuildConfig } from '../services/guildConfig.js';
-import { logger } from './logger.js';
-import { getFromDb, setInDb } from './database.js';
-
-/**
- * Enhanced moderation logging system with comprehensive tracking
- * @param {Object} options - The log options
- * @param {import('discord.js').Client} options.client - The Discord client
- * @param {import('discord.js').Guild} options.guild - The guild object
- * @param {Object} options.event - The event details
- * @param {string} options.event.action - The action that was taken
- * @param {string} options.event.target - The target user/channel/object
- * @param {string} options.event.executor - The moderator who performed the action
- * @param {string} [options.event.reason] - The reason for the action
- * @param {string} [options.event.duration] - Duration for timed actions
- * @param {Object} [options.event.metadata] - Additional metadata
- * @param {string} [options.event.color] - Embed color override
- * @returns {Promise}
- */
-export async function logEvent({ client, guild, guildId, event }) {
- try {
- if (!guild && guildId) {
- guild = client.guilds.cache.get(guildId) || await client.guilds.fetch(guildId).catch(() => null);
- }
- if (!guild) {
- logger.warn('logEvent invoked without valid guild or guildId');
- return;
- }
- const config = await getGuildConfig(client, guild.id);
- if (!config?.logChannelId || !config?.enableLogging) {
- logger.debug(`Logging disabled or no log channel configured for guild ${guild.id}`);
- return;
- }
-
- const logChannel = guild.channels.cache.get(config.logChannelId);
- if (!logChannel) {
- logger.warn(`Log channel ${config.logChannelId} not found in guild ${guild.id}`);
- return;
- }
-
- const actionStyles = {
- 'Member Banned': { color: '#721919', icon: '🔨' },
- 'Member Kicked': { color: '#FFA500', icon: '👢' },
- 'Member Timed Out': { color: '#F1C40F', icon: 'â³' },
- 'Member Untimeouted': { color: '#2ECC71', icon: '✅' },
- 'User Warned': { color: '#FEE75C', icon: 'âš ï¸' },
- 'Warnings Viewed': { color: '#3498DB', icon: 'ðŸ‘ï¸' },
- 'Messages Purged': { color: '#E67E22', icon: '🗑ï¸' },
- 'Channel Locked': { color: '#CC00CC', icon: '🔒' },
- 'Channel Unlocked': { color: '#2ECC71', icon: '🔓' },
- 'Case Created': { color: '#3498DB', icon: '📋' },
- 'Case Updated': { color: '#9B59B6', icon: 'ðŸ“' },
- 'DM Sent': { color: '#3498DB', icon: '✉ï¸' },
- 'Log Channel Activated': { color: '#2ECC71', icon: 'ðŸ“' }
- };
-
- const style = actionStyles[event.action] || { color: '#0099ff', icon: '🔨' };
-
- const embed = new EmbedBuilder()
- .setColor(event.color || style.color)
- .setTitle(`${style.icon} ${event.action}`)
- .addFields(
- { name: "Target", value: event.target, inline: true },
- { name: "Moderator", value: event.executor, inline: true }
- )
- .setTimestamp()
- .setFooter({
- text: `Guild ID: ${guild.id} | Moderator ID: ${event.executor.match(/\((\d+)\)/)?.[1] || 'Unknown'}`,
- iconURL: guild.iconURL()
- });
-
- if (event.reason) {
- embed.addFields({
- name: "Reason",
- value: event.reason.length > 1024 ? event.reason.substring(0, 1021) + '...' : event.reason,
- inline: false
- });
- }
-
- if (event.duration) {
- embed.addFields({
- name: "Duration",
- value: event.duration,
- inline: true
- });
- }
-
- if (event.metadata) {
- Object.entries(event.metadata).forEach(([key, value]) => {
- if (value !== undefined && value !== null) {
- embed.addFields({
- name: key.charAt(0).toUpperCase() + key.slice(1),
- value: String(value).length > 1024 ? String(value).substring(0, 1021) + '...' : String(value),
- inline: true
- });
- }
- });
- }
-
- if (event.caseId) {
- embed.addFields({
- name: "Case ID",
- value: `#${event.caseId}`,
- inline: true
- });
- }
-
- await logChannel.send({ embeds: [embed] });
-
- logger.info(`Moderation action logged: ${event.action} by ${event.executor} on ${event.target} in guild ${guild.id}`);
-
- } catch (error) {
- logger.error("Error logging moderation event:", error);
- }
-}
-
-/**
- * Generate a unique case ID for moderation actions
- * @param {import('discord.js').Client} client - The Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} Next case number
- */
-export async function generateCaseId(client, guildId) {
- try {
- const caseKey = `moderation_cases_${guildId}`;
- const currentCase = await getFromDb(caseKey, 0);
- const nextCase = currentCase + 1;
- await setInDb(caseKey, nextCase);
- return nextCase;
- } catch (error) {
- logger.error("Error generating case ID:", error);
-return Date.now();
- }
-}
-
-/**
- * Store moderation case in database for audit trail
- * @param {Object} options - The case options
- * @param {string} options.guildId - The guild ID
- * @param {number} options.caseId - The case ID
- * @param {Object} options.caseData - The case data
- * @returns {Promise} Success status
- */
-export async function storeModerationCase({ guildId, caseId, caseData }) {
- try {
- const caseKey = `moderation_case_${guildId}_${caseId}`;
- const caseDataWithTimestamp = {
- ...caseData,
- createdAt: new Date().toISOString(),
- caseId
- };
-
- await setInDb(caseKey, caseDataWithTimestamp);
-
- const caseListKey = `moderation_cases_list_${guildId}`;
- const caseList = await getFromDb(caseListKey, []);
- caseList.push(caseDataWithTimestamp);
-
- if (caseList.length > 1000) {
- caseList.splice(0, caseList.length - 1000);
- }
-
- await setInDb(caseListKey, caseList);
- return true;
- } catch (error) {
- logger.error("Error storing moderation case:", error);
- return false;
- }
-}
-
-/**
- * Get moderation cases for a guild
- * @param {string} guildId - The guild ID
- * @param {Object} filters - Optional filters
- * @returns {Promise} Array of cases
- */
-export async function getModerationCases(guildId, filters = {}) {
- try {
- const { userId, moderatorId, action, limit = 50, offset = 0 } = filters;
-
- const allCases = [];
-
- const caseListKey = `moderation_cases_list_${guildId}`;
- const caseList = await getFromDb(caseListKey, []);
-
- let filteredCases = caseList;
-
- if (userId) {
- filteredCases = filteredCases.filter(case_ => case_.targetUserId === userId);
- }
-
- if (moderatorId) {
- filteredCases = filteredCases.filter(case_ => case_.moderatorId === moderatorId);
- }
-
- if (action) {
- filteredCases = filteredCases.filter(case_ => case_.action === action);
- }
-
- filteredCases.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
-
- return filteredCases.slice(offset, offset + limit);
- } catch (error) {
- logger.error("Error getting moderation cases:", error);
- return [];
- }
-}
-
-/**
- * Enhanced logging function that stores case in database
- * @param {Object} options - The log options
- * @returns {Promise} The generated case ID
- */
-export async function logModerationAction({ client, guild, event }) {
- const caseId = await generateCaseId(client, guild.id);
-
- await storeModerationCase({
- guildId: guild.id,
- caseId,
- caseData: {
- action: event.action,
- target: event.target,
- executor: event.executor,
- reason: event.reason,
- duration: event.duration,
- metadata: event.metadata,
- targetUserId: event.metadata?.userId,
- moderatorId: event.metadata?.moderatorId
- }
- });
-
- await logEvent({
- client,
- guild,
- event: {
- ...event,
- caseId
- }
- });
-
- return caseId;
-}
-
diff --git a/src/utils/permissionGuard.js b/src/utils/permissionGuard.js
deleted file mode 100644
index 55a06961b8..0000000000
--- a/src/utils/permissionGuard.js
+++ /dev/null
@@ -1,169 +0,0 @@
-/**
- * Permission Guard - Centralized permission checking for commands
- * Ensures consistent security checks across all command handlers
- */
-
-import { PermissionFlagsBits, MessageFlags } from 'discord.js';
-import { logger } from './logger.js';
-import { errorEmbed } from './embeds.js';
-
-/**
- * Check if a user has admin permissions in the guild
- * @param {import('discord.js').GuildMember} member - The guild member to check
- * @returns {boolean} True if user is admin
- */
-export function isAdmin(member) {
- if (!member) return false;
- return member.permissions.has(PermissionFlagsBits.Administrator);
-}
-
-/**
- * Check if a user has mod permissions (Admin or ManageGuild)
- * @param {import('discord.js').GuildMember} member - The guild member to check
- * @returns {boolean} True if user has mod permissions
- */
-export function isModerator(member) {
- if (!member) return false;
- return member.permissions.has([
- PermissionFlagsBits.Administrator,
- PermissionFlagsBits.ManageGuild
- ]);
-}
-
-/**
- * Check if a user has specific permission(s)
- * @param {import('discord.js').GuildMember} member - The guild member to check
- * @param {string|string[]} permissions - Permission(s) to check
- * @returns {boolean} True if user has all specified permissions
- */
-export function hasPermission(member, permissions) {
- if (!member) return false;
- return member.permissions.has(permissions);
-}
-
-/**
- * Check if bot has required permissions in a channel
- * @param {import('discord.js').GuildChannel} channel - The channel to check
- * @param {string|string[]} permissions - Required permission(s)
- * @returns {boolean} True if bot has all required permissions
- */
-export function botHasPermission(channel, permissions) {
- if (!channel || !channel.guild) return false;
- const botMember = channel.guild.members.me;
- if (!botMember) return false;
- return channel.permissionsFor(botMember).has(permissions);
-}
-
-/**
- * Guard helper - Check user permissions and respond with error embed if missing
- * @param {import('discord.js').ChatInputCommandInteraction} interaction - The interaction
- * @param {string|string[]} requiredPermissions - Permission(s) required
- * @param {string} [errorMessage] - Custom error message
- * @returns {Promise} True if user has permissions, false otherwise (response already sent)
- */
-export async function checkUserPermissions(
- interaction,
- requiredPermissions,
- errorMessage = 'You do not have permission to use this command.'
-) {
- const member = interaction.member;
-
- if (!member.permissions.has(requiredPermissions)) {
- await interaction.reply({
- embeds: [errorEmbed('Permission Denied', errorMessage)],
- flags: MessageFlags.Ephemeral
- });
-
- logger.warn(
- `[PERMISSION_DENIED] User ${member.id} attempted command ${interaction.commandName} in guild ${interaction.guildId}`
- );
- return false;
- }
-
- return true;
-}
-
-/**
- * Guard helper - Check bot permissions in channel and respond with error if missing
- * @param {import('discord.js').ChatInputCommandInteraction} interaction - The interaction
- * @param {string|string[]} requiredPermissions - Permission(s) required
- * @param {import('discord.js').TextChannel} [channel] - Channel to check (defaults to command channel)
- * @returns {Promise} True if bot has permissions, false otherwise (response already sent)
- */
-export async function checkBotPermissions(
- interaction,
- requiredPermissions,
- channel = null
-) {
- const targetChannel = channel || interaction.channel;
-
- if (!targetChannel || !targetChannel.guild) {
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Could not determine channel.')],
- flags: MessageFlags.Ephemeral
- });
- return false;
- }
-
- const botMember = targetChannel.guild.members.me;
- if (!botMember) {
- await interaction.reply({
- embeds: [errorEmbed('Error', 'Could not find bot member in this guild.')],
- flags: MessageFlags.Ephemeral
- });
- return false;
- }
-
- const permissions = targetChannel.permissionsFor(botMember);
- const missingPerms = [];
-
- const permArray = Array.isArray(requiredPermissions) ? requiredPermissions : [requiredPermissions];
- for (const perm of permArray) {
- if (!permissions.has(perm)) {
- missingPerms.push(perm);
- }
- }
-
- if (missingPerms.length > 0) {
- await interaction.reply({
- embeds: [errorEmbed(
- 'Missing Permissions',
- `I need the following permissions in ${targetChannel}: ${missingPerms.join(', ')}`
- )],
- flags: MessageFlags.Ephemeral
- });
-
- logger.warn(
- `[BOT_PERMISSION_DENIED] Bot missing permissions [${missingPerms.join(', ')}] in channel ${targetChannel.id}`
- );
- return false;
- }
-
- return true;
-}
-
-/**
- * Audit log permission check result
- * @param {string} userId - User ID
- * @param {string} action - Action being performed
- * @param {boolean} allowed - Whether the action was allowed
- * @param {string} [reason] - Reason for deny
- */
-export function auditPermissionCheck(userId, action, allowed, reason = null) {
- if (allowed) {
- logger.debug(`[PERMISSION_AUDIT] User ${userId} authorized for action: ${action}`);
- } else {
- logger.warn(`[PERMISSION_AUDIT] User ${userId} denied for action: ${action} - ${reason || 'insufficient permissions'}`);
- }
-}
-
-export default {
- isAdmin,
- isModerator,
- hasPermission,
- botHasPermission,
- checkUserPermissions,
- checkBotPermissions,
- auditPermissionCheck
-};
-
diff --git a/src/utils/postgresDatabase.js b/src/utils/postgresDatabase.js
deleted file mode 100644
index e09f651017..0000000000
--- a/src/utils/postgresDatabase.js
+++ /dev/null
@@ -1,1114 +0,0 @@
-import pg from 'pg';
-import { pgConfig } from '../config/postgres.js';
-import { logger } from './logger.js';
-
-/**
- * PostgreSQL Database wrapper for Titan Bot
- * Replaces Redis with PostgreSQL for better data persistence and querying
- */
-class PostgreSQLDatabase {
- constructor() {
- this.pool = null;
- this.isConnected = false;
- this.connectionPromise = null;
- }
-
- /**
- * Initialize PostgreSQL connection
- * @returns {Promise} Connection status
- */
- async connect() {
- if (this.connectionPromise) {
- return this.connectionPromise;
- }
-
- this.connectionPromise = this._establishConnection();
- return this.connectionPromise;
- }
-
- async _establishConnection() {
- const retries = Number.isFinite(pgConfig.options.retries) ? pgConfig.options.retries : 0;
- const baseDelay = Number.isFinite(pgConfig.options.backoffBase) ? pgConfig.options.backoffBase : 100;
- const multiplier = Number.isFinite(pgConfig.options.backoffMultiplier) ? pgConfig.options.backoffMultiplier : 2;
- const attempts = Math.max(1, retries + 1);
-
- for (let attempt = 1; attempt <= attempts; attempt += 1) {
- try {
- await new Promise(resolve => setTimeout(resolve, 100));
-
- this.pool = new pg.Pool({
- host: pgConfig.options.host,
- port: pgConfig.options.port,
- database: pgConfig.options.database,
- user: pgConfig.options.user,
- password: pgConfig.options.password,
- ssl: pgConfig.options.ssl,
- max: pgConfig.options.max,
- min: pgConfig.options.min,
- idleTimeoutMillis: pgConfig.options.idleTimeoutMillis,
- connectionTimeoutMillis: pgConfig.options.connectionTimeoutMillis,
- });
-
- const client = await this.pool.connect();
- await client.query('SELECT NOW()');
- client.release();
-
- this.isConnected = true;
- logger.info('✅ PostgreSQL Database initialized successfully');
-
- if (pgConfig.features.autoCreateTables) {
- await this.createTables();
-
- try {
- const columnCheck = await this.pool.query(`
- SELECT column_name
- FROM information_schema.columns
- WHERE table_name = 'guilds' AND column_name = 'counters'
- `);
-
- if (columnCheck.rows.length === 0) {
- await this.pool.query(`
- ALTER TABLE ${pgConfig.tables.guilds}
- ADD COLUMN counters JSONB DEFAULT '[]'
- `);
- logger.info('✅ Added counters column to guilds table');
- }
- } catch (error) {
- logger.warn('Could not add counters column to guilds table:', error.message);
- }
- }
-
- return true;
- } catch (error) {
- if (this.pool) {
- try {
- await this.pool.end();
- } catch (closeError) {
- logger.warn('Failed to close PostgreSQL pool after error:', closeError.message);
- }
- this.pool = null;
- }
-
- const isLastAttempt = attempt >= attempts;
- if (isLastAttempt) {
- logger.error('⌠Failed to initialize PostgreSQL Database:', error);
- this.isConnected = false;
- return false;
- }
-
- logger.warn(`PostgreSQL connection attempt ${attempt} failed: ${error.message}`);
- const backoff = Math.round(baseDelay * Math.pow(multiplier, attempt - 1));
- await new Promise(resolve => setTimeout(resolve, backoff));
- }
- }
-
- this.isConnected = false;
- return false;
- }
-
- /**
- * Check if PostgreSQL is connected
- * @returns {boolean} Connection status
- */
- isAvailable() {
- return this.isConnected && this.pool;
- }
-
- /**
- * Create database tables
- */
- async createTables() {
- const tables = [
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.guilds} (
- id VARCHAR(20) PRIMARY KEY,
- config JSONB DEFAULT '{}',
- counters JSONB DEFAULT '[]',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.users} (
- id VARCHAR(20) PRIMARY KEY,
- username VARCHAR(100),
- discriminator VARCHAR(10),
- avatar VARCHAR(100),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.guild_users} (
- guild_id VARCHAR(20),
- user_id VARCHAR(20),
- joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (guild_id, user_id),
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE,
- FOREIGN KEY (user_id) REFERENCES ${pgConfig.tables.users}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.birthdays} (
- guild_id VARCHAR(20),
- user_id VARCHAR(20),
- month INTEGER NOT NULL,
- day INTEGER NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (guild_id, user_id),
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE,
- FOREIGN KEY (user_id) REFERENCES ${pgConfig.tables.users}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.giveaways} (
- id SERIAL PRIMARY KEY,
- guild_id VARCHAR(20),
- message_id VARCHAR(20) NOT NULL,
- data JSONB NOT NULL,
- ends_at TIMESTAMP,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE,
- UNIQUE(guild_id, message_id)
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.tickets} (
- guild_id VARCHAR(20),
- channel_id VARCHAR(20) PRIMARY KEY,
- data JSONB NOT NULL,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- expires_at TIMESTAMP,
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.afk_status} (
- guild_id VARCHAR(20),
- user_id VARCHAR(20),
- reason TEXT,
- status_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- expires_at TIMESTAMP,
- PRIMARY KEY (guild_id, user_id),
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE,
- FOREIGN KEY (user_id) REFERENCES ${pgConfig.tables.users}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.welcome_configs} (
- guild_id VARCHAR(20) PRIMARY KEY,
- config JSONB NOT NULL DEFAULT '{}',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.leveling_configs} (
- guild_id VARCHAR(20) PRIMARY KEY,
- config JSONB NOT NULL DEFAULT '{}',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.user_levels} (
- guild_id VARCHAR(20),
- user_id VARCHAR(20),
- xp BIGINT DEFAULT 0,
- level INTEGER DEFAULT 0,
- total_xp BIGINT DEFAULT 0,
- last_message TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- rank INTEGER DEFAULT 0,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (guild_id, user_id),
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE,
- FOREIGN KEY (user_id) REFERENCES ${pgConfig.tables.users}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.economy} (
- guild_id VARCHAR(20),
- user_id VARCHAR(20),
- balance BIGINT DEFAULT 0,
- bank BIGINT DEFAULT 0,
- data JSONB DEFAULT '{}',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (guild_id, user_id),
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE,
- FOREIGN KEY (user_id) REFERENCES ${pgConfig.tables.users}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.invite_tracking} (
- guild_id VARCHAR(20),
- inviter_id VARCHAR(20),
- invite_code VARCHAR(20),
- uses INTEGER DEFAULT 0,
- data JSONB DEFAULT '{}',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (guild_id, invite_code),
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.application_roles} (
- guild_id VARCHAR(20),
- role_id VARCHAR(20),
- data JSONB DEFAULT '{}',
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (guild_id, role_id),
- FOREIGN KEY (guild_id) REFERENCES ${pgConfig.tables.guilds}(id) ON DELETE CASCADE
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.temp_data} (
- key VARCHAR(255) PRIMARY KEY,
- value JSONB NOT NULL,
- expires_at TIMESTAMP,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )`,
-
- `CREATE TABLE IF NOT EXISTS ${pgConfig.tables.cache_data} (
- key VARCHAR(255) PRIMARY KEY,
- value JSONB NOT NULL,
- expires_at TIMESTAMP,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )`
- ];
-
- for (const table of tables) {
- try {
- await this.pool.query(table);
- } catch (error) {
- logger.error('Error creating table:', error);
- }
- }
-
- logger.info('✅ Database tables created/verified');
-
- await this.createIndexes();
- await this.createAuditTriggers();
- }
-
- /**
- * Create performance indexes for better query performance
- */
- async createIndexes() {
- const indexes = [
- `CREATE INDEX IF NOT EXISTS idx_guild_users_guild_id ON ${pgConfig.tables.guild_users}(guild_id)`,
- `CREATE INDEX IF NOT EXISTS idx_guild_users_user_id ON ${pgConfig.tables.guild_users}(user_id)`,
- `CREATE INDEX IF NOT EXISTS idx_birthdays_guild_id ON ${pgConfig.tables.birthdays}(guild_id)`,
- `CREATE INDEX IF NOT EXISTS idx_birthdays_month_day ON ${pgConfig.tables.birthdays}(month, day)`,
- `CREATE INDEX IF NOT EXISTS idx_giveaways_guild_id ON ${pgConfig.tables.giveaways}(guild_id)`,
- `CREATE INDEX IF NOT EXISTS idx_giveaways_ends_at ON ${pgConfig.tables.giveaways}(ends_at)`,
- `CREATE INDEX IF NOT EXISTS idx_tickets_guild_id ON ${pgConfig.tables.tickets}(guild_id)`,
- `CREATE INDEX IF NOT EXISTS idx_tickets_expires_at ON ${pgConfig.tables.tickets}(expires_at)`,
- `CREATE INDEX IF NOT EXISTS idx_afk_status_guild_id ON ${pgConfig.tables.afk_status}(guild_id)`,
- `CREATE INDEX IF NOT EXISTS idx_afk_status_expires_at ON ${pgConfig.tables.afk_status}(expires_at)`,
- `CREATE INDEX IF NOT EXISTS idx_user_levels_guild_id ON ${pgConfig.tables.user_levels}(guild_id)`,
- `CREATE INDEX IF NOT EXISTS idx_user_levels_xp ON ${pgConfig.tables.user_levels}(xp)`,
- `CREATE INDEX IF NOT EXISTS idx_economy_guild_id ON ${pgConfig.tables.economy}(guild_id)`,
- `CREATE INDEX IF NOT EXISTS idx_temp_data_expires_at ON ${pgConfig.tables.temp_data}(expires_at)`,
- `CREATE INDEX IF NOT EXISTS idx_cache_data_expires_at ON ${pgConfig.tables.cache_data}(expires_at)`
- ];
-
- for (const index of indexes) {
- try {
- await this.pool.query(index);
- } catch (error) {
- logger.warn('Error creating index:', error.message);
- }
- }
-
- logger.info('✅ Performance indexes created/verified');
- }
-
- /**
- * Create audit triggers for automatic updated_at timestamps
- */
- async createAuditTriggers() {
- try {
- const functionQuery = `
- CREATE OR REPLACE FUNCTION update_updated_at_column()
- RETURNS TRIGGER AS $$
- BEGIN
- NEW.updated_at = CURRENT_TIMESTAMP;
- RETURN NEW;
- END;
- $$ language 'plpgsql';
- `;
-
- await this.pool.query(functionQuery);
-
- const triggers = [
- `CREATE TRIGGER update_guilds_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.guilds}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_users_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.users}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_welcome_configs_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.welcome_configs}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_leveling_configs_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.leveling_configs}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_user_levels_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.user_levels}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_economy_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.economy}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_application_roles_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.application_roles}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_invite_tracking_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.invite_tracking}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_guild_users_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.guild_users}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_birthdays_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.birthdays}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_giveaways_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.giveaways}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_tickets_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.tickets}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`,
-
- `CREATE TRIGGER update_afk_status_updated_at
- BEFORE UPDATE ON ${pgConfig.tables.afk_status}
- FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();`
- ];
-
- for (const trigger of triggers) {
- try {
- await this.pool.query(trigger);
- } catch (error) {
- logger.warn('Error creating trigger:', error.message);
- }
- }
-
- logger.info('✅ Audit triggers created/verified');
- } catch (error) {
- logger.warn('Error creating audit triggers:', error.message);
- }
- }
-
- /**
- * Get a value from PostgreSQL (Redis compatibility layer)
- * @param {string} key - The key to retrieve
- * @param {any} defaultValue - Default value if key doesn't exist
- * @returns {Promise} The value or default
- */
- async get(key, defaultValue = null) {
- try {
- if (!this.isAvailable()) {
- logger.warn('PostgreSQL not available, returning default value');
- return defaultValue;
- }
-
- const parsedKey = this.parseKey(key);
-
- if (parsedKey.type === 'temp') {
- const result = await this.pool.query(
- `SELECT value FROM ${pgConfig.tables.temp_data} WHERE key = $1 AND (expires_at IS NULL OR expires_at > NOW())`,
- [parsedKey.fullKey]
- );
- return result.rows.length > 0 ? result.rows[0].value : defaultValue;
- }
-
- if (parsedKey.type === 'cache') {
- const result = await this.pool.query(
- `SELECT value FROM ${pgConfig.tables.cache_data} WHERE key = $1 AND (expires_at IS NULL OR expires_at > NOW())`,
- [parsedKey.fullKey]
- );
- return result.rows.length > 0 ? result.rows[0].value : defaultValue;
- }
-
- return await this.getStructuredData(parsedKey, defaultValue);
- } catch (error) {
- logger.error(`Error getting value for key ${key}:`, error);
- return defaultValue;
- }
- }
-
- /**
- * Set a value in PostgreSQL (Redis compatibility layer)
- * @param {string} key - The key to set
- * @param {any} value - The value to store
- * @param {number} ttl - Optional TTL in seconds
- * @returns {Promise} Success status
- */
- async set(key, value, ttl = null) {
- try {
- if (!this.isAvailable()) {
- logger.warn('PostgreSQL not available, cannot set value');
- return false;
- }
-
- const parsedKey = this.parseKey(key);
- const expiresAt = ttl ? new Date(Date.now() + ttl * 1000) : null;
-
- if (parsedKey.type === 'temp') {
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.temp_data} (key, value, expires_at)
- VALUES ($1, $2, $3)
- ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = $3`,
- [parsedKey.fullKey, value, expiresAt]
- );
- return true;
- }
-
- if (parsedKey.type === 'cache') {
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.cache_data} (key, value, expires_at)
- VALUES ($1, $2, $3)
- ON CONFLICT (key) DO UPDATE SET value = $2, expires_at = $3`,
- [parsedKey.fullKey, value, expiresAt]
- );
- return true;
- }
-
- return await this.setStructuredData(parsedKey, value, ttl);
- } catch (error) {
- logger.error(`Error setting value for key ${key}:`, error);
- return false;
- }
- }
-
- /**
- * Delete a key from PostgreSQL
- * @param {string} key - The key to delete
- * @returns {Promise} Success status
- */
- async delete(key) {
- try {
- if (!this.isAvailable()) {
- logger.warn('PostgreSQL not available, cannot delete key');
- return false;
- }
-
- const parsedKey = this.parseKey(key);
-
- if (parsedKey.type === 'temp') {
- await this.pool.query(`DELETE FROM ${pgConfig.tables.temp_data} WHERE key = $1`, [parsedKey.fullKey]);
- return true;
- }
-
- if (parsedKey.type === 'cache') {
- await this.pool.query(`DELETE FROM ${pgConfig.tables.cache_data} WHERE key = $1`, [parsedKey.fullKey]);
- return true;
- }
-
- return await this.deleteStructuredData(parsedKey);
- } catch (error) {
- logger.error(`Error deleting key ${key}:`, error);
- return false;
- }
- }
-
- /**
- * List all keys with a given prefix
- * @param {string} prefix - The prefix to search for
- * @returns {Promise} Array of matching keys
- */
- async list(prefix) {
- try {
- if (!this.isAvailable()) {
- logger.warn('PostgreSQL not available, returning empty list');
- return [];
- }
-
- const keys = [];
-
- const tempResult = await this.pool.query(
- `SELECT key FROM ${pgConfig.tables.temp_data} WHERE key LIKE $1 AND (expires_at IS NULL OR expires_at > NOW())`,
- [`${prefix}%`]
- );
- keys.push(...tempResult.rows.map(row => row.key));
-
- const cacheResult = await this.pool.query(
- `SELECT key FROM ${pgConfig.tables.cache_data} WHERE key LIKE $1 AND (expires_at IS NULL OR expires_at > NOW())`,
- [`${prefix}%`]
- );
- keys.push(...cacheResult.rows.map(row => row.key));
-
- return keys;
- } catch (error) {
- logger.error(`Error listing keys with prefix ${prefix}:`, error);
- return [];
- }
- }
-
- /**
- * Check if a key exists
- * @param {string} key - The key to check
- * @returns {Promise} Whether the key exists
- */
- async exists(key) {
- try {
- if (!this.isAvailable()) {
- return false;
- }
-
- const value = await this.get(key);
- return value !== null;
- } catch (error) {
- logger.error(`Error checking if key exists ${key}:`, error);
- return false;
- }
- }
-
- /**
- * Increment a numeric value
- * @param {string} key - The key to increment
- * @param {number} amount - Amount to increment by (default: 1)
- * @returns {Promise} New value
- */
- async increment(key, amount = 1) {
- try {
- if (!this.isAvailable()) {
- return amount;
- }
-
- const currentValue = await this.get(key, 0);
- const newValue = (typeof currentValue === 'number' ? currentValue : 0) + amount;
- await this.set(key, newValue);
- return newValue;
- } catch (error) {
- logger.error(`Error incrementing key ${key}:`, error);
- return amount;
- }
- }
-
- /**
- * Decrement a numeric value
- * @param {string} key - The key to decrement
- * @param {number} amount - Amount to decrement by (default: 1)
- * @returns {Promise} New value
- */
- async decrement(key, amount = 1) {
- try {
- if (!this.isAvailable()) {
- return -amount;
- }
-
- const currentValue = await this.get(key, 0);
- const newValue = (typeof currentValue === 'number' ? currentValue : 0) - amount;
- await this.set(key, newValue);
- return newValue;
- } catch (error) {
- logger.error(`Error decrementing key ${key}:`, error);
- return -amount;
- }
- }
-
- /**
- * Parse a Redis-style key to determine type and components
- * @param {string} key - The key to parse
- * @returns {Object} Parsed key information
- */
- parseKey(key) {
- if (key.startsWith('temp:')) {
- return { type: 'temp', fullKey: key };
- }
- if (key.startsWith('cache:')) {
- return { type: 'cache', fullKey: key };
- }
-
- const parts = key.split(':');
-
- if (parts[0] === 'guild') {
- if (parts[2] === 'config') {
- return { type: 'guild_config', guildId: parts[1], fullKey: key };
- }
- if (parts[2] === 'birthdays') {
- return { type: 'guild_birthdays', guildId: parts[1], fullKey: key };
- }
- if (parts[2] === 'giveaways') {
- return { type: 'guild_giveaways', guildId: parts[1], fullKey: key };
- }
- if (parts[2] === 'welcome') {
- return { type: 'welcome_config', guildId: parts[1], fullKey: key };
- }
- if (parts[2] === 'leveling') {
- if (parts[3] === 'config') {
- return { type: 'leveling_config', guildId: parts[1], fullKey: key };
- }
- if (parts[3] === 'users') {
- return { type: 'user_level', guildId: parts[1], userId: parts[4], fullKey: key };
- }
- return { type: 'leveling_data', guildId: parts[1], fullKey: key };
- }
- if (parts[2] === 'economy' && parts[3]) {
- return { type: 'economy', guildId: parts[1], userId: parts[3], fullKey: key };
- }
- if (parts[2] === 'afk' && parts[3]) {
- return { type: 'afk_status', guildId: parts[1], userId: parts[3], fullKey: key };
- }
- if (parts[2] === 'ticket' && parts[3]) {
- return { type: 'ticket', guildId: parts[1], channelId: parts[3], fullKey: key };
- }
- }
-
- if (parts[0] === 'counters' && parts[1]) {
- return { type: 'counters', guildId: parts[1], fullKey: key };
- }
-
- return { type: 'temp', fullKey: key };
- }
-
- /**
- * Get structured data from appropriate table
- * @param {Object} parsedKey - Parsed key information
- * @param {any} defaultValue - Default value
- * @returns {Promise} The data
- */
- async getStructuredData(parsedKey, defaultValue) {
- try {
- switch (parsedKey.type) {
- case 'guild_config':
- const guildResult = await this.pool.query(
- `SELECT config FROM ${pgConfig.tables.guilds} WHERE id = $1`,
- [parsedKey.guildId]
- );
- return guildResult.rows.length > 0 ? guildResult.rows[0].config : defaultValue;
-
- case 'guild_birthdays':
- const birthdayResult = await this.pool.query(
- `SELECT user_id, month, day FROM ${pgConfig.tables.birthdays} WHERE guild_id = $1`,
- [parsedKey.guildId]
- );
- const birthdays = {};
- birthdayResult.rows.forEach(row => {
- birthdays[row.user_id] = { month: row.month, day: row.day };
- });
- return birthdays;
-
- case 'guild_giveaways':
- const giveawayResult = await this.pool.query(
- `SELECT data FROM ${pgConfig.tables.giveaways} WHERE guild_id = $1`,
- [parsedKey.guildId]
- );
- return giveawayResult.rows.map(row => row.data);
-
- case 'welcome_config':
- const welcomeResult = await this.pool.query(
- `SELECT config FROM ${pgConfig.tables.welcome_configs} WHERE guild_id = $1`,
- [parsedKey.guildId]
- );
- return welcomeResult.rows.length > 0 ? welcomeResult.rows[0].config : defaultValue;
-
- case 'leveling_config':
- const levelingConfigResult = await this.pool.query(
- `SELECT config FROM ${pgConfig.tables.leveling_configs} WHERE guild_id = $1`,
- [parsedKey.guildId]
- );
- return levelingConfigResult.rows.length > 0 ? levelingConfigResult.rows[0].config : defaultValue;
-
- case 'user_level':
- const userLevelResult = await this.pool.query(
- `SELECT xp, level, total_xp, last_message, rank FROM ${pgConfig.tables.user_levels} WHERE guild_id = $1 AND user_id = $2`,
- [parsedKey.guildId, parsedKey.userId]
- );
- return userLevelResult.rows.length > 0 ? userLevelResult.rows[0] : defaultValue;
-
- case 'economy':
- const economyResult = await this.pool.query(
- `SELECT balance, bank, data FROM ${pgConfig.tables.economy} WHERE guild_id = $1 AND user_id = $2`,
- [parsedKey.guildId, parsedKey.userId]
- );
- return economyResult.rows.length > 0 ? economyResult.rows[0] : defaultValue;
-
- case 'afk_status':
- const afkResult = await this.pool.query(
- `SELECT reason, status_at, expires_at FROM ${pgConfig.tables.afk_status} WHERE guild_id = $1 AND user_id = $2`,
- [parsedKey.guildId, parsedKey.userId]
- );
- return afkResult.rows.length > 0 ? afkResult.rows[0] : defaultValue;
-
- case 'ticket':
- const ticketResult = await this.pool.query(
- `SELECT data FROM ${pgConfig.tables.tickets} WHERE guild_id = $1 AND channel_id = $2`,
- [parsedKey.guildId, parsedKey.channelId]
- );
- return ticketResult.rows.length > 0 ? ticketResult.rows[0].data : defaultValue;
-
- case 'counters':
- const counterResult = await this.pool.query(
- `SELECT counters FROM ${pgConfig.tables.guilds} WHERE id = $1`,
- [parsedKey.guildId]
- );
- return counterResult.rows.length > 0 ? counterResult.rows[0].counters : defaultValue;
-
- default:
- return defaultValue;
- }
- } catch (error) {
- logger.error(`Error getting structured data for ${parsedKey.fullKey}:`, error);
- return defaultValue;
- }
- }
-
- /**
- * Set structured data to appropriate table
- * @param {Object} parsedKey - Parsed key information
- * @param {any} value - Value to set
- * @param {number} ttl - Optional TTL
- * @returns {Promise} Success status
- */
- async setStructuredData(parsedKey, value, ttl) {
- try {
- switch (parsedKey.type) {
- case 'guild_config':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, config, updated_at)
- VALUES ($1, $2, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO UPDATE SET config = $2, updated_at = CURRENT_TIMESTAMP`,
- [parsedKey.guildId, value]
- );
- return true;
-
- case 'guild_birthdays':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.guildId]
- );
-
- await this.pool.query(`DELETE FROM ${pgConfig.tables.birthdays} WHERE guild_id = $1`, [parsedKey.guildId]);
-
- for (const [userId, birthday] of Object.entries(value)) {
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.users} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [userId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.birthdays} (guild_id, user_id, month, day)
- VALUES ($1, $2, $3, $4)`,
- [parsedKey.guildId, userId, birthday.month, birthday.day]
- );
- }
- return true;
-
- case 'guild_giveaways':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.guildId]
- );
-
- await this.pool.query(`DELETE FROM ${pgConfig.tables.giveaways} WHERE guild_id = $1`, [parsedKey.guildId]);
-
- for (const giveaway of value) {
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.giveaways} (guild_id, message_id, data, ends_at)
- VALUES ($1, $2, $3, $4)`,
- [parsedKey.guildId, giveaway.messageId, giveaway, giveaway.endsAt ? new Date(giveaway.endsAt) : null]
- );
- }
- return true;
-
- case 'welcome_config':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.guildId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.welcome_configs} (guild_id, config, updated_at)
- VALUES ($1, $2, CURRENT_TIMESTAMP)
- ON CONFLICT (guild_id) DO UPDATE SET config = $2, updated_at = CURRENT_TIMESTAMP`,
- [parsedKey.guildId, value]
- );
- return true;
-
- case 'leveling_config':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.guildId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.leveling_configs} (guild_id, config, updated_at)
- VALUES ($1, $2, CURRENT_TIMESTAMP)
- ON CONFLICT (guild_id) DO UPDATE SET config = $2, updated_at = CURRENT_TIMESTAMP`,
- [parsedKey.guildId, value]
- );
- return true;
-
- case 'user_level':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.guildId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.users} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.userId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.user_levels} (guild_id, user_id, xp, level, total_xp, last_message, rank, updated_at)
- VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP)
- ON CONFLICT (guild_id, user_id) DO UPDATE SET
- xp = $3, level = $4, total_xp = $5, last_message = $6, rank = $7, updated_at = CURRENT_TIMESTAMP`,
- [parsedKey.guildId, parsedKey.userId, value.xp || 0, value.level || 0, value.totalXp || 0, value.lastMessage || new Date(), value.rank || 0]
- );
- return true;
-
- case 'economy':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.guildId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.users} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.userId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.economy} (guild_id, user_id, balance, bank, data, updated_at)
- VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
- ON CONFLICT (guild_id, user_id) DO UPDATE SET
- balance = $3, bank = $4, data = $5, updated_at = CURRENT_TIMESTAMP`,
- [parsedKey.guildId, parsedKey.userId, value.balance || 0, value.bank || 0, value.data || {}]
- );
- return true;
-
- case 'afk_status':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.guildId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.users} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.userId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.afk_status} (guild_id, user_id, reason, expires_at)
- VALUES ($1, $2, $3, $4)
- ON CONFLICT (guild_id, user_id) DO UPDATE SET
- reason = $3, expires_at = $4, status_at = CURRENT_TIMESTAMP`,
- [parsedKey.guildId, parsedKey.userId, value.reason, value.expiresAt ? new Date(value.expiresAt) : null]
- );
- return true;
-
- case 'ticket':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.guildId]
- );
-
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.tickets} (guild_id, channel_id, data, expires_at)
- VALUES ($1, $2, $3, $4)
- ON CONFLICT (channel_id) DO UPDATE SET
- data = $3, expires_at = $4, updated_at = CURRENT_TIMESTAMP`,
- [parsedKey.guildId, parsedKey.channelId, value, ttl ? new Date(Date.now() + ttl * 1000) : null]
- );
- return true;
-
- case 'counters':
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, created_at)
- VALUES ($1, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO NOTHING`,
- [parsedKey.guildId]
- );
-
- const columnCheck = await this.pool.query(`
- SELECT column_name
- FROM information_schema.columns
- WHERE table_name = '${pgConfig.tables.guilds}' AND column_name = 'counters'
- `);
-
- if (columnCheck.rows.length === 0) {
- logger.warn('Counters column does not exist, attempting to add it...');
- try {
- await this.pool.query(`
- ALTER TABLE ${pgConfig.tables.guilds}
- ADD COLUMN counters JSONB DEFAULT '[]'
- `);
- logger.info('✅ Added counters column to guilds table');
- } catch (alterError) {
- logger.error('Failed to add counters column:', alterError);
- throw new Error(`Counters column missing and could not be created: ${alterError.message}`);
- }
- }
-
- console.log('Saving counter data to PostgreSQL:', JSON.stringify(value, null, 2));
- console.log('Value type:', typeof value);
- console.log('Is value an array?:', Array.isArray(value));
-
- try {
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, counters, updated_at)
- VALUES ($1, $2, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO UPDATE SET counters = $2, updated_at = CURRENT_TIMESTAMP`,
- [parsedKey.guildId, value]
- );
- } catch (queryError) {
- console.error('PostgreSQL query error details:', queryError);
- console.error('Error message:', queryError.message);
- console.error('Error detail:', queryError.detail);
- console.error('Error hint:', queryError.hint);
-
- try {
- const jsonString = JSON.stringify(value);
- console.log('Attempting to save as stringified JSON:', jsonString);
- await this.pool.query(
- `INSERT INTO ${pgConfig.tables.guilds} (id, counters, updated_at)
- VALUES ($1, $2::jsonb, CURRENT_TIMESTAMP)
- ON CONFLICT (id) DO UPDATE SET counters = $2::jsonb, updated_at = CURRENT_TIMESTAMP`,
- [parsedKey.guildId, jsonString]
- );
- console.log('Successfully saved as stringified JSON');
- } catch (fallbackError) {
- console.error('Fallback approach also failed:', fallbackError);
- throw queryError;
- }
- }
- return true;
-
- default:
- return false;
- }
- } catch (error) {
- logger.error(`Error setting structured data for ${parsedKey.fullKey}:`, error);
- return false;
- }
- }
-
- /**
- * Delete structured data from appropriate table
- * @param {Object} parsedKey - Parsed key information
- * @returns {Promise} Success status
- */
- async deleteStructuredData(parsedKey) {
- try {
- switch (parsedKey.type) {
- case 'guild_config':
- await this.pool.query(`DELETE FROM ${pgConfig.tables.guilds} WHERE id = $1`, [parsedKey.guildId]);
- return true;
-
- case 'guild_birthdays':
- await this.pool.query(`DELETE FROM ${pgConfig.tables.birthdays} WHERE guild_id = $1`, [parsedKey.guildId]);
- return true;
-
- case 'guild_giveaways':
- await this.pool.query(`DELETE FROM ${pgConfig.tables.giveaways} WHERE guild_id = $1`, [parsedKey.guildId]);
- return true;
-
- case 'welcome_config':
- await this.pool.query(`DELETE FROM ${pgConfig.tables.welcome_configs} WHERE guild_id = $1`, [parsedKey.guildId]);
- return true;
-
- case 'leveling_config':
- await this.pool.query(`DELETE FROM ${pgConfig.tables.leveling_configs} WHERE guild_id = $1`, [parsedKey.guildId]);
- return true;
-
- case 'user_level':
- await this.pool.query(`DELETE FROM ${pgConfig.tables.user_levels} WHERE guild_id = $1 AND user_id = $2`, [parsedKey.guildId, parsedKey.userId]);
- return true;
-
- case 'economy':
- await this.pool.query(`DELETE FROM ${pgConfig.tables.economy} WHERE guild_id = $1 AND user_id = $2`, [parsedKey.guildId, parsedKey.userId]);
- return true;
-
- case 'afk_status':
- await this.pool.query(`DELETE FROM ${pgConfig.tables.afk_status} WHERE guild_id = $1 AND user_id = $2`, [parsedKey.guildId, parsedKey.userId]);
- return true;
-
- case 'ticket':
- await this.pool.query(`DELETE FROM ${pgConfig.tables.tickets} WHERE guild_id = $1 AND channel_id = $2`, [parsedKey.guildId, parsedKey.channelId]);
- return true;
-
- default:
- return false;
- }
- } catch (error) {
- logger.error(`Error deleting structured data for ${parsedKey.fullKey}:`, error);
- return false;
- }
- }
-
- /**
- * Close PostgreSQL connection
- */
- async disconnect() {
- try {
- if (this.pool) {
- await this.pool.end();
- logger.info('PostgreSQL connection closed');
- }
- } catch (error) {
- logger.error('Error closing PostgreSQL connection:', error);
- }
- }
-
- /**
- * Get PostgreSQL info for debugging
- * @returns {Promise} PostgreSQL server info
- */
- async getInfo() {
- try {
- if (!this.isAvailable()) {
- return null;
- }
-
- const result = await this.pool.query('SELECT version()');
- return {
- version: result.rows[0].version,
- connected: this.isConnected,
- poolSize: this.pool.totalCount,
- idleCount: this.pool.idleCount,
- waitingCount: this.pool.waitingCount
- };
- } catch (error) {
- logger.error('Error getting PostgreSQL info:', error);
- return null;
- }
- }
-}
-
-const pgDb = new PostgreSQLDatabase();
-
-export { PostgreSQLDatabase, pgDb };
-
diff --git a/src/utils/schemas.js b/src/utils/schemas.js
deleted file mode 100644
index 352563fbdb..0000000000
--- a/src/utils/schemas.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import { z } from 'zod';
-
-const LogIgnoreSchema = z
- .object({
- users: z.array(z.string()).default([]),
- channels: z.array(z.string()).default([])
- })
- .default({ users: [], channels: [] });
-
-export const GuildConfigSchema = z
- .object({
- prefix: z.string().optional(),
- modRole: z.string().nullable().optional(),
- adminRole: z.string().nullable().optional(),
- logChannelId: z.string().nullable().optional(),
- welcomeChannel: z.string().nullable().optional(),
- welcomeMessage: z.string().optional(),
- autoRole: z.string().nullable().optional(),
- dmOnClose: z.boolean().optional(),
- reportChannelId: z.string().nullable().optional(),
- birthdayChannelId: z.string().nullable().optional(),
- premiumRoleId: z.string().nullable().optional(),
- logIgnore: LogIgnoreSchema.optional(),
- enabledCommands: z.record(z.boolean()).optional()
- })
- .passthrough();
-
-export const EconomyDataSchema = z
- .object({
- wallet: z.number().nonnegative().default(0),
- bank: z.number().nonnegative().default(0),
- bankLevel: z.number().int().nonnegative().default(0),
- dailyStreak: z.number().int().nonnegative().default(0),
- lastDaily: z.number().int().nonnegative().default(0),
- lastWeekly: z.number().int().nonnegative().default(0),
- lastWork: z.number().int().nonnegative().default(0),
- lastCrime: z.number().int().nonnegative().default(0),
- lastRob: z.number().int().nonnegative().default(0),
- lastDeposit: z.number().int().nonnegative().default(0),
- lastWithdraw: z.number().int().nonnegative().default(0),
- xp: z.number().int().nonnegative().default(0),
- level: z.number().int().nonnegative().default(1),
- inventory: z.record(z.any()).default({}),
- cooldowns: z.record(z.number().int().nonnegative()).default({})
- })
- .passthrough();
-
-export function normalizeGuildConfig(raw, defaults = {}) {
- const base = typeof raw === 'object' && raw !== null ? raw : {};
- const merged = { ...defaults, ...base };
- const parsed = GuildConfigSchema.safeParse(merged);
- return parsed.success ? parsed.data : { ...defaults, ...base };
-}
-
-export function normalizeEconomyData(raw, defaults = {}) {
- const base = typeof raw === 'object' && raw !== null ? raw : {};
- const merged = { ...defaults, ...base };
- const parsed = EconomyDataSchema.safeParse(merged);
- return parsed.success ? parsed.data : { ...defaults, ...base };
-}
-
diff --git a/src/utils/ticketLogging.js b/src/utils/ticketLogging.js
deleted file mode 100644
index fb9e99e2f5..0000000000
--- a/src/utils/ticketLogging.js
+++ /dev/null
@@ -1,296 +0,0 @@
-import { EmbedBuilder, ChannelType } from 'discord.js';
-import { getGuildConfig } from '../services/guildConfig.js';
-import { logger } from './logger.js';
-
-/**
- * Comprehensive ticket logging system with separate channels for different events
- * @param {Object} options - The log options
- * @param {import('discord.js').Client} options.client - The Discord client
- * @param {string} options.guildId - The guild ID
- * @param {Object} options.event - The ticket event details
- * @param {string} options.event.type - The type of ticket event (open, close, claim, unclaim, transcript, priority, etc.)
- * @param {string} [options.event.ticketId] - The ticket ID/channel
- * @param {string} [options.event.ticketNumber] - The ticket number
- * @param {string} [options.event.userId] - The user who created/owns the ticket
- * @param {string} [options.event.executorId] - The user who performed the action
- * @param {string} [options.event.reason] - The reason for the action
- * @param {string} [options.event.priority] - The ticket priority
- * @param {Object} [options.event.metadata] - Additional metadata
- * @param {import('discord.js').AttachmentBuilder[]} [options.event.attachments] - File attachments (for transcripts)
- * @returns {Promise}
- */
-export async function logTicketEvent({ client, guildId, event }) {
- try {
- const guild = client.guilds.cache.get(guildId) || await client.guilds.fetch(guildId).catch(() => null);
- if (!guild) {
- logger.warn(`logTicketEvent invoked without valid guild: ${guildId}`);
- return;
- }
-
- const config = await getGuildConfig(client, guildId);
-
- const logChannelId = getLogChannelForEventType(config, event.type);
- if (!logChannelId) {
- return;
- }
-
- const channel = guild.channels.cache.get(logChannelId) || await guild.channels.fetch(logChannelId).catch(() => null);
- if (!channel) {
- logger.warn(`Ticket log channel not found: ${logChannelId} for event type: ${event.type}`);
- return;
- }
-
- const permissions = channel.permissionsFor(guild.members.me);
- if (!permissions.has(['SendMessages', 'EmbedLinks'])) {
- logger.warn(`Missing permissions in ticket log channel: ${logChannelId}`);
- return;
- }
-
- const embed = await createTicketLogEmbed(guild, event);
-
- const messageOptions = { embeds: [embed] };
-
- if (event.attachments && event.attachments.length > 0) {
- messageOptions.files = event.attachments;
- }
-
- await channel.send(messageOptions);
- logger.info(`Ticket event logged: ${event.type} in guild ${guildId}`);
-
- } catch (error) {
- logger.error('Error logging ticket event:', error);
- }
-}
-
-/**
- * Get the appropriate log channel ID for a specific event type
- * @param {Object} config - Guild configuration
- * @param {string} eventType - The event type
- * @returns {string|null} Channel ID or null if logging disabled
- */
-function getLogChannelForEventType(config, eventType) {
- const ticketLogging = config.ticketLogging || {};
-
- switch (eventType) {
- case 'transcript':
- return ticketLogging.transcriptChannelId || config.logChannelId;
-
- case 'open':
- case 'close':
- case 'delete':
- return ticketLogging.lifecycleChannelId || config.logChannelId;
-
- case 'claim':
- case 'unclaim':
- case 'priority':
- return ticketLogging.lifecycleChannelId || config.logChannelId;
-
- default:
- return config.logChannelId;
- }
-}
-
-/**
- * Create a log embed for ticket events
- * @param {import('discord.js').Guild} guild - The guild
- * @param {Object} event - The event details
- * @returns {Promise}
- */
-async function createTicketLogEmbed(guild, event) {
- const embed = new EmbedBuilder();
-
- const eventColors = {
-open: 0x2ecc71,
-close: 0xe74c3c,
-delete: 0x8b0000,
-claim: 0x3498db,
-unclaim: 0xf39c12,
-priority: 0x9b59b6,
-transcript: 0x1abc9c
- };
-
- embed.setColor(eventColors[event.type] || 0x95a5a6);
-
- const eventInfo = getEventDisplayInfo(event);
- embed.setTitle(eventInfo.title);
- embed.setDescription(eventInfo.description);
-
- embed.setTimestamp();
-
- if (event.ticketId || event.ticketNumber) {
- embed.setFooter({
- text: `Ticket ID: ${event.ticketNumber || event.ticketId || 'Unknown'}`
- });
- }
-
- const fields = [];
-
- if (event.userId) {
- try {
- const user = await guild.client.users.fetch(event.userId).catch(() => null);
- if (user) {
- fields.push({
- name: '👤 Ticket User',
- value: `${user.tag} (${event.userId})`,
- inline: true
- });
- }
- } catch (error) {
- fields.push({
- name: '👤 Ticket User',
- value: `<@${event.userId}> (${event.userId})`,
- inline: true
- });
- }
- }
-
- if (event.executorId) {
- try {
- const executor = await guild.client.users.fetch(event.executorId).catch(() => null);
- if (executor) {
- fields.push({
- name: '🔨 Executed By',
- value: `${executor.tag} (${event.executorId})`,
- inline: true
- });
- }
- } catch (error) {
- fields.push({
- name: '🔨 Executed By',
- value: `<@${event.executorId}> (${event.executorId})`,
- inline: true
- });
- }
- }
-
- if (event.reason) {
- fields.push({
- name: '📠Reason',
- value: event.reason,
- inline: false
- });
- }
-
- if (event.priority) {
- const priorityEmojis = {
- none: '⚪',
- low: '🔵',
- medium: '🟢',
- high: '🟡',
- urgent: '🔴'
- };
-
- fields.push({
- name: '🎯 Priority',
- value: `${priorityEmojis[event.priority] || '⚪'} ${event.priority.charAt(0).toUpperCase() + event.priority.slice(1)}`,
- inline: true
- });
- }
-
- if (event.metadata) {
- Object.entries(event.metadata).forEach(([key, value]) => {
- if (value !== undefined && value !== null) {
- fields.push({
- name: `📊 ${key.charAt(0).toUpperCase() + key.slice(1)}`,
- value: String(value),
- inline: true
- });
- }
- });
- }
-
- embed.addFields(fields);
-
- return embed;
-}
-
-/**
- * Get display information for different event types
- * @param {Object} event - The event details
- * @returns {Object} { title: string, description: string }
- */
-function getEventDisplayInfo(event) {
- const ticketRef = event.ticketNumber ? `#${event.ticketNumber}` : event.ticketId ? `<#${event.ticketId}>` : 'Unknown';
-
- const eventMessages = {
- open: {
- title: '🎫 Ticket Opened',
- description: `A new ticket has been created: ${ticketRef}`
- },
- close: {
- title: '🔒 Ticket Closed',
- description: `Ticket ${ticketRef} has been closed`
- },
- delete: {
- title: 'ðŸ—‘ï¸ Ticket Deleted',
- description: `Ticket ${ticketRef} has been permanently deleted`
- },
- claim: {
- title: '🙋 Ticket Claimed',
- description: `Ticket ${ticketRef} has been claimed`
- },
- unclaim: {
- title: '🔓 Ticket Unclaimed',
- description: `Ticket ${ticketRef} has been unclaimed`
- },
- priority: {
- title: '🎯 Priority Updated',
- description: `Priority changed for ticket ${ticketRef}`
- },
- transcript: {
- title: '📜 Transcript Created',
- description: `Transcript generated for ticket ${ticketRef}`
- }
- };
-
- return eventMessages[event.type] || {
- title: '🎫 Ticket Event',
- description: `An event occurred for ticket ${ticketRef}`
- };
-}
-
-/**
- * Get current ticket logging configuration
- * @param {import('discord.js').Client} client - The Discord client
- * @param {string} guildId - The guild ID
- * @returns {Promise} Current configuration
- */
-export async function getTicketLoggingConfig(client, guildId) {
- const config = await getGuildConfig(client, guildId);
- return {
- enabled: !!(config.ticketLogging?.lifecycleChannelId || config.ticketLogging?.transcriptChannelId),
- lifecycleChannelId: config.ticketLogging?.lifecycleChannelId || null,
- transcriptChannelId: config.ticketLogging?.transcriptChannelId || null,
- fallbackChannelId: config.logChannelId || null
- };
-}
-
-/**
- * Validate a channel for ticket logging
- * @param {import('discord.js').GuildChannel} channel - The channel to validate
- * @param {import('discord.js').GuildMember} botMember - The bot's guild member
- * @returns {Object} Validation result
- */
-export function validateLogChannel(channel, botMember) {
- if (!channel || channel.type !== ChannelType.GuildText) {
- return {
- valid: false,
- error: 'Channel must be a text channel.'
- };
- }
-
- const permissions = channel.permissionsFor(botMember);
- const requiredPermissions = ['SendMessages', 'EmbedLinks'];
-
- const missing = requiredPermissions.filter(perm => !permissions.has(perm));
-
- if (missing.length > 0) {
- return {
- valid: false,
- error: `Missing permissions: ${missing.join(', ')}`
- };
- }
-
- return { valid: true };
-}
-
diff --git a/src/utils/tickets.js b/src/utils/tickets.js
deleted file mode 100644
index a5443915cd..0000000000
--- a/src/utils/tickets.js
+++ /dev/null
@@ -1,242 +0,0 @@
-import {
- ActionRowBuilder,
- ButtonBuilder,
- ButtonStyle,
- EmbedBuilder,
- PermissionFlagsBits
-} from 'discord.js';
-import { getColor } from './database.js';
-import { BotConfig } from '../config/bot.js';
-
-/**
- * Generates priority map from BotConfig for ticket priorities.
- * @returns {Object} Priority map with name, color, emoji, and label
- */
-export function getPriorityMap() {
- const priorities = BotConfig.tickets?.priorities || {};
- const map = {};
-
- for (const [key, config] of Object.entries(priorities)) {
- map[key] = {
- name: `${config.emoji} ${config.label.toUpperCase()}`,
- color: config.color,
- emoji: config.emoji,
- label: config.label,
- };
- }
-
- return map;
-}
-
-const PRIORITY_MAP = getPriorityMap();
-
-/**
- * Gets the database key for a ticket
- * @param {string} guildId - The guild ID
- * @param {string} channelId - The ticket channel ID
- * @returns {string} The database key
- */
-export function getTicketKey(guildId, channelId) {
- return `ticket:${guildId}:${channelId}`;
-}
-
-/**
- * Checks if a channel is a ticket channel and returns the owner ID
- * @param {import('discord.js').TextChannel} channel - The channel to check
- * @returns {string|null} The ticket owner ID or null if not a ticket
- */
-export function isTicketChannel(channel) {
- const topic = channel?.topic || '';
- const match = topic.match(/Ticket ID: (\d+)/);
- return match ? match[1] : null;
-}
-
-/**
- * Gets the ticket message from a channel
- * @param {import('discord.js').TextChannel} channel - The ticket channel
- * @returns {Promise} The ticket message or null
- */
-export async function getTicketMessage(channel) {
- try {
- const messages = await channel.messages.fetch({ limit: 1 });
- return messages.first() || null;
- } catch (error) {
- console.error(`Error fetching ticket message for channel ${channel.id}:`, error);
- return null;
- }
-}
-
-/**
- * Gets the claimer ID from a ticket embed
- * @param {import('discord.js').Message} message - The message containing the ticket embed
- * @returns {string|null} The claimer ID or null if not found
- */
-export function getClaimerIdFromEmbed(message) {
- try {
- if (!message.embeds || message.embeds.length === 0) return null;
-
- const embed = message.embeds[0];
- const claimedField = embed.fields?.find(f => f.name === 'Status' && f.value.includes('Claimed by'));
-
- if (!claimedField) return null;
-
- const match = claimedField.value.match(/<@!(\d+)>/);
- return match ? match[1] : null;
- } catch (error) {
- console.error('Error getting claimer ID from embed:', error);
- return null;
- }
-}
-
-/**
- * Gets the status of a ticket
- * @param {import('discord.js').TextChannel} channel - The ticket channel
- * @returns {Promise} Ticket status information
- */
-export async function getTicketStatus(channel) {
- try {
- const message = await getTicketMessage(channel);
- if (!message) return { isOpen: false, isClaimed: false };
-
- const embed = message.embeds?.[0];
- if (!embed) return { isOpen: false, isClaimed: false };
-
- const statusField = embed.fields?.find(f => f.name === 'Status');
- if (!statusField) return { isOpen: true, isClaimed: false };
-
- const isOpen = !statusField.value.includes('Closed');
- const isClaimed = statusField.value.includes('Claimed by');
-
- return { isOpen, isClaimed };
- } catch (error) {
- console.error(`Error getting status for ticket ${channel.id}:`, error);
- return { isOpen: false, isClaimed: false };
- }
-}
-
-/**
- * Updates the ticket message with current status
- * @param {import('discord.js').TextChannel} channel - The ticket channel
- * @param {Object} options - Update options
- * @param {boolean} [options.isClaimed] - Whether the ticket is claimed
- * @param {import('discord.js').User} [options.claimer] - The user who claimed the ticket
- * @param {boolean} [options.isClosed] - Whether the ticket is closed
- * @param {import('discord.js').User} [options.closer] - The user who closed the ticket
- * @param {string} [options.priority] - The ticket priority
- * @returns {Promise} The updated message
- */
-export async function updateTicketMessage(channel, options = {}) {
- const {
- isClaimed = false,
- claimer = null,
- isClosed = false,
- closer = null,
- priority = 'medium'
- } = options;
-
- try {
- const message = await getTicketMessage(channel);
- if (!message) throw new Error('No ticket message found');
-
- const embed = message.embeds[0] || new EmbedBuilder();
- const priorityInfo = PRIORITY_MAP[priority] || PRIORITY_MAP.medium;
-
- let statusText = '\n\n**Status**\n';
-
- if (isClosed) {
- statusText += `🔒 Closed${closer ? ` by ${closer}` : ''}`;
- } else if (isClaimed && claimer) {
- statusText += `🔑 Claimed by ${claimer}`;
- } else {
- statusText += '🟢 Open';
- }
-
- if (priority) {
- statusText += `\n\n**Priority**\n${priorityInfo.emoji} ${priorityInfo.label}`;
- }
-
- const fields = embed.data.fields || [];
- const statusFieldIndex = fields.findIndex(f => f.name === 'Status');
-
- if (statusFieldIndex >= 0) {
- fields[statusFieldIndex].value = statusText;
- } else {
- fields.push({ name: 'Status', value: statusText, inline: false });
- }
-
- embed.setFields(fields);
-
- if (isClosed) {
- embed.setColor(getColor('error'));
- } else if (isClaimed) {
- embed.setColor(getColor('warning'));
- } else {
- embed.setColor(priorityInfo.color || getColor('primary'));
- }
-
- const row = new ActionRowBuilder();
-
- if (!isClosed) {
- row.addComponents(
- new ButtonBuilder()
- .setCustomId('ticket_close')
- .setLabel('Close')
- .setStyle(ButtonStyle.Danger)
- .setEmoji('🔒'),
-
- new ButtonBuilder()
- .setCustomId('ticket_claim')
- .setLabel(isClaimed ? 'Unclaim' : 'Claim')
- .setStyle(isClaimed ? ButtonStyle.Secondary : ButtonStyle.Primary)
- .setEmoji(isClaimed ? '🔓' : '🔑')
- .setDisabled(isClaimed && claimer?.id !== message.author.id)
- );
-
- if (!isClaimed || (claimer && claimer.id === message.author.id)) {
- Object.entries(PRIORITY_MAP).forEach(([key, { emoji }]) => {
- row.addComponents(
- new ButtonBuilder()
- .setCustomId(`ticket_priority_${key}`)
- .setLabel(key.charAt(0).toUpperCase() + key.slice(1))
- .setStyle(ButtonStyle.Secondary)
- .setEmoji(emoji)
- .setDisabled(key === priority)
- );
- });
- }
- } else {
- row.addComponents(
- new ButtonBuilder()
- .setCustomId('ticket_reopen')
- .setLabel('Reopen')
- .setStyle(ButtonStyle.Success)
- .setEmoji('🔓')
- );
- }
-
- return message.edit({
- embeds: [embed],
- components: [row]
- });
- } catch (error) {
- console.error('Error updating ticket message:', error);
- throw error;
- }
-}
-
-/**
- * Gets the promo row for ticket messages
- * @returns {import('discord.js').ActionRowBuilder} The action row with promo button
- */
-export function getPromoRow() {
- const row = new ActionRowBuilder().addComponents(
- new ButtonBuilder()
- .setLabel('Need a bot like this?')
- .setURL('https://discord.gg/your-invite-link')
- .setStyle(ButtonStyle.Link)
- .setEmoji('🤖')
- );
-
- return row;
-}
-
diff --git a/src/utils/welcome.js b/src/utils/welcome.js
deleted file mode 100644
index 770bef50d0..0000000000
--- a/src/utils/welcome.js
+++ /dev/null
@@ -1,305 +0,0 @@
-import {
- ActionRowBuilder,
- ButtonBuilder,
- ButtonStyle,
- EmbedBuilder,
- Events,
- ChannelType,
- PermissionFlagsBits
-} from 'discord.js';
-import { getColor } from './database.js';
-
-const getWelcomeConfigKey = (guildId) => `welcome:${guildId}:config`;
-
-/**
- * Get welcome system configuration for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @returns {Promise} The welcome system configuration
- */
-export async function getWelcomeConfig(client, guildId) {
- try {
- if (!client.db || typeof client.db.get !== 'function') {
- return {};
- }
-
- const key = getWelcomeConfigKey(guildId);
- const rawConfig = await client.db.get(key, {});
-
- const defaultConfig = {
- enabled: false,
- channelId: null,
- message: 'Welcome {user.mention} to {guild.name}!',
- embed: {
- enabled: true,
- title: 'Welcome {user.username}!',
- description: 'Welcome to {guild.name}!',
- color: getColor('primary'),
- thumbnail: true,
- footer: 'Member #{guild.memberCount}'
- },
- autoRole: {
- enabled: false,
- roleId: null
- },
- dmMessage: {
- enabled: false,
- message: 'Thanks for joining {guild.name}!'
- },
- joinLogs: {
- enabled: false,
- channelId: null
- },
- leaveLogs: {
- enabled: false,
- channelId: null
- },
- antiRaid: {
- enabled: false,
- maxJoins: 5,
- timeWindow: 10,
-action: 'kick'
- },
-joinRoles: []
- };
-
- return { ...defaultConfig, ...rawConfig };
- } catch (error) {
- console.error(`Error getting welcome config for guild ${guildId}:`, error);
- return {};
- }
-}
-
-/**
- * Save welcome system configuration for a guild
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @param {Object} config - The configuration to save
- * @returns {Promise} Whether the operation was successful
- */
-export async function saveWelcomeConfig(client, guildId, config) {
- try {
- if (!client.db || typeof client.db.set !== 'function') {
- throw new Error('Database not available');
- }
-
- const key = getWelcomeConfigKey(guildId);
- await client.db.set(key, config);
- return true;
- } catch (error) {
- console.error(`Error saving welcome config for guild ${guildId}:`, error);
- return false;
- }
-}
-
-/**
- * Update specific fields in the welcome config
- * @param {Object} client - The Discord client
- * @param {string} guildId - The ID of the guild
- * @param {Object} updates - The fields to update
- * @returns {Promise} The updated config
- */
-export async function updateWelcomeConfig(client, guildId, updates) {
- try {
- const currentConfig = await getWelcomeConfig(client, guildId);
- const newConfig = { ...currentConfig, ...updates };
-
- await saveWelcomeConfig(client, guildId, newConfig);
- return newConfig;
- } catch (error) {
- console.error(`Error updating welcome config for guild ${guildId}:`, error);
- throw error;
- }
-}
-
-/**
- * Format a welcome message with placeholders
- * @param {string} message - The message template
- * @param {Object} data - The data to replace placeholders with
- * @returns {string} The formatted message
- */
-export function formatWelcomeMessage(message, data) {
- if (!message) return '';
-
- const placeholders = {
- '{user.mention}': data.user?.toString(),
- '{user.tag}': data.user?.tag,
- '{user.username}': data.user?.username,
- '{user.discriminator}': data.user?.discriminator,
- '{user.id}': data.user?.id,
-
- '{guild.name}': data.guild?.name,
- '{guild.id}': data.guild?.id,
- '{guild.memberCount}': data.guild?.memberCount,
-
- '{inviter.mention}': data.inviter?.toString(),
- '{inviter.tag}': data.inviter?.tag,
- '{inviter.username}': data.inviter?.username,
- '{inviter.id}': data.inviter?.id,
- '{invite.code}': data.invite?.code,
- '{invite.uses}': data.invite?.uses
- };
-
- let result = message;
- for (const [key, value] of Object.entries(placeholders)) {
- if (value !== undefined) {
- result = result.replace(new RegExp(key, 'g'), value);
- }
- }
-
- return result;
-}
-
-export const handleGuildMemberAdd = {
- name: Events.GuildMemberAdd,
-
- /**
- * Execute the guild member add event
- * @param {import('discord.js').GuildMember} member - The member that joined
- */
- async execute(member) {
- try {
- const { client, guild } = member;
- const config = await getWelcomeConfig(client, guild.id);
-
- if (!config.enabled) return;
-
- if (config.autoRole?.enabled && config.autoRole.roleId) {
- try {
- const role = await guild.roles.fetch(config.autoRole.roleId);
- if (role) {
- await member.roles.add(role, 'Auto-role on join');
- }
- } catch (error) {
- console.error(`Failed to add auto-role to ${member.id}:`, error);
- }
- }
-
- if (config.channelId) {
- try {
- const channel = await guild.channels.fetch(config.channelId);
- if (channel) {
- const message = formatWelcomeMessage(config.message, {
- user: member.user,
- guild: guild,
- member: member
- });
-
- if (config.embed?.enabled) {
- const embed = new EmbedBuilder()
- .setTitle(formatWelcomeMessage(config.embed.title || 'Welcome {user.username}!', {
- user: member.user,
- guild: guild
- }))
- .setDescription(formatWelcomeMessage(config.embed.description || 'Welcome to {guild.name}!', {
- user: member.user,
- guild: guild
- }))
- .setColor(config.embed.color || getColor('primary'))
- .setFooter({
- text: formatWelcomeMessage(config.embed.footer || 'Member #{guild.memberCount}', {
- user: member.user,
- guild: guild
- })
- })
- .setTimestamp();
-
- if (config.embed.thumbnail) {
- embed.setThumbnail(member.user.displayAvatarURL({ dynamic: true }));
- }
-
- await channel.send({
- content: message,
- embeds: [embed]
- });
- } else {
- await channel.send(message);
- }
- }
- } catch (error) {
- console.error(`Failed to send welcome message for ${member.id}:`, error);
- }
- }
-
- if (config.dmMessage?.enabled && config.dmMessage.message) {
- try {
- await member.send(
- formatWelcomeMessage(config.dmMessage.message, {
- user: member.user,
- guild: guild
- })
- );
- } catch (error) {
- console.error(`Failed to send DM to ${member.id}:`, error);
- }
- }
-
- if (config.joinLogs?.enabled && config.joinLogs.channelId) {
- try {
- const logChannel = await guild.channels.fetch(config.joinLogs.channelId);
- if (logChannel) {
- const embed = new EmbedBuilder()
- .setTitle('Member Joined')
- .setColor(getColor('success'))
- .setThumbnail(member.user.displayAvatarURL({ dynamic: true }))
- .addFields(
- { name: 'User', value: `${member.user.tag} (${member.id})`, inline: true },
- { name: 'Account Created', value: ``, inline: true },
- { name: 'Member Count', value: guild.memberCount.toString(), inline: true }
- )
- .setTimestamp();
-
- await logChannel.send({ embeds: [embed] });
- }
- } catch (error) {
- console.error(`Failed to log join for ${member.id}:`, error);
- }
- }
-
- } catch (error) {
- console.error('Error in guildMemberAdd handler:', error);
- }
- }
-};
-
-export const handleGuildMemberRemove = {
- name: Events.GuildMemberRemove,
-
- /**
- * Execute the guild member remove event
- * @param {import('discord.js').GuildMember} member - The member that left
- */
- async execute(member) {
- try {
- const { client, guild } = member;
- const config = await getWelcomeConfig(client, guild.id);
-
- if (!config.leaveLogs?.enabled || !config.leaveLogs.channelId) return;
-
- try {
- const logChannel = await guild.channels.fetch(config.leaveLogs.channelId);
- if (logChannel) {
- const embed = new EmbedBuilder()
- .setTitle('Member Left')
- .setColor(getColor('error'))
- .setThumbnail(member.user.displayAvatarURL({ dynamic: true }))
- .addFields(
- { name: 'User', value: `${member.user.tag} (${member.id})`, inline: true },
- { name: 'Account Age', value: ``, inline: true },
- { name: 'Member Since', value: member.joinedAt ? `` : 'Unknown', inline: true },
- { name: 'Member Count', value: guild.memberCount.toString(), inline: true }
- )
- .setTimestamp();
-
- await logChannel.send({ embeds: [embed] });
- }
- } catch (error) {
- console.error(`Failed to log leave for ${member.id}:`, error);
- }
-
- } catch (error) {
- console.error('Error in guildMemberRemove handler:', error);
- }
- }
-};
-