diff --git a/backend/bot/generate_price_tasks.php b/backend/bot/generate_price_tasks.php new file mode 100644 index 0000000..df9a514 --- /dev/null +++ b/backend/bot/generate_price_tasks.php @@ -0,0 +1,110 @@ +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"; diff --git a/backend/bot/worker.php b/backend/bot/worker.php new file mode 100644 index 0000000..5d8f807 --- /dev/null +++ b/backend/bot/worker.php @@ -0,0 +1,166 @@ + '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"); +} diff --git a/دراسة_الجدوى_سيرو_الإصدار_الثالث.docx b/دراسة_الجدوى_سيرو_الإصدار_الثالث.docx deleted file mode 100644 index dae74c6..0000000 Binary files a/دراسة_الجدوى_سيرو_الإصدار_الثالث.docx and /dev/null differ