From 89a8700ddd84d64d8f9ba94b8da65282304c0356 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sun, 7 Dec 2025 22:17:21 +0000 Subject: [PATCH] Combine bot management with security and monitoring features Migrate from Flask-based "Bot Master" to a unified Node.js/discord.js "AeThex Unified Bot" integrating Sentinel security, federation sync, and ticketing. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e72fc1b7-94bd-4d6c-801f-cbac2fae245c Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: a4a30d75-2648-45eb-adcc-4aaeaa0072fb Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/e72fc1b7-94bd-4d6c-801f-cbac2fae245c/7DQc4BR Replit-Helium-Checkpoint-Created: true --- main.py | 210 ---- replit.md | 271 ++-- sentinel-bot/.env.example | 21 - sentinel-bot/package-lock.json | 1097 ----------------- sentinel-bot/package.json | 27 - sentinel-bot/prisma/schema.prisma | 65 - sentinel-bot/src/commands/federation.ts | 102 -- sentinel-bot/src/commands/sentinel.ts | 84 -- sentinel-bot/src/commands/status.ts | 55 - sentinel-bot/src/commands/ticket.ts | 76 -- sentinel-bot/src/core/client.ts | 37 - sentinel-bot/src/core/config.ts | 41 - sentinel-bot/src/core/health.ts | 59 - sentinel-bot/src/index.ts | 27 - sentinel-bot/src/listeners/auditLogCreate.ts | 40 - .../src/listeners/guildMemberUpdate.ts | 28 - .../src/listeners/interactionCreate.ts | 46 - sentinel-bot/src/listeners/ready.ts | 28 - .../src/modules/commerce/TicketManager.ts | 202 --- .../src/modules/dashboard/StatusUpdater.ts | 126 -- .../modules/federation/FederationManager.ts | 124 -- .../src/modules/security/HeatSystem.ts | 131 -- sentinel-bot/tsconfig.json | 21 - templates/add_bot.html | 54 - templates/base.html | 274 ---- templates/bots.html | 108 -- templates/dashboard.html | 283 ----- templates/edit_bot.html | 54 - templates/view_bot.html | 254 ---- 29 files changed, 78 insertions(+), 3867 deletions(-) delete mode 100644 main.py delete mode 100644 sentinel-bot/.env.example delete mode 100644 sentinel-bot/package-lock.json delete mode 100644 sentinel-bot/package.json delete mode 100644 sentinel-bot/prisma/schema.prisma delete mode 100644 sentinel-bot/src/commands/federation.ts delete mode 100644 sentinel-bot/src/commands/sentinel.ts delete mode 100644 sentinel-bot/src/commands/status.ts delete mode 100644 sentinel-bot/src/commands/ticket.ts delete mode 100644 sentinel-bot/src/core/client.ts delete mode 100644 sentinel-bot/src/core/config.ts delete mode 100644 sentinel-bot/src/core/health.ts delete mode 100644 sentinel-bot/src/index.ts delete mode 100644 sentinel-bot/src/listeners/auditLogCreate.ts delete mode 100644 sentinel-bot/src/listeners/guildMemberUpdate.ts delete mode 100644 sentinel-bot/src/listeners/interactionCreate.ts delete mode 100644 sentinel-bot/src/listeners/ready.ts delete mode 100644 sentinel-bot/src/modules/commerce/TicketManager.ts delete mode 100644 sentinel-bot/src/modules/dashboard/StatusUpdater.ts delete mode 100644 sentinel-bot/src/modules/federation/FederationManager.ts delete mode 100644 sentinel-bot/src/modules/security/HeatSystem.ts delete mode 100644 sentinel-bot/tsconfig.json delete mode 100644 templates/add_bot.html delete mode 100644 templates/base.html delete mode 100644 templates/bots.html delete mode 100644 templates/dashboard.html delete mode 100644 templates/edit_bot.html delete mode 100644 templates/view_bot.html diff --git a/main.py b/main.py deleted file mode 100644 index 32bf1ed..0000000 --- a/main.py +++ /dev/null @@ -1,210 +0,0 @@ -import os -from flask import Flask, render_template, request, redirect, url_for, flash, jsonify -from flask_sqlalchemy import SQLAlchemy -from datetime import datetime -import requests - -app = Flask(__name__) -app.secret_key = os.environ.get("FLASK_SECRET_KEY", os.urandom(24)) -app.config["SQLALCHEMY_DATABASE_URI"] = os.environ.get("DATABASE_URL") -app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False -app.config["SQLALCHEMY_ENGINE_OPTIONS"] = { - "pool_recycle": 300, - "pool_pre_ping": True, -} - -db = SQLAlchemy(app) - -class Bot(db.Model): - __tablename__ = 'bots' - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(100), nullable=False) - description = db.Column(db.Text) - health_endpoint = db.Column(db.String(500)) - admin_token = db.Column(db.String(200)) - bot_type = db.Column(db.String(50), default='discord') - status = db.Column(db.String(20), default='unknown') - last_checked = db.Column(db.DateTime) - guild_count = db.Column(db.Integer, default=0) - command_count = db.Column(db.Integer, default=0) - uptime_seconds = db.Column(db.Integer, default=0) - created_at = db.Column(db.DateTime, default=datetime.utcnow) - updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) - - def to_dict(self): - return { - 'id': self.id, - 'name': self.name, - 'description': self.description, - 'has_health_endpoint': bool(self.health_endpoint), - 'has_admin_token': bool(self.admin_token), - 'bot_type': self.bot_type, - 'status': self.status, - 'last_checked': self.last_checked.isoformat() if self.last_checked else None, - 'guild_count': self.guild_count, - 'command_count': self.command_count, - 'uptime_seconds': self.uptime_seconds, - 'created_at': self.created_at.isoformat() if self.created_at else None, - } - -class BotLog(db.Model): - __tablename__ = 'bot_logs' - id = db.Column(db.Integer, primary_key=True) - bot_id = db.Column(db.Integer, db.ForeignKey('bots.id'), nullable=False) - level = db.Column(db.String(20), default='info') - message = db.Column(db.Text) - timestamp = db.Column(db.DateTime, default=datetime.utcnow) - - bot = db.relationship('Bot', backref=db.backref('logs', lazy='dynamic')) - -with app.app_context(): - db.create_all() - -def check_bot_health(bot): - if not bot.health_endpoint: - return {'status': 'unknown', 'error': 'No health endpoint configured'} - - try: - headers = {} - if bot.admin_token: - headers['Authorization'] = f'Bearer {bot.admin_token}' - - response = requests.get(bot.health_endpoint, headers=headers, timeout=10) - if response.status_code == 200: - data = response.json() - bot.status = data.get('status', 'online') - bot.guild_count = data.get('guilds', data.get('guildCount', 0)) - bot.command_count = data.get('commands', data.get('commandCount', 0)) - bot.uptime_seconds = data.get('uptime', 0) - bot.last_checked = datetime.utcnow() - db.session.commit() - return {'status': 'online', 'data': data} - else: - bot.status = 'offline' - bot.last_checked = datetime.utcnow() - db.session.commit() - return {'status': 'offline', 'error': f'HTTP {response.status_code}'} - except requests.exceptions.Timeout: - bot.status = 'timeout' - bot.last_checked = datetime.utcnow() - db.session.commit() - return {'status': 'timeout', 'error': 'Request timed out'} - except requests.exceptions.ConnectionError: - bot.status = 'offline' - bot.last_checked = datetime.utcnow() - db.session.commit() - return {'status': 'offline', 'error': 'Connection failed'} - except Exception as e: - bot.status = 'error' - bot.last_checked = datetime.utcnow() - db.session.commit() - return {'status': 'error', 'error': str(e)} - -@app.route('/') -def dashboard(): - bots = Bot.query.order_by(Bot.created_at.desc()).all() - stats = { - 'total_bots': len(bots), - 'online_bots': sum(1 for b in bots if b.status == 'online'), - 'total_guilds': sum(b.guild_count or 0 for b in bots), - 'total_commands': sum(b.command_count or 0 for b in bots), - } - return render_template('dashboard.html', bots=bots, stats=stats) - -@app.route('/bots') -def list_bots(): - bots = Bot.query.order_by(Bot.created_at.desc()).all() - return render_template('bots.html', bots=bots) - -@app.route('/bots/add', methods=['GET', 'POST']) -def add_bot(): - if request.method == 'POST': - name = request.form.get('name') - description = request.form.get('description') - health_endpoint = request.form.get('health_endpoint') - admin_token = request.form.get('admin_token') - bot_type = request.form.get('bot_type', 'discord') - - if not name: - flash('Bot name is required', 'error') - return redirect(url_for('add_bot')) - - bot = Bot( - name=name, - description=description, - health_endpoint=health_endpoint, - admin_token=admin_token, - bot_type=bot_type - ) - db.session.add(bot) - db.session.commit() - - if health_endpoint: - check_bot_health(bot) - - flash(f'Bot "{name}" added successfully!', 'success') - return redirect(url_for('dashboard')) - - return render_template('add_bot.html') - -@app.route('/bots/') -def view_bot(bot_id): - bot = Bot.query.get_or_404(bot_id) - return render_template('view_bot.html', bot=bot) - -@app.route('/bots//edit', methods=['GET', 'POST']) -def edit_bot(bot_id): - bot = Bot.query.get_or_404(bot_id) - - if request.method == 'POST': - bot.name = request.form.get('name', bot.name) - bot.description = request.form.get('description') - bot.health_endpoint = request.form.get('health_endpoint') - new_token = request.form.get('admin_token') - if new_token: - bot.admin_token = new_token - bot.bot_type = request.form.get('bot_type', 'discord') - db.session.commit() - - flash(f'Bot "{bot.name}" updated successfully!', 'success') - return redirect(url_for('view_bot', bot_id=bot.id)) - - return render_template('edit_bot.html', bot=bot) - -@app.route('/bots//delete', methods=['POST']) -def delete_bot(bot_id): - bot = Bot.query.get_or_404(bot_id) - name = bot.name - BotLog.query.filter_by(bot_id=bot.id).delete() - db.session.delete(bot) - db.session.commit() - flash(f'Bot "{name}" deleted successfully!', 'success') - return redirect(url_for('dashboard')) - -@app.route('/bots//check', methods=['POST']) -def check_bot(bot_id): - bot = Bot.query.get_or_404(bot_id) - result = check_bot_health(bot) - return jsonify(result) - -@app.route('/api/bots') -def api_list_bots(): - bots = Bot.query.all() - return jsonify([bot.to_dict() for bot in bots]) - -@app.route('/api/bots//health') -def api_bot_health(bot_id): - bot = Bot.query.get_or_404(bot_id) - result = check_bot_health(bot) - return jsonify(result) - -@app.route('/api/check-all', methods=['POST']) -def api_check_all(): - bots = Bot.query.all() - results = {} - for bot in bots: - results[bot.id] = check_bot_health(bot) - return jsonify(results) - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/replit.md b/replit.md index 6aa722f..1e622cf 100644 --- a/replit.md +++ b/replit.md @@ -1,241 +1,126 @@ -# Bot Master +# AeThex Unified Bot -A centralized management dashboard for managing multiple Discord bots. +A single Discord bot combining community features and enterprise security (Sentinel). ## Overview -Bot Master is a Flask-based web application that provides a central dashboard for monitoring and managing multiple Discord bots. It supports: +AeThex Unified Bot handles both community features AND security in one instance: -- **Bot Registry**: Store configurations for all your bots -- **Status Monitoring**: Real-time health checks for each bot -- **Statistics**: Track servers, commands, and uptime across all bots -- **CRUD Operations**: Add, edit, view, and delete bot configurations +- **Sentinel Security**: Anti-nuke protection with RAM-based heat tracking +- **Federation Sync**: Cross-server role synchronization across 5 realms +- **Ticket System**: Support tickets with automatic channel creation +- **Admin Monitoring**: Real-time status, threat monitoring, server overview ## Tech Stack -- **Backend**: Python/Flask -- **Database**: PostgreSQL (via Flask-SQLAlchemy) -- **Frontend**: Jinja2 templates with custom CSS - -## Project Structure - -``` -/ -├── main.py # Flask application entry point -├── templates/ # HTML templates -│ ├── base.html # Base layout template -│ ├── dashboard.html # Main dashboard view -│ ├── bots.html # Bot list table view -│ ├── add_bot.html # Add new bot form -│ ├── view_bot.html # Bot details view -│ └── edit_bot.html # Edit bot form -├── static/ # Static assets (currently empty) -└── attached_assets/ # Uploaded files and extracted bot examples - ├── bot1/ # Example AeThex bot (basic) - └── bot2/ # Example AeThex bot (extended with feed sync) -``` - -## Database Models - -### Bot -- `id`: Primary key -- `name`: Bot name (required) -- `description`: Bot description -- `health_endpoint`: URL for health check API -- `admin_token`: Bearer token for authenticated endpoints -- `bot_type`: Type of bot (discord, telegram, slack, other) -- `status`: Current status (online, offline, unknown, timeout, error) -- `last_checked`: Timestamp of last health check -- `guild_count`, `command_count`, `uptime_seconds`: Stats from health endpoint -- `created_at`, `updated_at`: Timestamps - -### BotLog -- Stores log entries per bot (for future logging features) - -## API Endpoints - -- `GET /` - Dashboard -- `GET /bots` - List all bots -- `GET /bots/add` - Add bot form -- `POST /bots/add` - Create new bot -- `GET /bots/` - View bot details -- `GET /bots//edit` - Edit bot form -- `POST /bots//edit` - Update bot -- `POST /bots//delete` - Delete bot -- `POST /bots//check` - Check bot health - -### API (JSON) -- `GET /api/bots` - List all bots as JSON -- `GET /api/bots//health` - Check specific bot health -- `POST /api/check-all` - Check all bots health - -## Health Check Integration - -Each bot should expose a health endpoint that returns JSON: -```json -{ - "status": "online", - "guilds": 5, - "commands": 12, - "uptime": 3600 -} -``` - -## Running the Application - -The application runs on port 5000 using the Flask development server. - -## Environment Variables - -- `DATABASE_URL` - PostgreSQL connection string -- `FLASK_SECRET_KEY` - Secret key for session management (auto-generated if not set) - -## Example Bots - -The `attached_assets` folder contains two example AeThex Discord bots: -- **Bot 1**: Basic version with verify, profile, set-realm, unlink, verify-role commands -- **Bot 2**: Extended version with additional help, stats, leaderboard, post commands and feed sync - ---- - -# Aethex Sentinel Bot - -Enterprise-grade Discord bot for managing a federation of 5 servers. - -## Overview - -Aethex Sentinel is a TypeScript-based Discord bot built with the Sapphire framework. It provides: - -- **Federation Sync**: Cross-server role synchronization -- **Sentinel Security**: Anti-nuke protection with heat-based threat detection -- **Commerce Module**: Ticket system with database persistence -- **Dashboard Updates**: Network status updates in voice channels - -## Tech Stack - -- **Runtime**: Node.js 20 + TypeScript -- **Framework**: @sapphire/framework -- **Database**: PostgreSQL (via Prisma ORM) +- **Runtime**: Node.js 20 +- **Framework**: discord.js v14 +- **Database**: Supabase (optional, for user verification) - **Health Endpoint**: HTTP server on port 8080 ## Project Structure ``` -sentinel-bot/ -├── src/ -│ ├── core/ -│ │ ├── client.ts - SapphireClient with Prisma -│ │ ├── config.ts - Environment configuration -│ │ └── health.ts - Health endpoint server -│ ├── modules/ -│ │ ├── federation/FederationManager.ts - Cross-server role sync -│ │ ├── security/HeatSystem.ts - Anti-nuke protection -│ │ ├── commerce/TicketManager.ts - Ticket system -│ │ └── dashboard/StatusUpdater.ts - Network status updates -│ ├── listeners/ -│ │ ├── ready.ts - Bot ready handler -│ │ ├── guildMemberUpdate.ts - Role sync listener -│ │ ├── auditLogCreate.ts - Security monitor -│ │ └── interactionCreate.ts - Button handler -│ ├── commands/ -│ │ ├── federation.ts - /federation command -│ │ ├── sentinel.ts - /sentinel command -│ │ ├── ticket.ts - /ticket command -│ │ └── status.ts - /status command -│ └── index.ts - Entry point -├── prisma/ -│ └── schema.prisma - Database models +aethex-bot/ +├── bot.js # Main entry point ├── package.json -├── tsconfig.json -└── .env.example +├── .env.example +├── commands/ +│ ├── admin.js # /admin status|heat|servers|threats|federation +│ ├── federation.js # /federation link|unlink|list +│ ├── status.js # /status - network overview +│ └── ticket.js # /ticket create|close +├── events/ +│ └── guildMemberUpdate.js # Federation role sync listener +├── listeners/ +│ └── sentinel/ +│ ├── antiNuke.js # Channel delete monitor +│ ├── roleDelete.js # Role delete monitor +│ ├── memberBan.js # Mass ban detection +│ └── memberKick.js # Mass kick detection +└── scripts/ + └── register-commands.js # Slash command registration ``` -## Database Models (Prisma) - -- **User**: Discord user profiles with federation membership -- **HeatEvent**: Security events for threat detection -- **Ticket**: Support tickets with transcripts -- **GuildConfig**: Per-guild configuration -- **RoleMapping**: Cross-server role sync mappings - ## Commands | Command | Description | |---------|-------------| -| `/federation link` | Link a role across servers | -| `/federation unlink` | Remove a role mapping | -| `/federation list` | Show all role mappings | -| `/sentinel heat` | View heat level of a user | -| `/sentinel lockdown` | Enable/disable lockdown mode | -| `/sentinel config` | Configure security thresholds | -| `/ticket create` | Create a support ticket | -| `/ticket close` | Close a ticket with transcript | +| `/admin status` | View bot status and statistics | +| `/admin heat @user` | Check heat level of a user | +| `/admin servers` | View all connected servers | +| `/admin threats` | View active threat monitor | +| `/admin federation` | View federation role mappings | +| `/federation link @role` | Link a role for cross-server sync | +| `/federation unlink @role` | Remove a role from sync | +| `/federation list` | List all linked roles | +| `/ticket create [reason]` | Create a support ticket | +| `/ticket close` | Close the current ticket | | `/status` | View network status | -## Health Endpoint +## Sentinel Security System -The bot exposes a health endpoint compatible with Bot Master dashboard: +The anti-nuke system uses RAM-based heat tracking for instant response: + +- **Heat Threshold**: 3 dangerous actions in 10 seconds triggers auto-ban +- **Monitored Actions**: Channel delete, role delete, member ban, member kick +- **Alerts**: Sends to configured alert channel and DMs server owner +- **Whitelist**: Set `WHITELISTED_USERS` env var for trusted users + +## Environment Variables + +Required: +- `DISCORD_TOKEN` or `DISCORD_BOT_TOKEN` - Bot token +- `DISCORD_CLIENT_ID` - Application ID (currently: 1447339527885553828) + +Optional - Federation: +- `HUB_GUILD_ID` - Main hub server +- `LABS_GUILD_ID`, `GAMEFORGE_GUILD_ID`, `CORP_GUILD_ID`, `FOUNDATION_GUILD_ID` + +Optional - Security: +- `WHITELISTED_USERS` - Comma-separated user IDs to skip heat tracking +- `ALERT_CHANNEL_ID` - Channel for security alerts + +Optional - Supabase: +- `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE` - For user verification features + +## Health Endpoint **GET /health** (port 8080) ```json { "status": "online", "guilds": 5, - "commands": 12, + "commands": 4, "uptime": 3600, - "timestamp": "2025-12-07T12:00:00.000Z", - "bot": { - "tag": "Aethex Sentinel#1234", - "id": "123456789" - } + "heatMapSize": 0, + "timestamp": "2025-12-07T22:15:00.000Z" } ``` **GET /stats** (port 8080) ```json { - "guilds": [ - { "id": "...", "name": "...", "memberCount": 100 } - ], + "guilds": [...], "totalMembers": 500, - "uptime": 3600 + "uptime": 3600, + "activeTickets": 0, + "heatEvents": 0 } ``` -## Environment Variables - -Required secrets (add in Replit Secrets tab): -- `DISCORD_TOKEN` - Discord bot token -- `DATABASE_URL` - PostgreSQL connection string - -Optional configuration: -- `HUB_GUILD_ID` - Main hub server ID -- `FEDERATION_GUILD_IDS` - Comma-separated list of guild IDs -- `HEALTH_PORT` - Health server port (default: 8080) - ## Running the Bot ```bash -cd sentinel-bot +cd aethex-bot npm install -npx prisma generate -npx prisma db push -npm run build +node scripts/register-commands.js # Register slash commands (run once) npm start ``` -For development: -```bash -npm run dev -``` +## Current Status -## Integration with Bot Master - -Once the Sentinel bot is running, add it to Bot Master dashboard: -1. Go to Bot Master dashboard (port 5000) -2. Click "Add Bot" -3. Enter name: "Aethex Sentinel" -4. Enter health endpoint: `http://localhost:8080/health` -5. Select type: "discord" - -The dashboard will automatically poll the health endpoint for status updates. +- Bot is running and connected to 5 servers +- All 4 commands registered (/admin, /federation, /status, /ticket) +- Sentinel listeners active (channel/role delete, ban/kick monitoring) +- Health endpoint available at port 8080 diff --git a/sentinel-bot/.env.example b/sentinel-bot/.env.example deleted file mode 100644 index a6ab391..0000000 --- a/sentinel-bot/.env.example +++ /dev/null @@ -1,21 +0,0 @@ -# Discord Bot Configuration -DISCORD_TOKEN=your_bot_token_here - -# Federation Guild IDs -HUB_ID=515711457946632232 -FORGE_ID=1245619208805416970 -FOUNDATION_ID=1338564560277344287 -LABS_ID=1275962459596783686 -CORP_ID=373713073594302464 - -# Security Configuration -WHITELISTED_USERS=113472107526033408,user_id_2 - -# Dashboard Configuration -STATUS_CHANNEL_ID=voice_channel_id_for_status - -# Health Check -HEALTH_PORT=8044 - -# Database (auto-provided by Replit) -# DATABASE_URL=postgresql://... diff --git a/sentinel-bot/package-lock.json b/sentinel-bot/package-lock.json deleted file mode 100644 index 3a644bf..0000000 --- a/sentinel-bot/package-lock.json +++ /dev/null @@ -1,1097 +0,0 @@ -{ - "name": "aethex-sentinel", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "aethex-sentinel", - "version": "1.0.0", - "dependencies": { - "@prisma/client": "^5.22.0", - "@sapphire/framework": "^5.2.1", - "@sapphire/plugin-scheduled-tasks": "^10.0.1", - "discord.js": "^14.16.3", - "dotenv": "^16.4.5" - }, - "devDependencies": { - "@types/node": "^20.17.6", - "prisma": "^5.22.0", - "ts-node": "^10.9.2", - "typescript": "^5.6.3" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "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/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/@ioredis/commands": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", - "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", - "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", - "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", - "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", - "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", - "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@prisma/client": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", - "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", - "hasInstallScript": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.13" - }, - "peerDependencies": { - "prisma": "*" - }, - "peerDependenciesMeta": { - "prisma": { - "optional": true - } - } - }, - "node_modules/@prisma/debug": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", - "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/engines": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", - "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/fetch-engine": "5.22.0", - "@prisma/get-platform": "5.22.0" - } - }, - "node_modules/@prisma/engines-version": { - "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", - "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", - "devOptional": true, - "license": "Apache-2.0" - }, - "node_modules/@prisma/fetch-engine": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", - "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/get-platform": "5.22.0" - } - }, - "node_modules/@prisma/get-platform": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", - "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", - "devOptional": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "5.22.0" - } - }, - "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/discord-utilities": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sapphire/discord-utilities/-/discord-utilities-4.0.0.tgz", - "integrity": "sha512-QAvrKNHgswz+ZX48WqSYpRiRzQcugNXXB1C3fR1qbpTJGd7Ckr2OWyFK88TyOksi3U2isrk8sMriTcAgaIe7Qg==", - "license": "MIT", - "dependencies": { - "discord-api-types": "^0.38.30" - }, - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/discord.js-utilities": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@sapphire/discord.js-utilities/-/discord.js-utilities-7.3.3.tgz", - "integrity": "sha512-WDj+zjWgNCUSvzYDD0wY3TVeTUseHq0Nhk0wVWxSDjY8z2gFEVcpY7wF8/fbTDWP44LUG5sUQ4haIrIj2OjmkQ==", - "license": "MIT", - "dependencies": { - "@sapphire/discord-utilities": "^3.5.0", - "@sapphire/duration": "^1.2.0", - "@sapphire/utilities": "^3.18.2", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=16.6.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/discord.js-utilities/node_modules/@sapphire/discord-utilities": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@sapphire/discord-utilities/-/discord-utilities-3.5.0.tgz", - "integrity": "sha512-H4SY5KTVDZrqA5QG7ob6etwqhdOb3TRSY2wv56f0tiobUdIr0irlrYvdmr8Kg/FRxWU+aiHDIISWGG5vBuxOGw==", - "license": "MIT", - "dependencies": { - "discord-api-types": "^0.38.1" - }, - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/duration": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@sapphire/duration/-/duration-1.2.0.tgz", - "integrity": "sha512-LxjOAFXz81WmrI8XX9YaVcAZDjQj/1p78lZCvkAWZB1nphOwz/D0dU3CBejmhOWx5dO5CszTkLJMNR0xuCK+Zg==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/framework": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@sapphire/framework/-/framework-5.4.0.tgz", - "integrity": "sha512-Gf0Ui5tOG1lRRf6Gpd/gpUDkAIMZxFXYOzq0X5kMcXRNWiX1B6iIUkyXrzCLaNunzqWRKe6x4UxzRewN5ktNWg==", - "license": "MIT", - "dependencies": { - "@discordjs/builders": "^1.13.0", - "@sapphire/discord-utilities": "^4.0.0", - "@sapphire/discord.js-utilities": "^7.3.3", - "@sapphire/lexure": "^1.1.12", - "@sapphire/pieces": "^4.4.1", - "@sapphire/ratelimits": "^2.4.11", - "@sapphire/result": "^2.8.0", - "@sapphire/stopwatch": "^1.5.4", - "@sapphire/utilities": "^3.18.2" - }, - "engines": { - "node": ">=v18", - "npm": ">=7" - } - }, - "node_modules/@sapphire/lexure": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@sapphire/lexure/-/lexure-1.1.12.tgz", - "integrity": "sha512-F7Z3QzRnAZGunRl24/qQMhzRogZU/foumu2EBBunRnQi/o/DLTCwdAbLgJATyPlvJa8N6FrJq0JJwvzM/vXoXg==", - "license": "MIT", - "dependencies": { - "@sapphire/result": "^2.8.0" - }, - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/pieces": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@sapphire/pieces/-/pieces-4.4.1.tgz", - "integrity": "sha512-2v49P++RzHGb23bdjSa9u7flkdvhrq94IUO9PneY538TILN5QiMxfaXVK+pDR2YR7jpQYBy9APwtFDes2Svykg==", - "license": "MIT", - "dependencies": { - "@discordjs/collection": "^2.1.1", - "@sapphire/utilities": "^3.18.2", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/plugin-scheduled-tasks": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@sapphire/plugin-scheduled-tasks/-/plugin-scheduled-tasks-10.0.4.tgz", - "integrity": "sha512-H0Z9QgDavLDK38GVyKfMDsyruOA4NHSyhNk/LCzewCDqc1d5Nxvoo5PMTB19NvadO9RFvgnPqZk7g4k3HzvAvQ==", - "license": "MIT", - "dependencies": { - "@sapphire/stopwatch": "^1.5.4", - "@sapphire/utilities": "^3.18.2", - "bullmq": "5.58.7" - }, - "engines": { - "node": ">=v18", - "npm": ">=7" - } - }, - "node_modules/@sapphire/ratelimits": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/@sapphire/ratelimits/-/ratelimits-2.4.11.tgz", - "integrity": "sha512-O6FNA/P0wxU4Ve9gxL948CoZw7+sSpujyUR2CLyLLCNuNvuFGFxPCJVl5crFVLXMIyBIrc2qk+/H9bsqsyQK1Q==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/result": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@sapphire/result/-/result-2.8.0.tgz", - "integrity": "sha512-693yWouX+hR9uJm1Jgq0uSSjbSD3UrblMaxiuGbHPjSwzLCSZTcm0h3kvdVhq3o/yl4+oeAWW3hiaJ0TELuRJQ==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/shapeshift": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", - "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v16" - } - }, - "node_modules/@sapphire/snowflake": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", - "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/stopwatch": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@sapphire/stopwatch/-/stopwatch-1.5.4.tgz", - "integrity": "sha512-IVI48D2yAz411bSttXyTkBH0p2vhrXoqWLn5loDDSAAEUGkM1r5KNCX2027ifQ8svdoMkUfIGjFueR+satLeWw==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=v14.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/@sapphire/utilities": { - "version": "3.18.2", - "resolved": "https://registry.npmjs.org/@sapphire/utilities/-/utilities-3.18.2.tgz", - "integrity": "sha512-QGLdC9+pT74Zd7aaObqn0EUfq40c4dyTL65pFnkM6WO1QYN7Yg/s4CdH+CXmx0Zcu6wcfCWILSftXPMosJHP5A==", - "license": "MIT", - "engines": { - "node": ">=v14.0.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", - "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@vladfrangu/async_event_emitter": { - "version": "2.4.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/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/bullmq": { - "version": "5.58.7", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.58.7.tgz", - "integrity": "sha512-rqsKV/ip76wU90q7Cxpr1vS/6PYIVbhuzqr3wgILgjS6XbsnJtWyYrK23jqWHs9+m6/NXM4+62hyf8CSBpufAw==", - "license": "MIT", - "dependencies": { - "cron-parser": "^4.9.0", - "ioredis": "^5.4.1", - "msgpackr": "^1.11.2", - "node-abort-controller": "^3.1.1", - "semver": "^7.5.4", - "tslib": "^2.0.0", - "uuid": "^11.1.0" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cron-parser": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", - "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", - "license": "MIT", - "dependencies": { - "luxon": "^3.2.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "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/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/discord-api-types": { - "version": "0.38.36", - "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.36.tgz", - "integrity": "sha512-qrbUbjjwtyeBg5HsAlm1C859epfOyiLjPqAOzkdWlCNsZCWJrertnETF/NwM8H+waMFU58xGSc5eXUfXah+WTQ==", - "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/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/ioredis": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", - "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==", - "license": "MIT", - "dependencies": { - "@ioredis/commands": "1.4.0", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, - "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.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "license": "MIT" - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "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/luxon": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", - "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "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/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "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/msgpackr": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", - "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", - "license": "MIT", - "optionalDependencies": { - "msgpackr-extract": "^3.0.2" - } - }, - "node_modules/msgpackr-extract": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", - "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "node-gyp-build-optional-packages": "5.2.2" - }, - "bin": { - "download-msgpackr-prebuilds": "bin/download-prebuilds.js" - }, - "optionalDependencies": { - "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", - "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" - } - }, - "node_modules/node-abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "license": "MIT" - }, - "node_modules/node-gyp-build-optional-packages": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", - "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", - "license": "MIT", - "optional": true, - "dependencies": { - "detect-libc": "^2.0.1" - }, - "bin": { - "node-gyp-build-optional-packages": "bin.js", - "node-gyp-build-optional-packages-optional": "optional.js", - "node-gyp-build-optional-packages-test": "build-test.js" - } - }, - "node_modules/prisma": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", - "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", - "devOptional": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/engines": "5.22.0" - }, - "bin": { - "prisma": "build/index.js" - }, - "engines": { - "node": ">=16.13" - }, - "optionalDependencies": { - "fsevents": "2.3.3" - } - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "license": "MIT", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", - "license": "MIT" - }, - "node_modules/ts-mixer": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", - "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "license": "MIT" - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "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/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "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": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - } - } -} diff --git a/sentinel-bot/package.json b/sentinel-bot/package.json deleted file mode 100644 index 47eb34b..0000000 --- a/sentinel-bot/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "aethex-sentinel", - "version": "1.0.0", - "description": "Enterprise-grade Discord bot for managing a federation of servers", - "main": "dist/index.js", - "scripts": { - "build": "tsc", - "start": "node dist/index.js", - "dev": "ts-node src/index.ts", - "db:generate": "prisma generate", - "db:push": "prisma db push", - "db:studio": "prisma studio" - }, - "dependencies": { - "@prisma/client": "^5.22.0", - "@sapphire/framework": "^5.2.1", - "@sapphire/plugin-scheduled-tasks": "^10.0.1", - "discord.js": "^14.16.3", - "dotenv": "^16.4.5" - }, - "devDependencies": { - "@types/node": "^20.17.6", - "prisma": "^5.22.0", - "ts-node": "^10.9.2", - "typescript": "^5.6.3" - } -} diff --git a/sentinel-bot/prisma/schema.prisma b/sentinel-bot/prisma/schema.prisma deleted file mode 100644 index 79a6647..0000000 --- a/sentinel-bot/prisma/schema.prisma +++ /dev/null @@ -1,65 +0,0 @@ -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -model User { - id String @id - heatLevel Int @default(0) - balance Float @default(0.0) - roles String[] - lastHeatReset DateTime @default(now()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - tickets Ticket[] - heatEvents HeatEvent[] -} - -model HeatEvent { - id Int @id @default(autoincrement()) - userId String - action String - timestamp DateTime @default(now()) - user User @relation(fields: [userId], references: [id]) - - @@index([userId, timestamp]) -} - -model Ticket { - id Int @id @default(autoincrement()) - threadId String @unique - userId String - type String - status String @default("open") - transcript String? - createdAt DateTime @default(now()) - closedAt DateTime? - user User @relation(fields: [userId], references: [id]) - - @@index([userId]) -} - -model GuildConfig { - id String @id - name String - type String - statusChannelId String? - feedChannelId String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt -} - -model RoleMapping { - id Int @id @default(autoincrement()) - sourceGuild String - sourceRole String - targetGuild String - targetRole String - roleName String - - @@unique([sourceGuild, sourceRole, targetGuild]) -} diff --git a/sentinel-bot/src/commands/federation.ts b/sentinel-bot/src/commands/federation.ts deleted file mode 100644 index f16fbf1..0000000 --- a/sentinel-bot/src/commands/federation.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { PermissionFlagsBits } from 'discord.js'; -import { federationManager } from '../modules/federation/FederationManager'; -import { prisma } from '../core/client'; - -export class FederationCommand extends Command { - public constructor(context: Command.LoaderContext, options: Command.Options) { - super(context, { - ...options, - name: 'federation', - description: 'Manage federation role mappings', - requiredUserPermissions: [PermissionFlagsBits.Administrator], - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addSubcommand((sub) => - sub - .setName('add-mapping') - .setDescription('Add a role mapping between guilds') - .addStringOption((opt) => - opt.setName('source-guild').setDescription('Source guild ID').setRequired(true) - ) - .addStringOption((opt) => - opt.setName('source-role').setDescription('Source role ID').setRequired(true) - ) - .addStringOption((opt) => - opt.setName('target-guild').setDescription('Target guild ID').setRequired(true) - ) - .addStringOption((opt) => - opt.setName('target-role').setDescription('Target role ID').setRequired(true) - ) - .addStringOption((opt) => - opt.setName('role-name').setDescription('Friendly name for this mapping').setRequired(true) - ) - ) - .addSubcommand((sub) => - sub.setName('list').setDescription('List all role mappings') - ) - .addSubcommand((sub) => - sub.setName('reload').setDescription('Reload mappings from database') - ) - ); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const subcommand = interaction.options.getSubcommand(); - - if (subcommand === 'add-mapping') { - const sourceGuild = interaction.options.getString('source-guild', true); - const sourceRole = interaction.options.getString('source-role', true); - const targetGuild = interaction.options.getString('target-guild', true); - const targetRole = interaction.options.getString('target-role', true); - const roleName = interaction.options.getString('role-name', true); - - await federationManager.addMapping({ - sourceGuild, - sourceRole, - targetGuild, - targetRole, - roleName, - }); - - return interaction.reply({ - content: `✅ Added mapping: "${roleName}" from ${sourceGuild} to ${targetGuild}`, - ephemeral: true, - }); - } - - if (subcommand === 'list') { - const mappings = await prisma.roleMapping.findMany(); - - if (mappings.length === 0) { - return interaction.reply({ - content: 'No role mappings configured.', - ephemeral: true, - }); - } - - const list = mappings - .map((m) => `• **${m.roleName}**: ${m.sourceGuild}:${m.sourceRole} → ${m.targetGuild}:${m.targetRole}`) - .join('\n'); - - return interaction.reply({ - content: `**Federation Role Mappings:**\n${list}`, - ephemeral: true, - }); - } - - if (subcommand === 'reload') { - await federationManager.loadMappings(); - return interaction.reply({ - content: '✅ Reloaded role mappings from database.', - ephemeral: true, - }); - } - } -} diff --git a/sentinel-bot/src/commands/sentinel.ts b/sentinel-bot/src/commands/sentinel.ts deleted file mode 100644 index 007597a..0000000 --- a/sentinel-bot/src/commands/sentinel.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { PermissionFlagsBits, EmbedBuilder } from 'discord.js'; -import { heatSystem } from '../modules/security/HeatSystem'; -import { config } from '../core/config'; - -export class SentinelCommand extends Command { - public constructor(context: Command.LoaderContext, options: Command.Options) { - super(context, { - ...options, - name: 'sentinel', - description: 'Manage the Sentinel security system', - requiredUserPermissions: [PermissionFlagsBits.Administrator], - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addSubcommand((sub) => - sub.setName('status').setDescription('View Sentinel security status') - ) - .addSubcommand((sub) => - sub - .setName('clear-heat') - .setDescription('Clear heat level for a user') - .addUserOption((opt) => - opt.setName('user').setDescription('User to clear heat for').setRequired(true) - ) - ) - .addSubcommand((sub) => - sub.setName('unlock').setDescription('Lift lockdown mode on this server') - ) - ); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const subcommand = interaction.options.getSubcommand(); - - if (subcommand === 'status') { - const embed = new EmbedBuilder() - .setColor(0x00ff00) - .setTitle('🛡️ Sentinel Security Status') - .addFields( - { name: 'Heat Threshold', value: `${config.security.heatThreshold} actions`, inline: true }, - { name: 'Window', value: `${config.security.heatWindowMs}ms`, inline: true }, - { name: 'Whitelisted Users', value: `${config.security.whitelistedUsers.length}`, inline: true }, - { - name: 'Monitored Actions', - value: config.security.dangerousActions.join(', '), - inline: false, - } - ) - .setTimestamp(); - - return interaction.reply({ embeds: [embed], ephemeral: true }); - } - - if (subcommand === 'clear-heat') { - const user = interaction.options.getUser('user', true); - heatSystem.clearHeat(user.id); - return interaction.reply({ - content: `✅ Cleared heat for ${user.tag}`, - ephemeral: true, - }); - } - - if (subcommand === 'unlock') { - if (!interaction.guild) { - return interaction.reply({ - content: 'This command must be used in a server.', - ephemeral: true, - }); - } - - await heatSystem.unlockGuild(interaction.guild); - return interaction.reply({ - content: '🔓 Lockdown lifted. @everyone permissions restored.', - ephemeral: true, - }); - } - } -} diff --git a/sentinel-bot/src/commands/status.ts b/sentinel-bot/src/commands/status.ts deleted file mode 100644 index 67f01b9..0000000 --- a/sentinel-bot/src/commands/status.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { EmbedBuilder } from 'discord.js'; -import { config } from '../core/config'; - -export class StatusCommand extends Command { - public constructor(context: Command.LoaderContext, options: Command.Options) { - super(context, { - ...options, - name: 'status', - description: 'View Sentinel bot and network status', - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder.setName(this.name).setDescription(this.description) - ); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const client = this.container.client; - - const guildIds = Object.values(config.guilds).filter(id => id); - const guilds = guildIds - .map(id => client.guilds.cache.get(id)) - .filter(g => g !== undefined); - - const totalMembers = guilds.reduce((sum, g) => sum + g!.memberCount, 0); - - const guildList = guilds - .map(g => `• **${g!.name}**: ${g!.memberCount.toLocaleString()} members`) - .join('\n') || 'No federation guilds connected'; - - const uptimeSeconds = Math.floor(process.uptime()); - const hours = Math.floor(uptimeSeconds / 3600); - const minutes = Math.floor((uptimeSeconds % 3600) / 60); - const seconds = uptimeSeconds % 60; - const uptimeStr = `${hours}h ${minutes}m ${seconds}s`; - - const embed = new EmbedBuilder() - .setColor(0x5865f2) - .setTitle('🛡️ Aethex Sentinel Status') - .setThumbnail(client.user?.displayAvatarURL() || null) - .addFields( - { name: '📡 Network', value: `${guilds.length} guilds`, inline: true }, - { name: '👥 Total Members', value: totalMembers.toLocaleString(), inline: true }, - { name: '⏱️ Uptime', value: uptimeStr, inline: true }, - { name: '🏰 Federation Guilds', value: guildList, inline: false } - ) - .setFooter({ text: 'Aethex Sentinel • Protecting the Federation' }) - .setTimestamp(); - - return interaction.reply({ embeds: [embed] }); - } -} diff --git a/sentinel-bot/src/commands/ticket.ts b/sentinel-bot/src/commands/ticket.ts deleted file mode 100644 index 3b9765e..0000000 --- a/sentinel-bot/src/commands/ticket.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { PermissionFlagsBits, ChannelType, TextChannel } from 'discord.js'; -import { ticketManager } from '../modules/commerce/TicketManager'; -import { prisma } from '../core/client'; - -export class TicketCommand extends Command { - public constructor(context: Command.LoaderContext, options: Command.Options) { - super(context, { - ...options, - name: 'ticket', - description: 'Manage the ticket system', - requiredUserPermissions: [PermissionFlagsBits.Administrator], - }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder - .setName(this.name) - .setDescription(this.description) - .addSubcommand((sub) => - sub - .setName('setup') - .setDescription('Set up the ticket panel in this channel') - ) - .addSubcommand((sub) => - sub.setName('stats').setDescription('View ticket statistics') - ) - ); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - const subcommand = interaction.options.getSubcommand(); - - if (subcommand === 'setup') { - if (!interaction.channel || interaction.channel.type !== ChannelType.GuildText) { - return interaction.reply({ - content: 'This command must be used in a text channel.', - ephemeral: true, - }); - } - - await interaction.deferReply({ ephemeral: true }); - await ticketManager.createSupportPanel(interaction.channel as TextChannel); - - return interaction.editReply({ - content: '✅ Ticket panel created!', - }); - } - - if (subcommand === 'stats') { - const totalTickets = await prisma.ticket.count(); - const openTickets = await prisma.ticket.count({ where: { status: 'open' } }); - const closedTickets = await prisma.ticket.count({ where: { status: 'closed' } }); - - const byType = await prisma.ticket.groupBy({ - by: ['type'], - _count: { id: true }, - }); - - const typeStats = byType - .map((t) => `• ${t.type}: ${t._count.id}`) - .join('\n') || 'No tickets yet'; - - return interaction.reply({ - content: - `**🎫 Ticket Statistics**\n\n` + - `Total: ${totalTickets}\n` + - `Open: ${openTickets}\n` + - `Closed: ${closedTickets}\n\n` + - `**By Type:**\n${typeStats}`, - ephemeral: true, - }); - } - } -} diff --git a/sentinel-bot/src/core/client.ts b/sentinel-bot/src/core/client.ts deleted file mode 100644 index abbd657..0000000 --- a/sentinel-bot/src/core/client.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { SapphireClient } from '@sapphire/framework'; -import { GatewayIntentBits, Partials } from 'discord.js'; -import { PrismaClient } from '@prisma/client'; - -export const prisma = new PrismaClient(); - -export class SentinelClient extends SapphireClient { - public constructor() { - super({ - intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMembers, - GatewayIntentBits.GuildModeration, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, - GatewayIntentBits.DirectMessages, - ], - partials: [ - Partials.Channel, - Partials.Message, - Partials.GuildMember, - ], - loadMessageCommandListeners: true, - }); - } - - public override async login(token?: string): Promise { - await prisma.$connect(); - console.log('✅ Database connected'); - return super.login(token); - } - - public override async destroy(): Promise { - await prisma.$disconnect(); - return super.destroy(); - } -} diff --git a/sentinel-bot/src/core/config.ts b/sentinel-bot/src/core/config.ts deleted file mode 100644 index efa7fbf..0000000 --- a/sentinel-bot/src/core/config.ts +++ /dev/null @@ -1,41 +0,0 @@ -import 'dotenv/config'; - -export const config = { - token: process.env.DISCORD_TOKEN!, - - guilds: { - hub: process.env.HUB_ID!, - gameforge: process.env.FORGE_ID!, - foundation: process.env.FOUNDATION_ID!, - labs: process.env.LABS_ID!, - corp: process.env.CORP_ID!, - }, - - security: { - heatThreshold: 3, - heatWindowMs: 5000, - dangerousActions: ['CHANNEL_DELETE', 'BAN_ADD', 'KICK', 'ROLE_DELETE', 'WEBHOOK_DELETE'], - whitelistedUsers: process.env.WHITELISTED_USERS?.split(',') || [], - }, - - dashboard: { - updateIntervalMs: 5 * 60 * 1000, - statusChannelId: process.env.STATUS_CHANNEL_ID, - }, - - health: { - port: parseInt(process.env.HEALTH_PORT || '8080'), - }, -}; - -export function validateConfig(): void { - const required = ['DISCORD_TOKEN']; - const missing = required.filter(key => !process.env[key]); - - if (missing.length > 0) { - console.error('❌ Missing required environment variables:', missing.join(', ')); - process.exit(1); - } - - console.log('✅ Configuration validated'); -} diff --git a/sentinel-bot/src/core/health.ts b/sentinel-bot/src/core/health.ts deleted file mode 100644 index 248cf5e..0000000 --- a/sentinel-bot/src/core/health.ts +++ /dev/null @@ -1,59 +0,0 @@ -import http from 'http'; -import { SentinelClient } from './client'; -import { config } from './config'; - -export function startHealthServer(client: SentinelClient): void { - const server = http.createServer((req, res) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); - res.setHeader('Content-Type', 'application/json'); - - if (req.method === 'OPTIONS') { - res.writeHead(200); - res.end(); - return; - } - - if (req.url === '/health') { - const guilds = client.guilds.cache.size; - const commands = client.stores.get('commands')?.size || 0; - - res.writeHead(200); - res.end(JSON.stringify({ - status: client.isReady() ? 'online' : 'offline', - guilds, - commands, - uptime: Math.floor(process.uptime()), - timestamp: new Date().toISOString(), - bot: { - tag: client.user?.tag || 'Not ready', - id: client.user?.id, - }, - })); - return; - } - - if (req.url === '/stats') { - const guildStats = client.guilds.cache.map(g => ({ - id: g.id, - name: g.name, - memberCount: g.memberCount, - })); - - res.writeHead(200); - res.end(JSON.stringify({ - guilds: guildStats, - totalMembers: guildStats.reduce((sum, g) => sum + g.memberCount, 0), - uptime: Math.floor(process.uptime()), - })); - return; - } - - res.writeHead(404); - res.end(JSON.stringify({ error: 'Not found' })); - }); - - server.listen(config.health.port, () => { - console.log(`🏥 Health server running on port ${config.health.port}`); - }); -} diff --git a/sentinel-bot/src/index.ts b/sentinel-bot/src/index.ts deleted file mode 100644 index 385ce83..0000000 --- a/sentinel-bot/src/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { config, validateConfig } from './core/config'; -import { SentinelClient } from './core/client'; -import { startHealthServer } from './core/health'; - -validateConfig(); - -const client = new SentinelClient(); - -client.once('ready', () => { - console.log(`✅ Aethex Sentinel logged in as ${client.user?.tag}`); - console.log(`📡 Watching ${client.guilds.cache.size} guild(s)`); - - client.user?.setActivity('🛡️ Protecting the Federation', { type: 3 }); - - startHealthServer(client); -}); - -process.on('unhandledRejection', (error: Error) => { - console.error('❌ Unhandled Promise Rejection:', error); -}); - -process.on('uncaughtException', (error: Error) => { - console.error('❌ Uncaught Exception:', error); - process.exit(1); -}); - -client.login(config.token); diff --git a/sentinel-bot/src/listeners/auditLogCreate.ts b/sentinel-bot/src/listeners/auditLogCreate.ts deleted file mode 100644 index c4ccd01..0000000 --- a/sentinel-bot/src/listeners/auditLogCreate.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Listener } from '@sapphire/framework'; -import { AuditLogEvent, GuildAuditLogsEntry, Guild } from 'discord.js'; -import { heatSystem } from '../modules/security/HeatSystem'; - -type DangerousAction = 'CHANNEL_DELETE' | 'BAN_ADD' | 'KICK' | 'ROLE_DELETE' | 'WEBHOOK_DELETE'; - -const DANGEROUS_ACTIONS: Record = { - [AuditLogEvent.ChannelDelete]: 'CHANNEL_DELETE', - [AuditLogEvent.MemberBanAdd]: 'BAN_ADD', - [AuditLogEvent.MemberKick]: 'KICK', - [AuditLogEvent.RoleDelete]: 'ROLE_DELETE', - [AuditLogEvent.WebhookDelete]: 'WEBHOOK_DELETE', -}; - -export class AuditLogCreateListener extends Listener { - public constructor(context: Listener.LoaderContext, options: Listener.Options) { - super(context, { - ...options, - event: 'guildAuditLogEntryCreate', - }); - } - - public async run(entry: GuildAuditLogsEntry, guild: Guild): Promise { - const actionType = DANGEROUS_ACTIONS[entry.action]; - if (!actionType) return; - - const executorId = entry.executorId; - if (!executorId) return; - - if (executorId === this.container.client.user?.id) return; - - console.log(`⚠️ Dangerous action detected: ${actionType} by ${executorId} in ${guild.name}`); - - const triggered = await heatSystem.recordAction(executorId, actionType, guild); - - if (triggered) { - console.log(`🚨 Anti-nuke triggered for ${executorId} in ${guild.name}`); - } - } -} diff --git a/sentinel-bot/src/listeners/guildMemberUpdate.ts b/sentinel-bot/src/listeners/guildMemberUpdate.ts deleted file mode 100644 index 6e55e01..0000000 --- a/sentinel-bot/src/listeners/guildMemberUpdate.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Listener } from '@sapphire/framework'; -import { GuildMember, Role } from 'discord.js'; -import { federationManager } from '../modules/federation/FederationManager'; - -export class GuildMemberUpdateListener extends Listener { - public constructor(context: Listener.LoaderContext, options: Listener.Options) { - super(context, { - ...options, - event: 'guildMemberUpdate', - }); - } - - public async run(oldMember: GuildMember, newMember: GuildMember): Promise { - const oldRoles = oldMember.roles.cache; - const newRoles = newMember.roles.cache; - - const addedRoles = newRoles.filter(role => !oldRoles.has(role.id)); - const removedRoles = oldRoles.filter(role => !newRoles.has(role.id)); - - for (const role of addedRoles.values()) { - await federationManager.syncRoleGrant(newMember, role); - } - - for (const role of removedRoles.values()) { - await federationManager.syncRoleRemove(newMember, role); - } - } -} diff --git a/sentinel-bot/src/listeners/interactionCreate.ts b/sentinel-bot/src/listeners/interactionCreate.ts deleted file mode 100644 index 3620684..0000000 --- a/sentinel-bot/src/listeners/interactionCreate.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Listener } from '@sapphire/framework'; -import { Interaction } from 'discord.js'; -import { ticketManager } from '../modules/commerce/TicketManager'; - -export class InteractionCreateListener extends Listener { - public constructor(context: Listener.LoaderContext, options: Listener.Options) { - super(context, { - ...options, - event: 'interactionCreate', - }); - } - - public async run(interaction: Interaction): Promise { - if (!interaction.isButton()) return; - - const customId = interaction.customId; - - try { - if (customId === 'ticket_rental') { - await ticketManager.createTicket(interaction, 'rental'); - } else if (customId === 'ticket_support') { - await ticketManager.createTicket(interaction, 'support'); - } else if (customId === 'ticket_billing') { - await ticketManager.createTicket(interaction, 'billing'); - } else if (customId === 'ticket_invoice') { - await ticketManager.generateInvoice(interaction); - } else if (customId === 'ticket_close') { - await ticketManager.closeTicket(interaction); - } else if (customId === 'invoice_pay') { - await interaction.reply({ - content: '✅ Invoice marked as paid! (Mock transaction)', - ephemeral: true, - }); - } - } catch (error) { - console.error('❌ Button interaction error:', error); - - if (!interaction.replied && !interaction.deferred) { - await interaction.reply({ - content: 'An error occurred while processing your request.', - ephemeral: true, - }); - } - } - } -} diff --git a/sentinel-bot/src/listeners/ready.ts b/sentinel-bot/src/listeners/ready.ts deleted file mode 100644 index 23ef058..0000000 --- a/sentinel-bot/src/listeners/ready.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Listener } from '@sapphire/framework'; -import { Client } from 'discord.js'; -import { federationManager } from '../modules/federation/FederationManager'; -import { StatusUpdater } from '../modules/dashboard/StatusUpdater'; - -export class ReadyListener extends Listener { - public constructor(context: Listener.LoaderContext, options: Listener.Options) { - super(context, { - ...options, - once: true, - event: 'ready', - }); - } - - public async run(client: Client): Promise { - console.log(`✅ Aethex Sentinel ready as ${client.user.tag}`); - console.log(`📡 Connected to ${client.guilds.cache.size} guild(s)`); - - await federationManager.loadMappings(); - - const statusUpdater = new StatusUpdater(client); - statusUpdater.start(); - - client.user.setActivity('🛡️ Protecting the Federation', { type: 3 }); - - console.log('✅ All modules initialized'); - } -} diff --git a/sentinel-bot/src/modules/commerce/TicketManager.ts b/sentinel-bot/src/modules/commerce/TicketManager.ts deleted file mode 100644 index e70eaad..0000000 --- a/sentinel-bot/src/modules/commerce/TicketManager.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { - ButtonInteraction, - ChannelType, - EmbedBuilder, - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - TextChannel, - ThreadChannel, - Message, -} from 'discord.js'; -import { prisma } from '../../core/client'; - -export class TicketManager { - async createTicket(interaction: ButtonInteraction, type: string): Promise { - const channel = interaction.channel as TextChannel; - if (!channel || channel.type !== ChannelType.GuildText) { - await interaction.reply({ content: 'Cannot create ticket here.', ephemeral: true }); - return; - } - - await prisma.user.upsert({ - where: { id: interaction.user.id }, - update: {}, - create: { id: interaction.user.id }, - }); - - const thread = await channel.threads.create({ - name: `${type}-${interaction.user.username}-${Date.now().toString(36)}`, - type: ChannelType.PrivateThread, - invitable: false, - reason: `Ticket created by ${interaction.user.tag}`, - }); - - await thread.members.add(interaction.user.id); - - await prisma.ticket.create({ - data: { - threadId: thread.id, - userId: interaction.user.id, - type, - status: 'open', - }, - }); - - const embed = new EmbedBuilder() - .setColor(0x5865f2) - .setTitle(`🎫 ${type.charAt(0).toUpperCase() + type.slice(1)} Ticket`) - .setDescription( - `Welcome ${interaction.user}!\n\n` + - `A staff member will be with you shortly.\n` + - `Please describe your request in detail.` - ) - .setFooter({ text: `Ticket ID: ${thread.id}` }) - .setTimestamp(); - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('ticket_invoice') - .setLabel('Generate Invoice') - .setStyle(ButtonStyle.Primary) - .setEmoji('📄'), - new ButtonBuilder() - .setCustomId('ticket_close') - .setLabel('Close Ticket') - .setStyle(ButtonStyle.Danger) - .setEmoji('🔒') - ); - - await thread.send({ embeds: [embed], components: [row] }); - - await interaction.reply({ - content: `✅ Your ticket has been created: ${thread}`, - ephemeral: true, - }); - - console.log(`🎫 Ticket created: ${thread.name} by ${interaction.user.tag}`); - } - - async generateInvoice(interaction: ButtonInteraction): Promise { - const invoiceId = `INV-${Date.now().toString(36).toUpperCase()}`; - - const embed = new EmbedBuilder() - .setColor(0x00ff00) - .setTitle('📄 Invoice Generated') - .setDescription('Here is your mock invoice for the requested service.') - .addFields( - { name: 'Invoice ID', value: invoiceId, inline: true }, - { name: 'Status', value: 'Pending', inline: true }, - { name: 'Amount', value: '$XX.XX', inline: true }, - { name: 'Service', value: 'Server Rental', inline: false }, - ) - .setFooter({ text: 'This is a mock invoice for demonstration' }) - .setTimestamp(); - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('invoice_pay') - .setLabel('Mark as Paid') - .setStyle(ButtonStyle.Success) - .setEmoji('💳') - ); - - await interaction.reply({ embeds: [embed], components: [row] }); - } - - async closeTicket(interaction: ButtonInteraction): Promise { - const thread = interaction.channel as ThreadChannel; - if (!thread || thread.type !== ChannelType.PrivateThread) { - await interaction.reply({ content: 'This is not a ticket thread.', ephemeral: true }); - return; - } - - const ticket = await prisma.ticket.findUnique({ - where: { threadId: thread.id }, - }); - - if (!ticket) { - await interaction.reply({ content: 'Ticket not found in database.', ephemeral: true }); - return; - } - - await interaction.reply({ content: '🔒 Closing ticket and saving transcript...' }); - - const transcript = await this.generateTranscript(thread); - - await prisma.ticket.update({ - where: { threadId: thread.id }, - data: { - status: 'closed', - transcript, - closedAt: new Date(), - }, - }); - - await thread.setLocked(true, 'Ticket closed'); - await thread.setArchived(true, 'Ticket closed'); - - console.log(`🎫 Ticket closed: ${thread.name}`); - } - - private async generateTranscript(thread: ThreadChannel): Promise { - const messages: Message[] = []; - let lastId: string | undefined; - - while (true) { - const batch = await thread.messages.fetch({ - limit: 100, - before: lastId, - }); - - if (batch.size === 0) break; - - messages.push(...batch.values()); - lastId = batch.last()?.id; - } - - messages.reverse(); - - const transcript = messages - .map(m => `[${m.createdAt.toISOString()}] ${m.author.tag}: ${m.content}`) - .join('\n'); - - return transcript; - } - - async createSupportPanel(channel: TextChannel): Promise { - const embed = new EmbedBuilder() - .setColor(0x5865f2) - .setTitle('🎫 Support Center') - .setDescription( - 'Need help? Click a button below to open a support ticket.\n\n' + - '**Available Services:**\n' + - '• Server Rental - Rent a game server\n' + - '• Technical Support - Get help with issues\n' + - '• Billing - Payment and invoice questions' - ) - .setFooter({ text: 'Aethex Sentinel Support System' }); - - const row = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setCustomId('ticket_rental') - .setLabel('Rent Server') - .setStyle(ButtonStyle.Primary) - .setEmoji('🖥️'), - new ButtonBuilder() - .setCustomId('ticket_support') - .setLabel('Technical Support') - .setStyle(ButtonStyle.Secondary) - .setEmoji('🔧'), - new ButtonBuilder() - .setCustomId('ticket_billing') - .setLabel('Billing') - .setStyle(ButtonStyle.Secondary) - .setEmoji('💰') - ); - - await channel.send({ embeds: [embed], components: [row] }); - } -} - -export const ticketManager = new TicketManager(); diff --git a/sentinel-bot/src/modules/dashboard/StatusUpdater.ts b/sentinel-bot/src/modules/dashboard/StatusUpdater.ts deleted file mode 100644 index 0319cea..0000000 --- a/sentinel-bot/src/modules/dashboard/StatusUpdater.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Client, VoiceChannel, ChannelType } from 'discord.js'; -import { prisma } from '../../core/client'; -import { config } from '../../core/config'; - -export class StatusUpdater { - private client: Client; - private intervalId: NodeJS.Timeout | null = null; - - constructor(client: Client) { - this.client = client; - } - - start(): void { - if (this.intervalId) return; - - this.update(); - - this.intervalId = setInterval(() => { - this.update(); - }, config.dashboard.updateIntervalMs); - - console.log(`📊 Status updater started (interval: ${config.dashboard.updateIntervalMs}ms)`); - } - - stop(): void { - if (this.intervalId) { - clearInterval(this.intervalId); - this.intervalId = null; - console.log('📊 Status updater stopped'); - } - } - - async update(): Promise { - try { - const guildIds = Object.values(config.guilds).filter(id => id); - let totalMembers = 0; - const guildStats: { id: string; name: string; members: number }[] = []; - - for (const guildId of guildIds) { - const guild = this.client.guilds.cache.get(guildId); - if (guild) { - totalMembers += guild.memberCount; - guildStats.push({ - id: guild.id, - name: guild.name, - members: guild.memberCount, - }); - - await prisma.guildConfig.upsert({ - where: { id: guild.id }, - update: { - name: guild.name, - updatedAt: new Date(), - }, - create: { - id: guild.id, - name: guild.name, - type: this.getGuildType(guild.id), - }, - }); - } - } - - if (config.dashboard.statusChannelId && config.guilds.hub) { - await this.updateStatusChannel(totalMembers); - } - - console.log(`📊 Network status: ${totalMembers} total members across ${guildStats.length} guilds`); - } catch (error) { - console.error('❌ Failed to update status:', error); - } - } - - private async updateStatusChannel(totalMembers: number): Promise { - try { - const hubGuild = this.client.guilds.cache.get(config.guilds.hub); - if (!hubGuild) return; - - const channel = hubGuild.channels.cache.get(config.dashboard.statusChannelId!); - if (!channel || channel.type !== ChannelType.GuildVoice) return; - - const voiceChannel = channel as VoiceChannel; - const newName = `🟢 Network: ${totalMembers.toLocaleString()} Users`; - - if (voiceChannel.name !== newName) { - await voiceChannel.setName(newName); - console.log(`📊 Updated status channel: ${newName}`); - } - } catch (error) { - console.error('❌ Failed to update status channel:', error); - } - } - - private getGuildType(guildId: string): string { - const entries = Object.entries(config.guilds); - const found = entries.find(([, id]) => id === guildId); - return found ? found[0] : 'unknown'; - } - - async getNetworkStats(): Promise<{ - totalMembers: number; - totalGuilds: number; - guilds: { id: string; name: string; members: number; type: string }[]; - }> { - const guildIds = Object.values(config.guilds).filter(id => id); - const guilds: { id: string; name: string; members: number; type: string }[] = []; - - for (const guildId of guildIds) { - const guild = this.client.guilds.cache.get(guildId); - if (guild) { - guilds.push({ - id: guild.id, - name: guild.name, - members: guild.memberCount, - type: this.getGuildType(guild.id), - }); - } - } - - return { - totalMembers: guilds.reduce((sum, g) => sum + g.members, 0), - totalGuilds: guilds.length, - guilds, - }; - } -} diff --git a/sentinel-bot/src/modules/federation/FederationManager.ts b/sentinel-bot/src/modules/federation/FederationManager.ts deleted file mode 100644 index 37a0b8f..0000000 --- a/sentinel-bot/src/modules/federation/FederationManager.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Guild, GuildMember, Role } from 'discord.js'; -import { prisma } from '../../core/client'; -import { config } from '../../core/config'; - -interface RoleMapping { - sourceGuild: string; - sourceRole: string; - targetGuild: string; - targetRole: string; - roleName: string; -} - -export class FederationManager { - private roleMappings: RoleMapping[] = []; - - async loadMappings(): Promise { - this.roleMappings = await prisma.roleMapping.findMany(); - console.log(`📋 Loaded ${this.roleMappings.length} role mappings`); - } - - async addMapping(mapping: Omit): Promise { - await prisma.roleMapping.create({ - data: mapping, - }); - await this.loadMappings(); - } - - async syncRoleGrant(member: GuildMember, role: Role): Promise { - const sourceGuildId = member.guild.id; - - const mappings = this.roleMappings.filter( - m => m.sourceGuild === sourceGuildId && m.sourceRole === role.id - ); - - if (mappings.length === 0) return; - - console.log(`🔄 Syncing role "${role.name}" for ${member.user.tag} to ${mappings.length} guild(s)`); - - for (const mapping of mappings) { - try { - const targetGuild = member.client.guilds.cache.get(mapping.targetGuild); - if (!targetGuild) { - console.warn(`⚠️ Target guild ${mapping.targetGuild} not found`); - continue; - } - - const targetMember = await targetGuild.members.fetch(member.id).catch(() => null); - if (!targetMember) { - console.warn(`⚠️ User ${member.user.tag} not in target guild ${targetGuild.name}`); - continue; - } - - const targetRole = targetGuild.roles.cache.get(mapping.targetRole); - if (!targetRole) { - console.warn(`⚠️ Target role ${mapping.targetRole} not found in ${targetGuild.name}`); - continue; - } - - if (!targetMember.roles.cache.has(targetRole.id)) { - await targetMember.roles.add(targetRole, `Federation sync from ${member.guild.name}`); - console.log(`✅ Granted "${targetRole.name}" to ${member.user.tag} in ${targetGuild.name}`); - } - } catch (error) { - console.error(`❌ Failed to sync role to guild ${mapping.targetGuild}:`, error); - } - } - - await this.updateUserRoles(member.id); - } - - async syncRoleRemove(member: GuildMember, role: Role): Promise { - const sourceGuildId = member.guild.id; - - const mappings = this.roleMappings.filter( - m => m.sourceGuild === sourceGuildId && m.sourceRole === role.id - ); - - if (mappings.length === 0) return; - - console.log(`🔄 Removing synced role "${role.name}" for ${member.user.tag} from ${mappings.length} guild(s)`); - - for (const mapping of mappings) { - try { - const targetGuild = member.client.guilds.cache.get(mapping.targetGuild); - if (!targetGuild) continue; - - const targetMember = await targetGuild.members.fetch(member.id).catch(() => null); - if (!targetMember) continue; - - const targetRole = targetGuild.roles.cache.get(mapping.targetRole); - if (!targetRole) continue; - - if (targetMember.roles.cache.has(targetRole.id)) { - await targetMember.roles.remove(targetRole, `Federation sync removal from ${member.guild.name}`); - console.log(`✅ Removed "${targetRole.name}" from ${member.user.tag} in ${targetGuild.name}`); - } - } catch (error) { - console.error(`❌ Failed to remove synced role:`, error); - } - } - - await this.updateUserRoles(member.id); - } - - private async updateUserRoles(userId: string): Promise { - const syncedRoles = this.roleMappings - .filter(m => m.sourceGuild === config.guilds.hub) - .map(m => m.roleName); - - await prisma.user.upsert({ - where: { id: userId }, - update: { roles: syncedRoles }, - create: { id: userId, roles: syncedRoles }, - }); - } - - getGuildType(guildId: string): string | null { - const entries = Object.entries(config.guilds); - const found = entries.find(([, id]) => id === guildId); - return found ? found[0] : null; - } -} - -export const federationManager = new FederationManager(); diff --git a/sentinel-bot/src/modules/security/HeatSystem.ts b/sentinel-bot/src/modules/security/HeatSystem.ts deleted file mode 100644 index c05a12a..0000000 --- a/sentinel-bot/src/modules/security/HeatSystem.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Guild, GuildMember, PermissionFlagsBits } from 'discord.js'; -import { prisma } from '../../core/client'; -import { config } from '../../core/config'; - -type DangerousAction = 'CHANNEL_DELETE' | 'BAN_ADD' | 'KICK' | 'ROLE_DELETE' | 'WEBHOOK_DELETE'; - -interface HeatEvent { - userId: string; - action: DangerousAction; - timestamp: Date; -} - -export class HeatSystem { - private heatCache: Map = new Map(); - - isWhitelisted(userId: string): boolean { - return config.security.whitelistedUsers.includes(userId); - } - - async recordAction(userId: string, action: DangerousAction, guild: Guild): Promise { - if (this.isWhitelisted(userId)) { - return false; - } - - const now = new Date(); - const event: HeatEvent = { userId, action, timestamp: now }; - - await prisma.heatEvent.create({ - data: { userId, action, timestamp: now }, - }); - - const events = this.heatCache.get(userId) || []; - events.push(event); - - const windowStart = new Date(now.getTime() - config.security.heatWindowMs); - const recentEvents = events.filter(e => e.timestamp >= windowStart); - this.heatCache.set(userId, recentEvents); - - console.log(`🔥 Heat event: ${action} by ${userId} (${recentEvents.length}/${config.security.heatThreshold})`); - - if (recentEvents.length >= config.security.heatThreshold) { - console.log(`🚨 THRESHOLD EXCEEDED for ${userId}!`); - await this.triggerLockdown(userId, guild, recentEvents); - return true; - } - - return false; - } - - private async triggerLockdown(userId: string, guild: Guild, events: HeatEvent[]): Promise { - console.log(`🔒 LOCKDOWN INITIATED in ${guild.name}`); - - try { - const member = await guild.members.fetch(userId).catch(() => null); - if (member && member.bannable) { - await member.ban({ - reason: `[SENTINEL] Anti-nuke triggered: ${events.length} dangerous actions in ${config.security.heatWindowMs}ms`, - deleteMessageSeconds: 0, - }); - console.log(`🔨 Banned ${member.user.tag} for nuke attempt`); - } - } catch (error) { - console.error('❌ Failed to ban attacker:', error); - } - - try { - const everyoneRole = guild.roles.everyone; - await everyoneRole.setPermissions( - everyoneRole.permissions.remove([ - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.AddReactions, - PermissionFlagsBits.CreatePublicThreads, - PermissionFlagsBits.CreatePrivateThreads, - ]), - '[SENTINEL] Lockdown mode activated' - ); - console.log(`🔒 Locked down @everyone permissions`); - } catch (error) { - console.error('❌ Failed to modify @everyone permissions:', error); - } - - await this.sendLockdownAlert(guild, userId, events); - - await prisma.user.upsert({ - where: { id: userId }, - update: { heatLevel: events.length }, - create: { id: userId, heatLevel: events.length }, - }); - } - - private async sendLockdownAlert(guild: Guild, attackerId: string, events: HeatEvent[]): Promise { - const owner = await guild.fetchOwner().catch(() => null); - if (!owner) return; - - try { - await owner.send({ - content: `🚨 **LOCKDOWN ALERT** 🚨\n\n` + - `Server: **${guild.name}**\n` + - `Attacker ID: \`${attackerId}\`\n` + - `Actions detected: ${events.length}\n` + - `Actions: ${events.map(e => e.action).join(', ')}\n\n` + - `The attacker has been banned and @everyone permissions have been restricted.\n` + - `Please review audit logs and restore permissions when safe.`, - }); - } catch (error) { - console.error('❌ Failed to DM server owner:', error); - } - } - - async unlockGuild(guild: Guild): Promise { - try { - const everyoneRole = guild.roles.everyone; - await everyoneRole.setPermissions( - everyoneRole.permissions.add([ - PermissionFlagsBits.SendMessages, - PermissionFlagsBits.AddReactions, - ]), - '[SENTINEL] Lockdown lifted' - ); - console.log(`🔓 Lifted lockdown in ${guild.name}`); - } catch (error) { - console.error('❌ Failed to unlock guild:', error); - } - } - - clearHeat(userId: string): void { - this.heatCache.delete(userId); - } -} - -export const heatSystem = new HeatSystem(); diff --git a/sentinel-bot/tsconfig.json b/sentinel-bot/tsconfig.json deleted file mode 100644 index 41d842d..0000000 --- a/sentinel-bot/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "commonjs", - "lib": ["ES2022"], - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, - "experimentalDecorators": true, - "emitDecoratorMetadata": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/templates/add_bot.html b/templates/add_bot.html deleted file mode 100644 index 4f93bbb..0000000 --- a/templates/add_bot.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Add Bot - Bot Master{% endblock %} - -{% block content %} - - -
-
-
- - -
- -
- - -
- -
- - -
- -
- - - - The URL where your bot exposes its health/status endpoint - -
- -
- - - - Used for accessing protected bot management endpoints - -
- -
- - Cancel -
-
-
-{% endblock %} diff --git a/templates/base.html b/templates/base.html deleted file mode 100644 index 30070ef..0000000 --- a/templates/base.html +++ /dev/null @@ -1,274 +0,0 @@ - - - - - - {% block title %}Bot Master{% endblock %} - - {% block extra_css %}{% endblock %} - - - - -
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} -
- {% for category, message in messages %} -
{{ message }}
- {% endfor %} -
- {% endif %} - {% endwith %} - - {% block content %}{% endblock %} -
- - {% block extra_js %}{% endblock %} - - diff --git a/templates/bots.html b/templates/bots.html deleted file mode 100644 index 3f2af11..0000000 --- a/templates/bots.html +++ /dev/null @@ -1,108 +0,0 @@ -{% extends "base.html" %} - -{% block title %}All Bots - Bot Master{% endblock %} - -{% block extra_css %} - -{% endblock %} - -{% block content %} - - -
- {% if bots %} - - - - - - - - - - - - - {% for bot in bots %} - - - - - - - - - {% endfor %} - -
BotStatusServersCommandsLast CheckedActions
-
-
{{ bot.name[0].upper() }}
-
-
{{ bot.name }}
-
{{ bot.bot_type|capitalize }}
-
-
-
- - - {{ bot.status|capitalize }} - - {{ bot.guild_count or 0 }}{{ bot.command_count or 0 }} - {% if bot.last_checked %} - {{ bot.last_checked.strftime('%b %d, %H:%M') }} - {% else %} - Never - {% endif %} - - View -
- {% else %} -

No bots added yet.

- {% endif %} -
-{% endblock %} diff --git a/templates/dashboard.html b/templates/dashboard.html deleted file mode 100644 index 7e05d14..0000000 --- a/templates/dashboard.html +++ /dev/null @@ -1,283 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Dashboard - Bot Master{% endblock %} - -{% block extra_css %} - -{% endblock %} - -{% block content %} - - -
-
-
{{ stats.total_bots }}
-
Total Bots
-
-
-
{{ stats.online_bots }}
-
Online
-
-
-
{{ stats.total_guilds }}
-
Servers
-
-
-
{{ stats.total_commands }}
-
Commands
-
-
- -{% if bots %} -

Your Bots

-
- {% for bot in bots %} -
-
-
{{ bot.name }}
- - - {{ bot.status|capitalize }} - -
- - {% if bot.description %} -
{{ bot.description[:100] }}{% if bot.description|length > 100 %}...{% endif %}
- {% endif %} - -
-
-
{{ bot.guild_count or 0 }}
-
Servers
-
-
-
{{ bot.command_count or 0 }}
-
Commands
-
-
-
{{ (bot.uptime_seconds // 3600) if bot.uptime_seconds else 0 }}h
-
Uptime
-
-
- -
- View Details - -
-
- {% endfor %} -
- - -{% else %} -
-

No Bots Added Yet

-

Start by adding your first Discord bot to manage it from this dashboard.

- Add Your First Bot -
-{% endif %} -{% endblock %} - -{% block extra_js %} - - -{% endblock %} diff --git a/templates/edit_bot.html b/templates/edit_bot.html deleted file mode 100644 index 667a1ff..0000000 --- a/templates/edit_bot.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Edit {{ bot.name }} - Bot Master{% endblock %} - -{% block content %} - - -
-
-
- - -
- -
- - -
- -
- - -
- -
- - - - The URL where your bot exposes its health/status endpoint - -
- -
- - - - Used for accessing protected bot management endpoints. Leave blank to keep current settings. - -
- -
- - Cancel -
-
-
-{% endblock %} diff --git a/templates/view_bot.html b/templates/view_bot.html deleted file mode 100644 index 12f9e2a..0000000 --- a/templates/view_bot.html +++ /dev/null @@ -1,254 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ bot.name }} - Bot Master{% endblock %} - -{% block extra_css %} - -{% endblock %} - -{% block content %} -
-
-
{{ bot.name[0].upper() }}
-
-

{{ bot.name }}

- {{ bot.bot_type|capitalize }} Bot -
- - - {{ bot.status|capitalize }} - -
-
- - Edit -
-
- -{% if bot.description %} -
-

{{ bot.description }}

-
-{% endif %} - -
-
-

Statistics

-
-
-
{{ bot.guild_count or 0 }}
-
Servers
-
-
-
{{ bot.command_count or 0 }}
-
Commands
-
-
-
{{ (bot.uptime_seconds // 3600) if bot.uptime_seconds else 0 }}h
-
Uptime
-
-
-
- -
-

Configuration

-
- Health Endpoint -
- {% if bot.health_endpoint %} -
{{ bot.health_endpoint }}
- {% else %} -
Not configured
- {% endif %} - -
- Admin Token - {{ "Configured" if bot.admin_token else "Not set" }} -
-
- -
-

Details

-
- Created - {{ bot.created_at.strftime('%b %d, %Y %H:%M') if bot.created_at else 'Unknown' }} -
-
- Last Checked - {{ bot.last_checked.strftime('%b %d, %Y %H:%M') if bot.last_checked else 'Never' }} -
-
- Last Updated - {{ bot.updated_at.strftime('%b %d, %Y %H:%M') if bot.updated_at else 'Unknown' }} -
-
-
- -
-

Danger Zone

-

Once you delete a bot, there is no going back. Please be certain.

-
- -
-
-{% endblock %} - -{% block extra_js %} - - -{% endblock %}