getenv('REDIS_HOST') ?: 'redis']); // Router $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $method = $_SERVER['REQUEST_METHOD']; // API: Queue if ($method === 'POST' && $uri === '/api/queue') { $data = json_decode(file_get_contents('php://input'), true); if (!$data || !isset($data['username'], $data['message'])) { http_response_code(400); echo json_encode(['error' => 'Missing username or message']); exit; } // Normalize Username (Add @ if missing and not numeric/phone) $username = trim($data['username']); if (!empty($username) && !is_numeric($username) && $username[0] !== '+' && $username[0] !== '@') { $username = '@' . $username; } $data['username'] = $username; $jobId = uniqid('job_'); $data['id'] = $jobId; $data['status'] = 'queued'; $data['created_at'] = date('Y-m-d H:i:s'); // Store status $redis->hmset("job:$jobId", $data); // Push to processing queue $redis->rpush('call_queue', json_encode($data)); // Push to history list (for display) $redis->lpush('job_history', $jobId); echo json_encode(['job_id' => $jobId, 'status' => 'queued', 'normalized_username' => $username]); exit; } // API: Jobs History (Paginated) if ($method === 'GET' && $uri === '/api/jobs') { $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; $limit = isset($_GET['limit']) ? max(1, min(100, (int)$_GET['limit'])) : 20; $start = ($page - 1) * $limit; $end = $start + $limit - 1; $total = $redis->llen('job_history'); $ids = $redis->lrange('job_history', $start, $end); $jobs = []; foreach ($ids as $id) { $job = $redis->hgetall("job:$id"); if ($job) { $jobs[] = $job; } } echo json_encode([ 'jobs' => $jobs, 'total' => $total, 'page' => $page, 'limit' => $limit, 'total_pages' => ceil($total / $limit) ]); exit; } // API: Preview Audio if ($method === 'POST' && $uri === '/api/preview') { $data = json_decode(file_get_contents('php://input'), true); if (!$data || !isset($data['message'])) { http_response_code(400); echo json_encode(['error' => 'Missing message']); exit; } try { require_once __DIR__ . '/TTS.php'; $path = TTS::generate($data['message'], 'es'); $filename = basename($path); echo json_encode(['url' => "/data/$filename"]); } catch (\Throwable $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } exit; } // API: Status if ($method === 'GET' && $uri === '/api/status') { $jobId = $_GET['id'] ?? null; if (!$jobId) { http_response_code(400); echo json_encode(['error' => 'Missing id']); exit; } $status = $redis->hgetall("job:$jobId"); echo json_encode($status ?: ['status' => 'not_found']); exit; } // API: Telegram Status if ($method === 'GET' && $uri === '/api/me') { $status = $redis->get('telegram:status') ?: 'unknown'; $me = json_decode($redis->get('telegram:me') ?: '{}', true); echo json_encode(['status' => $status, 'me' => $me]); exit; } // API: Legacy Send Message (Compatibility) if ($method === 'GET' && $uri === '/sendmessage') { $user = $_GET['user'] ?? null; $message = $_GET['message'] ?? null; $audioParam = $_GET['audio'] ?? null; if (!$user || !$message) { echo "Error: Missing user or message"; exit; } // Normalize Username (Add @ if missing and not numeric/phone) $user = trim($user); if (!empty($user) && !is_numeric($user) && $user[0] !== '+' && $user[0] !== '@') { $user = '@' . $user; } $audio = 0; if ($audioParam && strtolower($audioParam) === 'yes') { $audio = 1; } $jobId = uniqid('job_'); $data = [ 'id' => $jobId, 'username' => $user, // Map 'user' to 'username' as used internally 'message' => $message, 'audio_enabled' => ($audio === 1), 'status' => 'queued', 'created_at' => date('Y-m-d H:i:s') ]; // Store status $redis->hmset("job:$jobId", $data); // Push to processing queue $redis->rpush('call_queue', json_encode($data)); // Push to history list $redis->lpush('job_history', $jobId); // Return legacy format echo "Inserted {$user} - {$message} - {$audio}"; exit; } // API: Logout if ($method === 'POST' && $uri === '/api/logout') { touch('/app/data/logout.signal'); echo json_encode(['status' => 'logout_requested']); exit; } // API: Save Config if ($method === 'POST' && $uri === '/api/config') { $data = json_decode(file_get_contents('php://input'), true); if (!isset($data['api_id'], $data['api_hash'], $data['phone'])) { http_response_code(400); echo json_encode(['error' => 'Missing api_id, api_hash, or phone']); exit; } file_put_contents('/app/data/config.json', json_encode([ 'api_id' => trim($data['api_id']), 'api_hash' => trim($data['api_hash']), 'phone' => trim($data['phone']), 'call_timeout' => isset($data['call_timeout']) ? (int)$data['call_timeout'] : 15 ])); // Force worker restart to pick up new config touch('/app/data/logout.signal'); echo json_encode(['status' => 'config_saved']); exit; } // API: Reset Config if ($method === 'POST' && $uri === '/api/reset') { if (file_exists('/app/data/config.json')) { unlink('/app/data/config.json'); } // Force worker restart (which will then see missing config) touch('/app/data/logout.signal'); echo json_encode(['status' => 'reset_complete']); exit; } // Frontend: Dashboard if ($method === 'GET' && ($uri === '/' || $uri === '/index.php')) { include 'dashboard_view.php'; exit; } // Frontend: Login UI if ($method === 'GET' && $uri === '/login') { include 'login_view.php'; exit; } // Frontend: Login Logic if ($method === 'POST' && $uri === '/login') { // This is complex because MP interactive login is stateful. // We will use a dedicated session API wrapper or just basic calls if possible. // For simplicity, we try to Instantiate MP and check state. // Load Config if (!file_exists('/app/data/config.json')) { echo json_encode(['status' => 'error', 'message' => 'Config missing']); exit; } $config = json_decode(file_get_contents('/app/data/config.json'), true); $settings = new \danog\MadelineProto\Settings(); $settings->setAppInfo((new \danog\MadelineProto\Settings\AppInfo()) ->setApiId((int)$config['api_id']) ->setApiHash($config['api_hash']) ); $MadelineProto = new API('/app/data/session.madeline', $settings); $phone = $_POST['phone'] ?? $config['phone'] ?? null; $code = $_POST['code'] ?? null; $password = $_POST['password'] ?? null; try { if ($phone && !$code && !$password) { // Create lock file to pause worker touch('/app/data/login.lock'); $MadelineProto->phoneLogin($phone); echo json_encode(['status' => 'code_requested']); } elseif ($code) { $authorization = $MadelineProto->completePhoneLogin($code); // If we are here, login was successful (otherwise exception) unlink('/app/data/login.lock'); echo json_encode(['status' => 'success']); } elseif ($password) { $authorization = $MadelineProto->complete2faLogin($password); unlink('/app/data/login.lock'); echo json_encode(['status' => 'success']); } else { echo json_encode(['status' => 'error', 'message' => 'Invalid Request']); } } catch (\Throwable $e) { // Remove lock on error so worker can resume or user can retry if (file_exists('/app/data/login.lock')) { unlink('/app/data/login.lock'); } echo json_encode(['status' => 'error', 'message' => $e->getMessage()]); } exit; }