# First Time Setup

Welcome! This guide will walk you through the process of setting up your Drako Bot from start to finish. Follow each step carefully, and you'll be up and running in no time.

### Step 1: Setting Up Your Bot

#### 1.1 Obtain Your Bot Token

1. Visit the [Discord Developer Portal](https://discord.com/developers/applications).
2. Click **"New Application"** and give it a name.
3. Go to the **"Installation"** tab:
   * Uncheck **User Install**
   * Set **Install Link** to `"None"`

<figure><img src="/files/L1UGeeCXS50DTQJhSvob" alt=""><figcaption></figcaption></figure>

1. Navigate to the **"Bot"** tab:
   * Click **"Add Bot"**
   * Under **Privileged Gateway Intents**, enable:
     * `PRESENCE INTENT`
     * `SERVER MEMBERS INTENT`
     * `MESSAGE CONTENT INTENT`
   * Under **Public Bot** settings:
     * Disable **Public Bot**
     * Untick **Require OAuth2 Code Grant**

<figure><img src="/files/xMuEjiJLly6Ueg5dSMf5" alt=""><figcaption></figcaption></figure>

* Click **Save Changes**
* Copy your **Bot Token** from the Token section

{% hint style="success" %}
**Note:** All of these options are found under the **Bot** tab.
{% endhint %}

***

#### 1.2 Get Your License Key

Join our [Discord Server](https://discord.gg/drako) and navigate to the [`✅┃verify-purchase`](https://discord.com/channels/1192943606961557556/1322254110434525195) channel to verify your purchase and receive your license key.

***

### Step 2: Configuring Your Bot

Update your bot’s configuration file `core.yml`, with the following parameters:

```yaml
Version: "1.8.6"
BotToken: "YOUR_BOT_TOKEN"
LicenseKey: "YOUR_LICENSE_KEY"
mongoURI: "YOUR_MONGO_URI"
BotName: "Drako Bot"
LogCommands: true
Statistics: true
Timezone: "America/New_York"
```

***

### Step 3: Setting Up MongoDB

Drako Bot uses MongoDB for data storage.

Follow the steps in the [**MongoDB Setup Guide**](https://docs.drakodevelopment.net/getting-started/mongodb-setup) to:

* Create a MongoDB Atlas account
* Set up a new cluster and database
* Obtain your connection string (`MongoURI`)
* Follow the guide [here](https://docs.drakodevelopment.net/misc/mongodb-setup)

{% hint style="danger" %} <mark style="color:red;">**Important:**</mark> Do not skip this step! It is required.
{% endhint %}

***

### Step 4: Installing Node.js

Download and install **Node.js v21.7.3**:

[Download Node.js v21.7.3](https://nodejs.org/dist/v21.7.3/node-v21.7.3-x64.msi)

Follow the installation prompts to complete the setup.

{% hint style="success" %} <mark style="color:green;">**Note:**</mark> You can use our [Pterodactyl Guide](https://docs.drakodevelopment.net/getting-started/pterodactyl-setup-guide) if you aren't hosting it locally
{% endhint %}

***

### Step 5: Inviting Your Bot

1. In the Discord Developer Portal, go to **OAuth2 > URL Generator**
2. Under **Scopes**, select:
   * `bot`
   * `applications.commands`
3. Under **Bot Permissions**, select:
   * `Administrator`
4. Copy the generated URL and open it in your browser
5. Select your server and authorize the bot

***

### Step 6: Running Your Bot

#### 6.1 Navigate to the Bot Directory

Open Command Prompt or Terminal and navigate to the folder where your bot files are located:

```bash
cd path/to/your/bot
```

{% hint style="success" %}
Video Tutorial: <https://www.youtube.com/watch?v=neQUxiLPglg>
{% endhint %}

#### 6.2 Install Dependencies

Install required dependencies using npm:

```markup
npm install
```

#### 6.3 Start the Bot

Start your bot with:

```markup
npm start
```

If `npm` isn't recognized, restart your terminal and try again.

{% hint style="success" %}
**Note:** If `npm` isn't recognized, restart your terminal and try again.
{% endhint %}

***

### Support and Troubleshooting

If you run into any issues during setup, join our Support Server and open a ticket. We're happy to help.


# How to Update

This guide walks you through how to safely update Drako Bot to the latest version, while keeping your configuration and addons.

### Overview

**What you’ll do:**

1. Download the latest Drako Bot files
2. Remove all old files and folders (except `config` and `addon`)
3. Copy in the new files and folders

{% hint style="success" %}
&#x20;**Note:** Your `config` will automatically update when you start Drako Bot with the new version.
{% endhint %}

***

### Before You Start

* Make a quick backup of your bot folder, just in case:
  * Copy the entire bot folder to another location, or
  * At least back up the `config` and `addon` folders.

***

### Step 1 — Download the Latest Files

1. [Download](https://builtbybit.com/resources/drako-bot-multi-purpose-discord-bot.22266/) the latest release of Drako Bot.
2. Extract the downloaded archive to a temporary folder on your desktop.

***

### Step 2 — Delete Old Files (Keep `config` & `addon`)

Navigate to your existing **Drako Bot** folder.

1. **Delete all old files and folders** in the bot’s directory
2. **Do NOT delete:**
   * `config` folder
   * `addon` folder

{% hint style="danger" %}
**Important:** Double-check you did not delete `config` or `addon`. Those contain your settings and extra functionality.
{% endhint %}

***

### Step 3 — Add the New Files

1. Open the folder where you extracted the **latest Drako Bot** files (from Step 1).
2. Select **all** of the new files and folders.
3. Copy them into your existing Drako Bot directory (the one that still has `config` and `addon`).

{% hint style="danger" %}
**Important:** Make sure that you do not `overwrite` the `config` & addon folder
{% endhint %}

***

### Step 4 - Start the bot

1. Apon starting the bot, your config will automatically update
2. Node packages may need to be reinstalled. Run `npm install` to install the latest packages


# Dashboard Setup

Configuring the ticket dashboard

**Overview**\
The Drako Bot dashboard provides a powerful interface for managing your Discord bot. This guide will walk you through the setup and configuration process.

**Prerequisites**\
Before configuring the dashboard, ensure you have:

* Node.js installed (v18.20.6)
* Access to your Discord server with administrator permissions

**Basic Configuration**

#### Step 1: Discord Application Setup

1. Go to the **Discord Developer Portal**
2. Select your application (Bot)
3. Navigate to the **OAuth2** section
4. Add a redirect URL:
   * Use: **`http://IP:PORT/api/auth/callback`**
   * Make sure to replace **`IP` & `PORT`** with actual values.
5. Copy your **Client ID** and **Client Secret** from the OAuth2 page.

#### Step 2: Dashboard Configuration

In your `dashboard.yml` file, configure the following settings:

```yaml
Dashboard:
  Enabled: true
  ClientID: "YOUR_CLIENT_ID"
  ClientSecret: "YOUR_CLIENT_SECRET"
  Url: "http://IP:PORT"
  Port: 7000
  Auth:
    JWTSecret: "YOUR_SECURE_JWT_SECRET" # Make sure this is at least 32 long
```

{% hint style="danger" %} <mark style="color:red;">**Important:**</mark> The `Url` should **not** include `/api/auth/callback`
{% endhint %}

Replace the placeholders with your actual values:

* **`YOUR_CLIENT_ID`**: Your Discord application's Client ID
* **`YOUR_CLIENT_SECRET`**: Your Discord application's Client Secret
* **`YOUR_SECURE_JWT_SECRET`**: A secure random string for JWT token encryption

{% hint style="danger" %} <mark style="color:red;">**Important:**</mark> Make sure the JST Secret is 32 characters or longer
{% endhint %}

#### Step 3: Permissions Configuration

Configure role-based access control in your **`config.yml`**:

```yaml
Permissions:
  Dashboard:
    Login: ["ROLE_ID_1", "ROLE_ID_2"] # Roles that can access the dashboard
    Usage: ["ROLE_ID_1"] # Roles that can view analytics
    Settings: ["ROLE_ID_1"] # Roles that can modify settings
    Embed: ["ROLE_ID_1"] # Roles that can use the embed builder
    Suggestions: ["ROLE_ID_1"] # Roles that can manage suggestions
```

{% hint style="success" %}
**Note:** Login & pages won't be available if roles are not configured.
{% endhint %}

***

**Advanced Configuration**

#### Custom Navigation

You can customize the dashboard's navigation menu through the settings page:

1. Access the **dashboard settings**
2. Navigate to **Navigation Settings**
3. Add custom links with:
   * **Name**: Display name
   * **URL**: Target URL
   * **Icon**: FontAwesome icon name
   * **External**: Toggle for external links

#### Appearance Settings

Customize the dashboard's appearance:

1. Access **dashboard settings**
2. Navigate to **Dashboard Settings**
3. Configure:
   * **Navigation Name**: Brand name in the sidebar
   * **Tab Name**: Browser tab title
   * **Favicon**: Custom favicon URL

***

**Security Considerations**

#### JWT Secret

* Use a strong, random string for **`JWTSecret`**
* **Minimum recommended length**: 32 characters
* Keep this secret secure and never share it

***

**Troubleshooting**

#### Common Issues

**Cannot Access Dashboard**

* Verify the correct port is open
* Check if the Discord application **OAuth2** settings are correct
* Ensure the user has the required role

**Authentication Errors**

* Verify **`ClientID`** and **`ClientSecret`** are correct

{% hint style="danger" %} <mark style="color:red;">**Important:**</mark> If you receive `AuthError: Authentication failed` your client ID or secret is incorrect.
{% endhint %}

* Check if the redirect URL matches exactly
* Ensure **`JWTSecret`** is properly set
* Ensure that the URL has no leading /, such as <http://192.168.0.0:3000/>

{% hint style="success" %}
**Example**: URLs should always be filled in as Url: "<http://localhost:3000>" and not "<http://localhost:3000/>"
{% endhint %}

**Permission Issues**

* Verify role IDs are correct
* Check if roles are properly assigned
* Ensure the bot has necessary permissions


# Nginx Configuration

Configure the dashboard to work with your domain

{% stepper %}
{% step %}

### Prerequisites

```bash
sudo mkdir -p /var/www
cd /var/www
git clone https://github.com/YouSeeMeRunning2/DrakoBot.git # You won't be able to use my repository
sudo mv /var/www/DrakoBot /var/www/drakobot # Ensure the folder is named drakobot
```

{% hint style="info" %}
Run `ls` to verify the folder name before renaming.
{% endhint %}

{% hint style="warning" %}
You won't be able to use my git repository, it's just an example. Your folder may not be called DrakoBot to start with — update the `sudo mv` command to fit your needs.
{% endhint %}
{% endstep %}

{% step %}

### Create A Records

Create the following DNS A records:

* **Host:** `dashboard`
* **Value:** `<Server IP>`
  {% endstep %}

{% step %}

### Install NVM and Node.js 18.20.7

```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
\. "$HOME/.nvm/nvm.sh"
nvm install 18
node -v
```

{% hint style="info" %}
Verify the installation by running `node -v`. It should output `v18.x.x`.
{% endhint %}
{% endstep %}

{% step %}

### Install Nginx

```bash
sudo apt update
sudo apt install -y nginx
```

{% endstep %}

{% step %}

### Install Dashboard Dependencies

```bash
cd /var/www/drakobot/dashboard
npm install
```

{% endstep %}

{% step %}

### Create Nginx Configuration

Edit the Nginx configuration file:

```bash
sudo nano /etc/nginx/sites-available/dashboard.youseemerunning.com
```

{% hint style="warning" %}
Replace `youseemerunning.com` with your own domain.
{% endhint %}

Add the following configuration:

```nginx
server {
    server_name dashboard.youseemerunning.com;

    large_client_header_buffers 4 32k;
    client_header_buffer_size 32k;

    location / {
        proxy_pass http://localhost:7000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";
    add_header Referrer-Policy "strict-origin-when-cross-origin";

    # Example SSL configuration (uncomment after running: sudo certbot --nginx -d dashboard.youseemerunning.com)
    # listen 443 ssl;
    # ssl_certificate /etc/letsencrypt/live/dashboard.youseemerunning.com/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/dashboard.youseemerunning.com/privkey.pem;
    # include /etc/letsencrypt/options-ssl-nginx.conf;
    # ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    listen 80;
}

# Example HTTPS redirect (uncomment after enabling SSL above)
# server {
#     listen 80;
#     server_name dashboard.youseemerunning.com;
#     return 301 https://$host$request_uri;
# }
```

{% hint style="info" %}
Press `CTRL + X`, then `Y`, then `Enter` to save the file.
{% endhint %}

{% hint style="warning" %}
Replace `youseemerunning.com` with your own domain and update **5173** to match your port
{% endhint %}
{% endstep %}

{% step %}

### Enable the Site and Restart Nginx

```bash
sudo ln -s /etc/nginx/sites-available/dashboard.youseemerunning.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
```

{% hint style="info" %}
Run `sudo nginx -t` to test the configuration before restarting.
{% endhint %}

{% hint style="warning" %}
Replace `youseemerunning.com` with your own domain.
{% endhint %}
{% endstep %}

{% step %}

### Install SSL Certificate

```bash
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d dashboard.youseemerunning.com
```

{% hint style="info" %}
Follow the prompts to complete the SSL setup.
{% endhint %}

{% hint style="warning" %}
Replace `youseemerunning.com` with your own domain.
{% endhint %}
{% endstep %}

{% step %}

### Edit the Bot Configuration

```bash
cd /var/www/drakobot
sudo nano config/modules/dashboard.yml
```

Update the bot settings with the following values:

```yaml
Url: https://dashboard.youseemerunning.com
```

{% hint style="warning" %}
Replace `youseemerunning.com` with your own domain.
{% endhint %}
{% endstep %}

{% step %}

### Install PM2 and Run Services

```bash
npm install -g pm2

cd /var/www/drakobot
npm install
pm2 start npm --name "drakobot" -- run start

cd /var/www/drakobot/dashboard
pm2 start npm --name "dashboard" -- run dev

pm2 save
pm2 startup
```

{% hint style="info" %}
PM2 keeps both the bot and dashboard running. The dashboard runs Vite on port 5173.
{% endhint %}
{% endstep %}
{% endstepper %}

Your Drako Bot dashboard should now be up and running!


# Developer Mode (Copying IDs)

Discord IDs are unique numbers used to identify servers, channels, roles, users, and categories. They’re essential when setting up bots, webhooks, or troubleshooting issues.

#### Step 1 — Enable Developer Mode

Before you can copy IDs, you’ll need to enable Developer Mode in Discord:

* Open User Settings (⚙️ gear icon at the bottom left).
* Scroll down and select **Advanced**.
* Toggle **Developer Mode → ON**.

***

#### Step 2 — Copy Different Types of IDs

Once Developer Mode is on, you can right-click to copy IDs.

* **Server ID** → Right-click the server name (top-left) → *Copy Server ID*
* **Channel ID** → Right-click the channel name → *Copy Channel ID*
* **Category ID** → Right-click the category header → *Copy Category ID*
* **Role ID** → Go to *Server Settings → Roles* → Right-click a role → *Copy Role ID*
* **User ID** → Right-click a username or avatar → *Copy User ID*

***

#### Step 3 — Copy Emoji IDs

To get the ID of a custom emoji:

* Type a backslash (`\`) followed by the emoji in chat.

{% hint style="success" %}
**Hint:** You will want to type `\:DrakoTickets:` for example&#x20;
{% endhint %}

* Press **Enter** (it won’t send an actual emoji, just the raw format).
* You’ll see something like `<:emojiName:123456789012345678>`.
* Copy this **RAW** format to use it witnin the bot modules.&#x20;

*Tip: For animated emojis, you’ll see `<a:emojiName:123456789012345678>` — the ID works the same way.*

***

#### Step 4 — Copy Image Links (For Embeds)

If you want to embed an image (for example, in a bot or webhook), you’ll need its direct URL:

* Find the image you want in Discord (in chat or uploaded).
* Right-click the image.
* Select *Copy Image Address*.
* Paste the link wherever you need it (e.g., inside an embed).

💡 **Hint:** The link should end in `.png`, `.jpg`, or `.gif`. If it doesn’t, it may not embed correctly.

***

#### 💡 Tips & Best Practices

* IDs are long numbers (17–20 digits).
* Replace placeholders like `CHANNEL_ID` with actual IDs.
* Make sure your bot has permissions to access the IDs or images you’re using.

***

#### ⚠️ Common Problems

* **"Invalid snowflake"** → You pasted a placeholder instead of a real ID.
* **"Channel not found"** → Wrong server, or your bot doesn’t have access.
* **No "Copy ID" option** → Developer Mode isn’t enabled.
* **Image won’t embed** → Double-check the link ends in `.png`, `.jpg`, or `.gif`.


# Pterodactyl Setup Guide

This section is for users who want to host the bot on a server using the Pterodactyl Panel.

### Step 1: Upload Your Bot Files

1. ZIP your bot files on your local machine.
2. In your Pterodactyl server's **File Manager (File Tab)**, upload the ZIP file.
3. Once the upload completes, right-click the ZIP file (Or ...) and select **Unarchive**.
4. Confirm that all your bot files & folders (e.g., `package.json`, `config`, etc.) are in the root directory.

<figure><img src="/files/VQIrmGJn4znrPegHuG7y" alt=""><figcaption></figcaption></figure>

### Step 2: Configuration

1. Go to the settings / startup TAB and select Node js 21

<figure><img src="/files/y8p7UtHpZngVykSgLHFP" alt=""><figcaption></figcaption></figure>

### Step 2: Starting the bot

1. Go to the **Console** tab and start the bot
2. This can take up to 5 minutes while it installs the required dependencies

{% hint style="success" %} <mark style="color:green;">**Note:**</mark> You may need to run `npm install` & `npm start`, it depends on the host
{% endhint %}


# MongoDB Setup

Setup and configure MongoDB

***

### Step 1: Create an Account

#### Visit MongoDB Cloud:

1. **Open your web browser** and navigate to [MongoDB Cloud.](https://www.mongodb.com/products/platform/cloud)

#### Sign Up:

2. **Click on the Sign Up button**.
3. **Enter your email address and create a password**.
4. Alternatively, you can sign up using your **Google account**.

#### Accept Privacy Policy & Terms:

5. **Read through the Privacy Policy & Terms**.
6. **Check the box to accept them and proceed**.

#### Answer "Getting to Know You" Questions:

7. You will be presented with a few questions to help MongoDB understand your needs.
8. You can answer these questions randomly as they do not affect your setup.

***

### Step 2: Deploy Your Cluster

#### Choose the Free Tier:

1. After logging in, you will be directed to the **MongoDB Atlas dashboard**.
2. **Click on Build a Cluster**.
3. Select the **M0 (Free) tier option**.

#### Cluster Configuration:

**Name Your Cluster:**

4. You can leave the default name as **Cluster0** or choose a custom name.

**Cloud Provider & Region:**

5. Choose **AWS** as your cloud provider.
6. Select a region closest to you. (**Frankfurt** is recommended for European users).
7. Click **Create Cluster** to begin the deployment process.

***

### Step 3: Set Up a Database User

#### Create a Database User:

1. While your cluster is being created, you will need to set up a database user.
2. Go to the **Database Access tab**.
3. **Click on Add New Database User**.

#### Set a Username & Password:

4. Enter a username of your choice.
5. Create a strong password and make a note of it as you will need it later.
6. Click **Add User** to create the database user.

***

### Step 4: Choose a Connection Method

#### Connect to Your Cluster:

1. Once your cluster is created, go to the **Clusters view**.
2. **Click on the Connect button** for your cluster.

#### Choose a Connection Method:

3. Select **Connect Your Application**.

**Drivers:**

4. Select Drivers
5. Copy the connection string provided.

{% hint style="success" %}
**Note:** Ensure the connection string starts with `mongodb+srv://`.
{% endhint %}

#### Update Your Configuration File:

6. Open your **core.yml** file
7. Paste the connection string into the file.
8. Replace `<password>` in the connection string with the password you noted down earlier.

{% hint style="danger" %} <mark style="color:red;">I</mark><mark style="color:red;">**mportant:**</mark> Make sure to remove **`< >`**
{% endhint %}

***

### Step 5: Configure Network Access

#### Add IP Address:

1. Navigate to the **Network Access tab**.
2. **Click on Add IP Address**.
3. Enter the IP address of the server where you will be hosting your bot or application.
4. If you want to allow access from anywhere, you can add `0.0.0.0/0`, but this is not recommended for security reasons.
5. Click **Confirm** to add the IP address.

***

### Common Issues and Troubleshooting

#### Buffer Timeout

**Whitelist IP Address:**

1. Ensure your server's IP is whitelisted under the **Network Access tab**.

**Correct Password:**

2. Verify that the password you are using in the connection string is correct.
3. If you have forgotten your password, you can reset it in the **Database Access tab**:
   * Go to **Database Access**.
   * Click **Edit** next to the user.
   * Enter a new password and save the changes.


# Addon System

Understanding and setting up addons. This system is meant for experienced developers.

### Creating Addons for Drako Bot

To start creating addons for Drako Bot, please review the examples within the `Addons > Example` folder.

#### Types of Addons

There are two main types of addons you can create for Drako Bot:

1. **Command Addons**
2. **Event Addons**

#### Command Addons

* **Naming Convention**: Command addons need to be named in the format `cmd_x` (where `x` is a unique name).

**Example Command Addon**

```javascript
const { SlashCommandBuilder } = require('@discordjs/builders');

module.exports = {
    data: new SlashCommandBuilder()
        .setName('test')
        .setDescription('Test command!'),
    async execute(interaction, client) {
        interaction.reply({ content: 'This is a test', ephemeral: true });
    }
};
```

#### Event Addons

* **Naming Convention**: Event addons can be named anything. For example, `example_addon`.

**Example Event Addon**

```javascript
module.exports.run = async (client) => {
    client.on('ready', async () => {
        console.log("This is an example addon! You can delete it in addons/example");
    });
};
```

#### Steps to Create Addons

1. **Review Examples**: Start by looking at the examples provided in the `Addons > Example` folder. This will give you a good idea of the structure and conventions used for Drako Bot addons.
2. **Create a New Addon**: Based on the type of addon you want to create, follow the naming conventions and structure shown in the examples.
3. **Test Your Addon**: Before deploying your addon, make sure to test it thoroughly to ensure it works as expected and does not cause any issues with the bot.
4. **Deploy Your Addon**: Once tested, you can add your new addon to the `Addons` folder of Drako Bot and restart the bot to load the new addon.


# API Integration

Integrate your addons into core bot logic, unlocking a new level of power.

### ✨ Features

* **Event-driven architecture** — React to Discord and bot events
* **Backward compatibility** — Legacy addons still work
* **Priority system** — Control execution order of handlers
* **Error isolation** — One addon's error won't crash others
* **Rich context** — Access client, config, and language data
* **Async support** — Full `Promise` / `async-await` support

***

### 🚀 Quick Start

#### Example: HelloWorld Addon

A simple addon that responds to messages, welcomes new members, and reacts to tickets being created:

{% code expandable="true" %}

```javascript
module.exports = {
    // Only 'name' is required!
    name: 'HelloWorld',
    
    // Everything else is optional (but recommended for clarity)
    // version: '1.0.0'
    // description: '...'
    // author: 'Your Name'
    
    events: {
        // Respond to messages
        'discord:messageCreate': async (eventData, context) => {
            const { message } = eventData;
            if (message.author.bot) return;
            
            if (message.content.toLowerCase() === '!hello') {
                await message.channel.send(`Hello ${message.author.username}! 👋`);
            }
        },
        
        // Welcome new members
        'discord:guildMemberAdd': async (eventData, context) => {
            const { member } = eventData;
            console.log(`New member joined: ${member.user.username}`);
            
            const channel = member.guild.channels.cache.find(
                ch => ch.name === 'welcome' || ch.name === 'general'
            );
            if (channel) {
                await channel.send(`Welcome ${member.user.username}! 🎉`);
            }
        },
        
        // React to tickets being created
        'ticket:created': async (eventData, context) => {
            const { ticket, user } = eventData;
            console.log(`Ticket #${ticket.ticketId} was created by ${user.username}`);
        }
    }
};
```

{% endcode %}

#### ✅ With this addon

* Type `!hello` → Bot replies with a greeting
* New members join → Welcome message in **#welcome** or **#general**
* Ticket is created → Logged in console

***

### 📡 Event Reference

#### Discord Events

Listen to Discord.js events with the `discord:` prefix:

| Event                       | What It Does                | Key Data                 |
| --------------------------- | --------------------------- | ------------------------ |
| `discord:messageCreate`     | New message sent            | `message`                |
| `discord:messageUpdate`     | Message edited              | `message`, `oldMessage`  |
| `discord:messageDelete`     | Message deleted             | `message`                |
| `discord:guildMemberAdd`    | User joins server           | `member`                 |
| `discord:guildMemberRemove` | User leaves server          | `member`                 |
| `discord:guildMemberUpdate` | User roles/nickname changed | `oldMember`, `newMember` |
| `discord:voiceStateUpdate`  | Voice channel changes       | `oldState`, `newState`   |
| `discord:channelCreate`     | Channel created             | `channel`                |
| `discord:channelDelete`     | Channel deleted             | `channel`                |
| `discord:roleCreate`        | Role created                | `role`                   |
| `discord:roleDelete`        | Role deleted                | `role`                   |
| `discord:guildBanAdd`       | User banned                 | `ban`                    |
| `discord:guildBanRemove`    | User unbanned               | `ban`                    |
| `discord:interactionCreate` | Slash command/button used   | `interaction`            |
| `discord:ready`             | Bot started                 | `client`                 |

#### Bot Business Logic Events

**Ticket Events**

| Event              | What It Does                 | Key Data                                           |
| ------------------ | ---------------------------- | -------------------------------------------------- |
| `ticket:created`   | New ticket opened            | `ticket`, `user`, `ticketType`, `questions`        |
| `ticket:closed`    | Ticket closed/resolved       | `ticket`, `closedBy`, `closeReason`, `isAutoAlert` |
| `ticket:deleted`   | Ticket channel deleted       | `ticket`, `deletedBy`, `channelName`               |
| `ticket:claimed`   | Staff member claims ticket   | `ticket`, `claimer`                                |
| `ticket:unclaimed` | Staff member unclaims ticket | `ticket`, `previousClaimer`                        |
| `ticket:reviewed`  | User reviews service         | `ticket`, `rating`, `feedback`, `reviewer`         |

**Suggestion Events**

| Event                 | What It Does                 | Key Data                                               |
| --------------------- | ---------------------------- | ------------------------------------------------------ |
| `suggestion:created`  | New suggestion submitted     | `suggestion`, `author`, `text`, `message`              |
| `suggestion:accepted` | Suggestion approved by staff | `suggestion`, `acceptedBy`, `reason`, `originalAuthor` |
| `suggestion:denied`   | Suggestion rejected by staff | `suggestion`, `deniedBy`, `reason`, `originalAuthor`   |

**Leveling Events**

| Event      | What It Does                | Key Data                                               |
| ---------- | --------------------------- | ------------------------------------------------------ |
| `level:up` | User gains XP and levels up | `user`, `newLevel`, `oldLevel`, `xpGained`, `userData` |

**Auto-Moderation Events**

| Event                    | What It Does                     | Key Data                                                |
| ------------------------ | -------------------------------- | ------------------------------------------------------- |
| `autoReact:triggered`    | Bot auto-reacts to keyword       | `message`, `keyword`, `emoji`, `reaction`               |
| `autoResponse:triggered` | Bot sends auto-response          | `message`, `trigger`, `responseType`, `responseContent` |
| `moderation:antiSpam`    | Anti-spam/mass mention triggered | `type`, `user`, `action`, `reason`, `duration`          |

**Music Events**

| Event              | What It Does         | Key Data                        |
| ------------------ | -------------------- | ------------------------------- |
| `music:trackStart` | Track starts playing | `track`, `requestedBy`, `queue` |
| `music:trackAdded` | Track added to queue | `track`, `requestedBy`, `queue` |

**Giveaway Events**

| Event              | What It Does                        | Key Data                                       |
| ------------------ | ----------------------------------- | ---------------------------------------------- |
| `giveaway:created` | New giveaway started                | `giveaway`, `prize`, `winnerCount`, `hostedBy` |
| `giveaway:ended`   | Giveaway finished, winners selected | `giveaway`, `winners`, `prize`, `totalEntries` |

**Invite Events**

| Event                 | What It Does                    | Key Data                                  |
| --------------------- | ------------------------------- | ----------------------------------------- |
| `invite:memberJoined` | Member joins via tracked invite | `member`, `inviter`, `inviterInviteCount` |

**Moderation Events (Slash Commands)**

| `moderation:ban`             | A user is banned via slash command or audit log | `type`, `target`, `moderator`, `reason`, `guild`, `dmSent`, `method`                                       |
| ---------------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `moderation:unban`           | A previously banned user is unbanned            | `type`, `targetUserId`, `moderator`, `reason`, `guild`, `method`                                           |
| `moderation:kick`            | A member is removed from the server             | `type`, `target`, `moderator`, `reason`, `guild`, `dmSent`, `method`                                       |
| `moderation:warn`            | A moderator issues a warning                    | `type`, `target`, `moderator`, `reason`, `guild`, `warningCount`, `warning`, `method`                      |
| `moderation:unwarn`          | A warning is removed from a user                | `type`, `target`, `moderator`, `removedWarning`, `warningId`, `guild`, `remainingWarnings`, `method`       |
| `moderation:timeout`         | A user is muted/timed out                       | `type`, `subType`, `target`, `moderator`, `reason`, `duration`, `durationMs`, `endTime`, `guild`, `method` |
| `moderation:timeout_clear`   | Timeout or mute is lifted                       | `type`, `target`, `moderator`, `guild`, `wasMuted`, `method`                                               |
| `moderation:tempban`         | A moderator applies a timed ban                 | `type`, `target`, `moderator`, `reason`, `duration`, `endTime`, `guild`, `dmSent`, `method`                |
| `moderation:nickname_change` | A moderator edits a user’s nickname             | `type`, `target`, `moderator`, `oldNickname`, `newNickname`, `guild`, `method`                             |
| `moderation:purge`           | A moderator bulk deletes messages               | `type`, `moderator`, `channel`, `guild`, `messageCount`, `purgeType`, `originalAmount`, `method`           |

**Addon Lifecycle Events**

| Event              | What It Does                       | Key Data               |
| ------------------ | ---------------------------------- | ---------------------- |
| `addon:loaded`     | Addon loaded successfully          | `name`, `path`, `type` |
| `addon:unloaded`   | Addon unloaded                     | `name`, `path`         |
| `addon:registered` | Addon registered with event system | `name`, `events`       |

***

### 🔧 Custom Events

Emit and listen for your own events:

{% code expandable="true" %}

```javascript
// Emit
global.addonLoader.getEventManager().emitSafe('my-addon:custom-event', {
    data: 'some data',
    user,
    guild
});

// Listen
events: {
    'my-addon:custom-event': async (eventData, context) => {
        const { data, user, guild } = eventData;
        // Handle custom event
    }
}
```

{% endcode %}

***

### 🧰 Context Object

Every handler receives:

{% code expandable="true" %}

```javascript
{
    client: Client,       // Discord.js client
    config: Object,       // Bot config
    lang: Object,         // Language config
    eventName: String,    // Current event
    timestamp: Date,      // Emitted at
    guild: Guild|null,    // Guild if available
    user: User|null       // User if available
}
```

{% endcode %}

***

### ⚙️ Advanced Features

#### Priority System

Control execution order:

```javascript
module.exports = {
    name: 'HighPriority',
    priority: 100, // Higher runs first
    events: { /* ... */ }
};
```

#### Error Handling

Addons run independently:

{% code expandable="true" %}

```javascript
events: {
    'discord:messageCreate': async (eventData, context) => {
        try {
            // your logic
        } catch (err) {
            console.error('Error in my addon:', err);
            // Won't crash other addons
        }
    }
}
```

{% endcode %}

#### Database Integration

Use Mongoose inside addons:

{% code expandable="true" %}

```javascript
const mongoose = require('mongoose');

const mySchema = new mongoose.Schema({
    guildId: String,
    data: String
});

const MyModel = mongoose.model('MyData', mySchema);

// Use in events
await MyModel.findOneAndUpdate(
    { guildId: guild.id },
    { data: 'updated' },
    { upsert: true }
);
```

{% endcode %}

#### Conditional Event Handling

Run logic only in certain channels/users:

{% code expandable="true" %}

```javascript
'discord:messageCreate': async (eventData, context) => {
    const { message } = eventData;
    
    // Only in specific channels
    if (!['general', 'bot-commands'].includes(message.channel.name)) return;
    
    // Only non-bots
    if (message.author.bot) return;
    
    // Your logic here
}
```

{% endcode %}

***

### 🔄 Migration from Legacy Addons

#### Legacy Format (still works)

{% code expandable="true" %}

```javascript
module.exports = {
    run: async (client) => {
        client.on('messageCreate', async message => {
            if (message.content === '!hello') {
                await message.channel.send('Hello!');
            }
        });
    }
};
```

{% endcode %}

#### New Event API Format

{% code expandable="true" %}

```javascript
module.exports = {
    name: 'MyAddon',
    version: '1.0.0',
    events: {
        'discord:messageCreate': async (eventData, context) => {
            const { message } = eventData;
            if (message.content === '!hello') {
                await message.channel.send('Hello!');
            }
        }
    },
    
    // Optional: Keep initialization logic
    run: async (client, eventManager) => {
        console.log('Addon initialized!');
    }
};
```

{% endcode %}

#### Key Differences

* **Legacy** → `run` function, no `name` property
* **New** → `name` required, uses `events`
* **New format benefits:** event-driven API, hot reloading, enable/disable, error isolation, priorities

***

### 📋 Requirements Summary

#### Absolutely Required

* `name` (string) — Unique addon identifier

#### Required (pick one)

* `events` (object) — Event handlers
* `run` (function) — Init function

#### Optional (defaults)

* `version` → `'1.0.0'`
* `description` → `''`
* `author` → `'Unknown'`
* `priority` → `0`
* `permissions` → `[]`
* `dependencies` → `[]`

#### Minimal Working Addon

```javascript
module.exports = {
    name: 'Minimal',
    events: {
        'discord:messageCreate': async (eventData, context) => {
            // Your code here
        }
    }
};
```

***

### 💡 Practical Examples

#### Staff Notification System

{% code expandable="true" %}

```javascript
module.exports = {
    name: 'StaffNotifications',
    events: {
        'ticket:created': async ({ ticket, user, ticketType }) => {
            const staffChannel = ticket.guild.channels.cache.find(ch => ch.name === 'staff-alerts');
            if (staffChannel) {
                await staffChannel.send(`🎫 New ${ticketType.Name} ticket #${ticket.ticketId} by ${user.username}`);
            }
        },
        
        'suggestion:created': async ({ suggestion, author, text }) => {
            const staffChannel = suggestion.guild.channels.cache.find(ch => ch.name === 'suggestions');
            if (staffChannel) {
                const message = await staffChannel.send(`💡 New suggestion: "${text}" by ${author.username}`);
                await message.react('👍');
                await message.react('👎');
            }
        }
    }
};
```

{% endcode %}

#### Level Rewards System

{% code expandable="true" %}

```javascript
module.exports = {
    name: 'LevelRewards',
    events: {
        'level:up': async ({ user, newLevel, guild }) => {
            const member = guild.members.cache.get(user.id);
            
            // Milestone rewards
            if (newLevel === 10) {
                const role = guild.roles.cache.find(r => r.name === 'Active Member');
                if (role) await member.roles.add(role);
            }
            
            if (newLevel === 50) {
                const role = guild.roles.cache.find(r => r.name === 'Veteran');
                if (role) await member.roles.add(role);
            }
            
            // Celebrate big milestones
            if (newLevel % 25 === 0) {
                const channel = guild.channels.cache.find(ch => ch.name === 'general');
                if (channel) {
                    await channel.send(`🎉 ${user.username} reached level ${newLevel}! Amazing! 🎉`);
                }
            }
        }
    }
};
```

{% endcode %}

#### Auto-Moderation Logger

{% code expandable="true" %}

```javascript
module.exports = {
    name: 'ModLogger',
    events: {
        'moderation:antiSpam': async ({ type, user, action, reason, guild }) => {
            const logChannel = guild.channels.cache.find(ch => ch.name === 'mod-logs');
            if (logChannel) {
                await logChannel.send({
                    embeds: [{
                        title: '🛡️ Auto-Moderation',
                        description: `**User:** ${user.username}\n**Type:** ${type}\n**Action:** ${action}\n**Reason:** ${reason}`,
                        color: 0xff6b6b,
                        timestamp: new Date()
                    }]
                });
            }
        }
    }
};
```

{% endcode %}


# Store API

The Store API provides a powerful interface for managing server-specific stores with customizable items, categories, and reward systems.

## ✨ Features

* **Per-server stores** — Each guild has its own store
* **Multiple categories** — Pets, roles, boosters, titles, items, special
* **Reward types** — Pets, roles, boosters, titles, items, custom
* **Requirements** — Level and prestige requirements
* **Stock management** — Limited or unlimited stock
* **Purchase limits** — Max purchases per user
* **Transaction logging** — Full purchase history

## 🚀 Quick Start

The Store API is available globally as `global.storeAPI` — no require needed.

{% code title="quick-start.js" expandable="true" %}

```javascript
// Add a custom item
await global.storeAPI.addItem(guildId, {
    name: 'VIP Role',
    category: 'roles',
    price: 50000,
    rewardType: 'role',
    rewardData: { roleId: '123456789', roleDuration: null },
    emoji: '👑'
});

// Get all items in a category
const pets = await global.storeAPI.getItems(guildId, 'pets');

// Purchase an item for a user
const result = await global.storeAPI.purchaseItem(guildId, userId, itemId);
if (result.success) {
    console.log(`Purchased: ${result.item.name}`);
}
```

{% endcode %}

## 📦 Methods Reference

### addItem(guildId, itemData)

Add a new item to the store.

{% code title="addItem example" expandable="true" %}

```javascript
await global.storeAPI.addItem(guildId, {
    name: 'Dragon Pet',
    description: 'A legendary beast',
    category: 'pets',           // pets, roles, boosters, titles, items, special
    price: 50000,
    stock: -1,                  // -1 = unlimited
    maxPerUser: 1,              // -1 = unlimited
    enabled: true,
    rewardType: 'pet',
    rewardData: {
        petType: 'Dragon',
        petMultiplier: 1.15,
        petPassiveIncome: 100
    },
    requirements: {
        level: 10,
        prestige: 0
    },
    emoji: '🐉',
    color: '#FF4500',
    featured: true,
    sortOrder: 1
});
```

{% endcode %}

### removeItem(guildId, itemId)

Remove an item from the store.

{% code title="removeItem example" %}

```javascript
const removed = await global.storeAPI.removeItem(guildId, 'item-uuid');
// Returns: true if deleted, false if not found
```

{% endcode %}

### updateItem(guildId, itemId, updates)

Update an existing item.

{% code title="updateItem example" %}

```javascript
await global.storeAPI.updateItem(guildId, 'item-uuid', {
    price: 75000,
    'metadata.featured': true,
    enabled: false
});
```

{% endcode %}

### getItem(guildId, itemId)

Get a single item by ID.

{% code title="getItem example" %}

```javascript
const item = await global.storeAPI.getItem(guildId, 'item-uuid');
```

{% endcode %}

### getItems(guildId, category?)

Get all items, optionally filtered by category.

{% code title="getItems examples" %}

```javascript
// All items
const allItems = await global.storeAPI.getItems(guildId);

// Only pets
const pets = await global.storeAPI.getItems(guildId, 'pets');

// Only boosters
const boosters = await global.storeAPI.getItems(guildId, 'boosters');
```

{% endcode %}

### getCategories(guildId)

Get all categories that have items.

{% code title="getCategories example" %}

```javascript
const categories = await global.storeAPI.getCategories(guildId);
// Returns: ['pets', 'boosters', 'titles']
```

{% endcode %}

### getFeaturedItems(guildId)

Get all featured items.

{% code title="getFeaturedItems example" %}

```javascript
const featured = await global.storeAPI.getFeaturedItems(guildId);
```

{% endcode %}

### purchaseItem(guildId, userId, itemId, client?)

Process a purchase. Handles balance deduction, requirements validation, stock management, and reward application.

The `client` parameter is optional — if not provided, the API uses the client from its context.

{% code title="purchaseItem example" %}

```javascript
const result = await global.storeAPI.purchaseItem(guildId, userId, itemId);

if (result.success) {
    console.log(result.item);     // The purchased item
    console.log(result.purchase); // Purchase record
    console.log(result.reward);   // Reward message string
} else {
    console.log(result.error);    // Error message
}
```

{% endcode %}

Possible errors:

* Item not found
* Item out of stock
* User not found
* Insufficient funds
* Requires level X
* Requires prestige X
* Purchase limit reached
* You already own this pet!

### getUserPurchases(guildId, userId)

Get a user's purchase history.

{% code title="getUserPurchases example" %}

```javascript
const purchases = await global.storeAPI.getUserPurchases(guildId, userId);
// Returns array of purchase records sorted by date (newest first)
```

{% endcode %}

## 🎁 Reward Types

### pet

Adds a pet to the user's collection.

{% code title="pet reward example" %}

```javascript
rewardType: 'pet',
rewardData: {
    petType: 'Dragon',        // Pet type name
    petMultiplier: 1.15,      // Earnings multiplier (1.15 = +15%)
    petPassiveIncome: 100     // Coins per hour passive income
}
```

{% endcode %}

### role

Grants a Discord role to the user.

{% code title="role reward example" %}

```javascript
rewardType: 'role',
rewardData: {
    roleId: '123456789012345678',  // Discord role ID
    roleDuration: 2592000000       // Duration in ms (null = permanent)
}
```

{% endcode %}

### booster

Activates a temporary multiplier booster.

{% code title="booster reward example" %}

```javascript
rewardType: 'booster',
rewardData: {
    boosterType: 'Money',       // 'Money' or 'XP'
    boosterMultiplier: 1.5,     // 1.5 = +50% bonus
    boosterDuration: 86400000   // Duration in ms (24 hours)
}
```

{% endcode %}

### title

Unlocks a display title for the user.

{% code title="title reward example" %}

```javascript
rewardType: 'title',
rewardData: {
    title: '💰 Rich'   // The title string
}
```

{% endcode %}

### item

Adds a generic item to user's inventory.

{% code title="item reward example" %}

```javascript
rewardType: 'item',
rewardData: {
    itemType: 'charm',           // Custom item type
    itemData: { luckBonus: 0.05 } // Custom data
}
```

{% endcode %}

### custom

For addon-handled rewards. The reward isn't automatically applied.

{% code title="custom reward example" %}

```javascript
rewardType: 'custom',
rewardData: {
    // Your custom data here
}
```

{% endcode %}

## 📁 Categories

| Category   | Description                            |
| ---------- | -------------------------------------- |
| `pets`     | Virtual companions with bonuses        |
| `roles`    | Discord roles (temporary or permanent) |
| `boosters` | Temporary multiplier effects           |
| `titles`   | Display titles for users               |
| `items`    | Generic inventory items                |
| `special`  | Limited edition or seasonal items      |

## 💡 Addon Examples

### Adding Custom Shop Items

{% code title="CustomShopItems.js" expandable="true" %}

```javascript
module.exports = {
    name: 'CustomShopItems',
    
    async run(client) {
        for (const guild of client.guilds.cache.values()) {
            const existing = await global.storeAPI.getItem(guild.id, 'vip-membership');
            if (existing) continue;
            
            await global.storeAPI.addItem(guild.id, {
                itemId: 'vip-membership',
                name: 'VIP Membership',
                description: 'Get exclusive VIP role for 30 days',
                category: 'roles',
                price: 100000,
                maxPerUser: 1,
                rewardType: 'role',
                rewardData: {
                    roleId: 'YOUR_VIP_ROLE_ID',
                    roleDuration: 2592000000  // 30 days
                },
                emoji: '💎',
                featured: true
            });
            
            console.log(`Added VIP item to ${guild.name}`);
        }
    }
};
```

{% endcode %}

### Seasonal/Limited Items

{% code title="SeasonalStore.js" expandable="true" %}

```javascript
module.exports = {
    name: 'SeasonalStore',
    
    async run(client) {
        const month = new Date().getMonth();
        
        // December = Christmas items
        if (month === 11) {
            for (const guild of client.guilds.cache.values()) {
                const existing = await global.storeAPI.getItem(guild.id, 'santa-hat-2024');
                if (existing) continue;
                
                await global.storeAPI.addItem(guild.id, {
                    itemId: 'santa-hat-2024',
                    name: 'Santa Hat 2024',
                    description: 'Limited edition holiday item!',
                    category: 'special',
                    price: 25000,
                    stock: 100,  // Only 100 available!
                    rewardType: 'title',
                    rewardData: { title: '🎅 Santa 2024' },
                    emoji: '🎄',
                    featured: true
                });
            }
        }
    }
};
```

{% endcode %}

### Dynamic Pricing

{% code title="DynamicPricing.js" expandable="true" %}

```javascript
module.exports = {
    name: 'DynamicPricing',
    
    events: {
        'discord:ready': async (eventData, context) => {
            // Update prices every hour based on purchases
            setInterval(async () => {
                for (const guild of context.client.guilds.cache.values()) {
                    const items = await global.storeAPI.getItems(guild.id, 'pets');
                    
                    for (const item of items) {
                        // Increase price if stock is running low
                        if (item.stock > 0 && item.stock < 10) {
                            await global.storeAPI.updateItem(guild.id, item.itemId, {
                                price: Math.floor(item.price * 1.1)
                            });
                        }
                    }
                }
            }, 3600000); // Every hour
        }
    }
};
```

{% endcode %}

### Purchase Announcements

{% code title="PurchaseAnnouncer.js" expandable="true" %}

```javascript
module.exports = {
    name: 'PurchaseAnnouncer',
    
    async run(client) {
        const originalPurchase = global.storeAPI.purchaseItem.bind(global.storeAPI);
        
        global.storeAPI.purchaseItem = async (guildId, userId, itemId, c) => {
            const result = await originalPurchase(guildId, userId, itemId, c);
            
            if (result.success && result.item.metadata?.featured) {
                const guild = client.guilds.cache.get(guildId);
                const channel = guild?.channels.cache.find(
                    ch => ch.name === 'purchases' || ch.name === 'general'
                );
                
                if (channel) {
                    const user = await client.users.fetch(userId);
                    await channel.send(
                        `🎉 **${user.username}** just purchased **${result.item.name}**!`
                    );
                }
            }
            
            return result;
        };
    }
};
```

{% endcode %}

## 📊 Item Schema

Full item object structure:

{% code title="Item Schema" expandable="true" %}

```javascript
{
    itemId: 'uuid-string',
    guildId: 'guild-id',
    name: 'Item Name',
    description: 'Item description',
    category: 'pets',
    price: 50000,
    stock: -1,
    maxPerUser: -1,
    enabled: true,
    rewardType: 'pet',
    rewardData: { ... },
    requirements: {
        level: 0,
        prestige: 0
    },
    metadata: {
        emoji: '🐉',
        color: '#5865F2',
        image: null,
        featured: false,
        sortOrder: 0
    },
    createdAt: Date,
    updatedAt: Date
}
```

{% endcode %}

## 📋 Purchase Record Schema

{% code title="Purchase Record Schema" expandable="true" %}

```javascript
{
    orderId: 'uuid-string',
    guildId: 'guild-id',
    userId: 'user-id',
    itemId: 'item-id',
    itemName: 'Item Name',
    category: 'pets',
    price: 50000,
    rewardType: 'pet',
    rewardData: { ... },
    status: 'completed',
    purchasedAt: Date
}
```

{% endcode %}


# Dashboard API

Extend the DrakoBot dashboard with custom pages, navigation items, and API endpoints.

***

## ✨ Features

* **Custom Pages** — Add your own React pages to the dashboard
* **Navigation Integration** — Seamlessly add items to the sidebar
* **API Routes** — Create custom backend endpoints for your addon
* **Full React Support** — Use hooks, state, and all React features
* **Tailwind CSS** — Style with the dashboard's existing design system
* **Authentication** — Automatic auth handling for protected endpoints
* **Hot Reload** — Changes reflect instantly during development

***

## 🚀 Quick Start

### Folder Structure

```
addons/
└── my-addon/
    ├── my-addon.js          # Main addon file (events, etc.)
    └── dashboard/
        ├── config.js         # Dashboard configuration
        └── pages/
            ├── index.jsx     # Main page component
            └── settings.jsx  # Additional pages
```

### Example: Complete Dashboard Addon

**`addons/my-addon/dashboard/config.js`**

{% code expandable="true" %}

```javascript
module.exports = {
    // Register pages
    pages: [
        {
            path: '/addon/my-addon',
            component: 'index',
            title: 'My Addon'
        },
        {
            path: '/addon/my-addon/settings',
            component: 'settings',
            title: 'My Addon Settings'
        }
    ],
    
    // Add to sidebar navigation
    navItems: [
        {
            name: 'My Addon',
            path: '/addon/my-addon',
            emoji: '🚀',
            permission: null,  // null = any logged in user
            order: 50          // Lower = higher in sidebar
        }
    ],
    
    // Custom API endpoints
    apiRoutes: [
        {
            method: 'get',
            path: '/stats',
            handler: async (req, res) => {
                res.json({
                    message: 'Hello from My Addon!',
                    timestamp: new Date().toISOString()
                });
            }
        },
        {
            method: 'post',
            path: '/save',
            handler: async (req, res) => {
                const { setting } = req.body;
                // Save to database...
                res.json({ success: true, setting });
            }
        }
    ]
};
```

{% endcode %}

**`addons/my-addon/dashboard/pages/index.jsx`**

{% code expandable="true" %}

```jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';

export default function MyAddonPage() {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        const fetchData = async () => {
            try {
                // API path: /api/addons/{addonname}/{path}
                const response = await axios.get('/api/addons/myaddon/stats');
                setData(response.data);
            } catch (error) {
                console.error('Failed to fetch:', error);
            } finally {
                setLoading(false);
            }
        };
        fetchData();
    }, []);

    return (
        <div className="px-4 lg:px-6 space-y-6">
            <div className="glass-card p-6">
                <h1 className="text-xl font-bold text-foreground mb-4">
                    My Addon Dashboard
                </h1>
                
                {loading ? (
                    <p className="text-muted-foreground">Loading...</p>
                ) : (
                    <div className="glass-subtle rounded-xl p-4">
                        <p className="text-foreground">{data?.message}</p>
                        <p className="text-xs text-muted-foreground mt-2">
                            Last updated: {data?.timestamp}
                        </p>
                    </div>
                )}
            </div>
        </div>
    );
}
```

{% endcode %}

***

## 📄 Configuration Reference

### Pages

Register custom pages in your addon:

```javascript
pages: [
    {
        path: '/addon/my-addon',      // URL path (required)
        component: 'index',            // File in pages/ folder (required)
        title: 'My Addon',             // Page title (optional)
        requiredRoles: null            // Array of role IDs or null (optional)
    }
]
```

| Property        | Type            | Required | Description                              |
| --------------- | --------------- | -------- | ---------------------------------------- |
| `path`          | string          | ✅        | URL path for the page                    |
| `component`     | string          | ✅        | Filename in `pages/` (without extension) |
| `title`         | string          | ❌        | Browser tab title                        |
| `requiredRoles` | string\[]\|null | ❌        | Discord role IDs required to access      |

### Navigation Items

Add items to the dashboard sidebar:

```javascript
navItems: [
    {
        name: 'My Addon',              // Display name (required)
        path: '/addon/my-addon',       // Link destination (required)
        emoji: '🚀',                   // Emoji icon (optional)
        icon: 'faPuzzlePiece',         // FontAwesome icon (optional)
        requiredRoles: null,           // Array of role IDs or null (optional)
        order: 50                      // Sort order (optional)
    }
]
```

| Property        | Type            | Required | Description                           |
| --------------- | --------------- | -------- | ------------------------------------- |
| `name`          | string          | ✅        | Display name in sidebar               |
| `path`          | string          | ✅        | Navigation destination                |
| `emoji`         | string          | ❌        | Emoji to display                      |
| `icon`          | string          | ❌        | FontAwesome icon name                 |
| `requiredRoles` | string\[]\|null | ❌        | Discord role IDs required to see item |
| `order`         | number          | ❌        | Sort priority (lower = higher)        |

### API Routes

Create custom backend endpoints:

```javascript
apiRoutes: [
    {
        method: 'get',                 // HTTP method (required)
        path: '/stats',                // Endpoint path (required)
        requiredRoles: null,           // Array of role IDs or null (optional)
        handler: async (req, res) => { // Handler function (required)
            res.json({ data: 'value' });
        }
    }
]
```

| Property        | Type            | Required | Description                                             |
| --------------- | --------------- | -------- | ------------------------------------------------------- |
| `method`        | string          | ✅        | `get`, `post`, `put`, `delete`, `patch`                 |
| `path`          | string          | ✅        | Endpoint path (prefixed with `/api/addons/{addonname}`) |
| `handler`       | function        | ✅        | Express route handler                                   |
| `middleware`    | array           | ❌        | Additional middleware functions                         |
| `requiredRoles` | string\[]\|null | ❌        | Discord role IDs required to access                     |

***

## 🎨 Styling Guide

### Available CSS Classes

The dashboard uses Tailwind CSS with custom utility classes:

{% code expandable="true" %}

```jsx
// Card styles
<div className="glass-card p-6">         {/* Main card container */}
<div className="glass-subtle p-4">       {/* Subtle inner card */}

// Text colors
<h1 className="text-foreground">         {/* Primary text */}
<p className="text-muted-foreground">    {/* Secondary text */}

// Buttons
<button className="px-4 py-2 rounded-xl bg-primary text-primary-foreground">
    Primary Button
</button>

// Inputs
<input className="w-full h-10 px-4 rounded-xl glass-input text-sm" />

// Gradients
<div className="bg-gradient-to-br from-blue-500 to-blue-600">
```

{% endcode %}

### Responsive Design

```jsx
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
    {/* Cards auto-adjust based on screen size */}
</div>
```

***

## 🔌 API Endpoint Patterns

### Accessing Your API

Your API routes are available at:

```
/api/addons/{addonname-lowercase}/{path}
```

**Example:**

* Addon name: `MyAddon`
* Route path: `/stats`
* Full URL: `/api/addons/myaddon/stats`

### Making API Calls

{% code expandable="true" %}

```jsx
import axios from 'axios';

// GET request
const response = await axios.get('/api/addons/myaddon/stats');

// POST request
const response = await axios.post('/api/addons/myaddon/save', {
    setting: 'value'
});

// With error handling
try {
    const { data } = await axios.get('/api/addons/myaddon/stats');
    console.log(data);
} catch (error) {
    if (error.response?.status === 401) {
        console.log('Not authenticated');
    }
}
```

{% endcode %}

### Handler Examples

{% code expandable="true" %}

```javascript
apiRoutes: [
    // Simple GET
    {
        method: 'get',
        path: '/info',
        handler: async (req, res) => {
            res.json({ version: '1.0.0' });
        }
    },
    
    // With request body
    {
        method: 'post',
        path: '/settings',
        handler: async (req, res) => {
            const { enabled, message } = req.body;
            // Validate
            if (typeof enabled !== 'boolean') {
                return res.status(400).json({ error: 'Invalid enabled value' });
            }
            // Save and respond
            res.json({ success: true, enabled, message });
        }
    },
    
    // With URL parameters
    {
        method: 'get',
        path: '/user/:userId',
        handler: async (req, res) => {
            const { userId } = req.params;
            // Fetch user data...
            res.json({ userId, data: {} });
        }
    },
    
    // Database integration
    {
        method: 'get',
        path: '/guild-stats',
        handler: async (req, res) => {
            const mongoose = require('mongoose');
            const stats = await mongoose.model('GuildStats').find({});
            res.json({ stats });
        }
    }
]
```

{% endcode %}

***

## 🔐 Authentication & Permissions

### Using requiredRoles

The `requiredRoles` property accepts an array of Discord role IDs. Users must have **at least one** of the specified roles to access the resource.

```javascript
// Anyone logged in (no role restriction)
requiredRoles: null

// Require specific role(s)
requiredRoles: ['123456789012345678']

// Require any of multiple roles (OR logic)
requiredRoles: ['ROLE_ID1', 'ROLE_ID2', 'ROLE_ID3']
```

### Complete Example with Permissions

{% code expandable="true" %}

```javascript
module.exports = {
    pages: [
        {
            path: '/addon/admin-panel',
            component: 'admin',
            title: 'Admin Panel',
            // Only users with Admin or Owner role can access
            requiredRoles: ['111111111111111111', '222222222222222222']
        },
        {
            path: '/addon/admin-panel/public',
            component: 'public',
            title: 'Public Info',
            // Anyone logged in can access
            requiredRoles: null
        }
    ],
    
    navItems: [
        {
            name: 'Admin Panel',
            path: '/addon/admin-panel',
            emoji: '⚙️',
            // Only show in sidebar for admins
            requiredRoles: ['111111111111111111', '222222222222222222']
        }
    ],
    
    apiRoutes: [
        {
            method: 'get',
            path: '/public-data',
            requiredRoles: null,  // Public endpoint
            handler: async (req, res) => {
                res.json({ public: true });
            }
        },
        {
            method: 'post',
            path: '/admin-action',
            requiredRoles: ['111111111111111111'],  // Admin only
            handler: async (req, res) => {
                // Only admins reach here
                res.json({ success: true });
            }
        }
    ]
};
```

{% endcode %}

### Getting Role IDs

{% stepper %}
{% step %}
Enable Developer Mode in Discord:

* Settings → Advanced → Developer Mode
  {% endstep %}

{% step %}
Copy the role ID:

* Right-click the role → Copy Role ID
  {% endstep %}
  {% endstepper %}

### Checking User in API Handler

{% code expandable="true" %}

```javascript
{
    method: 'get',
    path: '/user-info',
    handler: async (req, res) => {
        // req.user is available after auth
        const user = req.user || req.session?.userData;
        
        if (!user) {
            return res.status(401).json({ error: 'Not authenticated' });
        }
        
        // Access user data
        res.json({ 
            userId: user.id,
            username: user.username,
            roles: user.roles  // Array of role IDs
        });
    }
}
```

{% endcode %}

***

## 🧩 Complete Examples

### Stats Dashboard

{% code expandable="true" %}

```javascript
// config.js
module.exports = {
    pages: [
        { path: '/addon/stats', component: 'index', title: 'Server Stats' }
    ],
    navItems: [
        { name: 'Stats', path: '/addon/stats', emoji: '📊', order: 10 }
    ],
    apiRoutes: [
        {
            method: 'get',
            path: '/overview',
            handler: async (req, res) => {
                const client = require('../../bot').getDiscordClient();
                
                res.json({
                    guilds: client.guilds.cache.size,
                    users: client.users.cache.size,
                    channels: client.channels.cache.size,
                    uptime: process.uptime()
                });
            }
        }
    ]
};
```

{% endcode %}

{% code expandable="true" %}

```jsx
// pages/index.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';

export default function StatsPage() {
    const [stats, setStats] = useState(null);

    useEffect(() => {
        axios.get('/api/addons/stats/overview')
            .then(res => setStats(res.data))
            .catch(console.error);
    }, []);

    if (!stats) return <div className="text-muted-foreground">Loading...</div>;

    return (
        <div className="px-4 lg:px-6">
            <h1 className="text-2xl font-bold text-foreground mb-6">Server Stats</h1>
            
            <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
                <StatCard label="Guilds" value={stats.guilds} emoji="🏠" />
                <StatCard label="Users" value={stats.users} emoji="👥" />
                <StatCard label="Channels" value={stats.channels} emoji="📺" />
                <StatCard label="Uptime" value={formatUptime(stats.uptime)} emoji="⏱️" />
            </div>
        </div>
    );
}

function StatCard({ label, value, emoji }) {
    return (
        <div className="glass-card p-4">
            <div className="text-2xl mb-2">{emoji}</div>
            <div className="text-2xl font-bold text-foreground">{value}</div>
            <div className="text-sm text-muted-foreground">{label}</div>
        </div>
    );
}

function formatUptime(seconds) {
    const hours = Math.floor(seconds / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    return `${hours}h ${minutes}m`;
}
```

{% endcode %}

### Settings Panel

{% code expandable="true" %}

```javascript
// config.js
module.exports = {
    pages: [
        { path: '/addon/my-settings', component: 'index', title: 'My Settings' }
    ],
    navItems: [
        { name: 'My Settings', path: '/addon/my-settings', emoji: '⚙️' }
    ],
    apiRoutes: [
        {
            method: 'get',
            path: '/config',
            handler: async (req, res) => {
                // Load from database or file
                res.json({
                    enabled: true,
                    welcomeMessage: 'Welcome!',
                    notifyChannel: '123456789'
                });
            }
        },
        {
            method: 'post',
            path: '/config',
            handler: async (req, res) => {
                const { enabled, welcomeMessage, notifyChannel } = req.body;
                // Save to database...
                res.json({ success: true });
            }
        }
    ]
};
```

{% endcode %}

{% code expandable="true" %}

```jsx
// pages/index.jsx
import React, { useState, useEffect } from 'react';
import axios from 'axios';

export default function SettingsPage() {
    const [settings, setSettings] = useState({
        enabled: false,
        welcomeMessage: '',
        notifyChannel: ''
    });
    const [saving, setSaving] = useState(false);

    useEffect(() => {
        axios.get('/api/addons/my-settings/config')
            .then(res => setSettings(res.data))
            .catch(console.error);
    }, []);

    const handleSave = async () => {
        setSaving(true);
        try {
            await axios.post('/api/addons/my-settings/config', settings);
            alert('Settings saved!');
        } catch (error) {
            alert('Failed to save');
        } finally {
            setSaving(false);
        }
    };

    return (
        <div className="px-4 lg:px-6 space-y-6">
            <div className="glass-card p-6">
                <h1 className="text-xl font-bold text-foreground mb-6">Settings</h1>
                
                {/* Toggle */}
                <div className="flex items-center justify-between p-4 glass-subtle rounded-xl mb-4">
                    <div>
                        <h3 className="text-sm font-medium text-foreground">Enable Feature</h3>
                        <p className="text-xs text-muted-foreground">Toggle the feature on/off</p>
                    </div>
                    <button
                        onClick={() => setSettings(s => ({ ...s, enabled: !s.enabled }))}
                        className={`w-12 h-6 rounded-full transition-colors ${
                            settings.enabled ? 'bg-primary' : 'bg-secondary'
                        }`}
                    >
                        <span className={`block w-4 h-4 rounded-full bg-white transition-transform ${
                            settings.enabled ? 'translate-x-7' : 'translate-x-1'
                        }`} />
                    </button>
                </div>
                
                {/* Text Input */}
                <div className="p-4 glass-subtle rounded-xl mb-4">
                    <label className="text-sm font-medium text-foreground block mb-2">
                        Welcome Message
                    </label>
                    <input
                        type="text"
                        value={settings.welcomeMessage}
                        onChange={e => setSettings(s => ({ ...s, welcomeMessage: e.target.value }))}
                        className="w-full h-10 px-4 rounded-xl glass-input text-sm"
                        placeholder="Enter message..."
                    />
                </div>
                
                {/* Save Button */}
                <div className="flex justify-end">
                    <button
                        onClick={handleSave}
                        disabled={saving}
                        className="px-6 py-2.5 rounded-xl bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors disabled:opacity-50"
                    >
                        {saving ? 'Saving...' : 'Save Settings'}
                    </button>
                </div>
            </div>
        </div>
    );
}
```

{% endcode %}

***

## 📋 Requirements Summary

### Minimum Required

```
addons/my-addon/
└── dashboard/
    ├── config.js      # Must export { pages, navItems, apiRoutes }
    └── pages/
        └── index.jsx  # At least one page component
```

### config.js Template

```javascript
module.exports = {
    pages: [],      // Required (can be empty)
    navItems: [],   // Required (can be empty)
    apiRoutes: []   // Required (can be empty)
};
```

### Page Component Template

```jsx
import React from 'react';

export default function MyPage() {
    return (
        <div className="px-4 lg:px-6">
            <div className="glass-card p-6">
                <h1 className="text-xl font-bold text-foreground">My Page</h1>
            </div>
        </div>
    );
}
```

***

## 🔄 Integration with Event API

Combine dashboard with event handlers:

**`addons/my-addon/my-addon.js`**

{% code expandable="true" %}

```javascript
module.exports = {
    name: 'MyAddon',
    version: '1.0.0',
    
    // Event handlers
    events: {
        'discord:messageCreate': async (eventData, context) => {
            // Handle Discord events
        }
    },
    
    // Initialization
    run: async (client, eventManager) => {
        console.log('[MyAddon] Loaded with dashboard support!');
    }
};
```

{% endcode %}

The dashboard config is automatically loaded from `dashboard/config.js` when the addon initializes.

***

## 💡 Tips & Best Practices

{% stepper %}
{% step %}
Use meaningful addon names — The addon name becomes part of the API URL
{% endstep %}

{% step %}
Handle loading states — Always show loading indicators
{% endstep %}

{% step %}
Error handling — Catch and display errors gracefully
{% endstep %}

{% step %}
API validation — Always validate request data on the backend
{% endstep %}

{% step %}
Permission checks — Use permissions for sensitive features
{% endstep %}
{% endstepper %}

***

## Troubleshooting

<details>

<summary>Page not loading</summary>

* Check browser console for errors
* Verify file exists in `dashboard/pages/`
* Ensure component has `export default`

</details>

<details>

<summary>API returns 404</summary>

* Check addon name is lowercase in URL
* Verify route is registered in config.js
* Check server logs for registration messages

</details>

<details>

<summary>Styles not applying</summary>

* Use existing Tailwind classes
* Check class names are correct
* Verify you're using the right CSS variable names

</details>

<details>

<summary>Authentication issues</summary>

* Ensure you're logged into the dashboard
* Check if route requires specific permissions
* Verify session is active

</details>

***


# Premium Addons

Premium addons for Drako


# Prefix Commands

The Prefix Bridge addon lets users run slash commands using a classic text prefix instead. Every mapped command is wrapped in a fake interaction and passed to the original command handler — no duplica

## Key Features

* **Prefix Commands** → Run any slash command with a text prefix (e.g. `^ban @user`)
* **YAML Mappings** → Each command has a simple `.yml` file that maps aliases to the slash command
* **Argument Parsing** → Supports users, channels, roles, numbers, booleans, and rest-of-message text
* **Permission Enforcement** → Respects `default_member_permissions`, per-mapping role locks, and each command's own internal checks
* **Per-User Cooldowns** → Prevent command spam with configurable cooldown timers
* **Components V2 Support** → Automatically patches rich message replies to work through prefix
* **Auto-Generated Mappings** → Includes a tool to scan your commands folder and generate all YAML mappings at once
* **Hot Reload** → Clears the command cache on load so you always get fresh command code
* **Clean Unload** → Properly removes its listener and clears state when disabled

## Configuration

Edit `config.yml` inside the `addons/PrefixBridge` folder.

```yaml
# The character(s) users type before a command, e.g. ^ban
PREFIX: "^"

# Reply to the user's message, or just send in the channel
REPLY_TO_INVOKER: true

# Delete the user's message after running the command
DELETE_INVOKING: false

# Tell the user when they type a prefix command that doesn't exist
UNKNOWN_COMMAND_REPLY: false

# Per-user cooldown between uses of the same command (in seconds)
COOLDOWN_SECONDS: 2

# Shown when someone tries a command they can't use
NO_PERMISSION_MESSAGE: "🔒 You don't have permission to use that command."

# Shown when a command throws an unexpected error
ERROR_MESSAGE: "Something went wrong running that command."
```

## Command Mappings

Each command has a YAML file in `addons/PrefixBridge/commands/`. A mapping tells the bridge which slash command to run, what aliases to listen for, and how to parse arguments.

### Basic Command (no subcommands)

```yaml
ENABLED: true
PATH: commands/Fun/8ball.js
NAMES:
  - 8ball
ARGS:
  - NAME: question
    TYPE: string
    REST: true
```

### Command with Subcommands

```yaml
ENABLED: true
PATH: commands/Moderation/moderation.js

SUBCOMMANDS:
  ban:
    NAMES: ["ban", "b"]
    ARGS:
      - USER_OR_ID:
          USER_OPTION: "user"
          ID_OPTION: "user_id"
        OPTIONAL: false
      - NAME: "reason"
        TYPE: "string"
        REST: true
        OPTIONAL: true

  kick:
    NAMES: ["kick", "k"]
    ARGS:
      - NAME: "user"
        TYPE: "user"
      - NAME: "reason"
        TYPE: "string"
        REST: true
```

### Mapping Options

| Option                 | Description                                                               |
| ---------------------- | ------------------------------------------------------------------------- |
| `ENABLED`              | Set to `false` to disable this mapping without deleting it                |
| `PATH`                 | Relative path to the slash command file (from bot root)                   |
| `NAMES`                | List of prefix aliases for the command                                    |
| `ARGS`                 | Argument definitions (see below)                                          |
| `SUBCOMMANDS`          | Map of subcommand definitions, each with their own `NAMES` and `ARGS`     |
| `REQUIRED_ROLES`       | Role IDs that can use this command (on top of existing permission checks) |
| `REQUIRED_PERMISSIONS` | Discord permission flags required to use this command                     |

### Subcommand Options

| Option                 | Description                                                                 |
| ---------------------- | --------------------------------------------------------------------------- |
| `NAMES`                | Aliases for the subcommand (e.g. `["ban", "b"]`)                            |
| `SUBCOMMAND`           | The actual subcommand name passed to the handler (defaults to the YAML key) |
| `GROUP`                | Subcommand group name, if the slash command uses groups                     |
| `ARGS`                 | Argument definitions for this subcommand                                    |
| `REQUIRED_ROLES`       | Override the top-level `REQUIRED_ROLES` for this subcommand                 |
| `REQUIRED_PERMISSIONS` | Override the top-level `REQUIRED_PERMISSIONS` for this subcommand           |

## Argument Types

| Type               | Description           | Example Input                      |
| ------------------ | --------------------- | ---------------------------------- |
| `string`           | Plain text            | `hello world`                      |
| `integer` / `int`  | Whole number          | `42`                               |
| `number`           | Decimal number        | `3.14`                             |
| `boolean` / `bool` | True or false         | `true`, `yes`, `1`, `on`           |
| `user`             | User mention or ID    | `@User` or `123456789012345678`    |
| `channel`          | Channel mention or ID | `#general` or `123456789012345678` |
| `role`             | Role mention or ID    | `@Admin` or `123456789012345678`   |

### Special Argument Options

| Option           | Description                                                                      |
| ---------------- | -------------------------------------------------------------------------------- |
| `REST: true`     | Captures all remaining text (must be the last argument)                          |
| `OPTIONAL: true` | Argument can be skipped                                                          |
| `USER_OR_ID`     | Accepts either a user mention or a raw ID and maps to two different option names |

## Permission System

Permissions are checked in this order:

1. **Administrators** always pass
2. **`REQUIRED_ROLES`** on the mapping — if set, user must have one of these roles
3. **`REQUIRED_PERMISSIONS`** on the mapping — if set, user must have these Discord permissions
4. **Command-level checks** — each command's own permission logic still runs inside `execute()`

{% hint style="info" %}
**Note:** Discord's per-channel and per-role slash command overrides (Server Settings → Integrations) are not enforced for prefix commands. If you rely on those, add `REQUIRED_ROLES` to the mapping or disable that mapping.
{% endhint %}

## Generating Mappings

The addon includes a tool to auto-generate YAML mappings from your existing slash commands.

```bash
node addons/PrefixBridge/tools/generate-mappings.js
```

| Flag              | Description                                      |
| ----------------- | ------------------------------------------------ |
| `--force`         | Overwrite existing mapping files                 |
| `--commands=path` | Custom commands directory (relative to bot root) |

The tool scans every `.js` file in your commands folder, reads its `SlashCommandBuilder` data, and writes a `.yml` mapping with correct argument types and subcommand structure.

After generating, review the files and tweak aliases as needed.

## Included Mappings

The addon ships with mappings for all built-in commands:

| Category       | Commands                                                                                                                                                                                                  |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Fun**        | 2048, 8ball, advice, ascii, compliment, connectfour, darkjoke, fact, fliptext, guess, hangman, hug, image, kill, kiss, lennyface, meme, pickupline, quote, rizz, roast, rps, say, slap, tictactoe, wordle |
| **Economy**    | balance, beg, booster, crime, daily, deposit, economy, games, heist, inventory, lottery, pets, prestige, rob, store, transfer, use, withdraw, work                                                        |
| **Music**      | music, play                                                                                                                                                                                               |
| **General**    | botavatar, botinfo, giveaway, help, inviter, invites, leaderboard, level, rank, reminder, serverinfo, snipe, suggestion, user                                                                             |
| **Moderation** | antihoist, moderation, modtools, poll, removerole, role                                                                                                                                                   |
| **Utility**    | autoreact, autoresponse, backup, botactivity, channelstats, embed, steal, tickets, translate, update                                                                                                      |

## How It Works

1. User sends a message starting with the prefix (e.g. `^ban @user spamming`)
2. The bridge looks up the alias in its registry
3. Permissions are checked (cooldown → role/permission → command-level)
4. Arguments are parsed according to the mapping's `ARGS` spec
5. A fake interaction object is created that mimics a real slash command interaction
6. The original command's `execute()` function is called with the fake interaction
7. Replies, edits, follow-ups, and deletions all route back to the channel

## Limitations

* **Ephemeral messages** — There's no way to send ephemeral messages outside of slash commands. Permission errors and other "ephemeral" replies will be visible in the channel.
* **Integration overrides** — Discord's per-channel/role slash command restrictions from Server Settings → Integrations are not enforced. Use `REQUIRED_ROLES` in mappings instead.

## Troubleshooting

<table><thead><tr><th>Problem</th><th width="452">Solution</th></tr></thead><tbody><tr><td>Command not found</td><td>Check that the mapping's <code>ENABLED</code> is <code>true</code> and the alias is listed in <code>NAMES</code></td></tr><tr><td>Wrong arguments</td><td>Review the <code>ARGS</code> order in the mapping — it must match what the command expects</td></tr><tr><td>Permission denied</td><td>Check <code>REQUIRED_ROLES</code> and <code>REQUIRED_PERMISSIONS</code></td></tr><tr><td>Command errors on run</td><td>The slash command itself may need a real interaction — check if it uses modals or components</td></tr><tr><td>Mapping not loading</td><td>Check the console for <code>[PrefixBridge]</code> errors — usually a bad <code>PATH</code> or missing command file</td></tr><tr><td>Stale command behavior</td><td>Restart the bot — the bridge clears the command cache on startup</td></tr></tbody></table>

## Support

If you run into issues:

* Check the console for `[PrefixBridge]` log messages
* Verify your `config.yml` formatting
* Make sure command files exist at the paths referenced in your mappings
* Re-run `generate-mappings.js --force` after adding or renaming commands


# Product Panel

Empower your users to download files directly within your Discord server with the Drako Bot Product Panel Addon!

### Key Features

* **Interactive Panels** → Use buttons or dropdown menus to deliver products
* **Smart Placeholder Injection** → Auto-replace placeholders with user data
* **Image & ZIP Support** → Process text inside images and zip archives safely
* **Database Integration** → Store unique user IDs for consistent downloads
* **Tracking & Analytics** → See who downloaded what, and when
* **Role-Based Access** → Restrict products to certain roles
* **Cooldowns** → Prevent spam downloads with custom cooldown timers

***

####

#### Configuration

Edit `config.yml` to define your panels, products, and permissions.

Example:

{% code expandable="true" %}

```yaml
ProductPanelRole: ["ROLE_ID_HERE"]  # Roles allowed to manage panels

panels:
  YourPanel:
    SelectMenu: true
    Title: "Your Product Panel"
    Description:
      - "**Choose a product to download:**"
      - "📦 **Standard Release** - $9.99"
      - "[Purchase Link](https://example.com)"
    Footer:
      Text: "Download your files today"
    Color: "#1769FF"

    products:
      - name: "Standard"
        emoji: "📦"
        description: "Standard Download"
        roleId: "REQUIRED_ROLE_ID"
        zipFilePath: "./products/YourPanel/Standard"
        buttonLabel: "Download Standard"
        buttonColor: "PRIMARY"
    
    cooldownDuration: 60  # seconds
```

{% endcode %}

Messages, logging embeds, and error handling are also customizable in `config.yml`.

***

### Placeholder System

You can inject placeholders into text, code, zip contents, or even jars.

| Placeholder         | Description           | Example                |
| ------------------- | --------------------- | ---------------------- |
| `%%_DISCORD_ID_%%`  | User’s Discord ID     | `123456789012345678`   |
| `%%_USERNAME_%%`    | User’s name           | `johndoe`              |
| `%%_RESOURCE_%%`    | Product name          | `Standard`             |
| `%%_TIMESTAMP_%%`   | Download time         | `2024-01-15T10:30:00Z` |
| `%%_UNIQUE_ID_X_%%` | Random ID of X length | `yDxY_Gr=9$Fw`         |

* Each user always gets the **same IDs** for each length.
* IDs are saved in MongoDB, so they stay consistent across products.
* Supports IDs of 3–32 characters with letters, numbers, and symbols.

{% hint style="success" %} <mark style="color:green;">**Hint:**</mark> Put these placeholders anywhere in your files and they'll be replaced on download
{% endhint %}

{% hint style="danger" %} <mark style="color:red;">**Important:**</mark> Make sure you replace **X** with a valid number (e.g., 4). Length **must** be more than 3.
{% endhint %}

***

### Supported File Types

The Product Panel can process placeholders in a wide range of files.

#### Text Files (inline replacement)

Includes most programming, config, and text formats:

* **Code:** `.js`, `.ts`, `.jsx`, `.tsx`, `.py`, `.php`, `.rb`, `.go`, `.rs`, `.java`, `.cpp`, `.c`, `.cs`, `.swift`, `.kt`, `.scala`, `.rb`, `.pl`, `.lua`, `.sh`, `.ps1`, `.bat, .jar`&#x20;
* **Web:** `.html`, `.htm`, `.css`, `.scss`, `.sass`, `.less`, `.xml`
* **Config & Data:** `.json`, `.yaml`, `.yml`, `.toml`, `.ini`, `.cfg`, `.properties`, `.env`
* **Docs & Logs:** `.txt`, `.md`, `.log`, `.sql`
* **Dev Tools:** `.gitignore`, `.editorconfig`, `.eslintrc`, `.prettierrc`, `.babelrc`, `.tsconfig`, `.dockerfile`, `.makefile`

*(and many other common scripting, package, and project files)*

#### Images (non-destructive)

* `.png`, `.jpg`, `.jpeg`, `.gif`, `.bmp`, `.ico`, `.svg`, `.webp`, `.tiff`, `.tif`

Placeholders are injected without breaking the image.

#### Archives (extract & repackage)

* `.zip`

Contents are unpacked, processed, and zipped back up automatically.

***

### Commands

* `/product create <panel>` → Post a product panel in a channel
* `/product list` → See all configured panels
* `/product stats <panel>` → View download stats for a panel
* `/product history [user]` → Show a user’s download history
* `/product trace [target|data]` → Advanced lookup (by user, Discord ID, username, or placeholder data)

***

### Best Practices

1. Use placeholders in configs, docs, and metadata to personalize downloads
2. Test downloads to make sure placeholders replace correctly
3. Watch file sizes (Discord’s limit is 8MB by default, up to 100MB with boosts)

***

### Troubleshooting

* **Placeholders not replaced** → Check file type and syntax (`%%_PLACEHOLDER_%%`)
* **Image corruption** → Only insert placeholders at the end of images
* **Permission errors** → Verify `ProductPanelRole` and per-product `roleId`
* **File too large** → Compress or remove unnecessary files

***

### Support

If you run into issues:

1. Double-check your config formatting
2. Use `/product trace` to debug user data
3. Review console logs for errors


# Sticky Messages

Ensure your members never miss an important message again with the Drako Bot Sticky Messages Addon!

**💰 Pricing:**

* **Obfuscated Version:** $5.99
* **Full Source:** $17.99

**📦 What It Does:**

* Allows you to create sticky messages that stay at the top of your channel.

**📜 Commands:**

* **/Sticky List:** View all sticky messages.
* **/Sticky Delete:** Remove a sticky message.
* **/Sticky Message:** Create sticky messages on the go.
* **/Sticky Config:** Configure and post predefined embeds and messages.

**🌟 Features:**

* **Link Button:** Add buttons to embeds that link to external sources.
* **Reply Button:** Replies with a predefined embed or message
* **Easy Management:** Create, configure, and manage sticky messages via commands and configuration.

**🛠️ Configuration Example:**

{% code expandable="true" %}

```yaml
AllowRoles: ["ROLE_ID", "ROLE_ID"] # who can use the command

Embed:
  List:
    Title: "Existing Sticky Messages"
    Color: "#000000"
    Image: "https://i.imgur.com/DF3p5Sv.png"
    Thumbnail: "https://i.imgur.com/w5XxKpc.png"
    Author:
      Text: ""
      Icon: "https://i.imgur.com/w5XxKpc.png"
    Footer:
      Text: "Drako Bot - Addon - Sticky Messages"
      Icon: "https://i.imgur.com/w5XxKpc.png"

StickyMessages:
  LeaveAReview:
    Enabled: true
    TriggerCount: 5
    Type: "EMBED" # EMBED OR TEXT
    Message: "This is a sticky message!" # If Type is TEXT
    Embed:  # If Type is EMBED
      Title: ""
      Description:
        - ""
      Fields:
        - Name: ""
          Value: ""
          Inline: true
      Image: "https://i.imgur.com/3eGfpfv.png"
      Thumbnail: ""
      Author:
        Text: "BuiltByBit"
        Icon: "https://i.imgur.com/arfQNGC.png"
        Url: "https://builtbybit.com/resources/drako-bot-multi-purpose-discord-bot.22266/"
      Footer:
        Text: "Drako Bot - Addon - Sticky Messages"
        Icon: "https://i.imgur.com/w5XxKpc.png"
      Color: "#000000"
    Buttons: # YOU HAVE TO FOLLOW THIS FORMAT, DONT EDIT THE STRUCTURE
      1: 
        Type: "LINK" # LINK, REPLY
        Style: "Link" # Primary, Secondary, Success, Danger
        Text: "Leave us a review"
        Emoji: "" # If blank then none
        Link: "https://builtbybit.com/resources/drako-bot-multi-purpose-discord-bot.22266/?ref=discover" # If using LINK button type
        Reply:
          Type: "EMBED" # TEXT OR EMBED
          Message: ""
          Embed:  # If Type is EMBED
            Title: ""
            Description:
              - ""
            Fields:
              - Name: ""
                Value: ""
                Inline: false
              - Name: ""
                Value: ""
                Inline: false
            Image: ""
            Thumbnail: ""
            Author:
              Text: ""
              Icon: ""
              Url: "https://i.imgur.com/w5XxKpc.png"
            Footer:
              Text: ""
              Icon: ""
            Color: "#000000"
      2: 
        Type: "REPLY" # LINK, REPLY
        Style: "Secondary" # Primary, Secondary, Success, Danger
        Text: "Click me!"
        Emoji: "" # If blank then none
        Link: "" # If using LINK button type
        Reply:
          Type: "EMBED" # TEXT OR EMBED
          Message: "By leaving a review, our products can reach more people."
          Embed:  # If Type is EMBED
            Title: "We Value Your Feedback"
            Description:
              - "Thank you for choosing our products. Your feedback is incredibly important to us and helps our products reach more customers like you."
              - "Please consider leaving a review!"
            Fields:
              - Name: "Quick Review Tips"
                Value: "• Be honest and precise.\n• Include how you use the product.\n• Mention what you liked or disliked."
                Inline: false
            Image: "https://i.imgur.com/3eGfpfv.png"
            Thumbnail: "https://i.imgur.com/w5XxKpc.png"
            Author:
              Text: "Customer Service Team"
              Icon: "https://i.imgur.com/w5XxKpc.png"
              Url: "https://i.imgur.com/w5XxKpc.png"
            Footer:
              Text: "Thank you for your support!"
              Icon: "https://i.imgur.com/w5XxKpc.png"
            Color: "#000000"
```

{% endcode %}


# Application System

Streamline your recruitment process with the Drako Bot Application System Addon!

**💰 Pricing:**

* **Obfuscated Version:** $5.99
* **Full Source:** $17.99

**📦 What It Does:**

* Allows you to manage and review applications directly within your Discord server.

**📜 Commands:**

* **/application panel:** Posts the configured panel to the channel.

**🌟 Features:**

* **Application Management:** Accept, deny, and review applications with ease.
* **Role-Based Access:** Control who can view, write, and manage applications.

**🛠️ Configuration Example:**

{% code expandable="true" %}

```yaml
Management: ["ROLE_ID", "ROLE_ID"]
# Management can accept/deny staff applications

Staff: ["ROLE_ID", "ROLE_ID"]
# Staff can view/write in the applications

ClosureTime: "1d" # 1w, 1d, 1h, 1m - How long after an app is denied/accepted should it remain

Panel:
  UseButtons: false
  Title: "Application Opportunities"
  Description:
    - "We're excited to see your interest in joining our team!"
    - "Review the options below and select the type of application you want to submit."
  Footer:
    Text: "Drako Development | Application System"
    Icon: "https://i.imgur.com/w5XxKpc.png"
  Author:
    Text: "Drako Development Team"
    Icon: "https://icons.veryicon.com/png/o/business/monochrome-financial-and-business-icons/staff-6.png"
  Color: "#FFA500"
  Image: "https://www.shutterstock.com/image-vector/apply-now-job-submit-button-600nw-2063501549.jpg"
  Thumbnail: ""

Applications:
  Staff:
    Cooldown: "1m"  # Minimum waiting period before reapplying
    Role: 
      - "ROLE_ID" # Who can open this app type
      - "ROLE_ID"
    Name: "Staff Recruitment Application"
    CategoryID: "CATEGORY_ID"  # Category for creating applications
    Button:
      Name: "Join Our Staff"
      Emoji: "🛡️"
      Color: "Secondary"
      Description: "Click to apply and join our dedicated staff team!"
    Questions:
      - "What is your name?"
      - "How old are you?"
      - "Why do you want to join our team?"
    WelcomeEmbed:  # Initial information provided in the application
      Title: "Staff Application"
      Description:
        - "Excited to have you apply! Join our amazing team today!"
        - "Select the type of application you wish to submit."
      Footer:
        Text: "Drako Development | Application System"
        Icon: "https://i.imgur.com/w5XxKpc.png"
      Author:
        Text: "Drako Development Team"
        Icon: "https://icons.veryicon.com/png/o/business/monochrome-financial-and-business-icons/staff-6.png"
      Color: "#ADD8E6"
      Image: "https://as2.ftcdn.net/v2/jpg/03/28/70/83/1000_F_328708360_JO6Ke56XBsx8YSnFooULSpS3LUb8mSLv.jpg"
      Thumbnail: ""
      Buttons: 
        1:
          Enabled: true
          Name: "Application Guide"
          Emoji: "📘"
          Color: "PRIMARY"
          Type: "LINK"
          Link: "https://www.google.com/"
        2:
          Enabled: true
          Name: "Learn About Us"
          Emoji: "🔍"
          Color: "SECONDARY"
          Type: "LINK"
          Link: "https://builtbybit.com/resources/drako-bot-multi-purpose-discord-bot.22266/"

ApplicationSubmit:
  Lang:
    InitialQuestion: "## Please answer the questions below."
    QuestionFormat: "**Question:** {question}"
    AnswerFormat: "**Answer:** `{answer}`"
  Title: "Application Ready for Submission"
  Description:
    - "Thank you, {user}, for completing all the questions."
    - " "
    - "👀 Please review your answers below to ensure accuracy before submitting your application for review."
    - " "
    - "{application}"
    - " "
    - "Once you're ready, click the submit button to finalize your application."
  Footer:
    Text: "Drako Development | Staff Application"
    Icon: "https://i.imgur.com/w5XxKpc.png"
  Author:
    Text: "Drako Development Team"
    Icon: "https://icons.veryicon.com/png/o/business/monochrome-financial-and-business-icons/staff-6.png"
  Color: "#000000"
  Image: ""
  Thumbnail: ""

ApplicationReview:
  Embed:
    Title: "📝 Application Under Review"
    Description:
      - "Thank you, {user}, for submitting your application. 📩"
      - " "
      - "👀 Our management team is currently reviewing your application. We aim to respond within 10-24 hours."
      - " "
      - "Below are the details of your submission for your reference:"
      - " "
      - "{application}"
    Footer:
      Text: "Drako Development | Staff Application"
      Icon: "https://i.imgur.com/w5XxKpc.png"
    Author:
      Text: "Drako Development Team"
      Icon: "https://icons.veryicon.com/png/o/business/monochrome-financial-and-business-icons/staff-6.png"
    Color: "#ADD8E6"
    Image: ""
    Thumbnail: ""

ApplicationAccept:
  Embed:
    Title: "Application Accepted!"
    Description:
      - "Congratulations, {user}! Your application has been accepted."
      - " "
      - "Our management team will contact you shortly to schedule an interview."
      - "Please be ready to discuss your application and potential start dates."
      - " "
      - "🗑️ This application will automatically be deleted {closertime}."
    Footer:
      Text: "Drako Development | Staff Application"
      Icon: "https://i.imgur.com/w5XxKpc.png"
    Author:
      Text: "Drako Development Team"
      Icon: "https://icons.veryicon.com/png/o/business/monochrome-financial-and-business-icons/staff-6.png"
    Color: "#4CAF50"
    Image: ""
    Thumbnail: ""

ApplicationDenied:    
  Embed:
    Title: "Application Denied"
    Description:
      - "Unfortunately, {user}, your application has been denied."
      - " "
      - "You are welcome to reapply in two weeks. We encourage you to review our requirements and improve your application."
      - " "
      - "🗑️ This application will automatically be deleted {closertime}."
    Footer:
      Text: "Drako Development | Staff Application"
      Icon: "https://i.imgur.com/w5XxKpc.png"
    Author:
      Text: "Drako Development Team"
      Icon: "https://icons.veryicon.com/png/o/business/monochrome-financial-and-business-icons/staff-6.png"
    Color: "#F44336"
    Image: ""
    Thumbnail: ""

Log:
  LogChannel: "CHANNEL_ID" # Leave blank to disable
  Embed:
    Title: "Application Decision"
    Description:
      - "> An application has been updated!"
      - " "
      - "**Information**"   
      - "> **Application**: {App}"
      - "> **Type**: {AppType}"  
      - "> **Decision**: {Decision}"
      - "> **Manager**: {Manager}"
      - " "
      - "**Statistics**"
      - "> **Time Spent**: `{SubmissionTime}`" # How long the user spent submitting the app
      - "> **Decision Time**: `{DecisionTime}`" # How long staff took to accept/deny
    Footer:
      Text: "Drako Development | Application System"
      Icon: "https://i.imgur.com/w5XxKpc.png"
    Author:
      Text: ""
      Icon: ""
    Color: "#4CAF50"
    Image: "https://cdn.vigyanix.com/site/wp-content/uploads/building-mobile-app-banner-image.png"
    Thumbnail: ""
```

{% endcode %}


# Ticket System

The ticket system provides comprehensive support functionality including multiple ticket types, priority levels, working hours, claiming system, automatic alerts, transcripts, and detailed logging.

The ticket system is a complete support solution, offering:

* Multiple ticket types
* Thread mode support
* Role-based priorities
* Working hours
* Claiming, auto-claim & auto-unclaim
* Close requests
* Watchers (DM notifications)
* Staff mention alerts
* Blacklisting (permanent & temporary)
* Bulk operations
* Transcripts (TXT & Web)
* Closure messages & reviews
* Ticket statistics & staff performance
* Detailed logging
* Multi-guild support
* Components V2 (rich message layouts)

## Core Settings

### General Setup

```yaml
TicketSettings:
  Enabled: false
  LogsChannelID: ["CHANNEL_ID"]
  MaxTickets: 1
  DeletionTime: "3s"
  useSelectMenu: true
  overFlow: ["CATEGORY_ID", "CATEGORY_ID"]
  UseThreads: false
  ThreadChannelID: ["CHANNEL_ID"]
```

| Option            | Description                                                   |
| ----------------- | ------------------------------------------------------------- |
| `Enabled`         | Master switch for the ticket system                           |
| `LogsChannelID`   | Channel(s) where all ticket events are logged (one per guild) |
| `MaxTickets`      | Limit how many tickets a single user can have at once         |
| `DeletionTime`    | Delay before a ticket is fully deleted after closure          |
| `useSelectMenu`   | `true` = dropdown menus, `false` = buttons                    |
| `overFlow`        | Backup categories if the main one reaches 50 channels         |
| `UseThreads`      | Create tickets as private threads instead of channels         |
| `ThreadChannelID` | Text/forum channel where threads are created (one per guild)  |

### Multi-Guild Support

Arrays like `LogsChannelID: ["id1", "id2"]` allow the bot to work in multiple guilds. Add one channel/category ID per guild — the bot automatically finds and uses the one that exists in the current guild.

### Thread Mode

When `UseThreads` is enabled, tickets are created as private threads instead of channels. `CategoryID`, `overFlow`, and `ArchiveCategory` per ticket type are ignored in this mode.

## Delayed Response System

Warns users when many tickets are open.

```yaml
DelayedResponse:
  Enabled: false
  TicketThreshold: 25
  Components:
    - Type: container
      AccentColor: "#FFA500"
      Components:
        - Type: section
          Text:
            Content:
              - "# 🎫 High Support Volume Notice"
              - "⚠️ We are currently experiencing higher than usual ticket volume."
              - "## Current Status"
              - "> 📊 **Active Tickets:** {openTickets}"
              - "> ⏱️ **Est. Response Time:** 24-48 hours"
```

| Option            | Description                                                                      |
| ----------------- | -------------------------------------------------------------------------------- |
| `TicketThreshold` | When this many open tickets exist, the warning is shown                          |
| `Components`      | Fully customizable Components V2 layout (placeholders like `{openTickets}` work) |

## Close Reasons

```yaml
CloseReasons:
  Enabled: true
  DefaultReason: "No reason provided"
  AllowCustomReason: true
  Reasons:
    - name: "Issue Resolved"
      emoji: "✅"
      value: "resolved"
    - name: "User Request"
      emoji: "👋"
      value: "user_request"
    - name: "Inactive"
      emoji: "⏰"
      value: "inactive"
    - name: "Invalid"
      emoji: "❌"
      value: "invalid"
```

Adds preset closure reasons staff can pick from a menu. `AllowCustomReason` lets staff write their own reason via the `custom_reason` option.

## Close Request System

Allows staff to request the ticket creator to close the ticket. The user can accept or decline.

```yaml
CloseRequest:
  Enabled: true
  DefaultReason: "Issue has been resolved"
  AutoCloseOnAccept: true

  Components:
    - Type: container
      AccentColor: "#FFA500"
      Components:
        - Type: text_display
          Content:
            - "# 📩 Close Request"
            - "Hey {user}!"
            - "**{staff}** has requested to close this ticket."
            - "**Reason:** {reason}"

  Buttons:
    Accept:
      Label: "Accept & Close"
      Emoji: "✅"
      Style: "Success"
    Decline:
      Label: "Decline"
      Emoji: "❌"
      Style: "Secondary"

  Messages:
    Accepted: "Close request accepted by {user}. Closing ticket..."
    Declined: "Close request declined by {user}. Staff will continue to assist."
    DeclinedStaff: "{user} has declined the close request. Please continue assisting them."
```

| Option              | Description                                                    |
| ------------------- | -------------------------------------------------------------- |
| `AutoCloseOnAccept` | Automatically close the ticket when the user accepts           |
| `Buttons`           | Customize the accept/decline button labels, emojis, and styles |
| `Messages`          | Customize the confirmation messages                            |

### Placeholders

| Placeholder | Description                           |
| ----------- | ------------------------------------- |
| `{user}`    | The ticket creator                    |
| `{staff}`   | The staff member who sent the request |
| `{reason}`  | The close request reason              |

## Staff Mention Alert

Alerts users when they mention a staff member who is offline, idle, or in DND.

```yaml
StaffMentionAlert:
  Enabled: true
  AlertStatuses: ["offline", "idle", "dnd"]
  EmbedColor: "#FFA500"
  ShowAlternatives: true
  AlternativesTitle: "🟢 Available Staff"
  Messages:
    Offline: "⚠️ {staff} is currently **offline** and may not see your message right away."
    Idle: "⚠️ {staff} is currently **away** and may take longer to respond."
    DND: "⚠️ {staff} has **Do Not Disturb** enabled and may not respond immediately."
```

| Option             | Description                                      |
| ------------------ | ------------------------------------------------ |
| `AlertStatuses`    | Which statuses trigger the alert                 |
| `ShowAlternatives` | Show a list of currently available staff members |
| `Messages`         | Customizable messages per status type            |

## Priority System

```yaml
Priority:
  Enabled: true
  DefaultPriority: "Low"

  Levels:
    Low:
      Roles: ["ROLE_ID"]
      Tag: ["ROLE_ID"]
      MoveTop: false
    Medium:
      Roles: ["ROLE_ID"]
      Tag: ["ROLE_ID"]
      MoveTop: true
    High:
      Roles: ["ROLE_ID"]
      Tag: ["ROLE_ID"]
      MoveTop: true
```

| Option    | Description                                       |
| --------- | ------------------------------------------------- |
| `Levels`  | Low / Medium / High (add more if needed)          |
| `Roles`   | Who gets assigned this priority                   |
| `Tag`     | Roles to ping when tickets open                   |
| `MoveTop` | Moves priority tickets to the top of the category |

## Working Hours

```yaml
WorkingHours:
  Enabled: false
  Timezone: Europe/London
  NonWorkingDays: ["Saturday", "Sunday"]
  Schedule:
    Monday: "16:00-22:00"
    Tuesday: "16:00-22:00"
  allowOpenTickets: true
```

| Option             | Description                                                                    |
| ------------------ | ------------------------------------------------------------------------------ |
| `Timezone`         | Must be IANA format (e.g. `America/New_York`)                                  |
| `Schedule`         | Define start/end times per day in 24h format                                   |
| `NonWorkingDays`   | Days with no support                                                           |
| `allowOpenTickets` | Allow tickets outside hours? If yes, a warning embed (`WorkingEmbed`) is shown |

### Working Hours Placeholders

These can be used in panel components:

| Placeholder                        | Description                       |
| ---------------------------------- | --------------------------------- |
| `{workinghours_start}`             | Today's start time                |
| `{workinghours_end}`               | Today's end time                  |
| `{workinghours_start_monday}`      | Monday's start time               |
| `{workinghours_end_monday}`        | Monday's end time                 |
| `{workinghours_start_tuesday}` ... | Per-day placeholders for all days |

## Ticket Panels

Panels are what users see when opening tickets.

```yaml
TicketPanelSettings:
  Panel1:
    Components:
      - Type: container
        AccentColor: "#1769FF"
        Components:
          - Type: text_display
            Content:
              - "# 📩 Support Tickets"
              - "Please select a category below for assistance."
```

You can create multiple panels (`Panel1`, `Panel2`...). Each panel controls how ticket buttons/menus look.

### Inline Ticket Components

You can embed ticket buttons or select menus directly inside containers using placeholders:

* `{button_TicketType1}` or `{button_TicketType1,TicketType2}` — Embeds ticket buttons
* `{selectmenu_TicketType1}` or `{selectmenu_TicketType1,TicketType2}` — Embeds a select menu

When using these placeholders, the default select menu / buttons below the container will NOT be added.

## Ticket Transcripts

```yaml
TicketTranscript:
  Type: TXT
  Save: true
  MinMessages: "1"
  SavePath: ./transcripts/
```

| Option        | Description                                                |
| ------------- | ---------------------------------------------------------- |
| `Type`        | `TXT` (file attachment) or `WEB` (web dashboard link)      |
| `Save`        | Save transcripts to disk                                   |
| `MinMessages` | Minimum messages required before a transcript is generated |
| `SavePath`    | Directory to save transcript files                         |

## Ticket Creation Templates

````yaml
TicketCreation:
  Default:
    Followup:
      Message: "Thank you {user} for reaching out! Please describe your issue in detail."
    Components:
      - Type: container
        AccentColor: "#1769FF"
        Components:
          - Type: section
            Text:
              Content:
                - "# 🎫 New Support Ticket"
                - "Welcome to our Support System, {user}!"
                - "**Ticket Type:** {ticketType}"
                - "**Claimed By:** {claimer}"
                - "{questions}"
            Accessory:
              Type: thumbnail
              Media:
                URL: "{userIcon}"
    QuestionFormat:
      - "**{question}**"
      - "```{answer}```"
````

Defines the first message inside the ticket. You can make ticket-type-specific templates (`TicketType1`, `TicketType2`).

### Features

* **Followup** — Sends an additional message after the ticket creation embed
* **Components** — Rich Components V2 layout
* **QuestionFormat** — Defines how questions/answers are formatted in the ticket

### Placeholders

| Placeholder    | Description                                                  |
| -------------- | ------------------------------------------------------------ |
| `{user}`       | Mentions the ticket creator                                  |
| `{ticketType}` | Name of the ticket type                                      |
| `{claimer}`    | Staff member who claimed the ticket                          |
| `{questions}`  | Displays answered questions (formatted via `QuestionFormat`) |
| `{userIcon}`   | The user's avatar URL                                        |
| `{guild}`      | The server name                                              |
| `{guildIcon}`  | The server icon URL                                          |

## Ticket Types

Each type is its own config block.

```yaml
TicketTypes:
  TicketType1:
    Enabled: true
    Panel: "Panel1"
    Name: "General Support"
    ChannelName: "{ticket-id}-General-{user}-{priority}"
    ChannelTopic: "Category: {ticketType} | User: {userid} | Priority: {priority}"
    CategoryID: ["CATEGORY_ID"]
    AutoAlert: "12h"
    ArchiveCategory: ["CATEGORY_ID"]
    SupportRole: ["ROLE_ID"]
    UserRole: ["ROLE_ID"]
    TagSupport: false
    TagCreator: true
    RestrictDeletion: false
    Claiming:
      Enabled: true
      AutoClaim: true
      Button:
        Name: "Claim Ticket"
        Emoji: "🎫"
        Style: "Secondary"
      RestrictResponse: true
      RestrictView: true
      AnnounceClaim: true
      AutoUnclaim:
        Enabled: true
        InactiveTime: "2h"
        NotifyChannel: true
    Button:
      Name: "General Support"
      Emoji: "🔍"
      Style: "Danger"
      Description: "Open to receive general support"
```

| Option             | Description                                                |
| ------------------ | ---------------------------------------------------------- |
| `Panel`            | Which panel it belongs to                                  |
| `ChannelName`      | Ticket channel name (supports placeholders)                |
| `ChannelTopic`     | Channel topic (supports placeholders)                      |
| `CategoryID`       | Category for ticket channels (one per guild)               |
| `AutoAlert`        | Automatically send an inactivity alert after this duration |
| `ArchiveCategory`  | Category to move tickets to when archived                  |
| `SupportRole`      | Roles that can access and manage tickets                   |
| `UserRole`         | Roles that can open this ticket type                       |
| `TagSupport`       | Ping support roles when ticket opens                       |
| `TagCreator`       | Ping the ticket creator in the ticket                      |
| `RestrictDeletion` | Only support roles can close (users cannot)                |

### ChannelName Placeholders

| Placeholder   | Description          |
| ------------- | -------------------- |
| `{ticket-id}` | Unique ticket number |
| `{user}`      | Username             |
| `{priority}`  | Priority level       |

### ChannelTopic Placeholders

| Placeholder    | Description      |
| -------------- | ---------------- |
| `{ticketType}` | Ticket type name |
| `{userid}`     | User mention     |
| `{priority}`   | Priority level   |
| `{ticket-id}`  | Ticket ID        |
| `{created-at}` | Creation date    |
| `{category}`   | Category name    |

### Claiming System

| Option                      | Description                                                        |
| --------------------------- | ------------------------------------------------------------------ |
| `Enabled`                   | Enable/disable claiming for this type                              |
| `AutoClaim`                 | First staff member to type automatically claims                    |
| `RestrictResponse`          | Only the claimer can respond until unclaimed                       |
| `RestrictView`              | Only the ticket owner and claimer can view the ticket when claimed |
| `AnnounceClaim`             | Post a message in the channel when claimed/unclaimed               |
| `AutoUnclaim.Enabled`       | Auto-unclaim if staff hasn't responded                             |
| `AutoUnclaim.InactiveTime`  | How long until ticket is auto-unclaimed                            |
| `AutoUnclaim.NotifyChannel` | Send a message when auto-unclaiming                                |

## Questions

You can add questions to any ticket type. Questions can be text inputs or select menus.

### Text Input

```yaml
Questions:
  - PurchaseID:
      Question: "Do you have a transaction ID?"
      Placeholder: "TBX-wdUGVApxKSMXham"
      Style: "Short"
      Required: false
      maxLength: 1000
```

| Option        | Description                                      |
| ------------- | ------------------------------------------------ |
| `Question`    | The question text                                |
| `Placeholder` | Example text shown in the input                  |
| `Style`       | `"Short"` (1 line) or `"Paragraph"` (multi-line) |
| `Required`    | `true` / `false`                                 |
| `maxLength`   | Character limit                                  |

### Select Menu

```yaml
Questions:
  - IssueType:
      Type: "StringSelect"
      Question: "What type of issue are you experiencing?"
      Description: "Please select the category that best describes your issue"
      Placeholder: "Choose an issue type..."
      Required: true
      Options:
        - Label: "Bug Report"
          Value: "bug"
          Description: "Something isn't working as expected"
          Emoji: "🐛"
        - Label: "Other"
          Value: "other"
          Description: "Something else not listed above"
          Emoji: "❓"
```

| Option        | Description                                        |
| ------------- | -------------------------------------------------- |
| `Type`        | `"StringSelect"`                                   |
| `Question`    | Text shown above dropdown                          |
| `Description` | Helper text (optional)                             |
| `Placeholder` | Text when no option is selected                    |
| `Required`    | `true` / `false`                                   |
| `Options`     | List of choices (Label, Value, Description, Emoji) |

## Alerts

```yaml
Alert:
  Enabled: true
  Time: "12h"
  DM:
    Enabled: true
    LogFailures: false
    Components:
      - Type: container
        AccentColor: "#FF0000"
        Components:
          - Type: text_display
            Content:
              - "# ⚠️ Ticket Alert"
              - "Hello {user}"
              - "> **Time Until Close:** {time}"
              - "> **Reason:** {reason}"
    Button:
      Label: "Go to Ticket"
      Emoji: "🎫"
  Embed:
    Title: "Support Ticket Notice"
    Description:
      - "> **User:** {user}"
      - "> **Time Until Close:** {time}"
      - "> **Reason:** {reason}"
    Button:
      Label: "Close Ticket"
      Emoji: "🔒"
      Style: "Danger"
```

| Option           | Description                                        |
| ---------------- | -------------------------------------------------- |
| `Time`           | Duration before the ticket auto-closes after alert |
| `DM.Enabled`     | Send alert via DM                                  |
| `DM.LogFailures` | Log in the ticket channel when DM fails            |
| `DM.Components`  | Components V2 layout for DM alerts                 |
| `Embed`          | Fallback embed layout for in-channel alert         |
| `Embed.Button`   | Close button displayed under the alert             |

Alerts can be sent manually via `/tickets alert` or automatically via `AutoAlert` on ticket types. The alert auto-cancels when the ticket creator responds.

## Ticket Closure DM

```yaml
TicketClosureDM:
  Enabled: true
  Transcript: true
  Components:
    - Type: container
      AccentColor: "#1769FF"
      Components:
        - Type: section
          Text:
            Content:
              - "# Ticket Closure Notification"
              - "{userTag}, your ticket in {guild} has been closed."
              - "**Ticket Summary**"
              - "> **Messages:** {messageCount}"
              - "> **Priority:** {priority}"
              - "> **Handled By:** {claimer}"
              - "> **Close Reason:** {reason}"
```

Sends users a DM when their ticket closes. Includes a summary, optional transcript attachment, and review button.

### Placeholders

| Placeholder      | Description                         |
| ---------------- | ----------------------------------- |
| `{userTag}`      | The user's mention                  |
| `{guild}`        | Server name                         |
| `{messageCount}` | Total messages in the ticket        |
| `{priority}`     | Ticket priority level               |
| `{claimer}`      | Staff member who handled the ticket |
| `{reason}`       | Closure reason                      |

## Watchers

Staff members can watch tickets to receive DM notifications on all activity.

### Commands

| Command             | Description                       |
| ------------------- | --------------------------------- |
| `/tickets watch`    | Start watching the current ticket |
| `/tickets unwatch`  | Stop watching                     |
| `/tickets watchers` | View all watchers                 |

### Events Watched

Watchers are notified via DM for:

* New messages
* Ticket claimed/unclaimed
* Priority changes
* Users added/removed
* Alerts sent
* Ticket closing
* Close requests

### Configuration

```yaml
WatcherNotifications:
  Message:
    Components:
      - Type: container
        AccentColor: "#5865F2"
        Components:
          - Type: text_display
            Content:
              - "## 💬 New Message"
              - "**Author:** <@{authorId}>"
              - "**Message:** {content}"
          - Type: text_display
            Content: "-# Ticket #{ticketId} • {guildName}"
  Claimed:
    # ...
  Unclaimed:
    # ...
  Priority:
    # ...
  UserAdded:
    # ...
  UserRemoved:
    # ...
  Alert:
    # ...
  Closing:
    # ...
  CloseRequest:
    # ...
```

Each event type has its own customizable Components V2 template with relevant placeholders.

## Blacklist System

Prevent specific users from opening tickets.

### Commands

| Command                                                        | Description                            |
| -------------------------------------------------------------- | -------------------------------------- |
| `/tickets blacklist add <user> [reason]`                       | Permanently blacklist a user           |
| `/tickets blacklist addtemp <user> <duration> <unit> [reason]` | Temporarily blacklist                  |
| `/tickets blacklist remove <user>`                             | Remove from blacklist                  |
| `/tickets blacklist view <user>`                               | View blacklist info                    |
| `/tickets blacklist list`                                      | View all blacklisted users (paginated) |

### Temporary Blacklists

Temporary blacklists support units: Minutes, Hours, Days, Weeks. They expire automatically.

### Configuration

All blacklist notifications are customizable via `TicketNotifications.Blacklist`:

```yaml
TicketNotifications:
  Blacklist:
    Added:
      Components: [...]
    View:
      Components: [...]
    Removed:
      Components: [...]
    TempAdded:
      Components: [...]
```

## Archive Design

When a ticket is closed, it can be moved to an archive category with action buttons.

```yaml
ArchiveDesign:
  Buttons:
    "1":
      Name: "Reopen"
      Emoji: "🟩"
      Style: "Primary"
      Type: "REOPEN"
    "2":
      Name: "Transcript"
      Emoji: "📜"
      Style: "Secondary"
      Type: "TRANSCRIPT"
    "3":
      Name: "Delete"
      Emoji: "🗑️"
      Style: "Danger"
      Type: "DELETE"
  Components:
    - Type: container
      AccentColor: "#2B2D31"
      Components:
        - Type: section
          Text:
            Content:
              - "# Ticket Archived"
              - "> **Creator:** {userTag}"
              - "> **Reason:** {reason}"
```

## User Left Design

Displayed when the ticket creator leaves the server.

```yaml
UserLeftDesign:
  Components:
    - Type: container
      AccentColor: "#1769FF"
      Components:
        - Type: section
          Text:
            Content:
              - "# User Alert"
              - "The creator of this ticket, **{user}** has left the server."
          Accessory:
            Type: thumbnail
            Media:
              URL: "{userIcon}"
  Button:
    Name: "Delete Ticket"
    Emoji: "⛔"
    Style: Secondary
```

## Ticket Notifications

All in-ticket notifications are customizable via Components V2:

```yaml
TicketNotifications:
  UserAdded:
    Components: [...]
  UserRemoved:
    Components: [...]
  Renamed:
    Components: [...]
  Transferred:
    Components: [...]
  PriorityChanged:
    Components: [...]
  AlertCancelled:
    Components: [...]
  Watch:
    Components: [...]
  CloseRequestSent:
    Components: [...]
  Blacklist:
    Added: [...]
    View: [...]
    Removed: [...]
    TempAdded: [...]
```

## Reviews

The review system lets users rate their support experience after a ticket is closed. They receive a DM with a **Leave a Review** button, which opens a modal with rating questions.

````yaml
Reviews:
  Enabled: false
  ChannelID: ["CHANNEL_ID"]
  Placeholder: "Rate us!"
  ButtonEmoji: "⭐"
  ButtonStyle: "Secondary"

  Questions:
    1:
      ID: "support_quality"
      Label: "Support Quality (1-5)"
      Description: "Rate the quality of support you received"
      Required: true
      Options:
        - Label: "Very Poor"
          Value: "1"
          Emoji: "😞"
        - Label: "Excellent"
          Value: "5"
          Emoji: "😍"

  FeedbackInput:
    Enabled: true
    Label: "Additional Feedback"
    Placeholder: "Please share any additional thoughts or suggestions..."
    Style: "Paragraph"
    Required: false
    MaxLength: 1000

  Components:
    - Type: container
      AccentColor: "#FFD700"
      Components:
        - Type: section
          Text:
            Content:
              - "# ⭐ Ticket Review"
              - "**Ticket Information**"
              - "> **Creator:** {ticketCreator}"
              - "> **Ticket:** {channelName}"
              - "> **Handled By:** {claimer}"
              - "**Ratings**"
              - "{ratings}"
              - "**Additional Feedback**"
              - "```{feedback}```"
````

| Option        | Description                                        |
| ------------- | -------------------------------------------------- |
| `ChannelID`   | Channel to post review summaries (one per guild)   |
| `Placeholder` | Button text in closure DMs                         |
| `ButtonEmoji` | Emoji on the review button                         |
| `ButtonStyle` | Button color (Primary, Secondary, Success, Danger) |
| `Components`  | Review summary posted to the reviews channel       |

### Questions

You can add up to **4 questions** (Discord's modal limit). Each question has:

| Option        | Description                         |
| ------------- | ----------------------------------- |
| `ID`          | Unique name (used in logs/database) |
| `Label`       | The question text                   |
| `Description` | Helper text shown under the label   |
| `Options`     | The ratings (Label, Value, Emoji)   |

### Feedback Input

Optional text box where users can write suggestions or comments.

### How It Works

1. User closes a ticket → receives a DM with the **Leave a Review** button
2. Clicking opens a modal with rating questions (and optional feedback)
3. Ratings + comments are saved
4. Review data is posted to the reviews channel and added to ticket logs

## Bulk Operations

Staff can perform mass actions on tickets.

| Command                        | Description                                      |
| ------------------------------ | ------------------------------------------------ |
| `/tickets bulk close [reason]` | Close all open tickets in the server             |
| `/tickets bulk delete`         | Delete all closed/archived tickets in the server |

Requires Administrator permission or any ticket support role. Operations are throttled (1.5s between each) to avoid rate limits.

## Ticket Statistics

`/tickets stats` provides detailed analytics:

* **Current Status** — Total, open, closed, deleted, claimed, unclaimed counts
* **Recent Activity** — Tickets created/closed in last 24h, 7d, 30d
* **Priority Distribution** — Breakdown by priority level
* **Ticket Type Distribution** — Breakdown by type
* **Rating Statistics** — Average rating, distribution
* **Response Metrics** — Average/fastest/slowest first response and resolution times
* **Staff Performance** — Top 5 staff by tickets claimed and resolution rate
* **Peak Activity Hours** — Busiest hours for ticket creation

## Commands Reference

| Command                                                        | Description                        |
| -------------------------------------------------------------- | ---------------------------------- |
| `/tickets panel <panel>`                                       | Send a ticket panel                |
| `/tickets close [reason] [custom_reason] [silent]`             | Close the current ticket           |
| `/tickets add <user>`                                          | Add a user to the ticket           |
| `/tickets remove <user>`                                       | Remove a user from the ticket      |
| `/tickets rename <name>`                                       | Rename the ticket channel          |
| `/tickets transfer <type>`                                     | Transfer ticket to another type    |
| `/tickets priority <level>`                                    | Change ticket priority             |
| `/tickets alert [reason]`                                      | Send an inactivity alert           |
| `/tickets cancel-alert`                                        | Cancel an active alert             |
| `/tickets closerequest [reason]`                               | Request the creator to close       |
| `/tickets watch`                                               | Watch for DM notifications         |
| `/tickets unwatch`                                             | Stop watching                      |
| `/tickets watchers`                                            | View all watchers                  |
| `/tickets stats`                                               | View ticket statistics             |
| `/tickets blacklist add <user> [reason]`                       | Blacklist a user                   |
| `/tickets blacklist addtemp <user> <duration> <unit> [reason]` | Temp blacklist                     |
| `/tickets blacklist remove <user>`                             | Remove from blacklist              |
| `/tickets blacklist view <user>`                               | View blacklist info                |
| `/tickets blacklist list`                                      | View all blacklisted users         |
| `/tickets bulk close [reason]`                                 | Close all open tickets             |
| `/tickets bulk delete`                                         | Delete all closed/archived tickets |

## Logs

```yaml
Logs:
  Close:
    Embed:
      Title: "**Ticket Closure**"
      Description:
        - "> Ticket has been closed by {userTag}."
        - "> **Reason:** {reason}"
        - "> **Creator:** {ticketCreator}"
        - "> **Messages:** {messageCount}"
        - "> **Priority:** {priority}"
        - "> **Claimed By:** {claimer}"
        - "> {channelName}"
    ReviewFormat:
      AverageRating: "Average Rating"
      Rating: "Rating"
      Review: "Review"
      AdditionalFeedback: "Additional Feedback"
      NoRating: "No Rating Yet"
      CloseReason: "Close Reason"
```

Logs support both Components V2 (`Logs.Close.Components`) and traditional embeds (`Logs.Close.Embed`).

## Components V2

All message templates in the ticket system support Discord's Components V2 layout format. This allows rich layouts with:

* `container` — Colored accent containers
* `text_display` — Text blocks (supports markdown)
* `section` + `Accessory` — Text with thumbnail images
* `separator` — Visual dividers
* `media_gallery` — Image galleries
* `action_row` — Button rows

{% hint style="success" %}
Use <https://builder.drako.gg/> to generate containers
{% endhint %}


# Music

This guide explains how to set up the music system

***

### Disclaimer

* **Use a burner account.** Your account may be banned or rate-limited.
* This method may violate Deezer's Terms of Service.
* Proceed with caution and only use this method if you understand the implications.

***

### Requirements

* Google Chrome Browser
* Basic understanding of browser developer tools
* Private (Incognito) browsing window

***

### Explaination

* No streaming is done via YouTube, this can cause certain songs to not be found and unplayable
* Songs may stutter, this is due to high CPU usage and live video playback

***

### Step-by-Step Instructions

#### 1. Open Deezer in Incognito Mode

* Open **Google Chrome**
* Launch an **Incognito Window** (Ctrl+Shift+N or ⌘+Shift+N on Mac)
* Navigate to <https://www.deezer.com/>

{% hint style="success" %}
**Tip:** Using incognito mode ensures a clean session.
{% endhint %}

***

#### 2. Sign Into Your Deezer Account

* Sign in with a valid Deezer account

{% hint style="danger" %}
**Important:** Use an account you don't mind losing access to
{% endhint %}

***

#### 3. Open Developer Tools

* Press `F12` or right-click anywhere on the page and select **Inspect**
* Go to the **Application** tab in the developer tools

***

#### 4. Locate & Copy the ARL token

* Copy the arl value as shown in the image below (Green Area)
* Enter it in `music.yml` under `DeezerARLCookie`

  <figure><img src="/files/ArAiIFhD75jNne0u7aTK" alt=""><figcaption></figcaption></figure>

***

#### 5. Obtain the Decryption Key

* Obtain the decryption key from public projects, such as Deezer Downloader
* Enter it in `music.yml` under `DeezerDecryptionKey`

{% hint style="success" %}
**Due to Legal reason this guide cannot display or inform you how to obtain the key directly.**
{% endhint %}


# Custom Commands

Custom commands allow you to create personalized commands that users can trigger with a prefix. You can configure these commands to send either text messages or embeds with buttons.

### Basic Configuration

In your `config.yml`, custom commands are configured under the `CustomCommands` section. Here are the two key settings:

```yaml
CommandsEnabled: true
CommandsPrefix: "!"
```

### Examples

#### MongoDB Setup Command

This command provides users with links and information for setting up MongoDB. It includes rich embeds with detailed descriptions and action buttons for quick navigation.

{% code expandable="true" %}

```yaml
CustomCommands:
  mongo:
    type: "EMBED"
    Embed:
      Title: "📊 MongoDB Setup Guide"
      Description:
        - "To get started with MongoDB, follow the detailed guide provided below. This guide will assist you in setting up MongoDB efficiently and correctly."
        - ""
        - "Need further assistance? Don't hesitate to open a support ticket."
      Footer:
        Text: "Drako Development | MongoDB Setup"
        Icon: "https://i.imgur.com/w5XxKpc.png"
      Color: "#1769FF"
    Roles:
      Whitelist: ["1272331356445347840", "1196187304008622101"]
    Options:
      DeleteTriggerMessage: false
      ReplyToUser: false
    Buttons:
      - Type: "LINK"
        Name: "MongoDB Setup"
        Emoji: "📑"
        Link: "https://docs.drakodevelopment.net/misc/mongodb-setup"
      - Type: "LINK"
        Name: "Wiki"
        Emoji: "📕"
        Link: "https://docs.drakodevelopment.net/"
```

{% endcode %}

#### Welcome Command

A welcoming command that dynamically greets new users, provides a quick start guide, and includes interactive buttons for further exploration.

{% code expandable="true" %}

```yaml
CustomCommands:
  welcome:
    type: "EMBED"
    Embed:
      Title: "🎉 Welcome to Drako Development!"
      Description:
        - "Hello {userMention}! We're glad you've joined our community of {memberCount} developers."
        - "Here's a quick guide to get you started:"
      Fields:
        - Name: "🚀 Quick Start"
          Value: "• Introduce yourself in #introductions\n• Set roles in #role-assignment\n• Check #announcements for updates"
          Inline: false
        - Name: "🆘 Need Help?"
          Value: "Visit #support or use `/ticket` for assistance."
          Inline: false
      Footer:
        Text: "Joined on {longTime}"
        Icon: "https://i.imgur.com/w5XxKpc.png"
      Author:
        Text: "Drako Development Team"
        Icon: "https://i.imgur.com/w5XxKpc.png"
      Color: "#1769FF"
    Roles:
      Whitelist: []
    Options:
      DeleteTriggerMessage: true
      ReplyToUser: true
    Buttons:
      - Type: "LINK"
        Name: "Documentation"
        Emoji: "📚"
        Link: "https://docs.drakodevelopment.net"
      - Type: "REPLY"
        Name: "Quick Tour"
        Emoji: "🗺️"
        Style: "Primary"
        Reply:
          Type: "TEXT"
          Ephemeral: true
          Embed:
            Title: "🗺️ Quick Server Tour"
            Description:
              - "📢 #announcements - Latest news"
              - "💡 #ideas-and-feedback - Share suggestions"
              - "🤖 #bot-commands - Test our bots"
              - "🎓 #tutorials - Learn new skills"
            Footer:
              Text: "Enjoy your stay at Drako Development!"
              Icon: "https://i.imgur.com/w5XxKpc.png"
            Author:
              Text: "Server Guide"
              Icon: "https://i.imgur.com/w5XxKpc.png"
            Color: "#4CAF50"
            Image: "https://i.imgur.com/w5XxKpc.png"
            Thumbnail: "https://i.imgur.com/w5XxKpc.png"
            Timestamp: true
            Fields:
              - Name: "Important Links"
                Value: "Check out our [website](https://docs.drakodevelopment.net/)"
                Inline: true
              - Name: "Support"
                Value: "Need help? Create a ticket!"
                Inline: true
```

{% endcode %}

### Additional Notes

* **Available Placeholders:**&#x20;

  ```yaml
  {guildName}, {guildId}, {userName}, {userId}, {userMention}, {channelName}, {channelId}, {channelMention}, {commandName}, {longTime}, {shortTime}, {memberCount}
  ```


# Suggestions

The suggestion system allows members to submit ideas through a modal form. You can fully customize this modal with text inputs and dropdown menus using Discord’s latest modal components.

In your `suggestions.yml`, navigate to the **`SuggestionSettings`** section.

Key settings:

* **`UseQuestionModal`**\
  Must be set to `true` to enable modal-based suggestions.
* **`AdditionalModalInputs`**\
  Defines up to **4 extra fields** in your suggestion modal.\
  These fields can be either:
  * `TextInput` → Free text fields
  * `StringSelect` → Dropdown menus with predefined options

***

### 🔧 AdditionalModalInputs Structure

Each input requires the following properties depending on its type:

#### 1. Text Inputs

```yaml
    1: # Additional details text input
      Type: "TextInput"
      ID: "details" # {modal_details}
      Question: "Additional Details (Optional)"
      Placeholder: "Any extra context, examples, or details about your suggestion..."
      Style: "Paragraph" # Short + Paragraph
      Required: false
      maxLength: 2000
```

#### 2. StringSelect Dropdowns

{% code expandable="true" %}

```yaml
    2: # Product selection dropdown
      Type: "StringSelect"
      ID: "product" # Available as: {modal_product}, {modal_product_formatted}, {modal_product_label}, {modal_product_emoji}
      Label: "Product Selection"
      Description: "Which product is this suggestion for?"
      Placeholder: "Choose a product..."
      Required: true
      Options:
        - Label: "Drako Bot"
          Value: "drako_bot"
          Description: "Multi-purpose Discord bot"
          Emoji: "🤖"
        - Label: "Drako Tickets"
          Value: "drako_tickets"
          Description: "Advanced ticket management system"
          Emoji: "🎫"
        - Label: "Discord Platform"
          Value: "discord"
          Description: "General Discord platform suggestions"
          Emoji: "💬"
        - Label: "Other"
          Value: "other"
          Description: "Other products or services"
          Emoji: "📦"
```

{% endcode %}

***

### 📝 Example Configuration

Here’s a complete example with a mix of inputs:

{% code expandable="true" %}

```yaml
SuggestionSettings:
  UseQuestionModal: true
  AdditionalModalInputs:
    1: # Product field
      Type: "TextInput"
      ID: "product"
      Question: "Which product is this suggestion for?"
      Placeholder: "Drako Bot, Drako Tickets, Discord"
      Style: "Short"
      Required: true

    2: # Priority dropdown
      Type: "StringSelect"
      ID: "priority"
      Label: "Suggestion Priority"
      Placeholder: "Choose a priority..."
      Required: true
      Options:
        - Label: "🟢 Low Priority"
          Value: "low"
        - Label: "🟡 Medium Priority"
          Value: "medium"
        - Label: "🔴 High Priority"
          Value: "high"

    3: # Category dropdown
      Type: "StringSelect"
      ID: "category"
      Label: "Suggestion Category"
      Required: false
      Options:
        - Label: "🐛 Bug Fix"
          Value: "bug"
        - Label: "✨ New Feature"
          Value: "feature"
        - Label: "⚡ Improvement"
          Value: "improvement"
```

{% endcode %}

***

### 🏷️ Using Placeholders in Embeds

Once configured, these inputs can be displayed inside your `SuggestionEmbed` using placeholders:

| Placeholder              | Example Output    |
| ------------------------ | ----------------- |
| `{modal_[ID]}`           | `low`             |
| `{modal_[ID]_formatted}` | `🟢 Low Priority` |
| `{modal_[ID]_label}`     | `Low Priority`    |
| `{modal_[ID]_emoji}`     | `🟢`              |

#### Example Embed

{% code expandable="true" %}

````yaml
SuggestionEmbed:
  EmbedColor: "#1769FF"
  EmbedTitle: "{user}'s Suggestion"
  EmbedDescription:
    - "**Suggestion**"
    - "```{suggestion}```"
    - ""
    - "**📋 Details**"
    - "> **Product:** {modal_product}"
    - "> **Priority:** {modal_priority_formatted}"
    - "> **Category:** {modal_category_formatted}"
  EmbedFooter: "Suggestion ID: {SuggestionID} | {LongTime}"
  Thumbnail: true
  AuthorIcon: true
````

{% endcode %}

***

### ⚙️ Important Notes

* Maximum **4 inputs** per modal (Discord limitation)
* Maximum **25 options** per dropdown
* Labels and values must each be **≤ 100 characters**


# Backup

Backups keep your server safe in case something goes wrong. Here’s how to set it up in just a few minutes.

Here’s the basic config you’ll use:

```yaml
Backup:
  Enabled: true          # Turn backups on
  Schedule: 1d           # How often to back up
  MaxBackups: 5          # How many backups to keep
  LogsChannelID: "CHANNEL_ID" # Channel for backup alerts
```

***

### What Each Option Means

* **Enabled**
  * Turns the backup system on or off.
  * Use `true` to activate, `false` to stop it.
* **Schedule**
  * How often backups happen.
  * Examples:
    * `30m` → every 30 minutes
    * `2h` → every 2 hours
    * `1d` → once a day
    * `7d` → once a week
* **MaxBackups**
  * The maximum number of backups saved. Old ones get deleted automatically.
  * Good rule of thumb:
    * Small servers → 3–5 backups
    * Big servers → 2–3 (they take up more space)
* **LogsChannelID**
  * The channel where you’ll see backup notifications.
  * To set this up:
    1. Right-click the channel you want.
    2. Copy its **ID**.
    3. Replace `CHANNEL_ID` with that number.
  * Leave it as `CHANNEL_ID` if you don’t want notifications.

***

Here’s the basic config you’ll use:

```yaml
Backup:
  Enabled: true          # Turn backups on
  Schedule: 1d           # How often to back up
  MaxBackups: 5          # How many backups to keep
  LogsChannelID: 123456789012345678   # Channel for backup alerts
```

***

### What Gets Backed Up?

When backups run, they include:\
✅ All channels & categories\
✅ Permissions & settings\
✅ All roles + permissions\
✅ Members, roles, and nicknames\
✅ Up to **50 messages per channel**

But note:\
❌ Images & attachments\
❌ Voice recordings\
❌ Server boosts, emojis, integrations\
…are **not** saved.


# Leveling

The leveling system rewards users with XP for chatting and voice activity. As they level up, they can unlock roles, earn coins, and show off with customizable rank cards.

### Core Settings

#### ✅ Enabled

Turns the leveling system on or off.

```yaml
Enabled: true
```

***

#### ResetDataOnLeave

* `true` → User’s XP & levels reset when they leave.
* `false` → Progress is saved if they rejoin (recommended).

```yaml
ResetDataOnLeave: false
```

***

#### MessageXP & 🎙 VoiceXP

* Format: `min-max`
* Defines how much XP users earn for activity.

**Examples**

* MessageXP: `5-10`
* VoiceXP: `2-5`

**Recommendations**

* Small servers → Higher XP (e.g., `10-20`, `2-4`)
* Large servers → Lower XP (e.g., `5-10`, `1-2`)

***

#### XPNeeded

* Base XP needed to level up.
* Scales with each level (Level 2 = 300 XP, Level 3 = 600 XP, etc).

**Examples**

* Fast leveling → `200-250`
* Normal → `300-400`
* Slow (competitive) → `500-750`

```yaml
XPNeeded: 300
```

***

### Channel & Category Settings

Control where XP is earned:

```yaml
ChannelSettings:
  LevelUpChannelID: ""           # Leave empty to use current channel
  DisabledChannels: ["12345"]    # No XP in these channels
  DisabledCategories: ["67890"]  # No XP in these categories
```

***

### XP Cooldown

Cooldowns prevent spam and balance XP gain.

```yaml
CooldownSettings:
  EnableXPCooldown: true
  XPCooldown: "30s"   # Message XP cooldown
  VoiceInterval: "60s" # How often voice XP is given
```

**Recommendations**

* Message cooldown → `30s–60s`
* Voice interval → `60s–120s`

***

### Level-Up Messages

Customize the message or embed shown when a user levels up.

**Available placeholders**

* `{user}` → Mention user
* `{userName}` → Username only
* `{userId}` → Discord ID
* `{userIcon}` → Profile picture
* `{userBanner}` → Banner image
* `{guildName}` → Server name
* `{oldLevel}` / `{newLevel}` → Levels
* `{oldXP}` / `{newXP}` → XP progress
* `{randomLevelMessage}` → Pulls from `lang.yml`

**Example (simple message):**

```yaml
LevelUpMessage: "{user}, you are now level {newLevel}"
UseEmbed: false
```

**Example (embed):**

```yaml
UseEmbed: true
Embed:
  Title: "🎉 Level Up!"
  Description:
    - "{userName} just reached level {newLevel}!"
    - "{randomLevelMessage}"
  Thumbnail: "{userIcon}"
  Color: "#eda3f0"
```

***

### Role Rewards

Give roles when users hit certain levels.

#### StackRoles

* `true` → Users keep all unlocked level roles (Level 1, 5, 10).
* `false` → User only has the highest level role.

**Example:**

```yaml
RoleSettings:
  StackRoles: true
  LevelRoles:
    - level: 1
      roleID: "ROLE_ID"
    - level: 5
      roleID: "ROLE_ID"
```

***

### Coin Rewards (Economy Integration)

Reward coins at specific levels.

* `+1` → Every level
* `+5` → Every 5 levels
* `25` → Exactly level 25

**Example:**

```yaml
ScaleRewards:
  StackRewards: false
  Rewards:
    - level: +1
      coins: 10
```

***

### Rank Card Customization

Your rank card can be styled with colors, progress bars, and emojis.

```yaml
RankCard:
  AccentColor: "#1769FF"
  SecondaryColor: "#4785FF"
  ProgressBar:
    StartColor: "#1769FF"
    EndColor: "#4785FF"
  Emojis:
    Level: "✧"
    TopRank: "♚"
    NormalRank: "★"
```

***

### Quick Setup Examples

* **Casual Server (fast leveling)**
  * MessageXP: `10-20`
  * VoiceXP: `2-4`
  * XPNeeded: `200`
* **Competitive Server (slow leveling)**
  * MessageXP: `5-8`
  * VoiceXP: `1-2`
  * XPNeeded: `500`
* **Community Server (balanced)**
  * MessageXP: `8-15`
  * VoiceXP: `1-3`
  * XPNeeded: `300`

***

### Commands

#### 👥 User Commands

* `/rank` → View your rank card
* `/rank @user` → View someone else’s rank
* `/leaderboard` → Show server leaderboard

#### 🔧 Admin Commands *(requires Permission roles)*

* `/level give @user 5` → Add XP
* `/level take @user 3` → Remove XP
* `/level set @user 10` → Set user’s level
* `/level reset @user` → Reset progress


# Giveaways

The giveaway system makes it easy to host and manage Discord giveaways. It supports advanced features like role requirements, entry restrictions, customizable embeds, and automatic winner selection.

### Core Settings (Config)

Add these to your config to control permissions and behavior:

{% code expandable="true" %}

```yaml
Giveaways:
  AllowRoles: ["ROLE_ID", "ROLE_ID"]     # Who can run /giveaway
  GiveawayStatusCheck: 7500              # How often to check end-times (ms)
  DirectMessageWinners: true             # DM winners on win

  Embed:
    ActiveGiveaway:
      EmbedColor: "#1769FF"
      EmbedImage: "https://i.imgur.com/yw6UcuW.jpg"
      EmbedFooterIcon: "https://i.imgur.com/13VlA3w.png"
      EmbedThumbnail: "https://i.imgur.com/ewT6bOT.png"
      ShowTitle: true
      ShowThumbnail: true
      ShowHostedBy: true
      ShowEndsIn: true
      ShowEntries: true
      ShowWhitelistRoles: true
      ShowBlacklistRoles: true
      ShowMinimumServerJoinDate: true
      ShowMinimumAccountAge: true
      ShowMinimumMessages: true
      ShowImage: true
      ShowFooter: true
      Button:
        JoinButton:
          ButtonStyle: "Primary"
          ButtonEmoji: "🎉"
          ButtonText: "Enter"
        CheckPercent:
          ButtonStyle: "Secondary"
          ButtonEmoji: "📈"
          ButtonText: "Odds"
        ShowEntries:
          ButtonStyle: "Secondary"
          ButtonEmoji: "👥"
          ButtonText: "Entries"
        ShowEntrantsList:
          ButtonStyle: "Secondary"
          ButtonEmoji: "👥"
          ButtonText: "Show Entrants"
          Embed:
            Title: "🎉 Giveaway Entrants - {prize}"
            Description:
              - "{entrantsList}"
            Footer:
              Text: "Total Entrants: {totalEntrants} • Page {currentPage}/{totalPages}"
              Icon: "{footerIcon}"
            Color: "#1769FF"
            Thumbnail: "https://i.imgur.com/ewT6bOT.png"

    EndedGiveaway:
      EmbedColor: "#1769FF"
      EmbedImage: "https://i.imgur.com/7TQDDAy.png"
      EmbedFooterIcon: "https://i.imgur.com/13VlA3w.png"
      EmbedThumbnail: "https://i.imgur.com/ewT6bOT.png"
      ShowTitle: true
      ShowThumbnail: true
      ShowImage: true
      ShowWinnersField: true
      ShowEntriesField: true
      ShowFooter: true
```

{% endcode %}

#### What these do

* **AllowRoles** → Only these roles can use `/giveaway` (all subcommands). Replace `"ROLE_ID"` with real IDs.
* **GiveawayStatusCheck** → Interval (ms) the bot uses to see if a giveaway ended.
  * Default: **7500** (7.5s)
  * Small servers: **5000–10000** (5–10s)
  * Large servers: **10000–15000** (10–15s)
* **DirectMessageWinners** → `true` is recommended so winners get a DM.

***

### Active Giveaway Embed Options

All `Show*` flags are `true/false` and control what the running giveaway shows:

* Title, Thumbnail, Hosted By, Ends In (countdown), Entries
* Whitelist/Blacklist roles
* Minimum Server Join Date / Account Age / Messages
* Image, Footer

#### Buttons & Styles

* **Primary** (Blue), **Secondary** (Gray), **Success** (Green), **Danger** (Red)
* Built-ins:
  * **Enter** (`JoinButton`)
  * **Odds** (`CheckPercent`)
  * **Entries** (`ShowEntries`)
  * **Show Entrants** (`ShowEntrantsList`, opens a paginated embed)

***

### Ended Giveaway Embed

After a giveaway ends, the **EndedGiveaway** embed appears. You can toggle:

* Title, Thumbnail, Image, Winners field, Entries field, Footer

***

### Entrants List Placeholders

Inside the entrants list embed you can use:

* `{prize}` – prize name
* `{entrantsList}` – formatted list of entrants
* `{totalEntrants}` – total count
* `{currentPage}` / `{totalPages}` – pagination info

***

### Commands

#### `/giveaway create`

Create a giveaway with all your options in one go.

**Required options**

* `channel` – The channel to post the giveaway in
* `time` – Duration (see formats below)
* `winners` – Number of winners
* `prize` – The prize name
* `hostedby` – Who is hosting (type `@Username`)

**Optional restrictions**

* `min_server_join_date` – Example: `January 1 2024`
* `min_account_age` – Example: `January 1 2023`
* `min_invites` – Integer (minimum invites to enter)
* `min_messages` – Integer (minimum messages to enter)
* `whitelist_roles` – Mention roles allowed to enter (e.g., `@VIP @Boosters`)
* `blacklist_roles` – Mention roles disallowed (e.g., `@Muted`)
* `notify` – One of:
  * `Nobody` → `notify_nobody`
  * `Whitelist Roles` → `notify_whitelist_roles`
  * `Everyone` → `notify_everyone`
* `extra_entries` – Give bonus entries to roles (format: `@role:entries @role2:entries`)
  * Example: `@VIP:5 @Booster:3`

**Examples**

* Simple daily Nitro:

  <pre><code><strong>/giveaway create
  </strong>  channel: #giveaways
    time: 1d
    winners: 1
    prize: Discord Nitro
    hostedby: @Staff
  </code></pre>
* With restrictions and extras:

  ```
  /giveaway create
    channel: #events
    time: 2h
    winners: 3
    prize: $25 Gift Card
    hostedby: @Admin
    min_server_join_date: January 1 2024
    min_account_age: January 1 2023
    min_messages: 100
    whitelist_roles: @Members @Boosters
    blacklist_roles: @Muted
    notify: notify_whitelist_roles
    extra_entries: @VIP:5 @Booster:3
  ```

{% hint style="success" %}
💡 **Tips:**&#x20;

* Date format must be like `January 1 2025` (month name, day, year).
* Mentions in strings should be typed as you would in Discord (e.g., `@Role`, `@User`).
  {% endhint %}

***

#### `/giveaway end`

End a running giveaway by its **Giveaway ID** (found in the footer of the embed).

```
/giveaway end giveaway_id: GW-12345
```

#### `/giveaway reroll`

Pick new winner(s) for a finished giveaway. Optionally specify particular users to reroll.

```
/giveaway reroll
  giveaway_id: GW-12345
  users: @UserOne @UserTwo   (optional)
```

***

### ⏲Duration Formats

Use these in the `time` field:

* **m** = minutes → `30m`, `45m`
* **h** = hours → `2h`, `12h`
* **d** = days → `1d`, `7d`
* **w** = weeks → `1w`, `2w`
* **y** = years → `1y`


# Economy

The economy system lets your members earn, gamble, and spend coins in your server. You can customize rewards, games, fishing spots, and even run a full server shop with ranks, boosters, and items.

### Core Settings

#### Administrator Roles

* Defines who can use `/economy admin` commands.
* Replace `"ROLE_ID"` with the actual role IDs.

```yaml
administrator: ["ROLE_ID"]
```

#### Interest System

Earn passive income on money stored in the bank.

* **defaultInterestRate** → Daily % (0.05 = 5%)
* **interestInterval** → When interest is paid (24h format)
* **maxInterestEarning** → Cap to prevent farming

Example:

```yaml
defaultInterestRate: 0.05   # 5% daily
interestInterval: ["10:00"] # Pays out at 10:00 AM
maxInterestEarning: 100000
```

***

### Income Commands

Your server’s main money-makers:

* **Daily** → Free daily coins (scales with streaks).
* **Work** → Earn coins for "working".
* **Beg** → Small random coins.
* **Crime** → Risk/reward: sometimes profit, sometimes loss.
* **Rob** → Steal from others (if they have enough balance).

Example:

```yaml
Daily:
  baseAmount: 200
  increasePerDay: 50
  maxAmount: 1000
```

***

### Fishing System

A fun minigame with multiple fishing spots. Each location has:

* **Cost** → Pay to fish.
* **Fish list** → Different fish, drop chances, and rewards.
* Higher cost = better fish.

Example (ocean spot):

```yaml
ocean:
  cost: 300
  fish:
    - name: "Mackerel"
      chance: 0.22
      minReward: 200
      maxReward: 400
    - name: "Cod"
      chance: 0.20
      minReward: 250
      maxReward: 500
```

👉 You can add as many custom fishing spots as you like!

***

### Gambling Games

Members can risk their coins in fun games:

* **Blackjack** – 1:1 payout
* **Roulette** – Bet on red/black/green
* **Slots** – Multiplier rewards
* **Coinflip & Roll** – Simple luck-based games

Example (Roulette):

```yaml
Roulette:
  winMultiplier:
    red: 2
    black: 2
    green: 14
```

***

### Store System

Your custom shop where members spend their coins.

#### Categories

* **Ranks** → Permanent Discord roles
* **Boosters** → Temporary buffs
* **Items** → Permanent upgrades or consumables
* **Equipment** → Tools & gear (like fishing rods)

#### Examples

**Ranks**

```yaml
Ranks:
  1:
    Name: "Supporter Rank"
    Description: "Gain the Supporter role in our server"
    Price: "10000"
    RoleID: ["ROLE_ID"]
    Limit: "1"
```

**Boosters**

```yaml
Boosters:
  1:
    Name: "Money Booster"
    Description: "Earn 1.5x coins for 24h"
    Price: "15000"
    Booster: "Money"
    Multiplier: "1.5"
    Duration: "24h"
```

**Items**

```yaml
Items:
  1:
    Name: "Bank Interest 0.3%"
    Description: "Increase bank interest rate"
    Price: "10000"
    Type: "Interest"
    Interest: "0.3"
    Limit: "1"
```

**Equipment**

```yaml
Equipment:
  1:
    Name: "Basic Fishing Rod"
    Description: "A simple rod for beginners"
    Price: "1000"
    Type: "FishingRod"
    CatchBonus: 1.1
    LuckBonus: 1.05
```

***

### Store Embed Customization

Make your shop look polished with placeholders:

* `{itemCount}` → Item number
* `{item}` → Item name
* `{price}` → Item price
* `{description}` → Item description
* `{pageCurrent}/{pageMax}` → Pagination


# Verification

The verification system provides two main features: Join Roles (automatic role assignment when users join) and Verification System (require users to verify before accessing the server).

### Overview

This system helps with **security, organization, and onboarding**. It has two main parts:

* **Join Roles** → Automatically give roles to users when they join.
* **Verification System** → Require users to verify before accessing the server.

***

### Join Roles System

#### How It Works

* **Automatic assignment** → Users get the listed roles instantly on joining.
* **Multiple roles** → You can assign more than one role at once.
* **Role restoration** → If enabled, the bot remembers and restores previous roles when someone rejoins.

#### Role Restoration Settings

* **Blacklist** → Roles that will **not** be restored (e.g., muted/punished roles).
* **Whitelist** → Only these roles are restored.

{% hint style="success" %}
**Hint:** If both are used → **Whitelist always takes priority**.
{% endhint %}

***

### Verification System

#### Basic Configuration

Enable this system to force users to verify before gaining server access.

#### Verification Types

1. **Button Verification (`BUTTON`)**
   * Easiest option: One click to verify.
   * Great for casual servers and smooth onboarding.
2. **Calculator Verification (`CALCULATOR`)**
   * Users must solve a random math problem (e.g., *What is 7 + 3?*).
   * Wrong answers let them retry.
   * Good balance of security vs. ease.
3. **CAPTCHA Verification (`CAPTCHA`)**
   * Bot sends an image/text CAPTCHA.
   * User must type the solution to verify.
   * Strongest anti-bot protection.

***

### Unverified Role System

When enabled:

* Bot creates an **@Unverified** role automatically.
* This role can only see the verification channel.
* Once verified, the role is removed and the user gets normal access.

**Alternative:** You can also set this role up manually.

***

### Setup Examples

#### Basic Server Verification

* Use **BUTTON** for easy onboarding.
* Single @Member role is given after verification.

#### Security-Focused Server

* Use **CAPTCHA** or **CALCULATOR**.
* Add @Unverified role with restricted access.
* Whitelist role restoration to prevent restoring punished roles.

#### Community Server

* BUTTON or CALCULATOR for balance.
* Multiple join roles (e.g., @Introduced, @Community).

#### Gaming Server

* BUTTON + Role menu after verification.
* Gives @Player role automatically.

***

### Common Issues & Solutions

**“Verification not working”**

* Make sure system is enabled.
* Check `ChannelID` is correct.
* Verify bot can send messages in that channel.

**“Roles not being assigned”**

* Replace all `ROLE_ID` with real IDs.
* Ensure bot’s role is **higher** than assigned roles.

**“Users can’t see verification channel”**

* Check **@Unverified** role permissions.
* Ensure **@everyone** can’t access other channels.

**“Role restoration not working”**

* Confirm `RestoreRoles.Enabled: true`.
* Check whitelist/blacklist setup.

**“Calculator or CAPTCHA too difficult”**

* Calculator only uses numbers **1–20** with addition/subtraction.
* CAPTCHA uses simple text recognition.
* Switch to **BUTTON** if you want less friction.

***

### Security Considerations

* **Anti-Bot Protection**:
  * BUTTON → easiest, least secure.
  * CALCULATOR / CAPTCHA → strongest security.
* **Role Security**:
  * Don’t restore punishment roles (muted, banned).
  * Use **whitelist** for sensitive setups.
* **Channel Security**:
  * Verification channel should be **read-only** except for bot.
  * Auto-delete verification attempts if needed.


# Invite Roles

The Invite Roles system rewards members with roles when they invite others to your server. It’s automatic, scalable, and encourages community growth.

### Basic Configuration

```yaml
InviteRoles:
  Enabled: true
  Tiers:
    1:
      Roles: ["ROLE_ID"]   # Example: Newbie Role
      Amount: 1            # Role unlocked at 1 invite
    2:
      Roles: ["ROLE_ID"]   # Example: Trusted Role
      Amount: 10           # Role unlocked at 10 invites
```

👉 Replace each `ROLE_ID` with real role IDs from your server.

***

### How It Works

* The bot tracks new joins via Discord’s invite system.
* When a user reaches the invite count of a tier, they get the corresponding role.
* Higher tiers override lower ones (user only keeps the **highest tier role** they qualify for).

***

### Commands

* `/invites` → Check your own invites
* `/invites @user` → Check another user’s invites
* `/inviter @user` → See who invited a user


# Reaction Roles

A comprehensive guide to setting up and configuring reaction role panels in DrakoBot.

## Overview

Reaction roles allow members to self-assign roles by clicking buttons, selecting from dropdowns, or reacting with emojis. DrakoBot supports three panel types with advanced features like role limits, conditions, and auto-granting.

***

## Configuration Location

Edit the reaction roles in: `config/modules/roles.yml`

All panels go under the `ReactionRoles:` section.

***

## Panel Types

| Type     | Description                           | Best For                    |
| -------- | ------------------------------------- | --------------------------- |
| `BUTTON` | Interactive buttons below the message | 2-5 roles, clean look       |
| `SELECT` | Dropdown menu                         | 5+ roles, saves space       |
| `REACT`  | Traditional emoji reactions           | Simple setups, legacy style |

***

## Basic Setup

### Minimal Configuration

{% code expandable="true" %}

```yaml
ReactionRoles:
  Enabled: true

  PanelName:
    type: "BUTTON"
    ChannelID: ["CHANNEL_ID"]
    
    Reactions:
      - Name: "Role Name"
        Emoji: "📢"
        Style: "Primary"
        RoleID: ["ROLE_ID"]
        Description: "Role description"
        RemoveRoles: []
```

{% endcode %}

Required Fields:

* `type` - Panel type (BUTTON, SELECT, or REACT)
* `ChannelID` - Where to post the panel (supports arrays for multi-guild)
* `Reactions` - Array of role configurations

Per-Reaction Fields:

* `Name` - Display name for the role
* `Emoji` - Emoji to show
* `RoleID` - Role ID to assign (supports arrays for multi-guild)
* `RemoveRoles` - Roles to remove when this is selected (optional)

Button Styles: `Primary` (blue), `Success` (green), `Danger` (red), `Secondary` (gray)

***

## Advanced Features

### Panel-Level Options

```yaml
PanelName:
  type: "BUTTON"
  ChannelID: ["CHANNEL_ID"]
  
  MaxSelections: 3
  RequiredRoles: ["VERIFIED_ROLE_ID"]
  resetReacts: true
  UseComponentsV2: true
```

Options:

* `MaxSelections` - Limit how many roles can be selected from this panel
* `RequiredRoles` - Member must have ALL these roles to use the panel
* `resetReacts` - Remove user's reaction after clicking (REACT type only)
* `UseComponentsV2` - Enable modern Discord UI components

### Per-Role Advanced Options

```yaml
Reactions:
  - Name: "VIP Role"
    Emoji: "💎"
    Style: "Primary"
    RoleID: ["ROLE_ID"]
    RemoveRoles: ["BASIC_ROLE_ID", "OTHER_ROLE_ID"]
    GrantRoles: ["CATEGORY_ROLE_ID", "PERK_ROLE_ID"]
    Description: "VIP membership"
    Conditions:
      RequiredRoles: ["VERIFIED_ID", "LEVEL_10_ID"]
      ExcludedRoles: ["BANNED_ID"]
      ForbiddenRoles: ["TRIAL_ID"]
```

* `RemoveRoles` - Automatically removes these roles when selected (e.g., remove "Blue" when selecting "Red")
* `GrantRoles` - Automatically grants additional roles (e.g., give "Has Color Role" category separator)

Conditions:

* `RequiredRoles` - Must have ALL of these to select this role
* `ExcludedRoles` - Cannot select if member has ANY of these
* `ForbiddenRoles` - Can only select if member has NONE of these

***

## Quick Examples

{% stepper %}
{% step %}

### Simple Notification Roles (Buttons)

{% code expandable="true" %}

```yaml
ReactionRoles:
  Enabled: true

  Notifications:
    type: "BUTTON"
    ChannelID: ["CHANNEL_ID"]
    
    Embed:
      Title: "📢 Notification Roles"
      Description:
        - "Click a button to toggle notification roles."
        - "You can select multiple roles!"
      Color: "#5865F2"
      Thumbnail: "https://i.imgur.com/w5XxKpc.png"
    
    Reactions:
      - Name: "Announcements"
        Emoji: "📢"
        Style: "Primary"
        RoleID: ["ROLE_ID"]
        Description: "Server announcements"
        RemoveRoles: []
      - Name: "Events"
        Emoji: "🎉"
        Style: "Success"
        RoleID: ["ROLE_ID"]
        Description: "Event notifications"
        RemoveRoles: []
      - Name: "Updates"
        Emoji: "📰"
        Style: "Secondary"
        RoleID: ["ROLE_ID"]
        Description: "Bot & server updates"
        RemoveRoles: []
```

{% endcode %}
{% endstep %}

{% step %}

### Color Roles with Limits (Select Menu)

{% code expandable="true" %}

```yaml
ReactionRoles:
  Enabled: true

  ColorRoles:
    type: "SELECT"
    ChannelID: ["CHANNEL_ID"]
    MaxSelections: 1
    
    Embed:
      Title: "🎨 Color Roles"
      Description:
        - "Choose ONE color for your name!"
      Color: "#FF5555"
    
    Reactions:
      - Name: "Red"
        Emoji: "🔴"
        Style: "Danger"
        RoleID: ["RED_ROLE_ID"]
        Description: "Red name color"
        RemoveRoles: ["BLUE_ROLE_ID", "GREEN_ROLE_ID", "YELLOW_ROLE_ID"]
      - Name: "Blue"
        Emoji: "🔵"
        Style: "Primary"
        RoleID: ["BLUE_ROLE_ID"]
        Description: "Blue name color"
        RemoveRoles: ["RED_ROLE_ID", "GREEN_ROLE_ID", "YELLOW_ROLE_ID"]
      - Name: "Green"
        Emoji: "🟢"
        Style: "Success"
        RoleID: ["GREEN_ROLE_ID"]
        Description: "Green name color"
        RemoveRoles: ["RED_ROLE_ID", "BLUE_ROLE_ID", "YELLOW_ROLE_ID"]
      - Name: "Yellow"
        Emoji: "🟡"
        Style: "Secondary"
        RoleID: ["YELLOW_ROLE_ID"]
        Description: "Yellow name color"
        RemoveRoles: ["RED_ROLE_ID", "BLUE_ROLE_ID", "GREEN_ROLE_ID"]
```

{% endcode %}
{% endstep %}

{% step %}

### Pronoun Roles (Buttons)

{% code expandable="true" %}

```yaml
ReactionRoles:
  Enabled: true

  Pronouns:
    type: "BUTTON"
    ChannelID: ["CHANNEL_ID"]
    
    Embed:
      Title: "🌈 Pronoun Roles"
      Description:
        - "Select your pronouns! You can pick multiple."
      Color: "#FFA500"
    
    Reactions:
      - Name: "He/Him"
        Emoji: "👨"
        Style: "Primary"
        RoleID: ["ROLE_ID"]
        Description: "He/Him pronouns"
        RemoveRoles: []
      - Name: "She/Her"
        Emoji: "👩"
        Style: "Primary"
        RoleID: ["ROLE_ID"]
        Description: "She/Her pronouns"
        RemoveRoles: []
      - Name: "They/Them"
        Emoji: "⚧️"
        Style: "Primary"
        RoleID: ["ROLE_ID"]
        Description: "They/Them pronouns"
        RemoveRoles: []
      - Name: "Any Pronouns"
        Emoji: "✨"
        Style: "Secondary"
        RoleID: ["ROLE_ID"]
        Description: "Any pronouns"
        RemoveRoles: []
```

{% endcode %}
{% endstep %}

{% step %}

### Gaming Roles with Auto-Grant (Select)

{% code expandable="true" %}

```yaml
ReactionRoles:
  Enabled: true

  GamingRoles:
    type: "SELECT"
    ChannelID: ["CHANNEL_ID"]
    MaxSelections: 3
    
    Embed:
      Title: "🎮 Gaming Roles"
      Description:
        - "Select up to 3 games you play!"
        - "You'll get access to game channels."
      Color: "#7289DA"
    
    Reactions:
      - Name: "Valorant"
        Emoji: "🎯"
        Style: "Primary"
        RoleID: ["VALORANT_ROLE_ID"]
        Description: "Valorant player"
        RemoveRoles: []
        GrantRoles: ["GAMER_ROLE_ID"]
      - Name: "League of Legends"
        Emoji: "⚔️"
        Style: "Success"
        RoleID: ["LOL_ROLE_ID"]
        Description: "LoL player"
        RemoveRoles: []
        GrantRoles: ["GAMER_ROLE_ID"]
      - Name: "Minecraft"
        Emoji: "🟩"
        Style: "Success"
        RoleID: ["MINECRAFT_ROLE_ID"]
        Description: "Minecraft player"
        RemoveRoles: []
        GrantRoles: ["GAMER_ROLE_ID"]
      - Name: "Fortnite"
        Emoji: "🏆"
        Style: "Primary"
        RoleID: ["FORTNITE_ROLE_ID"]
        Description: "Fortnite player"
        RemoveRoles: []
        GrantRoles: ["GAMER_ROLE_ID"]
```

{% endcode %}
{% endstep %}

{% step %}

### VIP Roles with Requirements (Buttons)

{% code expandable="true" %}

```yaml
ReactionRoles:
  Enabled: true

  VIPPerks:
    type: "BUTTON"
    ChannelID: ["CHANNEL_ID"]
    RequiredRoles: ["VIP_ROLE_ID"]
    
    Embed:
      Title: "💎 VIP Perks"
      Description:
        - "Exclusive roles for VIP members!"
        - "Must have VIP role to access."
      Color: "#FFD700"
    
    Reactions:
      - Name: "Early Access"
        Emoji: "⚡"
        Style: "Primary"
        RoleID: ["EARLY_ACCESS_ROLE_ID"]
        Description: "Early feature access"
        RemoveRoles: []
        Conditions:
          RequiredRoles: ["VIP_ROLE_ID"]
          ExcludedRoles: ["BANNED_ROLE_ID"]
      - Name: "Custom Color"
        Emoji: "🎨"
        Style: "Success"
        RoleID: ["CUSTOM_COLOR_ROLE_ID"]
        Description: "Custom name color"
        RemoveRoles: []
        Conditions:
          RequiredRoles: ["VIP_ROLE_ID"]
      - Name: "VIP Voice"
        Emoji: "🎤"
        Style: "Primary"
        RoleID: ["VIP_VOICE_ROLE_ID"]
        Description: "VIP voice channels"
        RemoveRoles: []
        Conditions:
          RequiredRoles: ["VIP_ROLE_ID"]
```

{% endcode %}
{% endstep %}

{% step %}

### Region Selection (Reactions)

{% code expandable="true" %}

```yaml
ReactionRoles:
  Enabled: true

  Regions:
    type: "REACT"
    resetReacts: true
    ChannelID: ["CHANNEL_ID"]
    
    Embed:
      Title: "🌍 Region Selection"
      Description:
        - "React with your region to get regional announcements."
      Color: "#3498DB"
    
    Reactions:
      - Name: "North America"
        Emoji: "🇺🇸"
        Style: "Primary"
        RoleID: ["NA_ROLE_ID"]
        Description: "North America"
        RemoveRoles: []
      - Name: "Europe"
        Emoji: "🇪🇺"
        Style: "Primary"
        RoleID: ["EU_ROLE_ID"]
        Description: "Europe"
        RemoveRoles: []
      - Name: "Asia"
        Emoji: "🇯🇵"
        Style: "Primary"
        RoleID: ["ASIA_ROLE_ID"]
        Description: "Asia"
        RemoveRoles: []
      - Name: "Oceania"
        Emoji: "🇦🇺"
        Style: "Primary"
        RoleID: ["OCE_ROLE_ID"]
        Description: "Oceania"
        RemoveRoles: []
```

{% endcode %}
{% endstep %}
{% endstepper %}

***

## ComponentsV2 (Modern UI)

ComponentsV2 provides a modern Discord UI experience with rich formatting and media support.

### Basic ComponentsV2 Example

{% code expandable="true" %}

````yaml
ReactionRoles:
  Enabled: true

  ModernPanel:
    type: "BUTTON"
    ChannelID: ["CHANNEL_ID"]
    UseComponentsV2: true
    
    ComponentsV2:
      Components:
        - Type: "container"
          AccentColor: "#5865F2"
          Components:
            - Type: "section"
              Text:
                Content: "**ROLE SELECTION**\n\nSelect your preferences below.\n\n```📢  Announcements  ·  Server updates\n🎉  Events         ·  Event pings\n📰  News           ·  News updates```\n\n-# Click buttons below  ·  Drako Bot"
              Accessory:
                Type: "thumbnail"
                Media:
                  URL: "https://i.imgur.com/w5XxKpc.png"
    
    Reactions:
      - Name: "Announcements"
        Emoji: "📢"
        Style: "Primary"
        RoleID: ["ROLE_ID"]
        Description: "Server announcements"
        RemoveRoles: []
````

{% endcode %}

ComponentsV2 Structure:

* `Type: "container"` - Main container
* `AccentColor` - Hex color for the accent bar
* `Type: "section"` - Content section
* `Text.Content` - Markdown-formatted content
* `Accessory.Type: "thumbnail"` - Optional thumbnail image

***

## Multi-Guild Support

All ID fields support arrays for multi-guild setups:

{% code expandable="true" %}

```yaml
ReactionRoles:
  Enabled: true

  MultiGuildPanel:
    type: "BUTTON"
    ChannelID: ["CHANNEL_ID_GUILD1", "CHANNEL_ID_GUILD2"]
    
    Reactions:
      - Name: "Member"
        Emoji: "✅"
        Style: "Success"
        RoleID: ["ROLE_ID_GUILD1", "ROLE_ID_GUILD2"]
        Description: "Member role"
        RemoveRoles: []
```

{% endcode %}

The bot will match the channel ID's index with the role ID's index (first channel uses first role, second channel uses second role).

***

## How to Deploy Panels

1. Edit `config/modules/roles.yml`
2. Add or modify panels under `ReactionRoles:`
3. Restart the bot completely
4. Panels will automatically appear in their configured channels

Note: Any time you edit a panel configuration, you must restart the bot for changes to take effect.

***

## Troubleshooting

<details>

<summary>❌ Buttons/Reactions Not Working</summary>

Check:

* `ReactionRoles.Enabled: true` is set
* Bot has `Manage Roles` permission
* Bot's highest role is **above** all roles being assigned
* Member meets panel's `RequiredRoles` (if set)
* Member meets reaction's `Conditions` (if set)

</details>

<details>

<summary>❌ Panel Not Appearing</summary>

Solutions:

* Verify `ChannelID` is correct (right-click channel → Copy ID)
* Ensure Developer Mode is enabled in Discord settings
* Restart the bot completely
* Check bot has `Send Messages` and `Embed Links` permissions in channel

</details>

<details>

<summary>❌ Roles Not Being Assigned</summary>

Check:

* Replace all `ROLE_ID` placeholders with actual role IDs
* Bot's role is **higher** in the role hierarchy than assigned roles
* Bot has `Manage Roles` permission
* Roles aren't marked as "managed" (bot roles, booster roles can't be assigned)

</details>

<details>

<summary>❌ "Max Selections Reached" Error</summary>

Solution:

* Remove some roles first, then add new ones
* Or increase `MaxSelections` value

</details>

<details>

<summary>❌ "Missing Required Roles" Error</summary>

Check:

* Member has all roles listed in panel's `RequiredRoles`
* Member has all roles listed in reaction's `Conditions.RequiredRoles`

</details>

<details>

<summary>❌ "Excluded By Role" Error</summary>

Solution:

* Member has a role in `ExcludedRoles` or `ForbiddenRoles`
* Remove the conflicting role first

</details>

<details>

<summary>❌ Multiple Panels Interfering</summary>

Solution:

* Each panel must have a **unique name** (e.g., `Panel1`, `Panel2`, not both named `Roles`)
* Use descriptive names like `ColorRoles`, `NotificationRoles`, `GamingRoles`

</details>

<details>

<summary>❌ Panel Posted in Wrong Channel</summary>

Solutions:

* Double-check `ChannelID` value
* For multi-guild: ensure channel IDs are in correct order matching role IDs
* Delete old panel messages manually if needed

</details>

***

## Best Practices

* Use descriptive panel names (`ColorRoles` not `Panel1`)
* Set `MaxSelections` for exclusive role categories
* Use `RemoveRoles` to prevent conflicting role combinations
* Add `RequiredRoles` to panels for verified members only
* Use ComponentsV2 for modern, polished appearance
* Use appropriate button styles (Primary/Success/Danger/Secondary)

***

## Getting Role & Channel IDs

1. Enable Developer Mode in Discord:
   * User Settings → App Settings → Advanced → Developer Mode: **ON**
2. Get Role ID:
   * Server Settings → Roles → Right-click role → Copy ID
3. Get Channel ID:
   * Right-click channel → Copy ID


# Moderation

The logging system records user actions, moderation events, voice activity, security incidents, and more. All logs are sent to configured channels using embeds, so staff can easily review activity

### Quick Setup

1. Enable the log/feature you want.

   ```yaml
   Enabled: true
   ```
2. Replace `"CHANNEL_ID"` with your **actual log channel ID**.
3. (Optional) Customize embeds with placeholders.

👉 [**How to copy a Channel ID**](https://docs.drakodevelopment.net/getting-started/developer-mode-copying-ids)

***

### User Activity Logs

#### 🔄 Profile Changes

* Tracks username, avatar, and discriminator updates.

#### 🎭 Role Management

* Logs when users gain or lose roles (manual or automatic).

***

### Server Management Logs

#### 🖼 Server Changes

* Name changes, icon updates, and settings modifications.

#### #️⃣ Channel Management

* Channel creation/deletion.
* Permission changes.

***

### Message Activity Logs

#### ❌ Message Deletion

* Shows deleted content.
* Can include attachments if `LogImages: true`.
* Shows who deleted it (if available).

#### ✏️ Message Edits

* Logs before/after message content.

#### 📦 Purge Logs

* Logs bulk message deletes in a **single purge channel**.

***

### Voice Activity Logs

* Voice joins/leaves.
* Channel switches.
* Streaming activity.

***

### Moderation Logs

#### ⏳ Timeout System

* Logs timeouts applied/removed.

#### ⚠️ Warning System

* Configurable warning tiers & punishments.

```yaml
Warnings:
  Expiry: 30d
  Punishments:
    1:
      Timeout: ""
    2:
      Timeout: "10m"
    3:
      Timeout: "15m"
```

#### 🔨 Ban/Kick System

* Logs bans, unbans, and kicks with moderator info.

**Available placeholders:**

* `{user}` → Mentioned user
* `{userTag}` → Username#0000
* `{userId}` → User ID
* `{moderator}` → Staff member
* `{reason}` → Reason for action
* `{guildName}` → Server name
* `{longtime}` → Full date/time
* `{shorttime}` → Shortened time

***

### Security & Anti-Abuse Logs

#### 👤 Alt Prevention

* Blocks/kicks accounts younger than `TimeLimit` (e.g. `30d`).
* Sends DM & log embed if triggered.

#### ☢️ Anti-Nuke

* Protects from mass bans/kicks/channel/role deletes.
* Uses tiered thresholds with escalating actions (mute/remove role/ban).

#### 🔰 Anti-Hoist

* Removes special characters (`-`, `_`, `!`, etc.) from usernames.
* Optionally renames to a default like `zName`.

#### 👥 Anti-Mass Mention

* Prevents spam mentions (`@everyone` or multiple users).
* Can timeout offenders and DM them.

#### 🚫 Anti-Spam

* Limits how many messages can be sent in a short time.
* Example: `MsgLimit: 4` within `TimeLimit: "1s"`.
* Can timeout & DM the user.

#### 📝 Blacklist Words

* Deletes messages with blacklisted patterns.
* Supports wildcards (`*badword*`) and regex.
* Can whitelist roles, channels, or categories.

***

### Miscellaneous Logs

* 📥 Invite tracking
* 🎉 Giveaway logs
* 🗑 Purge logs (bulk deletions)
* 📝 Reports (user-submitted reports with jump-to-message links)

Example report log:

```yaml
Report:
  LogsChannelID: "CHANNEL_ID"
  Embed:
    Title: "📢 New Report"
    Description:
      - "**User:** {user}"
      - "**Content:** {message}"
      - "**Channel:** {channel}"
      - "**Reporter:** {reportingUser}"
      - "**Reason:** {reason}"
      - "**Date:** {timestamp}"
```


# Welcome & Leave

The welcome and leave message system automatically sends customizable messages when users join or leave your server

### Placeholders

Before setting up, note the placeholders you can use.

Example:

```yml
# {userName}: YouSeeMeRunning
# {user}: @YouSeeMeRunning
# {userTag}: YouSeeMeRunning#1234
# {userId}: 264032125496459265 - This is the ID
# {userBanner}: Displays their users banner in the image field  
# {UserCreation}: How old is their account? Discord timestamp 
# {guildName}: Guild Name
# {guildIcon}: Guild Icon
# {memberCount}: Guild Member Count - 1st, 2nd, 3rd, 4th...
# {memberCountNumeric}: Displays 5, 6, 7...
# {longTime}: March 1st, 2023
# {shortTime}: 17:00
# {user-joinedAt}: For leave messages, displays when they first joined
# {user-createdAt}: The date when the user's Discord account was created
# {autoKickTime}: How long until the user is kicked
# {invitedBy}: Who invited them?
# {invitedByCount}: How many invites the person who invited them has
# {joinDate} # Discord timestamp for when they joined
# {joinTime} # Discord timestamp for when they joined
# {leaveDate} # Discord timestamp for when they left
# {leaveTime} # Discord timestamp for when they left
```

Use these in your `Text`, `Embed.Description`, `Footer`, etc.

***

### Welcome Messages

#### Core Settings

```yml
WelcomeMessage:
  Enabled: false
  ChannelID: "CHANNEL_ID"
  Type: "BOTH" # EMBED, MESSAGE, BOTH
  Text: "Welcome to **{guildName}**, {userName}"
```

* **Enabled** → Master switch for welcome system
* **ChannelID** → Channel where messages will be sent
* **Type** →
  * `MESSAGE` = text only
  * `EMBED` = embed only
  * `BOTH` = show both, embed and text.

***

#### Embed Options

{% code expandable="true" %}

```yml
Embed:
  Title: ""
  Description:
    - "Welcome to **{guildName}**, {userName}! 🎉"
    - "Invited By » {invitedBy} ({invitedByCount} invites)"
    - "Join Date » {joinDate} ({joinTime})"
    - "Account Age » {UserCreation}"
    - "Members » {memberCount} member"
  Footer:
    Text: "{guildName} • Today at {shortTime}"
    Icon: "{guildIcon}"
  Author:
    Text: "Welcome to {guildName}!"
    Icon: "https://.../hand-waving-icon.png"
  Color: "#1769FF"
  Image: "{userBanner}"
  Thumbnail: "{user-avatar}"
  Buttons:
    - Type: "LINK"
      Name: "Product"
      Emoji: "📜"
      Link: "https://builtbybit.com/..."
```

{% endcode %}

* **Description** → Multiple lines supported
* **Images/Thumbnails** → Use placeholders like `{user-avatar}`, `{userBanner}`
* **Buttons** → Add useful links (e.g., rules, website, product page)

***

#### Direct Message (DM) Welcome

{% code expandable="true" %}

```yml
DM:
  Enabled: false
  Embed:
    Title: "Welcome, {userName}!"
    Description:
      - "Welcome to **{guildName}**, {userName}! 🎉"
      - "You are our {memberCount} member!"
    Footer:
      Text: "{guildName} • Today at {shortTime}"
      Icon: "{guildIcon}"
    Color: "#1769FF"
    Image: "{userBanner}"
    Thumbnail: "{user-avatar}"
```

{% endcode %}

* Sends a private DM to each new member.
* Great for onboarding or linking rules.
* ⚠️ Some users disable server DMs → not a bug.

***

#### Auto-Delete System

```yml
AutoDelete:
  Enabled: false
  Delay: "15s"
```

* Deletes welcome messages after set time.
* **Delay** accepts: `10s`, `1m`, `1h`, `1d`.
* Useful for keeping your welcome channel clean.

***

### Leave Messages

#### Core Settings

```yml
LeaveMessage:
  Enabled: false
  ChannelID: "CHANNEL_ID"
  Type: "BOTH"
  Text: "Goodbye, {userName}!"
```

* **Enabled** → Master switch for leave system
* **ChannelID** → Where the leave message is posted
* **Type** → `MESSAGE`, `EMBED`, or `BOTH`

***

#### Embed Options

{% code expandable="true" %}

```yml
Embed:
  Description:
    - "Goodbye, {userName}!"
    - "Hope to see you again soon...."
  Footer:
    Text: "{guildName} • {shortTime}"
    Icon: "{guildIcon}"
  Author:
    Text: "👋 See you soon!"
  Color: "#1769FF"
  Thumbnail: "{user-avatar}"
```

{% endcode %}

* Keeps your farewell messages consistent with your welcome style.
* Use `{user-joinedAt}` to remind how long they were part of the server.

***

#### Auto-Delete System

```yml
AutoDelete:
  Enabled: false
  Delay: "15s"
```

* Works the same as the welcome auto-delete.
* Helpful for servers that want clean logs without permanent goodbye posts.

***

### Best Practices

* **Message design** → Keep it warm, friendly, and on-brand.
* **Channel management** → Use a dedicated `#welcome` or `#farewell` channel.
* **Invite tracking** → `{invitedBy}` only works if invite tracking is enabled.

***

### Common Issues

* **“Messages not showing”** → Make sure `Enabled: true` and bot has send/embed perms.
* **“Placeholders not working”** → They are case-sensitive, e.g., `{userName}` not `{username}`.
* **“Images not loading”** → Test the URL in a browser. Ensure that you right click the image and "Copy Image Address".
* **“DMs not delivered”** → Some users block DMs.


# Terms and Conditions

By using our products you agree to the below terms

### Terms of Service and Use Agreement

#### 1. Definitions

**1.1 Products**\
"Products" refers to any applications, software licenses, digital content, or items offered through Drako Development's online store.

**1.2 User**\
"User" denotes any individual or entity utilizing our services, including viewing, purchasing, registering, or participating in any manner on our website.

**1.3 Use**\
"Use" encompasses all forms of interaction with our products, including but not limited to downloading, installing, activating, hosting, and operating.

**1.4 Agreement**\
"Agreement" constitutes the binding legal contract represented by this Terms of Service and Use document.

**1.5 Guild**\
"Guild" refers to a Discord server or similar community instance in which the Product is deployed or used.

***

#### 2. Use of Information and Products

**2.1 Consent to Data Collection**\
Users consent to the collection, storage, and protection of personal data by Drako Development in accordance with our Privacy Policy.

**2.2 Sharing Information**\
User information may be shared with third parties only under conditions that meet our stringent privacy criteria.

**2.3 Prohibition of Unlawful Activities**\
Engaging in unlawful activities with our products is strictly prohibited and grounds for immediate termination of access and further legal action.

**2.4 Right to Modify or Discontinue**\
Drako Development reserves the right to modify or discontinue any product or service at any time, without notice.

***

#### 3. Third-Party Interactions and Security

**3.1 Hosting Providers**\
Users may select any web hosting provider for products that require such services, provided they adhere to this Agreement.

**3.2 Third-Party Compliance**\
Users are responsible for ensuring that third parties interacting with their hosted product comply with these terms.

**3.3 User Responsibility**\
The User is accountable for the security of their use of the Product and compliance with this Agreement.

***

#### 4. Permitted and Prohibited Uses

**4.1 Multi-Guild Usage (Owner-Only Permission)**\
Users may operate the Product across **multiple Guilds** **only if**:

* The User is the **verified owner** of each Guild, **or**
* The User has full administrative ownership rights equivalent to server ownership.

Under no circumstances may the Product be operated for Guilds owned, controlled, or primarily managed by third parties without explicit written consent from Drako Development.

**4.2 Hosting for Third Parties**\
Hosting, sublicensing, or otherwise making the Product available to third parties—whether for profit or otherwise—is expressly forbidden.

This includes, but is not limited to:

* Public bots available for invitation by unrelated Guilds
* Managed or shared bot instances
* Offering the Product as a service, platform, or hosted solution

**4.3 Modifications**\
Users may not modify the Product’s core functionality, including its licensing system. Customizations must not violate any terms of this Agreement.

***

#### 5. Product Distribution

**5.1 Redistribution**\
Redistribution, resale, sublicensing, or any form of transfer of the Product without explicit written consent from Drako Development is prohibited.

***

#### 6. Maintenance and Support

**6.1 Permanent Agreement**\
This Agreement is a permanent part of the Product and must be adhered to at all times.

**6.2 Liability for Interruptions**\
Drako Development is not liable for interruptions to Product functionality due to maintenance, downtime, or external service failures.

**6.3 Discontinued Products**\
Discontinued Products will not be re-offered for download; users are advised to maintain their backups.

***

#### 7. Intellectual Property

**7.1 Copyright Notices**\
Users must not remove or alter any copyright notices, trademarks, or credits associated with the Product.

**7.2 Third-Party Contributions**\
Products may incorporate contributions from third-party authors, as acknowledged in product documentation.

***

#### 8. Financial Terms

**8.1 Secure Transactions**\
All transactions are processed via specified secure methods. Drako Development does not directly handle payments.

**8.2 Refund Policy**\
Due to the digital nature of our Products, refunds are generally not offered except under exceptional circumstances.

***

#### 9. Limitation of Liability

**9.1 No Warranties**\
Drako Development's Products are provided “as is,” without warranties of any kind, either express or implied.

**9.2 User Responsibility**\
Users bear all responsibility for damages or liabilities arising from misuse of the Product.

***

#### 10. Violations and Enforcement

**10.1 Termination and Legal Action**\
Violations of this Agreement may result in immediate termination of access, license revocation, and legal action.

**10.2 Enforcement Rights**\
Drako Development reserves the right to enforce this Agreement through all legal and equitable remedies.

***

#### 11. Amendments

**11.1 Right to Amend**\
Drako Development reserves the right to amend this Agreement at any time. Continued use of the Product after amendments constitutes acceptance of the revised terms.

***

#### 12. Commercial Restrictions

**12.1 Commercial Hosting and Redistribution**\
Users are expressly forbidden from hosting, redistributing, sublicensing, or commercializing Drako Development's Products—including bots and software—for third parties without prior written consent.

**12.2 Prohibited Activities**\
This includes, but is not limited to:

* Public or shared bot deployments
* Bot-as-a-service offerings
* Paid or unpaid managed solutions
* Use in Guilds not owned by the User

**12.3 Consequences of Violation**\
Violation of these terms will result in immediate termination of the User’s license and may lead to legal action.


# Subscription Terms

## Drako Bot Premium Subscription - Terms of Service

**Effective Date:** November 25, 2024\
**Last Updated:** November 25, 2024

By subscribing to Drako Bot Premium, you agree to these terms in addition to our [General Terms of Service](https://docs.drakodevelopment.net/legal/terms-and-conditions).

***

### 1. Premium Subscription Service

#### 1.1 Service Description

Drako Bot Premium ("Premium Service") is a monthly subscription service that provides enhanced features and increased limits for Discord server management.

#### 1.2 Subscription Scope

* Premium subscription applies to **one Discord server** (guild) only
* Features are accessible only to the subscribed server
* Each server requires its own separate subscription

#### 1.3 Premium Features

Premium subscribers receive access to:

* Unlimited ticket types and panels
* Unlimited reaction role panels with unlimited roles per panel
* Unlimited auto-react and auto-respond rules
* Custom commands functionality
* Unlimited blacklist words
* Unlimited suggestion channels
* Unlimited active giveaways
* Invite tracker and audit logs
* Priority support
* Additional features as announced

***

### 2. Pricing and Payment

#### 2.1 Subscription Fee

* **Monthly Subscription:** $3.99 USD per month per server
* Pricing subject to change with 30 days notice

#### 2.2 Payment Processing

* All payments processed securely through PayPal
* Drako Development does not directly handle or store payment information
* You will be redirected to PayPal to complete your subscription

#### 2.3 Billing Cycle

* Subscriptions renew automatically on a monthly basis
* Billing occurs on the same day each month as your initial subscription
* You will be charged unless you cancel before the renewal date

#### 2.4 Payment Methods

* PayPal account or PayPal-accepted payment methods
* All transactions in USD

***

### 3. Subscription Management

#### 3.1 Activation

* Premium features activate immediately upon successful payment verification
* Activation typically occurs within 1-5 minutes
* If activation doesn't occur within 15 minutes, contact support

#### 3.2 Cancellation Policy

* You may cancel your subscription at any time
* Cancellation takes effect at the end of your current billing period
* You retain Premium access until the end of the paid period
* No partial refunds for early cancellation
* Cancel through the dashboard at: `https://dashboard.drako.gg/guild/YOUR_SERVER/tier`

#### 3.3 Suspension or Termination

Drako Development may suspend or terminate your Premium subscription if:

* Payment fails or is disputed
* You violate these terms or our General Terms of Service
* Your Discord server is banned or deleted
* Fraudulent activity is detected

***

### 4. Refund Policy

#### 4.1 No Refunds

Due to the digital nature of our service and immediate feature activation:

* **Subscriptions are non-refundable**
* **Cancellations do not result in prorated refunds**
* No refunds for partial billing periods

#### 4.2 Exceptions

Refunds may be considered only in exceptional circumstances:

* Service unavailability for extended periods (7+ consecutive days)
* Billing errors or duplicate charges
* Technical issues preventing feature access

#### 4.3 Refund Requests

* Must be submitted within 7 days of the charge
* Raise a ticket in: <https://discord.gg/drakobot>
* Include transaction ID and detailed explanation

***

### 5. Service Availability and Support

#### 5.1 Uptime

* We strive for 99.5% uptime but do not guarantee uninterrupted service
* Scheduled maintenance will be announced when possible
* Emergency maintenance may occur without notice

#### 5.2 Support

* Premium subscribers receive priority support
* Support available via Discord
* Response time: typically within 24 hours

#### 5.3 Feature Changes

* We reserve the right to modify, add, or remove Premium features
* Significant changes will be announced 30 days in advance when possible
* Subscription pricing applies to the current feature set

***

### 6. Data and Privacy

#### 6.1 Data Collection

By subscribing, you consent to collection of:

* Discord server ID (guild ID)
* Subscription status and billing information
* PayPal transaction IDs
* Feature usage data

#### 6.2 Data Usage

* Data used solely for service provision and improvement
* We do not sell your data to third parties
* Full details in our [Privacy Policy](https://docs.drakodevelopment.net/legal/privacy-policy)

#### 6.3 Data Retention

* Subscription data retained for accounting and tax purposes
* Discord bot data retained per our standard data retention policy
* You may request data deletion after subscription ends

***

### 7. Acceptable Use

#### 7.1 Prohibited Activities

Premium subscribers must NOT:

* Share subscription access with other servers
* Abuse or exploit Premium features
* Use the service for illegal purposes
* Attempt to circumvent payment systems
* Resell or redistribute Premium features
* Use automated systems to abuse features

#### 7.2 Discord Terms Compliance

* You must comply with [Discord's Terms of Service](https://discord.com/terms)
* You must comply with [Discord's Community Guidelines](https://discord.com/guidelines)
* Violations may result in immediate termination

#### 7.3 Commercial Use

* Premium subscription intended for legitimate Discord server management
* Commercial hosting or resale of Drako Bot services is prohibited
* See Section 12 of our General Terms of Service

***

### 8. Intellectual Property

#### 8.1 License Grant

* Premium subscription grants you a limited, non-exclusive, non-transferable license
* License to use Premium features for your subscribed Discord server only
* License terminates upon subscription cancellation or termination

#### 8.2 Restrictions

* You may not reverse engineer, decompile, or modify the bot
* You may not remove branding, credits, or copyright notices
* You may not create derivative works

#### 8.3 Drako Development Rights

* All rights, title, and interest in Drako Bot remain with Drako Development
* "Drako Bot" and related trademarks are property of Drako Development

***

### 9. Limitation of Liability

#### 9.1 Service "As Is"

* Premium Service provided "AS IS" and "AS AVAILABLE"
* No warranties, express or implied
* We do not guarantee specific results or uptime

#### 9.2 Limitation of Damages

TO THE MAXIMUM EXTENT PERMITTED BY LAW:

* Drako Development's liability limited to subscription fees paid in the last 3 months
* Not liable for indirect, incidental, special, or consequential damages
* Not liable for loss of data, profits, or business opportunities

#### 9.3 User Responsibility

* You are responsible for:
  * Your Discord server configuration
  * Actions of your server members
  * Backup of your server data and settings
  * Compliance with Discord and applicable laws

***

### 10. Chargebacks and Disputes

#### 10.1 Chargeback Policy

* Chargebacks will result in immediate subscription termination
* Account may be permanently banned
* We reserve the right to pursue payment collection

#### 10.2 Dispute Resolution

* Contact us first: <https://discord.gg/drakobot>
* We will work to resolve disputes within 14 days
* Chargebacks should be a last resort

***

### 11. Modifications to Terms

#### 11.1 Right to Modify

* We may modify these terms at any time
* Material changes announced 30 days in advance
* Continued use after changes constitutes acceptance

#### 11.2 Notification

* Changes posted to: <https://docs.drakodevelopment.net/legal/terms-and-conditions>
* Important changes may be emailed or announced on Discord

#### 11.3 Rejection of Changes

* If you reject changes, you may cancel your subscription
* No refunds for rejection-based cancellations

***

### 12. Governing Law and Jurisdiction

#### 12.1 Applicable Law

* These terms governed by the laws of United Kingdom
* Excluding conflict of law provisions

#### 12.2 Dispute Resolution

* Any disputes resolved through binding arbitration
* Class action waiver applies

***

### 13. Contact Information

For questions about Premium subscriptions:\
**Discord:** <https://discord.gg/drakobot\\>
**Documentation:** <https://docs.drakodevelopment.net>

***

### 14. Severability

If any provision is found unenforceable, remaining provisions remain in effect.

***

### 15. Entire Agreement

These terms, together with our General Terms of Service and Privacy Policy, constitute the entire agreement regarding Premium subscriptions

***

**By clicking "Subscribe with PayPal" you acknowledge that you have read, understood, and agree to be bound by these Terms of Service.**

For full terms, visit: <https://docs.drakodevelopment.net/legal/terms-and-conditions>


# Privacy Policy

By using our products you agree to the below terms

### Introduction

Drako Development is committed to protecting your privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website and use our products and services.

Please read this policy carefully. If you do not agree with the terms of this Privacy Policy, please do not access the site or content.

### Information We Collect

#### Personal Data

We collect personally identifiable information, such as your name, email address, and electronic information.

#### Derivative Data

Information our servers and systems automatically collect when you access the site and products, such as:

* IP address
* Browser type
* Operating system
* Access times
* Pages viewed directly before and after accessing the site and services

### Use of Your Information

* **Processing Transactions:** To process purchases, orders, payments, and other financial transactions.
* **Communication:** To send you information in regards to our services and products.
* **Improve Services:** To improve our website and services through analysis and interpretation of user behavior and preferences.
* **Legal Obligations:** To comply with legal obligations and protect our rights.

### Sharing Your Information

#### Third-Party Service Providers

We may share your information with third parties that perform services for us or on our behalf, such as payment processing, data analysis, and hosting services.

#### Business Transfers

We may share or transfer your information in connection with, or during negotiations of, any merger, sale of company assets, financing, or acquisition of all or a portion of our business to another company.

#### Legal Requirements

If we are legally required to disclose your information, we will comply with such requirements.

### Your Data Rights

* **Data Access:** You have the right to request access to the personal data we hold about you.
* **Data Portability:** You have the right to request a copy of your personal data in a structured, commonly used, and machine-readable format.
* **Data Deletion:** You have the right to request the deletion of your personal data. To request data deletion, please contact us using the information provided in the "Contact Us" section below. We will process your request within 30 days, subject to any legal obligations that may require us to retain certain information.
* **Data Correction:** You have the right to request correction of any inaccurate or incomplete personal data we hold about you.
* **Withdrawal of Consent:** Where we rely on your consent to process your personal data, you have the right to withdraw that consent at any time.

To exercise any of these rights, please contact us using the information provided in the "Contact Us" section below. We may need to verify your identity before processing your request.

### Security of Your Information

We use administrative and technical measures to help protect your personal information. While we have taken reasonable steps to secure the personal information you provide to us, please be aware that despite our efforts, no security measures are perfect or impenetrable.

### Changes to This Privacy Policy

We may update this Privacy Policy from time to time in order to reflect changes to our practices or for other operational, legal, or regulatory reasons. Please revisit this page periodically to ensure you are aware of any changes.

### Contact Us

If you have any questions or concerns about this Privacy Policy, or if you wish to exercise any of your data rights, please contact Drako Development at:

**User:** @youseemerunning\
**Discord:** <https://discord.gg/drakobot>

For data deletion requests, please include:

* Your Discord user ID
* The specific data you wish to have deleted
* Verification of your identity (we may ask for additional confirmation)

{% hint style="danger" %}

{% endhint %}


# First Time Setup

Welcome! This guide will walk you through the process of setting up Drako Paste from start to finish. Follow each step carefully, and you'll be up and running in no time.

### Step 1: Prerequisites

* **Node.js** v18+
* **MongoDB** (see [MongoDB Setup Guide)](https://docs.drakodevelopment.net/product-docs/getting-started/mongodb-setup)
* **Git** (optional)

***

### Step 2: Quick Setup

#### 1. Create `.env`

In the project folder, create a new file called `.env` and add:

```env
MONGODB_URI="mongodb://localhost:27017/drakopaste"

NEXTAUTH_SECRET="change-me"
NEXTAUTH_URL="http://localhost:3000"
SYSTEM_SETUP_KEY="change-me"

SECURE="false"
PORT="3000"
```

{% hint style="danger" %} <mark style="color:red;">**Important:**</mark> Make sure to make your own [Mongo URL](https://docs.drakodevelopment.net/product-docs/getting-started/mongodb-setup)
{% endhint %}

***

#### 2. Install

```bash
npm install
```

***

#### 3. Run

```bash
npm run dev
```

***

### Step 3: Admin Panel

* Open <http://localhost:3000/admin>
* Login:
  * **User:** `admin`
  * **Pass:** `admin`

⚠️ Change this password immediately in the admin panel.


# Nginx Setup

Deploy DrakoPaste behind Nginx with HTTPS/SSL for secure access.

### Step 1: Prerequisites

* Ubuntu/Debian server with root access
* Domain pointing to server
* DrakoPaste running on port **3000 (Optional)**

***

### Step 2: Install Nginx

```bash
sudo apt update
sudo apt install nginx
sudo systemctl enable --now nginx
```

***

### Step 3: SSL Certificate (Let’s Encrypt)

```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```

{% hint style="success" %}
**Hint:** Replace `your-domain.com` with your actual domain.
{% endhint %}

***

### Step 4: Nginx Config

Create config:

```bash
sudo nano /etc/nginx/sites-available/drakopaste
```

Basic setup:

```nginx
server {
    listen 80;
    server_name paste.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name paste.example.com;

    ssl_certificate /etc/letsencrypt/live/paste.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/paste.example.com/privkey.pem;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";
    add_header Referrer-Policy "strict-origin-when-cross-origin";

    # API routes
    location /api {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Static assets with caching
    location /_next/static {
        proxy_pass http://localhost:3000;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Next.js assets
    location /_next {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Main application
    location / {
        proxy_pass http://localhost:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

{% hint style="danger" %} <mark style="color:red;">**Important:**</mark> Make sure to change `paste.example.com` and `3000` if you are using a different port            &#x20;
{% endhint %}

Enable:

```bash
sudo ln -s /etc/nginx/sites-available/drakopaste /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
```

***

### Step 5: Firewall

```bash
sudo ufw allow 'Nginx Full'
sudo ufw status
```

***

### Step 6: Run DrakoPaste

```bash
cd /path/to/drakopaste
npm install
npm start
```

***

### Step 7: Test

* Visit [**https://your-domain.com**](https://your-domain.com?utm_source=chatgpt.com)
* Create a paste

***


# MongoDB Setup

Setup and configure MongoDB

***

### Step 1: Create an Account

#### Visit MongoDB Cloud:

1. **Open your web browser** and navigate to [MongoDB Cloud.](https://www.mongodb.com/products/platform/cloud)

#### Sign Up:

2. **Click on the Sign Up button**.
3. **Enter your email address and create a password**.
4. Alternatively, you can sign up using your **Google account**.

#### Accept Privacy Policy & Terms:

5. **Read through the Privacy Policy & Terms**.
6. **Check the box to accept them and proceed**.

#### Answer "Getting to Know You" Questions:

7. You will be presented with a few questions to help MongoDB understand your needs.
8. You can answer these questions randomly as they do not affect your setup.

***

### Step 2: Deploy Your Cluster

#### Choose the Free Tier:

1. After logging in, you will be directed to the **MongoDB Atlas dashboard**.
2. **Click on Build a Cluster**.
3. Select the **M0 (Free) tier option**.

#### Cluster Configuration:

**Name Your Cluster:**

4. You can leave the default name as **Cluster0** or choose a custom name.

**Cloud Provider & Region:**

5. Choose **AWS** as your cloud provider.
6. Select a region closest to you. (**Frankfurt** is recommended for European users).
7. Click **Create Cluster** to begin the deployment process.

***

### Step 3: Set Up a Database User

#### Create a Database User:

1. While your cluster is being created, you will need to set up a database user.
2. Go to the **Database Access tab**.
3. **Click on Add New Database User**.

#### Set a Username & Password:

4. Enter a username of your choice.
5. Create a strong password and make a note of it as you will need it later.
6. Click **Add User** to create the database user.

***

### Step 4: Choose a Connection Method

#### Connect to Your Cluster:

1. Once your cluster is created, go to the **Clusters view**.
2. **Click on the Connect button** for your cluster.

#### Choose a Connection Method:

3. Select **Connect Your Application**.

**Drivers:**

4. Select Drivers
5. Copy the connection string provided.

{% hint style="success" %}
**Note:** Ensure the connection string starts with `mongodb+srv://`.
{% endhint %}

#### Update Your Configuration File:

6. Open your **config.yml** file (Drako Bot config.yml).
7. Paste the connection string into the file.
8. Replace `<password>` in the connection string with the password you noted down earlier.

{% hint style="danger" %} <mark style="color:red;">I</mark><mark style="color:red;">**mportant:**</mark> Make sure to remove **`< >`**
{% endhint %}

***

### Step 5: Configure Network Access

#### Add IP Address:

1. Navigate to the **Network Access tab**.
2. **Click on Add IP Address**.
3. Enter the IP address of the server where you will be hosting your bot or application.
4. If you want to allow access from anywhere, you can add `0.0.0.0/0`, but this is not recommended for security reasons.
5. Click **Confirm** to add the IP address.

***

### Common Issues and Troubleshooting

#### Buffer Timeout

**Whitelist IP Address:**

1. Ensure your server's IP is whitelisted under the **Network Access tab**.

**Correct Password:**

2. Verify that the password you are using in the connection string is correct.
3. If you have forgotten your password, you can reset it in the **Database Access tab**:
   * Go to **Database Access**.
   * Click **Edit** next to the user.
   * Enter a new password and save the changes.


# API Requests

DrakoPaste lets you create and retrieve pastes via a simple REST API.

### Base URL

```
https://your-domain.com/api/v1
```

***

### Rate Limits

* **POST /pastes** → 20 req/min per IP
* **GET /pastes** → 10 req/min per IP

Headers in every response:

* `X-RateLimit-Limit`
* `X-RateLimit-Remaining`
* `X-RateLimit-Reset`

***

### Authentication

No authentication required. All endpoints are public (with rate limits).

***

### Endpoints

#### 1. Create Paste

**POST** `/pastes`

<table><thead><tr><th>Field</th><th>Type</th><th width="173">Required</th><th>Notes</th></tr></thead><tbody><tr><td><code>content</code></td><td>string</td><td>✅</td><td>Paste text (1MB)</td></tr><tr><td><code>title</code></td><td>string</td><td>❌</td><td>Max 100 chars</td></tr><tr><td><code>language</code></td><td>string</td><td>❌</td><td>Syntax highlighting</td></tr><tr><td><code>visibility</code></td><td>string</td><td>❌</td><td><code>public</code>, <code>unlisted</code>, <code>private</code></td></tr><tr><td><code>burnAfterRead</code></td><td>boolean</td><td>❌</td><td>Delete after first view</td></tr><tr><td><code>expiration</code></td><td>string</td><td>❌</td><td><code>never</code>, <code>1hour</code>, <code>1day</code>, <code>1week</code>, <code>1month</code></td></tr><tr><td><code>password</code></td><td>string</td><td>❌</td><td>Protect paste</td></tr><tr><td><code>maxViews</code></td><td>number</td><td>❌</td><td>1–1000 views</td></tr></tbody></table>

**Example:**

```bash
curl -X POST https://your-domain.com/api/v1/pastes \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Hello World",
    "content": "console.log(\"Hello, World!\");",
    "language": "javascript",
    "visibility": "public",
    "expiration": "1week"
  }'
```

***

#### 2. Retrieve Paste

**GET** `/pastes?id={pasteId}`

| Param | Type   | Required | Description |
| ----- | ------ | -------- | ----------- |
| `id`  | string | ✅        | Paste ID    |

**Example:**

```bash
curl "https://your-domain.com/api/v1/pastes?id=abc123def"
```

***

### Error Codes

| Code                  | Meaning                   |
| --------------------- | ------------------------- |
| `INVALID_JSON`        | Bad JSON body             |
| `EMPTY_BODY`          | Request body missing      |
| `VALIDATION_ERROR`    | Missing/invalid fields    |
| `CONTENT_TOO_LARGE`   | Exceeds 1MB               |
| `TITLE_TOO_LONG`      | Title >100 chars          |
| `INVALID_EXPIRATION`  | Wrong expiration value    |
| `RATE_LIMIT_EXCEEDED` | Too many requests         |
| `MISSING_ID`          | Paste ID required         |
| `PASTE_NOT_FOUND`     | Paste does not exist      |
| `PASTE_EXPIRED`       | Paste expired             |
| `PASTE_BURNED`        | Burn-after-read triggered |
| `MAX_VIEWS_REACHED`   | View limit hit            |
| `DATABASE_ERROR`      | DB issue                  |

***

### Examples

#### Simple text

```bash
curl -X POST https://your-domain.com/api/v1/pastes \
  -H "Content-Type: application/json" \
  -d '{"content": "My notes"}'
```

#### Code snippet

```bash
-d '{
  "title": "React Component",
  "content": "function App(){return <div>Hello</div>}",
  "language": "javascript"
}'
```

#### Private + password

```bash
-d '{
  "content": "api_key=secret123",
  "visibility": "private",
  "password": "mypassword"
}'
```

#### Burn after read

```bash
-d '{
  "content": "One-time secret",
  "burnAfterRead": true
}'
```

#### Limited views

```bash
-d '{
  "content": "5-view paste",
  "maxViews": 5
}'
```

***

### Supported Languages

DrakoPaste supports **90+ languages and formats**.

* **Web**: `javascript`, `typescript`, `jsx`, `tsx`, `html`, `css`, `scss`
* **General**: `python`, `java`, `csharp`, `cpp`, `c`, `go`, `rust`, `php`, `ruby`, `swift`
* **Functional**: `haskell`, `clojure`, `elixir`, `erlang`, `fsharp`, `ocaml`
* **Shell & Scripts**: `bash`, `zsh`, `powershell`, `batch`
* **Data/Config**: `json`, `yaml`, `toml`, `ini`, `xml`, `csv`
* **Database**: `sql`, `plsql`, `mongodb`
* **Docs**: `markdown`, `latex`, `asciidoc`
* **DevOps**: `docker`, `terraform`, `kubernetes`, `nginx`, `apache`
* **Other**: `regex`, `diff`, `git`, `protobuf`, `graphql`, `solidity`, `wasm`, `assembly`
* **Scientific/Legacy**: `r`, `matlab`, `julia`, `fortran`, `cobol`, `pascal`

👉 For plain text, use `text` or leave `language` empty.

***

### Client Examples

#### Node.js

```javascript
const res = await fetch("https://your-domain.com/api/v1/pastes", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ content: "console.log('Hello')" })
});
console.log(await res.json());
```

#### Python

```python
import requests
res = requests.post("https://your-domain.com/api/v1/pastes", json={
  "content": "print('Hello')"
})
print(res.json())
```


# First Time Setup

This guide walks you through getting Drako Tickets running for the first time.

{% embed url="<https://youtu.be/H0mCKO-OkQY>" %}

## Prerequisites

* Node.js 18 or later
* A [Supabase](https://supabase.com/) project **(free tier is fine)**
* A hosting provider (Vercel recommended) or a self-hosted server

{% stepper %}
{% step %}

### Install dependencies

Open a terminal in the project folder and run:

```bash
npm install
```

{% endstep %}

{% step %}

### Create your environment file

The project includes a `.env.local.example` file with every available variable and a description of each. Create a copy and call it `.env`

{% hint style="success" %}
Rename the `.env.example` to `.env`
{% endhint %}

Open `.env` in a text editor. The sections below explain where to find each value.

#### Supabase credentials

These are required for the app to connect to your database and auth service.

<table><thead><tr><th width="274.25">Variable</th><th>Where to find it</th></tr></thead><tbody><tr><td><code>NEXT_PUBLIC_SUPABASE_URL</code></td><td>Supabase dashboard → Select the Project  → Copy the Project URL under <em>"x's Project"</em></td></tr><tr><td><code>NEXT_PUBLIC_SUPABASE_ANON_KEY</code></td><td>Project Settings → API Keys → Legacy anon → <strong>Copy</strong> <code>anon public</code></td></tr><tr><td><code>SUPABASE_SERVICE_ROLE_KEY</code></td><td>Project Settings → API Keys → Legacy anon → <strong>Copy</strong> <code>service_role</code></td></tr><tr><td><code>DATABASE_URL</code></td><td>Project Overview → Connect <em>(Top of Page)</em> → Connection String → Change method to <strong>'Transaction pooler'</strong> → <strong>Copy</strong> database link</td></tr></tbody></table>

{% hint style="success" %}
You can view your project by navigating to <https://supabase.com/dashboard/org>
{% endhint %}

{% hint style="info" %}
Your `DATABASE_URL` will look like&#x20;

```bash
postgresql://postgres.xxx:[YOUR-PASSWORD]@aws-1-eu-west-1.pooler.supabase.com:6543/postgres
```

{% endhint %}

#### App URL

These three variables tell the app what URL it is running on. They are used to build absolute URLs for outbound emails, OAuth redirect callbacks, and the IMAP poller.

**Production (example):**

```env
APP_PROTOCOL=https
APP_HOST=your-app.vercel.app
APP_PORT=443
```

**Local development:**

```env
APP_PROTOCOL=http
APP_HOST=localhost
APP_PORT=3000
```

{% endstep %}

{% step %}

### Configure Supabase Auth

Before running the app, set the allowed URLs in your Supabase project so that OAuth and email callbacks work correctly.

1. Go to your Supabase dashboard → **Authentication → URL Configuration**.
2. Set **Site URL** to your app's URL, e.g. `https://your-app.vercel.app`.
3. Under **Redirect URLs**, add:
   * `https://your-app.vercel.app/auth/callback`
   * `http://localhost:3000/auth/callback` (for local development)

<figure><img src="/files/JVQDKHEXwclE3c8azx0E" alt=""><figcaption></figcaption></figure>
{% endstep %}

{% step %}

### Run the application

```bash
npm run start
```

On first boot the application will automatically:

1. Run all pending database migrations
2. Create a default admin account

**Default admin credentials:**

```
Email:    admin@admin.com
Password: password123
```

{% hint style="danger" %}
**Important:** Log in immediately and change the admin password. You will be prompted to set the email and password
{% endhint %}
{% endstep %}
{% endstepper %}

## Next steps

* [Email Integration](broken://pages/bcbb669a0de7f0f2d9b4fe2827bceef51eedd056) — set up SMTP outbound mail and IMAP inbound email-to-ticket
* [Discord Integration](broken://pages/ba1af47f8d873d53ffc145e6489756aeacaa5971) — connect the Discord bot to your server
* [Login Providers](broken://pages/4e071aae0b08c4518a130bb49a0e61e0410ee80c) — enable Google, Discord, or Microsoft OAuth sign-in


# Nginx Configuration

Configure Drako Tickets to run on your own domain with Nginx, HTTPS, and PM2.

{% hint style="info" %}
This guide uses `tickets.example.com` as the example domain and `/var/www/drako-tickets` as the installation directory. Replace both values where necessary.
{% endhint %}

## Prerequisites

Before continuing, make sure that:

* Drako Tickets has been uploaded to an Ubuntu or Debian server.
* You have a user with `sudo` access.
* Ports `80` and `443` are open in your server or hosting-provider firewall.
* You know the directory that contains `package.json`.

Check the application directory before using the commands in this guide:

```bash
cd /var/www
ls
```

Your directory may have a different name. Update `/var/www/drako-tickets` in the commands below if needed.

{% stepper %}
{% step %}

## Create the DNS Record

Create an **A record** with your DNS provider:

| Type | Host      | Value         |
| ---- | --------- | ------------- |
| A    | `tickets` | `<SERVER_IP>` |

This example makes Drako Tickets available at `tickets.example.com`.

If you use Cloudflare, set the record to **DNS only** until the SSL certificate has been issued. Remove any incorrect **AAAA record** unless your server is also configured for IPv6.

DNS changes can take time to propagate. Confirm that the domain resolves to your server before requesting an SSL certificate:

```bash
getent hosts tickets.example.com
```

{% endstep %}

{% step %}

## Install Nginx

```bash
sudo apt update
sudo apt install nginx curl -y
sudo systemctl enable --now nginx
```

If UFW is enabled, allow web traffic:

```bash
sudo ufw allow 'Nginx Full'
```

{% endstep %}

{% step %}

## Install Drako Tickets Dependencies

Change to the directory that contains the Drako Tickets `package.json` file:

```bash
cd /var/www/drako-tickets
npm c
```

If the application has not been configured yet, create its environment file:

```bash
cp .env.example .env
nano .env
```

Keep your existing licence, Supabase, database, and Discord values. Set the public application address as follows:

```dotenv
APP_PROTOCOL=https
APP_HOST=tickets.example.com
APP_PORT=443
```

`APP_HOST` must contain only the hostname. Do not include `http://`, `https://`, a path, or a trailing slash.

Press `CTRL + X`, then `Y`, then `Enter` to save the file.
{% endstep %}

{% step %}

## Run Drako Tickets with PM2

Install PM2 and start the application:

```bash
npm install -g pm2
cd /var/www/drako-tickets
pm2 start npm --name "drako-tickets" -- start
```

The production start command builds Drako Tickets and serves it on port `3000`. If `DISCORD_BOT_TOKEN` is present in `.env`, the same command also starts the Discord bot.

Wait for the build to finish, then check the service and test it locally:

```bash
pm2 status
pm2 logs drako-tickets --lines 100
curl -I http://127.0.0.1:3000
```

Press `CTRL + C` to exit the logs. A response from `curl` confirms that the application is listening; a redirect response is also normal.

Save the PM2 process list and configure it to start after a reboot:

```bash
pm2 save
pm2 startup
```

`pm2 startup` prints one additional command beginning with `sudo`. Copy and run that exact command, then run `pm2 save` once more.
{% endstep %}

{% step %}

## Create the Nginx Configuration

Create a site configuration for your domain:

```bash
sudo nano /etc/nginx/sites-available/tickets.example.com
```

Add the following configuration:

```nginx
server {
    listen 80;
    listen [::]:80;

    server_name tickets.example.com;

    client_max_body_size 12M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_cache_bypass $http_upgrade;
    }
}
```

Drako Tickets already supplies its application security headers, so they do not need to be duplicated in Nginx.

Press `CTRL + X`, then `Y`, then `Enter` to save the file.
{% endstep %}

{% step %}

## Enable the Site

Enable the configuration and test it before reloading Nginx:

```bash
sudo ln -s /etc/nginx/sites-available/tickets.example.com /etc/nginx/sites-enabled/tickets.example.com
sudo nginx -t
sudo systemctl reload nginx
```

Do not reload Nginx if `sudo nginx -t` reports an error. Correct the file first and test it again.

Open `http://tickets.example.com` in a browser to confirm that Nginx can reach Drako Tickets.
{% endstep %}

{% step %}

## Install the SSL Certificate

Install Certbot using the officially recommended snap package:

```bash
sudo apt install snapd -y
sudo snap install --classic certbot
sudo ln -sf /snap/bin/certbot /usr/local/bin/certbot
```

Request the certificate and let Certbot update the Nginx configuration:

```bash
sudo certbot --nginx -d tickets.example.com
```

When prompted, enable the redirect from HTTP to HTTPS. Then test automatic certificate renewal:

```bash
sudo certbot renew --dry-run
```

{% endstep %}

{% step %}

## Verify the Installation

Open the following address in your browser:

```
https://tickets.example.com
```

You can also check each service from the command line:

```bash
pm2 status
sudo systemctl status nginx --no-pager
curl -I https://tickets.example.com
```

Your Drako Tickets installation should now be available securely on your domain.
{% endstep %}

{% step %}

## Updating Drako Tickets

After replacing the application files with a newer release, reinstall the exact dependencies and restart the PM2 service:

```bash
cd /var/www/drako-tickets
npm ci
pm2 restart drako-tickets --update-env
pm2 save
```

The restart runs the production build before bringing the application back online.
{% endstep %}
{% endstepper %}

## Troubleshooting

### Nginx shows `502 Bad Gateway`

Confirm that Drako Tickets is running and listening on port `3000`:

```bash
pm2 status
pm2 logs drako-tickets --lines 100
curl -I http://127.0.0.1:3000
```

### The domain does not open

Check that the DNS record points to the correct public server IP and that ports `80` and `443` are open.

### Nginx configuration test fails

Inspect the error and verify the site configuration:

```bash
sudo nginx -t
sudo nano /etc/nginx/sites-available/tickets.example.com
```

### Emails, OAuth callbacks, or Discord links use the wrong URL

Confirm these values in `/var/www/drako-tickets/.env`, then restart the service:

```dotenv
APP_PROTOCOL=https
APP_HOST=tickets.example.com
APP_PORT=443
```

```bash
pm2 restart drako-tickets --update-env
```

### View application or Nginx logs

```bash
pm2 logs drako-tickets
sudo tail -f /var/log/nginx/error.log
```

## Useful References

* [NVM installation guide](https://github.com/nvm-sh/nvm#installing-and-updating)
* [Certbot instructions for Nginx](https://certbot.eff.org/instructions?ws=nginx\&os=snap)


# Email Integration

Drako Tickets supports two email directions:

* **Outbound (SMTP)** — send reply notifications and ticket confirmations to users
* **Inbound (IMAP)** — poll a mailbox so that email replies automatically append to the correct ticket

Both are configured in **Dashboard → Settings → Mail Server** and **Inbound Email**.

***

## Outbound mail (SMTP)

{% stepper %}
{% step %}

#### Gather your SMTP credentials

You can use any SMTP provider. Common choices:

| Provider                | Host                 | Port           | Notes                                                                                   |
| ----------------------- | -------------------- | -------------- | --------------------------------------------------------------------------------------- |
| Gmail                   | `smtp.gmail.com`     | 587            | Requires an [App Password](https://myaccount.google.com/apppasswords) if 2FA is enabled |
| Outlook / Microsoft 365 | `smtp.office365.com` | 587            | Use your full email as the username                                                     |
| Mailgun                 | `smtp.mailgun.org`   | 587            | Use SMTP credentials from your Mailgun domain                                           |
| SendGrid                | `smtp.sendgrid.net`  | 587            | Username is always `apikey`, password is your API key                                   |
| Custom / self-hosted    | your server          | 25 / 465 / 587 | Ensure port is open and not blocked by your host                                        |
| {% endstep %}           |                      |                |                                                                                         |

{% step %}

#### Configure in the dashboard

Go to **Settings → Mail Server** and fill in:

| Field         | Description                                                              |
| ------------- | ------------------------------------------------------------------------ |
| SMTP Host     | Your provider's hostname, e.g. `smtp.gmail.com`                          |
| SMTP Port     | Usually `587` (STARTTLS) or `465` (SSL)                                  |
| Secure (SSL)  | Enable if using port 465                                                 |
| Username      | Your email address or SMTP username                                      |
| Password      | Your SMTP password or app password                                       |
| From Address  | The address emails are sent from, e.g. `support@yourcompany.com`         |
| From Name     | Display name shown in the recipient's inbox, e.g. `Your Company Support` |
| {% endstep %} |                                                                          |

{% step %}

#### Test the connection

Click **Test SMTP Connection** to send a test message to yourself. If it fails, check:

* The host and port are correct
* Your firewall or hosting provider allows outbound SMTP on that port
* You are using an App Password if your account has 2FA enabled
  {% endstep %}

{% step %}

#### Enable SMTP

Toggle **Enable SMTP** on and click **Save**. Outbound notifications will now be sent when a technician replies to a ticket.
{% endstep %}
{% endstepper %}

***

## Inbound email (IMAP)

When IMAP is enabled, Drako Tickets polls your mailbox on a schedule. Emails are matched to existing tickets by the `[TK-XXXX]` reference in the subject line. If no match is found a new ticket is created.

{% stepper %}
{% step %}

#### Set up a dedicated support mailbox

Create a dedicated email address for support, e.g. `support@yourcompany.com`. This keeps your personal inbox separate and makes filtering easier.
{% endstep %}

{% step %}

#### Enable IMAP access on your mailbox

| Provider                | How to enable                                                                                                                                           |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Gmail                   | Settings → See all settings → Forwarding and POP/IMAP → Enable IMAP. If 2FA is on, create an [App Password](https://myaccount.google.com/apppasswords). |
| Outlook / Microsoft 365 | Settings → Mail → Sync email → Enable IMAP.                                                                                                             |
| Other providers         | Refer to your provider's documentation.                                                                                                                 |
| {% endstep %}           |                                                                                                                                                         |

{% step %}

#### Configure in the dashboard

Go to **Settings → Inbound Email** and fill in:

| Field         | Description                                          |
| ------------- | ---------------------------------------------------- |
| IMAP Host     | Your provider's IMAP hostname, e.g. `imap.gmail.com` |
| IMAP Port     | Usually `993` (SSL) or `143` (STARTTLS)              |
| Secure (SSL)  | Enable for port 993                                  |
| Username      | Your mailbox email address                           |
| Password      | Your IMAP password or app password                   |
| Mailbox       | The folder to poll, usually `INBOX`                  |
| Poll Interval | How often to check for new mail (minimum 1 minute)   |
| {% endstep %} |                                                      |

{% step %}

#### Test the connection

Click **Test IMAP Connection** to verify the credentials. A successful test means Drako Tickets can connect and read the mailbox.
{% endstep %}

{% step %}

#### Enable IMAP polling

Toggle **Enable IMAP** on and click **Save**. The poller starts immediately and will run on the configured interval.
{% endstep %}
{% endstepper %}

### How ticket matching works

When an email arrives, DrakoTickets looks for a `[TK-XXXX]` tag in the subject line (e.g. `Re: Your ticket [TK-0042]`). If found, the email body is appended as a new comment on that ticket. If no tag is found, a new ticket is created and a confirmation email is sent to the sender.

{% hint style="info" %}
**Tip:** The confirmation email template is customisable in **Settings → Email Templates**.
{% endhint %}

***

## Email templates

Go to **Settings → Email Templates** to customise:

* **Confirmation email** — sent to the requester when a new ticket is created via email
* **Reply email** — sent to the requester when a technician replies
* **Global signature** — appended to all outbound emails

Templates support the following variables:

| Variable             | Description                    |
| -------------------- | ------------------------------ |
| `{{ticket_number}}`  | The ticket number, e.g. `0042` |
| `{{ticket_title}}`   | The ticket subject / title     |
| `{{requester_name}}` | The requester's display name   |
| `{{from_name}}`      | The configured From Name       |


# Login Providers

Drako Tickets supports password-based login and three OAuth providers: **Google**, **Discord**, and **Microsoft**. OAuth providers are optional — you can enable any combination of them.

OAuth is configured in two places:

1. The **provider's developer console** — to create OAuth credentials
2. **Supabase** — to enable the provider and paste the credentials
3. **DrakoTickets dashboard** — to show the button on the login page

## Supabase callback URL

All three providers require the same redirect URL. You will need this in each setup below:

```
https://<your-supabase-project>.supabase.co/auth/v1/callback
```

You can find and copy this URL directly from **Settings → Login Providers** inside the DrakoTickets dashboard.

## Google

{% stepper %}
{% step %}

### Create OAuth credentials in Google Cloud

1. Go to [console.cloud.google.com](https://console.cloud.google.com/) and open or create a project.
2. Navigate to **APIs & Services → Credentials** and click **Create Credentials → OAuth 2.0 Client ID**.
3. Set the application type to **Web application**.
4. Under **Authorised redirect URIs**, add your Supabase callback URL and click **Save**.
5. Copy the **Client ID** and **Client Secret**.
   {% endstep %}

{% step %}

### Enable in Supabase

1. Open your Supabase dashboard → **Authentication → Providers → Google**.
2. Paste the **Client ID** and **Client Secret**.
3. Toggle the provider **Enabled** and click **Save**.
   {% endstep %}

{% step %}

### Enable in DrakoTickets

1. Go to **Settings → Login Providers**.
2. Toggle **Google** on and click **Save**.
   {% endstep %}
   {% endstepper %}

The Google sign-in button will now appear on the login and registration pages.

## Discord

{% stepper %}
{% step %}

### Create OAuth credentials in Discord

1. Go to [discord.com/developers/applications](https://discord.com/developers/applications) and click **New Application** (or open an existing one).
2. Open the **OAuth2** tab and click **Add Redirect**.
3. Paste your Supabase callback URL and click **Save Changes**.
4. Copy the **Client ID** and **Client Secret** from the top of the OAuth2 tab.

{% hint style="info" %}
If you are also using the Discord bot integration, you can use the same application — the bot token and OAuth credentials are separate.
{% endhint %}
{% endstep %}

{% step %}

### Enable in Supabase

1. Open your Supabase dashboard → **Authentication → Providers → Discord**.
2. Paste the **Client ID** and **Client Secret**.
3. Toggle the provider **Enabled** and click **Save**.
   {% endstep %}

{% step %}

### Enable in DrakoTickets

1. Go to **Settings → Login Providers**.
2. Toggle **Discord** on and click **Save**.
   {% endstep %}
   {% endstepper %}

## Microsoft

{% stepper %}
{% step %}

### Register an application in Azure

1. Go to the [Azure Portal](https://portal.azure.com/) and navigate to **Azure Active Directory → App registrations → New registration**.
2. Give the app a name and choose the supported account types:
   * **Single tenant** — only your organisation's accounts
   * **Multitenant** — any Microsoft / Azure AD account
   * **Multitenant + personal** — also allows personal Microsoft accounts (Outlook, Hotmail)
3. Under **Redirect URI**, select **Web** and paste your Supabase callback URL.
4. Click **Register**.
5. Copy the **Application (client) ID** from the Overview page.
6. Go to **Certificates & secrets → New client secret**, set an expiry, and copy the **Value** (not the Secret ID).
   {% endstep %}

{% step %}

### Enable in Supabase

1. Open your Supabase dashboard → **Authentication → Providers → Azure**.
2. Paste the **Client ID** (Application ID) and **Client Secret**.
3. If you registered for a single tenant, also set the **Azure Tenant ID** (found on the Azure Overview page).
4. Toggle the provider **Enabled** and click **Save**.
   {% endstep %}

{% step %}

### Enable in Drako Tickets

1. Go to **Settings → Login Providers**.
2. Toggle **Microsoft** on and click **Save**.
   {% endstep %}
   {% endstepper %}

## Troubleshooting

<details>

<summary>OAuth button appears but clicking it shows an error</summary>

* Double-check the redirect URI in the provider's console matches the Supabase callback URL exactly (no trailing slash).
* Ensure the provider is enabled in both Supabase and the DrakoTickets dashboard.

</details>

<details>

<summary>"Email already in use" after signing in with OAuth</summary>

* Supabase links OAuth accounts to existing email addresses automatically if **Link accounts** is enabled in your Supabase Auth settings.
* If it is disabled, the user must first log in with their password and then link the OAuth account from their profile.

</details>

<details>

<summary>Google shows "This app isn't verified"</summary>

* This warning appears during development. Click **Advanced → Go to \[app name] (unsafe)** to proceed.
* To remove the warning in production, submit your app for Google OAuth verification in the Google Cloud Console.

</details>


# Discord Integration

The Discord integration lets users raise support tickets directly from your Discord server. When a ticket is created, the bot opens a dedicated thread or channel, posts a rich embed, and keeps it in sync with the web dashboard.

## Overview

* Users click a **Raise a Ticket** button posted by the bot in your support channel
* A modal collects the subject and description
* The bot creates a ticket in the dashboard and opens a private thread or channel
* Staff can claim, close, and update tickets from either Discord or the web dashboard
* Replies made in the web dashboard are posted back to the Discord thread

## 1. Create a Discord application and bot

{% stepper %}
{% step %}

### Go to the Discord Developer Portal

Go to [discord.com/developers/applications](https://discord.com/developers/applications) and click **New Application**.
{% endstep %}

{% step %}

### Name the application

Give it a name (e.g. `DrakoTickets`) and click **Create**.
{% endstep %}

{% step %}

### Add a bot

Open the **Bot** tab and click **Add Bot**.
{% endstep %}

{% step %}

### Copy the bot token

Under **Token**, click **Reset Token** and copy the token — you will need this shortly.
{% endstep %}

{% step %}

### Enable privileged gateway intents

Scroll down to **Privileged Gateway Intents** and enable:

* **Server Members Intent**
* **Message Content Intent**
  {% endstep %}

{% step %}

### Save changes

Click **Save Changes**.
{% endstep %}
{% endstepper %}

## 2. Set the bot token in your environment

Add the token to your `.env.local` (or Vercel environment variables):

```env
DISCORD_BOT_TOKEN=your-discord-bot-token
```

Restart the application after adding this variable.

## 3. Invite the bot to your server

{% stepper %}
{% step %}

### Open the URL Generator

In the Developer Portal, open the **OAuth2 → URL Generator** tab.
{% endstep %}

{% step %}

### Select the required scopes

Under **Scopes**, select `bot` and `applications.commands`.
{% endstep %}

{% step %}

### Select bot permissions

Under **Bot Permissions**, select:

* Manage Channels
* Send Messages
* Embed Links
* Read Message History
* Manage Threads
* Create Public Threads
* Create Private Threads
  {% endstep %}

{% step %}

### Invite the bot

Copy the generated URL and open it in your browser to invite the bot to your server.
{% endstep %}
{% endstepper %}

## 4. Enable Developer Mode in Discord

You need Developer Mode on to copy IDs.

{% stepper %}
{% step %}

### Enable Developer Mode

Open Discord → User Settings → Advanced → Enable **Developer Mode**.
{% endstep %}

{% step %}

### Copy IDs

You can now right-click any server, channel, or role and select **Copy ID**.
{% endstep %}
{% endstepper %}

## 5. Configure the bot in the dashboard

Go to **Settings → Discord** and fill in the following fields.

### Bot Configuration

| Field                        | How to get it                                                                  |
| ---------------------------- | ------------------------------------------------------------------------------ |
| Guild (Server) ID            | Right-click your server name → Copy Server ID                                  |
| Support Channel ID           | Right-click the channel where the ticket button will appear → Copy Channel ID  |
| Support Role ID *(optional)* | Right-click the role that should have access to ticket channels → Copy Role ID |

### Ticket Channel Mode

Choose how new tickets are created in Discord:

* **Thread** — creates a private thread inside the support channel (recommended, keeps the channel tidy)
* **Channel** — creates a new channel inside a category

If you choose **Channel**, also fill in the **Ticket Category ID** (right-click the category → Copy ID).

## 6. Post the support panel

Once the bot configuration is saved, click **Post Panel** in the dashboard. The bot will post an embed with a **Raise a Ticket** button to your support channel. Users click this button to open the ticket creation modal.

{% hint style="info" %}
You only need to post the panel once. If you update the embed appearance, click **Update Embed** to refresh the existing message without re-posting.
{% endhint %}

## Customising the embeds

### Support panel embed

This is the message that sits in your support channel with the button.

| Field              | Description                               |
| ------------------ | ----------------------------------------- |
| Title              | Heading of the embed, e.g. `Support`      |
| Description        | Body text shown below the title           |
| Colour             | Hex colour of the embed's left border     |
| Image URL          | Optional large image shown in the embed   |
| Footer Text / Icon | Optional footer line at the bottom        |
| Author Name / Icon | Optional author line at the top           |
| Button Label       | Text on the button, e.g. `Raise a Ticket` |
| Button Emoji       | Emoji shown on the button, e.g. `🎫`      |

### Ticket embed

This is the embed posted inside each new ticket thread or channel.

| Field           | Description                                                  |
| --------------- | ------------------------------------------------------------ |
| Title           | Heading, e.g. `🎫 New Support Ticket`                        |
| Colour          | Hex colour of the embed                                      |
| Description     | Template for the embed body — supports variables (see below) |
| Footer / Author | Same as the panel embed                                      |
| Show Thumbnail  | Shows the requester's Discord avatar in the top-right corner |

#### Available template variables

| Variable          | Description                                            |
| ----------------- | ------------------------------------------------------ |
| `{user}`          | The Discord username of the requester                  |
| `{ticket_number}` | The ticket number, e.g. `TK-0042`                      |
| `{subject}`       | The ticket subject entered in the modal                |
| `{description}`   | The ticket description entered in the modal            |
| `{response_time}` | Expected response time (from SLA policy if configured) |
| `{claimed_by}`    | The technician who claimed the ticket, or `Unclaimed`  |

## Troubleshooting

**The bot is online but the panel button does nothing**

* Make sure `APP_PROTOCOL`, `APP_HOST`, and `APP_PORT` are set correctly so the bot can reach the web app's interaction endpoint at `/api/discord/interaction`.
* Check that the bot has the **applications.commands** scope.

**Ticket channels are not being created**

* Verify the bot has **Manage Channels** permission in the category or server.
* If using Channel mode, confirm the **Ticket Category ID** is correct.

**Replies from the dashboard are not appearing in Discord**

* Ensure `DISCORD_BOT_TOKEN` is set and the bot is still in the server.
* Check the server logs for `[discord/update-embed]` errors.


# Terms and Conditions

By using our products you agree to the below terms

### Terms of Service and Use Agreement

#### 1. Definitions

**1.1 Products**\
"Products" refers to any applications, software licenses, digital content, or items offered through Drako Development's online store.

**1.2 User**\
"User" denotes any individual or entity utilizing our services, including viewing, purchasing, registering, or participating in any manner on our website.

**1.3 Use**\
"Use" encompasses all forms of interaction with our products, including but not limited to downloading, installing, activating, hosting, and operating.

**1.4 Agreement**\
"Agreement" constitutes the binding legal contract represented by this Terms of Service and Use document.

**1.5 Guild**\
"Guild" refers to a Discord server or similar community instance in which the Product is deployed or used.

***

#### 2. Use of Information and Products

**2.1 Consent to Data Collection**\
Users consent to the collection, storage, and protection of personal data by Drako Development in accordance with our Privacy Policy.

**2.2 Sharing Information**\
User information may be shared with third parties only under conditions that meet our stringent privacy criteria.

**2.3 Prohibition of Unlawful Activities**\
Engaging in unlawful activities with our products is strictly prohibited and grounds for immediate termination of access and further legal action.

**2.4 Right to Modify or Discontinue**\
Drako Development reserves the right to modify or discontinue any product or service at any time, without notice.

***

#### 3. Third-Party Interactions and Security

**3.1 Hosting Providers**\
Users may select any web hosting provider for products that require such services, provided they adhere to this Agreement.

**3.2 Third-Party Compliance**\
Users are responsible for ensuring that third parties interacting with their hosted product comply with these terms.

**3.3 User Responsibility**\
The User is accountable for the security of their use of the Product and compliance with this Agreement.

***

#### 4. Permitted and Prohibited Uses

**4.1 Multi-Guild Usage (Owner-Only Permission)**\
Users may operate the Product across **multiple Guilds** **only if**:

* The User is the **verified owner** of each Guild, **or**
* The User has full administrative ownership rights equivalent to server ownership.

Under no circumstances may the Product be operated for Guilds owned, controlled, or primarily managed by third parties without explicit written consent from Drako Development.

**4.2 Hosting for Third Parties**\
Hosting, sublicensing, or otherwise making the Product available to third parties—whether for profit or otherwise—is expressly forbidden.

This includes, but is not limited to:

* Public bots available for invitation by unrelated Guilds
* Managed or shared bot instances
* Offering the Product as a service, platform, or hosted solution

**4.3 Modifications**\
Users may not modify the Product’s core functionality, including its licensing system. Customizations must not violate any terms of this Agreement.

***

#### 5. Product Distribution

**5.1 Redistribution**\
Redistribution, resale, sublicensing, or any form of transfer of the Product without explicit written consent from Drako Development is prohibited.

***

#### 6. Maintenance and Support

**6.1 Permanent Agreement**\
This Agreement is a permanent part of the Product and must be adhered to at all times.

**6.2 Liability for Interruptions**\
Drako Development is not liable for interruptions to Product functionality due to maintenance, downtime, or external service failures.

**6.3 Discontinued Products**\
Discontinued Products will not be re-offered for download; users are advised to maintain their backups.

***

#### 7. Intellectual Property

**7.1 Copyright Notices**\
Users must not remove or alter any copyright notices, trademarks, or credits associated with the Product.

**7.2 Third-Party Contributions**\
Products may incorporate contributions from third-party authors, as acknowledged in product documentation.

***

#### 8. Financial Terms

**8.1 Secure Transactions**\
All transactions are processed via specified secure methods. Drako Development does not directly handle payments.

**8.2 Refund Policy**\
Due to the digital nature of our Products, refunds are generally not offered except under exceptional circumstances.

***

#### 9. Limitation of Liability

**9.1 No Warranties**\
Drako Development's Products are provided “as is,” without warranties of any kind, either express or implied.

**9.2 User Responsibility**\
Users bear all responsibility for damages or liabilities arising from misuse of the Product.

***

#### 10. Violations and Enforcement

**10.1 Termination and Legal Action**\
Violations of this Agreement may result in immediate termination of access, license revocation, and legal action.

**10.2 Enforcement Rights**\
Drako Development reserves the right to enforce this Agreement through all legal and equitable remedies.

***

#### 11. Amendments

**11.1 Right to Amend**\
Drako Development reserves the right to amend this Agreement at any time. Continued use of the Product after amendments constitutes acceptance of the revised terms.

***

#### 12. Commercial Restrictions

**12.1 Commercial Hosting and Redistribution**\
Users are expressly forbidden from hosting, redistributing, sublicensing, or commercializing Drako Development's Products—including bots and software—for third parties without prior written consent.

**12.2 Prohibited Activities**\
This includes, but is not limited to:

* Public or shared bot deployments
* Bot-as-a-service offerings
* Paid or unpaid managed solutions
* Use in Guilds not owned by the User

**12.3 Consequences of Violation**\
Violation of these terms will result in immediate termination of the User’s license and may lead to legal action.


# Subscription Terms

## Drako Bot Premium Subscription - Terms of Service

**Effective Date:** November 25, 2024\
**Last Updated:** November 25, 2024

By subscribing to Drako Bot Premium, you agree to these terms in addition to our [General Terms of Service](https://docs.drakodevelopment.net/legal/terms-and-conditions).

***

### 1. Premium Subscription Service

#### 1.1 Service Description

Drako Bot Premium ("Premium Service") is a monthly subscription service that provides enhanced features and increased limits for Discord server management.

#### 1.2 Subscription Scope

* Premium subscription applies to **one Discord server** (guild) only
* Features are accessible only to the subscribed server
* Each server requires its own separate subscription

#### 1.3 Premium Features

Premium subscribers receive access to:

* Unlimited ticket types and panels
* Unlimited reaction role panels with unlimited roles per panel
* Unlimited auto-react and auto-respond rules
* Custom commands functionality
* Unlimited blacklist words
* Unlimited suggestion channels
* Unlimited active giveaways
* Invite tracker and audit logs
* Priority support
* Additional features as announced

***

### 2. Pricing and Payment

#### 2.1 Subscription Fee

* **Monthly Subscription:** $3.99 USD per month per server
* Pricing subject to change with 30 days notice

#### 2.2 Payment Processing

* All payments processed securely through PayPal
* Drako Development does not directly handle or store payment information
* You will be redirected to PayPal to complete your subscription

#### 2.3 Billing Cycle

* Subscriptions renew automatically on a monthly basis
* Billing occurs on the same day each month as your initial subscription
* You will be charged unless you cancel before the renewal date

#### 2.4 Payment Methods

* PayPal account or PayPal-accepted payment methods
* All transactions in USD

***

### 3. Subscription Management

#### 3.1 Activation

* Premium features activate immediately upon successful payment verification
* Activation typically occurs within 1-5 minutes
* If activation doesn't occur within 15 minutes, contact support

#### 3.2 Cancellation Policy

* You may cancel your subscription at any time
* Cancellation takes effect at the end of your current billing period
* You retain Premium access until the end of the paid period
* No partial refunds for early cancellation
* Cancel through the dashboard at: `https://dashboard.drako.gg/guild/YOUR_SERVER/tier`

#### 3.3 Suspension or Termination

Drako Development may suspend or terminate your Premium subscription if:

* Payment fails or is disputed
* You violate these terms or our General Terms of Service
* Your Discord server is banned or deleted
* Fraudulent activity is detected

***

### 4. Refund Policy

#### 4.1 No Refunds

Due to the digital nature of our service and immediate feature activation:

* **Subscriptions are non-refundable**
* **Cancellations do not result in prorated refunds**
* No refunds for partial billing periods

#### 4.2 Exceptions

Refunds may be considered only in exceptional circumstances:

* Service unavailability for extended periods (7+ consecutive days)
* Billing errors or duplicate charges
* Technical issues preventing feature access

#### 4.3 Refund Requests

* Must be submitted within 7 days of the charge
* Raise a ticket in: <https://discord.gg/drakobot>
* Include transaction ID and detailed explanation

***

### 5. Service Availability and Support

#### 5.1 Uptime

* We strive for 99.5% uptime but do not guarantee uninterrupted service
* Scheduled maintenance will be announced when possible
* Emergency maintenance may occur without notice

#### 5.2 Support

* Premium subscribers receive priority support
* Support available via Discord
* Response time: typically within 24 hours

#### 5.3 Feature Changes

* We reserve the right to modify, add, or remove Premium features
* Significant changes will be announced 30 days in advance when possible
* Subscription pricing applies to the current feature set

***

### 6. Data and Privacy

#### 6.1 Data Collection

By subscribing, you consent to collection of:

* Discord server ID (guild ID)
* Subscription status and billing information
* PayPal transaction IDs
* Feature usage data

#### 6.2 Data Usage

* Data used solely for service provision and improvement
* We do not sell your data to third parties
* Full details in our [Privacy Policy](https://docs.drakodevelopment.net/legal/privacy-policy)

#### 6.3 Data Retention

* Subscription data retained for accounting and tax purposes
* Discord bot data retained per our standard data retention policy
* You may request data deletion after subscription ends

***

### 7. Acceptable Use

#### 7.1 Prohibited Activities

Premium subscribers must NOT:

* Share subscription access with other servers
* Abuse or exploit Premium features
* Use the service for illegal purposes
* Attempt to circumvent payment systems
* Resell or redistribute Premium features
* Use automated systems to abuse features

#### 7.2 Discord Terms Compliance

* You must comply with [Discord's Terms of Service](https://discord.com/terms)
* You must comply with [Discord's Community Guidelines](https://discord.com/guidelines)
* Violations may result in immediate termination

#### 7.3 Commercial Use

* Premium subscription intended for legitimate Discord server management
* Commercial hosting or resale of Drako Bot services is prohibited
* See Section 12 of our General Terms of Service

***

### 8. Intellectual Property

#### 8.1 License Grant

* Premium subscription grants you a limited, non-exclusive, non-transferable license
* License to use Premium features for your subscribed Discord server only
* License terminates upon subscription cancellation or termination

#### 8.2 Restrictions

* You may not reverse engineer, decompile, or modify the bot
* You may not remove branding, credits, or copyright notices
* You may not create derivative works

#### 8.3 Drako Development Rights

* All rights, title, and interest in Drako Bot remain with Drako Development
* "Drako Bot" and related trademarks are property of Drako Development

***

### 9. Limitation of Liability

#### 9.1 Service "As Is"

* Premium Service provided "AS IS" and "AS AVAILABLE"
* No warranties, express or implied
* We do not guarantee specific results or uptime

#### 9.2 Limitation of Damages

TO THE MAXIMUM EXTENT PERMITTED BY LAW:

* Drako Development's liability limited to subscription fees paid in the last 3 months
* Not liable for indirect, incidental, special, or consequential damages
* Not liable for loss of data, profits, or business opportunities

#### 9.3 User Responsibility

* You are responsible for:
  * Your Discord server configuration
  * Actions of your server members
  * Backup of your server data and settings
  * Compliance with Discord and applicable laws

***

### 10. Chargebacks and Disputes

#### 10.1 Chargeback Policy

* Chargebacks will result in immediate subscription termination
* Account may be permanently banned
* We reserve the right to pursue payment collection

#### 10.2 Dispute Resolution

* Contact us first: <https://discord.gg/drakobot>
* We will work to resolve disputes within 14 days
* Chargebacks should be a last resort

***

### 11. Modifications to Terms

#### 11.1 Right to Modify

* We may modify these terms at any time
* Material changes announced 30 days in advance
* Continued use after changes constitutes acceptance

#### 11.2 Notification

* Changes posted to: <https://docs.drakodevelopment.net/legal/terms-and-conditions>
* Important changes may be emailed or announced on Discord

#### 11.3 Rejection of Changes

* If you reject changes, you may cancel your subscription
* No refunds for rejection-based cancellations

***

### 12. Governing Law and Jurisdiction

#### 12.1 Applicable Law

* These terms governed by the laws of United Kingdom
* Excluding conflict of law provisions

#### 12.2 Dispute Resolution

* Any disputes resolved through binding arbitration
* Class action waiver applies

***

### 13. Contact Information

For questions about Premium subscriptions:\
**Discord:** <https://discord.gg/drakobot\\>
**Documentation:** <https://docs.drakodevelopment.net>

***

### 14. Severability

If any provision is found unenforceable, remaining provisions remain in effect.

***

### 15. Entire Agreement

These terms, together with our General Terms of Service and Privacy Policy, constitute the entire agreement regarding Premium subscriptions

***

**By clicking "Subscribe with PayPal" you acknowledge that you have read, understood, and agree to be bound by these Terms of Service.**

For full terms, visit: <https://docs.drakodevelopment.net/legal/terms-and-conditions>


# Privacy Policy

By using our products you agree to the below terms

### Introduction

Drako Development is committed to protecting your privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website and use our products and services.

Please read this policy carefully. If you do not agree with the terms of this Privacy Policy, please do not access the site or content.

### Information We Collect

#### Personal Data

We collect personally identifiable information, such as your name, email address, and electronic information.

#### Derivative Data

Information our servers and systems automatically collect when you access the site and products, such as:

* IP address
* Browser type
* Operating system
* Access times
* Pages viewed directly before and after accessing the site and services

### Use of Your Information

* **Processing Transactions:** To process purchases, orders, payments, and other financial transactions.
* **Communication:** To send you information in regards to our services and products.
* **Improve Services:** To improve our website and services through analysis and interpretation of user behavior and preferences.
* **Legal Obligations:** To comply with legal obligations and protect our rights.

### Sharing Your Information

#### Third-Party Service Providers

We may share your information with third parties that perform services for us or on our behalf, such as payment processing, data analysis, and hosting services.

#### Business Transfers

We may share or transfer your information in connection with, or during negotiations of, any merger, sale of company assets, financing, or acquisition of all or a portion of our business to another company.

#### Legal Requirements

If we are legally required to disclose your information, we will comply with such requirements.

### Your Data Rights

* **Data Access:** You have the right to request access to the personal data we hold about you.
* **Data Portability:** You have the right to request a copy of your personal data in a structured, commonly used, and machine-readable format.
* **Data Deletion:** You have the right to request the deletion of your personal data. To request data deletion, please contact us using the information provided in the "Contact Us" section below. We will process your request within 30 days, subject to any legal obligations that may require us to retain certain information.
* **Data Correction:** You have the right to request correction of any inaccurate or incomplete personal data we hold about you.
* **Withdrawal of Consent:** Where we rely on your consent to process your personal data, you have the right to withdraw that consent at any time.

To exercise any of these rights, please contact us using the information provided in the "Contact Us" section below. We may need to verify your identity before processing your request.

### Security of Your Information

We use administrative and technical measures to help protect your personal information. While we have taken reasonable steps to secure the personal information you provide to us, please be aware that despite our efforts, no security measures are perfect or impenetrable.

### Changes to This Privacy Policy

We may update this Privacy Policy from time to time in order to reflect changes to our practices or for other operational, legal, or regulatory reasons. Please revisit this page periodically to ensure you are aware of any changes.

### Contact Us

If you have any questions or concerns about this Privacy Policy, or if you wish to exercise any of your data rights, please contact Drako Development at:

**User:** @youseemerunning\
**Discord:** <https://discord.gg/drakobot>

For data deletion requests, please include:

* Your Discord user ID
* The specific data you wish to have deleted
* Verification of your identity (we may ask for additional confirmation)

{% hint style="danger" %}

{% endhint %}


# Produt Overview

Drako Dungeons is an incremental dungeon-grinding plugin for Paper and Purpur. Each player receives a private combat session made from client-side mobs, holograms, and optional packet-block themes. Multiple players can use the same physical arena while seeing and fighting their own dungeon content.

## Supported environment

| Component          | Requirement                                         |
| ------------------ | --------------------------------------------------- |
| Java               | Java 21                                             |
| Server software    | Paper or Purpur                                     |
| Minecraft versions | 1.21.11 and 26.2                                    |
| PlaceholderAPI     | Optional; enables Drako Dungeons placeholders       |
| FastAsyncWorldEdit | Optional but recommended for visual arena selection |

{% hint style="warning" %}
Use a full server restart when installing or replacing the JAR. Plugin reload are not supported.
{% endhint %}

## Recommended admin workflow

{% stepper %}
{% step %}

## Install the JAR and start the server once

{% endstep %}

{% step %}

## Create the first zone

Use `/dga setup`.
{% endstep %}

{% step %}

## Configure the zone

Configure its arena, entry, mob points, stages, and optional systems.
{% endstep %}

{% step %}

## Edit YAML

Edit YAML for detailed balancing and presentation.
{% endstep %}

{% step %}

## Test as a non-admin player

Test with a non-admin player before opening the content publicly.
{% endstep %}
{% endstepper %}

Invalid configuration is rejected. Validation and reload errors include the affected file or key, and a failed candidate does not replace the previous valid configuration.


# Installation

## Before installing

Confirm that the server uses Java 21 and is running a supported Paper or Purpur build for Minecraft 1.21.11 or 26.2. Spigot, CraftBukkit, Folia, hybrid Forge/Bukkit servers, and plugin reload managers are outside the supported environment.

Optional integrations can be installed before or after DrakoDungeons:

* **PlaceholderAPI** exposes `%drakodungeons_*%` placeholders.
* **FastAsyncWorldEdit** lets setup mode read a `//pos1` and `//pos2` cuboid.

Neither dependency is required for basic combat, progression, or menus.

## Install the plugin

{% stepper %}
{% step %}

## Stop the server

Stop the server completely.
{% endstep %}

{% step %}

## Install the JAR

Place `DrakoDungeons-1.0.0.jar` in the server's `plugins` directory.
{% endstep %}

{% step %}

## Start the server

Start the server normally.
{% endstep %}

{% step %}

## Allow baseline files to generate

Wait for Drako Dungeons to generate its complete directory. Because the generated license key is blank, Drako Dungeons then reports `NO_LICENSE_KEY` and disables itself. This is expected on a clean installation and does not stop the Minecraft server.
{% endstep %}

{% step %}

## Stop the server again

Stop the server completely again.
{% endstep %}

{% step %}

## Add the license key

Open `plugins/DrakoDungeons/config.yml` and set the `LicenseKey` value at the top of the file:

```yaml
LicenseKey: 'YOUR-LICENSE-KEY'
```

{% endstep %}

{% step %}

## Save the configuration

Save the file without renaming it or changing the `LicenseKey` capitalization.
{% endstep %}

{% step %}

## Start the server

Start the server normally.
{% endstep %}

{% step %}

## Check the console

Check the console for `Drako Dungeons license validated successfully`, the supported-server confirmation, and the successful enable summary.
{% endstep %}

{% step %}

## Confirm generated directories

Confirm that `plugins/DrakoDungeons/` contains the settings, content, menus, language, docs, and zones directories.
{% endstep %}
{% endstepper %}

## Optional dependency behavior

When FastAsyncWorldEdit is absent, admins can still configure coordinates and use the non-FAWE setup tools. The FAWE selection action is unavailable until the dependency is installed and the server is restarted.

When PlaceholderAPI is absent, DrakoDungeons runs normally but does not register its placeholder expansion.

## Updating the JAR

{% stepper %}
{% step %}

## Save data

Run `/dga save` and wait for the success message.
{% endstep %}

{% step %}

## Stop the server

Stop the server.
{% endstep %}

{% step %}

## Back up plugin data

Back up `plugins/DrakoDungeons/`, especially `dungeons.db` and all YAML files.
{% endstep %}

{% step %}

## Replace the JAR

Replace the JAR.
{% endstep %}

{% step %}

## Start and validate

Start the server, confirm successful license validation in the console, and run `/dga validate`.
{% endstep %}
{% endstepper %}


# Commands & Security

## Player commands

| Command                                     | Purpose                                                   |
| ------------------------------------------- | --------------------------------------------------------- |
| `/dungeons help`                            | Show the paginated player command list                    |
| `/zones`                                    | Open the zone and stage selector                          |
| `/dungeons join [zone] [stage]`             | Enter the recommended or specified stage                  |
| `/dungeons leave`                           | Leave the current dungeon session                         |
| `/dungeons status`                          | Show progression, balances, and active target information |
| `/dungeons sword`                           | Receive or refresh the bound Dungeon Sword                |
| `/dungeons enchants [soul\|essence\|shard]` | Open enchant menus                                        |
| `/dungeons attributes`                      | View rebirth-gated Sword Attributes                       |
| `/dungeons abilities`                       | Select an unlocked sword ability                          |
| `/dungeons perks`                           | Open perk rolling and the perk index                      |
| `/crystal`                                  | Open crystal sockets, storage, tinkering, and upgrades    |
| `/armor`                                    | Open armor sets, skins, and upgrades                      |
| `/rebirth`                                  | Open rebirth purchasing and upgrades                      |
| `/companion bulkdelete <zone> <stage>`      | Open companion bulk deletion for a zone and stage         |

Aliases include `/dungeon`, `/dg`, `/zone`, `/armour`, `/crystals`, and `/companions`.

## Main admin commands

| Command               | Purpose                                                 |
| --------------------- | ------------------------------------------------------- |
| `/dga help [page]`    | Show the complete in-game admin command list            |
| `/dga validate`       | Validate configuration without publishing it            |
| `/dga reload`         | Validate and publish a new configuration generation     |
| `/dga save`           | Queue an asynchronous save of changed player profiles   |
| `/dga setup`          | Toggle the visual setup workflow                        |
| `/dga setup list`     | List configured zones, stages, and areas                |
| `/dga blocks status`  | Inspect packet-block generation and queue state         |
| `/dga blocks rebuild` | Rebuild packet-block themes through the reload pipeline |
| `/dga stop <player>`  | Stop a player's active session                          |
| `/dga debug`          | Show engine, profile, and packet diagnostics            |

## Player data and test tools

```
/dga player <player> <currency> <add|remove|set> <amount>
/dga player <player> <resource> <add|remove|set> <amount>
/dga give <player> ability <id> [amount]
/dga give <player> companion-scroll [amount]
/dga give <player> crystal <enchant> [amount] [apply-chance] [boost] [merged]
/dga unlock <player> <zone> <stage>
/dga armorskin unlock <player> <skin>
/dga testsword [player] [--with-procs]
/dga testsword reset [player]
/dga forceproc <player> <enchant|clear>
/dga testmob <spawn|remove> [player]
```

The `<resource>` form supports rebirths, perk tickets, pity progress, Crystal Dust, Enchanted Crystal Dust, stored crystals, and Crystal Boxes. Tab completion shows the currently valid IDs and arguments.

## Permissions

| Permission                          | Default   | Purpose                                              |
| ----------------------------------- | --------- | ---------------------------------------------------- |
| `drakodungeons.use`                 | Everyone  | Use normal dungeon commands and menus                |
| `drakodungeons.admin`               | Operators | Use `/dga` administration                            |
| `drakodungeons.afk`                 | Nobody    | Use configured AFK dungeon areas                     |
| `drakodungeons.perks.mass-roll`     | Nobody    | Start automatic perk-ticket Mass Roll                |
| `drakodungeons.packetblocks.bypass` | Operators | Edit physical coordinates protected by packet themes |

Armor skin permissions are defined by admins inside `content/armor.yml`.


# Configuration Workflow

Drako Dungeons loads one validated snapshot from the entire configuration tree. A change in one file can therefore be checked against another file before anything becomes live.

## Editing rules

* Keep `schema-version: 1` in every managed YAML file.
* Use spaces, not tab characters.
* Keep IDs lowercase and stable. Use underscores for multi-word IDs.
* Use Bukkit/Paper enum names such as `ZOMBIE`, `DIAMOND_SWORD`, or `END_ROD`.
* Menu slots are **1-based**. The sword hotbar slot and companion hotbar slot are **0-based**.
* Currency references must exist in `content/currencies.yml`.
* Zone stage numbers must be consecutive, starting at `1`.
* MiniMessage and familiar `&` color codes are supported in player-facing text.
* Do not rename placeholders or fixed YAML keys.

## Safe hot reload

Use this sequence after changing YAML:

```
/dga validate
/dga reload
```

`/dga validate` parses and cross-checks the candidate without changing the live server. `/dga reload` prepares the full candidate, publishes it, and refreshes active systems through configured tick budgets.

If validation fails, correct the path reported in chat or console and run validation again. The previous live generation remains active.

## Changes that should use a restart

Use a full restart for:

* replacing the plugin JAR;
* installing or removing PlaceholderAPI or FastAsyncWorldEdit;
* changing the packet entity ID starting range;
* recovering from an incomplete reload publication;
* restoring `dungeons.db` from a backup.

## Configuration ownership

| Directory             | Purpose                                                                  |
| --------------------- | ------------------------------------------------------------------------ |
| `settings/`           | Global engine, progression, visuals, setup defaults, and spawn positions |
| `content/`            | Reusable gameplay catalogues and balance                                 |
| `zones/`              | World-specific areas, mobs, stages, and rewards                          |
| `menus/`              | Inventory titles, sizes, slots, materials, names, and lore               |
| `language/messages/`  | Chat, action-bar, item, setup, and menu wording                          |
| `language/sounds.yml` | Event sound, category, volume, pitch, and enabled state                  |

## Development resets

This plugin is currently built around a clean current schema. To intentionally reset during development:

{% stepper %}
{% step %}

## Stop the server

{% endstep %}

{% step %}

## Back up anything you may need later

{% endstep %}

{% step %}

## Delete the `plugins/DrakoDungeons/` data directory

{% endstep %}

{% step %}

## Start the server to generate a fresh baseline

{% endstep %}
{% endstepper %}


# First Dungeon

The safest setup path is the in-game visual editor. Run all location-based commands while standing in the world that will contain the dungeon.

{% stepper %}
{% step %}

## Enter setup mode

```
/dga setup
```

Setup mode backs up the administrator's inventory and game mode, then provides a dedicated hotbar. Run the command again to leave setup mode and restore the previous state.
{% endstep %}

{% step %}

## Create a zone

You can use the setup menu or a direct command:

```
/dga setup createzone mines "Mines"
```

{% hint style="success" %}
**Hint:** Or use the zone button in your hotbar
{% endhint %}

The zone is created at your position with:

* a stable ID (`mines` in the example);
* a player-facing display name;
* a starter area and mob point;
* a starter mob and first stage;
* scaling copied from `settings/setup.yml`.

The first created zone also becomes `progression.starting-zone`.

{% hint style="info" %}
Treat IDs as permanent storage keys. Change `display-name` when you want different text; avoid renaming IDs after players have progress.
{% endhint %}
{% endstep %}

{% step %}

## Select the arena

With FastAsyncWorldEdit installed:

1. Select the two opposite corners with `//wand`, `//pos1`, and `//pos2`.
2. Stand inside the selected cuboid.
3. Open `/dga setup`, select the zone, and use the FAWE Arena tool.

Standing inside the selection ensures the entry and initial spawn remain inside the arena. Bounds are also required before packet-block themes can be configured.
{% endstep %}

{% step %}

## Set the player entry

Stand where players should arrive and use the setup tool, or run:

```
/dga setup mines entry
```

Player yaw and pitch are stored with the position.
{% endstep %}

{% step %}

## Add and face mob points

Stand at each desired point and run:

```
/dga setup mines addspawn
```

Look in the direction the mobs should face, then apply that direction to all shared points:

```
/dga setup mines facingall
```

At least one mob point must remain in every active area.
{% endstep %}

{% step %}

## Add stages

The visual stage editor can add and tune stages. Direct commands are also available:

```
/dga setup mines createstage stage_2 "Stage 2"
/dga setup mines stagemob 2 ZOMBIE
/dga setup mines stagescale 2 health 2.0
/dga setup mines stagescale 2 money 2.0
```

New stages inherit automatic costs, completion targets, rewards, and scaling from `settings/setup.yml`. Fine-tune the resulting `zones/mines.yml` afterward.
{% endstep %}

{% step %}

## Add optional systems

* Place a Companion Block with the setup hotbar or `/dga setup mines companionblock`.
* Place an AFK mob point with the setup hotbar or `/dga setup mines afkmob`.
* Configure safe client-only block replacements with the Packet Blocks setup tool.
* Create additional areas and attach them to selected stages when a zone has multiple arenas.
  {% endstep %}

{% step %}

## Validate and test

```
/dga validate
/dga reload
/dungeons join mines 1
```

Test the entry, arena boundary, every mob point, stage completion, rewards, reconnect behavior, and leaving the area. Run `/dga setup` again when finished to restore the admin inventory.
{% endstep %}
{% endstepper %}


# Menus & GUIs

Every inventory screen has a dedicated file under `plugins/DrakoDungeons/menus/`. Menu files control presentation and layout; gameplay values remain in `settings/`, `content/`, and `zones/`.

## Basic structure

```yaml
schema-version: 1
title: '<dark_aqua><bold>Dungeons</bold> <dark_gray>({page}/{pages})'
size: 54
filler:
  slots: [1-54]
  material: BLACK_STAINED_GLASS_PANE
  name: ' '
  lore: []
items:
  previous-page:
    slots: [46]
    material: ARROW
    name: '<yellow>Previous Page'
    lore: []
```

Menu sizes must be a valid inventory size. Slots are **1-based**, unlike zero-based hotbar settings.

Slot lists support individual numbers and inclusive ranges:

```yaml
slots: [1, 5, 11-17, 46]
```

Overlapping state templates are allowed only where the service expects them, such as selected/unselected variants occupying the same logical position.

## Item fields

Depending on the item template, available presentation fields include:

* `slots`
* `material`
* `amount`
* `custom-model-data`
* `head-texture`
* `name`
* `lore`
* `glow`
* `values` for repeated actions such as ticket or hatch amounts

Materials must be valid for the running Minecraft version. Head texture URLs should use the Minecraft texture service.

## Placeholders

Placeholders use braces, such as `{zone}`, `{page}`, `{price}`, or `{level}`. Each screen has its own supported values. Preserve existing placeholders when changing wording unless you intentionally want to hide that value.

Some placeholders expand into several lore rows. In `menus/sword-enchanter.yml`, keep `{description_lines}`, `{effects}`, and `{upgrade_lines}` on their own lines so the generated lore remains aligned.

## Capacity checks

Several gameplay catalogues are validated against their menu capacity:

* zones and stages paginate through their configured card slots;
* armor tier count must fit `menus/armor.yml`;
* Sword Attribute levels must fit each state in `menus/sword-attributes.yml`;
* ability definitions must fit `menus/abilities.yml`;
* companion definitions must fit `menus/companion-eggs.yml`;
* sword crystal socket count must equal the socket slots in `menus/crystals.yml`;
* perk ticket bundle values must match `content/perks.yml`.

Do not remove or rename required item IDs. The plugin validates the required controls for every menu and will reject incomplete layouts.

## Resource-pack titles

Some bundled menu titles contain private-use font glyphs such as `\uE7F0`. These select resource-pack backgrounds. Change them only when the matching font mappings and textures are also updated.

## Publishing changes

```
/dga validate
/dga reload
```

Open every modified menu at its first page, last page, empty state, locked state, affordable state, and maximum-level state before releasing the design.


# Messages & Sounds

Player-facing language is split across focused files under `language/messages/`:

| File           | Content                                                                     |
| -------------- | --------------------------------------------------------------------------- |
| `core.yml`     | Prefix, action bar, shared errors, progression, and shared visual fragments |
| `commands.yml` | Player/admin help and administrative feedback                               |
| `gameplay.yml` | Combat, enchants, swords, armor, crystals, stages, and rebirth              |
| `items.yml`    | Bound item names, lore, and reusable item fragments                         |
| `menus.yml`    | Dynamic menu wording and admin setup-menu text                              |
| `setup.yml`    | Visual setup prompts and results                                            |

## Text formatting

MiniMessage and familiar `&` color codes are supported:

```yaml
prefix: '<dark_gray>[<gradient:#58e6ff:#a66cff><bold>Dungeons</bold></gradient><dark_gray>] '
no-permission: '<red>You do not have permission to do that.'
```

Runtime placeholders use angle brackets, for example `<player>`, `<amount>`, or `<zone>`. Do not rename the YAML key or placeholder token. Missing required placeholders can remove useful information from a message even when the file still validates.

The shared `prefix` is added to normal chat messages. Item, menu, hologram, and reusable fragment entries are generally rendered without it.

## Action bar

`language/messages/core.yml: actionbar` supports configured currency gain and per-minute values:

```
<money_gain>
<money_minute>
<rebirth_bar>
<rebirth_percent>
```

Each configured currency also exposes `<currencyId>_gain` and `<currencyId>_minute`. Visibility and update interval are controlled in `settings/visuals.yml`.

## Sounds

Event sounds are configured in `language/sounds.yml`:

```yaml
sounds:
  attack-critical:
    sound: ENTITY_PLAYER_ATTACK_CRIT
    category: PLAYERS
    volume: 0.55
    pitch: 1.35
    enabled: true
```

`category` defaults to `PLAYERS`, and `enabled` defaults to `true`. Volume supports `0` through `4`; pitch supports `0.5` through `2`.

To silence one event:

```yaml
sounds:
  ability-no-proc:
    enabled: false
```

Enchant-specific proc sounds and particles are configured inside each `content/enchants/*.yml` file rather than the shared sound catalogue.


# Visuals

Global HUD and private hologram settings live in `settings/visuals.yml`. Message wording stays in `language/messages/`, while enchant-specific animation settings stay in each enchant file.

## HUD controls

```yaml
visuals:
  actionbar: true
  actionbar-interval-ticks: 5
  bossbar: true
```

The action-bar template is `language/messages/core.yml: actionbar`. A shorter update interval looks smoother but sends and formats updates more frequently.

Players can also use the Sword Settings menu for supported personal visibility options.

## Mob holograms and glows

```yaml
visuals:
  mob-hologram-height-offset: 0.30
  mob-glows:
    rare: BLUE
    epic: LIGHT_PURPLE
    legendary: GOLD
```

The permanent private label uses zone hologram colors and message fragments from `language/messages/core.yml`. Omitted rarities receive no outline.

## Damage holograms

```yaml
visuals:
  damage-holograms: true
  damage-hologram-duration-ticks: 24
  damage-hologram-limit-per-player: 16
  damage-hologram-height-offset: 0.45
  damage-hologram-fall-per-step: 0.035
  damage-hologram-drift-per-step: 0.025
  damage-hologram-symbol: '☆'
  damage-hologram-symbol-format: '&f&l'
  damage-hologram-normal-format: '&d&l'
  damage-hologram-critical-format: '&6&l'
```

The per-player limit prevents rapid multi-hit effects from creating an unbounded packet queue. Height is automatically capped below the permanent mob label.

Reduce duration, limit, or animation density when high attack rates create visual clutter. Disabling damage holograms removes that channel without changing combat.

## Rebirth progress bar

```yaml
progress-bar:
  segments: 9
  character: '▌'
  filled-format: '&a'
  empty-format: '&c'
```

These values build the `<rebirth_bar>` used in the action-bar message.

## Packet cosmetics

`settings/engine.yml: packets.player-specific-particles` and `player-specific-sounds` can disable private combat particle or sound channels globally without changing gameplay results.

Companion Block holograms, companion ambient particles, and optional equipped follower displays are configured independently in `content/companions.yml`.


# Placeholders

DrakoDungeons registers an internal PlaceholderAPI expansion when PlaceholderAPI is installed and enabled. Every placeholder begins with `%drakodungeons_` and ends with `%`.

## Leaderboards

Leaderboard positions are 1-based. Replace `#` with a number from 1 through 500 and replace `<currency>` with a configured currency ID such as `money`, `souls`, `essence`, `shards`, or `credits`.

| Placeholder                              | Result                                                 |
| ---------------------------------------- | ------------------------------------------------------ |
| `%drakodungeons_rebirth_pos_#%`          | Rebirth count held by the player at position `#`       |
| `%drakodungeons_rebirth_pos_#_name%`     | Name of the player at rebirth position `#`             |
| `%drakodungeons_rebirth_pos_current%`    | Current viewer's position on the rebirth leaderboard   |
| `%drakodungeons_<currency>_pos_#%`       | Compact balance held at position `#` for that currency |
| `%drakodungeons_<currency>_pos_#_name%`  | Name of the player at currency position `#`            |
| `%drakodungeons_<currency>_pos_current%` | Current viewer's position for that currency            |

For example:

```
%drakodungeons_rebirth_pos_1%
%drakodungeons_rebirth_pos_1_name%
%drakodungeons_rebirth_pos_current%

%drakodungeons_souls_pos_1%
%drakodungeons_souls_pos_1_name%
%drakodungeons_souls_pos_current%
```

The existing `currency_` and `balance_` prefixes are also accepted, so `%drakodungeons_currency_souls_pos_1%` and `%drakodungeons_balance_souls_pos_1%` are aliases for `%drakodungeons_souls_pos_1%`.

Only currencies with `leaderboard: true` support leaderboard placeholders. Missing positions and players not yet present in storage return `0`; a missing `_name` entry returns an empty string. Rebirth ties are ordered by sword kills and then UUID. Currency ties are ordered by UUID.

Leaderboard data is loaded asynchronously and cached for 30 seconds. This keeps PlaceholderAPI lookups off the server thread. A first lookup made before its asynchronous load completes returns the same `0` or empty-string fallback and fills on a later PlaceholderAPI refresh.

## Currency balances

| Placeholder                         | Result                               |
| ----------------------------------- | ------------------------------------ |
| `%drakodungeons_currency_<id>%`     | Compact balance for the viewer       |
| `%drakodungeons_currency_<id>_raw%` | Unabbreviated balance for the viewer |

`%drakodungeons_balance_<id>%`, `%drakodungeons_balance_<id>_raw%`, `%drakodungeons_<id>%`, and `%drakodungeons_<id>_raw%` are supported aliases for configured currency IDs.

## Sword

| Placeholder                             | Result                                     |
| --------------------------------------- | ------------------------------------------ |
| `%drakodungeons_sword_level%`           | Sword level                                |
| `%drakodungeons_sword_xp%`              | Compact current sword XP                   |
| `%drakodungeons_sword_xp_raw%`          | Unabbreviated current sword XP             |
| `%drakodungeons_sword_xp_required%`     | Compact XP needed for the next level       |
| `%drakodungeons_sword_xp_required_raw%` | Unabbreviated XP needed for the next level |
| `%drakodungeons_sword_xp_percent%`      | Progress toward the next sword level       |
| `%drakodungeons_sword_damage%`          | Compact sword damage                       |
| `%drakodungeons_sword_damage_raw%`      | Unabbreviated sword damage                 |
| `%drakodungeons_sword_critical_chance%` | Critical chance as a percentage number     |
| `%drakodungeons_sword_critical_damage%` | Critical damage multiplier                 |
| `%drakodungeons_sword_prestige%`        | Sword prestige count                       |
| `%drakodungeons_sword_crystal_slots%`   | Available crystal slots                    |
| `%drakodungeons_sword_attacks%`         | Compact lifetime sword attacks             |
| `%drakodungeons_sword_attacks_raw%`     | Unabbreviated lifetime sword attacks       |
| `%drakodungeons_sword_attack_rate%`     | Sword attacks per second, including `/s`   |
| `%drakodungeons_sword_attribute_level%` | Combined sword attribute level             |
| `%drakodungeons_sword_kills%`           | Lifetime sword kills                       |

## Rebirth and progression

| Placeholder                                | Result                                            |
| ------------------------------------------ | ------------------------------------------------- |
| `%drakodungeons_rebirth%`                  | Viewer rebirth count                              |
| `%drakodungeons_progression_zone%`         | Active or recommended zone ID                     |
| `%drakodungeons_progression_stage%`        | Active or recommended stage number                |
| `%drakodungeons_progression_stage_id%`     | Active or recommended stage ID                    |
| `%drakodungeons_progression_percent%`      | Overall completed-stage percentage                |
| `%drakodungeons_stage_progress%`           | Current stage completion percentage               |
| `%drakodungeons_stage_money_earned%`       | Compact Money earned for the current stage        |
| `%drakodungeons_stage_money_earned_raw%`   | Unabbreviated Money earned for the current stage  |
| `%drakodungeons_stage_money_required%`     | Compact Money required by the current stage       |
| `%drakodungeons_stage_money_required_raw%` | Unabbreviated Money required by the current stage |
| `%drakodungeons_stage_completed%`          | `true` when the current stage is complete         |
| `%drakodungeons_zone_unlocked%`            | `true` when the selected zone is unlocked         |

## Active session

| Placeholder                                     | Result                                           |
| ----------------------------------------------- | ------------------------------------------------ |
| `%drakodungeons_session_active%`                | Whether the viewer has an active dungeon session |
| `%drakodungeons_session_zone%`                  | Active zone ID                                   |
| `%drakodungeons_session_stage%`                 | Active stage number                              |
| `%drakodungeons_session_stage_id%`              | Active stage ID                                  |
| `%drakodungeons_session_area%`                  | Active grinding area ID                          |
| `%drakodungeons_session_grinding%`              | Whether automatic attacks are active             |
| `%drakodungeons_session_target%`                | Current target mob ID                            |
| `%drakodungeons_session_target_health%`         | Compact current target health                    |
| `%drakodungeons_session_target_health_raw%`     | Unabbreviated current target health              |
| `%drakodungeons_session_target_max_health%`     | Compact maximum target health                    |
| `%drakodungeons_session_target_health_percent%` | Current target health percentage                 |


# Drako Dungeons API

Drako Dungeons exposes one unified API v1. It provides immutable, implementation-independent profile, ability, leaderboard, extension and rebirth types together with custom-enchant, live currency-booster, crystal, loot and wallet APIs.

## Add the API to your plugin

Compile against the DrakoDungeons JAR, but do not shade it into your plugin:

```kotlin
dependencies {
    compileOnly(files("libs/DrakoDungeons-1.0.0.jar"))
}
```

Use a hard dependency when your plugin cannot run without DrakoDungeons:

```yaml
# plugin.yml
depend: [DrakoDungeons]
```

For an optional integration, use `softdepend` and check that the API is present.

```java
DungeonApi api = Bukkit.getServicesManager().load(DungeonApi.class);
if (api == null || api.apiVersion() != DungeonApi.CURRENT_API_VERSION) {
    getLogger().warning("DrakoDungeons API v1 is unavailable or incompatible");
    return;
}
```

`Dungeons.api()` is also available after DrakoDungeons has enabled. Service lookup is usually easier to test and handles optional integrations cleanly.

{% hint style="warning" %}
Live gameplay callbacks and mutating API methods run on Paper's primary thread. Profile and leaderboard futures complete asynchronously. Extension callbacks must be fast and must not perform network or database I/O during combat; cache external state before the callback runs.
{% endhint %}

## Use the unified v1 DTO API

New integrations should use the `Dungeon*` DTO methods. These records contain only API and JDK types, copy mutable collections on construction, and do not expose persistence implementation classes.

```java
UUID playerId = player.getUniqueId();

api.loadedPlayerProfile(playerId).ifPresent(profile ->
        getLogger().info(profile.lastKnownName() + " is sword level "
                + profile.swordLevel()));

api.playerProfile(playerId).thenAccept(profile -> profile.ifPresent(snapshot ->
        getLogger().info("Persisted profile revision: " + snapshot.revision())));

DungeonAbilityActivation activation =
        api.activateDungeonAbility(player, "earthquake");
if (activation.status() == DungeonAbilityActivation.Status.COOLDOWN) {
    player.sendMessage("Ready in " + activation.cooldownMillis() + " ms");
}

api.currencyLeaderboard(DungeonCurrencies.SOULS, 10).thenAccept(entries ->
        entries.forEach(entry -> getLogger().info(
                entry.lastKnownName() + ": " + entry.balance())));

DungeonExtensionSummary extensions = api.extensionSummary();
boolean customEnchantLoaded = extensions.customEnchantIds().contains("coin_burst");
```

`loadedPlayerProfile` reads only an already loaded profile. `playerProfile` and `currencyLeaderboard` may perform storage work and therefore return `CompletableFuture` results; do not block Paper's primary server thread while waiting for them. Ability activation and wallet mutations must run on the primary thread.

`RebirthUpgradeEvent#getUpgradeType()` exposes the API-owned `DungeonRebirthUpgrade` enum. Deprecated profile, ability-result, leaderboard and rebirth aliases remain as unversioned compatibility shims. Runtime provider registration through `extensions()` is an official v1 API; use `extensionSummary()` for read-only discovery. New integrations should otherwise use the API-owned `Dungeon*` DTOs.

## Register pet bonuses

A pet plugin should own selection and progression in its own storage, then register one dynamic provider with DrakoDungeons. Return `BigDecimal.ONE` when no active pet applies. Multiple providers stack multiplicatively, and a failing provider is isolated from combat.

```java
private ExtensionRegistration dungeonPets;

public void hookDungeons(DungeonApi api) {
    dungeonPets = api.extensions().register(new PetBonusProvider() {
        @Override
        public String id() {
            return "my_pets";
        }

        @Override
        public BigDecimal damageMultiplier(DungeonMobContext context) {
            ActivePet pet = petCache.get(context.player().getUniqueId());
            return pet == null ? BigDecimal.ONE : pet.dungeonDamageFactor();
        }

        @Override
        public BigDecimal rewardMultiplier(
                DungeonMobContext context, String currencyId, DungeonRewardCause cause) {
            ActivePet pet = petCache.get(context.player().getUniqueId());
            return pet == null ? BigDecimal.ONE : pet.dungeonCurrencyFactor(currencyId);
        }
    });
}

@Override
public void onDisable() {
    if (dungeonPets != null) dungeonPets.close();
}
```

`DungeonMobContext` identifies the zone, stage, area, mob, rarity, boss state, location, automatic/manual attack state and kill cause. Pet damage scales the direct strike and derived explosion, chain, zone and damage-over-time effects. Pet reward factors apply to per-swing and mob-kill currency.

## Integrate backpacks and loot collectors

`DungeonMobLootEvent` fires synchronously after configured drops, enchants, armour, rebirths, global reward providers and pet factors, but before wallet or inventory delivery. It contains one mutable loot bundle for the defeated mob.

A backpack listener should store what it can first, then leave only overflow in the event bundle:

```java
@EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true)
public void onDungeonLoot(DungeonMobLootEvent event) {
    MutableDungeonLoot loot = event.loot();

    List<ItemStack> itemOverflow = backpacks.insertItems(
            event.getPlayer().getUniqueId(), loot.items());
    loot.replaceItems(itemOverflow);

    for (Map.Entry<String, BigDecimal> reward : loot.currencies().entrySet()) {
        BigDecimal accepted = backpacks.insertCurrency(
                event.getPlayer().getUniqueId(), reward.getKey(), reward.getValue());
        BigDecimal remaining = reward.getValue().subtract(accepted);
        loot.setCurrency(reward.getKey(), remaining.max(BigDecimal.ZERO));
    }

    if (loot.isEmpty()) event.setCancelled(true);
}
```

Only configured dungeon currency IDs enter the normal wallet. Remaining items enter the player's inventory, with overflow dropped at the private mob's location. Item stacks are defensive copies; use `replaceItems` or `addItem` to mutate the bundle. Never clear or cancel loot until the external storage operation has succeeded.

Plugins can also add loot before the event by registering a `DungeonLootProvider`. Providers run in stable ID order on private working copies, so a failing provider's partial changes are discarded.

```java
ExtensionRegistration registration = api.extensions().register(new DungeonLootProvider() {
    @Override
    public String id() {
        return "my_custom_drops";
    }

    @Override
    public void contribute(DungeonMobContext context, MutableDungeonLoot loot) {
        if (context.boss()) loot.addItem(makeBossToken(context.mobId()));
    }
});
```

## Use built-in abilities

The API exposes the configured ability catalogue and creates correctly tagged redemption items. This lets mob, crate, quest or backpack plugins award unlocks without copying private persistent data keys.

```java
if (api.abilities().contains("booster")) {
    ItemStack token = api.abilityRedemptionItem("booster", 1); // primary thread
    rewards.add(token);
}
```

The player right-clicks the item to unlock it and selects it through `/dungeons abilities`. `selectedAbility(playerId)` reports the selection for a loaded profile. `activateDungeonAbility(player, id)` applies the same ownership, grinding and persistent cooldown checks as the Q input. Listen to `DungeonAbilityUnlockEvent` and `DungeonAbilityActivateEvent` to bridge activation into another system.

## Create crystal loot

Prefer the API factories for correctly tagged Crystal Boxes and physical crystals instead of copying item metadata or persistent-data keys.

```java
loot.addItem(api.crystalBox(1)); // amount
loot.addItem(api.crystalBox(4, 1)); // tier, amount

if (api.crystalEnchants().contains("archer")) {
    loot.addItem(api.crystalItem(
            "archer", new BigDecimal("72"), new BigDecimal("26"), false));
}
```

`crystalEnchants()` omits enchants whose base proc chance is already 100%. `applyChance` uses the displayed 0–100 percentage. Players can right-click either item to deposit or open it while all ownership, capacity, duplicate-type and application rules remain inside DrakoDungeons. The full `CrystalService` is separately available through Bukkit's service manager for advanced operations.

## Create a custom enchant

A custom enchant has two parts:

{% stepper %}
{% step %}

## Create the enchant YAML file

A normal enchant YAML file controls its name, GUI item, category, cost, levels, prestige and activation chance.

Create `plugins/DrakoDungeons/content/enchants/coin_burst.yml`. The file name must exactly match the enchant ID and each file must contain one enchant.

```yaml
schema-version: 1

enchants:
  coin_burst:
    display-name: '<gold><bold>Coin Burst'
    description: 'Occasionally deals extra damage and awards Money.'
    category: SOUL
    required-sword-level: 10
    max-level: 100

    gui:
      material: GOLD_INGOT
      primary-colour: '<gold>'
      accent-colour: '<yellow>'
      name: '{primary}<bold>Coin Burst'
      lore: ['{menu_template_lore}']

    activation-chance: { base: 0.10, per-level: 0.002, maximum: 0.35 }
    cost: { currency: souls, base: 250, multiplier: 1.08 }

    effects:
      burst:
        type: CUSTOM
        parameters: { money-per-level: 5.0, damage-multiplier: 1.25 }
        options: { reward-currency: money, menu-type: 'Damage and Money' }

    visual:
      particle: HAPPY_VILLAGER
      particle-count: 12
      sound: ENTITY_EXPERIENCE_ORB_PICKUP
      volume: 0.8
      pitch: 1.2
      animation: NONE

    prestige: { max-level: 5, cost-multiplier: 1.15, effect-bonus-per-level: 0.10 }
```

When `gui.slot` is omitted, DrakoDungeons places the enchant in the next free slot from `menus/sword-enchanter.yml`. A one-based `gui.slot` can be supplied when an exact position is needed. Slots cannot overlap another enchant in the same category.
{% endstep %}

{% step %}

## Register code with the same ID

```java
private ExtensionRegistration coinBurst;

private void registerCoinBurst(DungeonApi api) {
    coinBurst = api.extensions().register(new CustomEnchant() {
        @Override
        public String id() {
            return "coin_burst";
        }

        @Override
        public CustomEnchantResult activate(CustomEnchantContext context) {
            double configuredDamage = context.parameters().getOrDefault("damage-multiplier", 1.0);
            double damage = 1.0 + (configuredDamage - 1.0) * context.prestigeMultiplier();
            double perLevel = context.parameters().getOrDefault("money-per-level", 0.0);
            String currency = context.options().getOrDefault(
                    "reward-currency", DungeonCurrencies.MONEY);
            BigDecimal money = BigDecimal.valueOf(perLevel * context.level()
                    * context.prestigeMultiplier());

            return new CustomEnchantResult(
                    damage,
                    0.0, // optional flat bonus damage
                    Map.of(currency, money)
            );
        }
    });
}

@Override
public void onDisable() {
    if (coinBurst != null) coinBurst.close();
}
```

{% endstep %}
{% endstepper %}

The callback runs only after the configured proc chance succeeds. `CustomEnchantContext` provides the player and mob context, an immutable profile snapshot, level and prestige, current calculated damage, mob health, and the custom `parameters` and `options` maps.

The returned damage is applied before pet, armour and `DungeonMobDamageEvent` modifiers. Returned currency enters the normal reward pipeline, including registered boosters, zone/pet modifiers, sword currency attributes, `DungeonCurrencyGainEvent`, wallet limits and persistence.

Useful catalogue checks:

```java
boolean loaded = api.enchants().contains("coin_burst");
boolean custom = api.customEnchants().contains("coin_burst");
```

If the YAML exists but no matching callback is registered, the enchant can still appear and level up, but its `CUSTOM` effect is neutral. Callback exceptions and invalid results are contained and logged without stopping combat.

## Currency API

The stable core IDs are available as constants:

```java
DungeonCurrencies.MONEY;  // "money"
DungeonCurrencies.SHARDS; // "shards"
DungeonCurrencies.SOULS;  // "souls"
```

Discover the complete configured catalogue and its display metadata:

```java
Set<String> ids = api.currencies();

api.currency(DungeonCurrencies.SHARDS).ifPresent(currency ->
        getLogger().info(currency.displayName() + " uses " + currency.symbol()));
```

Wallet operations use `BigDecimal` and require a loaded player profile. Run mutations on Paper's primary server thread.

```java
UUID playerId = player.getUniqueId();

BigDecimal balance = api.balance(playerId, DungeonCurrencies.MONEY);

BigDecimal credited = api.addCurrency(
        playerId,
        DungeonCurrencies.SHARDS,
        new BigDecimal("25"),
        "my-plugin:quest-reward"
);

BigDecimal removed = api.removeCurrency(
        playerId,
        DungeonCurrencies.SOULS,
        new BigDecimal("100")
); // zero when the full amount cannot be afforded

BigDecimal newBalance = api.setCurrency(
        playerId,
        DungeonCurrencies.MONEY,
        new BigDecimal("5000")
);
```

`addCurrency` requires the player to be online, applies the configured sword currency attribute, and fires `DungeonCurrencyGainEvent`. `setCurrency` is capped and rounded to the configured wallet rules. `removeCurrency` is all-or-nothing and rounds as a currency cost.

## Create a currency booster

Register one provider for your plugin and return a factor for each reward calculation. Returning `1` leaves that currency unchanged. Providers stack multiplicatively.

```java
private final Map<UUID, Long> doubleCurrencyUntil = new ConcurrentHashMap<>();
private ExtensionRegistration dungeonBooster;

private void registerBooster(DungeonApi api) {
    dungeonBooster = api.extensions().register(new CurrencyBoosterProvider() {
        @Override
        public String id() {
            return "my_plugin_boosters";
        }

        @Override
        public BigDecimal multiplier(CurrencyBoostContext context) {
            long expires = doubleCurrencyUntil.getOrDefault(context.playerId(), 0L);
            if (expires <= context.nowEpochMilli()) return BigDecimal.ONE;

            return switch (context.currencyId()) {
                case DungeonCurrencies.MONEY,
                     DungeonCurrencies.SHARDS,
                     DungeonCurrencies.SOULS -> new BigDecimal("2");
                default -> BigDecimal.ONE;
            };
        }
    });
}

public void activateDoubleCurrency(Player player, Duration duration) {
    doubleCurrencyUntil.put(player.getUniqueId(),
            System.currentTimeMillis() + duration.toMillis());
}

@Override
public void onDisable() {
    if (dungeonBooster != null) dungeonBooster.close();
}
```

Boosters affect generated dungeon rewards such as per-swing Souls, mob drops, enchant rewards and custom-enchant currency. They do not multiply direct administrative calls to `addCurrency`, `setCurrency` or `removeCurrency`.

{% hint style="warning" %}
Callbacks run synchronously during combat. Keep them fast, use cached state, and do not perform network or database I/O inside a custom enchant or booster callback.
{% endhint %}

## Event timing

* `DungeonMobDamageEvent`: cancellable damage immediately before it is applied.
* `DungeonMobLootEvent`: cancellable and mutable loot before normal delivery or profile settlement.
* `DungeonCurrencyGainEvent`: final per-currency modification immediately before wallet planning.
* `DungeonMobKillEvent`: post-commit notification with credits, delivered item loot and kill cause.
* `DungeonAbilityUnlockEvent`: post-redeem notification after ownership is committed.
* `DungeonAbilityActivateEvent`: post-effect notification after the cooldown is committed.
* `TierCompleteEvent`: post-completion progression notification.

Pre-commit listeners may alter delivery. Post-commit listeners are notifications; cancelling or throwing from them cannot roll back an already committed reward.

## External Money delivery guarantees

When `economy.money-provider` selects Vault, combat first writes a unique reward intent to the DrakoDungeons SQLite outbox. A later bounded primary-thread pass calls the active Vault provider. Failures use exponential backoff and survive restarts. Only provider-confirmed amounts are published to gameplay events and player feedback. `/dga moneyoutbox status|replay` provides asynchronous operational inspection without performing SQLite work on the server thread.

The outbox supports one DrakoDungeons server per SQLite database. Vault does not expose an idempotency key, so delivery is necessarily at-least-once: a JVM or power loss after the provider accepts a deposit but before the local acknowledgement commits can replay that intent. Ordinary database failures, acknowledgement retries and interrupted deletion are protected by terminal tombstones and the orderly-shutdown flush.

## Reloading

After adding or editing an enchant file, run:

```
/dga reload
```

The reload validates the entire candidate config before publishing it. Existing extension registrations remain active, so a callback does not need to be registered again after a successful DrakoDungeons config reload.


# Troubleshooting

## The plugin does not enable

Check these first:

{% stepper %}
{% step %}

## Verify the server version

The server is Paper or Purpur on Minecraft 1.21.11 or 26.2.
{% endstep %}

{% step %}

## Verify the Java version

The process is using Java 21.
{% endstep %}

{% step %}

## Check for duplicate JARs

There is only one DrakoDungeons JAR in `plugins/`.
{% endstep %}

{% step %}

## Avoid reload managers

No reload manager was used after replacing the JAR.
{% endstep %}

{% step %}

## Review the first console error

The first DrakoDungeons error in the console identifies a configuration path or dependency problem.
{% endstep %}
{% endstepper %}

Run `/dga validate` when the command is available. If startup fails before commands register, correct the console error and restart.

## No zones are shown

A clean install intentionally contains zero zones. Create one in game:

```
/dga setup createzone mines "Mines"
```

If zone files exist, confirm that `settings/gameplay.yml: progression.starting-zone` names a valid zone and that zone orders and stage numbers are consecutive.

## A reload fails

* Read the full path in the error.
* Check indentation and duplicate YAML keys.
* Confirm that referenced currencies, zones, areas, mobs, enchants, and materials exist.
* Confirm that menu capacity matches the related content catalogue.
* Run `/dga validate` again before retrying reload.

{% hint style="warning" %}
If the message says publication or live refresh was incomplete, perform a full server restart before admitting players.
{% endhint %}

## Players cannot enter

Confirm that:

* the world is loaded;
* the zone and stage are unlocked;
* the stage has at least one valid area and mob;
* the area has an entry and mob point;
* the player is alive and not in spectator mode;
* AFK areas have the `drakodungeons.afk` permission;
* an optional selection-reroll cooldown is not active.

Use `/dungeons status`, `/dga debug`, and `/dga setup list` to inspect state.

## Private mobs cannot be clicked

Check `packets.interaction-hitbox-width`, `interaction-hitbox-height`, input interaction budgets, target range, area bounds, and the spawn's optional hitbox overrides. Test without another plugin intercepting entity interaction packets.

## Packet blocks do not appear

Run:

```
/dga blocks status
/dga validate
/dga blocks rebuild
```

The area needs explicit bounds, each source material must exist in the physical cuboid, and replacements must have compatible server behavior. If the physical build changed, stop the server and remove `cache/packet-blocks/`, then restart or rebuild.

## Menus fail validation

Do not remove required item IDs. Confirm the menu size, 1-based slots, valid materials, non-conflicting slot ranges, required state templates, and cross-file capacity rules.

## Profiles do not save

Use `/dga save` and inspect the console. Check disk space, filesystem permissions, antivirus locks, and whether another process is holding `dungeons.db`. Never copy over the live database.

## Clean development recovery

When the data is disposable and an incompatible development schema is the problem:

{% stepper %}
{% step %}

## Stop the server

{% endstep %}

{% step %}

## Back up anything needed

{% endstep %}

{% step %}

## Remove the plugin directory

Remove the complete `plugins/DrakoDungeons/` directory.
{% endstep %}

{% step %}

## Start again

{% endstep %}
{% endstepper %}

Partial deletion can leave a configuration that correctly fails validation.


# Performance

Drako Dungeons is budgeted so excess work remains queued for later ticks instead of running without limits. Tune from measurements, not from player count alone.

## Engine budgets

`settings/engine.yml: engine` controls the main loop:

| Setting                            | Purpose                                                            |
| ---------------------------------- | ------------------------------------------------------------------ |
| `tick-period-ticks`                | Delay between engine passes                                        |
| `attack-interval-ticks`            | Default automatic swing delay                                      |
| `maximum-attacks-per-tick`         | Global attack processing cap per pass                              |
| `maximum-player-updates-per-tick`  | Shared cap for transitions, refreshes, respawns, and hologram work |
| `maximum-session-starts-per-tick`  | Separate cap for expensive full private-wave starts                |
| `respawn-delay-ticks`              | Default defeated-mob rebuild delay                                 |
| `selection-reroll-cooldown-ticks`  | Optional anti-reroll delay                                         |
| `target-range` / `disengage-range` | Target acquisition and release distance                            |

Increasing a budget finishes queues sooner but leaves less main-thread time for the rest of the server. If starts or transitions arrive in bursts, tune the session-start cap separately from ordinary player updates.

## Packet entity budgets

`settings/engine.yml: packets` controls view distance, hitbox defaults, and inbound viewer/interaction caps. View distance has a large effect because it determines how many private entities can be visible and updated.

Keep `entity-id-start` at or above the documented safe range. Changing it requires a restart.

## Packet-block budgets

Area volume, changed blocks, theme count, capture rate, delivery rate, section rate, and queued section limits are all bounded. Prefer smaller deliberate bounds over raising global caps. See [Packet-Block Themes](/drako-dungeons/features/packet-block-themes) for the complete model.

## Profile saving

`saving.autosave-seconds` controls how often changed cached profiles are written. Only dirty profiles are saved. Very short intervals increase database activity; very long intervals increase the amount of recent progress at risk during an abnormal process crash.

## Feature-specific load controls

* Perks: `mass-roll.rolls-per-tick` is global across active rolls.
* Companions: follower display is disabled by default and can create two entities per equipped slot.
* Companions: `maximum-rebuilds-per-pass` limits structural bursts.
* Visuals: damage hologram duration and per-player cap bound temporary displays.
* Enchants: zone-wide target counts, hits, particles, and intervals are configured per enchant.
* AFK: AFK attacks share normal engine budgets.

## Production measurement checklist

{% stepper %}
{% step %}

### Test the intended maximum concurrent dungeon and AFK players

{% endstep %}

{% step %}

### Give test profiles realistic end-game attack speed, enchants, crystals, armor, and companions

{% endstep %}

{% step %}

### Include stage switches, reconnects, deaths, teleports, and reloads

{% endstep %}

{% step %}

### Test the largest packet-block arena and worst-case chunk boundary movement

{% endstep %}

{% step %}

### Monitor server MSPT, tick percentiles, heap, garbage collection, database latency, and DrakoDungeons queue diagnostics

{% endstep %}

{% step %}

### Use `/dga debug` and `/dga blocks status` during the test

{% endstep %}

{% step %}

### Lower burst-producing content or budgets before raising hardware limits

{% endstep %}
{% endstepper %}

Aim for stable headroom rather than a test that barely remains under 50 ms per tick.


# Placeholders

## PlaceholderAPI

Install PlaceholderAPI before startup to register the `drakodungeons` expansion. No separate eCloud download is required because the expansion is provided by the plugin.

### Currency placeholders

```
%drakodungeons_currency_<id>%
%drakodungeons_currency_<id>_raw%
```

The compact form abbreviates large values. The raw form returns the unshortened decimal.

### Sword placeholders

```
%drakodungeons_sword_level%
%drakodungeons_sword_xp%
%drakodungeons_sword_xp_required%
%drakodungeons_sword_xp_percent%
%drakodungeons_sword_damage%
%drakodungeons_sword_critical_chance%
%drakodungeons_sword_critical_damage%
%drakodungeons_sword_prestige%
%drakodungeons_sword_attacks%
%drakodungeons_sword_attack_rate%
%drakodungeons_sword_kills%
%drakodungeons_sword_attribute_level%
```

### Progression and session placeholders

```
%drakodungeons_progression_zone%
%drakodungeons_progression_stage%
%drakodungeons_progression_percent%
%drakodungeons_stage_progress%
%drakodungeons_stage_money_earned%
%drakodungeons_stage_money_required%
%drakodungeons_stage_completed%
%drakodungeons_session_active%
%drakodungeons_session_grinding%
%drakodungeons_session_zone%
%drakodungeons_session_stage%
%drakodungeons_session_area%
%drakodungeons_session_target%
%drakodungeons_session_target_health%
%drakodungeons_session_target_health_percent%
%drakodungeons_rebirth%
```

Placeholders return safe empty, false, or zero values while a profile is still loading.


# Abilities

Abilities are permanent Dungeon Sword unlocks configured in `content/abilities.yml`. Players redeem a tagged ability item, select the unlocked ability through `/dungeons abilities`, and press Q while holding their Dungeon Sword to activate it.

## Shared fields

```yaml
abilities:
  booster:
    order: 2
    display-name: '<light_purple><bold>Booster</bold></light_purple>'
    description:
      - 'Temporarily increases selected dungeon rewards.'
    material: PLAYER_HEAD
    head-texture: 'https://textures.minecraft.net/texture/...'
    cooldown-seconds: 120
    duration-seconds: 30
    effect: MULTIPLIER
    currency-multipliers:
      souls: 2.0
      essence: 2.0
```

IDs are persistent ownership keys. `order` controls menu order. Item presentation supports a normal material, head texture, and the configured text formats.

## Multiplier abilities

`effect: MULTIPLIER` can apply currency multipliers and a damage multiplier for `duration-seconds`. Every referenced currency must exist.

```yaml
effect: MULTIPLIER
duration-seconds: 20
currency-multipliers: { money: 2.0 }
damage-multiplier: 2.0
```

## Command abilities

`effect: COMMAND` runs configured console commands when its base chance succeeds:

```yaml
effect: COMMAND
base-chance: 0.25
commands:
  - chance: 1.0
    command: 'minecraft:give {player} minecraft:gold_block 1'
```

{% hint style="warning" %}
Use fully qualified command names where possible. Commands run with console authority, so review them carefully and never place untrusted player input into the command string.
{% endhint %}

## Granting abilities

```
/dga give <player> ability <id> [amount]
```

This gives a correctly tagged redemption item. The player must right-click it to commit the unlock. Giving a normal look-alike item does not unlock the ability.

## Menus, messages, and sounds

* `menus/abilities.yml` controls selection layout.
* `language/messages/gameplay.yml` controls unlock, selection, cooldown, and activation messages.
* `language/sounds.yml` controls ability event sounds.

{% hint style="info" %}
Validate command syntax, duration, cooldown, and effect references before reloading. Test activation while grinding and confirm that reconnecting does not bypass the persistent cooldown.
{% endhint %}


# AFK Grinding

Drako Dungeons supports dedicated AFK areas and one account-wide AFK target progression system. AFK access requires the `drakodungeons.afk` permission.

## Mark an area as AFK

An area can be marked directly in a zone file:

```yaml
areas:
  afk_room:
    display-name: '<aqua>AFK Room</aqua>'
    afk: true
    entry: 'world,150.5,65,200.5,90,0'
    activation-radius: 26.0
    spawns:
      centre: { position: 'world,160.5,65,200.5,-90,0' }
```

The setup workflow can toggle an existing area's AFK state. A zone-level AFK target position can be placed with the setup hotbar or:

```
/dga setup <zone> afkmob
/dga setup <zone> clearafkmob
```

Shared AFK positions are stored in `settings/spawns.yml: afk-mobs`.

## Progression rules

Edit `settings/setup.yml: setup.afk-mob`:

```yaml
afk-mob:
  maximum-level: 16
  allowed-currencies: [souls, essence]
  base-reward-multiplier: 0.25
  reward-multiplier-per-level: 0.03
  base-attack-interval-multiplier: 4.0
  attack-interval-reduction-per-level: 0.0666666667
  maximum-stage-reward-multiplier: 1.25
  upgrade-currency: credits
  base-upgrade-cost: 20.0
  upgrade-cost-multiplier: 1.15
```

{% hint style="warning" %}
`allowed-currencies` is a hard allowlist for AFK generation. Keep premium currencies, keys, and store currencies out unless AFK creation is deliberate.
{% endhint %}

Reward multipliers and attack intervals improve through `maximum-level`. `maximum-stage-reward-multiplier` caps the stage contribution so later content does not make AFK rewards unbounded.

## Cosmetic levels

Consecutive `cosmetic-upgrades` after the economic maximum can change glow color, attack animation, and optionally entity type. Cosmetic levels do not alter reward yield or attack speed.

## Menu and performance

`menus/afk-mob-upgrades.yml` controls the upgrade screen. AFK attacks share the normal engine attack and player-update budgets, so include expected AFK users in performance testing rather than tuning only active grinders.

Test permission denial, area entry, upgrade pricing, every allowed currency, economic level caps, cosmetic levels, reconnects, and the effect of later-stage multipliers.


# Armor

Dungeon armor is a persistent tier progression system configured in `content/armor.yml`. Players earn armor XP from successful dungeon swings, unlock tiers in sequence, equip cosmetic skins, and purchase optional upgrades.

## Global equipment and XP

```yaml
xp-per-swing: 2
equipment:
  helmet: LEATHER_HELMET
  chestplate: LEATHER_CHESTPLATE
  leggings: LEATHER_LEGGINGS
  boots: LEATHER_BOOTS
```

Leather equipment uses each tier or skin's configured dye. Other wearable materials retain their vanilla appearance.

## Armor tiers

```yaml
tiers:
  basic:
    order: 1
    display-name: 'Basic'
    styled-name: '&3&lBasic &b&lArmor'
    dye: '#269BD8'
    max-level: 25
    xp-base: 1500
    xp-growth: 1.05
    boost-per-level: 0.01
    boosts: { money: 0.10, souls: 0.10, essence: 0.10 }
```

Tier order must remain sequential. The number of tiers must fit the tier slots in `menus/armor.yml`. Boosts are additive: `0.10` adds ten percent, producing a `1.10x` total before other systems.

## Skins

The optional `skins` catalogue supports any number of administrator-defined entries:

```yaml
skins:
  enabled: true
  entries:
    winter:
      order: 1
      display-name: 'Winter'
      styled-name: '<aqua><bold>Winter Skin</bold></aqua>'
      permission: 'drakodungeons.armor.skin.winter'
      dye: '#7DEBFF'
      custom-model-data: 10019
      boosts:
        money: 0.05
        armor-xp: 0.05
        damage: 0.025
```

Permission access and persistent unlocks are independent. Grant a permanent unlock with:

```
/dga armorskin unlock <player> <skin>
```

Set `skins.enabled: false` to hide and disable skin selection without deleting stored selections.

## Upgrades

`upgrades.enabled` controls the complete upgrade menu. Each upgrade defines a currency, maximum level, base price, growth, and effect per level. The set multiplier also defines its active bonus and duration.

## Menus

* `menus/armor.yml` controls tier presentation.
* `menus/armor-skins.yml` controls cosmetic selection.
* `menus/armor-upgrades.yml` controls paid upgrades.

{% hint style="info" %}
After balance changes, test XP gain, tier transitions, equipment refresh, death, reconnect, skin permissions, and all configured reward currencies.
{% endhint %}


# Companions

Companions provide persistent, collectible boosts to one configured mob-kill currency. The complete catalogue and presentation are controlled by `content/companions.yml`.

## Enablement and bound item

```yaml
enabled: true
hotbar-slot: 1
prevent-drop: true
prevent-storage: true
keep-on-death: true
storage-item-material: TURTLE_EGG
storage-capacity: 300
equipped-slots: 3
maximum-equipped-slots: 6
```

The hotbar slot is zero-based. Players use the bound storage item to open storage and manage equipped companions. Transferable unlock scrolls can open additional slots up to the configured maximum:

```
/dga give <player> companion-scroll [amount]
```

## Companion Blocks

Each zone can have one protected Companion Block inside its arena. Place it with the setup tool or:

```
/dga setup <zone> companionblock
/dga setup <zone> clearcompanionblock
```

The `block-material`, `hologram`, and `particle` sections control the physical block and its presentation. Hologram lines support zone, price, currency, reward-currency, and block-material placeholders documented directly in the YAML.

## Price and boost scaling

```
egg price = base price
          × zone-price-multiplier ^ (zone order - 1)
          × stage-price-multiplier ^ (stage number - 1)

reward boost = base reward boost
             × zone-boost-multiplier ^ (zone order - 1)
             × stage-boost-multiplier ^ (stage number - 1)
```

`price.currency` pays for eggs. `reward-boost.currency` selects the one mob-kill wallet affected by equipped companions. Those currencies may be different.

Companion boosts are additive on top of the neutral `1x`. Three equipped companions worth `+0.5x`, `+0.75x`, and `+1x` produce `3.25x` of the configured reward.

## Rarities, qualities, and definitions

The `rarities` section controls display names, colors, and bulk-delete banner materials. The stable quality IDs are `normal`, `shiny`, `rainbow`, and `void`; admins can customize their names, colors, badges, and multipliers.

```yaml
companions:
  chicken:
    display-name: Chicken Companion
    rarity: basic
    icon: PLAYER_HEAD
    head-texture: 'http://textures.minecraft.net/texture/...'
    weight: 55.0
    base-reward-boost: '0.5'
```

Weights are relative across the enabled catalogue. The persistent variant key includes companion ID, zone, stage, quality, and fusion level. Renaming a stable ID creates a different variant.

## Progression and automation

`progression` configures upgrade currency, cost growth, rarity luck, quality chances, fusion limits and chance, fusion-level boost, and auto-open interval. Egg purchases are checked before charging, and automatic opening stops when payment or storage fails.

Players can set exact zone/stage auto-delete preferences. Rolled companions are still charged and resolved, then matching variants are deleted before entering storage.

## Follower display and performance

`display.enabled` is false by default. Enabling it creates protected real Armor Stand and Text Display entities behind each player—up to two entities per equipped slot. Load-test peak player counts before enabling this presentation. Storage, equipment, and reward boosts work while display is disabled.

Use `maximum-rebuilds-per-pass`, movement intervals, and structural reconciliation intervals to control burst cost.

## Menus

Companions use dedicated egg, storage, fusion, upgrade, single-delete, and bulk-delete menu files. Keep all required item IDs when redesigning those menus.


# Crystals

Crystals add enchant-specific boosts to Dungeon Sword sockets. Players can open Crystal Boxes, store crystals, improve apply chance with dust, tinker unwanted crystals, merge exceptional crystals, and buy permanent crystal upgrades.

All rules are configured in `content/crystals.yml`.

## Storage and sockets

```yaml
storage-capacity: 180
sword-xp-per-swing: 1.0
crystal-material: AMETHYST_SHARD
upgrade-currency: shards
socket-materials:
  equipped: END_CRYSTAL
  available: GRAY_DYE
  locked: RED_DYE
```

Storage capacity is independent of the menu page size. Available socket count comes from the milestones in `settings/gameplay.yml`, and the final count must match `menus/crystals.yml: items.crystal-slot.slots`.

## Crystal Box tiers

Each tier controls its item, eligible enchant sword-level range, apply chance, and boost range:

```yaml
box:
  default-tier: 1
  tiers:
    1:
      display-name: '&f&lTier I Crystal Box'
      material: LIGHT_GRAY_SHULKER_BOX
      minimum-enchant-sword-level: 1
      maximum-enchant-sword-level: 30
      minimum-apply-chance: 50
      maximum-apply-chance: 70
      minimum-boost: 3
      maximum-boost: 10
```

An empty eligible enchant pool prevents the box from opening. Enchants with a guaranteed base proc chance are omitted from normal crystal selection.

## Dust, tinkering, and merging

```yaml
dust:
  base-tinker-chance: 0.50
  normal-apply-increase: 5
  enchanted-apply-increase: 15

merge:
  base-success-chance: 0.25
  required-apply-chance: 100
  required-boost: 40
  merged-boost: 60
```

Normal Dust and Enchanted Dust improve apply chance. Merging requires two matching crystals that meet the configured thresholds. A failed merge follows the plugin's configured failure behavior and consumes the inputs.

## Permanent upgrades

The bundled upgrades improve Enchanted Dust luck, regular Dust luck, and merge success. Each has a maximum level, exponential price curve, and chance increase per level. All three use `upgrade-currency`.

## Admin commands

```
/dga give <player> crystal <enchant> [amount] [apply-chance] [boost] [merged]
/dga player <player> crystal-boxes <add|remove|set> <amount> [tier]
/dga player <player> stored-crystals <add|remove|set> <amount> <enchant|all> [apply-chance] [boost] [merged]
```

Use `/crystal` to test the complete player flow. Confirm socket capacity, duplicate-enchant rules, failed application, dust, bulk tinkering, storage paging, and merging.


# Currencies & Economy

Drako Dungeons stores profile-local wallets in `dungeons.db`. The current build uses the internal economy only; `settings/engine.yml: economy.money-provider` must remain `INTERNAL`.

## Currency catalogue

Currencies are declared in `content/currencies.yml`:

```yaml
schema-version: 1

currencies:
  money:
    display-name: '<green>Money</green>'
    symbol: '$'
    decimal-places: 2
  tokens:
    display-name: '<gold>Tokens</gold>'
    symbol: '✦'
    decimal-places: 0
    starting-balance: 0
    leaderboard: true
```

IDs are referenced by zones, rewards, purchases, abilities, armor, companions, setup scaling, and rebirth resets. `money`, `souls`, `essence`, and `shards` are core system wallets and must remain defined. Additional currency IDs can be added for custom content.

Optional currency fields include a starting balance, balance cap, and leaderboard availability. Values are rounded according to `decimal-places`.

## Referencing currencies

Use the stable lowercase ID, not the display name:

```yaml
purchase: { currency: money, amount: 25000 }
rewards:
  tokens: { amount: 3, chance: 0.10 }
```

Every reference is cross-validated. Removing or renaming a currency while another file still refers to it causes validation to fail.

## Admin balance commands

```
/dga player <player> <currency> add <amount>
/dga player <player> <currency> remove <amount>
/dga player <player> <currency> set <amount>
```

The target profile must be loaded. Negative results and values outside the supported range are rejected.

## Rebirth reset control

Currencies reset by rebirth are configured in `settings/gameplay.yml`:

```yaml
rebirth:
  reset:
    currencies: [money]
```

Use `currencies: []` to preserve every wallet. Every listed ID must exist in `content/currencies.yml`.

## PlaceholderAPI balances

With PlaceholderAPI installed, each configured currency exposes compact and raw values:

```
%drakodungeons_currency_money%
%drakodungeons_currency_money_raw%
%drakodungeons_currency_tokens%
```

The shorter `%drakodungeons_money%` form is also resolved for configured IDs, but the explicit `currency_<id>` format is recommended for clarity.


# Dungeon Sword

The Dungeon Sword is a bound progression item used for automatic dungeon combat. Players can receive or refresh it with `/dungeons sword`.

## Global sword rules

Edit `settings/gameplay.yml: sword`:

```yaml
sword:
  slot: 0
  base-critical-chance: 0.0
  base-critical-damage: 1.50
  souls-per-attack: 1.0
  xp-base: 250.0
  xp-growth: 1.001
  max-level: 10000
  crystal-slots:
    '1': 1
    '50': 2
    '100': 3
  prevent-drop: true
  prevent-storage: true
```

`slot` is zero-based. With storage prevention enabled, the sword is kept in that exact hotbar slot and cannot be moved into unsupported inventories.

XP required for level `N` is calculated as:

```
xp-base × xp-growth ^ (N - 1)
```

Crystal socket milestones must start at level 1 and increase. The final socket count must match the number of crystal socket slots in `menus/crystals.yml`.

## Skins and damage tiers

`content/swords.yml` defines the ordered skin catalogue. Each skin controls its display, material, optional custom model data, purchase currency, and damage tiers.

```yaml
skins:
  wooden:
    order: 1
    display-name: 'Wooden Sword'
    styled-name: '<gold><bold>Wooden Sword</bold></gold>'
    primary-colour: '<gold>'
    secondary-colour: '<yellow>'
    material: WOODEN_SWORD
    currency: money
    tiers:
      '1': { damage: 1, price: 0 }
      '2': { damage: 3, price: 1000 }
```

The order-1 skin and its first tier are always available to new players. Later tiers are bought in sequence, and a later skin requires the previous skin's progression.

## Sword Attributes

`content/sword-attributes.yml` defines sequential, rebirth-gated attribute levels. Each level can add:

* damage percent;
* enchant proc percent;
* Money, Souls, and Essence percent;
* attack-speed reduction in milliseconds.

The number of configured levels must fit the slots in `menus/sword-attributes.yml`, and rebirth requirements must strictly increase.

## Related presentation

* `menus/sword-skins.yml` controls skin selection.
* `menus/sword-tiers.yml` controls tier purchasing.
* `menus/sword-attributes.yml` controls attribute presentation.
* `menus/sword-settings.yml` controls player toggles.
* `language/messages/items.yml` controls sword name and lore fragments.

{% hint style="warning" %}
Always validate after changing socket counts, tier ordering, or attribute levels because those values are checked against menu capacity.
{% endhint %}


# Enchants

Each enchant is stored in its own file under `content/enchants/`. This keeps the catalogue easy to extend and lets validation report the exact file containing an error.

## Enchant schema

```yaml
schema-version: 1

enchants:
  speed:
    display-name: '<aqua><bold>Speed Enchant</bold></aqua>'
    description: 'Increase how fast the player walks.'
    category: SOUL
    required-sword-level: 1
    gui:
      slot: 11
      material: FEATHER
      primary-colour: '<aqua>'
      accent-colour: '<white>'
      name: '{primary}<bold>Speed {accent}<bold>Enchant'
      lore: ['{menu_template_lore}']
    max-level: 3
    activation-chance: { base: 1.0, per-level: 0.0, maximum: 1.0 }
    cost: { currency: souls, base: 100, multiplier: 10.0 }
    effects:
      movement_speed:
        type: WALK_SPEED
        parameters: { bonus-per-level: 0.10, maximum-bonus: 0.30 }
    visual:
      particle: CLOUD
      particle-count: 6
      sound: ENTITY_BREEZE_WIND_BURST
      volume: 0.35
      pitch: 1.5
      animation: SPEED
```

The map key (`speed`) is the persistent enchant ID. Keep it identical to the filename and do not change it after players own levels, toggles, crystals, or forced-proc state for that enchant.

## Categories and layout

Valid categories are `SOUL`, `ESSENCE`, and `SHARD`. The `gui.slot` must be one of the slots provided by `menus/sword-enchanter.yml: items.enchant.slots`. Slots explicitly assigned inside one category must not conflict.

`required-sword-level` controls when the enchant becomes purchasable. `cost.currency` must be a configured currency. The upgrade price grows from the base using the configured multiplier.

## Activation chance

Chance values are decimal probabilities:

```
chance at level = min(maximum, base + per-level × level adjustment)
```

The final effective rolled chance can also receive player bonuses, but is capped by `settings/gameplay.yml: rebirth.maximum-effective-enchant-proc-chance`. Guaranteed passive effects are not turned into rolled effects by that cap.

## Effect types

Supported runtime types include damage multipliers, flat damage boosts, currency multipliers, per-swing currency, sword XP, critical effects, multi-hit, explosions, chain attacks, execute, attack speed, walk speed, commands, damage-over-time, zone damage, and advanced abilities.

The safest way to add an enchant is to copy an existing file using the same effect type, assign a new stable ID and GUI slot, then adjust its values.

## Visual load

Particles, sounds, and animation types are configured per enchant. Large zone-wide effects can touch many private mobs, so keep `maximum-targets`, hit counts, and intervals bounded. Use reduced-effects player settings and the engine budgets when testing high-proc builds.

## Admin testing

```
/dga forceproc <player> <enchant>
/dga forceproc <player> clear
/dga testsword <player> --with-procs
```

Run `/dga validate` after every catalogue change. Test the enchant at minimum and maximum level before publishing it to players.


# Mobs Combat & Rewards

Dungeon mobs are private packet entities. They are rendered and interacted with like mobs, but they are not normal Bukkit entities and do not run vanilla AI. Each player receives an independent wave and target state.

## Mob definitions

Mobs may be declared once under the zone's `mobs` section or overridden inside an individual stage.

```yaml
mobs:
  cave_zombie:
    entity-type: ZOMBIE
    display-name: '<green>Cave Zombie</green>'
    rarity: COMMON
    boss: false
    max-health: 500
    selection-weight: 80
    sword-xp: 4
    respawn-delay-ticks: 20
    rewards:
      money: { min: 20, max: 35, chance: 1.0 }
      souls: { amount: 1, chance: 0.25 }
```

| Key                   | Meaning                                           |
| --------------------- | ------------------------------------------------- |
| `entity-type`         | Bukkit entity type used for the client-side model |
| `display-name`        | MiniMessage or `&`-formatted mob name             |
| `rarity`              | Base rarity label and glow selection              |
| `boss`                | Marks boss rewards, sounds, and presentation      |
| `max-health`          | Health before zone and stage multipliers          |
| `selection-weight`    | Relative chance when choosing from the pool       |
| `sword-xp`            | Sword XP awarded for the mob                      |
| `respawn-delay-ticks` | Optional per-mob replacement delay                |
| `rewards`             | Currency reward rolls paid on defeat              |

Reward entries use either a fixed `amount` or a `min` and `max` range. `chance` uses a decimal from `0.0` to `1.0`.

## Multipliers

Final health and rewards combine the base mob, zone multipliers, stage multipliers, rarity upgrades, and eligible player bonuses. Player bonuses can come from the sword, enchants, perks, crystals, armor, companions, rebirth upgrades, abilities, and registered integrations.

{% hint style="info" %}
Keep balance changes in YAML and test them with realistic player profiles. Large values are supported, but extreme multipliers can make holograms hard to read or create unexpectedly fast progression.
{% endhint %}

## Rarity upgrades

Each zone may contain `mob-lifecycle.upgrades` entries such as `rare`, `epic`, and `legendary`. An upgrade defines:

* the roll `chance`;
* a `health-multiplier`;
* per-currency `reward-multipliers`;
* a `display-name-format` using `{mob}`.

The client-only outline color for each rarity is configured separately in `settings/visuals.yml: visuals.mob-glows`.

## Wave lifecycle

`mob-lifecycle.respawn-all.interval-ticks` controls when an abandoned private wave expires. Ordinary defeated-mob replacement uses the mob-specific delay or the global `settings/engine.yml: engine.respawn-delay-ticks` default.

The engine selects targets inside `target-range` and releases them beyond `disengage-range`. Automatic sword attacks use `attack-interval-ticks` before player bonuses.

## Safe testing

```
/dga testsword [player]
/dga testsword [player] --with-procs
/dga testsword reset [player]
/dga forceproc <player> <enchant>
/dga testmob spawn [player]
/dga testmob remove [player]
```

The normal test sword suppresses procs for repeatable balance measurements. `--with-procs` is intended for stress testing. Reset the sword when testing is complete.


# Packet Block Themes

Packet-block themes let players sharing one physical arena see different cosmetic material palettes. The plugin sends sparse block changes through Paper's public packet API; it never changes the physical world and does not require ProtocolLib.

## Configure a theme

An area must have explicit bounds before it can define replacements:

```yaml
areas:
  shared_arena:
    bounds:
      minimum: 'world,-30,44,282,0,0'
      maximum: 'world,144,153,429,0,0'
    packet-blocks:
      replace-blocks:
        SAND: RED_SAND
        LIME_TERRACOTTA: ORANGE_TERRACOTTA
        SPRUCE_PLANKS: ACACIA_PLANKS
```

Material names may use Bukkit names or a `minecraft:` namespace. Source and target must be different block materials.

## Visual setup tool

After applying arena bounds in `/dga setup`:

{% stepper %}
{% step %}

## Select a source block

Right-click a physical source block inside the arena.
{% endstep %}

{% step %}

## Select a target appearance

Sneak-right-click any sample block with the desired target appearance.
{% endstep %}

{% step %}

## Remove a replacement

Punch a configured source block to remove its replacement.
{% endstep %}

{% step %}

## View the current selection

Right-click air to view the current selection and replacement count.
{% endstep %}
{% endstepper %}

Advanced direct commands are:

```
/dga setup <zone> packetblock <source> <target>
/dga setup <zone> packetblockremove <source>
```

## Safety validation

The physical server remains authoritative for collision, interaction, lighting, fluids, containers, redstone, and ticking. Validation rejects replacements that would make the client view disagree with server behavior.

It also rejects missing bounds, empty maps, unknown materials, sources absent from the live area, identical source and target, oversized areas, too many changed blocks, and configurations exceeding global theme or memory caps.

{% hint style="warning" %}
Use cosmetic full-block swaps with compatible behavior. Do not use themes to change geometry, doors, containers, fluids, redstone, or air/solid state.
{% endhint %}

## Engine limits

The `packet-blocks` section of `settings/engine.yml` controls:

* area and total captured volume;
* changes per theme and total retained changes;
* maximum theme count;
* capture blocks per tick;
* delivery blocks and sections per tick;
* maximum queued sections.

Lower per-tick budgets reduce pressure but make large preparation or delivery slower. Global caps reject unsafe configurations before publication.

## Commands and cache

```
/dga validate
/dga reload
/dga blocks status
/dga blocks rebuild
```

`validate` checks configuration but does not capture or publish world blocks. `reload` performs live capture, compilation, validation, and publication.

Captured bases are cached under `cache/packet-blocks/`. These files are disposable. To force a full recapture, stop the server and delete only that cache directory.

Coordinates used by an active compiled theme are protected from physical changes. Staff with `drakodungeons.packetblocks.bypass` may edit them deliberately; run `/dga reload` afterward to rebuild every affected palette.


# Perks

Perks are rolled Dungeon Sword bonuses defined in `content/perks.yml`. A player equips one rolled perk and can reroll the same perk through five effect levels.

## Global settings

```yaml
schema-version: 1
pity-threshold: 500
mass-roll:
  rolls-per-tick: 25
  maximum-rolls-per-invocation: 5000
  confirmation-window-millis: 700
ticket-currency: credits
ticket-prices:
  1: 50
  10: 400
  25: 750
```

* `pity-threshold` guarantees the configured pity behavior after repeated rolls.
* `ticket-currency` must exist in `content/currencies.yml`.
* `ticket-prices` maps bundle size to price and must match the bundle values shown by `menus/perks.yml`.
* Mass Roll is additionally protected by `drakodungeons.perks.mass-roll`.

{% hint style="warning" %}
`rolls-per-tick` is a global server budget shared by all active Mass Rolls. Increase it only after measuring tick time under concurrent use.
{% endhint %}

## Perk definitions

```yaml
perks:
  attack:
    display-name: '<green>Attack Perk</green>'
    description:
      - '<gray>Adds Dungeon Sword damage.'
    rarity: GREEN
    chance: 12.5
    material: PLAYER_HEAD
    head-texture: 'http://textures.minecraft.net/texture/...'
    effects:
      damage: [5, 10, 15, 20, 25]
```

`chance` is a relative weight. The service normalizes all configured weights at roll time. Each effect list contains the values for levels I through V.

### Supported effect keys

* `damage`
* `critical-damage`
* `money`, `shards`, `souls`, and `essence`
* `sword-xp`
* `enchant-proc`
* `attack-speed-millis`

Perks can combine several effects in one definition.

## Layout and administration

* `menus/perks.yml` controls rolling, ticket bundles, and navigation.
* `menus/perk-index.yml` controls the catalogue view.
* `menus/perk-confirmation.yml` controls the keep/continue result flow.

Admins can change tickets and pity progress with `/dga player`. Use tab completion to select the correct resource name and action.

{% hint style="info" %}
When balancing, compare the full level-V perk against sword tiers, armor, rebirth upgrades, and enchants. Percent values are bonuses, not standalone multipliers.
{% endhint %}


# Rebirth

Rebirth lets a player exchange configured Money progression for a persistent rebirth count and upgrade points. The rules are in `settings/gameplay.yml: rebirth`.

## Price curve

```yaml
rebirth:
  currency: money
  base-cost: 100000
  cost-growth: 1.01
  maximum-rebirths: 10000
```

The price for rebirth `N` is:

```
base-cost × cost-growth ^ (N - 1)
```

The configured currency must exist in `content/currencies.yml`.

## Configurable reset behavior

```yaml
rebirth:
  reset:
    currencies: [money]
    sword-skins: true
    zone-and-stage-progress: true
```

| Setting                   | Behavior                                                               |
| ------------------------- | ---------------------------------------------------------------------- |
| `currencies`              | Resets only the listed wallets to their configured starting balance    |
| `sword-skins`             | Removes purchased sword skin tiers and equips the order-1 default skin |
| `zone-and-stage-progress` | Returns progression to the starting zone and stage 1                   |

Use `currencies: []` to preserve all wallet balances. Setting either boolean to `false` preserves that part of progression.

{% hint style="info" %}
A successful rebirth always charges its price, even when every additional reset option is disabled. The profile update is committed atomically so a partial reset cannot be saved.
{% endhint %}

## Rebirth upgrades

The bundled upgrade tracks are:

* Soul multiplier;
* Essence multiplier;
* enchant proc chance;
* damage multiplier.

Each track defines `bonus-per-level` and `max-level`. One available rebirth point is consumed per purchased upgrade level. Keep the combined maximums aligned with the progression you intend players to complete.

## Sword Attributes

Rebirth count also unlocks levels in `content/sword-attributes.yml`. These are separate from spendable rebirth upgrade points: the configured `required-rebirth` gates each attribute level.

## Menus and commands

* `/rebirth` opens the rebirth menu.
* `menus/rebirth.yml` controls the purchase screen.
* `menus/rebirth-upgrades.yml` controls upgrades.
* `menus/rebirth-leaderboard.yml` controls leaderboard presentation.
* `/dga player <player> rebirths <add|remove|set> <amount>` changes the count within safe limits.

{% hint style="warning" %}
When testing reset options, use a disposable player profile and verify balances, sword skins, selected zone, unlocked stages, relocation, rebirth upgrades, and attribute access.
{% endhint %}


# Zones, Stages & Areas

Each file in `plugins/DrakoDungeons/zones/` defines one zone. Zones are ordered progression chapters, stages are the unlockable steps inside a zone, and areas are the physical arena regions used by those stages.

## Zone structure

```yaml
schema-version: 1
id: mines
order: 1
next-stage-sequence: 3
display-name: '<gold>Mines</gold>'
world: world
multipliers: { health: 1.0, money: 1.0, souls: 1.0, essence: 1.0 }

areas:
  main_arena:
    display-name: '<yellow>Main Arena</yellow>'
    entry: 'world,100.5,65,200.5,90,0'
    bounds:
      minimum: 'world,90,60,190,0,0'
      maximum: 'world,120,80,220,0,0'
    spawns:
      centre:
        position: 'world,105.5,65,200.5,-90,0'
        hitbox-width: 1.0
        hitbox-height: 2.2

stages:
  '1':
    id: stage_1
    display-name: '<green>Stage 1</green>'
    areas: [main_arena]
    health-multiplier: 1.0
    reward-multipliers: { money: 1.0 }
    purchase: { currency: money, amount: 0 }
    completion: { money-earned: 10000 }
    completion-rewards: { money: 1000, souls: 10 }
```

## Stable IDs and ordering

* `id` is the persistent zone key and should match the filename.
* `order` controls zone progression and menu ordering. Orders must be unique and consecutive.
* `display-name` can be changed without changing player ownership keys.
* `next-stage-sequence` allocates future stable stage IDs. Never lower it after deleting a stage.
* Stage map keys are numeric positions starting at `1`; each stage also has its own stable `id`.

The first zone in order should be selected by `settings/gameplay.yml: progression.starting-zone`.

## Areas

An area controls where a player arrives, where private mobs appear, and when a session is active.

* `entry` uses `world,x,y,z,yaw,pitch`.
* `bounds` defines a deliberate cuboid and is required for packet-block themes.
* `activation-radius` can be used for an unbounded starter area.
* `spawns` defines local mob points and optional hitbox overrides.
* `afk: true` marks the area as AFK-only and requires `drakodungeons.afk`.
* `packet-blocks` contains optional client-only material replacements.

Several stages may reuse an area, and one stage may list several areas. Several zones may intentionally reuse identical physical bounds when each zone should show a different packet-block palette.

## Stages

Stages must remain consecutive.

| Key                       | Meaning                                                   |
| ------------------------- | --------------------------------------------------------- |
| `id`                      | Stable stage ownership key                                |
| `areas`                   | Area IDs available to the stage                           |
| `purchase`                | Currency and amount needed to unlock the stage            |
| `completion.money-earned` | Gross Money earned in this stage before it completes      |
| `completion-rewards`      | One-time configured currency rewards                      |
| `health-multiplier`       | Multiplies stage mob health                               |
| `reward-multipliers`      | Per-currency stage reward multipliers                     |
| `mobs`                    | Optional stage-specific mob pool overriding the zone pool |

Completion tracks gross Money earned in the stage. Spending Money does not reduce recorded stage progress.

## Setup commands

Use `/dga setup` for routine editing. The visual workflow performs transactional writes, validates the result, and rolls back rejected changes.

Direct lifecycle commands include:

```
/dga setup createzone <id> [name]
/dga setup clonezone <source> <new-id>
/dga setup renamezone <id> <name>
/dga setup deletezone <id> confirm
/dga setup <zone> createstage <id> [name]
/dga setup <zone> renamestage <stage> <name>
/dga setup <zone> deletestage <stage> confirm
/dga setup <zone> stagearea <stage> <addarea|removearea> <area>
```


