Update: 2026-06-21 02:53:01

This commit is contained in:
Hamza-Ayed
2026-06-21 02:53:02 +03:00
parent b2fae9ec66
commit 2ac086d1fd
3 changed files with 276 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
<?php
// =========================================================
// backend/bot/generate_price_tasks.php
// Cron Job: Runs every 15 minutes
// Generates random Short & Long trips in Damascus
// =========================================================
// CLI or Web execution
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
try {
$con = Database::get('main');
} catch (Exception $e) {
die("Database connection failed\n");
}
// 1. Ensure Table Exists
$sql = "
CREATE TABLE IF NOT EXISTS competitor_prices (
id INT AUTO_INCREMENT PRIMARY KEY,
app_name VARCHAR(50) NOT NULL,
start_lat DECIMAL(10,8) NOT NULL,
start_lng DECIMAL(11,8) NOT NULL,
end_lat DECIMAL(10,8) NOT NULL,
end_lng DECIMAL(11,8) NOT NULL,
distance_km FLOAT NOT NULL,
price FLOAT NOT NULL,
recorded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_app_time (app_name, recorded_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
";
$con->exec($sql);
// 2. Ten Key Regions in Damascus
$regions = [
['name' => 'Umayyad Square', 'lat' => 33.5138, 'lng' => 36.2765],
['name' => 'Mezzeh', 'lat' => 33.5074, 'lng' => 36.2530],
['name' => 'Malki', 'lat' => 33.5220, 'lng' => 36.2840],
['name' => 'Kafersouseh', 'lat' => 33.4981, 'lng' => 36.2730],
['name' => 'Al-Midan', 'lat' => 33.4947, 'lng' => 36.2995],
['name' => 'Bab Tuma', 'lat' => 33.5126, 'lng' => 36.3150],
['name' => 'Rukneddine', 'lat' => 33.5350, 'lng' => 36.2950],
['name' => 'Dummar', 'lat' => 33.5385, 'lng' => 36.2250],
['name' => 'Baramkeh', 'lat' => 33.5100, 'lng' => 36.2885],
['name' => 'Muhajireen', 'lat' => 33.5320, 'lng' => 36.2720],
];
$competitors = ['yallago', 'zaken', 'tufaddal'];
// Helper to generate a random point within a radius (in km)
function generateRandomPoint($lat, $lng, $radius) {
$radiusInDegrees = $radius / 111.0; // 1 degree is ~111km
$u = lcg_value();
$v = lcg_value();
$w = $radiusInDegrees * sqrt($u);
$t = 2 * pi() * $v;
$x = $w * cos($t);
$y = $w * sin($t);
// Adjust longitude based on latitude
$new_lng = $x / cos(deg2rad($lat));
$new_lat = $y;
return [
'lat' => $lat + $new_lat,
'lng' => $lng + $new_lng
];
}
$tasksCreated = 0;
foreach ($regions as $region) {
// A. Generate Start Point (within 2km of region center)
$start = generateRandomPoint($region['lat'], $region['lng'], 2);
// B. Generate Short Trip (2-5 km from start)
$shortDist = rand(20, 50) / 10.0;
$shortEnd = generateRandomPoint($start['lat'], $start['lng'], $shortDist);
// C. Generate Long Trip (10-15 km from start)
$longDist = rand(100, 150) / 10.0;
$longEnd = generateRandomPoint($start['lat'], $start['lng'], $longDist);
$trips = [$shortEnd, $longEnd];
foreach ($trips as $end) {
foreach ($competitors as $app) {
$taskId = "prc_" . uniqid();
$taskData = [
"task_id" => $taskId,
"type" => "price_check",
"app" => $app,
"payload" => [
"start_lat" => $start['lat'],
"start_lng" => $start['lng'],
"end_lat" => $end['lat'],
"end_lng" => $end['lng']
]
];
// Push to Redis Queue
$redis->lpush('queue:bot:tasks', json_encode($taskData));
$tasksCreated++;
}
}
}
echo "Successfully generated and queued $tasksCreated pricing tasks.\n";

166
backend/bot/worker.php Normal file
View File

@@ -0,0 +1,166 @@
<?php
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
try {
$con = Database::get('main');
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['status' => 'failure', 'message' => 'Database connection failed']);
exit;
}
// =========================================================
// backend/bot/worker.php
// Endpoint for the standalone Android Bot (Worker Node)
// Handles Wallet Payments (ShamCash) and Competitor Pricing
// =========================================================
// 1. Security & Configuration
$SECRET_KEY = getenv('BOT_SECRET_KEY') ?: 'SIRO_BOT_SUPER_SECRET_123';
$ALLOWED_DEVICES = ['SHAM_CASH_BOT_01', 'PRICE_SCRAPER_BOT_01'];
$method = $_SERVER['REQUEST_METHOD'];
// 2. Validate Security (HMAC-SHA256 Signature)
function validateSignature($device_id, $ts, $sig, $secret_key) {
// Prevent replay attacks (valid for 5 minutes)
if (abs(time() - $ts) > 300) {
return false;
}
// Generate the expected signature
$expected_sig = hash_hmac('sha256', $device_id . $ts, $secret_key);
// Secure comparison to prevent timing attacks
return hash_equals($expected_sig, $sig);
}
if ($method === 'GET') {
// ---------------------------------------------------------
// GET: Fetch Pending Task for the Bot
// ---------------------------------------------------------
$device_id = filterRequest('device_id');
$ts = filterRequest('ts');
$sig = filterRequest('sig');
if (!$device_id || !$ts || !$sig) {
jsonError("Missing security parameters");
exit;
}
if (!in_array($device_id, $ALLOWED_DEVICES)) {
jsonError("Device not authorized: " . $device_id);
exit;
}
if (!validateSignature($device_id, $ts, $sig, $SECRET_KEY)) {
jsonError("Invalid signature or expired timestamp");
exit;
}
// Check Redis Queue for tasks
// Queue Name: queue:bot:tasks (Using Main Redis)
$taskJson = $redis->rpop('queue:bot:tasks');
if ($taskJson) {
$task = json_decode($taskJson, true);
echo json_encode([
"status" => "success",
"has_task" => true,
"task" => $task
]);
} else {
echo json_encode([
"status" => "success",
"has_task" => false
]);
}
exit;
} elseif ($method === 'POST') {
// ---------------------------------------------------------
// POST: Submit Result from the Bot
// ---------------------------------------------------------
// Read raw JSON body
$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
jsonError("Invalid JSON body");
exit;
}
$device_id = $input['device_id'] ?? null;
$ts = $input['ts'] ?? null;
$sig = $input['sig'] ?? null;
$task_id = $input['task_id'] ?? null;
$task_status = $input['status'] ?? null; // 'success' or 'failed'
$result_data = $input['result_data'] ?? [];
if (!$device_id || !$ts || !$sig || !$task_id || !$task_status) {
jsonError("Missing required parameters");
exit;
}
if (!in_array($device_id, $ALLOWED_DEVICES)) {
jsonError("Device not authorized");
exit;
}
if (!validateSignature($device_id, $ts, $sig, $SECRET_KEY)) {
jsonError("Invalid signature or expired timestamp");
exit;
}
$task_type = $input['type'] ?? 'payment';
// Process the result based on task type
if ($task_status === 'success') {
if ($task_type === 'price_check') {
$app_name = $result_data['app'] ?? 'unknown';
$price = (float)($result_data['price'] ?? 0);
$distance_km = (float)($result_data['distance_km'] ?? 0);
$start_lat = (float)($result_data['start_lat'] ?? 0);
$start_lng = (float)($result_data['start_lng'] ?? 0);
$end_lat = (float)($result_data['end_lat'] ?? 0);
$end_lng = (float)($result_data['end_lng'] ?? 0);
// 1. Save to MySQL
$stmt = $con->prepare("
INSERT INTO competitor_prices
(app_name, start_lat, start_lng, end_lat, end_lng, distance_km, price)
VALUES (?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([$app_name, $start_lat, $start_lng, $end_lat, $end_lng, $distance_km, $price]);
// 2. Save to Redis (Calculate Price Per KM)
if ($distance_km > 0 && $price > 0) {
$pricePerKm = $price / $distance_km;
// Store in Redis (Main) to be used by Pricing Engine
// Store recent 50 prices for the app
$redis->lpush("competitor:price_history:$app_name", $pricePerKm);
$redis->ltrim("competitor:price_history:$app_name", 0, 49);
error_log("[Bot Worker] Price Check $app_name: Dist $distance_km, Price $price");
}
} else {
// It's a payment task
$transaction_id = $result_data['transaction_id'] ?? 'N/A';
// TODO: Update MySQL driver balance/payout status
// $stmt = $con->prepare("UPDATE payouts SET status = 'paid', transaction_ref = ? WHERE task_id = ?");
// $stmt->execute([$transaction_id, $task_id]);
error_log("[Bot Worker] Task $task_id SUCCESS on $device_id. Ref: $transaction_id");
}
} else {
$error_msg = $result_data['error'] ?? 'Unknown Error';
error_log("[Bot Worker] Task $task_id FAILED on $device_id. Reason: $error_msg");
// Optional: Re-queue the task if it failed due to a temporary issue
}
echo json_encode(["status" => "success", "message" => "Result recorded successfully"]);
exit;
} else {
http_response_code(405);
jsonError("Method Not Allowed");
}