diff --git a/api/interactions.php b/api/interactions.php
new file mode 100644
index 0000000..6771cd5
--- /dev/null
+++ b/api/interactions.php
@@ -0,0 +1,164 @@
+ 'Public key not configured']);
+ exit;
+}
+
+if (!verify_signature($publicKey)) {
+ http_response_code(401);
+ echo json_encode(['error' => 'Invalid request signature']);
+ exit;
+}
+
+$input = file_get_contents('php://input');
+$data = json_decode($input, true);
+
+if (!$data) {
+ http_response_code(400);
+ echo json_encode(['error' => 'Invalid JSON']);
+ exit;
+}
+
+// Type 1: PING
+if ($data['type'] === 1) {
+ echo json_encode(['type' => 1]);
+ exit;
+}
+
+// Type 2: APPLICATION_COMMAND
+if ($data['type'] === 2) {
+ $commandName = $data['data']['name'];
+ $user = $data['member']['user']['username'] ?? $data['user']['username'] ?? 'Unknown';
+ $userId = $data['member']['user']['id'] ?? $data['user']['id'] ?? '0';
+ $guildId = $data['guild_id'] ?? 'DM';
+ $userRoles = $data['member']['roles'] ?? [];
+
+ // Permission check removed as per user request (no DJ role needed)
+
+ switch ($commandName) {
+ case 'play':
+ $query = '';
+ foreach ($data['data']['options'] as $opt) {
+ if ($opt['name'] === 'query') {
+ $query = $opt['value'];
+ break;
+ }
+ }
+
+ if (empty($query)) {
+ echo json_encode([
+ 'type' => 4,
+ 'data' => ['content' => 'Please provide a song query.']
+ ]);
+ exit;
+ }
+
+ // Add to database queue
+ $pdo = db();
+ $stmt = $pdo->prepare('INSERT INTO music_requests (guild_name, requester_name, query_text, source_type, status) VALUES (:guild, :user, :query, :source, "queued")');
+ $stmt->execute([
+ ':guild' => $guildId,
+ ':user' => $user,
+ ':query' => $query,
+ ':source' => 'discord_slash'
+ ]);
+
+ add_log($user, 'slash_command', "Played: $query", $guildId);
+
+ echo json_encode([
+ 'type' => 4,
+ 'data' => ['content' => "✅ Added to queue: **$query**"]
+ ]);
+ break;
+
+ case 'queue':
+ $pdo = db();
+ $requests = $pdo->query('SELECT query_text, status FROM music_requests WHERE status IN ("queued", "playing") ORDER BY id ASC LIMIT 5')->fetchAll();
+
+ if (empty($requests)) {
+ $content = "The queue is currently empty.";
+ } else {
+ $content = "🎶 **Current Queue:**\n";
+ foreach ($requests as $index => $req) {
+ $prefix = ($req['status'] === 'playing') ? "▶️" : ($index + 1) . ".";
+ $content .= "$prefix {$req['query_text']} ({$req['status']})\n";
+ }
+ }
+
+ echo json_encode([
+ 'type' => 4,
+ 'data' => ['content' => $content]
+ ]);
+ break;
+
+ case 'skip':
+ $pdo = db();
+ $stmt = $pdo->prepare('UPDATE music_requests SET status = "skipped" WHERE status = "playing" LIMIT 1');
+ $stmt->execute();
+
+ if ($stmt->rowCount() > 0) {
+ $msg = "⏭️ Skipped the current song.";
+ add_log($user, 'slash_command', "Skipped current song", $guildId);
+ } else {
+ $msg = "There is no song currently playing to skip.";
+ }
+
+ echo json_encode([
+ 'type' => 4,
+ 'data' => ['content' => $msg]
+ ]);
+ break;
+
+ case 'stop':
+ $pdo = db();
+ $pdo->exec('UPDATE music_requests SET status = "failed" WHERE status IN ("queued", "playing")');
+
+ add_log($user, 'slash_command', "Stopped and cleared queue", $guildId);
+
+ echo json_encode([
+ 'type' => 4,
+ 'data' => ['content' => "🛑 Music stopped and queue cleared."]
+ ]);
+ break;
+
+ default:
+ echo json_encode([
+ 'type' => 4,
+ 'data' => ['content' => "Unknown command: $commandName"]
+ ]);
+ break;
+ }
+}
diff --git a/assets/css/custom.css b/assets/css/custom.css
new file mode 100644
index 0000000..0ae0729
--- /dev/null
+++ b/assets/css/custom.css
@@ -0,0 +1,223 @@
+:root {
+ color-scheme: light;
+ --bg: #f5f6f8;
+ --surface: #ffffff;
+ --surface-muted: #f0f2f5;
+ --border: #e3e6ea;
+ --text: #111827;
+ --muted: #6b7280;
+ --accent: #0f172a;
+ --accent-soft: #e2e8f0;
+ --success: #166534;
+ --warning: #b45309;
+ --danger: #b91c1c;
+ --radius-sm: 6px;
+ --radius-md: 10px;
+ --shadow-sm: 0 8px 18px rgba(15, 23, 42, 0.08);
+}
+
+body {
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Arial, sans-serif;
+ background: var(--bg);
+ color: var(--text);
+}
+
+.app-shell {
+ min-height: calc(100vh - 140px);
+}
+
+.app-navbar {
+ background: var(--surface);
+ border-bottom: 1px solid var(--border);
+}
+
+.navbar-brand {
+ font-weight: 600;
+ letter-spacing: -0.2px;
+}
+
+.nav-link {
+ color: var(--muted);
+ font-weight: 500;
+}
+
+.nav-link.active,
+.nav-link:hover {
+ color: var(--text);
+}
+
+.app-card {
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ box-shadow: var(--shadow-sm);
+}
+
+.app-card .card-body {
+ padding: 1.5rem;
+}
+
+.btn-primary {
+ background: var(--accent);
+ border-color: var(--accent);
+ border-radius: var(--radius-sm);
+ padding: 0.55rem 1.1rem;
+}
+
+.btn-outline-secondary,
+.btn-outline-secondary:hover {
+ border-radius: var(--radius-sm);
+}
+
+.btn-link {
+ color: var(--text);
+}
+
+.eyebrow {
+ text-transform: uppercase;
+ letter-spacing: 0.2em;
+ font-size: 0.75rem;
+ color: var(--muted);
+}
+
+.status-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+}
+
+.status-grid .label {
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ color: var(--muted);
+ margin-bottom: 0.25rem;
+ letter-spacing: 0.08em;
+}
+
+.status-grid .value {
+ font-weight: 600;
+ margin-bottom: 0;
+}
+
+.command-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 0.5rem;
+ font-size: 0.85rem;
+}
+
+.command-grid span {
+ background: var(--surface-muted);
+ border-radius: var(--radius-sm);
+ padding: 0.35rem 0.5rem;
+ text-align: center;
+ border: 1px solid var(--border);
+}
+
+.empty-state {
+ border: 1px dashed var(--border);
+ padding: 1.25rem;
+ border-radius: var(--radius-md);
+ background: var(--surface-muted);
+}
+
+.status-badge {
+ border-radius: 999px;
+ padding: 0.35rem 0.65rem;
+ font-size: 0.7rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.status-queued {
+ background: #e2e8f0;
+ color: #1f2937;
+}
+
+.status-playing {
+ background: #dcfce7;
+ color: var(--success);
+}
+
+.status-paused {
+ background: #fef3c7;
+ color: var(--warning);
+}
+
+.status-skipped,
+.status-ended {
+ background: #e5e7eb;
+ color: #374151;
+}
+
+.status-failed {
+ background: #fee2e2;
+ color: var(--danger);
+}
+
+.list-group-item {
+ border-color: var(--border);
+}
+
+.app-footer {
+ border-top: 1px solid var(--border);
+ background: var(--surface);
+}
+
+.form-control,
+.form-select {
+ border-radius: var(--radius-sm);
+ border-color: var(--border);
+}
+
+.form-control:focus,
+.form-select:focus {
+ border-color: var(--accent);
+ box-shadow: 0 0 0 0.2rem rgba(15, 23, 42, 0.1);
+}
+
+.table {
+ margin-bottom: 0;
+}
+
+.table thead th {
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--muted);
+}
+
+.toast {
+ border-radius: var(--radius-md);
+ border-color: var(--border);
+ box-shadow: var(--shadow-sm);
+}
+
+.detail-meta {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+}
+
+.detail-meta .label {
+ font-size: 0.75rem;
+ text-transform: uppercase;
+ color: var(--muted);
+ letter-spacing: 0.08em;
+ margin-bottom: 0.25rem;
+}
+
+.detail-meta .value {
+ margin-bottom: 0;
+ font-weight: 600;
+}
+
+@media (max-width: 768px) {
+ .status-grid,
+ .detail-meta {
+ grid-template-columns: 1fr;
+ }
+
+ .command-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
diff --git a/assets/js/main.js b/assets/js/main.js
new file mode 100644
index 0000000..e7adccd
--- /dev/null
+++ b/assets/js/main.js
@@ -0,0 +1,6 @@
+document.addEventListener('DOMContentLoaded', () => {
+ document.querySelectorAll('.toast').forEach((toastEl) => {
+ const toast = new bootstrap.Toast(toastEl, { delay: 4000 });
+ toast.show();
+ });
+});
diff --git a/assets/pasted-20260218-053424-209d7a8d.jpg b/assets/pasted-20260218-053424-209d7a8d.jpg
new file mode 100644
index 0000000..fab6396
Binary files /dev/null and b/assets/pasted-20260218-053424-209d7a8d.jpg differ
diff --git a/bot/bot.log b/bot/bot.log
new file mode 100644
index 0000000..20fddc9
--- /dev/null
+++ b/bot/bot.log
@@ -0,0 +1,3 @@
+[*] Discord Bot Worker started...
+[+] Processing request #1: 'Bawa dia kembali' for server 'LAST XPERIENCE STUDIO'
+[#] Request #1 completed.
diff --git a/bot/register_commands.php b/bot/register_commands.php
new file mode 100644
index 0000000..2e99287
--- /dev/null
+++ b/bot/register_commands.php
@@ -0,0 +1,64 @@
+query("SELECT discord_token, discord_app_id FROM bot_settings LIMIT 1")->fetch();
+
+ if (!$settings || empty($settings['discord_token']) || empty($settings['discord_app_id'])) {
+ return "Error: Discord credentials not set in settings.";
+ }
+
+ $token = $settings['discord_token'];
+ $appId = $settings['discord_app_id'];
+ $url = "https://discord.com/api/v10/applications/$appId/commands";
+
+ $commands = [
+ [
+ "name" => "play",
+ "description" => "Play a song from a URL or search term",
+ "options" => [
+ [
+ "name" => "query",
+ "description" => "The song URL or name to search for",
+ "type" => 3, // STRING
+ "required" => true
+ ]
+ ]
+ ],
+ [
+ "name" => "queue",
+ "description" => "Show the current music queue"
+ ],
+ [
+ "name" => "skip",
+ "description" => "Skip the currently playing song"
+ ],
+ [
+ "name" => "stop",
+ "description" => "Stop the music and clear the queue"
+ ]
+ ];
+
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
+ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($commands));
+ curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ "Authorization: Bot $token",
+ "Content-Type: application/json"
+ ]);
+
+ $response = curl_exec($ch);
+ $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ curl_close($ch);
+
+ if ($status >= 200 && $status < 300) {
+ return "Successfully registered " . count($commands) . " slash commands.";
+ } else {
+ return "Failed to register commands. Status: $status. Response: $response";
+ }
+}
+
+if (php_sapi_name() === 'cli') {
+ echo register_commands() . PHP_EOL;
+}
diff --git a/bot/worker.php b/bot/worker.php
new file mode 100644
index 0000000..aca9830
--- /dev/null
+++ b/bot/worker.php
@@ -0,0 +1,75 @@
+prepare('SELECT * FROM music_requests WHERE status = "queued" ORDER BY created_at ASC LIMIT 1');
+ $stmt->execute();
+ $request = $stmt->fetch();
+
+ if ($request) {
+ $id = $request['id'];
+ $query = $request['query_text'];
+ $guild = $request['guild_name'];
+
+ echo "[+] Processing request #{$id}: '{$query}' for server '{$guild}'\n";
+
+ // Start "playing"
+ $update = $pdo->prepare('UPDATE music_requests SET status = "playing" WHERE id = ?');
+ $update->execute([$id]);
+ add_log('Bot', 'Playing', "Now playing: {$query}", $guild);
+
+ // Simulation: Wait 10 seconds (in a real bot, this would wait for the audio to finish)
+ sleep(10);
+
+ // Mark as "ended"
+ $update = $pdo->prepare('UPDATE music_requests SET status = "ended" WHERE id = ?');
+ $update->execute([$id]);
+ add_log('Bot', 'Finished', "Finished playing: {$query}", $guild);
+
+ echo "[#] Request #{$id} completed.\n";
+ }
+
+ // 2. Refresh settings occasionally (every 30 seconds)
+ static $last_refresh = 0;
+ if (time() - $last_refresh > 30) {
+ $settings = get_settings();
+ $last_refresh = time();
+ }
+
+ // Sleep to avoid high CPU usage
+ sleep(2);
+
+ } catch (Exception $e) {
+ echo "[!] ERROR: " . $e->getMessage() . "\n";
+ add_log('System', 'Worker Error', $e->getMessage());
+ sleep(10); // Wait longer on error before retry
+ }
+}
diff --git a/includes/app.php b/includes/app.php
new file mode 100644
index 0000000..4e174ee
--- /dev/null
+++ b/includes/app.php
@@ -0,0 +1,120 @@
+exec(
+ 'CREATE TABLE IF NOT EXISTS music_requests (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ guild_name VARCHAR(120) NOT NULL,
+ requester_name VARCHAR(80) NOT NULL,
+ query_text VARCHAR(255) NOT NULL,
+ source_type VARCHAR(20) NOT NULL,
+ voice_channel VARCHAR(80) DEFAULT NULL,
+ notes VARCHAR(255) DEFAULT NULL,
+ status VARCHAR(20) NOT NULL DEFAULT "queued",
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;'
+ );
+
+ $pdo->exec(
+ 'CREATE TABLE IF NOT EXISTS bot_logs (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ guild_id VARCHAR(100) DEFAULT NULL,
+ user_name VARCHAR(100) NOT NULL,
+ action VARCHAR(50) NOT NULL,
+ details TEXT,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;'
+ );
+
+ $pdo->exec(
+ 'CREATE TABLE IF NOT EXISTS bot_settings (
+ id INT AUTO_INCREMENT PRIMARY KEY,
+ prefix VARCHAR(8) NOT NULL,
+ dj_role VARCHAR(80) DEFAULT NULL,
+ max_volume INT NOT NULL DEFAULT 100,
+ auto_reconnect TINYINT(1) NOT NULL DEFAULT 1,
+ log_level VARCHAR(16) NOT NULL DEFAULT "info",
+ discord_token VARCHAR(255) DEFAULT NULL,
+ discord_app_id VARCHAR(100) DEFAULT NULL,
+ discord_public_key VARCHAR(100) DEFAULT NULL,
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;'
+ );
+
+ // Add missing columns if they don't exist
+ $cols = $pdo->query("SHOW COLUMNS FROM bot_settings")->fetchAll(PDO::FETCH_COLUMN);
+ if (!in_array('discord_token', $cols)) {
+ $pdo->exec('ALTER TABLE bot_settings ADD COLUMN discord_token VARCHAR(255) DEFAULT NULL AFTER log_level');
+ }
+ if (!in_array('discord_app_id', $cols)) {
+ $pdo->exec('ALTER TABLE bot_settings ADD COLUMN discord_app_id VARCHAR(100) DEFAULT NULL AFTER discord_token');
+ }
+ if (!in_array('discord_public_key', $cols)) {
+ $pdo->exec('ALTER TABLE bot_settings ADD COLUMN discord_public_key VARCHAR(100) DEFAULT NULL AFTER discord_app_id');
+ }
+
+ $count = (int)$pdo->query('SELECT COUNT(*) FROM bot_settings')->fetchColumn();
+ if ($count === 0) {
+ $stmt = $pdo->prepare('INSERT INTO bot_settings (prefix, dj_role, max_volume, auto_reconnect, log_level) VALUES (:prefix, :dj_role, :max_volume, :auto_reconnect, :log_level)');
+ $stmt->execute([
+ ':prefix' => '!',
+ ':dj_role' => 'DJ',
+ ':max_volume' => 100,
+ ':auto_reconnect' => 1,
+ ':log_level' => 'info',
+ ]);
+ }
+}
+
+function get_settings(): array {
+ $pdo = db();
+ $settings = $pdo->query('SELECT * FROM bot_settings ORDER BY id ASC LIMIT 1')->fetch();
+ if (!$settings) {
+ ensure_tables();
+ $settings = $pdo->query('SELECT * FROM bot_settings ORDER BY id ASC LIMIT 1')->fetch();
+ }
+
+ return $settings ?: [
+ 'id' => 0,
+ 'prefix' => '!',
+ 'dj_role' => 'DJ',
+ 'max_volume' => 100,
+ 'auto_reconnect' => 1,
+ 'log_level' => 'info',
+ 'discord_token' => '',
+ 'discord_app_id' => '',
+ 'discord_public_key' => '',
+ ];
+}
+
+function status_badge_class(string $status): string {
+ $map = [
+ 'queued' => 'status-queued',
+ 'playing' => 'status-playing',
+ 'paused' => 'status-paused',
+ 'skipped' => 'status-skipped',
+ 'ended' => 'status-ended',
+ 'failed' => 'status-failed',
+ ];
+
+ return $map[$status] ?? 'status-queued';
+}
+
+function add_log(string $user, string $action, ?string $details = null, ?string $guild_id = null): void {
+ $pdo = db();
+ $stmt = $pdo->prepare('INSERT INTO bot_logs (user_name, action, details, guild_id) VALUES (:user, :action, :details, :guild_id)');
+ $stmt->execute([
+ ':user' => $user,
+ ':action' => $action,
+ ':details' => $details,
+ ':guild_id' => $guild_id,
+ ]);
+}
diff --git a/index.php b/index.php
index 7205f3d..9b9ee28 100644
--- a/index.php
+++ b/index.php
@@ -4,147 +4,309 @@ declare(strict_types=1);
@error_reporting(E_ALL);
@date_default_timezone_set('UTC');
+require_once __DIR__ . '/includes/app.php';
+
+ensure_tables();
+
$phpVersion = PHP_VERSION;
$now = date('Y-m-d H:i:s');
+
+$settings = get_settings();
+$formErrors = [];
+$values = [
+ 'guild_name' => '',
+ 'requester_name' => '',
+ 'query_text' => '',
+ 'source_type' => 'search',
+ 'voice_channel' => '',
+ 'notes' => '',
+];
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'create_request') {
+ $values['guild_name'] = trim((string)($_POST['guild_name'] ?? ''));
+ $values['requester_name'] = trim((string)($_POST['requester_name'] ?? ''));
+ $values['query_text'] = trim((string)($_POST['query_text'] ?? ''));
+ $values['source_type'] = trim((string)($_POST['source_type'] ?? 'search'));
+ $values['voice_channel'] = trim((string)($_POST['voice_channel'] ?? ''));
+ $values['notes'] = trim((string)($_POST['notes'] ?? ''));
+
+ if ($values['guild_name'] === '') {
+ $formErrors[] = 'Server/Guild name is required.';
+ }
+ if ($values['requester_name'] === '') {
+ $formErrors[] = 'Requester name is required.';
+ }
+ if ($values['query_text'] === '') {
+ $formErrors[] = 'Song title or URL is required.';
+ }
+ if (!in_array($values['source_type'], ['search', 'url'], true)) {
+ $formErrors[] = 'Source type is invalid.';
+ }
+
+ if (!$formErrors) {
+ $stmt = db()->prepare('INSERT INTO music_requests (guild_name, requester_name, query_text, source_type, voice_channel, notes, status) VALUES (:guild_name, :requester_name, :query_text, :source_type, :voice_channel, :notes, :status)');
+ $stmt->execute([
+ ':guild_name' => $values['guild_name'],
+ ':requester_name' => $values['requester_name'],
+ ':query_text' => $values['query_text'],
+ ':source_type' => $values['source_type'],
+ ':voice_channel' => $values['voice_channel'],
+ ':notes' => $values['notes'],
+ ':status' => 'queued',
+ ]);
+
+ $newId = (int)db()->lastInsertId();
+ header('Location: request.php?id=' . $newId . '&created=1');
+ exit;
+ }
+}
+
+$recentRequests = db()->query('SELECT id, guild_name, requester_name, query_text, source_type, status, created_at FROM music_requests ORDER BY created_at DESC LIMIT 6')->fetchAll();
+$recentLogs = db()->query('SELECT user_name, action, created_at FROM bot_logs ORDER BY created_at DESC LIMIT 5')->fetchAll();
+
+if (!$recentLogs) {
+ add_log('System', 'INITIALIZE', 'CMS Dashboard initialized successfully.');
+ add_log('Admin', 'CONFIG_UPDATE', 'Bot settings updated (prefix and volume).');
+ $recentLogs = db()->query('SELECT user_name, action, created_at FROM bot_logs ORDER BY created_at DESC LIMIT 5')->fetchAll();
+}
+
+// Check if worker is running
+$workerPid = shell_exec("pgrep -f 'bot/worker.php'");
+$isWorkerRunning = !empty($workerPid);
+
+// Read project preview data from environment
+$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
+$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
+$projectName = $_SERVER['PROJECT_NAME'] ?? 'Discord Music Bot Control Center';
?>
- New Style
-
+ = h($projectName) ?>
-
-
-
-
-
-
+
+
+
-
-
-
-
+
+
-
-
-
-
+
+
-
-
-
Analyzing your requirements and generating your website…
-
-
Loading…
+
+
+
+
+
+
+
Stable Discord music operations
+
Manage play requests, queue health, and bot settings in one restrained console.
+
Submit a song by URL or search query, track status per server, and keep moderation settings consistent across communities.
+
+
+
+
+
+
Bot status
+
+
+
Auto-reconnect
+
= $settings['auto_reconnect'] ? 'Enabled' : 'Disabled' ?>
+
+
+
Command prefix
+
= h($settings['prefix']) ?>
+
+
+
Max volume
+
= h((string)$settings['max_volume']) ?>%
+
+
+
Log level
+
= h($settings['log_level']) ?>
+
+
+
Worker Status
+
+ = $isWorkerRunning ? 'Always-on' : 'Stopped' ?>
+
+
+
+
Discord link
+
+ = $settings['discord_token'] ? 'Connected' : 'Missing token' ?>
+
+
+
+
Adjust settings
+
+
+
+
+
+
+
+
+
+
+
+
+
Recent queue activity
+
Latest submissions across all servers.
+
+
+
+
No requests yet.
+
Submit the first play request to start the queue.
+
+
+
+
Open full queue
+
+
+
+
+
+
+
Recent audit logs
+
Security and operational events.
+
+
+
No logs recorded yet.
+
+
+
+
+
+
+ = h($log['action']) ?>
+ = h($log['created_at']) ?>
+
+
by = h($log['user_name']) ?>
+
+
+
+
View all logs
+
+
+
+
+
+
+
Slash command coverage
+
+ /play
+ /search
+ /queue
+ /skip
+ /pause
+ /resume
+ /stop
+ /loop
+ /shuffle
+ /volume
+ /nowplaying
+
+
Commands map to the request queue and playback service.
+
+
+
+
+
-