mirror of
https://github.com/mivodev/mivo.git
synced 2026-01-26 05:25:42 +07:00
Initial Release v1.0.0: Full feature set with Docker automation, Nginx/Alpine stack
This commit is contained in:
240
app/Controllers/PublicStatusController.php
Normal file
240
app/Controllers/PublicStatusController.php
Normal file
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Controller;
|
||||
use App\Models\Config;
|
||||
use App\Config\SiteConfig;
|
||||
use App\Libraries\RouterOSAPI;
|
||||
use App\Helpers\EncryptionHelper;
|
||||
use App\Helpers\FormatHelper;
|
||||
|
||||
class PublicStatusController extends Controller {
|
||||
|
||||
// View: Show Search Page
|
||||
public function index($session) {
|
||||
// Just verify session existence to display Hotspot Name
|
||||
$configModel = new Config();
|
||||
$creds = $configModel->getSession($session);
|
||||
|
||||
if (!$creds) {
|
||||
// If session invalid, maybe show 404 or generic error
|
||||
echo "Session not found.";
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'session' => $session,
|
||||
'hotspot_name' => $creds['hotspot_name'] ?? 'Hotspot',
|
||||
'footer_text' => SiteConfig::getFooter()
|
||||
];
|
||||
|
||||
return $this->view('public/status', $data);
|
||||
}
|
||||
|
||||
// API: Check Status
|
||||
public function check($codeUrl = null) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Allow POST and GET
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $_SERVER['REQUEST_METHOD'] !== 'GET') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method Not Allowed']);
|
||||
return;
|
||||
}
|
||||
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
||||
|
||||
// Session: Try Body -> Try Header
|
||||
$session = $input['session'] ?? '';
|
||||
if (empty($session)) {
|
||||
$headers = getallheaders();
|
||||
// Handle case-insensitivity of headers
|
||||
$session = $headers['X-Mivo-Session'] ?? ($headers['x-mivo-session'] ?? '');
|
||||
}
|
||||
|
||||
// Code: Can be in URL or Body
|
||||
$code = $codeUrl ?? ($input['code'] ?? '');
|
||||
|
||||
if (empty($session) || empty($code)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Session and Voucher Code are required']);
|
||||
return;
|
||||
}
|
||||
|
||||
$configModel = new Config();
|
||||
$creds = $configModel->getSession($session);
|
||||
|
||||
if (!$creds) {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Session not found']);
|
||||
return;
|
||||
}
|
||||
|
||||
$password = $creds['password'];
|
||||
if (isset($creds['source']) && $creds['source'] === 'legacy') {
|
||||
$password = RouterOSAPI::decrypt($password);
|
||||
}
|
||||
|
||||
$api = new RouterOSAPI();
|
||||
if (!$api->connect($creds['ip'], $creds['user'], $password)) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Router Connection Failed']);
|
||||
return;
|
||||
}
|
||||
|
||||
// Logic Refactor: Pivot to User Table as primary source for Voucher Details
|
||||
// 1. Check User in Database
|
||||
$user = $api->comm("/ip/hotspot/user/print", [
|
||||
"?name" => $code
|
||||
]);
|
||||
|
||||
if (!empty($user)) {
|
||||
$u = $user[0];
|
||||
|
||||
// DEBUG: Log the user data to see raw values
|
||||
error_log("Status Debug: " . json_encode($u));
|
||||
|
||||
// --- SECURITY CHECK: Hide Unused Vouchers ---
|
||||
$uptimeRaw = $u['uptime'] ?? '0s';
|
||||
$bytesIn = intval($u['bytes-in'] ?? 0);
|
||||
$bytesOut = intval($u['bytes-out'] ?? 0);
|
||||
|
||||
if (($uptimeRaw === '0s' || empty($uptimeRaw)) && ($bytesIn + $bytesOut) === 0) {
|
||||
$api->disconnect();
|
||||
echo json_encode(['success' => false, 'message' => 'Voucher Not Found']);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- SECURITY CHECK: Hide Unlimited Members ---
|
||||
$limitBytes = isset($u['limit-bytes-total']) ? intval($u['limit-bytes-total']) : 0;
|
||||
$limitUptime = $u['limit-uptime'] ?? '0s';
|
||||
|
||||
if ($limitBytes === 0 && ($limitUptime === '0s' || empty($limitUptime))) {
|
||||
// Option: Allow checking them but show minimalistic info, or hide.
|
||||
// Sticking to original logic: Hide them.
|
||||
$api->disconnect();
|
||||
echo json_encode(['success' => false, 'message' => 'Voucher Not Found']);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- CALCULATIONS ---
|
||||
$dataUsed = $bytesIn + $bytesOut;
|
||||
$dataLeft = 'Unlimited';
|
||||
|
||||
if ($limitBytes > 0) {
|
||||
$remaining = max(0, $limitBytes - $dataUsed);
|
||||
$dataLeft = ($remaining === 0) ? '0 B' : FormatHelper::formatBytes($remaining);
|
||||
}
|
||||
|
||||
// Validity Logic
|
||||
$validityRaw = $u['limit-uptime'] ?? '0s';
|
||||
$validityDisplay = ($validityRaw === '0s') ? 'Unlimited' : FormatHelper::elapsedTime($validityRaw);
|
||||
$expiration = '-';
|
||||
|
||||
$comment = strtolower($u['comment'] ?? '');
|
||||
if (preg_match('/exp\W+([a-z]{3}\/\d{2}\/\d{4}\s\d{2}:\d{2}:\d{2})/', $comment, $matches)) {
|
||||
$expiration = $matches[1];
|
||||
} elseif ($validityRaw !== '0s') {
|
||||
$totalSeconds = FormatHelper::parseDuration($validityRaw);
|
||||
$usedSeconds = FormatHelper::parseDuration($uptimeRaw);
|
||||
$remainingSeconds = max(0, $totalSeconds - $usedSeconds);
|
||||
|
||||
if ($remainingSeconds > 0) {
|
||||
$expiration = date('d M Y H:i', time() + $remainingSeconds);
|
||||
} else {
|
||||
$expiration = 'Expired';
|
||||
}
|
||||
}
|
||||
|
||||
// BASE STATUS
|
||||
$status = 'offline';
|
||||
$statusLabel = 'Valid / Offline';
|
||||
$isDisabled = ($u['disabled'] ?? 'false') === 'true';
|
||||
|
||||
// Calculate Time Left
|
||||
$timeLeft = 'Unlimited';
|
||||
if ($expiration !== '-' && $expiration !== 'Expired') {
|
||||
$expTime = strtotime($expiration);
|
||||
if ($expTime) {
|
||||
$rem = max(0, $expTime - time());
|
||||
$timeLeft = ($rem === 0) ? 'Expired' : FormatHelper::formatSeconds($rem);
|
||||
}
|
||||
} elseif ($validityRaw !== '0s') {
|
||||
$totalSeconds = FormatHelper::parseDuration($validityRaw);
|
||||
$usedSeconds = FormatHelper::parseDuration($uptimeRaw);
|
||||
$rem = max(0, $totalSeconds - $usedSeconds);
|
||||
$timeLeft = ($rem === 0) ? 'Expired' : FormatHelper::formatSeconds($rem);
|
||||
}
|
||||
|
||||
if (strpos($comment, 'exp') !== false || ($expiration === 'Expired')) {
|
||||
$status = 'expired';
|
||||
$statusLabel = 'Expired';
|
||||
} elseif ($limitBytes > 0 && $dataUsed >= $limitBytes) {
|
||||
$status = 'limited';
|
||||
$statusLabel = 'Quota Exceeded';
|
||||
} elseif ($isDisabled) {
|
||||
$status = 'locked';
|
||||
$statusLabel = 'Locked / Disabled';
|
||||
}
|
||||
|
||||
// 2. CHECK ACTIVE OVERRIDE
|
||||
// If user is conceptually valid (or even if limited?), check if they are currently active
|
||||
// Because they might be active BUT expiring soon, or active BUT over quota (if server hasn't kicked them yet)
|
||||
$active = $api->comm("/ip/hotspot/active/print", [
|
||||
"?user" => $code
|
||||
]);
|
||||
|
||||
if (!empty($active)) {
|
||||
$status = 'active';
|
||||
$statusLabel = 'Active (Online)';
|
||||
}
|
||||
|
||||
$data = [
|
||||
'status' => $status,
|
||||
'status_label' => $statusLabel,
|
||||
'username' => $u['name'] ?? 'Unknown',
|
||||
'profile' => $u['profile'] ?? 'default',
|
||||
'uptime_used' => FormatHelper::elapsedTime($uptimeRaw),
|
||||
'validity' => $validityDisplay,
|
||||
'data_used' => FormatHelper::formatBytes($dataUsed),
|
||||
'data_left' => $dataLeft,
|
||||
'expiration' => $expiration,
|
||||
'time_left' => $timeLeft,
|
||||
'comment' => $u['comment'] ?? '',
|
||||
];
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $data]);
|
||||
$api->disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Fallback: Check Active Only (Trial Users or IP Bindings not in User Table)
|
||||
$active = $api->comm("/ip/hotspot/active/print", [
|
||||
"?user" => $code
|
||||
]);
|
||||
|
||||
if (!empty($active)) {
|
||||
$u = $active[0];
|
||||
$data = [
|
||||
'status' => 'active',
|
||||
'status_label' => 'Active (Online)',
|
||||
'username' => $u['user'] ?? 'Unknown',
|
||||
'profile' => '-', // Active usually doesn't have profile name directly unless queried
|
||||
'uptime_used' => FormatHelper::elapsedTime($u['uptime'] ?? '0s'),
|
||||
'validity' => '-',
|
||||
'data_used' => FormatHelper::formatBytes(intval($u['bytes-in'] ?? 0) + intval($u['bytes-out'] ?? 0)),
|
||||
'data_left' => 'Unknown',
|
||||
'time_left' => isset($u['session-time-left']) ? FormatHelper::elapsedTime($u['session-time-left']) : '-',
|
||||
'expiration' => '-',
|
||||
'comment' => ''
|
||||
];
|
||||
echo json_encode(['success' => true, 'data' => $data]);
|
||||
$api->disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
$api->disconnect();
|
||||
echo json_encode(['success' => false, 'message' => 'Voucher Not Found']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user