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");
|
||||
}
|
||||
Reference in New Issue
Block a user