Files
Siro/backend/bot/standalone_worker.php
2026-06-30 02:33:51 +03:00

881 lines
36 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// ============================================================================
// backend/bot/standalone_worker.php
// Standalone database-free / Redis-free Task Server & Dashboard for Android Scraper Bot
// ============================================================================
define('TASKS_FILE', __DIR__ . '/tasks.json');
define('RESULTS_FILE', __DIR__ . '/results.json');
define('SECRET_KEY', 'SIRO_BOT_SUPER_SECRET_123'); // Matches WorkerClient.kt secret key
// Ensure JSON data files exist
if (!file_exists(TASKS_FILE)) {
file_put_contents(TASKS_FILE, json_encode([]));
}
if (!file_exists(RESULTS_FILE)) {
file_put_contents(RESULTS_FILE, json_encode([]));
}
// ----------------------------------------------------------------------------
// Helper Security functions
// ----------------------------------------------------------------------------
function validateSignature($device_id, $ts, $sig, $secret_key) {
// Prevent replay attacks (valid for 15 minutes to allow debugging tolerance)
if (abs(time() - $ts) > 900) {
return false;
}
// Generate the expected signature
$expected_sig = hash_hmac('sha256', $device_id . $ts, $secret_key);
// Secure comparison
return hash_equals($expected_sig, $sig);
}
function jsonError($message, $code = 400) {
http_response_code($code);
echo json_encode(['status' => 'failure', 'message' => $message]);
exit;
}
// ----------------------------------------------------------------------------
// API Request Routing
// ----------------------------------------------------------------------------
$method = $_SERVER['REQUEST_METHOD'];
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
// Check if request is API call from the Bot
$isApiCall = false;
$device_id = null;
$ts = null;
$sig = null;
if ($method === 'GET' && isset($_GET['device_id'], $_GET['ts'], $_GET['sig'])) {
$isApiCall = true;
$device_id = $_GET['device_id'];
$ts = intval($_GET['ts']);
$sig = $_GET['sig'];
} elseif ($method === 'POST') {
// Check if JSON body has device credentials
$rawInput = file_get_contents('php://input');
$input = json_decode($rawInput, true);
if ($input && isset($input['device_id'], $input['ts'], $input['sig'])) {
$isApiCall = true;
$device_id = $input['device_id'];
$ts = intval($input['ts']);
$sig = $input['sig'];
}
}
if ($isApiCall) {
header('Content-Type: application/json; charset=UTF-8');
// Validate signature
if (!validateSignature($device_id, $ts, $sig, SECRET_KEY)) {
jsonError("Unauthorized device signature or expired timestamp. Device ID: $device_id, ts: $ts", 401);
}
if ($method === 'GET') {
// Dequeue one task for this bot (FIFO)
$tasks = json_decode(file_get_contents(TASKS_FILE), true);
if (count($tasks) > 0) {
$task = array_shift($tasks);
file_put_contents(TASKS_FILE, json_encode($tasks, JSON_PRETTY_PRINT));
echo json_encode([
"status" => "success",
"has_task" => true,
"task" => $task
]);
} else {
echo json_encode([
"status" => "success",
"has_task" => false
]);
}
exit;
} elseif ($method === 'POST') {
// Record Scrape result
if (empty($input['task_id']) || empty($input['status'])) {
jsonError("Missing required parameters in payload");
}
$results = json_decode(file_get_contents(RESULTS_FILE), true);
$newResult = [
'task_id' => $input['task_id'],
'device_id' => $device_id,
'status' => $input['status'], // 'success' or 'failed'
'type' => $input['type'] ?? 'price_check',
'recorded_at' => date('Y-m-d H:i:s'),
'result_data' => $input['result_data'] ?? []
];
array_unshift($results, $newResult);
file_put_contents(RESULTS_FILE, json_encode($results, JSON_PRETTY_PRINT));
echo json_encode([
"status" => "success",
"message" => "Result recorded successfully"
]);
exit;
}
}
// ----------------------------------------------------------------------------
// Admin Dashboard UI (Rendered on normal GET browser requests)
// ----------------------------------------------------------------------------
// Handle Admin Actions
$message = '';
$msgType = 'success';
if (isset($_POST['action'])) {
if ($_POST['action'] === 'add_task') {
$app = $_POST['app'] ?? '';
$start_loc = $_POST['start_location'] ?? '';
$end_loc = $_POST['end_location'] ?? '';
$start_lat = floatval($_POST['start_lat'] ?? 0.0);
$start_lng = floatval($_POST['start_lng'] ?? 0.0);
$end_lat = floatval($_POST['end_lat'] ?? 0.0);
$end_lng = floatval($_POST['end_lng'] ?? 0.0);
if ($app && $start_loc && $end_loc) {
$tasks = json_decode(file_get_contents(TASKS_FILE), true);
$taskId = "prc_" . uniqid();
$newTask = [
"task_id" => $taskId,
"type" => "price_check",
"app" => $app,
"start_location" => $start_loc,
"end_location" => $end_loc,
"payload" => [
"start_lat" => $start_lat,
"start_lng" => $start_lng,
"end_lat" => $end_lat,
"end_lng" => $end_lng
]
];
$tasks[] = $newTask;
file_put_contents(TASKS_FILE, json_encode($tasks, JSON_PRETTY_PRINT));
$message = "Task successfully added and queued! Task ID: $taskId";
} else {
$message = "Please fill in all required fields.";
$msgType = 'error';
}
} elseif ($_POST['action'] === 'generate_10_trips') {
$app = $_POST['app'] ?? 'com.taxif.passenger';
$ammanLocations = [
['name' => 'Abdoun', 'lat' => 31.9392, 'lng' => 35.8942],
['name' => 'Jabal Amman', 'lat' => 31.9511, 'lng' => 35.9189],
['name' => 'Sweileh', 'lat' => 32.0167, 'lng' => 35.8333],
['name' => 'Khalda', 'lat' => 31.9861, 'lng' => 35.8450],
['name' => 'Al-Jubaiha', 'lat' => 32.0194, 'lng' => 35.8753],
['name' => 'Tla Al-Ali', 'lat' => 31.9961, 'lng' => 35.8647],
['name' => 'Shmeisani', 'lat' => 31.9680, 'lng' => 35.9020],
['name' => 'Um Uthaina', 'lat' => 31.9610, 'lng' => 35.8770],
['name' => 'Marj Al-Hamam', 'lat' => 31.9000, 'lng' => 35.8500],
['name' => 'Al-Muqabalain', 'lat' => 31.8720, 'lng' => 35.8900],
['name' => 'Al-Qweismeh', 'lat' => 31.8900, 'lng' => 35.9200],
['name' => 'Hashmi Al-Janoubi', 'lat' => 31.9350, 'lng' => 35.9350],
['name' => 'Al-Madina', 'lat' => 31.8500, 'lng' => 35.8000],
['name' => 'Sports City', 'lat' => 31.9820, 'lng' => 35.8880],
];
// 10 trip pairs with varied distances (~2km to ~17km)
$tripPairs = [
[13, 5], // Sports City → Tla Al-Ali (~2km)
[6, 0], // Shmeisani → Abdoun (~3km)
[7, 1], // Um Uthaina → Jabal Amman (~4km)
[3, 13], // Khalda → Sports City (~5km)
[4, 2], // Al-Jubaiha → Sweileh (~5km)
[0, 8], // Abdoun → Marj Al-Hamam (~6km)
[1, 10], // Jabal Amman → Al-Qweismeh (~9km)
[6, 9], // Shmeisani → Al-Muqabalain (~11km)
[2, 12], // Sweileh → Al-Madina (~17km)
[5, 11], // Tla Al-Ali → Hashmi (~5km)
];
$tasks = json_decode(file_get_contents(TASKS_FILE), true);
$count = 0;
foreach ($tripPairs as $i => $pair) {
$start = $ammanLocations[$pair[0]];
$end = $ammanLocations[$pair[1]];
$taskId = "prc_" . uniqid();
$newTask = [
'task_id' => $taskId,
'type' => 'price_check',
'app' => $app,
'start_location' => $start['name'],
'end_location' => $end['name'],
'payload' => [
'start_lat' => $start['lat'],
'start_lng' => $start['lng'],
'end_lat' => $end['lat'],
'end_lng' => $end['lng'],
],
];
$tasks[] = $newTask;
$count++;
}
file_put_contents(TASKS_FILE, json_encode($tasks, JSON_PRETTY_PRINT));
$message = "Generated $count standard tasks (one per trip) for TaxiF!";
} elseif ($_POST['action'] === 'clear_tasks') {
file_put_contents(TASKS_FILE, json_encode([]));
$message = "Task queue cleared successfully.";
} elseif ($_POST['action'] === 'clear_results') {
file_put_contents(RESULTS_FILE, json_encode([]));
$message = "Scrape results history cleared successfully.";
}
}
// Fetch stats and lists for display
$currentTasks = json_decode(file_get_contents(TASKS_FILE), true);
$scrapedResults = json_decode(file_get_contents(RESULTS_FILE), true);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Siro Bot - Standalone Server Control Panel</title>
<!-- Premium Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Space+Grotesk:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color: #0b0f19;
--card-bg: rgba(22, 28, 45, 0.45);
--border-color: rgba(255, 255, 255, 0.08);
--accent-color: #3b82f6;
--accent-gradient: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
--text-color: #f3f4f6;
--text-muted: #9ca3af;
--success-color: #10b981;
--error-color: #ef4444;
--glass-blur: blur(12px);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Outfit', sans-serif;
background-color: var(--bg-color);
background-image:
radial-gradient(at 10% 10%, rgba(29, 78, 216, 0.15) 0px, transparent 50%),
radial-gradient(at 90% 90%, rgba(16, 185, 129, 0.1) 0px, transparent 50%);
color: var(--text-color);
min-height: 100vh;
padding: 2rem 1rem;
line-height: 1.5;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
/* Header design */
header {
margin-bottom: 2.5rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border-color);
padding-bottom: 1.5rem;
}
.logo-section h1 {
font-family: 'Space Grotesk', sans-serif;
font-size: 2.2rem;
font-weight: 700;
background: linear-gradient(to right, #3b82f6, #60a5fa, #10b981);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: -0.5px;
}
.logo-section p {
color: var(--text-muted);
font-size: 0.95rem;
margin-top: 0.25rem;
}
.badge-status {
background-color: rgba(16, 185, 129, 0.15);
border: 1px solid var(--success-color);
color: var(--success-color);
padding: 0.4rem 0.8rem;
border-radius: 9999px;
font-size: 0.85rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.5rem;
box-shadow: 0 0 15px rgba(16, 185, 129, 0.2);
}
.status-dot {
width: 8px;
height: 8px;
background-color: var(--success-color);
border-radius: 50%;
display: inline-block;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { transform: scale(0.9); opacity: 0.6; }
50% { transform: scale(1.2); opacity: 1; }
100% { transform: scale(0.9); opacity: 0.6; }
}
/* Notification alert */
.alert {
padding: 1rem;
border-radius: 12px;
margin-bottom: 2rem;
font-weight: 500;
font-size: 0.95rem;
display: flex;
justify-content: space-between;
align-items: center;
animation: fadeIn 0.4s ease-out;
}
.alert-success {
background-color: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.3);
color: #34d399;
}
.alert-error {
background-color: rgba(239, 68, 68, 0.1);
border: 1px solid rgba(239, 68, 68, 0.3);
color: #f87171;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Grid Dashboard Layout */
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 2rem;
}
@media (max-width: 900px) {
.dashboard-grid {
grid-template-columns: 1fr;
}
}
.card {
background: var(--card-bg);
border: 1px solid var(--border-color);
backdrop-filter: var(--glass-blur);
-webkit-backdrop-filter: var(--glass-blur);
border-radius: 20px;
padding: 1.8rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
box-shadow: 0 15px 35px rgba(59, 130, 246, 0.05);
}
.card-title {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.3rem;
font-weight: 700;
margin-bottom: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
color: #ffffff;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
padding-bottom: 0.75rem;
}
.card-subtitle {
font-size: 0.85rem;
color: var(--text-muted);
font-weight: normal;
}
/* Form Inputs */
.form-group {
margin-bottom: 1.25rem;
}
label {
display: block;
font-size: 0.85rem;
font-weight: 600;
color: var(--text-muted);
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
input[type="text"], select {
width: 100%;
padding: 0.8rem 1rem;
background: rgba(10, 15, 30, 0.6);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: #fff;
font-family: inherit;
font-size: 0.95rem;
transition: border-color 0.2s, box-shadow 0.2s;
}
input[type="text"]:focus, select:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 0.85rem 1.5rem;
background: var(--accent-gradient);
border: none;
border-radius: 10px;
color: #fff;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
transition: opacity 0.2s, transform 0.2s, box-shadow 0.2s;
box-shadow: 0 4px 15px rgba(29, 78, 216, 0.4);
}
.btn:hover {
opacity: 0.9;
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(29, 78, 216, 0.55);
}
.btn:active {
transform: translateY(1px);
}
.btn-secondary {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #e5e7eb;
box-shadow: none;
font-weight: 600;
padding: 0.5rem 1rem;
font-size: 0.85rem;
border-radius: 8px;
width: auto;
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
color: #fff;
box-shadow: none;
transform: none;
}
/* List components */
.list-container {
display: flex;
flex-direction: column;
gap: 1rem;
max-height: 500px;
overflow-y: auto;
padding-right: 0.5rem;
}
/* Scrollbar styling */
.list-container::-webkit-scrollbar {
width: 6px;
}
.list-container::-webkit-scrollbar-track {
background: transparent;
}
.list-container::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.1);
border-radius: 10px;
}
.list-item {
background: rgba(10, 15, 30, 0.4);
border: 1px solid rgba(255, 255, 255, 0.03);
border-radius: 12px;
padding: 1rem 1.2rem;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.2s;
}
.list-item:hover {
background: rgba(10, 15, 30, 0.6);
}
.task-info h4 {
font-size: 1rem;
font-weight: 600;
color: #fff;
margin-bottom: 0.25rem;
display: flex;
align-items: center;
gap: 0.6rem;
}
.task-info p {
font-size: 0.85rem;
color: var(--text-muted);
}
.badge-app {
font-size: 0.75rem;
font-weight: 700;
padding: 0.2rem 0.5rem;
border-radius: 6px;
text-transform: uppercase;
}
.app-yallago { background: rgba(59, 130, 246, 0.15); color: #60a5fa; border: 1px solid rgba(59, 130, 246, 0.3); }
.app-zaken { background: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.3); }
.app-tufaddal { background: rgba(245, 158, 11, 0.15); color: #fbbf24; border: 1px solid rgba(245, 158, 11, 0.3); }
.app-careem { background: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.3); }
.app-uber { background: rgba(255, 255, 255, 0.1); color: #f3f4f6; border: 1px solid rgba(255, 255, 255, 0.2); }
.app-taxif { background: rgba(239, 68, 68, 0.15); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.3); }
.app-jeeny { background: rgba(139, 92, 246, 0.15); color: #a78bfa; border: 1px solid rgba(139, 92, 246, 0.3); }
.time-badge {
font-size: 0.80rem;
color: var(--text-muted);
background: rgba(255, 255, 255, 0.05);
padding: 0.25rem 0.6rem;
border-radius: 6px;
font-family: 'Space Grotesk', sans-serif;
}
.result-success { border-left: 4px solid var(--success-color); }
.result-failed { border-left: 4px solid var(--error-color); }
.price-text {
font-family: 'Space Grotesk', sans-serif;
font-size: 1.15rem;
font-weight: 700;
color: #fff;
}
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--text-muted);
font-style: italic;
}
.tabs-header {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
}
.tab-btn {
background: none;
border: none;
color: var(--text-muted);
font-family: inherit;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
padding: 0.5rem 0.25rem;
border-bottom: 2px solid transparent;
transition: color 0.2s, border-color 0.2s;
}
.tab-btn.active {
color: #fff;
border-bottom-color: var(--accent-color);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="logo-section">
<h1 id="main-title">Siro Bot Control Center</h1>
<p>Standalone API Endpoint & Simulation Controller Panel</p>
</div>
<div class="badge-status" id="server-status">
<span class="status-dot"></span>
Server Online
</div>
</header>
<?php if ($message): ?>
<div class="alert alert-<?php echo $msgType; ?>" id="info-alert">
<span><?php echo htmlspecialchars($message); ?></span>
<button class="btn-secondary" onclick="document.getElementById('info-alert').style.display='none'">Dismiss</button>
</div>
<?php endif; ?>
<div class="dashboard-grid">
<!-- Left Side: Task Generator Form -->
<div class="card">
<h2 class="card-title">
Task Generator
</h2>
<form method="POST" action="">
<input type="hidden" name="action" value="add_task">
<div class="form-group">
<label for="app">Competitor Application</label>
<select name="app" id="app" required>
<option value="ae.com.yalla.go.dubai.client">YallaGo (ae.com.yalla.go.dubai.client)</option>
<option value="com.zakinn.app">Zaken / Zakinn (com.zakinn.app)</option>
<option value="com.bis.taxi">Tfadal (com.bis.taxi)</option>
<option value="com.careem.acma">Careem (com.careem.acma)</option>
<option value="com.ubercab">Uber (com.ubercab)</option>
<option value="com.taxif.passenger">TaxiF (com.taxif.passenger)</option>
<option value="me.com.easytaxi">Jeeny (me.com.easytaxi)</option>
</select>
</div>
<div class="form-group">
<label for="start_location">Start Location (Text hint)</label>
<input type="text" name="start_location" id="start_location" placeholder="e.g. Abdoun, Amman" required value="Abdoun">
</div>
<div class="form-group">
<label for="end_location">End Location (Text hint)</label>
<input type="text" name="end_location" id="end_location" placeholder="e.g. Queen Alia Airport, Amman" required value="Queen Alia Airport">
</div>
<div class="form-row">
<div class="form-group">
<label for="start_lat">Start Latitude</label>
<input type="text" name="start_lat" id="start_lat" placeholder="31.9392" value="31.9392">
</div>
<div class="form-group">
<label for="start_lng">Start Longitude</label>
<input type="text" name="start_lng" id="start_lng" placeholder="35.8942" value="35.8942">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="end_lat">End Latitude</label>
<input type="text" name="end_lat" id="end_lat" placeholder="31.7225" value="31.7225">
</div>
<div class="form-group">
<label for="end_lng">End Longitude</label>
<input type="text" name="end_lng" id="end_lng" placeholder="35.9892" value="35.9892">
</div>
</div>
<button type="submit" class="btn" id="submit-task-btn">Queue Scraping Task</button>
</form>
<!-- Batch generator form -->
<form method="POST" action="" style="margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border-color);">
<input type="hidden" name="action" value="generate_10_trips">
<input type="hidden" name="app" value="com.taxif.passenger">
<h3 style="font-size:1rem; font-weight:600; margin-bottom:0.75rem; color:#fff;">Generate 10 Amman Trips</h3>
<p style="font-size:0.85rem; color:var(--text-muted); margin-bottom:1rem;">
Queues 10 separate standard tasks (one per trip) with varied distances (217 km) for TaxiF.
The bot processes each trip individually through normal polling.
</p>
<button type="submit" class="btn" id="batch-btn" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); box-shadow: 0 4px 15px rgba(16, 185, 129, 0.4);">
Generate 10 Amman Trips
</button>
</form>
</div>
<!-- Right Side: Lists Dashboard -->
<div class="card">
<div class="tabs-header">
<button class="tab-btn active" onclick="switchTab('tasks')">Pending Tasks (<?php echo count($currentTasks); ?>)</button>
<button class="tab-btn" onclick="switchTab('results')">Scrape History (<?php echo count($scrapedResults); ?>)</button>
</div>
<!-- Tab 1: Tasks Queue -->
<div id="tasks-tab" class="tab-content active">
<div class="card-title" style="border:none; margin-bottom:1rem; font-size:1rem; color:var(--text-muted);">
<span>Queued task list (Sent to bot via GET request)</span>
<?php if (count($currentTasks) > 0): ?>
<form method="POST" style="margin: 0;">
<input type="hidden" name="action" value="clear_tasks">
<button type="submit" class="btn-secondary" id="clear-tasks-btn">Clear All</button>
</form>
<?php endif; ?>
</div>
<div class="list-container">
<?php if (count($currentTasks) === 0): ?>
<div class="empty-state">No pending tasks. Queue some tasks using the form on the left!</div>
<?php else: ?>
<?php foreach ($currentTasks as $task):
$app = $task['app'] ?? '';
if ($app === 'ae.com.yalla.go.dubai.client') {
$appLabel = 'yallago';
} elseif ($app === 'com.zakinn.app') {
$appLabel = 'zaken';
} elseif ($app === 'com.bis.taxi') {
$appLabel = 'tufaddal';
} elseif ($app === 'com.careem.acma') {
$appLabel = 'careem';
} elseif ($app === 'com.ubercab') {
$appLabel = 'uber';
} elseif ($app === 'com.taxif.passenger') {
$appLabel = 'taxif';
} elseif ($app === 'me.com.easytaxi') {
$appLabel = 'jeeny';
} else {
$appLabel = 'unknown';
}
?>
<div class="list-item">
<div class="task-info">
<h4>
<span class="badge-app app-<?php echo $appLabel; ?>"><?php echo htmlspecialchars($appLabel); ?></span>
<span><?php echo htmlspecialchars($task['task_id']); ?></span>
</h4>
<?php
$isBatch = ($task['type'] ?? '') === 'batch_multi_trip';
if ($isBatch && isset($task['trips'])) {
$tripCount = count($task['trips']);
$firstTrip = $task['trips'][0];
echo '<p>Batch: <strong>' . $tripCount . ' trips</strong> | First: <strong>' . htmlspecialchars($firstTrip['start_location']) . '</strong> &rarr; <strong>' . htmlspecialchars($firstTrip['end_location']) . '</strong></p>';
} else {
echo '<p>Route: <strong>' . htmlspecialchars($task['start_location']) . '</strong> &rarr; <strong>' . htmlspecialchars($task['end_location']) . '</strong></p>';
}
?>
</div>
<div class="time-badge">
Pending
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<!-- Tab 2: Scrape History -->
<div id="results-tab" class="tab-content">
<div class="card-title" style="border:none; margin-bottom:1rem; font-size:1rem; color:var(--text-muted);">
<span>Results submitted by Bot via POST request</span>
<?php if (count($scrapedResults) > 0): ?>
<form method="POST" style="margin: 0;">
<input type="hidden" name="action" value="clear_results">
<button type="submit" class="btn-secondary" id="clear-results-btn">Clear History</button>
</form>
<?php endif; ?>
</div>
<div class="list-container">
<?php if (count($scrapedResults) === 0): ?>
<div class="empty-state">No scraping results recorded yet. Results will appear here when the Android bot submits them.</div>
<?php else: ?>
<?php foreach ($scrapedResults as $res):
$data = $res['result_data'];
$app = $data['app'] ?? 'unknown';
if (strpos($app, 'yalla') !== false) {
$appLabel = 'yallago';
} elseif (strpos($app, 'zakinn') !== false || strpos($app, 'zaken') !== false) {
$appLabel = 'zaken';
} elseif (strpos($app, 'bis.taxi') !== false || strpos($app, 'tufaddal') !== false) {
$appLabel = 'tufaddal';
} elseif (strpos($app, 'careem') !== false) {
$appLabel = 'careem';
} elseif (strpos($app, 'ubercab') !== false || strpos($app, 'uber') !== false) {
$appLabel = 'uber';
} elseif (strpos($app, 'taxif') !== false) {
$appLabel = 'taxif';
} elseif (strpos($app, 'easytaxi') !== false || strpos($app, 'jeeny') !== false) {
$appLabel = 'jeeny';
} else {
$appLabel = 'unknown';
}
$isSuccess = ($res['status'] === 'success');
$price = $data['price'] ?? 0;
$dist = $data['distance_km'] ?? 0;
?>
<div class="list-item <?php echo $isSuccess ? 'result-success' : 'result-failed'; ?>">
<div class="task-info">
<h4>
<span class="badge-app app-<?php echo $appLabel; ?>"><?php echo htmlspecialchars($appLabel); ?></span>
<span>Task ID: <?php echo htmlspecialchars($res['task_id']); ?></span>
</h4>
<p>Device: <?php echo htmlspecialchars($res['device_id']); ?></p>
<p>Distance: <strong><?php echo number_format($dist, 2); ?> km</strong> | Time: <?php echo htmlspecialchars($res['recorded_at']); ?></p>
</div>
<div class="price-text">
<?php if ($isSuccess): ?>
<?php
$currency = (in_array($appLabel, ['careem', 'uber', 'taxif', 'jeeny'])) ? 'JOD' : 'SYP';
$decimals = ($currency === 'JOD') ? 2 : 0;
echo number_format($price, $decimals) . ' ' . $currency;
?>
<?php else: ?>
<span style="color:var(--error-color); font-size:0.9rem;">Scrape Failed</span>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<script>
function switchTab(tabId) {
// Remove active classes
document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
// Add active class to selected tab button
const buttons = document.querySelectorAll('.tab-btn');
if (tabId === 'tasks') {
buttons[0].classList.add('active');
document.getElementById('tasks-tab').classList.add('active');
} else {
buttons[1].classList.add('active');
document.getElementById('results-tab').classList.add('active');
}
}
</script>
</body>
</html>