- DiscordBotProject: new project type (discord-bot), auto-detects from main.py + cogs/ - botcontrol API: start/stop/restart/status via sudo systemctl - botconfig API: atomic read/write of config.json; CRUD for streamers, RSS feeds, Mastodon accounts, and LinkedIn pages; tokens masked in GET responses - botlogs API: tail log file (path via project_settings.bot_log_file) - Tab partials: _tab_bot (process control), _tab_botconfig (structured editor), _tab_logs (log viewer with auto-refresh) - view.php: register bot/botconfig/logs tabs with labels, icons, and groups - app.js: bot control panel, config editor with account/page dropdowns in RSS modal, Mastodon accounts CRUD, LinkedIn pages CRUD; audit labels/icons for all new actions - CLAUDE.md: document Discord Bot integration, sudoers requirement, API actions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
67 lines
2.4 KiB
PHP
67 lines
2.4 KiB
PHP
<?php
|
|
header('Content-Type: application/json');
|
|
|
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
|
|
$project_id = (int)(($method === 'GET' ? $_GET : (json_decode(file_get_contents('php://input'), true) ?? []))['project_id'] ?? 0);
|
|
|
|
$stmt = $db->prepare('SELECT * FROM projects WHERE id = ? AND is_active = 1');
|
|
$stmt->execute([$project_id]);
|
|
$project = $stmt->fetch();
|
|
if (!$project) { http_response_code(404); echo json_encode(['error' => 'Project not found']); exit; }
|
|
|
|
// Service name from project settings (default: bashybot)
|
|
$psStmt = $db->prepare('SELECT value FROM project_settings WHERE project_id = ? AND key = ?');
|
|
$psStmt->execute([$project_id, 'bot_service_name']);
|
|
$row = $psStmt->fetch();
|
|
$service = $row ? $row['value'] : 'bashybot';
|
|
$service = preg_replace('/[^a-zA-Z0-9_\-\.]/', '', $service); // sanitize
|
|
|
|
if ($method === 'GET') {
|
|
$action = $_GET['action'] ?? 'status';
|
|
if ($action !== 'status') { http_response_code(400); echo json_encode(['error' => 'Unknown action']); exit; }
|
|
|
|
exec('sudo systemctl is-active ' . escapeshellarg($service) . ' 2>&1', $activeOut, $activeCode);
|
|
$isActive = trim(implode('', $activeOut)) === 'active';
|
|
|
|
exec('sudo systemctl show ' . escapeshellarg($service) . ' --property=ActiveEnterTimestamp --value 2>&1', $tsOut);
|
|
$since = trim(implode('', $tsOut));
|
|
|
|
echo json_encode([
|
|
'active' => $isActive,
|
|
'status' => trim(implode('', $activeOut)),
|
|
'since' => $since,
|
|
'service' => $service,
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
if ($method !== 'POST') { http_response_code(405); echo json_encode(['error' => 'Method not allowed']); exit; }
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
$action = $input['action'] ?? '';
|
|
|
|
$allowed = ['start', 'stop', 'restart'];
|
|
if (!in_array($action, $allowed, true)) {
|
|
http_response_code(400);
|
|
echo json_encode(['error' => 'Unknown action']);
|
|
exit;
|
|
}
|
|
|
|
$output = [];
|
|
$exit_code = 0;
|
|
exec('sudo systemctl ' . $action . ' ' . escapeshellarg($service) . ' 2>&1', $output, $exit_code);
|
|
|
|
Audit::log($db, 'bot_' . $action, $project_id, $service);
|
|
|
|
// Return updated status after action
|
|
exec('sudo systemctl is-active ' . escapeshellarg($service) . ' 2>&1', $activeOut);
|
|
$isActive = trim(implode('', $activeOut)) === 'active';
|
|
|
|
echo json_encode([
|
|
'ok' => $exit_code === 0,
|
|
'action' => $action,
|
|
'output' => implode("\n", $output),
|
|
'active' => $isActive,
|
|
'status' => trim(implode('', $activeOut)),
|
|
]);
|