Update: 2026-06-21 02:53:01
This commit is contained in:
110
backend/bot/generate_price_tasks.php
Normal file
110
backend/bot/generate_price_tasks.php
Normal 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
166
backend/bot/worker.php
Normal 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");
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user